Skip to main content

vldb_sqlite/
ffi.rs

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/// FFI 状态码,供 Go / C 调用方判断调用是否成功。
34/// FFI status code used by Go / C callers to determine whether an invocation succeeded.
35#[repr(C)]
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum VldbSqliteStatusCode {
38    /// 调用成功。
39    /// The invocation completed successfully.
40    Success = 0,
41    /// 调用失败,可通过最近一次错误消息读取原因。
42    /// The invocation failed; the reason can be read from the latest error message.
43    Failure = 1,
44}
45
46/// FFI 分词模式枚举,作为非 JSON 主接口的稳定入参。
47/// FFI tokenizer-mode enum used as a stable input for the non-JSON main interface.
48#[repr(C)]
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum VldbSqliteFfiTokenizerMode {
51    /// 不启用 Jieba,仅做轻量规范化。
52    /// Do not enable Jieba; only perform light normalization.
53    None = 0,
54    /// 启用 Jieba 与内建词典能力。
55    /// Enable Jieba and the built-in dictionary capabilities.
56    Jieba = 1,
57}
58
59/// FFI Runtime 句柄,负责纯库多库管理。
60/// FFI runtime handle responsible for pure-library multi-database management.
61pub struct VldbSqliteRuntimeHandle {
62    /// 内部多库运行时实例。
63    /// Internal multi-database runtime instance.
64    inner: SqliteRuntime,
65}
66
67/// FFI 数据库句柄,负责复用指定数据库的打开选项与连接策略。
68/// FFI database handle used to reuse open options and connection rules for a specific database.
69pub struct VldbSqliteDatabaseHandle {
70    /// 内部数据库句柄引用。
71    /// Internal database-handle reference.
72    inner: Arc<SqliteDatabaseHandle>,
73}
74
75/// FFI 分词结果句柄,供调用方通过 getter 读取。
76/// FFI tokenize-result handle read by callers through getter functions.
77pub struct VldbSqliteTokenizeResultHandle {
78    /// 内部分词结果。
79    /// Internal tokenize result.
80    inner: TokenizeOutput,
81}
82
83/// FFI 自定义词列表句柄,供调用方通过 getter 读取。
84/// FFI custom-word list handle read by callers through getter functions.
85pub struct VldbSqliteCustomWordListHandle {
86    /// 内部自定义词列表结果。
87    /// Internal custom-word list result.
88    inner: ListCustomWordsResult,
89}
90
91/// FFI FTS 检索结果句柄,供调用方通过 getter 读取。
92/// FFI FTS search-result handle read by callers through getter functions.
93pub struct VldbSqliteSearchResultHandle {
94    /// 内部 FTS 检索结果。
95    /// Internal FTS search result.
96    inner: crate::fts::SearchFtsResult,
97}
98
99/// FFI 通用 SQL 执行结果句柄。
100/// FFI shared SQL execution-result handle.
101pub struct VldbSqliteExecuteResultHandle {
102    /// 内部执行结果。
103    /// Internal execution result.
104    inner: VldbSqliteExecuteResult,
105}
106
107/// FFI JSON 查询结果句柄。
108/// FFI JSON-query result handle.
109pub struct VldbSqliteQueryJsonResultHandle {
110    /// 内部 JSON 查询结果。
111    /// Internal JSON query result.
112    inner: QueryJsonResult,
113}
114
115/// FFI Arrow IPC chunk 查询结果句柄。
116/// FFI Arrow IPC chunk query-result handle.
117pub struct VldbSqliteQueryStreamHandle {
118    /// 内部 Arrow IPC chunk 查询结果句柄。
119    /// Internal Arrow IPC chunk query-result handle.
120    inner: Arc<FfiQueryStreamSharedState>,
121}
122
123/// 主 FFI QueryStream 单个 chunk 的文件偏移描述。
124/// File-offset descriptor for a single main-FFI QueryStream chunk.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126struct FfiQueryStreamChunkDescriptor {
127    /// chunk 在暂存文件中的起始偏移。
128    /// Starting offset of the chunk in the spool file.
129    offset: u64,
130    /// chunk 的字节长度。
131    /// Byte length of the chunk.
132    len: u64,
133}
134
135/// 主 FFI QueryStream 的共享内部状态。
136/// Shared internal state for the main-FFI QueryStream.
137#[derive(Debug)]
138struct FfiQueryStreamSharedInner {
139    /// 暂存文件路径。
140    /// Spool file path.
141    file_path: PathBuf,
142    /// 已生成的 chunk 索引。
143    /// Descriptors for chunks already produced.
144    chunk_descriptors: Vec<FfiQueryStreamChunkDescriptor>,
145    /// 最终返回行数。
146    /// Final row count.
147    row_count: u64,
148    /// 最终 chunk 数量。
149    /// Final chunk count.
150    chunk_count: u64,
151    /// 最终总字节数。
152    /// Final total byte size.
153    total_bytes: u64,
154    /// 是否已完成生成。
155    /// Whether generation has completed.
156    complete: bool,
157    /// 异常信息(如果有)。
158    /// Failure message, if generation failed.
159    error: Option<String>,
160}
161
162/// 主 FFI QueryStream 的共享状态包装。
163/// Shared state wrapper for the main-FFI QueryStream.
164#[derive(Debug)]
165struct FfiQueryStreamSharedState {
166    /// 共享可变状态。
167    /// Shared mutable state.
168    inner: Mutex<FfiQueryStreamSharedInner>,
169    /// 用于等待 chunk 或完成信号。
170    /// Condvar used to wait for chunks or completion.
171    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
287/// 主 FFI QueryStream 的后台文件写入器。
288/// Background file writer used by the main-FFI QueryStream.
289struct 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
376/// JSON QueryStream 注册表条目。
377/// JSON QueryStream registry entry.
378struct JsonQueryStreamEntry {
379    /// 暂存后的流结果。
380    /// Spool-backed stream result.
381    result: QueryStreamResult,
382    /// 最近访问时间。
383    /// Last access time.
384    last_accessed_at: Instant,
385}
386
387/// 非 JSON FFI 的 SQL 执行结果。
388/// SQL execution result used by the non-JSON FFI.
389#[derive(Debug, Clone)]
390struct VldbSqliteExecuteResult {
391    /// 是否执行成功。
392    /// Whether the execution succeeded.
393    success: bool,
394    /// 结果消息。
395    /// Result message.
396    message: String,
397    /// 受影响行数。
398    /// Number of affected rows.
399    rows_changed: i64,
400    /// 最近一次插入行 ID。
401    /// Last inserted row id.
402    last_insert_rowid: i64,
403    /// 已执行语句次数,脚本执行为 1 或 0。
404    /// Number of executed statements; script execution uses 1 or 0.
405    statements_executed: i64,
406}
407
408/// 自定义词修改结果结构。
409/// Custom-word mutation result structure.
410#[repr(C)]
411#[derive(Clone, Copy, Debug, Default)]
412pub struct VldbSqliteDictionaryMutationResultPod {
413    /// 操作是否成功。
414    /// Whether the operation succeeded.
415    pub success: u8,
416    /// 受影响行数。
417    /// Number of affected rows.
418    pub affected_rows: u64,
419}
420
421/// FTS 索引确保结果结构。
422/// FTS ensure-index result structure.
423#[repr(C)]
424#[derive(Clone, Copy, Debug, Default)]
425pub struct VldbSqliteEnsureFtsIndexResultPod {
426    /// 操作是否成功。
427    /// Whether the operation succeeded.
428    pub success: u8,
429    /// 最终分词模式。
430    /// Effective tokenizer mode.
431    pub tokenizer_mode: u32,
432}
433
434/// FTS 索引重建结果结构。
435/// FTS rebuild-index result structure.
436#[repr(C)]
437#[derive(Clone, Copy, Debug, Default)]
438pub struct VldbSqliteRebuildFtsIndexResultPod {
439    /// 操作是否成功。
440    /// Whether the operation succeeded.
441    pub success: u8,
442    /// 最终分词模式。
443    /// Effective tokenizer mode.
444    pub tokenizer_mode: u32,
445    /// 重建回写的文档数量。
446    /// Number of documents reindexed during rebuild.
447    pub reindexed_rows: u64,
448}
449
450/// FTS 文档写入/删除结果结构。
451/// FTS document mutation result structure.
452#[repr(C)]
453#[derive(Clone, Copy, Debug, Default)]
454pub struct VldbSqliteFtsMutationResultPod {
455    /// 操作是否成功。
456    /// Whether the operation succeeded.
457    pub success: u8,
458    /// 受影响行数。
459    /// Number of affected rows.
460    pub affected_rows: u64,
461}
462
463/// 字节视图结构,供 FFI 输入 bytes 参数使用。
464/// Byte-view structure used by the FFI for input bytes parameters.
465#[repr(C)]
466#[derive(Clone, Copy, Debug, Default)]
467pub struct VldbSqliteByteView {
468    /// 字节数据指针。
469    /// Pointer to the byte data.
470    pub data: *const u8,
471    /// 字节长度。
472    /// Length of the byte data.
473    pub len: u64,
474}
475
476/// 可释放字节缓冲区结构,供 QueryStream chunk getter 返回。
477/// Releasable byte-buffer structure returned by QueryStream chunk getters.
478#[repr(C)]
479#[derive(Clone, Copy, Debug, Default)]
480pub struct VldbSqliteByteBuffer {
481    /// 字节数据指针。
482    /// Pointer to the byte data.
483    pub data: *mut u8,
484    /// 字节长度。
485    /// Length of the byte data.
486    pub len: u64,
487    /// 原始容量,用于恢复 `Vec<u8>`。
488    /// Original capacity used to rebuild the source `Vec<u8>`.
489    pub cap: u64,
490}
491
492/// FFI SQL 值类型枚举。
493/// FFI SQL value-kind enum.
494#[repr(C)]
495#[derive(Clone, Copy, Debug, PartialEq, Eq)]
496pub enum VldbSqliteFfiValueKind {
497    /// Null 值。
498    /// Null value.
499    Null = 0,
500    /// 64 位有符号整数。
501    /// Signed 64-bit integer.
502    Int64 = 1,
503    /// 64 位浮点数。
504    /// 64-bit floating point number.
505    Float64 = 2,
506    /// UTF-8 字符串。
507    /// UTF-8 string.
508    String = 3,
509    /// 原始字节数组。
510    /// Raw byte array.
511    Bytes = 4,
512    /// 布尔值。
513    /// Boolean value.
514    Bool = 5,
515}
516
517/// FFI SQL 参数值结构。
518/// FFI SQL parameter value structure.
519#[repr(C)]
520#[derive(Clone, Copy, Debug)]
521pub struct VldbSqliteFfiValue {
522    /// 当前值类型。
523    /// Current value kind.
524    pub kind: VldbSqliteFfiValueKind,
525    /// int64 值。
526    /// int64 value.
527    pub int64_value: i64,
528    /// float64 值。
529    /// float64 value.
530    pub float64_value: f64,
531    /// string 值,要求为 NUL 结尾 UTF-8 字符串。
532    /// string value, expected to be a NUL-terminated UTF-8 string.
533    pub string_value: *const c_char,
534    /// bytes 值。
535    /// bytes value.
536    pub bytes_value: VldbSqliteByteView,
537    /// bool 值,0 为 false,非 0 为 true。
538    /// bool value, 0 for false and non-zero for true.
539    pub bool_value: u8,
540}
541
542/// FFI SQL 参数切片结构。
543/// FFI SQL parameter-slice structure.
544#[repr(C)]
545#[derive(Clone, Copy, Debug, Default)]
546pub struct VldbSqliteFfiValueSlice {
547    /// 参数数组指针。
548    /// Pointer to the parameter array.
549    pub values: *const VldbSqliteFfiValue,
550    /// 参数数量。
551    /// Number of parameters.
552    pub len: u64,
553}
554
555/// 最近一次 FFI 错误消息缓存。
556/// Cache for the latest FFI error message.
557static 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    // SAFETY:
618    // 调用方保证传入指向一个以 NUL 结尾的只读字符串。
619    // The caller guarantees the pointer refers to a NUL-terminated read-only string.
620    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
643/// 基于已打开的数据库句柄创建符合运行时规则的连接。
644/// Create a connection that follows runtime rules from an opened database handle.
645fn 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
653/// 读取 runtime 句柄引用。
654/// Read a runtime-handle reference.
655fn 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    // SAFETY:
663    // 指针由 `Box::into_raw` 创建,且调用期间保持有效。
664    // The pointer is created by `Box::into_raw` and remains valid during the call.
665    Ok(unsafe { &*handle })
666}
667
668/// 读取数据库句柄引用。
669/// Read a database-handle reference.
670fn 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    // SAFETY:
678    // 指针由 `Box::into_raw` 创建,且调用期间保持有效。
679    // The pointer is created by `Box::into_raw` and remains valid during the call.
680    Ok(unsafe { &*handle })
681}
682
683/// 读取分词结果句柄引用。
684/// Read a tokenize-result handle reference.
685fn 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    // SAFETY:
693    // 指针由 `Box::into_raw` 创建,且调用期间保持有效。
694    // The pointer is created by `Box::into_raw` and remains valid during the call.
695    Ok(unsafe { &*handle })
696}
697
698/// 读取自定义词列表句柄引用。
699/// Read a custom-word list handle reference.
700fn 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    // SAFETY:
708    // 指针由 `Box::into_raw` 创建,且调用期间保持有效。
709    // The pointer is created by `Box::into_raw` and remains valid during the call.
710    Ok(unsafe { &*handle })
711}
712
713/// 读取 FTS 检索结果句柄引用。
714/// Read an FTS search-result handle reference.
715fn 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    // SAFETY:
723    // 指针由 `Box::into_raw` 创建,且调用期间保持有效。
724    // The pointer is created by `Box::into_raw` and remains valid during the call.
725    Ok(unsafe { &*handle })
726}
727
728/// 读取通用 SQL 执行结果句柄引用。
729/// Read a shared SQL execution-result handle reference.
730fn 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    // SAFETY:
738    // 指针由 `Box::into_raw` 创建,且调用期间保持有效。
739    // The pointer is created by `Box::into_raw` and remains valid during the call.
740    Ok(unsafe { &*handle })
741}
742
743/// 读取 JSON 查询结果句柄引用。
744/// Read a JSON-query result handle reference.
745fn 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    // SAFETY:
753    // 指针由 `Box::into_raw` 创建,且调用期间保持有效。
754    // The pointer is created by `Box::into_raw` and remains valid during the call.
755    Ok(unsafe { &*handle })
756}
757
758/// 读取 Arrow IPC chunk 查询结果句柄引用。
759/// Read an Arrow IPC chunk query-result handle reference.
760fn 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    // SAFETY:
768    // 指针由 `Box::into_raw` 创建,且调用期间保持有效。
769    // The pointer is created by `Box::into_raw` and remains valid during the call.
770    Ok(unsafe { &*handle })
771}
772
773/// 将 FFI 分词模式映射到内部模式。
774/// Map the FFI tokenizer mode into the internal tokenizer mode.
775fn parse_ffi_tokenizer_mode(mode: VldbSqliteFfiTokenizerMode) -> TokenizerMode {
776    match mode {
777        VldbSqliteFfiTokenizerMode::None => TokenizerMode::None,
778        VldbSqliteFfiTokenizerMode::Jieba => TokenizerMode::Jieba,
779    }
780}
781
782/// 将内部模式映射回 FFI 枚举值。
783/// Map the internal tokenizer mode back to the FFI enum value.
784fn 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
791/// 读取索引参数中的有效标题字段。
792/// Read the effective title field from input arguments.
793fn 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
801/// 把 JSON typed value 转成 SQLite 值。
802/// Convert a JSON typed value into a SQLite value.
803fn 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
814/// 解析 JSON 兼容接口中的参数。
815/// Parse parameters from the JSON compatibility interface.
816fn 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
839/// 把 FFI SQL 值转换为内部 SQLite 值。
840/// Convert an FFI SQL value into an internal SQLite value.
841fn 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                // SAFETY:
857                // 调用方保证 data/len 描述一段有效的只读字节切片。
858                // The caller guarantees that data/len describe a valid read-only byte slice.
859                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
874/// 从 FFI 指针参数解析参数数组。
875/// Parse a parameter array from FFI pointer inputs.
876fn 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        // SAFETY:
893        // 调用方保证 params/params_len 描述一段有效的只读数组。
894        // The caller guarantees that params/params_len describe a valid read-only array.
895        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(&params_json_string).map_err(|error| error.to_string())
906}
907
908/// 从 FFI 批量参数切片解析批量参数。
909/// Parse batch parameters from FFI parameter slices.
910fn 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    // SAFETY:
919    // 调用方保证 items/items_len 描述一段有效的只读切片。
920    // The caller guarantees that items/items_len describe a valid read-only slice.
921    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            // SAFETY:
942            // 调用方保证 item.values/item.len 描述一段有效的只读数组。
943            // The caller guarantees that item.values/item.len describe a valid read-only array.
944            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
956/// 把原始 chunk 字节复制为可释放缓冲区。
957/// Copy raw chunk bytes into a releasable byte buffer.
958fn 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
1035/// 注册 JSON 兼容层的 QueryStream 结果并返回流句柄 ID。
1036/// Register a JSON-compat QueryStream result and return its stream-handle ID.
1037fn 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
1053/// 读取 JSON 兼容层的 QueryStream 结果。
1054/// Read a JSON-compat QueryStream result from the registry.
1055fn 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
1070/// 关闭并移除 JSON 兼容层的 QueryStream 结果。
1071/// Close and remove a JSON-compat QueryStream result from the registry.
1072fn 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/// 导出库模式基础元信息。
1247/// Export bootstrap library metadata as a JSON string.
1248#[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/// 释放由本库分配的 JSON/C 字符串。
1261/// Free a JSON/C string allocated by this library.
1262#[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    // SAFETY:
1269    // 指针来自 `CString::into_raw`,这里回收所有权即可。
1270    // The pointer comes from `CString::into_raw`, so reclaiming ownership here is valid.
1271    unsafe {
1272        drop(CString::from_raw(value));
1273    }
1274}
1275
1276/// 获取最近一次 FFI 错误消息。
1277/// Get the latest FFI error message.
1278#[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/// 清理最近一次 FFI 错误消息。
1290/// Clear the latest FFI error message.
1291#[unsafe(no_mangle)]
1292pub extern "C" fn vldb_sqlite_clear_last_error() {
1293    clear_last_error_inner();
1294}
1295
1296/// 返回 JSON 指针是否为空,便于上层快速探测调用是否成功。
1297/// Return whether the JSON pointer is null so callers can quickly detect failure.
1298#[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/// 创建默认多库 runtime 句柄。
1304/// Create a default multi-database runtime handle.
1305#[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/// 释放多库 runtime 句柄。
1314/// Destroy a multi-database runtime handle.
1315#[unsafe(no_mangle)]
1316pub extern "C" fn vldb_sqlite_runtime_destroy(handle: *mut VldbSqliteRuntimeHandle) {
1317    if handle.is_null() {
1318        return;
1319    }
1320
1321    // SAFETY:
1322    // 指针来自 `Box::into_raw`,此处按原路径回收所有权。
1323    // The pointer comes from `Box::into_raw`, so reclaiming ownership here is valid.
1324    unsafe {
1325        drop(Box::from_raw(handle));
1326    }
1327}
1328
1329/// 打开或复用指定路径的数据库句柄。
1330/// Open or reuse a database handle for the specified path.
1331#[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/// 关闭 runtime 中缓存的数据库。
1359/// Close a cached database from the runtime.
1360#[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/// 释放数据库句柄。
1386/// Destroy a database handle.
1387#[unsafe(no_mangle)]
1388pub extern "C" fn vldb_sqlite_database_destroy(handle: *mut VldbSqliteDatabaseHandle) {
1389    if handle.is_null() {
1390        return;
1391    }
1392
1393    // SAFETY:
1394    // 指针来自 `Box::into_raw`,此处按原路径回收所有权。
1395    // The pointer comes from `Box::into_raw`, so reclaiming ownership here is valid.
1396    unsafe {
1397        drop(Box::from_raw(handle));
1398    }
1399}
1400
1401/// 返回数据库句柄绑定的路径字符串。
1402/// Return the bound database path string for a database handle.
1403#[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/// 释放由 QueryStream chunk getter 返回的字节缓冲区。
1418/// Free a byte buffer returned by a QueryStream chunk getter.
1419#[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        // SAFETY:
1427        // 指针由 `bytes_to_buffer` 基于 `Vec<u8>` 导出,len/cap 与原始 Vec 布局匹配。
1428        // The pointer is produced by `bytes_to_buffer` from a `Vec<u8>`, and len/cap match the original Vec layout.
1429        unsafe {
1430            let _ = Vec::from_raw_parts(buffer.data, len, cap);
1431        }
1432    }
1433}
1434
1435/// 通过数据库句柄执行脚本或单条 SQL。
1436/// Execute a script or a single SQL statement through a database handle.
1437#[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/// 通过数据库句柄执行批量 SQL。
1471/// Execute batch SQL through a database handle.
1472#[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/// 通过数据库句柄执行 JSON 查询。
1505/// Execute a JSON query through a database handle.
1506#[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/// 通过数据库句柄执行 Arrow IPC chunk 查询。
1532/// Execute an Arrow IPC chunk query through a database handle.
1533#[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/// 释放通用 SQL 执行结果句柄。
1565/// Destroy a shared SQL execution-result handle.
1566#[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/// 返回通用 SQL 执行结果中的 success 标志。
1577/// Return the success flag from a shared SQL execution result.
1578#[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/// 返回通用 SQL 执行结果中的 message。
1592/// Return the message from a shared SQL execution result.
1593#[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/// 返回通用 SQL 执行结果中的 rows_changed。
1608/// Return rows_changed from a shared SQL execution result.
1609#[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/// 返回通用 SQL 执行结果中的 last_insert_rowid。
1623/// Return last_insert_rowid from a shared SQL execution result.
1624#[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/// 返回通用 SQL 执行结果中的 statements_executed。
1638/// Return statements_executed from a shared SQL execution result.
1639#[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/// 释放 JSON 查询结果句柄。
1653/// Destroy a JSON-query result handle.
1654#[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/// 返回 JSON 查询结果中的 JSON 行集字符串。
1667/// Return the JSON row-set string from a JSON-query result.
1668#[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/// 返回 JSON 查询结果中的行数。
1683/// Return the row count from a JSON-query result.
1684#[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/// 释放 Arrow IPC chunk 查询结果句柄。
1698/// Destroy an Arrow IPC chunk query-result handle.
1699#[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/// 返回 Arrow IPC chunk 数量。
1710/// Return the number of Arrow IPC chunks.
1711#[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/// 返回 Arrow IPC chunk 查询结果中的行数。
1731/// Return the row count from an Arrow IPC chunk query result.
1732#[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/// 返回 Arrow IPC chunk 查询结果的总字节数。
1752/// Return the total byte count from an Arrow IPC chunk query result.
1753#[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/// 返回指定下标的 Arrow IPC chunk。
1773/// Return the Arrow IPC chunk at the specified index.
1774#[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/// 通过数据库句柄执行分词主接口,返回结果句柄。
1797/// Execute the main tokenize interface from a database handle and return a result handle.
1798#[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/// 释放分词结果句柄。
1831/// Destroy a tokenize-result handle.
1832#[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    // SAFETY:
1841    // 指针来自 `Box::into_raw`,此处按原路径回收所有权。
1842    // The pointer comes from `Box::into_raw`, so reclaiming ownership here is valid.
1843    unsafe {
1844        drop(Box::from_raw(handle));
1845    }
1846}
1847
1848/// 返回分词结果中的规范化文本。
1849/// Return the normalized text from a tokenize result.
1850#[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/// 返回分词结果中的 FTS 查询表达式。
1865/// Return the FTS query expression from a tokenize result.
1866#[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/// 返回分词结果中的词元数量。
1881/// Return the token count from a tokenize result.
1882#[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/// 返回指定下标的词元字符串。
1897/// Return the token string at the specified index.
1898#[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/// 通过数据库句柄热更新自定义词。
1924/// Hot-update a custom word through a database handle.
1925#[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            // SAFETY:
1954            // `out_result` 已经过空指针检查,且指向调用方可写内存。
1955            // `out_result` has been null-checked and points to caller-writable memory.
1956            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/// 通过数据库句柄删除自定义词。
1969/// Remove a custom word through a database handle.
1970#[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            // SAFETY:
1994            // `out_result` 已经过空指针检查,且指向调用方可写内存。
1995            // `out_result` has been null-checked and points to caller-writable memory.
1996            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/// 通过数据库句柄列出自定义词,返回列表句柄。
2009/// List custom words through a database handle and return a list handle.
2010#[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/// 释放自定义词列表句柄。
2034/// Destroy a custom-word list handle.
2035#[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    // SAFETY:
2044    // 指针来自 `Box::into_raw`,此处按原路径回收所有权。
2045    // The pointer comes from `Box::into_raw`, so reclaiming ownership here is valid.
2046    unsafe {
2047        drop(Box::from_raw(handle));
2048    }
2049}
2050
2051/// 返回自定义词列表长度。
2052/// Return the number of custom words in the list.
2053#[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/// 返回指定自定义词条目的词文本。
2068/// Return the word text of a specific custom-word entry.
2069#[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/// 返回指定自定义词条目的权重。
2095/// Return the weight of a specific custom-word entry.
2096#[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/// 通过数据库句柄确保 FTS 索引存在。
2122/// Ensure an FTS index exists through a database handle.
2123#[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/// 通过数据库句柄重建 FTS 索引。
2165/// Rebuild an FTS index through a database handle.
2166#[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/// 通过数据库句柄写入或更新 FTS 文档。
2209/// Upsert an FTS document through a database handle.
2210#[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/// 通过数据库句柄删除 FTS 文档。
2263/// Delete an FTS document through a database handle.
2264#[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/// 通过数据库句柄执行 FTS 检索并返回结果句柄。
2302/// Execute FTS search through a database handle and return a result handle.
2303#[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/// 释放 FTS 检索结果句柄。
2341/// Destroy an FTS search-result handle.
2342#[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    // SAFETY:
2351    // 指针来自 `Box::into_raw`,此处按原路径回收所有权。
2352    // The pointer comes from `Box::into_raw`, so reclaiming ownership here is valid.
2353    unsafe {
2354        drop(Box::from_raw(handle));
2355    }
2356}
2357
2358/// 返回 FTS 检索总命中数。
2359/// Return the total hit count of an FTS search result.
2360#[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/// 返回 FTS 检索当前页命中数。
2375/// Return the page hit count of an FTS search result.
2376#[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/// 返回检索结果的来源标签。
2391/// Return the source label of a search result.
2392#[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/// 返回检索结果的查询模式标签。
2407/// Return the query-mode label of a search result.
2408#[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/// 返回指定命中的业务 ID。
2423/// Return the business ID of the specified hit.
2424#[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/// 返回指定命中的文件路径。
2450/// Return the file path of the specified hit.
2451#[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/// 返回指定命中的标题。
2477/// Return the title of the specified hit.
2478#[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/// 返回指定命中的高亮标题。
2504/// Return the highlighted title of the specified hit.
2505#[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/// 返回指定命中的内容片段。
2531/// Return the content snippet of the specified hit.
2532#[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/// 返回指定命中的标准化分数。
2558/// Return the normalized score of the specified hit.
2559#[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/// 返回指定命中的名次。
2585/// Return the rank of the specified hit.
2586#[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/// 返回指定命中的原始 BM25 分数。
2612/// Return the raw BM25 score of the specified hit.
2613#[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/// 通过 JSON 请求执行脚本或单条 SQL。
2639/// Execute a script or a single SQL statement from a JSON request.
2640#[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/// 通过 JSON 请求执行批量 SQL。
2670/// Execute batch SQL from a JSON request.
2671#[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/// 通过 JSON 请求执行 JSON 查询。
2706/// Execute a JSON query from a JSON request.
2707#[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/// 通过 JSON 请求执行 Arrow IPC chunk 查询。
2732/// Execute an Arrow IPC chunk query from a JSON request.
2733#[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/// 通过 JSON 请求读取指定 QueryStream 句柄中的单个 chunk。
2777/// Read a single chunk from a JSON QueryStream handle.
2778#[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/// 通过 JSON 请求关闭 QueryStream 句柄。
2819/// Close a JSON QueryStream handle.
2820#[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/// 通过 JSON 请求执行分词,并返回 JSON 结果。
2852/// Execute tokenization from a JSON request and return a JSON response.
2853#[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/// 通过 JSON 请求热更新自定义词。
2904/// Hot-update a custom dictionary word from a JSON request.
2905#[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/// 通过 JSON 请求删除自定义词。
2937/// Remove a custom dictionary word from a JSON request.
2938#[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/// 通过 JSON 请求列出当前库中的自定义词。
2966/// List custom dictionary words from a JSON request.
2967#[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/// 通过 JSON 请求确保 FTS 索引存在。
2995/// Ensure an FTS index exists from a JSON request.
2996#[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/// 通过 JSON 请求重建 FTS 索引。
3027/// Rebuild an FTS index from a JSON request.
3028#[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/// 通过 JSON 请求写入 FTS 文档。
3059/// Upsert an FTS document from a JSON request.
3060#[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/// 通过 JSON 请求删除 FTS 文档。
3099/// Delete an FTS document from a JSON request.
3100#[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/// 通过 JSON 请求执行标准化 FTS 检索。
3130/// Execute normalized FTS search from a JSON request.
3131#[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    /// 仅用于测试:把 C 字符串安全转回 Rust 字符串。
3190    /// Test helper: convert a C string back to a Rust string safely.
3191    fn c_string_to_string(value: *const c_char) -> Option<String> {
3192        if value.is_null() {
3193            return None;
3194        }
3195
3196        // SAFETY:
3197        // 调用方保证传入指向一个以 NUL 结尾的只读字符串。
3198        // The caller guarantees the pointer refers to a NUL-terminated read-only string.
3199        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}