1use base64::Engine as _;
2use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
3use crate::fts::{
4 delete_fts_document, ensure_fts_index, rebuild_fts_index, search_fts, upsert_fts_document,
5};
6use crate::library::library_info;
7use crate::runtime::{
8 SqliteDatabaseHandle, SqliteOpenOptions, SqliteRuntime, open_sqlite_connection,
9};
10use crate::sql_exec::{
11 DEFAULT_IPC_CHUNK_BYTES, QueryJsonResult, QueryStreamResult, SqlExecCoreError,
12 QueryStreamChunkWriter, QueryStreamMetrics, query_stream_with_writer,
13 count_sql_statements, execute_batch as execute_batch_core, execute_script as execute_script_core,
14 parse_legacy_params_json, query_json as query_json_core, query_stream as query_stream_core,
15};
16use crate::tokenizer::{
17 ListCustomWordsResult, TokenizeOutput, TokenizerMode, list_custom_words, remove_custom_word,
18 tokenize_text, upsert_custom_word,
19};
20use rusqlite::Connection;
21use rusqlite::types::Value as SqliteValue;
22use serde::{Deserialize, Serialize};
23use std::ffi::{CStr, CString, c_char};
24use std::fs::File;
25use std::io::{Read, Seek, SeekFrom, Write};
26use std::path::Path;
27use std::path::PathBuf;
28use std::sync::atomic::{AtomicU64, Ordering};
29use std::sync::{Arc, Condvar, Mutex, OnceLock};
30use std::thread;
31use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
32
33#[repr(C)]
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum VldbSqliteStatusCode {
38 Success = 0,
41 Failure = 1,
44}
45
46#[repr(C)]
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum VldbSqliteFfiTokenizerMode {
51 None = 0,
54 Jieba = 1,
57}
58
59pub struct VldbSqliteRuntimeHandle {
62 inner: SqliteRuntime,
65}
66
67pub struct VldbSqliteDatabaseHandle {
70 inner: Arc<SqliteDatabaseHandle>,
73}
74
75pub struct VldbSqliteTokenizeResultHandle {
78 inner: TokenizeOutput,
81}
82
83pub struct VldbSqliteCustomWordListHandle {
86 inner: ListCustomWordsResult,
89}
90
91pub struct VldbSqliteSearchResultHandle {
94 inner: crate::fts::SearchFtsResult,
97}
98
99pub struct VldbSqliteExecuteResultHandle {
102 inner: VldbSqliteExecuteResult,
105}
106
107pub struct VldbSqliteQueryJsonResultHandle {
110 inner: QueryJsonResult,
113}
114
115pub struct VldbSqliteQueryStreamHandle {
118 inner: Arc<FfiQueryStreamSharedState>,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126struct FfiQueryStreamChunkDescriptor {
127 offset: u64,
130 len: u64,
133}
134
135#[derive(Debug)]
138struct FfiQueryStreamSharedInner {
139 file_path: PathBuf,
142 chunk_descriptors: Vec<FfiQueryStreamChunkDescriptor>,
145 row_count: u64,
148 chunk_count: u64,
151 total_bytes: u64,
154 complete: bool,
157 error: Option<String>,
160}
161
162#[derive(Debug)]
165struct FfiQueryStreamSharedState {
166 inner: Mutex<FfiQueryStreamSharedInner>,
169 ready: Condvar,
172}
173
174impl FfiQueryStreamSharedState {
175 fn new(file_path: PathBuf) -> Arc<Self> {
176 Arc::new(Self {
177 inner: Mutex::new(FfiQueryStreamSharedInner {
178 file_path,
179 chunk_descriptors: Vec::new(),
180 row_count: 0,
181 chunk_count: 0,
182 total_bytes: 0,
183 complete: false,
184 error: None,
185 }),
186 ready: Condvar::new(),
187 })
188 }
189
190 fn register_chunk(&self, offset: u64, len: u64) {
191 let mut guard = self
192 .inner
193 .lock()
194 .unwrap_or_else(|poisoned| poisoned.into_inner());
195 guard
196 .chunk_descriptors
197 .push(FfiQueryStreamChunkDescriptor { offset, len });
198 self.ready.notify_all();
199 }
200
201 fn finish(&self, metrics: QueryStreamMetrics) {
202 let mut guard = self
203 .inner
204 .lock()
205 .unwrap_or_else(|poisoned| poisoned.into_inner());
206 guard.row_count = metrics.row_count;
207 guard.chunk_count = metrics.chunk_count;
208 guard.total_bytes = metrics.total_bytes;
209 guard.complete = true;
210 self.ready.notify_all();
211 }
212
213 fn fail(&self, error: String) {
214 let mut guard = self
215 .inner
216 .lock()
217 .unwrap_or_else(|poisoned| poisoned.into_inner());
218 guard.error = Some(error);
219 guard.complete = true;
220 self.ready.notify_all();
221 }
222
223 fn wait_for_metrics(&self) -> Result<(u64, u64, u64), String> {
224 let mut guard = self
225 .inner
226 .lock()
227 .map_err(|_| "failed to lock query stream state / 无法锁定 QueryStream 状态".to_string())?;
228 while !guard.complete {
229 guard = self
230 .ready
231 .wait(guard)
232 .map_err(|_| "failed to wait for query stream completion / 等待 QueryStream 完成失败".to_string())?;
233 }
234 if let Some(error) = guard.error.clone() {
235 return Err(error);
236 }
237 Ok((guard.chunk_count, guard.row_count, guard.total_bytes))
238 }
239
240 fn read_chunk(&self, index: usize) -> Result<Vec<u8>, String> {
241 let (file_path, descriptor) = {
242 let mut guard = self
243 .inner
244 .lock()
245 .map_err(|_| "failed to lock query stream state / 无法锁定 QueryStream 状态".to_string())?;
246 loop {
247 if let Some(descriptor) = guard.chunk_descriptors.get(index).copied() {
248 break (guard.file_path.clone(), descriptor);
249 }
250 if let Some(error) = guard.error.clone() {
251 return Err(error);
252 }
253 if guard.complete {
254 return Err("chunk index out of bounds / chunk 下标越界".to_string());
255 }
256 guard = self
257 .ready
258 .wait(guard)
259 .map_err(|_| "failed to wait for query stream chunk / 等待 QueryStream chunk 失败".to_string())?;
260 }
261 };
262
263 let mut file = File::open(&file_path)
264 .map_err(|error| format!("open query stream spool file failed: {error}"))?;
265 file.seek(SeekFrom::Start(descriptor.offset))
266 .map_err(|error| format!("seek query stream spool file failed: {error}"))?;
267 let chunk_len = usize::try_from(descriptor.len)
268 .map_err(|_| "query stream chunk length exceeds usize / QueryStream chunk 长度超过 usize".to_string())?;
269 let mut chunk = vec![0_u8; chunk_len];
270 file.read_exact(&mut chunk)
271 .map_err(|error| format!("read query stream spool chunk failed: {error}"))?;
272 Ok(chunk)
273 }
274}
275
276impl Drop for FfiQueryStreamSharedState {
277 fn drop(&mut self) {
278 let file_path = self
279 .inner
280 .lock()
281 .map(|guard| guard.file_path.clone())
282 .unwrap_or_else(|poisoned| poisoned.into_inner().file_path.clone());
283 let _ = std::fs::remove_file(file_path);
284 }
285}
286
287struct FfiStreamingTempFileWriter {
290 file: File,
291 state: Arc<FfiQueryStreamSharedState>,
292 pending: Vec<u8>,
293 target_chunk_size: usize,
294 emitted_chunks: usize,
295 emitted_bytes: usize,
296 current_offset: u64,
297}
298
299impl FfiStreamingTempFileWriter {
300 fn new(state: Arc<FfiQueryStreamSharedState>, target_chunk_size: usize) -> Result<Self, String> {
301 let chunk_size = target_chunk_size.max(64 * 1024);
302 let file_path = {
303 let guard = state
304 .inner
305 .lock()
306 .map_err(|_| "failed to lock query stream state / 无法锁定 QueryStream 状态".to_string())?;
307 guard.file_path.clone()
308 };
309 let file = File::create(&file_path)
310 .map_err(|error| format!("create query stream spool file failed: {error}"))?;
311 Ok(Self {
312 file,
313 state,
314 pending: Vec::with_capacity(chunk_size),
315 target_chunk_size: chunk_size,
316 emitted_chunks: 0,
317 emitted_bytes: 0,
318 current_offset: 0,
319 })
320 }
321
322 fn emit_full_chunks(&mut self) -> std::io::Result<()> {
323 while self.pending.len() >= self.target_chunk_size {
324 let remainder = self.pending.split_off(self.target_chunk_size);
325 let chunk = std::mem::replace(&mut self.pending, remainder);
326 self.write_chunk(chunk)?;
327 }
328 Ok(())
329 }
330
331 fn emit_remaining(&mut self) -> std::io::Result<()> {
332 if self.pending.is_empty() {
333 return Ok(());
334 }
335 let chunk = std::mem::take(&mut self.pending);
336 self.write_chunk(chunk)
337 }
338
339 fn write_chunk(&mut self, chunk: Vec<u8>) -> std::io::Result<()> {
340 self.file.write_all(&chunk)?;
341 let chunk_len = u64::try_from(chunk.len()).unwrap_or(u64::MAX);
342 self.state.register_chunk(self.current_offset, chunk_len);
343 self.current_offset = self.current_offset.saturating_add(chunk_len);
344 self.emitted_chunks += 1;
345 self.emitted_bytes += chunk.len();
346 Ok(())
347 }
348}
349
350impl QueryStreamChunkWriter for FfiStreamingTempFileWriter {
351 fn emitted_chunk_count(&self) -> u64 {
352 u64::try_from(self.emitted_chunks).unwrap_or(u64::MAX)
353 }
354
355 fn emitted_total_bytes(&self) -> u64 {
356 u64::try_from(self.emitted_bytes).unwrap_or(u64::MAX)
357 }
358}
359
360impl Write for FfiStreamingTempFileWriter {
361 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
362 if buf.is_empty() {
363 return Ok(0);
364 }
365 self.pending.extend_from_slice(buf);
366 self.emit_full_chunks()?;
367 Ok(buf.len())
368 }
369
370 fn flush(&mut self) -> std::io::Result<()> {
371 self.emit_remaining()?;
372 self.file.flush()
373 }
374}
375
376struct JsonQueryStreamEntry {
379 result: QueryStreamResult,
382 last_accessed_at: Instant,
385}
386
387#[derive(Debug, Clone)]
390struct VldbSqliteExecuteResult {
391 success: bool,
394 message: String,
397 rows_changed: i64,
400 last_insert_rowid: i64,
403 statements_executed: i64,
406}
407
408#[repr(C)]
411#[derive(Clone, Copy, Debug, Default)]
412pub struct VldbSqliteDictionaryMutationResultPod {
413 pub success: u8,
416 pub affected_rows: u64,
419}
420
421#[repr(C)]
424#[derive(Clone, Copy, Debug, Default)]
425pub struct VldbSqliteEnsureFtsIndexResultPod {
426 pub success: u8,
429 pub tokenizer_mode: u32,
432}
433
434#[repr(C)]
437#[derive(Clone, Copy, Debug, Default)]
438pub struct VldbSqliteRebuildFtsIndexResultPod {
439 pub success: u8,
442 pub tokenizer_mode: u32,
445 pub reindexed_rows: u64,
448}
449
450#[repr(C)]
453#[derive(Clone, Copy, Debug, Default)]
454pub struct VldbSqliteFtsMutationResultPod {
455 pub success: u8,
458 pub affected_rows: u64,
461}
462
463#[repr(C)]
466#[derive(Clone, Copy, Debug, Default)]
467pub struct VldbSqliteByteView {
468 pub data: *const u8,
471 pub len: u64,
474}
475
476#[repr(C)]
479#[derive(Clone, Copy, Debug, Default)]
480pub struct VldbSqliteByteBuffer {
481 pub data: *mut u8,
484 pub len: u64,
487 pub cap: u64,
490}
491
492#[repr(C)]
495#[derive(Clone, Copy, Debug, PartialEq, Eq)]
496pub enum VldbSqliteFfiValueKind {
497 Null = 0,
500 Int64 = 1,
503 Float64 = 2,
506 String = 3,
509 Bytes = 4,
512 Bool = 5,
515}
516
517#[repr(C)]
520#[derive(Clone, Copy, Debug)]
521pub struct VldbSqliteFfiValue {
522 pub kind: VldbSqliteFfiValueKind,
525 pub int64_value: i64,
528 pub float64_value: f64,
531 pub string_value: *const c_char,
534 pub bytes_value: VldbSqliteByteView,
537 pub bool_value: u8,
540}
541
542#[repr(C)]
545#[derive(Clone, Copy, Debug, Default)]
546pub struct VldbSqliteFfiValueSlice {
547 pub values: *const VldbSqliteFfiValue,
550 pub len: u64,
553}
554
555static LAST_ERROR: OnceLock<Mutex<Option<CString>>> = OnceLock::new();
558static NEXT_JSON_STREAM_ID: AtomicU64 = AtomicU64::new(1);
559static NEXT_MAIN_FFI_STREAM_ID: AtomicU64 = AtomicU64::new(1);
560static JSON_QUERY_STREAMS: OnceLock<Mutex<std::collections::HashMap<u64, JsonQueryStreamEntry>>> =
561 OnceLock::new();
562static JSON_QUERY_STREAM_CLEANER: OnceLock<()> = OnceLock::new();
563
564const JSON_QUERY_STREAM_TTL: Duration = Duration::from_secs(300);
565const JSON_QUERY_STREAM_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
566const JSON_QUERY_STREAM_MAX_ENTRIES: usize = 64;
567
568fn last_error_slot() -> &'static Mutex<Option<CString>> {
569 LAST_ERROR.get_or_init(|| Mutex::new(None))
570}
571
572fn json_query_stream_registry(
573) -> &'static Mutex<std::collections::HashMap<u64, JsonQueryStreamEntry>> {
574 JSON_QUERY_STREAM_CLEANER.get_or_init(|| {
575 thread::spawn(|| loop {
576 thread::sleep(JSON_QUERY_STREAM_CLEANUP_INTERVAL);
577 if let Ok(mut guard) = json_query_stream_registry().lock() {
578 cleanup_json_query_stream_registry_at(&mut guard, Instant::now());
579 }
580 });
581 });
582 JSON_QUERY_STREAMS.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
583}
584
585fn update_last_error(message: impl Into<String>) {
586 let message = sanitize_for_c_string(message.into());
587 if let Ok(mut guard) = last_error_slot().lock() {
588 *guard = CString::new(message).ok();
589 }
590}
591
592fn clear_last_error_inner() {
593 if let Ok(mut guard) = last_error_slot().lock() {
594 *guard = None;
595 }
596}
597
598fn sanitize_for_c_string(value: String) -> String {
599 value.replace('\0', " ")
600}
601
602fn json_to_c_string(value: impl Into<String>) -> *mut c_char {
603 match CString::new(sanitize_for_c_string(value.into())) {
604 Ok(c_string) => c_string.into_raw(),
605 Err(error) => {
606 update_last_error(format!("failed to allocate FFI string: {error}"));
607 std::ptr::null_mut()
608 }
609 }
610}
611
612fn c_json_arg_to_string(value: *const c_char) -> Result<String, String> {
613 if value.is_null() {
614 return Err("JSON argument must not be null / JSON 参数不能为空指针".to_string());
615 }
616
617 let raw = unsafe { CStr::from_ptr(value) };
621 raw.to_str()
622 .map(ToOwned::to_owned)
623 .map_err(|error| format!("invalid UTF-8 JSON argument: {error}"))
624}
625
626fn open_connection_for_db_path(db_path: &str) -> Result<Connection, String> {
627 if db_path.trim().is_empty() {
628 return Err("db_path must not be empty / db_path 不能为空".to_string());
629 }
630
631 if db_path != ":memory:"
632 && let Some(parent) = Path::new(db_path).parent()
633 && !parent.as_os_str().is_empty()
634 {
635 std::fs::create_dir_all(parent)
636 .map_err(|error| format!("failed to create database directory: {error}"))?;
637 }
638
639 open_sqlite_connection(db_path, &SqliteOpenOptions::default())
640 .map_err(|error| format!("failed to open sqlite database with runtime defaults: {error}"))
641}
642
643fn open_connection_for_handle(handle: *mut VldbSqliteDatabaseHandle) -> Result<Connection, String> {
646 let database = database_handle_ref(handle)?;
647 database
648 .inner
649 .open_connection()
650 .map_err(|error| format!("failed to open runtime-managed sqlite connection: {error}"))
651}
652
653fn runtime_handle_ref(
656 handle: *mut VldbSqliteRuntimeHandle,
657) -> Result<&'static VldbSqliteRuntimeHandle, String> {
658 if handle.is_null() {
659 return Err("runtime handle must not be null / runtime 句柄不能为空".to_string());
660 }
661
662 Ok(unsafe { &*handle })
666}
667
668fn database_handle_ref(
671 handle: *mut VldbSqliteDatabaseHandle,
672) -> Result<&'static VldbSqliteDatabaseHandle, String> {
673 if handle.is_null() {
674 return Err("database handle must not be null / database 句柄不能为空".to_string());
675 }
676
677 Ok(unsafe { &*handle })
681}
682
683fn tokenize_result_handle_ref(
686 handle: *mut VldbSqliteTokenizeResultHandle,
687) -> Result<&'static VldbSqliteTokenizeResultHandle, String> {
688 if handle.is_null() {
689 return Err("tokenize result handle must not be null / 分词结果句柄不能为空".to_string());
690 }
691
692 Ok(unsafe { &*handle })
696}
697
698fn custom_word_list_handle_ref(
701 handle: *mut VldbSqliteCustomWordListHandle,
702) -> Result<&'static VldbSqliteCustomWordListHandle, String> {
703 if handle.is_null() {
704 return Err("custom word list handle must not be null / 自定义词列表句柄不能为空".to_string());
705 }
706
707 Ok(unsafe { &*handle })
711}
712
713fn search_result_handle_ref(
716 handle: *mut VldbSqliteSearchResultHandle,
717) -> Result<&'static VldbSqliteSearchResultHandle, String> {
718 if handle.is_null() {
719 return Err("search result handle must not be null / 检索结果句柄不能为空".to_string());
720 }
721
722 Ok(unsafe { &*handle })
726}
727
728fn execute_result_handle_ref(
731 handle: *mut VldbSqliteExecuteResultHandle,
732) -> Result<&'static VldbSqliteExecuteResultHandle, String> {
733 if handle.is_null() {
734 return Err("execute result handle must not be null / 执行结果句柄不能为空".to_string());
735 }
736
737 Ok(unsafe { &*handle })
741}
742
743fn query_json_result_handle_ref(
746 handle: *mut VldbSqliteQueryJsonResultHandle,
747) -> Result<&'static VldbSqliteQueryJsonResultHandle, String> {
748 if handle.is_null() {
749 return Err("query json result handle must not be null / JSON 查询结果句柄不能为空".to_string());
750 }
751
752 Ok(unsafe { &*handle })
756}
757
758fn query_stream_handle_ref(
761 handle: *mut VldbSqliteQueryStreamHandle,
762) -> Result<&'static VldbSqliteQueryStreamHandle, String> {
763 if handle.is_null() {
764 return Err("query stream handle must not be null / QueryStream 句柄不能为空".to_string());
765 }
766
767 Ok(unsafe { &*handle })
771}
772
773fn parse_ffi_tokenizer_mode(mode: VldbSqliteFfiTokenizerMode) -> TokenizerMode {
776 match mode {
777 VldbSqliteFfiTokenizerMode::None => TokenizerMode::None,
778 VldbSqliteFfiTokenizerMode::Jieba => TokenizerMode::Jieba,
779 }
780}
781
782fn ffi_tokenizer_mode_code(mode: TokenizerMode) -> u32 {
785 match mode {
786 TokenizerMode::None => VldbSqliteFfiTokenizerMode::None as u32,
787 TokenizerMode::Jieba => VldbSqliteFfiTokenizerMode::Jieba as u32,
788 }
789}
790
791fn optional_title_arg(value: *const c_char) -> Result<String, String> {
794 if value.is_null() {
795 return Ok(String::new());
796 }
797
798 c_json_arg_to_string(value)
799}
800
801fn json_typed_value_to_sqlite_value(value: FfiJsonSqliteValue) -> Result<SqliteValue, String> {
804 match value {
805 FfiJsonSqliteValue::Int64 { value } => Ok(SqliteValue::Integer(value)),
806 FfiJsonSqliteValue::Float64 { value } => Ok(SqliteValue::Real(value)),
807 FfiJsonSqliteValue::String { value } => Ok(SqliteValue::Text(value)),
808 FfiJsonSqliteValue::Bytes { value } => Ok(SqliteValue::Blob(value)),
809 FfiJsonSqliteValue::Bool { value } => Ok(SqliteValue::Integer(i64::from(value))),
810 FfiJsonSqliteValue::Null => Ok(SqliteValue::Null),
811 }
812}
813
814fn parse_json_compat_params(
817 params: Option<Vec<FfiJsonSqliteValue>>,
818 params_json: Option<&str>,
819) -> Result<Vec<SqliteValue>, String> {
820 if let Some(params) = params {
821 if let Some(params_json) = params_json
822 && !params_json.trim().is_empty()
823 {
824 return Err(
825 "provide either typed params or params_json, but not both / 不能同时提供 typed params 与 params_json"
826 .to_string(),
827 );
828 }
829
830 return params
831 .into_iter()
832 .map(json_typed_value_to_sqlite_value)
833 .collect();
834 }
835
836 parse_legacy_params_json(params_json.unwrap_or_default()).map_err(|error| error.to_string())
837}
838
839fn ffi_value_to_sqlite_value(value: &VldbSqliteFfiValue) -> Result<SqliteValue, String> {
842 match value.kind {
843 VldbSqliteFfiValueKind::Null => Ok(SqliteValue::Null),
844 VldbSqliteFfiValueKind::Int64 => Ok(SqliteValue::Integer(value.int64_value)),
845 VldbSqliteFfiValueKind::Float64 => Ok(SqliteValue::Real(value.float64_value)),
846 VldbSqliteFfiValueKind::String => {
847 Ok(SqliteValue::Text(c_json_arg_to_string(value.string_value)?))
848 }
849 VldbSqliteFfiValueKind::Bytes => {
850 if value.bytes_value.data.is_null() && value.bytes_value.len > 0 {
851 return Err("bytes_value.data must not be null when len > 0 / len > 0 时 bytes_value.data 不能为空".to_string());
852 }
853 let bytes = if value.bytes_value.data.is_null() || value.bytes_value.len == 0 {
854 Vec::new()
855 } else {
856 unsafe {
860 std::slice::from_raw_parts(
861 value.bytes_value.data,
862 usize::try_from(value.bytes_value.len)
863 .map_err(|_| "bytes length exceeds usize / bytes 长度超过 usize".to_string())?,
864 )
865 .to_vec()
866 }
867 };
868 Ok(SqliteValue::Blob(bytes))
869 }
870 VldbSqliteFfiValueKind::Bool => Ok(SqliteValue::Integer(i64::from(value.bool_value != 0))),
871 }
872}
873
874fn ffi_values_from_parts(
877 params: *const VldbSqliteFfiValue,
878 params_len: u64,
879 params_json: *const c_char,
880) -> Result<Vec<SqliteValue>, String> {
881 let params_json_string = if params_json.is_null() {
882 String::new()
883 } else {
884 c_json_arg_to_string(params_json)?
885 };
886
887 if !params.is_null() && params_len > 0 {
888 if !params_json_string.trim().is_empty() {
889 return Err("provide either typed params or params_json, but not both / 不能同时提供 typed params 与 params_json".to_string());
890 }
891
892 let values = unsafe {
896 std::slice::from_raw_parts(
897 params,
898 usize::try_from(params_len)
899 .map_err(|_| "params_len exceeds usize / params_len 超过 usize".to_string())?,
900 )
901 };
902 return values.iter().map(ffi_value_to_sqlite_value).collect();
903 }
904
905 parse_legacy_params_json(¶ms_json_string).map_err(|error| error.to_string())
906}
907
908fn ffi_batch_values_from_parts(
911 items: *const VldbSqliteFfiValueSlice,
912 items_len: u64,
913) -> Result<Vec<Vec<SqliteValue>>, String> {
914 if items.is_null() || items_len == 0 {
915 return Err("items must not be empty / items 不能为空".to_string());
916 }
917
918 let item_slices = unsafe {
922 std::slice::from_raw_parts(
923 items,
924 usize::try_from(items_len)
925 .map_err(|_| "items_len exceeds usize / items_len 超过 usize".to_string())?,
926 )
927 };
928
929 item_slices
930 .iter()
931 .map(|item| {
932 if item.values.is_null() && item.len > 0 {
933 return Err(
934 "item.values must not be null when item.len > 0 / item.len > 0 时 item.values 不能为空"
935 .to_string(),
936 );
937 }
938 if item.values.is_null() || item.len == 0 {
939 return Ok(Vec::new());
940 }
941 let values = unsafe {
945 std::slice::from_raw_parts(
946 item.values,
947 usize::try_from(item.len)
948 .map_err(|_| "item.len exceeds usize / item.len 超过 usize".to_string())?,
949 )
950 };
951 values.iter().map(ffi_value_to_sqlite_value).collect()
952 })
953 .collect()
954}
955
956fn bytes_to_buffer(bytes: &[u8]) -> VldbSqliteByteBuffer {
959 let mut owned = bytes.to_vec();
960 let buffer = VldbSqliteByteBuffer {
961 data: owned.as_mut_ptr(),
962 len: u64::try_from(owned.len()).unwrap_or(u64::MAX),
963 cap: u64::try_from(owned.capacity()).unwrap_or(u64::MAX),
964 };
965 std::mem::forget(owned);
966 buffer
967}
968
969fn sql_exec_error_to_string(error: SqlExecCoreError) -> String {
970 error.to_string()
971}
972
973fn make_main_ffi_query_stream_spool_path() -> PathBuf {
974 let unique = NEXT_MAIN_FFI_STREAM_ID.fetch_add(1, Ordering::Relaxed);
975 let millis = SystemTime::now()
976 .duration_since(UNIX_EPOCH)
977 .map(|value| value.as_millis())
978 .unwrap_or_default();
979 std::env::temp_dir().join(format!(
980 "vldb-sqlite-ffi-query-stream-{}-{}-{}.bin",
981 std::process::id(),
982 unique,
983 millis
984 ))
985}
986
987fn start_main_ffi_query_stream(
988 database: Arc<SqliteDatabaseHandle>,
989 sql: String,
990 bound_values: Vec<SqliteValue>,
991 target_chunk_size: usize,
992) -> Arc<FfiQueryStreamSharedState> {
993 let state = FfiQueryStreamSharedState::new(make_main_ffi_query_stream_spool_path());
994 let worker_state = Arc::clone(&state);
995 thread::spawn(move || {
996 let result = (|| -> Result<QueryStreamMetrics, String> {
997 let mut connection = database
998 .open_connection()
999 .map_err(|error| format!("failed to open runtime-managed sqlite connection: {error}"))?;
1000 let writer = FfiStreamingTempFileWriter::new(Arc::clone(&worker_state), target_chunk_size)?;
1001 let (_writer, metrics) = query_stream_with_writer(&mut connection, &sql, &bound_values, writer)
1002 .map_err(sql_exec_error_to_string)?;
1003 Ok(metrics)
1004 })();
1005
1006 match result {
1007 Ok(metrics) => worker_state.finish(metrics),
1008 Err(error) => worker_state.fail(error),
1009 }
1010 });
1011 state
1012}
1013
1014fn cleanup_json_query_stream_registry_at(
1015 guard: &mut std::collections::HashMap<u64, JsonQueryStreamEntry>,
1016 now: Instant,
1017) {
1018 guard.retain(|_, entry| now.duration_since(entry.last_accessed_at) <= JSON_QUERY_STREAM_TTL);
1019
1020 if guard.len() <= JSON_QUERY_STREAM_MAX_ENTRIES {
1021 return;
1022 }
1023
1024 let mut ordered = guard
1025 .iter()
1026 .map(|(stream_id, entry)| (*stream_id, entry.last_accessed_at))
1027 .collect::<Vec<_>>();
1028 ordered.sort_by_key(|(_, last_accessed_at)| *last_accessed_at);
1029 let remove_count = guard.len().saturating_sub(JSON_QUERY_STREAM_MAX_ENTRIES);
1030 for (stream_id, _) in ordered.into_iter().take(remove_count) {
1031 guard.remove(&stream_id);
1032 }
1033}
1034
1035fn register_json_query_stream(result: QueryStreamResult) -> Result<u64, String> {
1038 let stream_id = NEXT_JSON_STREAM_ID.fetch_add(1, Ordering::Relaxed);
1039 let mut guard = json_query_stream_registry()
1040 .lock()
1041 .map_err(|_| "failed to lock JSON query stream registry / 无法锁定 JSON QueryStream 注册表".to_string())?;
1042 cleanup_json_query_stream_registry_at(&mut guard, Instant::now());
1043 guard.insert(
1044 stream_id,
1045 JsonQueryStreamEntry {
1046 result,
1047 last_accessed_at: Instant::now(),
1048 },
1049 );
1050 Ok(stream_id)
1051}
1052
1053fn with_json_query_stream<T>(
1056 stream_id: u64,
1057 f: impl FnOnce(&QueryStreamResult) -> Result<T, String>,
1058) -> Result<T, String> {
1059 let mut guard = json_query_stream_registry()
1060 .lock()
1061 .map_err(|_| "failed to lock JSON query stream registry / 无法锁定 JSON QueryStream 注册表".to_string())?;
1062 cleanup_json_query_stream_registry_at(&mut guard, Instant::now());
1063 let entry = guard
1064 .get_mut(&stream_id)
1065 .ok_or_else(|| format!("query stream handle not found: {stream_id} / QueryStream 句柄不存在"))?;
1066 entry.last_accessed_at = Instant::now();
1067 f(&entry.result)
1068}
1069
1070fn close_json_query_stream(stream_id: u64) -> Result<bool, String> {
1073 let mut guard = json_query_stream_registry()
1074 .lock()
1075 .map_err(|_| "failed to lock JSON query stream registry / 无法锁定 JSON QueryStream 注册表".to_string())?;
1076 cleanup_json_query_stream_registry_at(&mut guard, Instant::now());
1077 Ok(guard.remove(&stream_id).is_some())
1078}
1079
1080#[derive(Debug, Deserialize)]
1081struct FfiTokenizeTextRequest {
1082 text: String,
1083 tokenizer_mode: Option<String>,
1084 search_mode: Option<bool>,
1085 db_path: Option<String>,
1086}
1087
1088#[derive(Debug, Deserialize)]
1089struct FfiUpsertCustomWordRequest {
1090 db_path: String,
1091 word: String,
1092 weight: Option<u64>,
1093}
1094
1095#[derive(Debug, Deserialize)]
1096struct FfiRemoveCustomWordRequest {
1097 db_path: String,
1098 word: String,
1099}
1100
1101#[derive(Debug, Deserialize)]
1102struct FfiListCustomWordsRequest {
1103 db_path: String,
1104}
1105
1106#[derive(Debug, Deserialize)]
1107struct FfiEnsureFtsIndexRequest {
1108 db_path: String,
1109 index_name: String,
1110 tokenizer_mode: Option<String>,
1111}
1112
1113#[derive(Debug, Deserialize)]
1114struct FfiRebuildFtsIndexRequest {
1115 db_path: String,
1116 index_name: String,
1117 tokenizer_mode: Option<String>,
1118}
1119
1120#[derive(Debug, Deserialize)]
1121struct FfiUpsertFtsDocumentRequest {
1122 db_path: String,
1123 index_name: String,
1124 tokenizer_mode: Option<String>,
1125 id: String,
1126 file_path: String,
1127 title: Option<String>,
1128 content: String,
1129}
1130
1131#[derive(Debug, Deserialize)]
1132struct FfiDeleteFtsDocumentRequest {
1133 db_path: String,
1134 index_name: String,
1135 id: String,
1136}
1137
1138#[derive(Debug, Deserialize)]
1139struct FfiSearchFtsRequest {
1140 db_path: String,
1141 index_name: String,
1142 tokenizer_mode: Option<String>,
1143 query: String,
1144 limit: Option<u32>,
1145 offset: Option<u32>,
1146}
1147
1148#[derive(Debug, Clone, Deserialize)]
1149#[serde(tag = "kind", rename_all = "snake_case")]
1150enum FfiJsonSqliteValue {
1151 Int64 { value: i64 },
1152 Float64 { value: f64 },
1153 String { value: String },
1154 Bytes { value: Vec<u8> },
1155 Bool { value: bool },
1156 Null,
1157}
1158
1159#[derive(Debug, Deserialize)]
1160struct FfiExecuteScriptJsonRequest {
1161 db_path: String,
1162 sql: String,
1163 params_json: Option<String>,
1164 params: Option<Vec<FfiJsonSqliteValue>>,
1165}
1166
1167#[derive(Debug, Deserialize)]
1168struct FfiExecuteBatchJsonRequest {
1169 db_path: String,
1170 sql: String,
1171 items: Vec<Vec<FfiJsonSqliteValue>>,
1172}
1173
1174#[derive(Debug, Deserialize)]
1175struct FfiQueryJsonJsonRequest {
1176 db_path: String,
1177 sql: String,
1178 params_json: Option<String>,
1179 params: Option<Vec<FfiJsonSqliteValue>>,
1180}
1181
1182#[derive(Debug, Deserialize)]
1183struct FfiQueryStreamJsonRequest {
1184 db_path: String,
1185 sql: String,
1186 params_json: Option<String>,
1187 params: Option<Vec<FfiJsonSqliteValue>>,
1188 chunk_bytes: Option<u64>,
1189}
1190
1191#[derive(Debug, Deserialize)]
1192struct FfiQueryStreamChunkJsonRequest {
1193 stream_id: u64,
1194 index: u64,
1195}
1196
1197#[derive(Debug, Deserialize)]
1198struct FfiQueryStreamCloseJsonRequest {
1199 stream_id: u64,
1200}
1201
1202#[derive(Debug, Serialize)]
1203struct FfiExecuteJsonResponse {
1204 success: bool,
1205 message: String,
1206 rows_changed: i64,
1207 last_insert_rowid: i64,
1208}
1209
1210#[derive(Debug, Serialize)]
1211struct FfiExecuteBatchJsonResponse {
1212 success: bool,
1213 message: String,
1214 rows_changed: i64,
1215 last_insert_rowid: i64,
1216 statements_executed: i64,
1217}
1218
1219#[derive(Debug, Serialize)]
1220struct FfiQueryStreamJsonResponse {
1221 success: bool,
1222 message: String,
1223 stream_id: u64,
1224 row_count: u64,
1225 chunk_count: u64,
1226 total_bytes: u64,
1227}
1228
1229#[derive(Debug, Serialize)]
1230struct FfiQueryStreamChunkJsonResponse {
1231 success: bool,
1232 message: String,
1233 stream_id: u64,
1234 index: u64,
1235 byte_count: u64,
1236 chunk_base64: String,
1237}
1238
1239#[derive(Debug, Serialize)]
1240struct FfiQueryStreamCloseJsonResponse {
1241 success: bool,
1242 message: String,
1243 stream_id: u64,
1244}
1245
1246#[unsafe(no_mangle)]
1249pub extern "C" fn vldb_sqlite_library_info_json() -> *mut c_char {
1250 clear_last_error_inner();
1251 match serde_json::to_string(&library_info()) {
1252 Ok(json) => json_to_c_string(json),
1253 Err(error) => {
1254 update_last_error(format!("failed to serialize library info: {error}"));
1255 std::ptr::null_mut()
1256 }
1257 }
1258}
1259
1260#[unsafe(no_mangle)]
1263pub extern "C" fn vldb_sqlite_string_free(value: *mut c_char) {
1264 if value.is_null() {
1265 return;
1266 }
1267
1268 unsafe {
1272 drop(CString::from_raw(value));
1273 }
1274}
1275
1276#[unsafe(no_mangle)]
1279pub extern "C" fn vldb_sqlite_last_error_message() -> *const c_char {
1280 match last_error_slot().lock() {
1281 Ok(guard) => guard
1282 .as_ref()
1283 .map(|value| value.as_ptr())
1284 .unwrap_or(std::ptr::null()),
1285 Err(_) => std::ptr::null(),
1286 }
1287}
1288
1289#[unsafe(no_mangle)]
1292pub extern "C" fn vldb_sqlite_clear_last_error() {
1293 clear_last_error_inner();
1294}
1295
1296#[unsafe(no_mangle)]
1299pub extern "C" fn vldb_sqlite_json_is_null(value: *const c_char) -> u8 {
1300 if value.is_null() { 1 } else { 0 }
1301}
1302
1303#[unsafe(no_mangle)]
1306pub extern "C" fn vldb_sqlite_runtime_create_default() -> *mut VldbSqliteRuntimeHandle {
1307 clear_last_error_inner();
1308 Box::into_raw(Box::new(VldbSqliteRuntimeHandle {
1309 inner: SqliteRuntime::new(),
1310 }))
1311}
1312
1313#[unsafe(no_mangle)]
1316pub extern "C" fn vldb_sqlite_runtime_destroy(handle: *mut VldbSqliteRuntimeHandle) {
1317 if handle.is_null() {
1318 return;
1319 }
1320
1321 unsafe {
1325 drop(Box::from_raw(handle));
1326 }
1327}
1328
1329#[unsafe(no_mangle)]
1332pub extern "C" fn vldb_sqlite_runtime_open_database(
1333 runtime: *mut VldbSqliteRuntimeHandle,
1334 db_path: *const c_char,
1335) -> *mut VldbSqliteDatabaseHandle {
1336 clear_last_error_inner();
1337 let result = (|| -> Result<*mut VldbSqliteDatabaseHandle, String> {
1338 let runtime = runtime_handle_ref(runtime)?;
1339 let db_path = c_json_arg_to_string(db_path)?;
1340 let handle = runtime
1341 .inner
1342 .open_database(db_path.as_str())
1343 .map_err(|error| format!("failed to open database via runtime: {error}"))?;
1344 Ok(Box::into_raw(Box::new(VldbSqliteDatabaseHandle {
1345 inner: handle,
1346 })))
1347 })();
1348
1349 match result {
1350 Ok(handle) => handle,
1351 Err(error) => {
1352 update_last_error(error);
1353 std::ptr::null_mut()
1354 }
1355 }
1356}
1357
1358#[unsafe(no_mangle)]
1361pub extern "C" fn vldb_sqlite_runtime_close_database(
1362 runtime: *mut VldbSqliteRuntimeHandle,
1363 db_path: *const c_char,
1364) -> u8 {
1365 clear_last_error_inner();
1366 let result = (|| -> Result<u8, String> {
1367 let runtime = runtime_handle_ref(runtime)?;
1368 let db_path = c_json_arg_to_string(db_path)?;
1369 Ok(if runtime.inner.close_database(db_path.as_str()) {
1370 1
1371 } else {
1372 0
1373 })
1374 })();
1375
1376 match result {
1377 Ok(value) => value,
1378 Err(error) => {
1379 update_last_error(error);
1380 0
1381 }
1382 }
1383}
1384
1385#[unsafe(no_mangle)]
1388pub extern "C" fn vldb_sqlite_database_destroy(handle: *mut VldbSqliteDatabaseHandle) {
1389 if handle.is_null() {
1390 return;
1391 }
1392
1393 unsafe {
1397 drop(Box::from_raw(handle));
1398 }
1399}
1400
1401#[unsafe(no_mangle)]
1404pub extern "C" fn vldb_sqlite_database_db_path(
1405 handle: *mut VldbSqliteDatabaseHandle,
1406) -> *mut c_char {
1407 clear_last_error_inner();
1408 match database_handle_ref(handle) {
1409 Ok(handle) => json_to_c_string(handle.inner.db_path().to_string()),
1410 Err(error) => {
1411 update_last_error(error);
1412 std::ptr::null_mut()
1413 }
1414 }
1415}
1416
1417#[unsafe(no_mangle)]
1420pub extern "C" fn vldb_sqlite_bytes_free(buffer: VldbSqliteByteBuffer) {
1421 if buffer.data.is_null() {
1422 return;
1423 }
1424
1425 if let (Ok(len), Ok(cap)) = (usize::try_from(buffer.len), usize::try_from(buffer.cap)) {
1426 unsafe {
1430 let _ = Vec::from_raw_parts(buffer.data, len, cap);
1431 }
1432 }
1433}
1434
1435#[unsafe(no_mangle)]
1438pub extern "C" fn vldb_sqlite_database_execute_script(
1439 handle: *mut VldbSqliteDatabaseHandle,
1440 sql: *const c_char,
1441 params: *const VldbSqliteFfiValue,
1442 params_len: u64,
1443 params_json: *const c_char,
1444) -> *mut VldbSqliteExecuteResultHandle {
1445 clear_last_error_inner();
1446 match (|| -> Result<VldbSqliteExecuteResultHandle, String> {
1447 let sql = c_json_arg_to_string(sql)?;
1448 let mut connection = open_connection_for_handle(handle)?;
1449 let bound_values = ffi_values_from_parts(params, params_len, params_json)?;
1450 let result = execute_script_core(&mut connection, &sql, &bound_values)
1451 .map_err(sql_exec_error_to_string)?;
1452 Ok(VldbSqliteExecuteResultHandle {
1453 inner: VldbSqliteExecuteResult {
1454 success: result.success,
1455 message: result.message,
1456 rows_changed: result.rows_changed,
1457 last_insert_rowid: result.last_insert_rowid,
1458 statements_executed: i64::try_from(count_sql_statements(&sql)).unwrap_or(i64::MAX),
1459 },
1460 })
1461 })() {
1462 Ok(result) => Box::into_raw(Box::new(result)),
1463 Err(error) => {
1464 update_last_error(error);
1465 std::ptr::null_mut()
1466 }
1467 }
1468}
1469
1470#[unsafe(no_mangle)]
1473pub extern "C" fn vldb_sqlite_database_execute_batch(
1474 handle: *mut VldbSqliteDatabaseHandle,
1475 sql: *const c_char,
1476 items: *const VldbSqliteFfiValueSlice,
1477 items_len: u64,
1478) -> *mut VldbSqliteExecuteResultHandle {
1479 clear_last_error_inner();
1480 match (|| -> Result<VldbSqliteExecuteResultHandle, String> {
1481 let sql = c_json_arg_to_string(sql)?;
1482 let mut connection = open_connection_for_handle(handle)?;
1483 let batch_params = ffi_batch_values_from_parts(items, items_len)?;
1484 let result = execute_batch_core(&mut connection, &sql, &batch_params)
1485 .map_err(sql_exec_error_to_string)?;
1486 Ok(VldbSqliteExecuteResultHandle {
1487 inner: VldbSqliteExecuteResult {
1488 success: result.success,
1489 message: result.message,
1490 rows_changed: result.rows_changed,
1491 last_insert_rowid: result.last_insert_rowid,
1492 statements_executed: result.statements_executed,
1493 },
1494 })
1495 })() {
1496 Ok(result) => Box::into_raw(Box::new(result)),
1497 Err(error) => {
1498 update_last_error(error);
1499 std::ptr::null_mut()
1500 }
1501 }
1502}
1503
1504#[unsafe(no_mangle)]
1507pub extern "C" fn vldb_sqlite_database_query_json(
1508 handle: *mut VldbSqliteDatabaseHandle,
1509 sql: *const c_char,
1510 params: *const VldbSqliteFfiValue,
1511 params_len: u64,
1512 params_json: *const c_char,
1513) -> *mut VldbSqliteQueryJsonResultHandle {
1514 clear_last_error_inner();
1515 match (|| -> Result<VldbSqliteQueryJsonResultHandle, String> {
1516 let sql = c_json_arg_to_string(sql)?;
1517 let mut connection = open_connection_for_handle(handle)?;
1518 let bound_values = ffi_values_from_parts(params, params_len, params_json)?;
1519 let result = query_json_core(&mut connection, &sql, &bound_values)
1520 .map_err(sql_exec_error_to_string)?;
1521 Ok(VldbSqliteQueryJsonResultHandle { inner: result })
1522 })() {
1523 Ok(result) => Box::into_raw(Box::new(result)),
1524 Err(error) => {
1525 update_last_error(error);
1526 std::ptr::null_mut()
1527 }
1528 }
1529}
1530
1531#[unsafe(no_mangle)]
1534pub extern "C" fn vldb_sqlite_database_query_stream(
1535 handle: *mut VldbSqliteDatabaseHandle,
1536 sql: *const c_char,
1537 params: *const VldbSqliteFfiValue,
1538 params_len: u64,
1539 params_json: *const c_char,
1540 chunk_bytes: u64,
1541) -> *mut VldbSqliteQueryStreamHandle {
1542 clear_last_error_inner();
1543 match (|| -> Result<VldbSqliteQueryStreamHandle, String> {
1544 let sql = c_json_arg_to_string(sql)?;
1545 let bound_values = ffi_values_from_parts(params, params_len, params_json)?;
1546 let database = Arc::clone(&database_handle_ref(handle)?.inner);
1547 let target_chunk_size = if chunk_bytes == 0 {
1548 DEFAULT_IPC_CHUNK_BYTES
1549 } else {
1550 usize::try_from(chunk_bytes)
1551 .map_err(|_| "chunk_bytes exceeds usize / chunk_bytes 超过 usize".to_string())?
1552 };
1553 let state = start_main_ffi_query_stream(database, sql, bound_values, target_chunk_size);
1554 Ok(VldbSqliteQueryStreamHandle { inner: state })
1555 })() {
1556 Ok(result) => Box::into_raw(Box::new(result)),
1557 Err(error) => {
1558 update_last_error(error);
1559 std::ptr::null_mut()
1560 }
1561 }
1562}
1563
1564#[unsafe(no_mangle)]
1567pub extern "C" fn vldb_sqlite_execute_result_destroy(handle: *mut VldbSqliteExecuteResultHandle) {
1568 if handle.is_null() {
1569 return;
1570 }
1571 unsafe {
1572 drop(Box::from_raw(handle));
1573 }
1574}
1575
1576#[unsafe(no_mangle)]
1579pub extern "C" fn vldb_sqlite_execute_result_success(
1580 handle: *mut VldbSqliteExecuteResultHandle,
1581) -> u8 {
1582 match execute_result_handle_ref(handle) {
1583 Ok(handle) => u8::from(handle.inner.success),
1584 Err(error) => {
1585 update_last_error(error);
1586 0
1587 }
1588 }
1589}
1590
1591#[unsafe(no_mangle)]
1594pub extern "C" fn vldb_sqlite_execute_result_message(
1595 handle: *mut VldbSqliteExecuteResultHandle,
1596) -> *mut c_char {
1597 clear_last_error_inner();
1598 match execute_result_handle_ref(handle) {
1599 Ok(handle) => json_to_c_string(handle.inner.message.clone()),
1600 Err(error) => {
1601 update_last_error(error);
1602 std::ptr::null_mut()
1603 }
1604 }
1605}
1606
1607#[unsafe(no_mangle)]
1610pub extern "C" fn vldb_sqlite_execute_result_rows_changed(
1611 handle: *mut VldbSqliteExecuteResultHandle,
1612) -> i64 {
1613 match execute_result_handle_ref(handle) {
1614 Ok(handle) => handle.inner.rows_changed,
1615 Err(error) => {
1616 update_last_error(error);
1617 0
1618 }
1619 }
1620}
1621
1622#[unsafe(no_mangle)]
1625pub extern "C" fn vldb_sqlite_execute_result_last_insert_rowid(
1626 handle: *mut VldbSqliteExecuteResultHandle,
1627) -> i64 {
1628 match execute_result_handle_ref(handle) {
1629 Ok(handle) => handle.inner.last_insert_rowid,
1630 Err(error) => {
1631 update_last_error(error);
1632 0
1633 }
1634 }
1635}
1636
1637#[unsafe(no_mangle)]
1640pub extern "C" fn vldb_sqlite_execute_result_statements_executed(
1641 handle: *mut VldbSqliteExecuteResultHandle,
1642) -> i64 {
1643 match execute_result_handle_ref(handle) {
1644 Ok(handle) => handle.inner.statements_executed,
1645 Err(error) => {
1646 update_last_error(error);
1647 0
1648 }
1649 }
1650}
1651
1652#[unsafe(no_mangle)]
1655pub extern "C" fn vldb_sqlite_query_json_result_destroy(
1656 handle: *mut VldbSqliteQueryJsonResultHandle,
1657) {
1658 if handle.is_null() {
1659 return;
1660 }
1661 unsafe {
1662 drop(Box::from_raw(handle));
1663 }
1664}
1665
1666#[unsafe(no_mangle)]
1669pub extern "C" fn vldb_sqlite_query_json_result_json_data(
1670 handle: *mut VldbSqliteQueryJsonResultHandle,
1671) -> *mut c_char {
1672 clear_last_error_inner();
1673 match query_json_result_handle_ref(handle) {
1674 Ok(handle) => json_to_c_string(handle.inner.json_data.clone()),
1675 Err(error) => {
1676 update_last_error(error);
1677 std::ptr::null_mut()
1678 }
1679 }
1680}
1681
1682#[unsafe(no_mangle)]
1685pub extern "C" fn vldb_sqlite_query_json_result_row_count(
1686 handle: *mut VldbSqliteQueryJsonResultHandle,
1687) -> u64 {
1688 match query_json_result_handle_ref(handle) {
1689 Ok(handle) => handle.inner.row_count,
1690 Err(error) => {
1691 update_last_error(error);
1692 0
1693 }
1694 }
1695}
1696
1697#[unsafe(no_mangle)]
1700pub extern "C" fn vldb_sqlite_query_stream_destroy(handle: *mut VldbSqliteQueryStreamHandle) {
1701 if handle.is_null() {
1702 return;
1703 }
1704 unsafe {
1705 drop(Box::from_raw(handle));
1706 }
1707}
1708
1709#[unsafe(no_mangle)]
1712pub extern "C" fn vldb_sqlite_query_stream_chunk_count(
1713 handle: *mut VldbSqliteQueryStreamHandle,
1714) -> u64 {
1715 match query_stream_handle_ref(handle) {
1716 Ok(handle) => match handle.inner.wait_for_metrics() {
1717 Ok((chunk_count, _, _)) => chunk_count,
1718 Err(error) => {
1719 update_last_error(error);
1720 0
1721 }
1722 },
1723 Err(error) => {
1724 update_last_error(error);
1725 0
1726 }
1727 }
1728}
1729
1730#[unsafe(no_mangle)]
1733pub extern "C" fn vldb_sqlite_query_stream_row_count(
1734 handle: *mut VldbSqliteQueryStreamHandle,
1735) -> u64 {
1736 match query_stream_handle_ref(handle) {
1737 Ok(handle) => match handle.inner.wait_for_metrics() {
1738 Ok((_, row_count, _)) => row_count,
1739 Err(error) => {
1740 update_last_error(error);
1741 0
1742 }
1743 },
1744 Err(error) => {
1745 update_last_error(error);
1746 0
1747 }
1748 }
1749}
1750
1751#[unsafe(no_mangle)]
1754pub extern "C" fn vldb_sqlite_query_stream_total_bytes(
1755 handle: *mut VldbSqliteQueryStreamHandle,
1756) -> u64 {
1757 match query_stream_handle_ref(handle) {
1758 Ok(handle) => match handle.inner.wait_for_metrics() {
1759 Ok((_, _, total_bytes)) => total_bytes,
1760 Err(error) => {
1761 update_last_error(error);
1762 0
1763 }
1764 },
1765 Err(error) => {
1766 update_last_error(error);
1767 0
1768 }
1769 }
1770}
1771
1772#[unsafe(no_mangle)]
1775pub extern "C" fn vldb_sqlite_query_stream_get_chunk(
1776 handle: *mut VldbSqliteQueryStreamHandle,
1777 index: u64,
1778) -> VldbSqliteByteBuffer {
1779 clear_last_error_inner();
1780 match (|| -> Result<VldbSqliteByteBuffer, String> {
1781 let handle = query_stream_handle_ref(handle)?;
1782 let chunk = handle.inner.read_chunk(
1783 usize::try_from(index)
1784 .map_err(|_| "chunk index exceeds usize / chunk 下标超过 usize".to_string())?,
1785 )?;
1786 Ok(bytes_to_buffer(&chunk))
1787 })() {
1788 Ok(buffer) => buffer,
1789 Err(error) => {
1790 update_last_error(error);
1791 VldbSqliteByteBuffer::default()
1792 }
1793 }
1794}
1795
1796#[unsafe(no_mangle)]
1799pub extern "C" fn vldb_sqlite_database_tokenize_text(
1800 handle: *mut VldbSqliteDatabaseHandle,
1801 tokenizer_mode: VldbSqliteFfiTokenizerMode,
1802 text: *const c_char,
1803 search_mode: u8,
1804) -> *mut VldbSqliteTokenizeResultHandle {
1805 clear_last_error_inner();
1806 let result = (|| -> Result<*mut VldbSqliteTokenizeResultHandle, String> {
1807 let text = c_json_arg_to_string(text)?;
1808 let connection = open_connection_for_handle(handle)?;
1809 let result = tokenize_text(
1810 Some(&connection),
1811 parse_ffi_tokenizer_mode(tokenizer_mode),
1812 text.as_str(),
1813 search_mode != 0,
1814 )
1815 .map_err(|error| format!("tokenize_text failed: {error}"))?;
1816 Ok(Box::into_raw(Box::new(VldbSqliteTokenizeResultHandle {
1817 inner: result,
1818 })))
1819 })();
1820
1821 match result {
1822 Ok(handle) => handle,
1823 Err(error) => {
1824 update_last_error(error);
1825 std::ptr::null_mut()
1826 }
1827 }
1828}
1829
1830#[unsafe(no_mangle)]
1833pub extern "C" fn vldb_sqlite_tokenize_result_destroy(
1834 handle: *mut VldbSqliteTokenizeResultHandle,
1835) {
1836 if handle.is_null() {
1837 return;
1838 }
1839
1840 unsafe {
1844 drop(Box::from_raw(handle));
1845 }
1846}
1847
1848#[unsafe(no_mangle)]
1851pub extern "C" fn vldb_sqlite_tokenize_result_normalized_text(
1852 handle: *mut VldbSqliteTokenizeResultHandle,
1853) -> *mut c_char {
1854 clear_last_error_inner();
1855 match tokenize_result_handle_ref(handle) {
1856 Ok(handle) => json_to_c_string(handle.inner.normalized_text.clone()),
1857 Err(error) => {
1858 update_last_error(error);
1859 std::ptr::null_mut()
1860 }
1861 }
1862}
1863
1864#[unsafe(no_mangle)]
1867pub extern "C" fn vldb_sqlite_tokenize_result_fts_query(
1868 handle: *mut VldbSqliteTokenizeResultHandle,
1869) -> *mut c_char {
1870 clear_last_error_inner();
1871 match tokenize_result_handle_ref(handle) {
1872 Ok(handle) => json_to_c_string(handle.inner.fts_query.clone()),
1873 Err(error) => {
1874 update_last_error(error);
1875 std::ptr::null_mut()
1876 }
1877 }
1878}
1879
1880#[unsafe(no_mangle)]
1883pub extern "C" fn vldb_sqlite_tokenize_result_token_count(
1884 handle: *mut VldbSqliteTokenizeResultHandle,
1885) -> u64 {
1886 clear_last_error_inner();
1887 match tokenize_result_handle_ref(handle) {
1888 Ok(handle) => handle.inner.tokens.len() as u64,
1889 Err(error) => {
1890 update_last_error(error);
1891 0
1892 }
1893 }
1894}
1895
1896#[unsafe(no_mangle)]
1899pub extern "C" fn vldb_sqlite_tokenize_result_get_token(
1900 handle: *mut VldbSqliteTokenizeResultHandle,
1901 index: u64,
1902) -> *mut c_char {
1903 clear_last_error_inner();
1904 let result = (|| -> Result<*mut c_char, String> {
1905 let handle = tokenize_result_handle_ref(handle)?;
1906 let token = handle
1907 .inner
1908 .tokens
1909 .get(index as usize)
1910 .ok_or_else(|| format!("token index out of range: {index}"))?;
1911 Ok(json_to_c_string(token.clone()))
1912 })();
1913
1914 match result {
1915 Ok(value) => value,
1916 Err(error) => {
1917 update_last_error(error);
1918 std::ptr::null_mut()
1919 }
1920 }
1921}
1922
1923#[unsafe(no_mangle)]
1926pub extern "C" fn vldb_sqlite_database_upsert_custom_word(
1927 handle: *mut VldbSqliteDatabaseHandle,
1928 word: *const c_char,
1929 weight: u64,
1930 out_result: *mut VldbSqliteDictionaryMutationResultPod,
1931) -> i32 {
1932 clear_last_error_inner();
1933 let result = (|| -> Result<VldbSqliteDictionaryMutationResultPod, String> {
1934 if out_result.is_null() {
1935 return Err("out_result must not be null / out_result 不能为空".to_string());
1936 }
1937 let word = c_json_arg_to_string(word)?;
1938 let connection = open_connection_for_handle(handle)?;
1939 let result = upsert_custom_word(
1940 &connection,
1941 word.as_str(),
1942 usize::try_from(weight.max(1)).unwrap_or(usize::MAX),
1943 )
1944 .map_err(|error| format!("upsert_custom_word failed: {error}"))?;
1945 Ok(VldbSqliteDictionaryMutationResultPod {
1946 success: if result.success { 1 } else { 0 },
1947 affected_rows: result.affected_rows,
1948 })
1949 })();
1950
1951 match result {
1952 Ok(result) => {
1953 unsafe {
1957 *out_result = result;
1958 }
1959 VldbSqliteStatusCode::Success as i32
1960 }
1961 Err(error) => {
1962 update_last_error(error);
1963 VldbSqliteStatusCode::Failure as i32
1964 }
1965 }
1966}
1967
1968#[unsafe(no_mangle)]
1971pub extern "C" fn vldb_sqlite_database_remove_custom_word(
1972 handle: *mut VldbSqliteDatabaseHandle,
1973 word: *const c_char,
1974 out_result: *mut VldbSqliteDictionaryMutationResultPod,
1975) -> i32 {
1976 clear_last_error_inner();
1977 let result = (|| -> Result<VldbSqliteDictionaryMutationResultPod, String> {
1978 if out_result.is_null() {
1979 return Err("out_result must not be null / out_result 不能为空".to_string());
1980 }
1981 let word = c_json_arg_to_string(word)?;
1982 let connection = open_connection_for_handle(handle)?;
1983 let result = remove_custom_word(&connection, word.as_str())
1984 .map_err(|error| format!("remove_custom_word failed: {error}"))?;
1985 Ok(VldbSqliteDictionaryMutationResultPod {
1986 success: if result.success { 1 } else { 0 },
1987 affected_rows: result.affected_rows,
1988 })
1989 })();
1990
1991 match result {
1992 Ok(result) => {
1993 unsafe {
1997 *out_result = result;
1998 }
1999 VldbSqliteStatusCode::Success as i32
2000 }
2001 Err(error) => {
2002 update_last_error(error);
2003 VldbSqliteStatusCode::Failure as i32
2004 }
2005 }
2006}
2007
2008#[unsafe(no_mangle)]
2011pub extern "C" fn vldb_sqlite_database_list_custom_words(
2012 handle: *mut VldbSqliteDatabaseHandle,
2013) -> *mut VldbSqliteCustomWordListHandle {
2014 clear_last_error_inner();
2015 let result = (|| -> Result<*mut VldbSqliteCustomWordListHandle, String> {
2016 let connection = open_connection_for_handle(handle)?;
2017 let result = list_custom_words(&connection)
2018 .map_err(|error| format!("list_custom_words failed: {error}"))?;
2019 Ok(Box::into_raw(Box::new(VldbSqliteCustomWordListHandle {
2020 inner: result,
2021 })))
2022 })();
2023
2024 match result {
2025 Ok(handle) => handle,
2026 Err(error) => {
2027 update_last_error(error);
2028 std::ptr::null_mut()
2029 }
2030 }
2031}
2032
2033#[unsafe(no_mangle)]
2036pub extern "C" fn vldb_sqlite_custom_word_list_destroy(
2037 handle: *mut VldbSqliteCustomWordListHandle,
2038) {
2039 if handle.is_null() {
2040 return;
2041 }
2042
2043 unsafe {
2047 drop(Box::from_raw(handle));
2048 }
2049}
2050
2051#[unsafe(no_mangle)]
2054pub extern "C" fn vldb_sqlite_custom_word_list_len(
2055 handle: *mut VldbSqliteCustomWordListHandle,
2056) -> u64 {
2057 clear_last_error_inner();
2058 match custom_word_list_handle_ref(handle) {
2059 Ok(handle) => handle.inner.words.len() as u64,
2060 Err(error) => {
2061 update_last_error(error);
2062 0
2063 }
2064 }
2065}
2066
2067#[unsafe(no_mangle)]
2070pub extern "C" fn vldb_sqlite_custom_word_list_get_word(
2071 handle: *mut VldbSqliteCustomWordListHandle,
2072 index: u64,
2073) -> *mut c_char {
2074 clear_last_error_inner();
2075 let result = (|| -> Result<*mut c_char, String> {
2076 let handle = custom_word_list_handle_ref(handle)?;
2077 let entry = handle
2078 .inner
2079 .words
2080 .get(index as usize)
2081 .ok_or_else(|| format!("custom word index out of range: {index}"))?;
2082 Ok(json_to_c_string(entry.word.clone()))
2083 })();
2084
2085 match result {
2086 Ok(value) => value,
2087 Err(error) => {
2088 update_last_error(error);
2089 std::ptr::null_mut()
2090 }
2091 }
2092}
2093
2094#[unsafe(no_mangle)]
2097pub extern "C" fn vldb_sqlite_custom_word_list_get_weight(
2098 handle: *mut VldbSqliteCustomWordListHandle,
2099 index: u64,
2100) -> u64 {
2101 clear_last_error_inner();
2102 let result = (|| -> Result<u64, String> {
2103 let handle = custom_word_list_handle_ref(handle)?;
2104 let entry = handle
2105 .inner
2106 .words
2107 .get(index as usize)
2108 .ok_or_else(|| format!("custom word index out of range: {index}"))?;
2109 Ok(entry.weight as u64)
2110 })();
2111
2112 match result {
2113 Ok(value) => value,
2114 Err(error) => {
2115 update_last_error(error);
2116 0
2117 }
2118 }
2119}
2120
2121#[unsafe(no_mangle)]
2124pub extern "C" fn vldb_sqlite_database_ensure_fts_index(
2125 handle: *mut VldbSqliteDatabaseHandle,
2126 index_name: *const c_char,
2127 tokenizer_mode: VldbSqliteFfiTokenizerMode,
2128 out_result: *mut VldbSqliteEnsureFtsIndexResultPod,
2129) -> i32 {
2130 clear_last_error_inner();
2131 let result = (|| -> Result<VldbSqliteEnsureFtsIndexResultPod, String> {
2132 if out_result.is_null() {
2133 return Err("out_result must not be null / out_result 不能为空".to_string());
2134 }
2135 let index_name = c_json_arg_to_string(index_name)?;
2136 let connection = open_connection_for_handle(handle)?;
2137 let result = ensure_fts_index(
2138 &connection,
2139 index_name.as_str(),
2140 parse_ffi_tokenizer_mode(tokenizer_mode),
2141 )
2142 .map_err(|error| format!("ensure_fts_index failed: {error}"))?;
2143 let effective_mode = TokenizerMode::parse(result.tokenizer_mode.as_str()).unwrap_or_default();
2144 Ok(VldbSqliteEnsureFtsIndexResultPod {
2145 success: if result.success { 1 } else { 0 },
2146 tokenizer_mode: ffi_tokenizer_mode_code(effective_mode),
2147 })
2148 })();
2149
2150 match result {
2151 Ok(result) => {
2152 unsafe {
2153 *out_result = result;
2154 }
2155 VldbSqliteStatusCode::Success as i32
2156 }
2157 Err(error) => {
2158 update_last_error(error);
2159 VldbSqliteStatusCode::Failure as i32
2160 }
2161 }
2162}
2163
2164#[unsafe(no_mangle)]
2167pub extern "C" fn vldb_sqlite_database_rebuild_fts_index(
2168 handle: *mut VldbSqliteDatabaseHandle,
2169 index_name: *const c_char,
2170 tokenizer_mode: VldbSqliteFfiTokenizerMode,
2171 out_result: *mut VldbSqliteRebuildFtsIndexResultPod,
2172) -> i32 {
2173 clear_last_error_inner();
2174 let result = (|| -> Result<VldbSqliteRebuildFtsIndexResultPod, String> {
2175 if out_result.is_null() {
2176 return Err("out_result must not be null / out_result 不能为空".to_string());
2177 }
2178 let index_name = c_json_arg_to_string(index_name)?;
2179 let connection = open_connection_for_handle(handle)?;
2180 let result = rebuild_fts_index(
2181 &connection,
2182 index_name.as_str(),
2183 parse_ffi_tokenizer_mode(tokenizer_mode),
2184 )
2185 .map_err(|error| format!("rebuild_fts_index failed: {error}"))?;
2186 let effective_mode = TokenizerMode::parse(result.tokenizer_mode.as_str()).unwrap_or_default();
2187 Ok(VldbSqliteRebuildFtsIndexResultPod {
2188 success: if result.success { 1 } else { 0 },
2189 tokenizer_mode: ffi_tokenizer_mode_code(effective_mode),
2190 reindexed_rows: result.reindexed_rows,
2191 })
2192 })();
2193
2194 match result {
2195 Ok(result) => {
2196 unsafe {
2197 *out_result = result;
2198 }
2199 VldbSqliteStatusCode::Success as i32
2200 }
2201 Err(error) => {
2202 update_last_error(error);
2203 VldbSqliteStatusCode::Failure as i32
2204 }
2205 }
2206}
2207
2208#[unsafe(no_mangle)]
2211pub extern "C" fn vldb_sqlite_database_upsert_fts_document(
2212 handle: *mut VldbSqliteDatabaseHandle,
2213 index_name: *const c_char,
2214 tokenizer_mode: VldbSqliteFfiTokenizerMode,
2215 id: *const c_char,
2216 file_path: *const c_char,
2217 title: *const c_char,
2218 content: *const c_char,
2219 out_result: *mut VldbSqliteFtsMutationResultPod,
2220) -> i32 {
2221 clear_last_error_inner();
2222 let result = (|| -> Result<VldbSqliteFtsMutationResultPod, String> {
2223 if out_result.is_null() {
2224 return Err("out_result must not be null / out_result 不能为空".to_string());
2225 }
2226 let index_name = c_json_arg_to_string(index_name)?;
2227 let id = c_json_arg_to_string(id)?;
2228 let file_path = c_json_arg_to_string(file_path)?;
2229 let title = optional_title_arg(title)?;
2230 let content = c_json_arg_to_string(content)?;
2231 let connection = open_connection_for_handle(handle)?;
2232 let result = upsert_fts_document(
2233 &connection,
2234 index_name.as_str(),
2235 parse_ffi_tokenizer_mode(tokenizer_mode),
2236 id.as_str(),
2237 file_path.as_str(),
2238 title.as_str(),
2239 content.as_str(),
2240 )
2241 .map_err(|error| format!("upsert_fts_document failed: {error}"))?;
2242 Ok(VldbSqliteFtsMutationResultPod {
2243 success: if result.success { 1 } else { 0 },
2244 affected_rows: result.affected_rows,
2245 })
2246 })();
2247
2248 match result {
2249 Ok(result) => {
2250 unsafe {
2251 *out_result = result;
2252 }
2253 VldbSqliteStatusCode::Success as i32
2254 }
2255 Err(error) => {
2256 update_last_error(error);
2257 VldbSqliteStatusCode::Failure as i32
2258 }
2259 }
2260}
2261
2262#[unsafe(no_mangle)]
2265pub extern "C" fn vldb_sqlite_database_delete_fts_document(
2266 handle: *mut VldbSqliteDatabaseHandle,
2267 index_name: *const c_char,
2268 id: *const c_char,
2269 out_result: *mut VldbSqliteFtsMutationResultPod,
2270) -> i32 {
2271 clear_last_error_inner();
2272 let result = (|| -> Result<VldbSqliteFtsMutationResultPod, String> {
2273 if out_result.is_null() {
2274 return Err("out_result must not be null / out_result 不能为空".to_string());
2275 }
2276 let index_name = c_json_arg_to_string(index_name)?;
2277 let id = c_json_arg_to_string(id)?;
2278 let connection = open_connection_for_handle(handle)?;
2279 let result = delete_fts_document(&connection, index_name.as_str(), id.as_str())
2280 .map_err(|error| format!("delete_fts_document failed: {error}"))?;
2281 Ok(VldbSqliteFtsMutationResultPod {
2282 success: if result.success { 1 } else { 0 },
2283 affected_rows: result.affected_rows,
2284 })
2285 })();
2286
2287 match result {
2288 Ok(result) => {
2289 unsafe {
2290 *out_result = result;
2291 }
2292 VldbSqliteStatusCode::Success as i32
2293 }
2294 Err(error) => {
2295 update_last_error(error);
2296 VldbSqliteStatusCode::Failure as i32
2297 }
2298 }
2299}
2300
2301#[unsafe(no_mangle)]
2304pub extern "C" fn vldb_sqlite_database_search_fts(
2305 handle: *mut VldbSqliteDatabaseHandle,
2306 index_name: *const c_char,
2307 tokenizer_mode: VldbSqliteFfiTokenizerMode,
2308 query: *const c_char,
2309 limit: u32,
2310 offset: u32,
2311) -> *mut VldbSqliteSearchResultHandle {
2312 clear_last_error_inner();
2313 let result = (|| -> Result<*mut VldbSqliteSearchResultHandle, String> {
2314 let index_name = c_json_arg_to_string(index_name)?;
2315 let query = c_json_arg_to_string(query)?;
2316 let connection = open_connection_for_handle(handle)?;
2317 let result = search_fts(
2318 &connection,
2319 index_name.as_str(),
2320 parse_ffi_tokenizer_mode(tokenizer_mode),
2321 query.as_str(),
2322 limit,
2323 offset,
2324 )
2325 .map_err(|error| format!("search_fts failed: {error}"))?;
2326 Ok(Box::into_raw(Box::new(VldbSqliteSearchResultHandle {
2327 inner: result,
2328 })))
2329 })();
2330
2331 match result {
2332 Ok(handle) => handle,
2333 Err(error) => {
2334 update_last_error(error);
2335 std::ptr::null_mut()
2336 }
2337 }
2338}
2339
2340#[unsafe(no_mangle)]
2343pub extern "C" fn vldb_sqlite_search_result_destroy(
2344 handle: *mut VldbSqliteSearchResultHandle,
2345) {
2346 if handle.is_null() {
2347 return;
2348 }
2349
2350 unsafe {
2354 drop(Box::from_raw(handle));
2355 }
2356}
2357
2358#[unsafe(no_mangle)]
2361pub extern "C" fn vldb_sqlite_search_result_total(
2362 handle: *mut VldbSqliteSearchResultHandle,
2363) -> u64 {
2364 clear_last_error_inner();
2365 match search_result_handle_ref(handle) {
2366 Ok(handle) => handle.inner.total,
2367 Err(error) => {
2368 update_last_error(error);
2369 0
2370 }
2371 }
2372}
2373
2374#[unsafe(no_mangle)]
2377pub extern "C" fn vldb_sqlite_search_result_len(
2378 handle: *mut VldbSqliteSearchResultHandle,
2379) -> u64 {
2380 clear_last_error_inner();
2381 match search_result_handle_ref(handle) {
2382 Ok(handle) => handle.inner.hits.len() as u64,
2383 Err(error) => {
2384 update_last_error(error);
2385 0
2386 }
2387 }
2388}
2389
2390#[unsafe(no_mangle)]
2393pub extern "C" fn vldb_sqlite_search_result_source(
2394 handle: *mut VldbSqliteSearchResultHandle,
2395) -> *mut c_char {
2396 clear_last_error_inner();
2397 match search_result_handle_ref(handle) {
2398 Ok(handle) => json_to_c_string(handle.inner.source.clone()),
2399 Err(error) => {
2400 update_last_error(error);
2401 std::ptr::null_mut()
2402 }
2403 }
2404}
2405
2406#[unsafe(no_mangle)]
2409pub extern "C" fn vldb_sqlite_search_result_query_mode(
2410 handle: *mut VldbSqliteSearchResultHandle,
2411) -> *mut c_char {
2412 clear_last_error_inner();
2413 match search_result_handle_ref(handle) {
2414 Ok(handle) => json_to_c_string(handle.inner.query_mode.clone()),
2415 Err(error) => {
2416 update_last_error(error);
2417 std::ptr::null_mut()
2418 }
2419 }
2420}
2421
2422#[unsafe(no_mangle)]
2425pub extern "C" fn vldb_sqlite_search_result_get_id(
2426 handle: *mut VldbSqliteSearchResultHandle,
2427 index: u64,
2428) -> *mut c_char {
2429 clear_last_error_inner();
2430 let result = (|| -> Result<*mut c_char, String> {
2431 let handle = search_result_handle_ref(handle)?;
2432 let hit = handle
2433 .inner
2434 .hits
2435 .get(index as usize)
2436 .ok_or_else(|| format!("search hit index out of range: {index}"))?;
2437 Ok(json_to_c_string(hit.id.clone()))
2438 })();
2439
2440 match result {
2441 Ok(value) => value,
2442 Err(error) => {
2443 update_last_error(error);
2444 std::ptr::null_mut()
2445 }
2446 }
2447}
2448
2449#[unsafe(no_mangle)]
2452pub extern "C" fn vldb_sqlite_search_result_get_file_path(
2453 handle: *mut VldbSqliteSearchResultHandle,
2454 index: u64,
2455) -> *mut c_char {
2456 clear_last_error_inner();
2457 let result = (|| -> Result<*mut c_char, String> {
2458 let handle = search_result_handle_ref(handle)?;
2459 let hit = handle
2460 .inner
2461 .hits
2462 .get(index as usize)
2463 .ok_or_else(|| format!("search hit index out of range: {index}"))?;
2464 Ok(json_to_c_string(hit.file_path.clone()))
2465 })();
2466
2467 match result {
2468 Ok(value) => value,
2469 Err(error) => {
2470 update_last_error(error);
2471 std::ptr::null_mut()
2472 }
2473 }
2474}
2475
2476#[unsafe(no_mangle)]
2479pub extern "C" fn vldb_sqlite_search_result_get_title(
2480 handle: *mut VldbSqliteSearchResultHandle,
2481 index: u64,
2482) -> *mut c_char {
2483 clear_last_error_inner();
2484 let result = (|| -> Result<*mut c_char, String> {
2485 let handle = search_result_handle_ref(handle)?;
2486 let hit = handle
2487 .inner
2488 .hits
2489 .get(index as usize)
2490 .ok_or_else(|| format!("search hit index out of range: {index}"))?;
2491 Ok(json_to_c_string(hit.title.clone()))
2492 })();
2493
2494 match result {
2495 Ok(value) => value,
2496 Err(error) => {
2497 update_last_error(error);
2498 std::ptr::null_mut()
2499 }
2500 }
2501}
2502
2503#[unsafe(no_mangle)]
2506pub extern "C" fn vldb_sqlite_search_result_get_title_highlight(
2507 handle: *mut VldbSqliteSearchResultHandle,
2508 index: u64,
2509) -> *mut c_char {
2510 clear_last_error_inner();
2511 let result = (|| -> Result<*mut c_char, String> {
2512 let handle = search_result_handle_ref(handle)?;
2513 let hit = handle
2514 .inner
2515 .hits
2516 .get(index as usize)
2517 .ok_or_else(|| format!("search hit index out of range: {index}"))?;
2518 Ok(json_to_c_string(hit.title_highlight.clone()))
2519 })();
2520
2521 match result {
2522 Ok(value) => value,
2523 Err(error) => {
2524 update_last_error(error);
2525 std::ptr::null_mut()
2526 }
2527 }
2528}
2529
2530#[unsafe(no_mangle)]
2533pub extern "C" fn vldb_sqlite_search_result_get_content_snippet(
2534 handle: *mut VldbSqliteSearchResultHandle,
2535 index: u64,
2536) -> *mut c_char {
2537 clear_last_error_inner();
2538 let result = (|| -> Result<*mut c_char, String> {
2539 let handle = search_result_handle_ref(handle)?;
2540 let hit = handle
2541 .inner
2542 .hits
2543 .get(index as usize)
2544 .ok_or_else(|| format!("search hit index out of range: {index}"))?;
2545 Ok(json_to_c_string(hit.content_snippet.clone()))
2546 })();
2547
2548 match result {
2549 Ok(value) => value,
2550 Err(error) => {
2551 update_last_error(error);
2552 std::ptr::null_mut()
2553 }
2554 }
2555}
2556
2557#[unsafe(no_mangle)]
2560pub extern "C" fn vldb_sqlite_search_result_get_score(
2561 handle: *mut VldbSqliteSearchResultHandle,
2562 index: u64,
2563) -> f64 {
2564 clear_last_error_inner();
2565 let result = (|| -> Result<f64, String> {
2566 let handle = search_result_handle_ref(handle)?;
2567 let hit = handle
2568 .inner
2569 .hits
2570 .get(index as usize)
2571 .ok_or_else(|| format!("search hit index out of range: {index}"))?;
2572 Ok(hit.score)
2573 })();
2574
2575 match result {
2576 Ok(value) => value,
2577 Err(error) => {
2578 update_last_error(error);
2579 0.0
2580 }
2581 }
2582}
2583
2584#[unsafe(no_mangle)]
2587pub extern "C" fn vldb_sqlite_search_result_get_rank(
2588 handle: *mut VldbSqliteSearchResultHandle,
2589 index: u64,
2590) -> u64 {
2591 clear_last_error_inner();
2592 let result = (|| -> Result<u64, String> {
2593 let handle = search_result_handle_ref(handle)?;
2594 let hit = handle
2595 .inner
2596 .hits
2597 .get(index as usize)
2598 .ok_or_else(|| format!("search hit index out of range: {index}"))?;
2599 Ok(hit.rank)
2600 })();
2601
2602 match result {
2603 Ok(value) => value,
2604 Err(error) => {
2605 update_last_error(error);
2606 0
2607 }
2608 }
2609}
2610
2611#[unsafe(no_mangle)]
2614pub extern "C" fn vldb_sqlite_search_result_get_raw_score(
2615 handle: *mut VldbSqliteSearchResultHandle,
2616 index: u64,
2617) -> f64 {
2618 clear_last_error_inner();
2619 let result = (|| -> Result<f64, String> {
2620 let handle = search_result_handle_ref(handle)?;
2621 let hit = handle
2622 .inner
2623 .hits
2624 .get(index as usize)
2625 .ok_or_else(|| format!("search hit index out of range: {index}"))?;
2626 Ok(hit.raw_score)
2627 })();
2628
2629 match result {
2630 Ok(value) => value,
2631 Err(error) => {
2632 update_last_error(error);
2633 0.0
2634 }
2635 }
2636}
2637
2638#[unsafe(no_mangle)]
2641pub extern "C" fn vldb_sqlite_execute_script_json(request_json: *const c_char) -> *mut c_char {
2642 clear_last_error_inner();
2643 let result = (|| -> Result<String, String> {
2644 let request_json = c_json_arg_to_string(request_json)?;
2645 let request: FfiExecuteScriptJsonRequest = serde_json::from_str(&request_json)
2646 .map_err(|error| format!("failed to parse execute_script request JSON: {error}"))?;
2647 let mut connection = open_connection_for_db_path(request.db_path.as_str())?;
2648 let bound_values = parse_json_compat_params(request.params, request.params_json.as_deref())?;
2649 let response = execute_script_core(&mut connection, &request.sql, &bound_values)
2650 .map_err(sql_exec_error_to_string)?;
2651 serde_json::to_string(&FfiExecuteJsonResponse {
2652 success: response.success,
2653 message: response.message,
2654 rows_changed: response.rows_changed,
2655 last_insert_rowid: response.last_insert_rowid,
2656 })
2657 .map_err(|error| format!("failed to serialize execute_script response: {error}"))
2658 })();
2659
2660 match result {
2661 Ok(json) => json_to_c_string(json),
2662 Err(error) => {
2663 update_last_error(error);
2664 std::ptr::null_mut()
2665 }
2666 }
2667}
2668
2669#[unsafe(no_mangle)]
2672pub extern "C" fn vldb_sqlite_execute_batch_json(request_json: *const c_char) -> *mut c_char {
2673 clear_last_error_inner();
2674 let result = (|| -> Result<String, String> {
2675 let request_json = c_json_arg_to_string(request_json)?;
2676 let request: FfiExecuteBatchJsonRequest = serde_json::from_str(&request_json)
2677 .map_err(|error| format!("failed to parse execute_batch request JSON: {error}"))?;
2678 let mut connection = open_connection_for_db_path(request.db_path.as_str())?;
2679 let batch_params = request
2680 .items
2681 .into_iter()
2682 .map(|row| row.into_iter().map(json_typed_value_to_sqlite_value).collect())
2683 .collect::<Result<Vec<Vec<SqliteValue>>, String>>()?;
2684 let response =
2685 execute_batch_core(&mut connection, &request.sql, &batch_params).map_err(sql_exec_error_to_string)?;
2686 serde_json::to_string(&FfiExecuteBatchJsonResponse {
2687 success: response.success,
2688 message: response.message,
2689 rows_changed: response.rows_changed,
2690 last_insert_rowid: response.last_insert_rowid,
2691 statements_executed: response.statements_executed,
2692 })
2693 .map_err(|error| format!("failed to serialize execute_batch response: {error}"))
2694 })();
2695
2696 match result {
2697 Ok(json) => json_to_c_string(json),
2698 Err(error) => {
2699 update_last_error(error);
2700 std::ptr::null_mut()
2701 }
2702 }
2703}
2704
2705#[unsafe(no_mangle)]
2708pub extern "C" fn vldb_sqlite_query_json_json(request_json: *const c_char) -> *mut c_char {
2709 clear_last_error_inner();
2710 let result = (|| -> Result<String, String> {
2711 let request_json = c_json_arg_to_string(request_json)?;
2712 let request: FfiQueryJsonJsonRequest = serde_json::from_str(&request_json)
2713 .map_err(|error| format!("failed to parse query_json request JSON: {error}"))?;
2714 let mut connection = open_connection_for_db_path(request.db_path.as_str())?;
2715 let bound_values = parse_json_compat_params(request.params, request.params_json.as_deref())?;
2716 let response =
2717 query_json_core(&mut connection, &request.sql, &bound_values).map_err(sql_exec_error_to_string)?;
2718 serde_json::to_string(&response)
2719 .map_err(|error| format!("failed to serialize query_json response: {error}"))
2720 })();
2721
2722 match result {
2723 Ok(json) => json_to_c_string(json),
2724 Err(error) => {
2725 update_last_error(error);
2726 std::ptr::null_mut()
2727 }
2728 }
2729}
2730
2731#[unsafe(no_mangle)]
2734pub extern "C" fn vldb_sqlite_query_stream_json(request_json: *const c_char) -> *mut c_char {
2735 clear_last_error_inner();
2736 let result = (|| -> Result<String, String> {
2737 let request_json = c_json_arg_to_string(request_json)?;
2738 let request: FfiQueryStreamJsonRequest = serde_json::from_str(&request_json)
2739 .map_err(|error| format!("failed to parse query_stream request JSON: {error}"))?;
2740 let mut connection = open_connection_for_db_path(request.db_path.as_str())?;
2741 let bound_values = parse_json_compat_params(request.params, request.params_json.as_deref())?;
2742 let chunk_bytes = request
2743 .chunk_bytes
2744 .map(|value| usize::try_from(value).map_err(|_| "chunk_bytes exceeds usize / chunk_bytes 超过 usize".to_string()))
2745 .transpose()?
2746 .unwrap_or(DEFAULT_IPC_CHUNK_BYTES);
2747 let response = query_stream_core(&mut connection, &request.sql, &bound_values, chunk_bytes)
2748 .map_err(sql_exec_error_to_string)?;
2749 let row_count = response.row_count;
2750 let chunk_count = response.chunk_count;
2751 let total_bytes = response.total_bytes;
2752 let stream_id = register_json_query_stream(response)?;
2753 serde_json::to_string(&FfiQueryStreamJsonResponse {
2754 success: true,
2755 message: format!(
2756 "query_stream executed successfully (chunk_count={} total_bytes={})",
2757 chunk_count, total_bytes
2758 ),
2759 stream_id,
2760 row_count,
2761 chunk_count,
2762 total_bytes,
2763 })
2764 .map_err(|error| format!("failed to serialize query_stream response: {error}"))
2765 })();
2766
2767 match result {
2768 Ok(json) => json_to_c_string(json),
2769 Err(error) => {
2770 update_last_error(error);
2771 std::ptr::null_mut()
2772 }
2773 }
2774}
2775
2776#[unsafe(no_mangle)]
2779pub extern "C" fn vldb_sqlite_query_stream_chunk_json(request_json: *const c_char) -> *mut c_char {
2780 clear_last_error_inner();
2781 let result = (|| -> Result<String, String> {
2782 let request_json = c_json_arg_to_string(request_json)?;
2783 let request: FfiQueryStreamChunkJsonRequest = serde_json::from_str(&request_json)
2784 .map_err(|error| format!("failed to parse query_stream_chunk request JSON: {error}"))?;
2785 let response = with_json_query_stream(request.stream_id, |result| {
2786 let chunk = result
2787 .read_chunk(
2788 usize::try_from(request.index)
2789 .map_err(|_| "chunk index exceeds usize / chunk 下标超过 usize".to_string())?,
2790 )
2791 .map_err(sql_exec_error_to_string)?;
2792 Ok(FfiQueryStreamChunkJsonResponse {
2793 success: true,
2794 message: format!(
2795 "query_stream chunk {} loaded successfully ({} bytes)",
2796 request.index,
2797 chunk.len()
2798 ),
2799 stream_id: request.stream_id,
2800 index: request.index,
2801 byte_count: u64::try_from(chunk.len()).unwrap_or(u64::MAX),
2802 chunk_base64: BASE64_STANDARD.encode(chunk),
2803 })
2804 })?;
2805 serde_json::to_string(&response)
2806 .map_err(|error| format!("failed to serialize query_stream_chunk response: {error}"))
2807 })();
2808
2809 match result {
2810 Ok(json) => json_to_c_string(json),
2811 Err(error) => {
2812 update_last_error(error);
2813 std::ptr::null_mut()
2814 }
2815 }
2816}
2817
2818#[unsafe(no_mangle)]
2821pub extern "C" fn vldb_sqlite_query_stream_close_json(request_json: *const c_char) -> *mut c_char {
2822 clear_last_error_inner();
2823 let result = (|| -> Result<String, String> {
2824 let request_json = c_json_arg_to_string(request_json)?;
2825 let request: FfiQueryStreamCloseJsonRequest = serde_json::from_str(&request_json)
2826 .map_err(|error| format!("failed to parse query_stream_close request JSON: {error}"))?;
2827 let removed = close_json_query_stream(request.stream_id)?;
2828 if !removed {
2829 return Err(format!(
2830 "query stream handle not found: {} / QueryStream 句柄不存在",
2831 request.stream_id
2832 ));
2833 }
2834 serde_json::to_string(&FfiQueryStreamCloseJsonResponse {
2835 success: true,
2836 message: format!("query_stream handle {} closed successfully", request.stream_id),
2837 stream_id: request.stream_id,
2838 })
2839 .map_err(|error| format!("failed to serialize query_stream_close response: {error}"))
2840 })();
2841
2842 match result {
2843 Ok(json) => json_to_c_string(json),
2844 Err(error) => {
2845 update_last_error(error);
2846 std::ptr::null_mut()
2847 }
2848 }
2849}
2850
2851#[unsafe(no_mangle)]
2854pub extern "C" fn vldb_sqlite_tokenize_text_json(request_json: *const c_char) -> *mut c_char {
2855 clear_last_error_inner();
2856 let result = (|| -> Result<String, String> {
2857 let request_json = c_json_arg_to_string(request_json)?;
2858 let request: FfiTokenizeTextRequest =
2859 serde_json::from_str(&request_json).map_err(|error| {
2860 format!("failed to parse tokenize request JSON: {error}")
2861 })?;
2862 let mode = request
2863 .tokenizer_mode
2864 .as_deref()
2865 .and_then(TokenizerMode::parse)
2866 .unwrap_or_default();
2867
2868 let response_json = if let Some(db_path) = request.db_path.as_deref() {
2869 let connection = open_connection_for_db_path(db_path)?;
2870 let response = tokenize_text(
2871 Some(&connection),
2872 mode,
2873 request.text.as_str(),
2874 request.search_mode.unwrap_or(false),
2875 )
2876 .map_err(|error| format!("tokenize_text failed: {error}"))?;
2877 serde_json::to_string(&response)
2878 .map_err(|error| format!("failed to serialize tokenize response: {error}"))?
2879 } else {
2880 let response = tokenize_text(
2881 None,
2882 mode,
2883 request.text.as_str(),
2884 request.search_mode.unwrap_or(false),
2885 )
2886 .map_err(|error| format!("tokenize_text failed: {error}"))?;
2887 serde_json::to_string(&response)
2888 .map_err(|error| format!("failed to serialize tokenize response: {error}"))?
2889 };
2890
2891 Ok(response_json)
2892 })();
2893
2894 match result {
2895 Ok(json) => json_to_c_string(json),
2896 Err(error) => {
2897 update_last_error(error);
2898 std::ptr::null_mut()
2899 }
2900 }
2901}
2902
2903#[unsafe(no_mangle)]
2906pub extern "C" fn vldb_sqlite_upsert_custom_word_json(
2907 request_json: *const c_char,
2908) -> *mut c_char {
2909 clear_last_error_inner();
2910 let result = (|| -> Result<String, String> {
2911 let request_json = c_json_arg_to_string(request_json)?;
2912 let request: FfiUpsertCustomWordRequest =
2913 serde_json::from_str(&request_json).map_err(|error| {
2914 format!("failed to parse upsert request JSON: {error}")
2915 })?;
2916 let connection = open_connection_for_db_path(request.db_path.as_str())?;
2917 let response = upsert_custom_word(
2918 &connection,
2919 request.word.as_str(),
2920 usize::try_from(request.weight.unwrap_or(1).max(1)).unwrap_or(1),
2921 )
2922 .map_err(|error| format!("upsert_custom_word failed: {error}"))?;
2923 serde_json::to_string(&response)
2924 .map_err(|error| format!("failed to serialize upsert response: {error}"))
2925 })();
2926
2927 match result {
2928 Ok(json) => json_to_c_string(json),
2929 Err(error) => {
2930 update_last_error(error);
2931 std::ptr::null_mut()
2932 }
2933 }
2934}
2935
2936#[unsafe(no_mangle)]
2939pub extern "C" fn vldb_sqlite_remove_custom_word_json(
2940 request_json: *const c_char,
2941) -> *mut c_char {
2942 clear_last_error_inner();
2943 let result = (|| -> Result<String, String> {
2944 let request_json = c_json_arg_to_string(request_json)?;
2945 let request: FfiRemoveCustomWordRequest =
2946 serde_json::from_str(&request_json).map_err(|error| {
2947 format!("failed to parse remove request JSON: {error}")
2948 })?;
2949 let connection = open_connection_for_db_path(request.db_path.as_str())?;
2950 let response = remove_custom_word(&connection, request.word.as_str())
2951 .map_err(|error| format!("remove_custom_word failed: {error}"))?;
2952 serde_json::to_string(&response)
2953 .map_err(|error| format!("failed to serialize remove response: {error}"))
2954 })();
2955
2956 match result {
2957 Ok(json) => json_to_c_string(json),
2958 Err(error) => {
2959 update_last_error(error);
2960 std::ptr::null_mut()
2961 }
2962 }
2963}
2964
2965#[unsafe(no_mangle)]
2968pub extern "C" fn vldb_sqlite_list_custom_words_json(
2969 request_json: *const c_char,
2970) -> *mut c_char {
2971 clear_last_error_inner();
2972 let result = (|| -> Result<String, String> {
2973 let request_json = c_json_arg_to_string(request_json)?;
2974 let request: FfiListCustomWordsRequest =
2975 serde_json::from_str(&request_json).map_err(|error| {
2976 format!("failed to parse list_custom_words request JSON: {error}")
2977 })?;
2978 let connection = open_connection_for_db_path(request.db_path.as_str())?;
2979 let response = list_custom_words(&connection)
2980 .map_err(|error| format!("list_custom_words failed: {error}"))?;
2981 serde_json::to_string(&response)
2982 .map_err(|error| format!("failed to serialize list_custom_words response: {error}"))
2983 })();
2984
2985 match result {
2986 Ok(json) => json_to_c_string(json),
2987 Err(error) => {
2988 update_last_error(error);
2989 std::ptr::null_mut()
2990 }
2991 }
2992}
2993
2994#[unsafe(no_mangle)]
2997pub extern "C" fn vldb_sqlite_ensure_fts_index_json(
2998 request_json: *const c_char,
2999) -> *mut c_char {
3000 clear_last_error_inner();
3001 let result = (|| -> Result<String, String> {
3002 let request_json = c_json_arg_to_string(request_json)?;
3003 let request: FfiEnsureFtsIndexRequest = serde_json::from_str(&request_json)
3004 .map_err(|error| format!("failed to parse ensure_fts_index request JSON: {error}"))?;
3005 let connection = open_connection_for_db_path(request.db_path.as_str())?;
3006 let mode = request
3007 .tokenizer_mode
3008 .as_deref()
3009 .and_then(TokenizerMode::parse)
3010 .unwrap_or_default();
3011 let response = ensure_fts_index(&connection, request.index_name.as_str(), mode)
3012 .map_err(|error| format!("ensure_fts_index failed: {error}"))?;
3013 serde_json::to_string(&response)
3014 .map_err(|error| format!("failed to serialize ensure_fts_index response: {error}"))
3015 })();
3016
3017 match result {
3018 Ok(json) => json_to_c_string(json),
3019 Err(error) => {
3020 update_last_error(error);
3021 std::ptr::null_mut()
3022 }
3023 }
3024}
3025
3026#[unsafe(no_mangle)]
3029pub extern "C" fn vldb_sqlite_rebuild_fts_index_json(
3030 request_json: *const c_char,
3031) -> *mut c_char {
3032 clear_last_error_inner();
3033 let result = (|| -> Result<String, String> {
3034 let request_json = c_json_arg_to_string(request_json)?;
3035 let request: FfiRebuildFtsIndexRequest = serde_json::from_str(&request_json)
3036 .map_err(|error| format!("failed to parse rebuild_fts_index request JSON: {error}"))?;
3037 let connection = open_connection_for_db_path(request.db_path.as_str())?;
3038 let mode = request
3039 .tokenizer_mode
3040 .as_deref()
3041 .and_then(TokenizerMode::parse)
3042 .unwrap_or_default();
3043 let response = rebuild_fts_index(&connection, request.index_name.as_str(), mode)
3044 .map_err(|error| format!("rebuild_fts_index failed: {error}"))?;
3045 serde_json::to_string(&response)
3046 .map_err(|error| format!("failed to serialize rebuild_fts_index response: {error}"))
3047 })();
3048
3049 match result {
3050 Ok(json) => json_to_c_string(json),
3051 Err(error) => {
3052 update_last_error(error);
3053 std::ptr::null_mut()
3054 }
3055 }
3056}
3057
3058#[unsafe(no_mangle)]
3061pub extern "C" fn vldb_sqlite_upsert_fts_document_json(
3062 request_json: *const c_char,
3063) -> *mut c_char {
3064 clear_last_error_inner();
3065 let result = (|| -> Result<String, String> {
3066 let request_json = c_json_arg_to_string(request_json)?;
3067 let request: FfiUpsertFtsDocumentRequest = serde_json::from_str(&request_json)
3068 .map_err(|error| format!("failed to parse upsert_fts_document request JSON: {error}"))?;
3069 let connection = open_connection_for_db_path(request.db_path.as_str())?;
3070 let mode = request
3071 .tokenizer_mode
3072 .as_deref()
3073 .and_then(TokenizerMode::parse)
3074 .unwrap_or_default();
3075 let response = upsert_fts_document(
3076 &connection,
3077 request.index_name.as_str(),
3078 mode,
3079 request.id.as_str(),
3080 request.file_path.as_str(),
3081 request.title.as_deref().unwrap_or_default(),
3082 request.content.as_str(),
3083 )
3084 .map_err(|error| format!("upsert_fts_document failed: {error}"))?;
3085 serde_json::to_string(&response)
3086 .map_err(|error| format!("failed to serialize upsert_fts_document response: {error}"))
3087 })();
3088
3089 match result {
3090 Ok(json) => json_to_c_string(json),
3091 Err(error) => {
3092 update_last_error(error);
3093 std::ptr::null_mut()
3094 }
3095 }
3096}
3097
3098#[unsafe(no_mangle)]
3101pub extern "C" fn vldb_sqlite_delete_fts_document_json(
3102 request_json: *const c_char,
3103) -> *mut c_char {
3104 clear_last_error_inner();
3105 let result = (|| -> Result<String, String> {
3106 let request_json = c_json_arg_to_string(request_json)?;
3107 let request: FfiDeleteFtsDocumentRequest = serde_json::from_str(&request_json)
3108 .map_err(|error| format!("failed to parse delete_fts_document request JSON: {error}"))?;
3109 let connection = open_connection_for_db_path(request.db_path.as_str())?;
3110 let response = delete_fts_document(
3111 &connection,
3112 request.index_name.as_str(),
3113 request.id.as_str(),
3114 )
3115 .map_err(|error| format!("delete_fts_document failed: {error}"))?;
3116 serde_json::to_string(&response)
3117 .map_err(|error| format!("failed to serialize delete_fts_document response: {error}"))
3118 })();
3119
3120 match result {
3121 Ok(json) => json_to_c_string(json),
3122 Err(error) => {
3123 update_last_error(error);
3124 std::ptr::null_mut()
3125 }
3126 }
3127}
3128
3129#[unsafe(no_mangle)]
3132pub extern "C" fn vldb_sqlite_search_fts_json(request_json: *const c_char) -> *mut c_char {
3133 clear_last_error_inner();
3134 let result = (|| -> Result<String, String> {
3135 let request_json = c_json_arg_to_string(request_json)?;
3136 let request: FfiSearchFtsRequest = serde_json::from_str(&request_json)
3137 .map_err(|error| format!("failed to parse search_fts request JSON: {error}"))?;
3138 let connection = open_connection_for_db_path(request.db_path.as_str())?;
3139 let mode = request
3140 .tokenizer_mode
3141 .as_deref()
3142 .and_then(TokenizerMode::parse)
3143 .unwrap_or_default();
3144 let response = search_fts(
3145 &connection,
3146 request.index_name.as_str(),
3147 mode,
3148 request.query.as_str(),
3149 request.limit.unwrap_or(20),
3150 request.offset.unwrap_or(0),
3151 )
3152 .map_err(|error| format!("search_fts failed: {error}"))?;
3153 serde_json::to_string(&response)
3154 .map_err(|error| format!("failed to serialize search_fts response: {error}"))
3155 })();
3156
3157 match result {
3158 Ok(json) => json_to_c_string(json),
3159 Err(error) => {
3160 update_last_error(error);
3161 std::ptr::null_mut()
3162 }
3163 }
3164}
3165
3166#[cfg(test)]
3167mod tests {
3168 use super::{
3169 VldbSqliteFfiValue, VldbSqliteFfiValueKind, VldbSqliteFfiValueSlice,
3170 vldb_sqlite_clear_last_error, vldb_sqlite_database_execute_batch,
3171 vldb_sqlite_database_execute_script, vldb_sqlite_database_query_json,
3172 vldb_sqlite_database_query_stream, vldb_sqlite_execute_result_destroy,
3173 vldb_sqlite_execute_result_rows_changed, vldb_sqlite_execute_result_statements_executed,
3174 vldb_sqlite_json_is_null, vldb_sqlite_last_error_message, vldb_sqlite_library_info_json,
3175 vldb_sqlite_query_json_json, vldb_sqlite_query_json_result_destroy,
3176 vldb_sqlite_query_json_result_json_data, vldb_sqlite_query_stream_chunk_count,
3177 vldb_sqlite_query_stream_close_json, vldb_sqlite_query_stream_destroy,
3178 vldb_sqlite_query_stream_get_chunk, vldb_sqlite_query_stream_json,
3179 vldb_sqlite_query_stream_chunk_json, vldb_sqlite_runtime_create_default,
3180 vldb_sqlite_runtime_destroy, vldb_sqlite_runtime_open_database, vldb_sqlite_string_free,
3181 vldb_sqlite_bytes_free,
3182 };
3183 use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
3184 use base64::Engine as _;
3185 use serde_json::Value as JsonValue;
3186 use std::ffi::{CStr, CString, c_char};
3187 use std::time::{SystemTime, UNIX_EPOCH};
3188
3189 fn c_string_to_string(value: *const c_char) -> Option<String> {
3192 if value.is_null() {
3193 return None;
3194 }
3195
3196 let raw = unsafe { CStr::from_ptr(value) };
3200 raw.to_str().ok().map(ToOwned::to_owned)
3201 }
3202
3203 fn make_c_string(value: &str) -> CString {
3204 CString::new(value).expect("test CString should stay valid")
3205 }
3206
3207 fn temp_db_path(label: &str) -> String {
3208 let nanos = SystemTime::now()
3209 .duration_since(UNIX_EPOCH)
3210 .expect("clock should move forward")
3211 .as_nanos();
3212 std::env::temp_dir()
3213 .join(format!("vldb-sqlite-ffi-{label}-{nanos}.sqlite3"))
3214 .to_string_lossy()
3215 .to_string()
3216 }
3217
3218 #[test]
3219 fn library_info_json_contains_expected_fields() {
3220 let raw = vldb_sqlite_library_info_json();
3221 assert_eq!(vldb_sqlite_json_is_null(raw), 0);
3222
3223 let json = c_string_to_string(raw).expect("library info JSON should be readable");
3224 let value: JsonValue =
3225 serde_json::from_str(&json).expect("library info JSON should stay valid");
3226
3227 assert_eq!(value["name"], "vldb-sqlite");
3228 assert_eq!(value["version"], env!("CARGO_PKG_VERSION"));
3229 assert_eq!(value["ffi_stage"], "sqlite-runtime-go-ffi");
3230 assert!(
3231 value["capabilities"]
3232 .as_array()
3233 .expect("capabilities should stay an array")
3234 .iter()
3235 .any(|entry| entry == "runtime_create_default")
3236 );
3237 assert!(
3238 value["capabilities"]
3239 .as_array()
3240 .expect("capabilities should stay an array")
3241 .iter()
3242 .any(|entry| entry == "database_search_fts")
3243 );
3244 assert!(
3245 value["capabilities"]
3246 .as_array()
3247 .expect("capabilities should stay an array")
3248 .iter()
3249 .any(|entry| entry == "database_execute_script")
3250 );
3251 assert!(
3252 value["capabilities"]
3253 .as_array()
3254 .expect("capabilities should stay an array")
3255 .iter()
3256 .any(|entry| entry == "query_stream_json")
3257 );
3258 assert!(
3259 value["capabilities"]
3260 .as_array()
3261 .expect("capabilities should stay an array")
3262 .iter()
3263 .any(|entry| entry == "query_stream_chunk_json")
3264 );
3265
3266 vldb_sqlite_string_free(raw);
3267 }
3268
3269 #[test]
3270 fn last_error_is_empty_after_success() {
3271 vldb_sqlite_clear_last_error();
3272 let raw = vldb_sqlite_last_error_message();
3273 assert!(raw.is_null());
3274 }
3275
3276 #[test]
3277 fn ffi_execute_script_and_query_json_round_trip() {
3278 let runtime = vldb_sqlite_runtime_create_default();
3279 assert!(!runtime.is_null());
3280 let db_path = temp_db_path("execute-query");
3281 let db_path_c = make_c_string(&db_path);
3282 let db = vldb_sqlite_runtime_open_database(runtime, db_path_c.as_ptr());
3283 assert!(!db.is_null());
3284
3285 let create_sql = make_c_string(
3286 "CREATE TABLE demo(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, score REAL);",
3287 );
3288 let create_result =
3289 vldb_sqlite_database_execute_script(db, create_sql.as_ptr(), std::ptr::null(), 0, std::ptr::null());
3290 assert!(!create_result.is_null());
3291 assert_eq!(vldb_sqlite_execute_result_statements_executed(create_result), 1);
3292 vldb_sqlite_execute_result_destroy(create_result);
3293
3294 let insert_sql = make_c_string("INSERT INTO demo(name, score) VALUES (?1, ?2)");
3295 let name = make_c_string("alpha");
3296 let params = [
3297 VldbSqliteFfiValue {
3298 kind: VldbSqliteFfiValueKind::String,
3299 int64_value: 0,
3300 float64_value: 0.0,
3301 string_value: name.as_ptr(),
3302 bytes_value: Default::default(),
3303 bool_value: 0,
3304 },
3305 VldbSqliteFfiValue {
3306 kind: VldbSqliteFfiValueKind::Float64,
3307 int64_value: 0,
3308 float64_value: 7.5,
3309 string_value: std::ptr::null(),
3310 bytes_value: Default::default(),
3311 bool_value: 0,
3312 },
3313 ];
3314 let insert_result = vldb_sqlite_database_execute_script(
3315 db,
3316 insert_sql.as_ptr(),
3317 params.as_ptr(),
3318 params.len() as u64,
3319 std::ptr::null(),
3320 );
3321 assert!(!insert_result.is_null());
3322 assert_eq!(vldb_sqlite_execute_result_rows_changed(insert_result), 1);
3323 assert_eq!(vldb_sqlite_execute_result_statements_executed(insert_result), 1);
3324 vldb_sqlite_execute_result_destroy(insert_result);
3325
3326 let query_sql = make_c_string("SELECT id, name, score FROM demo ORDER BY id");
3327 let query_result =
3328 vldb_sqlite_database_query_json(db, query_sql.as_ptr(), std::ptr::null(), 0, std::ptr::null());
3329 assert!(!query_result.is_null());
3330 let json_ptr = vldb_sqlite_query_json_result_json_data(query_result);
3331 let json = c_string_to_string(json_ptr).expect("query_json result should be readable");
3332 assert!(json.contains("\"alpha\""));
3333 vldb_sqlite_string_free(json_ptr);
3334 vldb_sqlite_query_json_result_destroy(query_result);
3335
3336 vldb_sqlite_runtime_destroy(runtime);
3337 }
3338
3339 #[test]
3340 fn ffi_execute_batch_and_query_stream_work() {
3341 let runtime = vldb_sqlite_runtime_create_default();
3342 assert!(!runtime.is_null());
3343 let db_path = temp_db_path("batch-stream");
3344 let db_path_c = make_c_string(&db_path);
3345 let db = vldb_sqlite_runtime_open_database(runtime, db_path_c.as_ptr());
3346 assert!(!db.is_null());
3347
3348 let create_sql = make_c_string(
3349 "CREATE TABLE demo(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, score REAL);",
3350 );
3351 let create_result =
3352 vldb_sqlite_database_execute_script(db, create_sql.as_ptr(), std::ptr::null(), 0, std::ptr::null());
3353 assert!(!create_result.is_null());
3354 vldb_sqlite_execute_result_destroy(create_result);
3355
3356 let sql = make_c_string("INSERT INTO demo(name, score) VALUES (?1, ?2)");
3357 let alpha = make_c_string("alpha");
3358 let beta = make_c_string("beta");
3359 let row1 = [
3360 VldbSqliteFfiValue {
3361 kind: VldbSqliteFfiValueKind::String,
3362 int64_value: 0,
3363 float64_value: 0.0,
3364 string_value: alpha.as_ptr(),
3365 bytes_value: Default::default(),
3366 bool_value: 0,
3367 },
3368 VldbSqliteFfiValue {
3369 kind: VldbSqliteFfiValueKind::Float64,
3370 int64_value: 0,
3371 float64_value: 1.5,
3372 string_value: std::ptr::null(),
3373 bytes_value: Default::default(),
3374 bool_value: 0,
3375 },
3376 ];
3377 let row2 = [
3378 VldbSqliteFfiValue {
3379 kind: VldbSqliteFfiValueKind::String,
3380 int64_value: 0,
3381 float64_value: 0.0,
3382 string_value: beta.as_ptr(),
3383 bytes_value: Default::default(),
3384 bool_value: 0,
3385 },
3386 VldbSqliteFfiValue {
3387 kind: VldbSqliteFfiValueKind::Float64,
3388 int64_value: 0,
3389 float64_value: 2.5,
3390 string_value: std::ptr::null(),
3391 bytes_value: Default::default(),
3392 bool_value: 0,
3393 },
3394 ];
3395 let items = [
3396 VldbSqliteFfiValueSlice {
3397 values: row1.as_ptr(),
3398 len: row1.len() as u64,
3399 },
3400 VldbSqliteFfiValueSlice {
3401 values: row2.as_ptr(),
3402 len: row2.len() as u64,
3403 },
3404 ];
3405 let batch_result =
3406 vldb_sqlite_database_execute_batch(db, sql.as_ptr(), items.as_ptr(), items.len() as u64);
3407 assert!(!batch_result.is_null());
3408 assert_eq!(vldb_sqlite_execute_result_statements_executed(batch_result), 2);
3409 vldb_sqlite_execute_result_destroy(batch_result);
3410
3411 let query_sql = make_c_string("SELECT id, name, score FROM demo ORDER BY id");
3412 let stream =
3413 vldb_sqlite_database_query_stream(db, query_sql.as_ptr(), std::ptr::null(), 0, std::ptr::null(), 0);
3414 assert!(!stream.is_null());
3415 assert!(vldb_sqlite_query_stream_chunk_count(stream) >= 1);
3416 let chunk = vldb_sqlite_query_stream_get_chunk(stream, 0);
3417 assert!(chunk.len > 0);
3418 vldb_sqlite_bytes_free(chunk);
3419 vldb_sqlite_query_stream_destroy(stream);
3420
3421 vldb_sqlite_runtime_destroy(runtime);
3422 }
3423
3424 #[test]
3425 fn json_and_typed_params_produce_equivalent_query_results() {
3426 let runtime = vldb_sqlite_runtime_create_default();
3427 let db_path = temp_db_path("typed-json-equivalent");
3428 let db_path_c = make_c_string(&db_path);
3429 let db = vldb_sqlite_runtime_open_database(runtime, db_path_c.as_ptr());
3430
3431 let create_sql = make_c_string(
3432 "CREATE TABLE demo(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, score REAL);",
3433 );
3434 let create_result =
3435 vldb_sqlite_database_execute_script(db, create_sql.as_ptr(), std::ptr::null(), 0, std::ptr::null());
3436 vldb_sqlite_execute_result_destroy(create_result);
3437
3438 let insert_json = serde_json::json!({
3439 "db_path": db_path,
3440 "sql": "INSERT INTO demo(name, score) VALUES (?1, ?2)",
3441 "params": [
3442 {"kind":"string", "value":"alpha"},
3443 {"kind":"float64", "value":7.5}
3444 ]
3445 })
3446 .to_string();
3447 let insert_json_c = make_c_string(&insert_json);
3448 let insert_json_result = super::vldb_sqlite_execute_script_json(insert_json_c.as_ptr());
3449 assert!(!insert_json_result.is_null());
3450 vldb_sqlite_string_free(insert_json_result);
3451
3452 let query_sql = make_c_string("SELECT id, name, score FROM demo WHERE name = ?1");
3453 let name = make_c_string("alpha");
3454 let typed_params = [VldbSqliteFfiValue {
3455 kind: VldbSqliteFfiValueKind::String,
3456 int64_value: 0,
3457 float64_value: 0.0,
3458 string_value: name.as_ptr(),
3459 bytes_value: Default::default(),
3460 bool_value: 0,
3461 }];
3462 let typed_result = vldb_sqlite_database_query_json(
3463 db,
3464 query_sql.as_ptr(),
3465 typed_params.as_ptr(),
3466 typed_params.len() as u64,
3467 std::ptr::null(),
3468 );
3469 let typed_json_ptr = vldb_sqlite_query_json_result_json_data(typed_result);
3470 let typed_json = c_string_to_string(typed_json_ptr).expect("typed query json should exist");
3471 vldb_sqlite_string_free(typed_json_ptr);
3472 vldb_sqlite_query_json_result_destroy(typed_result);
3473
3474 let query_json_request = serde_json::json!({
3475 "db_path": db_path,
3476 "sql": "SELECT id, name, score FROM demo WHERE name = ?1",
3477 "params_json": "[\"alpha\"]"
3478 })
3479 .to_string();
3480 let query_json_request_c = make_c_string(&query_json_request);
3481 let query_json_ptr = vldb_sqlite_query_json_json(query_json_request_c.as_ptr());
3482 let query_json = c_string_to_string(query_json_ptr).expect("json query should exist");
3483 vldb_sqlite_string_free(query_json_ptr);
3484
3485 assert_eq!(typed_json, serde_json::from_str::<JsonValue>(&query_json).expect("query_json_json should return serializable response")["json_data"]);
3486
3487 let stream_json_request = serde_json::json!({
3488 "db_path": db_path,
3489 "sql": "SELECT id, name, score FROM demo ORDER BY id",
3490 "params_json": "[]"
3491 })
3492 .to_string();
3493 let stream_json_request_c = make_c_string(&stream_json_request);
3494 let stream_json_ptr = vldb_sqlite_query_stream_json(stream_json_request_c.as_ptr());
3495 let stream_json = c_string_to_string(stream_json_ptr).expect("query_stream_json should exist");
3496 let stream_payload: JsonValue = serde_json::from_str(&stream_json).expect("stream JSON should parse");
3497 let stream_id = stream_payload["stream_id"].as_u64().expect("stream_id should exist");
3498 assert!(stream_payload["chunk_count"].as_u64().unwrap_or(0) >= 1);
3499 assert!(stream_payload.get("chunks").is_none());
3500 vldb_sqlite_string_free(stream_json_ptr);
3501
3502 let chunk_request = serde_json::json!({
3503 "stream_id": stream_id,
3504 "index": 0
3505 })
3506 .to_string();
3507 let chunk_request_c = make_c_string(&chunk_request);
3508 let chunk_json_ptr = vldb_sqlite_query_stream_chunk_json(chunk_request_c.as_ptr());
3509 let chunk_json = c_string_to_string(chunk_json_ptr).expect("query_stream_chunk_json should exist");
3510 let chunk_payload: JsonValue = serde_json::from_str(&chunk_json).expect("chunk JSON should parse");
3511 assert!(chunk_payload["byte_count"].as_u64().unwrap_or(0) > 0);
3512 let chunk_base64 = chunk_payload["chunk_base64"]
3513 .as_str()
3514 .expect("chunk_base64 should exist");
3515 let decoded_chunk = BASE64_STANDARD
3516 .decode(chunk_base64)
3517 .expect("chunk_base64 should decode");
3518 assert!(!decoded_chunk.is_empty());
3519 vldb_sqlite_string_free(chunk_json_ptr);
3520
3521 let close_request = serde_json::json!({
3522 "stream_id": stream_id
3523 })
3524 .to_string();
3525 let close_request_c = make_c_string(&close_request);
3526 let close_ptr = vldb_sqlite_query_stream_close_json(close_request_c.as_ptr());
3527 assert!(!close_ptr.is_null());
3528 vldb_sqlite_string_free(close_ptr);
3529
3530 vldb_sqlite_runtime_destroy(runtime);
3531 }
3532
3533 #[test]
3534 fn json_execute_script_reuses_runtime_initialization_for_jieba_fts() {
3535 let db_path = temp_db_path("json-jieba-init");
3536 let create_json = serde_json::json!({
3537 "db_path": db_path,
3538 "sql": "CREATE VIRTUAL TABLE demo_fts USING fts5(content, tokenize='jieba')"
3539 })
3540 .to_string();
3541 let create_json_c = make_c_string(&create_json);
3542 let create_ptr = super::vldb_sqlite_execute_script_json(create_json_c.as_ptr());
3543 let create_payload = c_string_to_string(create_ptr).expect("execute_script_json should succeed");
3544 let create_value: JsonValue = serde_json::from_str(&create_payload).expect("create response should parse");
3545 assert_eq!(create_value["success"], true);
3546 vldb_sqlite_string_free(create_ptr);
3547 }
3548}