1#![allow(clippy::not_unsafe_ptr_arg_deref)]
4
5use std::collections::BTreeMap;
6use std::ffi::{CStr, CString, c_char, c_float, c_int, c_uchar, c_ulonglong};
7use std::ptr;
8use std::sync::{Arc, Mutex, OnceLock};
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value as JsonValue;
12use tokio::runtime::Runtime;
13use vldb_controller_client::{
14 ClientRegistration, ControllerClient, ControllerClientConfig, ControllerLanceDbColumnDef,
15 ControllerLanceDbColumnType, ControllerLanceDbCreateTableResult, ControllerLanceDbDeleteResult,
16 ControllerLanceDbDropTableResult, ControllerLanceDbEnableRequest, ControllerLanceDbInputFormat,
17 ControllerLanceDbOutputFormat, ControllerLanceDbSearchResult, ControllerLanceDbUpsertResult,
18 ControllerProcessMode, ControllerSqliteCustomWordEntry,
19 ControllerSqliteDictionaryMutationResult, ControllerSqliteEnableRequest,
20 ControllerSqliteEnsureFtsIndexResult, ControllerSqliteExecuteBatchResult,
21 ControllerSqliteExecuteResult, ControllerSqliteFtsMutationResult,
22 ControllerSqliteListCustomWordsResult, ControllerSqliteQueryResult,
23 ControllerSqliteRebuildFtsIndexResult, ControllerSqliteSearchFtsHit,
24 ControllerSqliteSearchFtsResult, ControllerSqliteTokenizeResult, ControllerSqliteTokenizerMode,
25 ControllerSqliteValue, ControllerStatusSnapshot, SpaceBackendStatus, SpaceKind,
26 SpaceRegistration, SpaceSnapshot,
27};
28
29const FFI_STATUS_OK: c_int = 0;
32
33const FFI_STATUS_ERR: c_int = 1;
36
37static FFI_CLIENT_HANDLE_REGISTRY: OnceLock<
40 Mutex<BTreeMap<usize, Arc<FfiControllerClientHandleState>>>,
41> = OnceLock::new();
42
43pub struct FfiControllerClientHandle {
58 _private: u8,
59}
60
61struct FfiControllerClientHandleInner {
64 runtime: Runtime,
65 client: ControllerClient,
66}
67
68struct FfiControllerClientHandleState {
71 inner: Mutex<Option<FfiControllerClientHandleInner>>,
72}
73
74#[repr(C)]
77pub struct FfiControllerClientConfig {
78 pub endpoint: *const c_char,
79 pub auto_spawn: c_uchar,
80 pub spawn_executable: *const c_char,
81 pub spawn_process_mode: c_int,
82 pub minimum_uptime_secs: c_ulonglong,
83 pub idle_timeout_secs: c_ulonglong,
84 pub default_lease_ttl_secs: c_ulonglong,
85 pub connect_timeout_secs: c_ulonglong,
86 pub startup_timeout_secs: c_ulonglong,
87 pub startup_retry_interval_ms: c_ulonglong,
88 pub lease_renew_interval_secs: c_ulonglong,
89}
90
91#[repr(C)]
94pub struct FfiClientRegistration {
95 pub client_name: *const c_char,
96 pub host_kind: *const c_char,
97 pub process_id: u32,
98 pub process_name: *const c_char,
99 pub lease_ttl_secs: c_ulonglong,
100}
101
102#[repr(C)]
105pub struct FfiSpaceRegistration {
106 pub space_id: *const c_char,
107 pub space_label: *const c_char,
108 pub space_kind: c_int,
109 pub space_root: *const c_char,
110}
111
112#[repr(C)]
115pub struct FfiControllerSqliteEnableRequest {
116 pub space_id: *const c_char,
117 pub db_path: *const c_char,
118 pub connection_pool_size: c_ulonglong,
119 pub busy_timeout_ms: c_ulonglong,
120 pub journal_mode: *const c_char,
121 pub synchronous: *const c_char,
122 pub foreign_keys: c_uchar,
123 pub temp_store: *const c_char,
124 pub wal_autocheckpoint_pages: u32,
125 pub cache_size_kib: i64,
126 pub mmap_size_bytes: c_ulonglong,
127 pub enforce_db_file_lock: c_uchar,
128 pub read_only: c_uchar,
129 pub allow_uri_filenames: c_uchar,
130 pub trusted_schema: c_uchar,
131 pub defensive: c_uchar,
132}
133
134#[repr(C)]
137pub struct FfiControllerLanceDbEnableRequest {
138 pub space_id: *const c_char,
139 pub default_db_path: *const c_char,
140 pub db_root: *const c_char,
141 pub read_consistency_interval_ms: c_ulonglong,
142 pub max_upsert_payload: c_ulonglong,
143 pub max_search_limit: c_ulonglong,
144 pub max_concurrent_requests: c_ulonglong,
145}
146
147#[repr(C)]
150pub struct FfiSpaceBackendStatus {
151 pub enabled: c_uchar,
152 pub mode: *mut c_char,
153 pub target: *mut c_char,
154}
155
156#[repr(C)]
159pub struct FfiSpaceSnapshot {
160 pub space_id: *mut c_char,
161 pub space_label: *mut c_char,
162 pub space_kind: c_int,
163 pub space_root: *mut c_char,
164 pub attached_clients: c_ulonglong,
165 pub sqlite: *mut FfiSpaceBackendStatus,
166 pub lancedb: *mut FfiSpaceBackendStatus,
167}
168
169#[repr(C)]
172pub struct FfiSpaceSnapshotArray {
173 pub items: *mut FfiSpaceSnapshot,
174 pub len: usize,
175}
176
177#[repr(C)]
180pub struct FfiControllerStatusSnapshot {
181 pub process_mode: c_int,
182 pub bind_addr: *mut c_char,
183 pub started_at_unix_ms: c_ulonglong,
184 pub last_request_at_unix_ms: c_ulonglong,
185 pub minimum_uptime_secs: c_ulonglong,
186 pub idle_timeout_secs: c_ulonglong,
187 pub default_lease_ttl_secs: c_ulonglong,
188 pub active_clients: c_ulonglong,
189 pub attached_spaces: c_ulonglong,
190 pub inflight_requests: c_ulonglong,
191 pub shutdown_candidate: c_uchar,
192}
193
194#[repr(C)]
197pub struct FfiControllerSqliteExecuteResult {
198 pub success: c_uchar,
199 pub message: *mut c_char,
200 pub rows_changed: i64,
201 pub last_insert_rowid: i64,
202}
203
204#[repr(C)]
207pub struct FfiControllerSqliteQueryResult {
208 pub json_data: *mut c_char,
209 pub row_count: c_ulonglong,
210}
211
212#[repr(C)]
215pub struct FfiByteBuffer {
216 pub data: *mut u8,
217 pub len: usize,
218}
219
220#[repr(C)]
223pub struct FfiByteBufferArray {
224 pub items: *mut FfiByteBuffer,
225 pub len: usize,
226}
227
228#[repr(C)]
231pub struct FfiStringArray {
232 pub items: *const *const c_char,
233 pub len: usize,
234}
235
236#[repr(C)]
239pub struct FfiSqliteValue {
240 pub kind: c_int,
241 pub int64_value: i64,
242 pub float64_value: f64,
243 pub string_value: *const c_char,
244 pub bytes_value: *const u8,
245 pub bytes_len: usize,
246 pub bool_value: c_uchar,
247}
248
249#[repr(C)]
252pub struct FfiSqliteBatchItem {
253 pub params: *const FfiSqliteValue,
254 pub params_len: usize,
255}
256
257#[repr(C)]
260pub struct FfiControllerSqliteExecuteBatchResult {
261 pub success: c_uchar,
262 pub message: *mut c_char,
263 pub rows_changed: i64,
264 pub last_insert_rowid: i64,
265 pub statements_executed: i64,
266}
267
268#[repr(C)]
271pub struct FfiControllerSqliteQueryStreamResult {
272 pub chunks: *mut FfiByteBufferArray,
273 pub row_count: c_ulonglong,
274 pub chunk_count: c_ulonglong,
275 pub total_bytes: c_ulonglong,
276}
277
278#[repr(C)]
281pub struct FfiControllerSqliteTokenizeResult {
282 pub tokenizer_mode: *mut c_char,
283 pub normalized_text: *mut c_char,
284 pub tokens_json: *mut c_char,
285 pub fts_query: *mut c_char,
286}
287
288#[repr(C)]
291pub struct FfiControllerSqliteCustomWordEntry {
292 pub word: *mut c_char,
293 pub weight: c_ulonglong,
294}
295
296#[repr(C)]
299pub struct FfiControllerSqliteCustomWordArray {
300 pub items: *mut FfiControllerSqliteCustomWordEntry,
301 pub len: usize,
302}
303
304#[repr(C)]
307pub struct FfiControllerSqliteDictionaryMutationResult {
308 pub success: c_uchar,
309 pub message: *mut c_char,
310 pub affected_rows: c_ulonglong,
311}
312
313#[repr(C)]
316pub struct FfiControllerSqliteListCustomWordsResult {
317 pub success: c_uchar,
318 pub message: *mut c_char,
319 pub words: *mut FfiControllerSqliteCustomWordArray,
320}
321
322#[repr(C)]
325pub struct FfiControllerSqliteEnsureFtsIndexResult {
326 pub success: c_uchar,
327 pub message: *mut c_char,
328 pub index_name: *mut c_char,
329 pub tokenizer_mode: *mut c_char,
330}
331
332#[repr(C)]
335pub struct FfiControllerSqliteRebuildFtsIndexResult {
336 pub success: c_uchar,
337 pub message: *mut c_char,
338 pub index_name: *mut c_char,
339 pub tokenizer_mode: *mut c_char,
340 pub reindexed_rows: c_ulonglong,
341}
342
343#[repr(C)]
346pub struct FfiControllerSqliteFtsMutationResult {
347 pub success: c_uchar,
348 pub message: *mut c_char,
349 pub affected_rows: c_ulonglong,
350 pub index_name: *mut c_char,
351}
352
353#[repr(C)]
356pub struct FfiControllerSqliteSearchFtsHit {
357 pub id: *mut c_char,
358 pub file_path: *mut c_char,
359 pub title: *mut c_char,
360 pub title_highlight: *mut c_char,
361 pub content_snippet: *mut c_char,
362 pub score: f64,
363 pub rank: c_ulonglong,
364 pub raw_score: f64,
365}
366
367#[repr(C)]
370pub struct FfiControllerSqliteSearchFtsHitArray {
371 pub items: *mut FfiControllerSqliteSearchFtsHit,
372 pub len: usize,
373}
374
375#[repr(C)]
378pub struct FfiControllerSqliteSearchFtsResult {
379 pub success: c_uchar,
380 pub message: *mut c_char,
381 pub index_name: *mut c_char,
382 pub tokenizer_mode: *mut c_char,
383 pub normalized_query: *mut c_char,
384 pub fts_query: *mut c_char,
385 pub source: *mut c_char,
386 pub query_mode: *mut c_char,
387 pub total: c_ulonglong,
388 pub hits: *mut FfiControllerSqliteSearchFtsHitArray,
389}
390
391#[repr(C)]
394pub struct FfiControllerLanceDbCreateTableResult {
395 pub message: *mut c_char,
396}
397
398#[repr(C)]
401pub struct FfiControllerLanceDbUpsertResult {
402 pub message: *mut c_char,
403 pub version: c_ulonglong,
404 pub input_rows: c_ulonglong,
405 pub inserted_rows: c_ulonglong,
406 pub updated_rows: c_ulonglong,
407 pub deleted_rows: c_ulonglong,
408}
409
410#[repr(C)]
413pub struct FfiControllerLanceDbSearchResult {
414 pub message: *mut c_char,
415 pub format: *mut c_char,
416 pub rows: c_ulonglong,
417 pub data: *mut u8,
418 pub data_len: usize,
419}
420
421#[repr(C)]
424pub struct FfiControllerLanceDbDeleteResult {
425 pub message: *mut c_char,
426 pub version: c_ulonglong,
427 pub deleted_rows: c_ulonglong,
428}
429
430#[repr(C)]
433pub struct FfiControllerLanceDbDropTableResult {
434 pub message: *mut c_char,
435}
436
437#[repr(C)]
440pub struct FfiControllerLanceDbColumnDef {
441 pub name: *const c_char,
442 pub column_type: c_int,
443 pub vector_dim: u32,
444 pub nullable: c_uchar,
445}
446
447#[derive(Deserialize)]
450struct CreateClientJsonRequest {
451 config: ControllerClientConfig,
452 registration: ClientRegistration,
453}
454
455#[derive(Default, Deserialize)]
458struct EmptyJsonRequest {}
459
460#[derive(Deserialize)]
463struct AttachSpaceJsonRequest {
464 registration: SpaceRegistration,
465}
466
467#[derive(Deserialize)]
470struct DetachSpaceJsonRequest {
471 space_id: String,
472}
473
474#[derive(Deserialize)]
477struct EnableSqliteJsonRequest {
478 request: ControllerSqliteEnableRequest,
479}
480
481#[derive(Deserialize)]
484struct DisableBackendJsonRequest {
485 space_id: String,
486}
487
488#[derive(Deserialize)]
491struct ExecuteSqliteScriptJsonRequest {
492 space_id: String,
493 sql: String,
494 #[serde(default)]
495 params: Vec<JsonValue>,
496}
497
498#[derive(Deserialize)]
501struct QuerySqliteJsonJsonRequest {
502 space_id: String,
503 sql: String,
504 #[serde(default)]
505 params: Vec<JsonValue>,
506}
507
508#[derive(Deserialize)]
511struct ExecuteSqliteBatchJsonRequest {
512 space_id: String,
513 sql: String,
514 #[serde(default)]
515 batch_params: Vec<Vec<JsonValue>>,
516}
517
518#[derive(Deserialize)]
521struct QuerySqliteStreamJsonRequest {
522 space_id: String,
523 sql: String,
524 #[serde(default)]
525 params: Vec<JsonValue>,
526 target_chunk_size: Option<u64>,
527}
528
529#[derive(Deserialize)]
532struct TokenizeSqliteTextJsonRequest {
533 space_id: String,
534 tokenizer_mode: String,
535 text: String,
536 search_mode: bool,
537}
538
539#[derive(Deserialize)]
542struct ListSqliteCustomWordsJsonRequest {
543 space_id: String,
544}
545
546#[derive(Deserialize)]
549struct UpsertSqliteCustomWordJsonRequest {
550 space_id: String,
551 word: String,
552 weight: u32,
553}
554
555#[derive(Deserialize)]
558struct RemoveSqliteCustomWordJsonRequest {
559 space_id: String,
560 word: String,
561}
562
563#[derive(Deserialize)]
566struct EnsureSqliteFtsIndexJsonRequest {
567 space_id: String,
568 index_name: String,
569 tokenizer_mode: String,
570}
571
572#[derive(Deserialize)]
575struct RebuildSqliteFtsIndexJsonRequest {
576 space_id: String,
577 index_name: String,
578 tokenizer_mode: String,
579}
580
581#[derive(Deserialize)]
584struct UpsertSqliteFtsDocumentJsonRequest {
585 space_id: String,
586 index_name: String,
587 tokenizer_mode: String,
588 id: String,
589 file_path: String,
590 title: String,
591 content: String,
592}
593
594#[derive(Deserialize)]
597struct DeleteSqliteFtsDocumentJsonRequest {
598 space_id: String,
599 index_name: String,
600 id: String,
601}
602
603#[derive(Deserialize)]
606struct SearchSqliteFtsJsonRequest {
607 space_id: String,
608 index_name: String,
609 tokenizer_mode: String,
610 query: String,
611 limit: u32,
612 offset: u32,
613}
614
615#[derive(Deserialize)]
618struct EnableLanceDbJsonRequest {
619 request: ControllerLanceDbEnableRequest,
620}
621
622#[derive(Deserialize)]
625struct CreateLanceDbTableJsonRequest {
626 space_id: String,
627 table_name: String,
628 columns: Vec<CreateLanceDbTableJsonColumn>,
629 #[serde(default)]
630 overwrite_if_exists: bool,
631}
632
633#[derive(Deserialize, Serialize)]
636struct CreateLanceDbTableJsonColumn {
637 name: String,
638 column_type: String,
639 #[serde(default)]
640 vector_dim: u32,
641 #[serde(default = "default_nullable")]
642 nullable: bool,
643}
644
645#[derive(Deserialize)]
648struct UpsertLanceDbJsonRequest {
649 space_id: String,
650 table_name: String,
651 input_format: String,
652 #[serde(default)]
653 key_columns: Vec<String>,
654 data_base64: String,
655}
656
657#[derive(Deserialize)]
660struct SearchLanceDbJsonRequest {
661 space_id: String,
662 table_name: String,
663 vector: Vec<f32>,
664 #[serde(default = "default_search_limit")]
665 limit: u32,
666 #[serde(default)]
667 filter: String,
668 #[serde(default)]
669 vector_column: String,
670 #[serde(default)]
671 output_format: String,
672}
673
674#[derive(Deserialize)]
677struct DeleteLanceDbJsonRequest {
678 space_id: String,
679 table_name: String,
680 condition: String,
681}
682
683#[derive(Deserialize)]
686struct JsonSqliteBytesValue {
687 #[serde(default)]
688 r#type: String,
689 #[serde(default)]
690 __type: String,
691 base64: String,
692}
693
694#[derive(Serialize)]
697struct CreateLanceDbTableJsonCompatRequest {
698 table_name: String,
699 columns: Vec<CreateLanceDbTableJsonColumn>,
700 overwrite_if_exists: bool,
701}
702
703impl From<CreateLanceDbTableJsonRequest> for CreateLanceDbTableJsonCompatRequest {
704 fn from(value: CreateLanceDbTableJsonRequest) -> Self {
705 Self {
706 table_name: value.table_name,
707 columns: value.columns,
708 overwrite_if_exists: value.overwrite_if_exists,
709 }
710 }
711}
712
713#[derive(Serialize)]
716struct UpsertLanceDbJsonCompatRequest {
717 table_name: String,
718 input_format: String,
719 key_columns: Vec<String>,
720}
721
722impl From<&UpsertLanceDbJsonRequest> for UpsertLanceDbJsonCompatRequest {
723 fn from(value: &UpsertLanceDbJsonRequest) -> Self {
724 Self {
725 table_name: value.table_name.clone(),
726 input_format: value.input_format.clone(),
727 key_columns: value.key_columns.clone(),
728 }
729 }
730}
731
732#[derive(Serialize)]
735struct SearchLanceDbJsonCompatRequest {
736 table_name: String,
737 vector: Vec<f32>,
738 limit: u32,
739 filter: String,
740 vector_column: String,
741 output_format: String,
742}
743
744impl From<&SearchLanceDbJsonRequest> for SearchLanceDbJsonCompatRequest {
745 fn from(value: &SearchLanceDbJsonRequest) -> Self {
746 Self {
747 table_name: value.table_name.clone(),
748 vector: value.vector.clone(),
749 limit: value.limit,
750 filter: value.filter.clone(),
751 vector_column: value.vector_column.clone(),
752 output_format: value.output_format.clone(),
753 }
754 }
755}
756
757#[derive(Serialize)]
760struct DeleteLanceDbJsonCompatRequest {
761 table_name: String,
762 condition: String,
763}
764
765impl From<&DeleteLanceDbJsonRequest> for DeleteLanceDbJsonCompatRequest {
766 fn from(value: &DeleteLanceDbJsonRequest) -> Self {
767 Self {
768 table_name: value.table_name.clone(),
769 condition: value.condition.clone(),
770 }
771 }
772}
773
774#[derive(Deserialize)]
777struct DropLanceDbTableJsonRequest {
778 space_id: String,
779 table_name: String,
780}
781
782#[derive(Serialize)]
785struct SuccessJsonResponse {
786 ok: bool,
787}
788
789#[derive(Serialize)]
792struct QuerySqliteStreamJsonResponse {
793 chunks_base64: Vec<String>,
794 row_count: u64,
795 chunk_count: u64,
796 total_bytes: u64,
797}
798
799struct ControllerSqliteQueryStreamCompatResult {
802 chunks: Vec<Vec<u8>>,
803 row_count: u64,
804 chunk_count: u64,
805 total_bytes: u64,
806}
807
808#[unsafe(no_mangle)]
811pub extern "C" fn vldb_controller_ffi_version() -> *mut c_char {
812 string_into_raw(env!("CARGO_PKG_VERSION").to_string())
813}
814
815#[unsafe(no_mangle)]
818pub extern "C" fn vldb_controller_ffi_string_free(value: *mut c_char) {
819 if !value.is_null() {
820 unsafe {
821 drop(CString::from_raw(value));
822 }
823 }
824}
825
826#[unsafe(no_mangle)]
829pub extern "C" fn vldb_controller_ffi_bytes_free(data: *mut u8, len: usize) {
830 if !data.is_null() {
831 unsafe {
832 drop(Vec::from_raw_parts(data, len, len));
833 }
834 }
835}
836
837#[unsafe(no_mangle)]
840pub extern "C" fn vldb_controller_ffi_client_create(
841 config: *const FfiControllerClientConfig,
842 registration: *const FfiClientRegistration,
843 client_out: *mut *mut FfiControllerClientHandle,
844 error_out: *mut *mut c_char,
845) -> c_int {
846 if client_out.is_null() {
847 return ffi_error_status(error_out, "client_out pointer must not be null");
848 }
849
850 clear_out_ptr(client_out);
851 match native_client_create(config, registration) {
852 Ok(handle) => {
853 write_out_ptr(client_out, handle);
854 ffi_ok_status(error_out)
855 }
856 Err(error) => ffi_error_status(error_out, error),
857 }
858}
859
860#[unsafe(no_mangle)]
863pub extern "C" fn vldb_controller_ffi_client_create_json(
864 request_json: *const c_char,
865 client_out: *mut *mut FfiControllerClientHandle,
866 response_out: *mut *mut c_char,
867 error_out: *mut *mut c_char,
868) -> c_int {
869 if client_out.is_null() {
870 return ffi_error_status(error_out, "client_out pointer must not be null");
871 }
872
873 clear_out_ptr(client_out);
874 clear_out_ptr(response_out);
875 let result = (|| -> Result<(*mut FfiControllerClientHandle, String), String> {
876 let request: CreateClientJsonRequest = parse_json_input(request_json)?;
877 let handle = build_client_handle(request.config, request.registration)?;
878 let response = serde_json::to_string(&SuccessJsonResponse { ok: true })
879 .map_err(|error| format!("failed to serialize create_client_json response: {error}"))?;
880 Ok((handle, response))
881 })();
882
883 match result {
884 Ok((handle, response)) => {
885 write_out_ptr(client_out, handle);
886 write_string_out(response_out, response);
887 ffi_ok_status(error_out)
888 }
889 Err(error) => ffi_error_status(error_out, error),
890 }
891}
892
893#[unsafe(no_mangle)]
896pub extern "C" fn vldb_controller_ffi_client_free(client: *mut FfiControllerClientHandle) {
897 let Ok(handle_id) = ffi_client_handle_id_from_ptr(client) else {
898 return;
899 };
900 let state = {
901 let mut registry = match ffi_client_handle_registry().lock() {
902 Ok(registry) => registry,
903 Err(poisoned) => poisoned.into_inner(),
904 };
905 registry.remove(&handle_id)
906 };
907 if let Some(state) = state {
908 let mut guard = match state.inner.lock() {
909 Ok(guard) => guard,
910 Err(poisoned) => poisoned.into_inner(),
911 };
912 if let Some(inner) = guard.take() {
913 let _ = inner.runtime.block_on(inner.client.shutdown());
914 }
915 unsafe {
916 drop(Box::from_raw(client));
917 }
918 }
919}
920
921#[unsafe(no_mangle)]
924pub extern "C" fn vldb_controller_ffi_client_connect(
925 client: *mut FfiControllerClientHandle,
926 error_out: *mut *mut c_char,
927) -> c_int {
928 let result = with_client_handle(client, |handle| {
929 handle
930 .runtime
931 .block_on(handle.client.connect())
932 .map_err(error_to_string)
933 });
934 match result {
935 Ok(()) => ffi_ok_status(error_out),
936 Err(error) => ffi_error_status(error_out, error),
937 }
938}
939
940#[unsafe(no_mangle)]
943pub extern "C" fn vldb_controller_ffi_client_connect_json(
944 client: *mut FfiControllerClientHandle,
945 request_json: *const c_char,
946 response_out: *mut *mut c_char,
947 error_out: *mut *mut c_char,
948) -> c_int {
949 clear_out_ptr(response_out);
950 let result = (|| -> Result<String, String> {
951 let _: EmptyJsonRequest = parse_json_input(request_json)?;
952 with_client_handle(client, |handle| {
953 handle
954 .runtime
955 .block_on(handle.client.connect())
956 .map_err(error_to_string)?;
957 serde_json::to_string(&SuccessJsonResponse { ok: true })
958 .map_err(|error| format!("failed to serialize connect_json response: {error}"))
959 })
960 })();
961 match result {
962 Ok(response) => {
963 write_string_out(response_out, response);
964 ffi_ok_status(error_out)
965 }
966 Err(error) => ffi_error_status(error_out, error),
967 }
968}
969
970#[unsafe(no_mangle)]
973pub extern "C" fn vldb_controller_ffi_client_shutdown(
974 client: *mut FfiControllerClientHandle,
975 error_out: *mut *mut c_char,
976) -> c_int {
977 let result = with_client_handle(client, |handle| {
978 handle
979 .runtime
980 .block_on(handle.client.shutdown())
981 .map_err(error_to_string)
982 });
983 match result {
984 Ok(()) => ffi_ok_status(error_out),
985 Err(error) => ffi_error_status(error_out, error),
986 }
987}
988
989#[unsafe(no_mangle)]
992pub extern "C" fn vldb_controller_ffi_client_shutdown_json(
993 client: *mut FfiControllerClientHandle,
994 request_json: *const c_char,
995 response_out: *mut *mut c_char,
996 error_out: *mut *mut c_char,
997) -> c_int {
998 clear_out_ptr(response_out);
999 let result = (|| -> Result<String, String> {
1000 let _: EmptyJsonRequest = parse_json_input(request_json)?;
1001 with_client_handle(client, |handle| {
1002 handle
1003 .runtime
1004 .block_on(handle.client.shutdown())
1005 .map_err(error_to_string)?;
1006 serde_json::to_string(&SuccessJsonResponse { ok: true })
1007 .map_err(|error| format!("failed to serialize shutdown_json response: {error}"))
1008 })
1009 })();
1010 match result {
1011 Ok(response) => {
1012 write_string_out(response_out, response);
1013 ffi_ok_status(error_out)
1014 }
1015 Err(error) => ffi_error_status(error_out, error),
1016 }
1017}
1018
1019#[unsafe(no_mangle)]
1022pub extern "C" fn vldb_controller_ffi_client_get_status(
1023 client: *mut FfiControllerClientHandle,
1024 status_out: *mut *mut FfiControllerStatusSnapshot,
1025 error_out: *mut *mut c_char,
1026) -> c_int {
1027 clear_out_ptr(status_out);
1028 let result = with_client_handle(client, |handle| {
1029 handle
1030 .runtime
1031 .block_on(handle.client.get_status())
1032 .map_err(error_to_string)
1033 });
1034 match result {
1035 Ok(snapshot) => {
1036 write_boxed_out_ptr(status_out, map_status_snapshot(snapshot));
1037 ffi_ok_status(error_out)
1038 }
1039 Err(error) => ffi_error_status(error_out, error),
1040 }
1041}
1042
1043#[unsafe(no_mangle)]
1046pub extern "C" fn vldb_controller_ffi_client_get_status_json(
1047 client: *mut FfiControllerClientHandle,
1048 response_out: *mut *mut c_char,
1049 error_out: *mut *mut c_char,
1050) -> c_int {
1051 clear_out_ptr(response_out);
1052 let result = with_client_handle(client, |handle| {
1053 let snapshot = handle
1054 .runtime
1055 .block_on(handle.client.get_status())
1056 .map_err(error_to_string)?;
1057 serde_json::to_string(&snapshot)
1058 .map_err(|error| format!("failed to serialize get_status_json response: {error}"))
1059 });
1060 match result {
1061 Ok(response) => {
1062 write_string_out(response_out, response);
1063 ffi_ok_status(error_out)
1064 }
1065 Err(error) => ffi_error_status(error_out, error),
1066 }
1067}
1068
1069#[unsafe(no_mangle)]
1072pub extern "C" fn vldb_controller_ffi_controller_status_free(
1073 status: *mut FfiControllerStatusSnapshot,
1074) {
1075 if !status.is_null() {
1076 unsafe {
1077 let status = Box::from_raw(status);
1078 vldb_controller_ffi_string_free(status.bind_addr);
1079 }
1080 }
1081}
1082
1083#[unsafe(no_mangle)]
1086pub extern "C" fn vldb_controller_ffi_client_attach_space(
1087 client: *mut FfiControllerClientHandle,
1088 registration: *const FfiSpaceRegistration,
1089 space_out: *mut *mut FfiSpaceSnapshot,
1090 error_out: *mut *mut c_char,
1091) -> c_int {
1092 clear_out_ptr(space_out);
1093 let result = (|| -> Result<SpaceSnapshot, String> {
1094 let registration = ffi_space_registration_to_rust(registration)?;
1095 with_client_handle(client, |handle| {
1096 handle
1097 .runtime
1098 .block_on(handle.client.attach_space(registration))
1099 .map_err(error_to_string)
1100 })
1101 })();
1102 match result {
1103 Ok(snapshot) => {
1104 write_boxed_out_ptr(space_out, map_space_snapshot(snapshot));
1105 ffi_ok_status(error_out)
1106 }
1107 Err(error) => ffi_error_status(error_out, error),
1108 }
1109}
1110
1111#[unsafe(no_mangle)]
1114pub extern "C" fn vldb_controller_ffi_client_attach_space_json(
1115 client: *mut FfiControllerClientHandle,
1116 request_json: *const c_char,
1117 response_out: *mut *mut c_char,
1118 error_out: *mut *mut c_char,
1119) -> c_int {
1120 clear_out_ptr(response_out);
1121 let result = (|| -> Result<String, String> {
1122 let request: AttachSpaceJsonRequest = parse_json_input(request_json)?;
1123 let snapshot = with_client_handle(client, |handle| {
1124 handle
1125 .runtime
1126 .block_on(handle.client.attach_space(request.registration))
1127 .map_err(error_to_string)
1128 })?;
1129 serde_json::to_string(&snapshot)
1130 .map_err(|error| format!("failed to serialize attach_space_json response: {error}"))
1131 })();
1132 match result {
1133 Ok(response) => {
1134 write_string_out(response_out, response);
1135 ffi_ok_status(error_out)
1136 }
1137 Err(error) => ffi_error_status(error_out, error),
1138 }
1139}
1140
1141#[unsafe(no_mangle)]
1144pub extern "C" fn vldb_controller_ffi_client_detach_space(
1145 client: *mut FfiControllerClientHandle,
1146 space_id: *const c_char,
1147 detached_out: *mut c_uchar,
1148 error_out: *mut *mut c_char,
1149) -> c_int {
1150 clear_out_u8(detached_out);
1151 let result = (|| -> Result<bool, String> {
1152 let space_id = required_c_string(space_id, "space_id")?;
1153 with_client_handle(client, |handle| {
1154 handle
1155 .runtime
1156 .block_on(handle.client.detach_space(space_id))
1157 .map_err(error_to_string)
1158 })
1159 })();
1160 match result {
1161 Ok(detached) => {
1162 write_out_u8(detached_out, if detached { 1 } else { 0 });
1163 ffi_ok_status(error_out)
1164 }
1165 Err(error) => ffi_error_status(error_out, error),
1166 }
1167}
1168
1169#[unsafe(no_mangle)]
1172pub extern "C" fn vldb_controller_ffi_client_detach_space_json(
1173 client: *mut FfiControllerClientHandle,
1174 request_json: *const c_char,
1175 response_out: *mut *mut c_char,
1176 error_out: *mut *mut c_char,
1177) -> c_int {
1178 clear_out_ptr(response_out);
1179 let result = (|| -> Result<String, String> {
1180 let request: DetachSpaceJsonRequest = parse_json_input(request_json)?;
1181 let detached = with_client_handle(client, |handle| {
1182 handle
1183 .runtime
1184 .block_on(handle.client.detach_space(request.space_id))
1185 .map_err(error_to_string)
1186 })?;
1187 serde_json::to_string(&detached)
1188 .map_err(|error| format!("failed to serialize detach_space_json response: {error}"))
1189 })();
1190 match result {
1191 Ok(response) => {
1192 write_string_out(response_out, response);
1193 ffi_ok_status(error_out)
1194 }
1195 Err(error) => ffi_error_status(error_out, error),
1196 }
1197}
1198
1199#[unsafe(no_mangle)]
1202pub extern "C" fn vldb_controller_ffi_client_list_spaces(
1203 client: *mut FfiControllerClientHandle,
1204 spaces_out: *mut *mut FfiSpaceSnapshotArray,
1205 error_out: *mut *mut c_char,
1206) -> c_int {
1207 clear_out_ptr(spaces_out);
1208 let result = with_client_handle(client, |handle| {
1209 handle
1210 .runtime
1211 .block_on(handle.client.list_spaces())
1212 .map_err(error_to_string)
1213 });
1214 match result {
1215 Ok(spaces) => {
1216 let mapped = map_space_snapshot_array(spaces);
1217 write_boxed_out_ptr(spaces_out, mapped);
1218 ffi_ok_status(error_out)
1219 }
1220 Err(error) => ffi_error_status(error_out, error),
1221 }
1222}
1223
1224#[unsafe(no_mangle)]
1227pub extern "C" fn vldb_controller_ffi_client_list_spaces_json(
1228 client: *mut FfiControllerClientHandle,
1229 response_out: *mut *mut c_char,
1230 error_out: *mut *mut c_char,
1231) -> c_int {
1232 clear_out_ptr(response_out);
1233 let result = with_client_handle(client, |handle| {
1234 let spaces = handle
1235 .runtime
1236 .block_on(handle.client.list_spaces())
1237 .map_err(error_to_string)?;
1238 serde_json::to_string(&spaces)
1239 .map_err(|error| format!("failed to serialize list_spaces_json response: {error}"))
1240 });
1241 match result {
1242 Ok(response) => {
1243 write_string_out(response_out, response);
1244 ffi_ok_status(error_out)
1245 }
1246 Err(error) => ffi_error_status(error_out, error),
1247 }
1248}
1249
1250#[unsafe(no_mangle)]
1253pub extern "C" fn vldb_controller_ffi_space_snapshot_array_free(
1254 spaces: *mut FfiSpaceSnapshotArray,
1255) {
1256 if spaces.is_null() {
1257 return;
1258 }
1259 unsafe {
1260 let spaces = Box::from_raw(spaces);
1261 if !spaces.items.is_null() {
1262 let items = Vec::from_raw_parts(spaces.items, spaces.len, spaces.len);
1263 for item in items {
1264 free_space_snapshot_fields(item);
1265 }
1266 }
1267 }
1268}
1269
1270#[unsafe(no_mangle)]
1273pub extern "C" fn vldb_controller_ffi_space_snapshot_free(space: *mut FfiSpaceSnapshot) {
1274 if space.is_null() {
1275 return;
1276 }
1277 unsafe {
1278 let space = Box::from_raw(space);
1279 free_space_snapshot_fields(*space);
1280 }
1281}
1282
1283#[unsafe(no_mangle)]
1286pub extern "C" fn vldb_controller_ffi_client_enable_sqlite(
1287 client: *mut FfiControllerClientHandle,
1288 request: *const FfiControllerSqliteEnableRequest,
1289 error_out: *mut *mut c_char,
1290) -> c_int {
1291 let result = (|| -> Result<(), String> {
1292 let request = ffi_sqlite_enable_request_to_rust(request)?;
1293 with_client_handle(client, |handle| {
1294 handle
1295 .runtime
1296 .block_on(handle.client.enable_sqlite(request))
1297 .map_err(error_to_string)
1298 })
1299 })();
1300 match result {
1301 Ok(()) => ffi_ok_status(error_out),
1302 Err(error) => ffi_error_status(error_out, error),
1303 }
1304}
1305
1306#[unsafe(no_mangle)]
1309pub extern "C" fn vldb_controller_ffi_client_enable_sqlite_json(
1310 client: *mut FfiControllerClientHandle,
1311 request_json: *const c_char,
1312 response_out: *mut *mut c_char,
1313 error_out: *mut *mut c_char,
1314) -> c_int {
1315 clear_out_ptr(response_out);
1316 let result = (|| -> Result<String, String> {
1317 let request: EnableSqliteJsonRequest = parse_json_input(request_json)?;
1318 with_client_handle(client, |handle| {
1319 handle
1320 .runtime
1321 .block_on(handle.client.enable_sqlite(request.request))
1322 .map_err(error_to_string)
1323 })?;
1324 serde_json::to_string(&SuccessJsonResponse { ok: true })
1325 .map_err(|error| format!("failed to serialize enable_sqlite_json response: {error}"))
1326 })();
1327 match result {
1328 Ok(response) => {
1329 write_string_out(response_out, response);
1330 ffi_ok_status(error_out)
1331 }
1332 Err(error) => ffi_error_status(error_out, error),
1333 }
1334}
1335
1336#[unsafe(no_mangle)]
1339pub extern "C" fn vldb_controller_ffi_client_disable_sqlite(
1340 client: *mut FfiControllerClientHandle,
1341 space_id: *const c_char,
1342 disabled_out: *mut c_uchar,
1343 error_out: *mut *mut c_char,
1344) -> c_int {
1345 clear_out_u8(disabled_out);
1346 let result = (|| -> Result<bool, String> {
1347 let space_id = required_c_string(space_id, "space_id")?;
1348 let binding_id = space_id.clone();
1349 with_client_handle(client, |handle| {
1350 handle
1351 .runtime
1352 .block_on(handle.client.disable_sqlite(space_id, binding_id))
1353 .map_err(error_to_string)
1354 })
1355 })();
1356 match result {
1357 Ok(disabled) => {
1358 write_out_u8(disabled_out, if disabled { 1 } else { 0 });
1359 ffi_ok_status(error_out)
1360 }
1361 Err(error) => ffi_error_status(error_out, error),
1362 }
1363}
1364
1365#[unsafe(no_mangle)]
1368pub extern "C" fn vldb_controller_ffi_client_disable_sqlite_json(
1369 client: *mut FfiControllerClientHandle,
1370 request_json: *const c_char,
1371 response_out: *mut *mut c_char,
1372 error_out: *mut *mut c_char,
1373) -> c_int {
1374 clear_out_ptr(response_out);
1375 let result = (|| -> Result<String, String> {
1376 let request: DisableBackendJsonRequest = parse_json_input(request_json)?;
1377 let binding_id = request.space_id.clone();
1378 let disabled = with_client_handle(client, |handle| {
1379 handle
1380 .runtime
1381 .block_on(handle.client.disable_sqlite(request.space_id, binding_id))
1382 .map_err(error_to_string)
1383 })?;
1384 serde_json::to_string(&disabled)
1385 .map_err(|error| format!("failed to serialize disable_sqlite_json response: {error}"))
1386 })();
1387 match result {
1388 Ok(response) => {
1389 write_string_out(response_out, response);
1390 ffi_ok_status(error_out)
1391 }
1392 Err(error) => ffi_error_status(error_out, error),
1393 }
1394}
1395
1396#[unsafe(no_mangle)]
1399pub extern "C" fn vldb_controller_ffi_client_execute_sqlite_script(
1400 client: *mut FfiControllerClientHandle,
1401 space_id: *const c_char,
1402 sql: *const c_char,
1403 params: *const FfiSqliteValue,
1404 params_len: usize,
1405 result_out: *mut *mut FfiControllerSqliteExecuteResult,
1406 error_out: *mut *mut c_char,
1407) -> c_int {
1408 clear_out_ptr(result_out);
1409 let result = (|| -> Result<ControllerSqliteExecuteResult, String> {
1410 let space_id = required_c_string(space_id, "space_id")?;
1411 let binding_id = space_id.clone();
1412 let sql = required_c_string(sql, "sql")?;
1413 let params = read_sqlite_values(params, params_len, "params")?;
1414 with_client_handle(client, |handle| {
1415 handle
1416 .runtime
1417 .block_on(
1418 handle
1419 .client
1420 .execute_sqlite_script_typed(space_id, binding_id, sql, params),
1421 )
1422 .map_err(error_to_string)
1423 })
1424 })();
1425 match result {
1426 Ok(result) => {
1427 write_boxed_out_ptr(result_out, map_sqlite_execute_result(result));
1428 ffi_ok_status(error_out)
1429 }
1430 Err(error) => ffi_error_status(error_out, error),
1431 }
1432}
1433
1434#[unsafe(no_mangle)]
1437pub extern "C" fn vldb_controller_ffi_client_execute_sqlite_script_json(
1438 client: *mut FfiControllerClientHandle,
1439 request_json: *const c_char,
1440 response_out: *mut *mut c_char,
1441 error_out: *mut *mut c_char,
1442) -> c_int {
1443 clear_out_ptr(response_out);
1444 let result = (|| -> Result<String, String> {
1445 let request: ExecuteSqliteScriptJsonRequest = parse_json_input(request_json)?;
1446 let binding_id = request.space_id.clone();
1447 let result = with_client_handle(client, |handle| {
1448 let params_json = serde_json::to_string(&request.params).map_err(|error| {
1449 format!("failed to serialize execute_sqlite_script_json params: {error}")
1450 })?;
1451 handle
1452 .runtime
1453 .block_on(handle.client.execute_sqlite_script(
1454 request.space_id,
1455 binding_id,
1456 request.sql,
1457 params_json,
1458 ))
1459 .map_err(error_to_string)
1460 })?;
1461 serde_json::to_string(&result).map_err(|error| {
1462 format!("failed to serialize execute_sqlite_script_json response: {error}")
1463 })
1464 })();
1465 match result {
1466 Ok(response) => {
1467 write_string_out(response_out, response);
1468 ffi_ok_status(error_out)
1469 }
1470 Err(error) => ffi_error_status(error_out, error),
1471 }
1472}
1473
1474#[unsafe(no_mangle)]
1477pub extern "C" fn vldb_controller_ffi_sqlite_execute_result_free(
1478 result: *mut FfiControllerSqliteExecuteResult,
1479) {
1480 if !result.is_null() {
1481 unsafe {
1482 let result = Box::from_raw(result);
1483 vldb_controller_ffi_string_free(result.message);
1484 }
1485 }
1486}
1487
1488#[unsafe(no_mangle)]
1491pub extern "C" fn vldb_controller_ffi_client_query_sqlite_json(
1492 client: *mut FfiControllerClientHandle,
1493 space_id: *const c_char,
1494 sql: *const c_char,
1495 params: *const FfiSqliteValue,
1496 params_len: usize,
1497 result_out: *mut *mut FfiControllerSqliteQueryResult,
1498 error_out: *mut *mut c_char,
1499) -> c_int {
1500 clear_out_ptr(result_out);
1501 let result = (|| -> Result<ControllerSqliteQueryResult, String> {
1502 let space_id = required_c_string(space_id, "space_id")?;
1503 let binding_id = space_id.clone();
1504 let sql = required_c_string(sql, "sql")?;
1505 let params = read_sqlite_values(params, params_len, "params")?;
1506 with_client_handle(client, |handle| {
1507 handle
1508 .runtime
1509 .block_on(
1510 handle
1511 .client
1512 .query_sqlite_json_typed(space_id, binding_id, sql, params),
1513 )
1514 .map_err(error_to_string)
1515 })
1516 })();
1517 match result {
1518 Ok(result) => {
1519 write_boxed_out_ptr(result_out, map_sqlite_query_result(result));
1520 ffi_ok_status(error_out)
1521 }
1522 Err(error) => ffi_error_status(error_out, error),
1523 }
1524}
1525
1526#[unsafe(no_mangle)]
1529pub extern "C" fn vldb_controller_ffi_client_query_sqlite_json_json(
1530 client: *mut FfiControllerClientHandle,
1531 request_json: *const c_char,
1532 response_out: *mut *mut c_char,
1533 error_out: *mut *mut c_char,
1534) -> c_int {
1535 clear_out_ptr(response_out);
1536 let result = (|| -> Result<String, String> {
1537 let request: QuerySqliteJsonJsonRequest = parse_json_input(request_json)?;
1538 let binding_id = request.space_id.clone();
1539 let result = with_client_handle(client, |handle| {
1540 let params_json = serde_json::to_string(&request.params).map_err(|error| {
1541 format!("failed to serialize query_sqlite_json_json params: {error}")
1542 })?;
1543 handle
1544 .runtime
1545 .block_on(handle.client.query_sqlite_json(
1546 request.space_id,
1547 binding_id,
1548 request.sql,
1549 params_json,
1550 ))
1551 .map_err(error_to_string)
1552 })?;
1553 serde_json::to_string(&result).map_err(|error| {
1554 format!("failed to serialize query_sqlite_json_json response: {error}")
1555 })
1556 })();
1557 match result {
1558 Ok(response) => {
1559 write_string_out(response_out, response);
1560 ffi_ok_status(error_out)
1561 }
1562 Err(error) => ffi_error_status(error_out, error),
1563 }
1564}
1565
1566#[unsafe(no_mangle)]
1569pub extern "C" fn vldb_controller_ffi_sqlite_query_result_free(
1570 result: *mut FfiControllerSqliteQueryResult,
1571) {
1572 if !result.is_null() {
1573 unsafe {
1574 let result = Box::from_raw(result);
1575 vldb_controller_ffi_string_free(result.json_data);
1576 }
1577 }
1578}
1579
1580#[unsafe(no_mangle)]
1583pub extern "C" fn vldb_controller_ffi_client_execute_sqlite_batch(
1584 client: *mut FfiControllerClientHandle,
1585 space_id: *const c_char,
1586 sql: *const c_char,
1587 items: *const FfiSqliteBatchItem,
1588 items_len: usize,
1589 result_out: *mut *mut FfiControllerSqliteExecuteBatchResult,
1590 error_out: *mut *mut c_char,
1591) -> c_int {
1592 clear_out_ptr(result_out);
1593 let result = (|| -> Result<ControllerSqliteExecuteBatchResult, String> {
1594 let space_id = required_c_string(space_id, "space_id")?;
1595 let binding_id = space_id.clone();
1596 let sql = required_c_string(sql, "sql")?;
1597 let items = read_sqlite_batch_items(items, items_len, "items")?;
1598 with_client_handle(client, |handle| {
1599 handle
1600 .runtime
1601 .block_on(
1602 handle
1603 .client
1604 .execute_sqlite_batch_typed(space_id, binding_id, sql, items),
1605 )
1606 .map_err(error_to_string)
1607 })
1608 })();
1609 match result {
1610 Ok(result) => {
1611 write_boxed_out_ptr(result_out, map_sqlite_execute_batch_result(result));
1612 ffi_ok_status(error_out)
1613 }
1614 Err(error) => ffi_error_status(error_out, error),
1615 }
1616}
1617
1618#[unsafe(no_mangle)]
1621pub extern "C" fn vldb_controller_ffi_client_execute_sqlite_batch_json(
1622 client: *mut FfiControllerClientHandle,
1623 request_json: *const c_char,
1624 response_out: *mut *mut c_char,
1625 error_out: *mut *mut c_char,
1626) -> c_int {
1627 clear_out_ptr(response_out);
1628 let result = (|| -> Result<String, String> {
1629 let request: ExecuteSqliteBatchJsonRequest = parse_json_input(request_json)?;
1630 let binding_id = request.space_id.clone();
1631 let result = with_client_handle(client, |handle| {
1632 let batch_params_json = request
1633 .batch_params
1634 .iter()
1635 .map(|item| {
1636 serde_json::to_string(item).map_err(|error| {
1637 format!(
1638 "failed to serialize execute_sqlite_batch_json batch_params item: {error}"
1639 )
1640 })
1641 })
1642 .collect::<Result<Vec<_>, _>>()?;
1643 handle
1644 .runtime
1645 .block_on(handle.client.execute_sqlite_batch(
1646 request.space_id,
1647 binding_id,
1648 request.sql,
1649 batch_params_json,
1650 ))
1651 .map_err(error_to_string)
1652 })?;
1653 serde_json::to_string(&result).map_err(|error| {
1654 format!("failed to serialize execute_sqlite_batch_json response: {error}")
1655 })
1656 })();
1657 match result {
1658 Ok(response) => {
1659 write_string_out(response_out, response);
1660 ffi_ok_status(error_out)
1661 }
1662 Err(error) => ffi_error_status(error_out, error),
1663 }
1664}
1665
1666#[unsafe(no_mangle)]
1669pub extern "C" fn vldb_controller_ffi_sqlite_execute_batch_result_free(
1670 result: *mut FfiControllerSqliteExecuteBatchResult,
1671) {
1672 if !result.is_null() {
1673 unsafe {
1674 let result = Box::from_raw(result);
1675 vldb_controller_ffi_string_free(result.message);
1676 }
1677 }
1678}
1679
1680#[unsafe(no_mangle)]
1683pub extern "C" fn vldb_controller_ffi_client_query_sqlite_stream(
1684 client: *mut FfiControllerClientHandle,
1685 space_id: *const c_char,
1686 sql: *const c_char,
1687 params: *const FfiSqliteValue,
1688 params_len: usize,
1689 target_chunk_size: c_ulonglong,
1690 result_out: *mut *mut FfiControllerSqliteQueryStreamResult,
1691 error_out: *mut *mut c_char,
1692) -> c_int {
1693 clear_out_ptr(result_out);
1694 let result = (|| -> Result<ControllerSqliteQueryStreamCompatResult, String> {
1695 let space_id = required_c_string(space_id, "space_id")?;
1696 let binding_id = space_id.clone();
1697 let sql = required_c_string(sql, "sql")?;
1698 let params = read_sqlite_values(params, params_len, "params")?;
1699 let chunk_size = if target_chunk_size == 0 {
1700 None
1701 } else {
1702 Some(target_chunk_size)
1703 };
1704 with_client_handle(client, |handle| {
1705 collect_sqlite_query_stream_result(
1706 &handle.runtime,
1707 &handle.client,
1708 space_id,
1709 binding_id,
1710 sql,
1711 params,
1712 chunk_size,
1713 )
1714 })
1715 })();
1716 match result {
1717 Ok(result) => {
1718 write_boxed_out_ptr(result_out, map_sqlite_query_stream_result(result));
1719 ffi_ok_status(error_out)
1720 }
1721 Err(error) => ffi_error_status(error_out, error),
1722 }
1723}
1724
1725#[unsafe(no_mangle)]
1728pub extern "C" fn vldb_controller_ffi_client_query_sqlite_stream_json(
1729 client: *mut FfiControllerClientHandle,
1730 request_json: *const c_char,
1731 response_out: *mut *mut c_char,
1732 error_out: *mut *mut c_char,
1733) -> c_int {
1734 clear_out_ptr(response_out);
1735 let result = (|| -> Result<String, String> {
1736 let request: QuerySqliteStreamJsonRequest = parse_json_input(request_json)?;
1737 let binding_id = request.space_id.clone();
1738 let result = with_client_handle(client, |handle| {
1739 let params = request
1740 .params
1741 .iter()
1742 .cloned()
1743 .map(json_to_sqlite_value)
1744 .collect::<Result<Vec<_>, _>>()?;
1745 collect_sqlite_query_stream_result(
1746 &handle.runtime,
1747 &handle.client,
1748 request.space_id,
1749 binding_id,
1750 request.sql,
1751 params,
1752 request.target_chunk_size,
1753 )
1754 })?;
1755 serde_json::to_string(&QuerySqliteStreamJsonResponse {
1756 chunks_base64: result
1757 .chunks
1758 .iter()
1759 .map(|chunk| encode_base64(chunk))
1760 .collect(),
1761 row_count: result.row_count,
1762 chunk_count: result.chunk_count,
1763 total_bytes: result.total_bytes,
1764 })
1765 .map_err(|error| format!("failed to serialize query_sqlite_stream_json response: {error}"))
1766 })();
1767 match result {
1768 Ok(response) => {
1769 write_string_out(response_out, response);
1770 ffi_ok_status(error_out)
1771 }
1772 Err(error) => ffi_error_status(error_out, error),
1773 }
1774}
1775
1776#[unsafe(no_mangle)]
1779pub extern "C" fn vldb_controller_ffi_sqlite_query_stream_result_free(
1780 result: *mut FfiControllerSqliteQueryStreamResult,
1781) {
1782 if !result.is_null() {
1783 unsafe {
1784 let result = Box::from_raw(result);
1785 vldb_controller_ffi_byte_buffer_array_free(result.chunks);
1786 }
1787 }
1788}
1789
1790#[unsafe(no_mangle)]
1793pub extern "C" fn vldb_controller_ffi_byte_buffer_array_free(value: *mut FfiByteBufferArray) {
1794 if value.is_null() {
1795 return;
1796 }
1797 unsafe {
1798 let value = Box::from_raw(value);
1799 if !value.items.is_null() {
1800 let items = Vec::from_raw_parts(value.items, value.len, value.len);
1801 for item in items {
1802 vldb_controller_ffi_bytes_free(item.data, item.len);
1803 }
1804 }
1805 }
1806}
1807
1808#[unsafe(no_mangle)]
1811pub extern "C" fn vldb_controller_ffi_client_tokenize_sqlite_text(
1812 client: *mut FfiControllerClientHandle,
1813 space_id: *const c_char,
1814 tokenizer_mode: *const c_char,
1815 text: *const c_char,
1816 search_mode: c_uchar,
1817 result_out: *mut *mut FfiControllerSqliteTokenizeResult,
1818 error_out: *mut *mut c_char,
1819) -> c_int {
1820 clear_out_ptr(result_out);
1821 let result = (|| -> Result<ControllerSqliteTokenizeResult, String> {
1822 let space_id = required_c_string(space_id, "space_id")?;
1823 let binding_id = space_id.clone();
1824 let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
1825 let text = required_c_string_preserve(text, "text")?;
1826 with_client_handle(client, |handle| {
1827 handle
1828 .runtime
1829 .block_on(handle.client.tokenize_sqlite_text(
1830 space_id,
1831 binding_id,
1832 tokenizer_mode,
1833 text,
1834 search_mode != 0,
1835 ))
1836 .map_err(error_to_string)
1837 })
1838 })();
1839 match result {
1840 Ok(result) => {
1841 write_boxed_out_ptr(result_out, map_sqlite_tokenize_result(result));
1842 ffi_ok_status(error_out)
1843 }
1844 Err(error) => ffi_error_status(error_out, error),
1845 }
1846}
1847
1848#[unsafe(no_mangle)]
1851pub extern "C" fn vldb_controller_ffi_client_tokenize_sqlite_text_json(
1852 client: *mut FfiControllerClientHandle,
1853 request_json: *const c_char,
1854 response_out: *mut *mut c_char,
1855 error_out: *mut *mut c_char,
1856) -> c_int {
1857 clear_out_ptr(response_out);
1858 let result = (|| -> Result<String, String> {
1859 let request: TokenizeSqliteTextJsonRequest = parse_json_input(request_json)?;
1860 let binding_id = request.space_id.clone();
1861 let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
1862 let result = with_client_handle(client, |handle| {
1863 handle
1864 .runtime
1865 .block_on(handle.client.tokenize_sqlite_text(
1866 request.space_id,
1867 binding_id,
1868 tokenizer_mode,
1869 request.text,
1870 request.search_mode,
1871 ))
1872 .map_err(error_to_string)
1873 })?;
1874 serde_json::to_string(&result).map_err(|error| {
1875 format!("failed to serialize tokenize_sqlite_text_json response: {error}")
1876 })
1877 })();
1878 match result {
1879 Ok(response) => {
1880 write_string_out(response_out, response);
1881 ffi_ok_status(error_out)
1882 }
1883 Err(error) => ffi_error_status(error_out, error),
1884 }
1885}
1886
1887#[unsafe(no_mangle)]
1890pub extern "C" fn vldb_controller_ffi_sqlite_tokenize_result_free(
1891 result: *mut FfiControllerSqliteTokenizeResult,
1892) {
1893 if !result.is_null() {
1894 unsafe {
1895 let result = Box::from_raw(result);
1896 vldb_controller_ffi_string_free(result.tokenizer_mode);
1897 vldb_controller_ffi_string_free(result.normalized_text);
1898 vldb_controller_ffi_string_free(result.tokens_json);
1899 vldb_controller_ffi_string_free(result.fts_query);
1900 }
1901 }
1902}
1903
1904#[unsafe(no_mangle)]
1907pub extern "C" fn vldb_controller_ffi_client_list_sqlite_custom_words(
1908 client: *mut FfiControllerClientHandle,
1909 space_id: *const c_char,
1910 result_out: *mut *mut FfiControllerSqliteListCustomWordsResult,
1911 error_out: *mut *mut c_char,
1912) -> c_int {
1913 clear_out_ptr(result_out);
1914 let result = (|| -> Result<ControllerSqliteListCustomWordsResult, String> {
1915 let space_id = required_c_string(space_id, "space_id")?;
1916 let binding_id = space_id.clone();
1917 with_client_handle(client, |handle| {
1918 handle
1919 .runtime
1920 .block_on(handle.client.list_sqlite_custom_words(space_id, binding_id))
1921 .map_err(error_to_string)
1922 })
1923 })();
1924 match result {
1925 Ok(result) => {
1926 write_boxed_out_ptr(result_out, map_sqlite_list_custom_words_result(result));
1927 ffi_ok_status(error_out)
1928 }
1929 Err(error) => ffi_error_status(error_out, error),
1930 }
1931}
1932
1933#[unsafe(no_mangle)]
1936pub extern "C" fn vldb_controller_ffi_client_list_sqlite_custom_words_json(
1937 client: *mut FfiControllerClientHandle,
1938 request_json: *const c_char,
1939 response_out: *mut *mut c_char,
1940 error_out: *mut *mut c_char,
1941) -> c_int {
1942 clear_out_ptr(response_out);
1943 let result = (|| -> Result<String, String> {
1944 let request: ListSqliteCustomWordsJsonRequest = parse_json_input(request_json)?;
1945 let binding_id = request.space_id.clone();
1946 let result = with_client_handle(client, |handle| {
1947 handle
1948 .runtime
1949 .block_on(
1950 handle
1951 .client
1952 .list_sqlite_custom_words(request.space_id, binding_id),
1953 )
1954 .map_err(error_to_string)
1955 })?;
1956 serde_json::to_string(&result).map_err(|error| {
1957 format!("failed to serialize list_sqlite_custom_words_json response: {error}")
1958 })
1959 })();
1960 match result {
1961 Ok(response) => {
1962 write_string_out(response_out, response);
1963 ffi_ok_status(error_out)
1964 }
1965 Err(error) => ffi_error_status(error_out, error),
1966 }
1967}
1968
1969#[unsafe(no_mangle)]
1972pub extern "C" fn vldb_controller_ffi_sqlite_custom_word_array_free(
1973 value: *mut FfiControllerSqliteCustomWordArray,
1974) {
1975 if value.is_null() {
1976 return;
1977 }
1978 unsafe {
1979 let value = Box::from_raw(value);
1980 if !value.items.is_null() {
1981 let items = Vec::from_raw_parts(value.items, value.len, value.len);
1982 for item in items {
1983 vldb_controller_ffi_string_free(item.word);
1984 }
1985 }
1986 }
1987}
1988
1989#[unsafe(no_mangle)]
1992pub extern "C" fn vldb_controller_ffi_sqlite_list_custom_words_result_free(
1993 result: *mut FfiControllerSqliteListCustomWordsResult,
1994) {
1995 if !result.is_null() {
1996 unsafe {
1997 let result = Box::from_raw(result);
1998 vldb_controller_ffi_string_free(result.message);
1999 vldb_controller_ffi_sqlite_custom_word_array_free(result.words);
2000 }
2001 }
2002}
2003
2004#[unsafe(no_mangle)]
2007pub extern "C" fn vldb_controller_ffi_client_upsert_sqlite_custom_word(
2008 client: *mut FfiControllerClientHandle,
2009 space_id: *const c_char,
2010 word: *const c_char,
2011 weight: u32,
2012 result_out: *mut *mut FfiControllerSqliteDictionaryMutationResult,
2013 error_out: *mut *mut c_char,
2014) -> c_int {
2015 clear_out_ptr(result_out);
2016 let result = (|| -> Result<ControllerSqliteDictionaryMutationResult, String> {
2017 let space_id = required_c_string(space_id, "space_id")?;
2018 let binding_id = space_id.clone();
2019 let word = required_c_string(word, "word")?;
2020 with_client_handle(client, |handle| {
2021 handle
2022 .runtime
2023 .block_on(
2024 handle
2025 .client
2026 .upsert_sqlite_custom_word(space_id, binding_id, word, weight),
2027 )
2028 .map_err(error_to_string)
2029 })
2030 })();
2031 match result {
2032 Ok(result) => {
2033 write_boxed_out_ptr(result_out, map_sqlite_dictionary_mutation_result(result));
2034 ffi_ok_status(error_out)
2035 }
2036 Err(error) => ffi_error_status(error_out, error),
2037 }
2038}
2039
2040#[unsafe(no_mangle)]
2043pub extern "C" fn vldb_controller_ffi_client_upsert_sqlite_custom_word_json(
2044 client: *mut FfiControllerClientHandle,
2045 request_json: *const c_char,
2046 response_out: *mut *mut c_char,
2047 error_out: *mut *mut c_char,
2048) -> c_int {
2049 clear_out_ptr(response_out);
2050 let result = (|| -> Result<String, String> {
2051 let request: UpsertSqliteCustomWordJsonRequest = parse_json_input(request_json)?;
2052 let binding_id = request.space_id.clone();
2053 let result = with_client_handle(client, |handle| {
2054 handle
2055 .runtime
2056 .block_on(handle.client.upsert_sqlite_custom_word(
2057 request.space_id,
2058 binding_id,
2059 request.word,
2060 request.weight,
2061 ))
2062 .map_err(error_to_string)
2063 })?;
2064 serde_json::to_string(&result).map_err(|error| {
2065 format!("failed to serialize upsert_sqlite_custom_word_json response: {error}")
2066 })
2067 })();
2068 match result {
2069 Ok(response) => {
2070 write_string_out(response_out, response);
2071 ffi_ok_status(error_out)
2072 }
2073 Err(error) => ffi_error_status(error_out, error),
2074 }
2075}
2076
2077#[unsafe(no_mangle)]
2080pub extern "C" fn vldb_controller_ffi_client_remove_sqlite_custom_word(
2081 client: *mut FfiControllerClientHandle,
2082 space_id: *const c_char,
2083 word: *const c_char,
2084 result_out: *mut *mut FfiControllerSqliteDictionaryMutationResult,
2085 error_out: *mut *mut c_char,
2086) -> c_int {
2087 clear_out_ptr(result_out);
2088 let result = (|| -> Result<ControllerSqliteDictionaryMutationResult, String> {
2089 let space_id = required_c_string(space_id, "space_id")?;
2090 let binding_id = space_id.clone();
2091 let word = required_c_string(word, "word")?;
2092 with_client_handle(client, |handle| {
2093 handle
2094 .runtime
2095 .block_on(
2096 handle
2097 .client
2098 .remove_sqlite_custom_word(space_id, binding_id, word),
2099 )
2100 .map_err(error_to_string)
2101 })
2102 })();
2103 match result {
2104 Ok(result) => {
2105 write_boxed_out_ptr(result_out, map_sqlite_dictionary_mutation_result(result));
2106 ffi_ok_status(error_out)
2107 }
2108 Err(error) => ffi_error_status(error_out, error),
2109 }
2110}
2111
2112#[unsafe(no_mangle)]
2115pub extern "C" fn vldb_controller_ffi_client_remove_sqlite_custom_word_json(
2116 client: *mut FfiControllerClientHandle,
2117 request_json: *const c_char,
2118 response_out: *mut *mut c_char,
2119 error_out: *mut *mut c_char,
2120) -> c_int {
2121 clear_out_ptr(response_out);
2122 let result = (|| -> Result<String, String> {
2123 let request: RemoveSqliteCustomWordJsonRequest = parse_json_input(request_json)?;
2124 let binding_id = request.space_id.clone();
2125 let result = with_client_handle(client, |handle| {
2126 handle
2127 .runtime
2128 .block_on(handle.client.remove_sqlite_custom_word(
2129 request.space_id,
2130 binding_id,
2131 request.word,
2132 ))
2133 .map_err(error_to_string)
2134 })?;
2135 serde_json::to_string(&result).map_err(|error| {
2136 format!("failed to serialize remove_sqlite_custom_word_json response: {error}")
2137 })
2138 })();
2139 match result {
2140 Ok(response) => {
2141 write_string_out(response_out, response);
2142 ffi_ok_status(error_out)
2143 }
2144 Err(error) => ffi_error_status(error_out, error),
2145 }
2146}
2147
2148#[unsafe(no_mangle)]
2151pub extern "C" fn vldb_controller_ffi_sqlite_dictionary_mutation_result_free(
2152 result: *mut FfiControllerSqliteDictionaryMutationResult,
2153) {
2154 if !result.is_null() {
2155 unsafe {
2156 let result = Box::from_raw(result);
2157 vldb_controller_ffi_string_free(result.message);
2158 }
2159 }
2160}
2161
2162#[unsafe(no_mangle)]
2165pub extern "C" fn vldb_controller_ffi_client_ensure_sqlite_fts_index(
2166 client: *mut FfiControllerClientHandle,
2167 space_id: *const c_char,
2168 index_name: *const c_char,
2169 tokenizer_mode: *const c_char,
2170 result_out: *mut *mut FfiControllerSqliteEnsureFtsIndexResult,
2171 error_out: *mut *mut c_char,
2172) -> c_int {
2173 clear_out_ptr(result_out);
2174 let result = (|| -> Result<ControllerSqliteEnsureFtsIndexResult, String> {
2175 let space_id = required_c_string(space_id, "space_id")?;
2176 let binding_id = space_id.clone();
2177 let index_name = required_c_string(index_name, "index_name")?;
2178 let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
2179 with_client_handle(client, |handle| {
2180 handle
2181 .runtime
2182 .block_on(handle.client.ensure_sqlite_fts_index(
2183 space_id,
2184 binding_id,
2185 index_name,
2186 tokenizer_mode,
2187 ))
2188 .map_err(error_to_string)
2189 })
2190 })();
2191 match result {
2192 Ok(result) => {
2193 write_boxed_out_ptr(result_out, map_sqlite_ensure_fts_index_result(result));
2194 ffi_ok_status(error_out)
2195 }
2196 Err(error) => ffi_error_status(error_out, error),
2197 }
2198}
2199
2200#[unsafe(no_mangle)]
2203pub extern "C" fn vldb_controller_ffi_client_ensure_sqlite_fts_index_json(
2204 client: *mut FfiControllerClientHandle,
2205 request_json: *const c_char,
2206 response_out: *mut *mut c_char,
2207 error_out: *mut *mut c_char,
2208) -> c_int {
2209 clear_out_ptr(response_out);
2210 let result = (|| -> Result<String, String> {
2211 let request: EnsureSqliteFtsIndexJsonRequest = parse_json_input(request_json)?;
2212 let binding_id = request.space_id.clone();
2213 let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
2214 let result = with_client_handle(client, |handle| {
2215 handle
2216 .runtime
2217 .block_on(handle.client.ensure_sqlite_fts_index(
2218 request.space_id,
2219 binding_id,
2220 request.index_name,
2221 tokenizer_mode,
2222 ))
2223 .map_err(error_to_string)
2224 })?;
2225 serde_json::to_string(&result).map_err(|error| {
2226 format!("failed to serialize ensure_sqlite_fts_index_json response: {error}")
2227 })
2228 })();
2229 match result {
2230 Ok(response) => {
2231 write_string_out(response_out, response);
2232 ffi_ok_status(error_out)
2233 }
2234 Err(error) => ffi_error_status(error_out, error),
2235 }
2236}
2237
2238#[unsafe(no_mangle)]
2241pub extern "C" fn vldb_controller_ffi_sqlite_ensure_fts_index_result_free(
2242 result: *mut FfiControllerSqliteEnsureFtsIndexResult,
2243) {
2244 if !result.is_null() {
2245 unsafe {
2246 let result = Box::from_raw(result);
2247 vldb_controller_ffi_string_free(result.message);
2248 vldb_controller_ffi_string_free(result.index_name);
2249 vldb_controller_ffi_string_free(result.tokenizer_mode);
2250 }
2251 }
2252}
2253
2254#[unsafe(no_mangle)]
2257pub extern "C" fn vldb_controller_ffi_client_rebuild_sqlite_fts_index(
2258 client: *mut FfiControllerClientHandle,
2259 space_id: *const c_char,
2260 index_name: *const c_char,
2261 tokenizer_mode: *const c_char,
2262 result_out: *mut *mut FfiControllerSqliteRebuildFtsIndexResult,
2263 error_out: *mut *mut c_char,
2264) -> c_int {
2265 clear_out_ptr(result_out);
2266 let result = (|| -> Result<ControllerSqliteRebuildFtsIndexResult, String> {
2267 let space_id = required_c_string(space_id, "space_id")?;
2268 let binding_id = space_id.clone();
2269 let index_name = required_c_string(index_name, "index_name")?;
2270 let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
2271 with_client_handle(client, |handle| {
2272 handle
2273 .runtime
2274 .block_on(handle.client.rebuild_sqlite_fts_index(
2275 space_id,
2276 binding_id,
2277 index_name,
2278 tokenizer_mode,
2279 ))
2280 .map_err(error_to_string)
2281 })
2282 })();
2283 match result {
2284 Ok(result) => {
2285 write_boxed_out_ptr(result_out, map_sqlite_rebuild_fts_index_result(result));
2286 ffi_ok_status(error_out)
2287 }
2288 Err(error) => ffi_error_status(error_out, error),
2289 }
2290}
2291
2292#[unsafe(no_mangle)]
2295pub extern "C" fn vldb_controller_ffi_client_rebuild_sqlite_fts_index_json(
2296 client: *mut FfiControllerClientHandle,
2297 request_json: *const c_char,
2298 response_out: *mut *mut c_char,
2299 error_out: *mut *mut c_char,
2300) -> c_int {
2301 clear_out_ptr(response_out);
2302 let result = (|| -> Result<String, String> {
2303 let request: RebuildSqliteFtsIndexJsonRequest = parse_json_input(request_json)?;
2304 let binding_id = request.space_id.clone();
2305 let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
2306 let result = with_client_handle(client, |handle| {
2307 handle
2308 .runtime
2309 .block_on(handle.client.rebuild_sqlite_fts_index(
2310 request.space_id,
2311 binding_id,
2312 request.index_name,
2313 tokenizer_mode,
2314 ))
2315 .map_err(error_to_string)
2316 })?;
2317 serde_json::to_string(&result).map_err(|error| {
2318 format!("failed to serialize rebuild_sqlite_fts_index_json response: {error}")
2319 })
2320 })();
2321 match result {
2322 Ok(response) => {
2323 write_string_out(response_out, response);
2324 ffi_ok_status(error_out)
2325 }
2326 Err(error) => ffi_error_status(error_out, error),
2327 }
2328}
2329
2330#[unsafe(no_mangle)]
2333pub extern "C" fn vldb_controller_ffi_sqlite_rebuild_fts_index_result_free(
2334 result: *mut FfiControllerSqliteRebuildFtsIndexResult,
2335) {
2336 if !result.is_null() {
2337 unsafe {
2338 let result = Box::from_raw(result);
2339 vldb_controller_ffi_string_free(result.message);
2340 vldb_controller_ffi_string_free(result.index_name);
2341 vldb_controller_ffi_string_free(result.tokenizer_mode);
2342 }
2343 }
2344}
2345
2346#[unsafe(no_mangle)]
2349#[allow(clippy::too_many_arguments)]
2350pub extern "C" fn vldb_controller_ffi_client_upsert_sqlite_fts_document(
2351 client: *mut FfiControllerClientHandle,
2352 space_id: *const c_char,
2353 index_name: *const c_char,
2354 tokenizer_mode: *const c_char,
2355 id: *const c_char,
2356 file_path: *const c_char,
2357 title: *const c_char,
2358 content: *const c_char,
2359 result_out: *mut *mut FfiControllerSqliteFtsMutationResult,
2360 error_out: *mut *mut c_char,
2361) -> c_int {
2362 clear_out_ptr(result_out);
2363 let result = (|| -> Result<ControllerSqliteFtsMutationResult, String> {
2364 let space_id = required_c_string(space_id, "space_id")?;
2365 let binding_id = space_id.clone();
2366 let index_name = required_c_string(index_name, "index_name")?;
2367 let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
2368 let id = required_c_string(id, "id")?;
2369 let file_path = required_c_string_preserve(file_path, "file_path")?;
2370 let title = required_c_string_preserve(title, "title")?;
2371 let content = required_c_string_preserve(content, "content")?;
2372 with_client_handle(client, |handle| {
2373 handle
2374 .runtime
2375 .block_on(handle.client.upsert_sqlite_fts_document(
2376 space_id,
2377 binding_id,
2378 index_name,
2379 tokenizer_mode,
2380 id,
2381 file_path,
2382 title,
2383 content,
2384 ))
2385 .map_err(error_to_string)
2386 })
2387 })();
2388 match result {
2389 Ok(result) => {
2390 write_boxed_out_ptr(result_out, map_sqlite_fts_mutation_result(result));
2391 ffi_ok_status(error_out)
2392 }
2393 Err(error) => ffi_error_status(error_out, error),
2394 }
2395}
2396
2397#[unsafe(no_mangle)]
2400pub extern "C" fn vldb_controller_ffi_client_upsert_sqlite_fts_document_json(
2401 client: *mut FfiControllerClientHandle,
2402 request_json: *const c_char,
2403 response_out: *mut *mut c_char,
2404 error_out: *mut *mut c_char,
2405) -> c_int {
2406 clear_out_ptr(response_out);
2407 let result = (|| -> Result<String, String> {
2408 let request: UpsertSqliteFtsDocumentJsonRequest = parse_json_input(request_json)?;
2409 let binding_id = request.space_id.clone();
2410 let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
2411 let result = with_client_handle(client, |handle| {
2412 handle
2413 .runtime
2414 .block_on(handle.client.upsert_sqlite_fts_document(
2415 request.space_id,
2416 binding_id,
2417 request.index_name,
2418 tokenizer_mode,
2419 request.id,
2420 request.file_path,
2421 request.title,
2422 request.content,
2423 ))
2424 .map_err(error_to_string)
2425 })?;
2426 serde_json::to_string(&result).map_err(|error| {
2427 format!("failed to serialize upsert_sqlite_fts_document_json response: {error}")
2428 })
2429 })();
2430 match result {
2431 Ok(response) => {
2432 write_string_out(response_out, response);
2433 ffi_ok_status(error_out)
2434 }
2435 Err(error) => ffi_error_status(error_out, error),
2436 }
2437}
2438
2439#[unsafe(no_mangle)]
2442pub extern "C" fn vldb_controller_ffi_client_delete_sqlite_fts_document(
2443 client: *mut FfiControllerClientHandle,
2444 space_id: *const c_char,
2445 index_name: *const c_char,
2446 id: *const c_char,
2447 result_out: *mut *mut FfiControllerSqliteFtsMutationResult,
2448 error_out: *mut *mut c_char,
2449) -> c_int {
2450 clear_out_ptr(result_out);
2451 let result = (|| -> Result<ControllerSqliteFtsMutationResult, String> {
2452 let space_id = required_c_string(space_id, "space_id")?;
2453 let binding_id = space_id.clone();
2454 let index_name = required_c_string(index_name, "index_name")?;
2455 let id = required_c_string(id, "id")?;
2456 with_client_handle(client, |handle| {
2457 handle
2458 .runtime
2459 .block_on(
2460 handle
2461 .client
2462 .delete_sqlite_fts_document(space_id, binding_id, index_name, id),
2463 )
2464 .map_err(error_to_string)
2465 })
2466 })();
2467 match result {
2468 Ok(result) => {
2469 write_boxed_out_ptr(result_out, map_sqlite_fts_mutation_result(result));
2470 ffi_ok_status(error_out)
2471 }
2472 Err(error) => ffi_error_status(error_out, error),
2473 }
2474}
2475
2476#[unsafe(no_mangle)]
2479pub extern "C" fn vldb_controller_ffi_client_delete_sqlite_fts_document_json(
2480 client: *mut FfiControllerClientHandle,
2481 request_json: *const c_char,
2482 response_out: *mut *mut c_char,
2483 error_out: *mut *mut c_char,
2484) -> c_int {
2485 clear_out_ptr(response_out);
2486 let result = (|| -> Result<String, String> {
2487 let request: DeleteSqliteFtsDocumentJsonRequest = parse_json_input(request_json)?;
2488 let binding_id = request.space_id.clone();
2489 let result = with_client_handle(client, |handle| {
2490 handle
2491 .runtime
2492 .block_on(handle.client.delete_sqlite_fts_document(
2493 request.space_id,
2494 binding_id,
2495 request.index_name,
2496 request.id,
2497 ))
2498 .map_err(error_to_string)
2499 })?;
2500 serde_json::to_string(&result).map_err(|error| {
2501 format!("failed to serialize delete_sqlite_fts_document_json response: {error}")
2502 })
2503 })();
2504 match result {
2505 Ok(response) => {
2506 write_string_out(response_out, response);
2507 ffi_ok_status(error_out)
2508 }
2509 Err(error) => ffi_error_status(error_out, error),
2510 }
2511}
2512
2513#[unsafe(no_mangle)]
2516pub extern "C" fn vldb_controller_ffi_sqlite_fts_mutation_result_free(
2517 result: *mut FfiControllerSqliteFtsMutationResult,
2518) {
2519 if !result.is_null() {
2520 unsafe {
2521 let result = Box::from_raw(result);
2522 vldb_controller_ffi_string_free(result.message);
2523 vldb_controller_ffi_string_free(result.index_name);
2524 }
2525 }
2526}
2527
2528#[unsafe(no_mangle)]
2531pub extern "C" fn vldb_controller_ffi_client_search_sqlite_fts(
2532 client: *mut FfiControllerClientHandle,
2533 space_id: *const c_char,
2534 index_name: *const c_char,
2535 tokenizer_mode: *const c_char,
2536 query: *const c_char,
2537 limit: u32,
2538 offset: u32,
2539 result_out: *mut *mut FfiControllerSqliteSearchFtsResult,
2540 error_out: *mut *mut c_char,
2541) -> c_int {
2542 clear_out_ptr(result_out);
2543 let result = (|| -> Result<ControllerSqliteSearchFtsResult, String> {
2544 let space_id = required_c_string(space_id, "space_id")?;
2545 let binding_id = space_id.clone();
2546 let index_name = required_c_string(index_name, "index_name")?;
2547 let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
2548 let query = required_c_string(query, "query")?;
2549 with_client_handle(client, |handle| {
2550 handle
2551 .runtime
2552 .block_on(handle.client.search_sqlite_fts(
2553 space_id,
2554 binding_id,
2555 index_name,
2556 tokenizer_mode,
2557 query,
2558 limit,
2559 offset,
2560 ))
2561 .map_err(error_to_string)
2562 })
2563 })();
2564 match result {
2565 Ok(result) => {
2566 write_boxed_out_ptr(result_out, map_sqlite_search_fts_result(result));
2567 ffi_ok_status(error_out)
2568 }
2569 Err(error) => ffi_error_status(error_out, error),
2570 }
2571}
2572
2573#[unsafe(no_mangle)]
2576pub extern "C" fn vldb_controller_ffi_client_search_sqlite_fts_json(
2577 client: *mut FfiControllerClientHandle,
2578 request_json: *const c_char,
2579 response_out: *mut *mut c_char,
2580 error_out: *mut *mut c_char,
2581) -> c_int {
2582 clear_out_ptr(response_out);
2583 let result = (|| -> Result<String, String> {
2584 let request: SearchSqliteFtsJsonRequest = parse_json_input(request_json)?;
2585 let binding_id = request.space_id.clone();
2586 let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
2587 let result = with_client_handle(client, |handle| {
2588 handle
2589 .runtime
2590 .block_on(handle.client.search_sqlite_fts(
2591 request.space_id,
2592 binding_id,
2593 request.index_name,
2594 tokenizer_mode,
2595 request.query,
2596 request.limit,
2597 request.offset,
2598 ))
2599 .map_err(error_to_string)
2600 })?;
2601 serde_json::to_string(&result).map_err(|error| {
2602 format!("failed to serialize search_sqlite_fts_json response: {error}")
2603 })
2604 })();
2605 match result {
2606 Ok(response) => {
2607 write_string_out(response_out, response);
2608 ffi_ok_status(error_out)
2609 }
2610 Err(error) => ffi_error_status(error_out, error),
2611 }
2612}
2613
2614#[unsafe(no_mangle)]
2617pub extern "C" fn vldb_controller_ffi_sqlite_search_fts_hit_array_free(
2618 value: *mut FfiControllerSqliteSearchFtsHitArray,
2619) {
2620 if value.is_null() {
2621 return;
2622 }
2623 unsafe {
2624 let value = Box::from_raw(value);
2625 if !value.items.is_null() {
2626 let items = Vec::from_raw_parts(value.items, value.len, value.len);
2627 for item in items {
2628 vldb_controller_ffi_string_free(item.id);
2629 vldb_controller_ffi_string_free(item.file_path);
2630 vldb_controller_ffi_string_free(item.title);
2631 vldb_controller_ffi_string_free(item.title_highlight);
2632 vldb_controller_ffi_string_free(item.content_snippet);
2633 }
2634 }
2635 }
2636}
2637
2638#[unsafe(no_mangle)]
2641pub extern "C" fn vldb_controller_ffi_sqlite_search_fts_result_free(
2642 result: *mut FfiControllerSqliteSearchFtsResult,
2643) {
2644 if !result.is_null() {
2645 unsafe {
2646 let result = Box::from_raw(result);
2647 vldb_controller_ffi_string_free(result.message);
2648 vldb_controller_ffi_string_free(result.index_name);
2649 vldb_controller_ffi_string_free(result.tokenizer_mode);
2650 vldb_controller_ffi_string_free(result.normalized_query);
2651 vldb_controller_ffi_string_free(result.fts_query);
2652 vldb_controller_ffi_string_free(result.source);
2653 vldb_controller_ffi_string_free(result.query_mode);
2654 vldb_controller_ffi_sqlite_search_fts_hit_array_free(result.hits);
2655 }
2656 }
2657}
2658
2659#[unsafe(no_mangle)]
2662pub extern "C" fn vldb_controller_ffi_client_enable_lancedb(
2663 client: *mut FfiControllerClientHandle,
2664 request: *const FfiControllerLanceDbEnableRequest,
2665 error_out: *mut *mut c_char,
2666) -> c_int {
2667 let result = (|| -> Result<(), String> {
2668 let request = ffi_lancedb_enable_request_to_rust(request)?;
2669 with_client_handle(client, |handle| {
2670 handle
2671 .runtime
2672 .block_on(handle.client.enable_lancedb(request))
2673 .map_err(error_to_string)
2674 })
2675 })();
2676 match result {
2677 Ok(()) => ffi_ok_status(error_out),
2678 Err(error) => ffi_error_status(error_out, error),
2679 }
2680}
2681
2682#[unsafe(no_mangle)]
2685pub extern "C" fn vldb_controller_ffi_client_enable_lancedb_json(
2686 client: *mut FfiControllerClientHandle,
2687 request_json: *const c_char,
2688 response_out: *mut *mut c_char,
2689 error_out: *mut *mut c_char,
2690) -> c_int {
2691 clear_out_ptr(response_out);
2692 let result = (|| -> Result<String, String> {
2693 let request: EnableLanceDbJsonRequest = parse_json_input(request_json)?;
2694 with_client_handle(client, |handle| {
2695 handle
2696 .runtime
2697 .block_on(handle.client.enable_lancedb(request.request))
2698 .map_err(error_to_string)
2699 })?;
2700 serde_json::to_string(&SuccessJsonResponse { ok: true })
2701 .map_err(|error| format!("failed to serialize enable_lancedb_json response: {error}"))
2702 })();
2703 match result {
2704 Ok(response) => {
2705 write_string_out(response_out, response);
2706 ffi_ok_status(error_out)
2707 }
2708 Err(error) => ffi_error_status(error_out, error),
2709 }
2710}
2711
2712#[unsafe(no_mangle)]
2715pub extern "C" fn vldb_controller_ffi_client_disable_lancedb(
2716 client: *mut FfiControllerClientHandle,
2717 space_id: *const c_char,
2718 disabled_out: *mut c_uchar,
2719 error_out: *mut *mut c_char,
2720) -> c_int {
2721 clear_out_u8(disabled_out);
2722 let result = (|| -> Result<bool, String> {
2723 let space_id = required_c_string(space_id, "space_id")?;
2724 let binding_id = space_id.clone();
2725 with_client_handle(client, |handle| {
2726 handle
2727 .runtime
2728 .block_on(handle.client.disable_lancedb(space_id, binding_id))
2729 .map_err(error_to_string)
2730 })
2731 })();
2732 match result {
2733 Ok(disabled) => {
2734 write_out_u8(disabled_out, if disabled { 1 } else { 0 });
2735 ffi_ok_status(error_out)
2736 }
2737 Err(error) => ffi_error_status(error_out, error),
2738 }
2739}
2740
2741#[unsafe(no_mangle)]
2744pub extern "C" fn vldb_controller_ffi_client_disable_lancedb_json(
2745 client: *mut FfiControllerClientHandle,
2746 request_json: *const c_char,
2747 response_out: *mut *mut c_char,
2748 error_out: *mut *mut c_char,
2749) -> c_int {
2750 clear_out_ptr(response_out);
2751 let result = (|| -> Result<String, String> {
2752 let request: DisableBackendJsonRequest = parse_json_input(request_json)?;
2753 let binding_id = request.space_id.clone();
2754 let disabled = with_client_handle(client, |handle| {
2755 handle
2756 .runtime
2757 .block_on(handle.client.disable_lancedb(request.space_id, binding_id))
2758 .map_err(error_to_string)
2759 })?;
2760 serde_json::to_string(&disabled)
2761 .map_err(|error| format!("failed to serialize disable_lancedb_json response: {error}"))
2762 })();
2763 match result {
2764 Ok(response) => {
2765 write_string_out(response_out, response);
2766 ffi_ok_status(error_out)
2767 }
2768 Err(error) => ffi_error_status(error_out, error),
2769 }
2770}
2771
2772#[unsafe(no_mangle)]
2775pub extern "C" fn vldb_controller_ffi_client_create_lancedb_table(
2776 client: *mut FfiControllerClientHandle,
2777 space_id: *const c_char,
2778 table_name: *const c_char,
2779 columns: *const FfiControllerLanceDbColumnDef,
2780 columns_len: usize,
2781 overwrite_if_exists: c_uchar,
2782 result_out: *mut *mut FfiControllerLanceDbCreateTableResult,
2783 error_out: *mut *mut c_char,
2784) -> c_int {
2785 clear_out_ptr(result_out);
2786 let result = (|| -> Result<ControllerLanceDbCreateTableResult, String> {
2787 let space_id = required_c_string(space_id, "space_id")?;
2788 let binding_id = space_id.clone();
2789 let table_name = required_c_string(table_name, "table_name")?;
2790 let columns = read_lancedb_columns(columns, columns_len, "columns")?;
2791 with_client_handle(client, |handle| {
2792 handle
2793 .runtime
2794 .block_on(handle.client.create_lancedb_table_typed(
2795 space_id,
2796 binding_id,
2797 table_name,
2798 columns,
2799 overwrite_if_exists != 0,
2800 ))
2801 .map_err(error_to_string)
2802 })
2803 })();
2804 match result {
2805 Ok(result) => {
2806 write_boxed_out_ptr(result_out, map_lancedb_create_table_result(result));
2807 ffi_ok_status(error_out)
2808 }
2809 Err(error) => ffi_error_status(error_out, error),
2810 }
2811}
2812
2813#[unsafe(no_mangle)]
2816pub extern "C" fn vldb_controller_ffi_client_create_lancedb_table_json(
2817 client: *mut FfiControllerClientHandle,
2818 request_json: *const c_char,
2819 response_out: *mut *mut c_char,
2820 error_out: *mut *mut c_char,
2821) -> c_int {
2822 clear_out_ptr(response_out);
2823 let result = (|| -> Result<String, String> {
2824 let request: CreateLanceDbTableJsonRequest = parse_json_input(request_json)?;
2825 let space_id = request.space_id.clone();
2826 let binding_id = request.space_id.clone();
2827 let result = with_client_handle(client, |handle| {
2828 let inner_request_json =
2829 serde_json::to_string(&CreateLanceDbTableJsonCompatRequest::from(request))
2830 .map_err(|error| {
2831 format!("failed to serialize create_lancedb_table_json request: {error}")
2832 })?;
2833 handle
2834 .runtime
2835 .block_on(handle.client.create_lancedb_table(
2836 space_id,
2837 binding_id,
2838 inner_request_json,
2839 ))
2840 .map_err(error_to_string)
2841 })?;
2842 serde_json::to_string(&result).map_err(|error| {
2843 format!("failed to serialize create_lancedb_table_json response: {error}")
2844 })
2845 })();
2846 match result {
2847 Ok(response) => {
2848 write_string_out(response_out, response);
2849 ffi_ok_status(error_out)
2850 }
2851 Err(error) => ffi_error_status(error_out, error),
2852 }
2853}
2854
2855#[unsafe(no_mangle)]
2858pub extern "C" fn vldb_controller_ffi_lancedb_create_table_result_free(
2859 result: *mut FfiControllerLanceDbCreateTableResult,
2860) {
2861 if !result.is_null() {
2862 unsafe {
2863 let result = Box::from_raw(result);
2864 vldb_controller_ffi_string_free(result.message);
2865 }
2866 }
2867}
2868
2869#[unsafe(no_mangle)]
2872pub extern "C" fn vldb_controller_ffi_client_upsert_lancedb(
2873 client: *mut FfiControllerClientHandle,
2874 space_id: *const c_char,
2875 table_name: *const c_char,
2876 input_format: c_int,
2877 data: *const u8,
2878 data_len: usize,
2879 key_columns: *const *const c_char,
2880 key_columns_len: usize,
2881 result_out: *mut *mut FfiControllerLanceDbUpsertResult,
2882 error_out: *mut *mut c_char,
2883) -> c_int {
2884 clear_out_ptr(result_out);
2885 let result = (|| -> Result<ControllerLanceDbUpsertResult, String> {
2886 let space_id = required_c_string(space_id, "space_id")?;
2887 let binding_id = space_id.clone();
2888 let table_name = required_c_string(table_name, "table_name")?;
2889 let input_format = map_lancedb_input_format_native(input_format)?;
2890 let data = required_bytes(data, data_len, "data")?.to_vec();
2891 let key_columns = optional_string_array(key_columns, key_columns_len, "key_columns")?;
2892 with_client_handle(client, |handle| {
2893 handle
2894 .runtime
2895 .block_on(handle.client.upsert_lancedb_typed(
2896 space_id,
2897 binding_id,
2898 table_name,
2899 input_format,
2900 data,
2901 key_columns,
2902 ))
2903 .map_err(error_to_string)
2904 })
2905 })();
2906 match result {
2907 Ok(result) => {
2908 write_boxed_out_ptr(result_out, map_lancedb_upsert_result(result));
2909 ffi_ok_status(error_out)
2910 }
2911 Err(error) => ffi_error_status(error_out, error),
2912 }
2913}
2914
2915#[unsafe(no_mangle)]
2918pub extern "C" fn vldb_controller_ffi_client_upsert_lancedb_json(
2919 client: *mut FfiControllerClientHandle,
2920 request_json: *const c_char,
2921 response_out: *mut *mut c_char,
2922 error_out: *mut *mut c_char,
2923) -> c_int {
2924 clear_out_ptr(response_out);
2925 let result = (|| -> Result<String, String> {
2926 let request: UpsertLanceDbJsonRequest = parse_json_input(request_json)?;
2927 let binding_id = request.space_id.clone();
2928 let data = decode_base64(&request.data_base64)?;
2929 let result = with_client_handle(client, |handle| {
2930 let inner_request_json = serde_json::to_string(&UpsertLanceDbJsonCompatRequest::from(
2931 &request,
2932 ))
2933 .map_err(|error| format!("failed to serialize upsert_lancedb_json request: {error}"))?;
2934 handle
2935 .runtime
2936 .block_on(handle.client.upsert_lancedb(
2937 request.space_id,
2938 binding_id,
2939 inner_request_json,
2940 data,
2941 ))
2942 .map_err(error_to_string)
2943 })?;
2944 serde_json::to_string(&result)
2945 .map_err(|error| format!("failed to serialize upsert_lancedb_json response: {error}"))
2946 })();
2947 match result {
2948 Ok(response) => {
2949 write_string_out(response_out, response);
2950 ffi_ok_status(error_out)
2951 }
2952 Err(error) => ffi_error_status(error_out, error),
2953 }
2954}
2955
2956#[unsafe(no_mangle)]
2959pub extern "C" fn vldb_controller_ffi_lancedb_upsert_result_free(
2960 result: *mut FfiControllerLanceDbUpsertResult,
2961) {
2962 if !result.is_null() {
2963 unsafe {
2964 let result = Box::from_raw(result);
2965 vldb_controller_ffi_string_free(result.message);
2966 }
2967 }
2968}
2969
2970#[unsafe(no_mangle)]
2973pub extern "C" fn vldb_controller_ffi_client_search_lancedb(
2974 client: *mut FfiControllerClientHandle,
2975 space_id: *const c_char,
2976 table_name: *const c_char,
2977 vector: *const c_float,
2978 vector_len: usize,
2979 limit: u32,
2980 filter: *const c_char,
2981 vector_column: *const c_char,
2982 output_format: c_int,
2983 result_out: *mut *mut FfiControllerLanceDbSearchResult,
2984 error_out: *mut *mut c_char,
2985) -> c_int {
2986 clear_out_ptr(result_out);
2987 let result = (|| -> Result<ControllerLanceDbSearchResult, String> {
2988 let space_id = required_c_string(space_id, "space_id")?;
2989 let binding_id = space_id.clone();
2990 let table_name = required_c_string(table_name, "table_name")?;
2991 let vector = required_f32_slice(vector, vector_len, "vector")?.to_vec();
2992 let filter = optional_c_string(filter)?.unwrap_or_default();
2993 let vector_column = optional_c_string(vector_column)?.unwrap_or_default();
2994 let output_format = map_lancedb_output_format_native(output_format)?;
2995 with_client_handle(client, |handle| {
2996 handle
2997 .runtime
2998 .block_on(handle.client.search_lancedb_typed(
2999 space_id,
3000 binding_id,
3001 table_name,
3002 vector,
3003 limit,
3004 filter,
3005 vector_column,
3006 output_format,
3007 ))
3008 .map_err(error_to_string)
3009 })
3010 })();
3011 match result {
3012 Ok(result) => {
3013 write_boxed_out_ptr(result_out, map_lancedb_search_result(result));
3014 ffi_ok_status(error_out)
3015 }
3016 Err(error) => ffi_error_status(error_out, error),
3017 }
3018}
3019
3020#[unsafe(no_mangle)]
3023pub extern "C" fn vldb_controller_ffi_client_search_lancedb_json(
3024 client: *mut FfiControllerClientHandle,
3025 request_json: *const c_char,
3026 response_out: *mut *mut c_char,
3027 error_out: *mut *mut c_char,
3028) -> c_int {
3029 clear_out_ptr(response_out);
3030 let result = (|| -> Result<String, String> {
3031 let request: SearchLanceDbJsonRequest = parse_json_input(request_json)?;
3032 let binding_id = request.space_id.clone();
3033 let result = with_client_handle(client, |handle| {
3034 let inner_request_json = serde_json::to_string(&SearchLanceDbJsonCompatRequest::from(
3035 &request,
3036 ))
3037 .map_err(|error| format!("failed to serialize search_lancedb_json request: {error}"))?;
3038 handle
3039 .runtime
3040 .block_on(handle.client.search_lancedb(
3041 request.space_id,
3042 binding_id,
3043 inner_request_json,
3044 ))
3045 .map_err(error_to_string)
3046 })?;
3047 serde_json::to_string(&SearchLanceDbJsonResponse::from(result))
3048 .map_err(|error| format!("failed to serialize search_lancedb_json response: {error}"))
3049 })();
3050 match result {
3051 Ok(response) => {
3052 write_string_out(response_out, response);
3053 ffi_ok_status(error_out)
3054 }
3055 Err(error) => ffi_error_status(error_out, error),
3056 }
3057}
3058
3059#[unsafe(no_mangle)]
3062pub extern "C" fn vldb_controller_ffi_lancedb_search_result_free(
3063 result: *mut FfiControllerLanceDbSearchResult,
3064) {
3065 if !result.is_null() {
3066 unsafe {
3067 let result = Box::from_raw(result);
3068 vldb_controller_ffi_string_free(result.message);
3069 vldb_controller_ffi_string_free(result.format);
3070 vldb_controller_ffi_bytes_free(result.data, result.data_len);
3071 }
3072 }
3073}
3074
3075#[unsafe(no_mangle)]
3078pub extern "C" fn vldb_controller_ffi_client_delete_lancedb(
3079 client: *mut FfiControllerClientHandle,
3080 space_id: *const c_char,
3081 table_name: *const c_char,
3082 condition: *const c_char,
3083 result_out: *mut *mut FfiControllerLanceDbDeleteResult,
3084 error_out: *mut *mut c_char,
3085) -> c_int {
3086 clear_out_ptr(result_out);
3087 let result = (|| -> Result<ControllerLanceDbDeleteResult, String> {
3088 let space_id = required_c_string(space_id, "space_id")?;
3089 let binding_id = space_id.clone();
3090 let table_name = required_c_string(table_name, "table_name")?;
3091 let condition = required_c_string(condition, "condition")?;
3092 with_client_handle(client, |handle| {
3093 handle
3094 .runtime
3095 .block_on(
3096 handle
3097 .client
3098 .delete_lancedb_typed(space_id, binding_id, table_name, condition),
3099 )
3100 .map_err(error_to_string)
3101 })
3102 })();
3103 match result {
3104 Ok(result) => {
3105 write_boxed_out_ptr(result_out, map_lancedb_delete_result(result));
3106 ffi_ok_status(error_out)
3107 }
3108 Err(error) => ffi_error_status(error_out, error),
3109 }
3110}
3111
3112#[unsafe(no_mangle)]
3115pub extern "C" fn vldb_controller_ffi_client_delete_lancedb_json(
3116 client: *mut FfiControllerClientHandle,
3117 request_json: *const c_char,
3118 response_out: *mut *mut c_char,
3119 error_out: *mut *mut c_char,
3120) -> c_int {
3121 clear_out_ptr(response_out);
3122 let result = (|| -> Result<String, String> {
3123 let request: DeleteLanceDbJsonRequest = parse_json_input(request_json)?;
3124 let binding_id = request.space_id.clone();
3125 let result = with_client_handle(client, |handle| {
3126 let inner_request_json = serde_json::to_string(&DeleteLanceDbJsonCompatRequest::from(
3127 &request,
3128 ))
3129 .map_err(|error| format!("failed to serialize delete_lancedb_json request: {error}"))?;
3130 handle
3131 .runtime
3132 .block_on(handle.client.delete_lancedb(
3133 request.space_id,
3134 binding_id,
3135 inner_request_json,
3136 ))
3137 .map_err(error_to_string)
3138 })?;
3139 serde_json::to_string(&result)
3140 .map_err(|error| format!("failed to serialize delete_lancedb_json response: {error}"))
3141 })();
3142 match result {
3143 Ok(response) => {
3144 write_string_out(response_out, response);
3145 ffi_ok_status(error_out)
3146 }
3147 Err(error) => ffi_error_status(error_out, error),
3148 }
3149}
3150
3151#[unsafe(no_mangle)]
3154pub extern "C" fn vldb_controller_ffi_lancedb_delete_result_free(
3155 result: *mut FfiControllerLanceDbDeleteResult,
3156) {
3157 if !result.is_null() {
3158 unsafe {
3159 let result = Box::from_raw(result);
3160 vldb_controller_ffi_string_free(result.message);
3161 }
3162 }
3163}
3164
3165#[unsafe(no_mangle)]
3168pub extern "C" fn vldb_controller_ffi_client_drop_lancedb_table(
3169 client: *mut FfiControllerClientHandle,
3170 space_id: *const c_char,
3171 table_name: *const c_char,
3172 result_out: *mut *mut FfiControllerLanceDbDropTableResult,
3173 error_out: *mut *mut c_char,
3174) -> c_int {
3175 clear_out_ptr(result_out);
3176 let result = (|| -> Result<ControllerLanceDbDropTableResult, String> {
3177 let space_id = required_c_string(space_id, "space_id")?;
3178 let binding_id = space_id.clone();
3179 let table_name = required_c_string(table_name, "table_name")?;
3180 with_client_handle(client, |handle| {
3181 handle
3182 .runtime
3183 .block_on(
3184 handle
3185 .client
3186 .drop_lancedb_table(space_id, binding_id, table_name),
3187 )
3188 .map_err(error_to_string)
3189 })
3190 })();
3191 match result {
3192 Ok(result) => {
3193 write_boxed_out_ptr(result_out, map_lancedb_drop_table_result(result));
3194 ffi_ok_status(error_out)
3195 }
3196 Err(error) => ffi_error_status(error_out, error),
3197 }
3198}
3199
3200#[unsafe(no_mangle)]
3203pub extern "C" fn vldb_controller_ffi_client_drop_lancedb_table_json(
3204 client: *mut FfiControllerClientHandle,
3205 request_json: *const c_char,
3206 response_out: *mut *mut c_char,
3207 error_out: *mut *mut c_char,
3208) -> c_int {
3209 clear_out_ptr(response_out);
3210 let result = (|| -> Result<String, String> {
3211 let request: DropLanceDbTableJsonRequest = parse_json_input(request_json)?;
3212 let binding_id = request.space_id.clone();
3213 let result = with_client_handle(client, |handle| {
3214 handle
3215 .runtime
3216 .block_on(handle.client.drop_lancedb_table(
3217 request.space_id,
3218 binding_id,
3219 request.table_name,
3220 ))
3221 .map_err(error_to_string)
3222 })?;
3223 serde_json::to_string(&result).map_err(|error| {
3224 format!("failed to serialize drop_lancedb_table_json response: {error}")
3225 })
3226 })();
3227 match result {
3228 Ok(response) => {
3229 write_string_out(response_out, response);
3230 ffi_ok_status(error_out)
3231 }
3232 Err(error) => ffi_error_status(error_out, error),
3233 }
3234}
3235
3236#[unsafe(no_mangle)]
3239pub extern "C" fn vldb_controller_ffi_lancedb_drop_table_result_free(
3240 result: *mut FfiControllerLanceDbDropTableResult,
3241) {
3242 if !result.is_null() {
3243 unsafe {
3244 let result = Box::from_raw(result);
3245 vldb_controller_ffi_string_free(result.message);
3246 }
3247 }
3248}
3249
3250#[derive(Serialize)]
3253struct SearchLanceDbJsonResponse {
3254 message: String,
3255 format: String,
3256 rows: u64,
3257 data_base64: String,
3258}
3259
3260impl From<ControllerLanceDbSearchResult> for SearchLanceDbJsonResponse {
3261 fn from(value: ControllerLanceDbSearchResult) -> Self {
3264 Self {
3265 message: value.message,
3266 format: value.format,
3267 rows: value.rows,
3268 data_base64: encode_base64(&value.data),
3269 }
3270 }
3271}
3272
3273fn build_client_handle(
3276 config: ControllerClientConfig,
3277 registration: ClientRegistration,
3278) -> Result<*mut FfiControllerClientHandle, String> {
3279 let runtime =
3280 Runtime::new().map_err(|error| format!("failed to create tokio runtime: {error}"))?;
3281 let client = ControllerClient::new(config, registration);
3282 register_client_handle(FfiControllerClientHandleInner { runtime, client })
3283}
3284
3285fn native_client_create(
3288 config: *const FfiControllerClientConfig,
3289 registration: *const FfiClientRegistration,
3290) -> Result<*mut FfiControllerClientHandle, String> {
3291 let config = ffi_client_config_to_rust(config)?;
3292 let registration = ffi_client_registration_to_rust(registration)?;
3293 build_client_handle(config, registration)
3294}
3295
3296fn ffi_client_handle_registry()
3299-> &'static Mutex<BTreeMap<usize, Arc<FfiControllerClientHandleState>>> {
3300 FFI_CLIENT_HANDLE_REGISTRY.get_or_init(|| Mutex::new(BTreeMap::new()))
3301}
3302
3303fn ffi_client_handle_id_from_ptr(client: *mut FfiControllerClientHandle) -> Result<usize, String> {
3306 if client.is_null() {
3307 return Err("client handle pointer must not be null".to_string());
3308 }
3309 Ok(client as usize)
3310}
3311
3312fn register_client_handle(
3315 inner: FfiControllerClientHandleInner,
3316) -> Result<*mut FfiControllerClientHandle, String> {
3317 let state = Arc::new(FfiControllerClientHandleState {
3318 inner: Mutex::new(Some(inner)),
3319 });
3320 let mut registry = ffi_client_handle_registry()
3321 .lock()
3322 .map_err(|poisoned| format!("ffi client handle registry lock poisoned: {poisoned}"))?;
3323 let handle_ptr = Box::into_raw(Box::new(FfiControllerClientHandle { _private: 0 }));
3324 let handle_id = handle_ptr as usize;
3325 registry.insert(handle_id, state);
3326 Ok(handle_ptr)
3327}
3328
3329fn ffi_client_config_to_rust(
3332 config: *const FfiControllerClientConfig,
3333) -> Result<ControllerClientConfig, String> {
3334 if config.is_null() {
3335 return Err("config pointer must not be null".to_string());
3336 }
3337 let config = unsafe { &*config };
3338 let defaults = ControllerClientConfig::default();
3339 Ok(ControllerClientConfig {
3340 endpoint: optional_c_string(config.endpoint)?.unwrap_or(defaults.endpoint),
3341 auto_spawn: config.auto_spawn != 0,
3342 spawn_executable: optional_c_string(config.spawn_executable)?,
3343 spawn_process_mode: map_process_mode_native(config.spawn_process_mode)?,
3344 minimum_uptime_secs: default_u64(config.minimum_uptime_secs, defaults.minimum_uptime_secs),
3345 idle_timeout_secs: default_u64(config.idle_timeout_secs, defaults.idle_timeout_secs),
3346 default_lease_ttl_secs: default_u64(
3347 config.default_lease_ttl_secs,
3348 defaults.default_lease_ttl_secs,
3349 ),
3350 connect_timeout_secs: default_u64(
3351 config.connect_timeout_secs,
3352 defaults.connect_timeout_secs,
3353 ),
3354 startup_timeout_secs: default_u64(
3355 config.startup_timeout_secs,
3356 defaults.startup_timeout_secs,
3357 ),
3358 startup_retry_interval_ms: default_u64(
3359 config.startup_retry_interval_ms,
3360 defaults.startup_retry_interval_ms,
3361 ),
3362 lease_renew_interval_secs: default_u64(
3363 config.lease_renew_interval_secs,
3364 defaults.lease_renew_interval_secs,
3365 ),
3366 })
3367}
3368
3369fn ffi_client_registration_to_rust(
3372 registration: *const FfiClientRegistration,
3373) -> Result<ClientRegistration, String> {
3374 if registration.is_null() {
3375 return Err("registration pointer must not be null".to_string());
3376 }
3377 let registration = unsafe { &*registration };
3378 Ok(ClientRegistration {
3379 client_name: required_c_string(registration.client_name, "client_name")?,
3380 host_kind: required_c_string(registration.host_kind, "host_kind")?,
3381 process_id: registration.process_id,
3382 process_name: required_c_string(registration.process_name, "process_name")?,
3383 lease_ttl_secs: optional_nonzero_u64(registration.lease_ttl_secs),
3384 })
3385}
3386
3387fn ffi_space_registration_to_rust(
3390 registration: *const FfiSpaceRegistration,
3391) -> Result<SpaceRegistration, String> {
3392 if registration.is_null() {
3393 return Err("registration pointer must not be null".to_string());
3394 }
3395 let registration = unsafe { &*registration };
3396 Ok(SpaceRegistration {
3397 space_id: required_c_string(registration.space_id, "space_id")?,
3398 space_label: required_c_string(registration.space_label, "space_label")?,
3399 space_kind: map_space_kind_native(registration.space_kind)?,
3400 space_root: required_c_string(registration.space_root, "space_root")?,
3401 })
3402}
3403
3404fn ffi_sqlite_enable_request_to_rust(
3407 request: *const FfiControllerSqliteEnableRequest,
3408) -> Result<ControllerSqliteEnableRequest, String> {
3409 if request.is_null() {
3410 return Err("sqlite enable request pointer must not be null".to_string());
3411 }
3412 let request = unsafe { &*request };
3413 let defaults = ControllerSqliteEnableRequest::default();
3414 let space_id = required_c_string(request.space_id, "space_id")?;
3415 Ok(ControllerSqliteEnableRequest {
3416 space_id: space_id.clone(),
3417 binding_id: space_id,
3418 db_path: required_c_string(request.db_path, "db_path")?,
3419 connection_pool_size: default_u64(
3420 request.connection_pool_size,
3421 defaults.connection_pool_size as u64,
3422 ) as usize,
3423 busy_timeout_ms: default_u64(request.busy_timeout_ms, defaults.busy_timeout_ms),
3424 journal_mode: optional_c_string(request.journal_mode)?.unwrap_or(defaults.journal_mode),
3425 synchronous: optional_c_string(request.synchronous)?.unwrap_or(defaults.synchronous),
3426 foreign_keys: request.foreign_keys != 0,
3427 temp_store: optional_c_string(request.temp_store)?.unwrap_or(defaults.temp_store),
3428 wal_autocheckpoint_pages: if request.wal_autocheckpoint_pages == 0 {
3429 defaults.wal_autocheckpoint_pages
3430 } else {
3431 request.wal_autocheckpoint_pages
3432 },
3433 cache_size_kib: if request.cache_size_kib == 0 {
3434 defaults.cache_size_kib
3435 } else {
3436 request.cache_size_kib
3437 },
3438 mmap_size_bytes: default_u64(request.mmap_size_bytes, defaults.mmap_size_bytes),
3439 enforce_db_file_lock: request.enforce_db_file_lock != 0,
3440 read_only: request.read_only != 0,
3441 allow_uri_filenames: request.allow_uri_filenames != 0,
3442 trusted_schema: request.trusted_schema != 0,
3443 defensive: request.defensive != 0,
3444 })
3445}
3446
3447fn ffi_lancedb_enable_request_to_rust(
3450 request: *const FfiControllerLanceDbEnableRequest,
3451) -> Result<ControllerLanceDbEnableRequest, String> {
3452 if request.is_null() {
3453 return Err("lancedb enable request pointer must not be null".to_string());
3454 }
3455 let request = unsafe { &*request };
3456 let defaults = ControllerLanceDbEnableRequest::default();
3457 let space_id = required_c_string(request.space_id, "space_id")?;
3458 Ok(ControllerLanceDbEnableRequest {
3459 space_id: space_id.clone(),
3460 binding_id: space_id,
3461 default_db_path: required_c_string(request.default_db_path, "default_db_path")?,
3462 db_root: optional_c_string(request.db_root)?,
3463 read_consistency_interval_ms: optional_nonzero_u64(request.read_consistency_interval_ms),
3464 max_upsert_payload: default_u64(
3465 request.max_upsert_payload,
3466 defaults.max_upsert_payload as u64,
3467 ) as usize,
3468 max_search_limit: default_u64(request.max_search_limit, defaults.max_search_limit as u64)
3469 as usize,
3470 max_concurrent_requests: default_u64(
3471 request.max_concurrent_requests,
3472 defaults.max_concurrent_requests as u64,
3473 ) as usize,
3474 })
3475}
3476
3477fn map_status_snapshot(snapshot: ControllerStatusSnapshot) -> FfiControllerStatusSnapshot {
3480 FfiControllerStatusSnapshot {
3481 process_mode: map_process_mode_to_native(snapshot.process_mode),
3482 bind_addr: string_into_raw(snapshot.bind_addr),
3483 started_at_unix_ms: snapshot.started_at_unix_ms,
3484 last_request_at_unix_ms: snapshot.last_request_at_unix_ms,
3485 minimum_uptime_secs: snapshot.minimum_uptime_secs,
3486 idle_timeout_secs: snapshot.idle_timeout_secs,
3487 default_lease_ttl_secs: snapshot.default_lease_ttl_secs,
3488 active_clients: snapshot.active_clients as c_ulonglong,
3489 attached_spaces: snapshot.attached_spaces as c_ulonglong,
3490 inflight_requests: snapshot.inflight_requests as c_ulonglong,
3491 shutdown_candidate: if snapshot.shutdown_candidate { 1 } else { 0 },
3492 }
3493}
3494
3495fn map_space_snapshot(snapshot: SpaceSnapshot) -> FfiSpaceSnapshot {
3498 FfiSpaceSnapshot {
3499 space_id: string_into_raw(snapshot.space_id),
3500 space_label: string_into_raw(snapshot.space_label),
3501 space_kind: map_space_kind_to_native(snapshot.space_kind),
3502 space_root: string_into_raw(snapshot.space_root),
3503 attached_clients: snapshot.attached_clients as c_ulonglong,
3504 sqlite: map_backend_status_option(snapshot.sqlite),
3505 lancedb: map_backend_status_option(snapshot.lancedb),
3506 }
3507}
3508
3509fn map_space_snapshot_array(spaces: Vec<SpaceSnapshot>) -> FfiSpaceSnapshotArray {
3512 let mut mapped: Vec<FfiSpaceSnapshot> = spaces.into_iter().map(map_space_snapshot).collect();
3513 let items = mapped.as_mut_ptr();
3514 let len = mapped.len();
3515 std::mem::forget(mapped);
3516 FfiSpaceSnapshotArray { items, len }
3517}
3518
3519fn map_backend_status_option(status: Option<SpaceBackendStatus>) -> *mut FfiSpaceBackendStatus {
3522 status
3523 .map(|status| {
3524 Box::into_raw(Box::new(FfiSpaceBackendStatus {
3525 enabled: if status.enabled { 1 } else { 0 },
3526 mode: string_into_raw(status.mode),
3527 target: string_into_raw(status.target),
3528 }))
3529 })
3530 .unwrap_or(ptr::null_mut())
3531}
3532
3533fn map_sqlite_execute_result(
3536 result: ControllerSqliteExecuteResult,
3537) -> FfiControllerSqliteExecuteResult {
3538 FfiControllerSqliteExecuteResult {
3539 success: if result.success { 1 } else { 0 },
3540 message: string_into_raw(result.message),
3541 rows_changed: result.rows_changed,
3542 last_insert_rowid: result.last_insert_rowid,
3543 }
3544}
3545
3546fn map_sqlite_query_result(result: ControllerSqliteQueryResult) -> FfiControllerSqliteQueryResult {
3549 FfiControllerSqliteQueryResult {
3550 json_data: string_into_raw(result.json_data),
3551 row_count: result.row_count,
3552 }
3553}
3554
3555fn map_sqlite_execute_batch_result(
3558 result: ControllerSqliteExecuteBatchResult,
3559) -> FfiControllerSqliteExecuteBatchResult {
3560 FfiControllerSqliteExecuteBatchResult {
3561 success: if result.success { 1 } else { 0 },
3562 message: string_into_raw(result.message),
3563 rows_changed: result.rows_changed,
3564 last_insert_rowid: result.last_insert_rowid,
3565 statements_executed: result.statements_executed,
3566 }
3567}
3568
3569fn map_sqlite_query_stream_result(
3572 result: ControllerSqliteQueryStreamCompatResult,
3573) -> FfiControllerSqliteQueryStreamResult {
3574 let chunks = map_byte_buffer_array(result.chunks);
3575 FfiControllerSqliteQueryStreamResult {
3576 chunks: Box::into_raw(Box::new(chunks)),
3577 row_count: result.row_count,
3578 chunk_count: result.chunk_count,
3579 total_bytes: result.total_bytes,
3580 }
3581}
3582
3583fn collect_sqlite_query_stream_result(
3586 runtime: &Runtime,
3587 client: &ControllerClient,
3588 space_id: String,
3589 binding_id: String,
3590 sql: String,
3591 params: Vec<ControllerSqliteValue>,
3592 chunk_size: Option<u64>,
3593) -> Result<ControllerSqliteQueryStreamCompatResult, String> {
3594 let stream = runtime
3595 .block_on(
3596 client.open_sqlite_query_stream_typed(space_id, binding_id, sql, params, chunk_size),
3597 )
3598 .map_err(error_to_string)?;
3599 let stream_id = stream.stream_id;
3600
3601 let collected = (|| -> Result<ControllerSqliteQueryStreamCompatResult, String> {
3602 let metrics = runtime
3603 .block_on(client.wait_sqlite_query_stream_metrics(stream_id))
3604 .map_err(error_to_string)?;
3605 let mut chunks = Vec::with_capacity(metrics.chunk_count as usize);
3606 for index in 0..metrics.chunk_count {
3607 let chunk = runtime
3608 .block_on(client.read_sqlite_query_stream_chunk(stream_id, index))
3609 .map_err(error_to_string)?;
3610 chunks.push(chunk);
3611 }
3612 Ok(ControllerSqliteQueryStreamCompatResult {
3613 chunks,
3614 row_count: metrics.row_count,
3615 chunk_count: metrics.chunk_count,
3616 total_bytes: metrics.total_bytes,
3617 })
3618 })();
3619
3620 let _ = runtime.block_on(client.close_sqlite_query_stream(stream_id));
3621 collected
3622}
3623
3624fn map_sqlite_tokenize_result(
3627 result: ControllerSqliteTokenizeResult,
3628) -> FfiControllerSqliteTokenizeResult {
3629 FfiControllerSqliteTokenizeResult {
3630 tokenizer_mode: string_into_raw(result.tokenizer_mode),
3631 normalized_text: string_into_raw(result.normalized_text),
3632 tokens_json: string_into_raw(
3633 serde_json::to_string(&result.tokens).expect("token list serialization must not fail"),
3634 ),
3635 fts_query: string_into_raw(result.fts_query),
3636 }
3637}
3638
3639fn map_sqlite_list_custom_words_result(
3642 result: ControllerSqliteListCustomWordsResult,
3643) -> FfiControllerSqliteListCustomWordsResult {
3644 FfiControllerSqliteListCustomWordsResult {
3645 success: if result.success { 1 } else { 0 },
3646 message: string_into_raw(result.message),
3647 words: Box::into_raw(Box::new(map_custom_word_array(result.words))),
3648 }
3649}
3650
3651fn map_sqlite_dictionary_mutation_result(
3654 result: ControllerSqliteDictionaryMutationResult,
3655) -> FfiControllerSqliteDictionaryMutationResult {
3656 FfiControllerSqliteDictionaryMutationResult {
3657 success: if result.success { 1 } else { 0 },
3658 message: string_into_raw(result.message),
3659 affected_rows: result.affected_rows,
3660 }
3661}
3662
3663fn map_sqlite_ensure_fts_index_result(
3666 result: ControllerSqliteEnsureFtsIndexResult,
3667) -> FfiControllerSqliteEnsureFtsIndexResult {
3668 FfiControllerSqliteEnsureFtsIndexResult {
3669 success: if result.success { 1 } else { 0 },
3670 message: string_into_raw(result.message),
3671 index_name: string_into_raw(result.index_name),
3672 tokenizer_mode: string_into_raw(result.tokenizer_mode),
3673 }
3674}
3675
3676fn map_sqlite_rebuild_fts_index_result(
3679 result: ControllerSqliteRebuildFtsIndexResult,
3680) -> FfiControllerSqliteRebuildFtsIndexResult {
3681 FfiControllerSqliteRebuildFtsIndexResult {
3682 success: if result.success { 1 } else { 0 },
3683 message: string_into_raw(result.message),
3684 index_name: string_into_raw(result.index_name),
3685 tokenizer_mode: string_into_raw(result.tokenizer_mode),
3686 reindexed_rows: result.reindexed_rows,
3687 }
3688}
3689
3690fn map_sqlite_fts_mutation_result(
3693 result: ControllerSqliteFtsMutationResult,
3694) -> FfiControllerSqliteFtsMutationResult {
3695 FfiControllerSqliteFtsMutationResult {
3696 success: if result.success { 1 } else { 0 },
3697 message: string_into_raw(result.message),
3698 affected_rows: result.affected_rows,
3699 index_name: string_into_raw(result.index_name),
3700 }
3701}
3702
3703fn map_sqlite_search_fts_result(
3706 result: ControllerSqliteSearchFtsResult,
3707) -> FfiControllerSqliteSearchFtsResult {
3708 FfiControllerSqliteSearchFtsResult {
3709 success: if result.success { 1 } else { 0 },
3710 message: string_into_raw(result.message),
3711 index_name: string_into_raw(result.index_name),
3712 tokenizer_mode: string_into_raw(result.tokenizer_mode),
3713 normalized_query: string_into_raw(result.normalized_query),
3714 fts_query: string_into_raw(result.fts_query),
3715 source: string_into_raw(result.source),
3716 query_mode: string_into_raw(result.query_mode),
3717 total: result.total,
3718 hits: Box::into_raw(Box::new(map_search_fts_hit_array(result.hits))),
3719 }
3720}
3721
3722fn map_lancedb_create_table_result(
3725 result: ControllerLanceDbCreateTableResult,
3726) -> FfiControllerLanceDbCreateTableResult {
3727 FfiControllerLanceDbCreateTableResult {
3728 message: string_into_raw(result.message),
3729 }
3730}
3731
3732fn map_lancedb_upsert_result(
3735 result: ControllerLanceDbUpsertResult,
3736) -> FfiControllerLanceDbUpsertResult {
3737 FfiControllerLanceDbUpsertResult {
3738 message: string_into_raw(result.message),
3739 version: result.version,
3740 input_rows: result.input_rows,
3741 inserted_rows: result.inserted_rows,
3742 updated_rows: result.updated_rows,
3743 deleted_rows: result.deleted_rows,
3744 }
3745}
3746
3747fn map_lancedb_search_result(
3750 result: ControllerLanceDbSearchResult,
3751) -> FfiControllerLanceDbSearchResult {
3752 let (data, data_len) = bytes_into_raw(result.data);
3753 FfiControllerLanceDbSearchResult {
3754 message: string_into_raw(result.message),
3755 format: string_into_raw(result.format),
3756 rows: result.rows,
3757 data,
3758 data_len,
3759 }
3760}
3761
3762fn map_lancedb_delete_result(
3765 result: ControllerLanceDbDeleteResult,
3766) -> FfiControllerLanceDbDeleteResult {
3767 FfiControllerLanceDbDeleteResult {
3768 message: string_into_raw(result.message),
3769 version: result.version,
3770 deleted_rows: result.deleted_rows,
3771 }
3772}
3773
3774fn map_lancedb_drop_table_result(
3777 result: ControllerLanceDbDropTableResult,
3778) -> FfiControllerLanceDbDropTableResult {
3779 FfiControllerLanceDbDropTableResult {
3780 message: string_into_raw(result.message),
3781 }
3782}
3783
3784fn map_byte_buffer_array(chunks: Vec<Vec<u8>>) -> FfiByteBufferArray {
3787 let mut items: Vec<FfiByteBuffer> = chunks
3788 .into_iter()
3789 .map(|chunk| {
3790 let (data, len) = bytes_into_raw(chunk);
3791 FfiByteBuffer { data, len }
3792 })
3793 .collect();
3794 let ptr = items.as_mut_ptr();
3795 let len = items.len();
3796 std::mem::forget(items);
3797 FfiByteBufferArray { items: ptr, len }
3798}
3799
3800fn map_custom_word_array(
3803 words: Vec<ControllerSqliteCustomWordEntry>,
3804) -> FfiControllerSqliteCustomWordArray {
3805 let mut items: Vec<FfiControllerSqliteCustomWordEntry> = words
3806 .into_iter()
3807 .map(|entry| FfiControllerSqliteCustomWordEntry {
3808 word: string_into_raw(entry.word),
3809 weight: entry.weight as c_ulonglong,
3810 })
3811 .collect();
3812 let ptr = items.as_mut_ptr();
3813 let len = items.len();
3814 std::mem::forget(items);
3815 FfiControllerSqliteCustomWordArray { items: ptr, len }
3816}
3817
3818fn map_search_fts_hit_array(
3821 hits: Vec<ControllerSqliteSearchFtsHit>,
3822) -> FfiControllerSqliteSearchFtsHitArray {
3823 let mut items: Vec<FfiControllerSqliteSearchFtsHit> = hits
3824 .into_iter()
3825 .map(|hit| FfiControllerSqliteSearchFtsHit {
3826 id: string_into_raw(hit.id),
3827 file_path: string_into_raw(hit.file_path),
3828 title: string_into_raw(hit.title),
3829 title_highlight: string_into_raw(hit.title_highlight),
3830 content_snippet: string_into_raw(hit.content_snippet),
3831 score: hit.score,
3832 rank: hit.rank,
3833 raw_score: hit.raw_score,
3834 })
3835 .collect();
3836 let ptr = items.as_mut_ptr();
3837 let len = items.len();
3838 std::mem::forget(items);
3839 FfiControllerSqliteSearchFtsHitArray { items: ptr, len }
3840}
3841
3842fn required_c_string(value: *const c_char, field_name: &str) -> Result<String, String> {
3845 if value.is_null() {
3846 return Err(format!("{field_name} pointer must not be null"));
3847 }
3848 let value = unsafe { CStr::from_ptr(value) };
3849 let text = value
3850 .to_str()
3851 .map_err(|error| format!("{field_name} must be valid UTF-8: {error}"))?
3852 .trim()
3853 .to_string();
3854 if text.is_empty() {
3855 return Err(format!("{field_name} must not be empty"));
3856 }
3857 Ok(text)
3858}
3859
3860fn required_c_string_preserve(value: *const c_char, field_name: &str) -> Result<String, String> {
3863 if value.is_null() {
3864 return Err(format!("{field_name} pointer must not be null"));
3865 }
3866 let value = unsafe { CStr::from_ptr(value) };
3867 value
3868 .to_str()
3869 .map(|text| text.to_string())
3870 .map_err(|error| format!("{field_name} must be valid UTF-8: {error}"))
3871}
3872
3873fn required_string_array(
3876 items: *const *const c_char,
3877 len: usize,
3878 field_name: &str,
3879) -> Result<Vec<String>, String> {
3880 if items.is_null() {
3881 return Err(format!("{field_name} pointer must not be null"));
3882 }
3883 let slice = unsafe { std::slice::from_raw_parts(items, len) };
3884 slice
3885 .iter()
3886 .enumerate()
3887 .map(|(index, item)| required_c_string(*item, &format!("{field_name}[{index}]")))
3888 .collect()
3889}
3890
3891fn optional_c_string(value: *const c_char) -> Result<Option<String>, String> {
3894 if value.is_null() {
3895 return Ok(None);
3896 }
3897 let value = unsafe { CStr::from_ptr(value) };
3898 let text = value
3899 .to_str()
3900 .map_err(|error| format!("optional string must be valid UTF-8: {error}"))?
3901 .trim()
3902 .to_string();
3903 if text.is_empty() {
3904 Ok(None)
3905 } else {
3906 Ok(Some(text))
3907 }
3908}
3909
3910fn parse_sqlite_tokenizer_mode_text(
3913 value: *const c_char,
3914) -> Result<ControllerSqliteTokenizerMode, String> {
3915 let value = required_c_string(value, "tokenizer_mode")?;
3916 parse_sqlite_tokenizer_mode_name(&value)
3917}
3918
3919fn parse_sqlite_tokenizer_mode_name(value: &str) -> Result<ControllerSqliteTokenizerMode, String> {
3922 match value.trim().to_ascii_lowercase().as_str() {
3923 "" | "none" => Ok(ControllerSqliteTokenizerMode::None),
3924 "jieba" => Ok(ControllerSqliteTokenizerMode::Jieba),
3925 other => Err(format!("unsupported tokenizer_mode: {other}")),
3926 }
3927}
3928
3929fn json_to_sqlite_value(value: JsonValue) -> Result<ControllerSqliteValue, String> {
3932 match value {
3933 JsonValue::Null => Ok(ControllerSqliteValue::Null),
3934 JsonValue::Bool(value) => Ok(ControllerSqliteValue::Bool(value)),
3935 JsonValue::Number(value) => {
3936 if let Some(value) = value.as_i64() {
3937 Ok(ControllerSqliteValue::Int64(value))
3938 } else if let Some(value) = value.as_u64() {
3939 Ok(ControllerSqliteValue::Int64(i64::try_from(value).map_err(
3940 |_| "params contains an unsigned integer larger than i64".to_string(),
3941 )?))
3942 } else if let Some(value) = value.as_f64() {
3943 Ok(ControllerSqliteValue::Float64(value))
3944 } else {
3945 Err("params contains an unsupported numeric value".to_string())
3946 }
3947 }
3948 JsonValue::String(value) => Ok(ControllerSqliteValue::String(value)),
3949 JsonValue::Object(value) => {
3950 let wrapper = serde_json::from_value::<JsonSqliteBytesValue>(JsonValue::Object(value))
3951 .map_err(|_| {
3952 "params object values must use {\"type\":\"bytes_base64\",\"base64\":\"...\"} or {\"__type\":\"bytes_base64\",\"base64\":\"...\"}".to_string()
3953 })?;
3954 let wrapper_type = if !wrapper.r#type.trim().is_empty() {
3955 wrapper.r#type.trim()
3956 } else {
3957 wrapper.__type.trim()
3958 };
3959 if wrapper_type != "bytes_base64" {
3960 return Err(
3961 "params object values only support the bytes_base64 wrapper type".to_string(),
3962 );
3963 }
3964 Ok(ControllerSqliteValue::Bytes(decode_base64(
3965 &wrapper.base64,
3966 )?))
3967 }
3968 JsonValue::Array(_) => {
3969 Err("params only supports scalar JSON values or bytes wrapper objects".to_string())
3970 }
3971 }
3972}
3973
3974fn required_bytes<'a>(data: *const u8, len: usize, field_name: &str) -> Result<&'a [u8], String> {
3977 if data.is_null() {
3978 return Err(format!("{field_name} pointer must not be null"));
3979 }
3980 if len == 0 {
3981 return Err(format!("{field_name} length must be greater than zero"));
3982 }
3983 Ok(unsafe { std::slice::from_raw_parts(data, len) })
3984}
3985
3986fn optional_bytes<'a>(data: *const u8, len: usize, field_name: &str) -> Result<&'a [u8], String> {
3989 if len == 0 {
3990 return Ok(&[]);
3991 }
3992 if data.is_null() {
3993 return Err(format!(
3994 "{field_name} pointer must not be null when len > 0"
3995 ));
3996 }
3997 Ok(unsafe { std::slice::from_raw_parts(data, len) })
3998}
3999
4000fn optional_string_array(
4003 items: *const *const c_char,
4004 len: usize,
4005 field_name: &str,
4006) -> Result<Vec<String>, String> {
4007 if len == 0 {
4008 return Ok(Vec::new());
4009 }
4010 required_string_array(items, len, field_name)
4011}
4012
4013fn required_f32_slice<'a>(
4016 data: *const c_float,
4017 len: usize,
4018 field_name: &str,
4019) -> Result<&'a [c_float], String> {
4020 if data.is_null() {
4021 return Err(format!("{field_name} pointer must not be null"));
4022 }
4023 if len == 0 {
4024 return Err(format!("{field_name} length must be greater than zero"));
4025 }
4026 Ok(unsafe { std::slice::from_raw_parts(data, len) })
4027}
4028
4029fn read_sqlite_values(
4032 values: *const FfiSqliteValue,
4033 len: usize,
4034 field_name: &str,
4035) -> Result<Vec<ControllerSqliteValue>, String> {
4036 if len == 0 {
4037 return Ok(Vec::new());
4038 }
4039 if values.is_null() {
4040 return Err(format!("{field_name} pointer must not be null"));
4041 }
4042 let slice = unsafe { std::slice::from_raw_parts(values, len) };
4043 slice
4044 .iter()
4045 .enumerate()
4046 .map(|(index, value)| map_sqlite_value_native(value, &format!("{field_name}[{index}]")))
4047 .collect()
4048}
4049
4050fn read_sqlite_batch_items(
4053 items: *const FfiSqliteBatchItem,
4054 len: usize,
4055 field_name: &str,
4056) -> Result<Vec<Vec<ControllerSqliteValue>>, String> {
4057 if len == 0 {
4058 return Ok(Vec::new());
4059 }
4060 if items.is_null() {
4061 return Err(format!("{field_name} pointer must not be null"));
4062 }
4063 let slice = unsafe { std::slice::from_raw_parts(items, len) };
4064 slice
4065 .iter()
4066 .enumerate()
4067 .map(|(index, item)| {
4068 read_sqlite_values(
4069 item.params,
4070 item.params_len,
4071 &format!("{field_name}[{index}].params"),
4072 )
4073 })
4074 .collect()
4075}
4076
4077fn read_lancedb_columns(
4080 columns: *const FfiControllerLanceDbColumnDef,
4081 len: usize,
4082 field_name: &str,
4083) -> Result<Vec<ControllerLanceDbColumnDef>, String> {
4084 if columns.is_null() {
4085 return Err(format!("{field_name} pointer must not be null"));
4086 }
4087 if len == 0 {
4088 return Err(format!("{field_name} length must be greater than zero"));
4089 }
4090 let slice = unsafe { std::slice::from_raw_parts(columns, len) };
4091 slice
4092 .iter()
4093 .enumerate()
4094 .map(|(index, column)| {
4095 Ok(ControllerLanceDbColumnDef {
4096 name: required_c_string(column.name, &format!("{field_name}[{index}].name"))?,
4097 column_type: map_lancedb_column_type_native(column.column_type)?,
4098 vector_dim: column.vector_dim,
4099 nullable: column.nullable != 0,
4100 })
4101 })
4102 .collect()
4103}
4104
4105fn map_sqlite_value_native(
4108 value: &FfiSqliteValue,
4109 field_name: &str,
4110) -> Result<ControllerSqliteValue, String> {
4111 match value.kind {
4112 0 => Ok(ControllerSqliteValue::Int64(value.int64_value)),
4113 1 => Ok(ControllerSqliteValue::Float64(value.float64_value)),
4114 2 => Ok(ControllerSqliteValue::String(required_c_string_preserve(
4115 value.string_value,
4116 &format!("{field_name}.string_value"),
4117 )?)),
4118 3 => Ok(ControllerSqliteValue::Bytes(
4119 optional_bytes(
4120 value.bytes_value,
4121 value.bytes_len,
4122 &format!("{field_name}.bytes_value"),
4123 )?
4124 .to_vec(),
4125 )),
4126 4 => Ok(ControllerSqliteValue::Bool(value.bool_value != 0)),
4127 5 => Ok(ControllerSqliteValue::Null),
4128 other => Err(format!("unsupported sqlite value kind `{other}`")),
4129 }
4130}
4131
4132fn default_nullable() -> bool {
4135 true
4136}
4137
4138fn default_search_limit() -> u32 {
4141 10
4142}
4143
4144fn parse_json_input<T>(json: *const c_char) -> Result<T, String>
4147where
4148 T: for<'de> Deserialize<'de>,
4149{
4150 let json = required_c_string(json, "json")?;
4151 serde_json::from_str(&json).map_err(|error| format!("failed to parse json input: {error}"))
4152}
4153
4154fn string_into_raw(value: String) -> *mut c_char {
4157 let sanitized = if value.contains('\0') {
4158 value.replace('\0', "\u{FFFD}")
4159 } else {
4160 value
4161 };
4162 CString::new(sanitized)
4163 .expect("sanitized string must not contain interior null bytes")
4164 .into_raw()
4165}
4166
4167fn bytes_into_raw(mut value: Vec<u8>) -> (*mut u8, usize) {
4170 let ptr = value.as_mut_ptr();
4171 let len = value.len();
4172 std::mem::forget(value);
4173 (ptr, len)
4174}
4175
4176fn with_client_handle<T>(
4179 client: *mut FfiControllerClientHandle,
4180 func: impl FnOnce(&mut FfiControllerClientHandleInner) -> Result<T, String>,
4181) -> Result<T, String> {
4182 let handle_id = ffi_client_handle_id_from_ptr(client)?;
4183 let state = {
4184 let registry = ffi_client_handle_registry()
4185 .lock()
4186 .map_err(|poisoned| format!("ffi client handle registry lock poisoned: {poisoned}"))?;
4187 registry
4188 .get(&handle_id)
4189 .cloned()
4190 .ok_or_else(|| "client handle is invalid or has been freed".to_string())?
4191 };
4192 let mut guard = match state.inner.lock() {
4193 Ok(guard) => guard,
4194 Err(poisoned) => poisoned.into_inner(),
4195 };
4196 let inner = guard
4197 .as_mut()
4198 .ok_or_else(|| "client handle is invalid or has been freed".to_string())?;
4199 func(inner)
4200}
4201
4202fn map_process_mode_native(raw: c_int) -> Result<ControllerProcessMode, String> {
4205 match raw {
4206 0 => Ok(ControllerProcessMode::Service),
4207 1 => Ok(ControllerProcessMode::Managed),
4208 _ => Err(format!("unsupported process mode value `{raw}`")),
4209 }
4210}
4211
4212fn map_process_mode_to_native(mode: ControllerProcessMode) -> c_int {
4215 match mode {
4216 ControllerProcessMode::Service => 0,
4217 ControllerProcessMode::Managed => 1,
4218 }
4219}
4220
4221fn map_space_kind_native(raw: c_int) -> Result<SpaceKind, String> {
4224 match raw {
4225 0 => Ok(SpaceKind::Root),
4226 1 => Ok(SpaceKind::User),
4227 2 => Ok(SpaceKind::Project),
4228 _ => Err(format!("unsupported space kind value `{raw}`")),
4229 }
4230}
4231
4232fn map_space_kind_to_native(kind: SpaceKind) -> c_int {
4235 match kind {
4236 SpaceKind::Root => 0,
4237 SpaceKind::User => 1,
4238 SpaceKind::Project => 2,
4239 }
4240}
4241
4242fn map_lancedb_column_type_native(raw: c_int) -> Result<ControllerLanceDbColumnType, String> {
4245 match raw {
4246 0 => Ok(ControllerLanceDbColumnType::Unspecified),
4247 1 => Ok(ControllerLanceDbColumnType::String),
4248 2 => Ok(ControllerLanceDbColumnType::Int64),
4249 3 => Ok(ControllerLanceDbColumnType::Float64),
4250 4 => Ok(ControllerLanceDbColumnType::Bool),
4251 5 => Ok(ControllerLanceDbColumnType::VectorFloat32),
4252 6 => Ok(ControllerLanceDbColumnType::Float32),
4253 7 => Ok(ControllerLanceDbColumnType::Uint64),
4254 8 => Ok(ControllerLanceDbColumnType::Int32),
4255 9 => Ok(ControllerLanceDbColumnType::Uint32),
4256 other => Err(format!("unsupported lancedb column type `{other}`")),
4257 }
4258}
4259
4260fn map_lancedb_input_format_native(raw: c_int) -> Result<ControllerLanceDbInputFormat, String> {
4263 match raw {
4264 0 => Ok(ControllerLanceDbInputFormat::Unspecified),
4265 1 => Ok(ControllerLanceDbInputFormat::JsonRows),
4266 2 => Ok(ControllerLanceDbInputFormat::ArrowIpc),
4267 other => Err(format!("unsupported lancedb input format `{other}`")),
4268 }
4269}
4270
4271fn map_lancedb_output_format_native(raw: c_int) -> Result<ControllerLanceDbOutputFormat, String> {
4274 match raw {
4275 0 => Ok(ControllerLanceDbOutputFormat::Unspecified),
4276 1 => Ok(ControllerLanceDbOutputFormat::ArrowIpc),
4277 2 => Ok(ControllerLanceDbOutputFormat::JsonRows),
4278 other => Err(format!("unsupported lancedb output format `{other}`")),
4279 }
4280}
4281
4282fn default_u64(value: c_ulonglong, fallback: u64) -> u64 {
4285 if value == 0 { fallback } else { value }
4286}
4287
4288fn optional_nonzero_u64(value: c_ulonglong) -> Option<u64> {
4291 if value == 0 { None } else { Some(value) }
4292}
4293
4294fn write_out_ptr<T>(slot: *mut *mut T, value: *mut T) {
4297 if !slot.is_null() {
4298 unsafe {
4299 *slot = value;
4300 }
4301 }
4302}
4303
4304fn write_boxed_out_ptr<T>(slot: *mut *mut T, value: T) {
4307 if !slot.is_null() {
4308 unsafe {
4309 *slot = Box::into_raw(Box::new(value));
4310 }
4311 }
4312}
4313
4314fn clear_out_ptr<T>(slot: *mut *mut T) {
4317 if !slot.is_null() {
4318 unsafe {
4319 *slot = ptr::null_mut();
4320 }
4321 }
4322}
4323
4324fn write_out_u8(slot: *mut c_uchar, value: c_uchar) {
4327 if !slot.is_null() {
4328 unsafe {
4329 *slot = value;
4330 }
4331 }
4332}
4333
4334fn clear_out_u8(slot: *mut c_uchar) {
4337 if !slot.is_null() {
4338 unsafe {
4339 *slot = 0;
4340 }
4341 }
4342}
4343
4344fn clear_error_out(error_out: *mut *mut c_char) {
4347 clear_out_ptr(error_out);
4348}
4349
4350fn write_string_out(slot: *mut *mut c_char, value: String) {
4353 if !slot.is_null() {
4354 unsafe {
4355 *slot = string_into_raw(value);
4356 }
4357 }
4358}
4359
4360fn ffi_ok_status(error_out: *mut *mut c_char) -> c_int {
4363 clear_error_out(error_out);
4364 FFI_STATUS_OK
4365}
4366
4367fn ffi_error_status(error_out: *mut *mut c_char, message: impl Into<String>) -> c_int {
4370 clear_error_out(error_out);
4371 if !error_out.is_null() {
4372 unsafe {
4373 *error_out = string_into_raw(message.into());
4374 }
4375 }
4376 FFI_STATUS_ERR
4377}
4378
4379fn error_to_string(error: impl std::fmt::Display) -> String {
4382 error.to_string()
4383}
4384
4385fn free_space_snapshot_fields(snapshot: FfiSpaceSnapshot) {
4388 vldb_controller_ffi_string_free(snapshot.space_id);
4389 vldb_controller_ffi_string_free(snapshot.space_label);
4390 vldb_controller_ffi_string_free(snapshot.space_root);
4391 free_backend_status_ptr(snapshot.sqlite);
4392 free_backend_status_ptr(snapshot.lancedb);
4393}
4394
4395fn free_backend_status_ptr(status: *mut FfiSpaceBackendStatus) {
4398 if !status.is_null() {
4399 unsafe {
4400 let status = Box::from_raw(status);
4401 vldb_controller_ffi_string_free(status.mode);
4402 vldb_controller_ffi_string_free(status.target);
4403 }
4404 }
4405}
4406
4407fn encode_base64(data: &[u8]) -> String {
4410 const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4411 let mut output = String::with_capacity(data.len().div_ceil(3) * 4);
4412 for chunk in data.chunks(3) {
4413 let a = chunk[0];
4414 let b = *chunk.get(1).unwrap_or(&0);
4415 let c = *chunk.get(2).unwrap_or(&0);
4416 output.push(TABLE[(a >> 2) as usize] as char);
4417 output.push(TABLE[(((a & 0x03) << 4) | (b >> 4)) as usize] as char);
4418 if chunk.len() > 1 {
4419 output.push(TABLE[(((b & 0x0f) << 2) | (c >> 6)) as usize] as char);
4420 } else {
4421 output.push('=');
4422 }
4423 if chunk.len() > 2 {
4424 output.push(TABLE[(c & 0x3f) as usize] as char);
4425 } else {
4426 output.push('=');
4427 }
4428 }
4429 output
4430}
4431
4432fn decode_base64(input: &str) -> Result<Vec<u8>, String> {
4435 let bytes = input.as_bytes();
4436 if !bytes.len().is_multiple_of(4) {
4437 return Err("base64 input length must be a multiple of 4".to_string());
4438 }
4439 let mut output = Vec::with_capacity(bytes.len() / 4 * 3);
4440 for chunk in bytes.chunks(4) {
4441 let a = decode_base64_char(chunk[0])?;
4442 let b = decode_base64_char(chunk[1])?;
4443 let c = if chunk[2] == b'=' {
4444 64
4445 } else {
4446 decode_base64_char(chunk[2])?
4447 };
4448 let d = if chunk[3] == b'=' {
4449 64
4450 } else {
4451 decode_base64_char(chunk[3])?
4452 };
4453
4454 output.push((a << 2) | (b >> 4));
4455 if c != 64 {
4456 output.push(((b & 0x0f) << 4) | (c >> 2));
4457 }
4458 if d != 64 {
4459 output.push(((c & 0x03) << 6) | d);
4460 }
4461 }
4462 Ok(output)
4463}
4464
4465fn decode_base64_char(value: u8) -> Result<u8, String> {
4468 match value {
4469 b'A'..=b'Z' => Ok(value - b'A'),
4470 b'a'..=b'z' => Ok(value - b'a' + 26),
4471 b'0'..=b'9' => Ok(value - b'0' + 52),
4472 b'+' => Ok(62),
4473 b'/' => Ok(63),
4474 _ => Err(format!("invalid base64 character `{}`", value as char)),
4475 }
4476}
4477
4478#[cfg(test)]
4479mod tests {
4480 use super::{
4481 CreateLanceDbTableJsonRequest, DeleteLanceDbJsonRequest, ExecuteSqliteBatchJsonRequest,
4482 ExecuteSqliteScriptJsonRequest, FFI_STATUS_ERR, FFI_STATUS_OK, FfiClientRegistration,
4483 FfiControllerClientConfig, FfiControllerClientHandle, FfiSqliteValue,
4484 QuerySqliteJsonJsonRequest, QuerySqliteStreamJsonRequest, SearchLanceDbJsonRequest,
4485 UpsertLanceDbJsonRequest, build_client_handle, map_sqlite_value_native,
4486 required_c_string_preserve, vldb_controller_ffi_client_create,
4487 vldb_controller_ffi_client_free, with_client_handle,
4488 };
4489 use serde_json::json;
4490 use std::ffi::CString;
4491 use vldb_controller_client::{
4492 ClientRegistration, ControllerClientConfig, ControllerSqliteValue,
4493 };
4494
4495 #[test]
4496 fn sqlite_native_string_preserves_whitespace_and_allows_empty() {
4497 let spaced = CString::new(" a ").expect("cstring should build");
4498 let empty = CString::new("").expect("cstring should build");
4499 let spaced_value = FfiSqliteValue {
4500 kind: 2,
4501 int64_value: 0,
4502 float64_value: 0.0,
4503 string_value: spaced.as_ptr(),
4504 bytes_value: std::ptr::null(),
4505 bytes_len: 0,
4506 bool_value: 0,
4507 };
4508 let empty_value = FfiSqliteValue {
4509 kind: 2,
4510 int64_value: 0,
4511 float64_value: 0.0,
4512 string_value: empty.as_ptr(),
4513 bytes_value: std::ptr::null(),
4514 bytes_len: 0,
4515 bool_value: 0,
4516 };
4517
4518 assert_eq!(
4519 map_sqlite_value_native(&spaced_value, "value").expect("string should parse"),
4520 ControllerSqliteValue::String(" a ".to_string())
4521 );
4522 assert_eq!(
4523 map_sqlite_value_native(&empty_value, "value").expect("empty string should parse"),
4524 ControllerSqliteValue::String(String::new())
4525 );
4526 assert_eq!(
4527 required_c_string_preserve(empty.as_ptr(), "value").expect("empty string should read"),
4528 String::new()
4529 );
4530 }
4531
4532 #[test]
4533 fn sqlite_native_bytes_allow_empty_blob() {
4534 let value = FfiSqliteValue {
4535 kind: 3,
4536 int64_value: 0,
4537 float64_value: 0.0,
4538 string_value: std::ptr::null(),
4539 bytes_value: std::ptr::null(),
4540 bytes_len: 0,
4541 bool_value: 0,
4542 };
4543
4544 assert_eq!(
4545 map_sqlite_value_native(&value, "value").expect("empty blob should parse"),
4546 ControllerSqliteValue::Bytes(Vec::new())
4547 );
4548 }
4549
4550 #[test]
4551 fn preserve_string_reader_keeps_whitespace_for_text_payloads() {
4552 let spaced = CString::new(" keep ").expect("cstring should build");
4553 assert_eq!(
4554 required_c_string_preserve(spaced.as_ptr(), "text").expect("text should read"),
4555 " keep ".to_string()
4556 );
4557 }
4558
4559 #[test]
4560 fn preserve_string_reader_allows_empty_text_payloads() {
4561 let empty = CString::new("").expect("cstring should build");
4562 assert_eq!(
4563 required_c_string_preserve(empty.as_ptr(), "content")
4564 .expect("empty content should read"),
4565 String::new()
4566 );
4567 }
4568
4569 #[test]
4570 fn sqlite_json_requests_accept_nested_params_without_inner_json_strings() {
4571 let execute: ExecuteSqliteScriptJsonRequest = serde_json::from_value(json!({
4572 "space_id": "ROOT",
4573 "sql": "SELECT ?",
4574 "params": [1, {"type": "bytes_base64", "base64": "AQID"}]
4575 }))
4576 .expect("execute request should parse");
4577 assert_eq!(execute.params.len(), 2);
4578
4579 let query: QuerySqliteJsonJsonRequest = serde_json::from_value(json!({
4580 "space_id": "ROOT",
4581 "sql": "SELECT ?",
4582 "params": [" keep "]
4583 }))
4584 .expect("query request should parse");
4585 assert_eq!(query.params.len(), 1);
4586
4587 let batch: ExecuteSqliteBatchJsonRequest = serde_json::from_value(json!({
4588 "space_id": "ROOT",
4589 "sql": "INSERT INTO demo VALUES (?)",
4590 "batch_params": [[1], [2], [{"__type": "bytes_base64", "base64": "AQ=="}]]
4591 }))
4592 .expect("batch request should parse");
4593 assert_eq!(batch.batch_params.len(), 3);
4594
4595 let stream: QuerySqliteStreamJsonRequest = serde_json::from_value(json!({
4596 "space_id": "ROOT",
4597 "sql": "SELECT ?",
4598 "params": [],
4599 "target_chunk_size": 1024
4600 }))
4601 .expect("stream request should parse");
4602 assert_eq!(stream.target_chunk_size, Some(1024));
4603 }
4604
4605 #[test]
4606 fn lancedb_json_requests_accept_nested_objects_without_request_json() {
4607 let create: CreateLanceDbTableJsonRequest = serde_json::from_value(json!({
4608 "space_id": "ROOT",
4609 "table_name": "memory",
4610 "columns": [
4611 {"name": "id", "column_type": "string", "nullable": false},
4612 {"name": "vector", "column_type": "vector", "vector_dim": 3, "nullable": false}
4613 ],
4614 "overwrite_if_exists": true
4615 }))
4616 .expect("create request should parse");
4617 assert_eq!(create.columns.len(), 2);
4618
4619 let upsert: UpsertLanceDbJsonRequest = serde_json::from_value(json!({
4620 "space_id": "ROOT",
4621 "table_name": "memory",
4622 "input_format": "json_rows",
4623 "key_columns": ["id"],
4624 "data_base64": "W10="
4625 }))
4626 .expect("upsert request should parse");
4627 assert_eq!(upsert.key_columns, vec!["id".to_string()]);
4628
4629 let search: SearchLanceDbJsonRequest = serde_json::from_value(json!({
4630 "space_id": "ROOT",
4631 "table_name": "memory",
4632 "vector": [0.1, 0.2, 0.3],
4633 "limit": 5,
4634 "filter": "kind = 'note'",
4635 "vector_column": "vector",
4636 "output_format": "json_rows"
4637 }))
4638 .expect("search request should parse");
4639 assert_eq!(search.vector.len(), 3);
4640
4641 let delete: DeleteLanceDbJsonRequest = serde_json::from_value(json!({
4642 "space_id": "ROOT",
4643 "table_name": "memory",
4644 "condition": "id = 'a'"
4645 }))
4646 .expect("delete request should parse");
4647 assert_eq!(delete.condition, "id = 'a'");
4648 }
4649
4650 #[test]
4651 fn freed_handle_rejects_follow_up_calls_safely() {
4652 let handle_ptr = build_client_handle(
4653 ControllerClientConfig::default(),
4654 ClientRegistration {
4655 client_name: "ffi-test-client".to_string(),
4656 host_kind: "test".to_string(),
4657 process_id: 7,
4658 process_name: "ffi-test".to_string(),
4659 lease_ttl_secs: Some(60),
4660 },
4661 )
4662 .expect("handle should build");
4663
4664 with_client_handle(handle_ptr, |_handle| Ok(())).expect("live handle should work");
4665 vldb_controller_ffi_client_free(handle_ptr);
4666
4667 let error = with_client_handle(handle_ptr, |_handle| Ok(()))
4668 .expect_err("freed handle should reject later calls");
4669 assert!(
4670 error.contains("freed"),
4671 "error should mention freed state, got: {error}"
4672 );
4673
4674 vldb_controller_ffi_client_free(handle_ptr);
4675 }
4676
4677 #[test]
4678 fn native_create_keeps_error_out_optional_and_clears_reused_slots() {
4679 let client_name = CString::new("ffi-create-client").expect("client_name should build");
4680 let host_kind = CString::new("test").expect("host_kind should build");
4681 let process_name = CString::new("ffi-create").expect("process_name should build");
4682 let config = FfiControllerClientConfig {
4683 endpoint: std::ptr::null(),
4684 auto_spawn: 0,
4685 spawn_executable: std::ptr::null(),
4686 spawn_process_mode: 0,
4687 minimum_uptime_secs: 0,
4688 idle_timeout_secs: 0,
4689 default_lease_ttl_secs: 0,
4690 connect_timeout_secs: 0,
4691 startup_timeout_secs: 0,
4692 startup_retry_interval_ms: 0,
4693 lease_renew_interval_secs: 0,
4694 };
4695 let registration = FfiClientRegistration {
4696 client_name: client_name.as_ptr(),
4697 host_kind: host_kind.as_ptr(),
4698 process_id: 9,
4699 process_name: process_name.as_ptr(),
4700 lease_ttl_secs: 0,
4701 };
4702
4703 let mut handle_ptr = std::ptr::null_mut::<FfiControllerClientHandle>();
4704 let create_status = vldb_controller_ffi_client_create(
4705 &config,
4706 ®istration,
4707 &mut handle_ptr,
4708 std::ptr::null_mut(),
4709 );
4710 assert_eq!(create_status, FFI_STATUS_OK);
4711 assert!(
4712 !handle_ptr.is_null(),
4713 "create should still succeed without error_out"
4714 );
4715 vldb_controller_ffi_client_free(handle_ptr);
4716
4717 let mut stale_handle_ptr = std::ptr::dangling_mut::<FfiControllerClientHandle>();
4718 let failure_status = vldb_controller_ffi_client_create(
4719 std::ptr::null(),
4720 ®istration,
4721 &mut stale_handle_ptr,
4722 std::ptr::null_mut(),
4723 );
4724 assert_eq!(failure_status, FFI_STATUS_ERR);
4725 assert!(
4726 stale_handle_ptr.is_null(),
4727 "client_out should be cleared before returning an error"
4728 );
4729 }
4730
4731 #[test]
4732 fn forged_handle_pointer_is_rejected_even_when_other_handles_are_live() {
4733 let handle_ptr = build_client_handle(
4734 ControllerClientConfig::default(),
4735 ClientRegistration {
4736 client_name: "ffi-forge-client".to_string(),
4737 host_kind: "test".to_string(),
4738 process_id: 11,
4739 process_name: "ffi-forge".to_string(),
4740 lease_ttl_secs: Some(60),
4741 },
4742 )
4743 .expect("handle should build");
4744
4745 let forged_handle_ptr = std::ptr::dangling_mut::<FfiControllerClientHandle>();
4746 assert_ne!(
4747 handle_ptr, forged_handle_ptr,
4748 "forged handle pointer must differ from the live registered handle"
4749 );
4750
4751 let error = with_client_handle(forged_handle_ptr, |_handle| Ok(()))
4752 .expect_err("forged handle pointer should be rejected");
4753 assert!(
4754 error.contains("invalid") || error.contains("freed"),
4755 "error should mention invalid or freed handle state, got: {error}"
4756 );
4757
4758 with_client_handle(handle_ptr, |_handle| Ok(())).expect("live handle should still work");
4759 vldb_controller_ffi_client_free(handle_ptr);
4760 }
4761}