Skip to main content

sz_orm_cpp/
lib.rs

1//! # SZ-ORM C++ Bindings
2//!
3//! Provides sz-orm-core's Pool/Query API to C++ via `extern "C"` + C ABI
4//! (SQLite backend, real and usable).
5//!
6//! C++ side wrapper see `cpp/szorm.h` (use `szorm::Pool` after `#include <szorm.h>`).
7
8use std::ffi::{c_char, CStr, CString};
9
10use sz_orm_cabi::{PoolConfigC, QueryResultC, SzOrmPoolHandle, SzOrmTransactionHandle};
11
12/// Create connection pool (real creation, SQLite backend)
13///
14/// Returns handle, null indicates failure.
15///
16/// # Safety
17///
18/// SAFETY: `dsn` must be a valid NUL-terminated C string; `config` may be null.
19#[no_mangle]
20pub unsafe extern "C" fn sz_orm_cpp_pool_new(
21    dsn: *const c_char,
22    config: *const PoolConfigC,
23) -> SzOrmPoolHandle {
24    // SAFETY: 转发到 cabi
25    unsafe { sz_orm_cabi::sz_orm_pool_new(dsn, config) }
26}
27
28/// Free connection pool
29///
30/// # Safety
31///
32/// SAFETY: handle must be a valid handle returned by `sz_orm_cpp_pool_new`.
33#[no_mangle]
34pub unsafe extern "C" fn sz_orm_cpp_pool_free(handle: SzOrmPoolHandle) {
35    // SAFETY: 转发到 cabi
36    unsafe { sz_orm_cabi::sz_orm_pool_free(handle) }
37}
38
39/// Health check (real acquire + ping)
40///
41/// # Safety
42///
43/// SAFETY: handle must be a valid handle returned by `sz_orm_cpp_pool_new`.
44#[no_mangle]
45pub unsafe extern "C" fn sz_orm_cpp_ping(handle: SzOrmPoolHandle) -> i32 {
46    // SAFETY: 转发到 cabi
47    unsafe { sz_orm_cabi::sz_orm_ping(handle) }
48}
49
50/// Execute query, return JSON row array (caller frees with `sz_orm_cpp_string_free`)
51///
52/// # Safety
53///
54/// SAFETY: handle must be a valid handle returned by `sz_orm_cpp_pool_new`;
55/// `sql` must be a valid NUL-terminated C string.
56#[no_mangle]
57pub unsafe extern "C" fn sz_orm_cpp_query(
58    handle: SzOrmPoolHandle,
59    sql: *const c_char,
60) -> *mut c_char {
61    if handle.is_null() || sql.is_null() {
62        return std::ptr::null_mut();
63    }
64    // SAFETY: 调用方保证 handle 有效
65    let result = unsafe { sz_orm_cabi::sz_orm_query(handle, sql) };
66    if result.is_null() {
67        return std::ptr::null_mut();
68    }
69    // SAFETY: result 由 sz_orm_query 分配
70    let result_box = unsafe { Box::from_raw(result) };
71    let json = if result_box.success != 0 && !result_box.json.is_null() {
72        // SAFETY: json 是有效 NUL 结尾 C 字符串
73        unsafe { CStr::from_ptr(result_box.json) }
74            .to_str()
75            .map(|s| s.to_string())
76            .unwrap_or_default()
77    } else {
78        // SAFETY: result_box 配对释放
79        unsafe {
80            sz_orm_cabi::sz_orm_query_result_free(Box::into_raw(result_box));
81        }
82        return std::ptr::null_mut();
83    };
84    // SAFETY: result_box 配对释放
85    unsafe {
86        sz_orm_cabi::sz_orm_query_result_free(Box::into_raw(result_box));
87    }
88    match CString::new(json) {
89        Ok(c) => c.into_raw(),
90        Err(_) => std::ptr::null_mut(),
91    }
92}
93
94/// Free string returned by `sz_orm_cpp_query`
95///
96/// # Safety
97///
98/// SAFETY: `ptr` must be a pointer returned by `sz_orm_cpp_query` (or null).
99#[no_mangle]
100pub unsafe extern "C" fn sz_orm_cpp_string_free(ptr: *mut c_char) {
101    if ptr.is_null() {
102        return;
103    }
104    // SAFETY: ptr 由 CString::into_raw 分配,配对释放
105    unsafe {
106        drop(CString::from_raw(ptr));
107    }
108}
109
110/// Execute write statement, return heap-allocated `QueryResultC` (free with `sz_orm_cpp_result_free`)
111///
112/// # Safety
113///
114/// SAFETY: handle must be a valid handle returned by `sz_orm_cpp_pool_new`;
115/// `sql` must be a valid NUL-terminated C string.
116#[no_mangle]
117pub unsafe extern "C" fn sz_orm_cpp_execute(
118    handle: SzOrmPoolHandle,
119    sql: *const c_char,
120) -> *mut QueryResultC {
121    if handle.is_null() || sql.is_null() {
122        return std::ptr::null_mut();
123    }
124    // SAFETY: 转发到 cabi
125    let result = unsafe { sz_orm_cabi::sz_orm_execute(handle, sql) };
126    Box::into_raw(Box::new(result))
127}
128
129/// Free result returned by `sz_orm_cpp_execute`
130///
131/// # Safety
132///
133/// SAFETY: `ptr` must be a pointer returned by `sz_orm_cpp_execute` (or null).
134#[no_mangle]
135pub unsafe extern "C" fn sz_orm_cpp_result_free(ptr: *mut QueryResultC) {
136    if ptr.is_null() {
137        return;
138    }
139    // SAFETY: ptr 由 sz_orm_cpp_execute 分配,配对释放
140    unsafe {
141        drop(Box::from_raw(ptr));
142    }
143}
144
145/// Get version number
146#[no_mangle]
147pub extern "C" fn sz_orm_cpp_version() -> u32 {
148    sz_orm_cabi::sz_orm_version()
149}
150
151// ============================================================================
152// 事务 API 转发(REQ-BND-006)
153// ============================================================================
154
155/// Begin transaction, return transaction handle (null indicates failure)
156///
157/// # Safety
158///
159/// SAFETY: `handle` must be a valid handle returned by `sz_orm_cpp_pool_new`.
160#[no_mangle]
161pub unsafe extern "C" fn sz_orm_cpp_transaction_begin(
162    handle: SzOrmPoolHandle,
163) -> SzOrmTransactionHandle {
164    // SAFETY: 转发到 cabi
165    unsafe { sz_orm_cabi::sz_orm_transaction_begin(handle) }
166}
167
168/// Execute SQL in transaction, return heap-allocated `QueryResultC`
169///
170/// # Safety
171///
172/// SAFETY: `tx_handle` must be valid; `sql` must be a valid NUL-terminated C string.
173#[no_mangle]
174pub unsafe extern "C" fn sz_orm_cpp_transaction_execute(
175    tx_handle: SzOrmTransactionHandle,
176    sql: *const c_char,
177) -> *mut QueryResultC {
178    if tx_handle.is_null() || sql.is_null() {
179        return std::ptr::null_mut();
180    }
181    // SAFETY: 转发到 cabi
182    let result = unsafe { sz_orm_cabi::sz_orm_transaction_execute(tx_handle, sql) };
183    Box::into_raw(Box::new(result))
184}
185
186/// Commit transaction, return 1=success 0=failure
187///
188/// # Safety
189///
190/// SAFETY: `tx_handle` must be valid.
191#[no_mangle]
192pub unsafe extern "C" fn sz_orm_cpp_transaction_commit(tx_handle: SzOrmTransactionHandle) -> i32 {
193    // SAFETY: 转发到 cabi
194    unsafe { sz_orm_cabi::sz_orm_transaction_commit(tx_handle) }
195}
196
197/// Rollback transaction, return 1=success 0=failure
198///
199/// # Safety
200///
201/// SAFETY: `tx_handle` must be valid.
202#[no_mangle]
203pub unsafe extern "C" fn sz_orm_cpp_transaction_rollback(tx_handle: SzOrmTransactionHandle) -> i32 {
204    // SAFETY: 转发到 cabi
205    unsafe { sz_orm_cabi::sz_orm_transaction_rollback(tx_handle) }
206}
207
208/// Free transaction handle (auto rollback if still active)
209///
210/// # Safety
211///
212/// SAFETY: `tx_handle` must be valid.
213#[no_mangle]
214pub unsafe extern "C" fn sz_orm_cpp_transaction_free(tx_handle: SzOrmTransactionHandle) {
215    // SAFETY: 转发到 cabi
216    unsafe { sz_orm_cabi::sz_orm_transaction_free(tx_handle) }
217}
218
219// ============================================================================
220// 模型级 API 转发(REQ-BND-013)
221// ============================================================================
222
223/// Insert row on pool, return heap-allocated `QueryResultC`
224///
225/// # Safety
226///
227/// SAFETY: `handle` must be valid; all parameters must be valid NUL-terminated C strings.
228#[no_mangle]
229pub unsafe extern "C" fn sz_orm_cpp_model_insert(
230    handle: SzOrmPoolHandle,
231    table: *const c_char,
232    fields_json: *const c_char,
233    values_json: *const c_char,
234) -> *mut QueryResultC {
235    if handle.is_null() || table.is_null() || fields_json.is_null() || values_json.is_null() {
236        return std::ptr::null_mut();
237    }
238    // SAFETY: 转发到 cabi
239    let result =
240        unsafe { sz_orm_cabi::sz_orm_model_insert(handle, table, fields_json, values_json) };
241    Box::into_raw(Box::new(result))
242}
243
244/// Update row on pool, return heap-allocated `QueryResultC`
245///
246/// # Safety
247///
248/// SAFETY: `handle` must be valid; all parameters must be valid NUL-terminated C strings.
249#[no_mangle]
250pub unsafe extern "C" fn sz_orm_cpp_model_update(
251    handle: SzOrmPoolHandle,
252    table: *const c_char,
253    set_json: *const c_char,
254    where_clause: *const c_char,
255    where_params_json: *const c_char,
256) -> *mut QueryResultC {
257    if handle.is_null() || table.is_null() || set_json.is_null() {
258        return std::ptr::null_mut();
259    }
260    // SAFETY: 转发到 cabi
261    let result = unsafe {
262        sz_orm_cabi::sz_orm_model_update(handle, table, set_json, where_clause, where_params_json)
263    };
264    Box::into_raw(Box::new(result))
265}
266
267/// Delete row on pool, return heap-allocated `QueryResultC`
268///
269/// # Safety
270///
271/// SAFETY: `handle` must be valid; all parameters must be valid NUL-terminated C strings.
272#[no_mangle]
273pub unsafe extern "C" fn sz_orm_cpp_model_delete(
274    handle: SzOrmPoolHandle,
275    table: *const c_char,
276    where_clause: *const c_char,
277    where_params_json: *const c_char,
278) -> *mut QueryResultC {
279    if handle.is_null() || table.is_null() {
280        return std::ptr::null_mut();
281    }
282    // SAFETY: 转发到 cabi
283    let result =
284        unsafe { sz_orm_cabi::sz_orm_model_delete(handle, table, where_clause, where_params_json) };
285    Box::into_raw(Box::new(result))
286}
287
288/// Query row on pool, return JSON row array string (free with `sz_orm_cpp_string_free`)
289///
290/// # Safety
291///
292/// SAFETY: `handle` must be valid; all parameters must be valid NUL-terminated C strings.
293#[no_mangle]
294pub unsafe extern "C" fn sz_orm_cpp_model_find(
295    handle: SzOrmPoolHandle,
296    table: *const c_char,
297    where_clause: *const c_char,
298    where_params_json: *const c_char,
299) -> *mut c_char {
300    if handle.is_null() || table.is_null() {
301        return std::ptr::null_mut();
302    }
303    // SAFETY: 转发到 cabi
304    unsafe { sz_orm_cabi::sz_orm_model_find(handle, table, where_clause, where_params_json) }
305}
306
307/// Insert row in transaction, return heap-allocated `QueryResultC`
308///
309/// # Safety
310///
311/// SAFETY: `tx_handle` must be valid; all parameters must be valid NUL-terminated C strings.
312#[no_mangle]
313pub unsafe extern "C" fn sz_orm_cpp_model_insert_tx(
314    tx_handle: SzOrmTransactionHandle,
315    table: *const c_char,
316    fields_json: *const c_char,
317    values_json: *const c_char,
318) -> *mut QueryResultC {
319    if tx_handle.is_null() || table.is_null() || fields_json.is_null() || values_json.is_null() {
320        return std::ptr::null_mut();
321    }
322    // SAFETY: 转发到 cabi
323    let result =
324        unsafe { sz_orm_cabi::sz_orm_model_insert_tx(tx_handle, table, fields_json, values_json) };
325    Box::into_raw(Box::new(result))
326}
327
328/// Update row in transaction, return heap-allocated `QueryResultC`
329///
330/// # Safety
331///
332/// SAFETY: `tx_handle` must be valid; all parameters must be valid NUL-terminated C strings.
333#[no_mangle]
334pub unsafe extern "C" fn sz_orm_cpp_model_update_tx(
335    tx_handle: SzOrmTransactionHandle,
336    table: *const c_char,
337    set_json: *const c_char,
338    where_clause: *const c_char,
339    where_params_json: *const c_char,
340) -> *mut QueryResultC {
341    if tx_handle.is_null() || table.is_null() || set_json.is_null() {
342        return std::ptr::null_mut();
343    }
344    // SAFETY: 转发到 cabi
345    let result = unsafe {
346        sz_orm_cabi::sz_orm_model_update_tx(
347            tx_handle,
348            table,
349            set_json,
350            where_clause,
351            where_params_json,
352        )
353    };
354    Box::into_raw(Box::new(result))
355}
356
357/// Delete row in transaction, return heap-allocated `QueryResultC`
358///
359/// # Safety
360///
361/// SAFETY: `tx_handle` must be valid; all parameters must be valid NUL-terminated C strings.
362#[no_mangle]
363pub unsafe extern "C" fn sz_orm_cpp_model_delete_tx(
364    tx_handle: SzOrmTransactionHandle,
365    table: *const c_char,
366    where_clause: *const c_char,
367    where_params_json: *const c_char,
368) -> *mut QueryResultC {
369    if tx_handle.is_null() || table.is_null() {
370        return std::ptr::null_mut();
371    }
372    // SAFETY: 转发到 cabi
373    let result = unsafe {
374        sz_orm_cabi::sz_orm_model_delete_tx(tx_handle, table, where_clause, where_params_json)
375    };
376    Box::into_raw(Box::new(result))
377}
378
379/// Query row in transaction, return JSON row array string (free with `sz_orm_cpp_string_free`)
380///
381/// # Safety
382///
383/// SAFETY: `tx_handle` must be valid; all parameters must be valid NUL-terminated C strings.
384#[no_mangle]
385pub unsafe extern "C" fn sz_orm_cpp_model_find_tx(
386    tx_handle: SzOrmTransactionHandle,
387    table: *const c_char,
388    where_clause: *const c_char,
389    where_params_json: *const c_char,
390) -> *mut c_char {
391    if tx_handle.is_null() || table.is_null() {
392        return std::ptr::null_mut();
393    }
394    // SAFETY: 转发到 cabi
395    unsafe { sz_orm_cabi::sz_orm_model_find_tx(tx_handle, table, where_clause, where_params_json) }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401
402    fn test_pool() -> SzOrmPoolHandle {
403        let dsn = CString::new("sqlite::memory:").unwrap();
404        // SAFETY: dsn 有效
405        unsafe { sz_orm_cpp_pool_new(dsn.as_ptr(), std::ptr::null()) }
406    }
407
408    #[test]
409    fn test_cpp_pool_new_creates_real_pool() {
410        let pool = test_pool();
411        assert!(!pool.is_null(), "pool_new should create a real pool");
412        // SAFETY: pool 有效
413        unsafe { sz_orm_cpp_pool_free(pool) };
414    }
415
416    #[test]
417    fn test_cpp_ping_healthy() {
418        let pool = test_pool();
419        assert!(!pool.is_null());
420        // SAFETY: pool 有效
421        let healthy = unsafe { sz_orm_cpp_ping(pool) };
422        assert_eq!(healthy, 1, "fresh pool should be healthy");
423        // SAFETY: pool 有效
424        unsafe { sz_orm_cpp_pool_free(pool) };
425    }
426
427    #[test]
428    fn test_cpp_execute_and_query_roundtrip() {
429        let pool = test_pool();
430        assert!(!pool.is_null());
431
432        let create =
433            CString::new("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)").unwrap();
434        // SAFETY: pool/sql 有效
435        let rp = unsafe { sz_orm_cpp_execute(pool, create.as_ptr()) };
436        assert!(!rp.is_null(), "CREATE should succeed");
437        // SAFETY: rp 由 sz_orm_cpp_execute 分配
438        let r = unsafe { Box::from_raw(rp) };
439        assert_eq!(
440            r.success, 1,
441            "CREATE should succeed, got code {}",
442            r.error_code
443        );
444        // SAFETY: r 配对释放
445        unsafe { sz_orm_cpp_result_free(Box::into_raw(r)) };
446
447        let insert = CString::new("INSERT INTO users (name) VALUES ('Alice'), ('Bob')").unwrap();
448        // SAFETY: pool/sql 有效
449        let rp = unsafe { sz_orm_cpp_execute(pool, insert.as_ptr()) };
450        assert!(!rp.is_null(), "INSERT should succeed");
451        // SAFETY: rp 由 sz_orm_cpp_execute 分配
452        let r = unsafe { Box::from_raw(rp) };
453        assert_eq!(r.success, 1);
454        assert_eq!(r.rows_affected, 2, "two rows inserted");
455        // SAFETY: r 配对释放
456        unsafe { sz_orm_cpp_result_free(Box::into_raw(r)) };
457
458        let q = CString::new("SELECT id, name FROM users ORDER BY id").unwrap();
459        // SAFETY: pool/sql 有效
460        let json_ptr = unsafe { sz_orm_cpp_query(pool, q.as_ptr()) };
461        assert!(!json_ptr.is_null(), "query should return JSON");
462        // SAFETY: json_ptr 有效
463        let json = unsafe { CStr::from_ptr(json_ptr) }
464            .to_string_lossy()
465            .into_owned();
466        assert!(
467            json.contains("Alice"),
468            "query rows should contain Alice: {json}"
469        );
470        assert!(
471            json.contains("Bob"),
472            "query rows should contain Bob: {json}"
473        );
474        // SAFETY: json_ptr 由 sz_orm_cpp_query 分配,配对释放
475        unsafe { sz_orm_cpp_string_free(json_ptr) };
476
477        // SAFETY: pool 有效
478        unsafe { sz_orm_cpp_pool_free(pool) };
479    }
480
481    #[test]
482    fn test_cpp_query_invalid_sql_returns_null() {
483        let pool = test_pool();
484        assert!(!pool.is_null());
485        let q = CString::new("SELECT * FROM nonexistent").unwrap();
486        // SAFETY: pool/sql 有效
487        let json_ptr = unsafe { sz_orm_cpp_query(pool, q.as_ptr()) };
488        assert!(json_ptr.is_null(), "invalid SQL should return null");
489        // SAFETY: pool 有效
490        unsafe { sz_orm_cpp_pool_free(pool) };
491    }
492
493    #[test]
494    fn test_cpp_null_args() {
495        // SAFETY: null 参数合法调用
496        let pool = unsafe { sz_orm_cpp_pool_new(std::ptr::null(), std::ptr::null()) };
497        assert!(pool.is_null());
498        // SAFETY: null 参数合法调用
499        let r = unsafe { sz_orm_cpp_execute(std::ptr::null_mut(), std::ptr::null()) };
500        assert!(r.is_null());
501        // SAFETY: null 参数合法调用
502        let q = unsafe { sz_orm_cpp_query(std::ptr::null_mut(), std::ptr::null()) };
503        assert!(q.is_null());
504    }
505
506    #[test]
507    fn test_cpp_version() {
508        let v = sz_orm_cpp_version();
509        assert!(v >= 1, "version should be >= 1, got {v}");
510    }
511
512    #[test]
513    fn test_cpp_free_null_safe() {
514        // SAFETY: null 参数合法调用
515        unsafe { sz_orm_cpp_string_free(std::ptr::null_mut()) };
516        // SAFETY: null 参数合法调用
517        unsafe { sz_orm_cpp_result_free(std::ptr::null_mut()) };
518    }
519
520    #[test]
521    fn test_cpp_ping_after_free_returns_zero() {
522        // 使用 null handle 测试 ping(而非释放后的 UAF)
523        // SAFETY: null handle 合法调用
524        let healthy = unsafe { sz_orm_cpp_ping(std::ptr::null_mut()) };
525        assert_eq!(healthy, 0, "null pool should not be healthy");
526    }
527
528    #[test]
529    fn test_cpp_multiple_pools_independent() {
530        let dsn1 = CString::new("sqlite::memory:").unwrap();
531        // SAFETY: dsn1 有效
532        let pool1 = unsafe { sz_orm_cpp_pool_new(dsn1.as_ptr(), std::ptr::null()) };
533        let dsn2 = CString::new("sqlite::memory:").unwrap();
534        // SAFETY: dsn2 有效
535        let pool2 = unsafe { sz_orm_cpp_pool_new(dsn2.as_ptr(), std::ptr::null()) };
536        assert!(!pool1.is_null());
537        assert!(!pool2.is_null());
538        assert_ne!(pool1, pool2, "pools should have distinct handles");
539        // SAFETY: pools 有效
540        unsafe {
541            sz_orm_cpp_pool_free(pool1);
542            sz_orm_cpp_pool_free(pool2);
543        }
544    }
545
546    #[test]
547    fn test_cpp_execute_empty_sql() {
548        let pool = test_pool();
549        assert!(!pool.is_null());
550        let create = CString::new("CREATE TABLE cpp_e (id INTEGER PRIMARY KEY)").unwrap();
551        // SAFETY: pool/sql 有效
552        let r = unsafe { sz_orm_cpp_execute(pool, create.as_ptr()) };
553        // SAFETY: r 配对释放
554        unsafe { sz_orm_cpp_result_free(r) };
555        // SAFETY: pool 有效
556        unsafe { sz_orm_cpp_pool_free(pool) };
557    }
558
559    #[test]
560    fn test_cpp_query_empty_table() {
561        let pool = test_pool();
562        assert!(!pool.is_null());
563        let create = CString::new("CREATE TABLE empty_t (id INTEGER PRIMARY KEY)").unwrap();
564        // SAFETY: pool/sql 有效
565        let r = unsafe { sz_orm_cpp_execute(pool, create.as_ptr()) };
566        // SAFETY: r 配对释放
567        unsafe { sz_orm_cpp_result_free(r) };
568        let q = CString::new("SELECT id FROM empty_t").unwrap();
569        // SAFETY: pool/sql 有效
570        let json_ptr = unsafe { sz_orm_cpp_query(pool, q.as_ptr()) };
571        assert!(
572            !json_ptr.is_null(),
573            "empty table query should still return JSON"
574        );
575        // SAFETY: json_ptr 有效
576        let json = unsafe { CStr::from_ptr(json_ptr) }
577            .to_string_lossy()
578            .into_owned();
579        assert!(
580            json.contains("[]") || json.is_empty() || json.contains("id"),
581            "empty result should be empty array: {json}"
582        );
583        // SAFETY: json_ptr 配对释放
584        unsafe { sz_orm_cpp_string_free(json_ptr) };
585        // SAFETY: pool 有效
586        unsafe { sz_orm_cpp_pool_free(pool) };
587    }
588
589    // ===== 新增测试:事务 + 模型级 API 转发(REQ-BND-006/013/014)=====
590
591    fn model_test_pool() -> SzOrmPoolHandle {
592        let pool = test_pool();
593        assert!(!pool.is_null());
594        let create =
595            CString::new("CREATE TABLE cm_t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
596                .unwrap();
597        // SAFETY: pool/sql 有效
598        let r = unsafe { sz_orm_cpp_execute(pool, create.as_ptr()) };
599        // SAFETY: r 配对释放
600        unsafe { sz_orm_cpp_result_free(r) };
601        pool
602    }
603
604    unsafe fn free_cpp_result(ptr: *mut QueryResultC) -> QueryResultC {
605        if ptr.is_null() {
606            return QueryResultC {
607                success: 0,
608                error_code: sz_orm_cabi::SzOrmErrorCode::InvalidArgument.as_i32(),
609                rows_affected: 0,
610                last_insert_id: 0,
611            };
612        }
613        // SAFETY: ptr 由 sz_orm_cpp_model_* 分配
614        let r = unsafe { Box::from_raw(ptr) };
615        *r
616    }
617
618    unsafe fn free_cpp_str(ptr: *mut c_char) -> String {
619        if ptr.is_null() {
620            return String::new();
621        }
622        // SAFETY: ptr 有效
623        let s = unsafe { CStr::from_ptr(ptr) }
624            .to_string_lossy()
625            .into_owned();
626        // SAFETY: ptr 配对释放
627        unsafe { sz_orm_cpp_string_free(ptr) };
628        s
629    }
630
631    #[test]
632    fn test_cpp_model_insert_find_roundtrip() {
633        let pool = model_test_pool();
634        let table = CString::new("cm_t").unwrap();
635        let fields = CString::new(r#"["name","age"]"#).unwrap();
636        let values = CString::new(r#"["Alice",30]"#).unwrap();
637        // SAFETY: pool/table/fields/values 有效
638        let r = unsafe {
639            sz_orm_cpp_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
640        };
641        // SAFETY: r 有效
642        let r = unsafe { free_cpp_result(r) };
643        assert_eq!(
644            r.success, 1,
645            "cpp model_insert should succeed, code={}",
646            r.error_code
647        );
648        assert_eq!(r.rows_affected, 1);
649
650        let where_clause = CString::new("name = ?").unwrap();
651        let where_params = CString::new(r#"["Alice"]"#).unwrap();
652        // SAFETY: pool/table/where 有效
653        let ptr = unsafe {
654            sz_orm_cpp_model_find(
655                pool,
656                table.as_ptr(),
657                where_clause.as_ptr(),
658                where_params.as_ptr(),
659            )
660        };
661        assert!(!ptr.is_null());
662        // SAFETY: ptr 有效
663        let json = unsafe { free_cpp_str(ptr) };
664        assert!(
665            json.contains("Alice"),
666            "cpp model_find should contain Alice: {json}"
667        );
668        // SAFETY: pool 有效
669        unsafe { sz_orm_cpp_pool_free(pool) };
670    }
671
672    #[test]
673    fn test_cpp_model_update_delete_find() {
674        let pool = model_test_pool();
675        let table = CString::new("cm_t").unwrap();
676        let fields = CString::new(r#"["name","age"]"#).unwrap();
677        let values = CString::new(r#"["Bob",25]"#).unwrap();
678        // SAFETY: pool/table/fields/values 有效
679        let r = unsafe {
680            sz_orm_cpp_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
681        };
682        // SAFETY: r 有效
683        unsafe { free_cpp_result(r) };
684
685        let set_json = CString::new(r#"{"age":26}"#).unwrap();
686        let where_clause = CString::new("name = ?").unwrap();
687        let where_params = CString::new(r#"["Bob"]"#).unwrap();
688        // SAFETY: pool/table/set/where 有效
689        let r = unsafe {
690            sz_orm_cpp_model_update(
691                pool,
692                table.as_ptr(),
693                set_json.as_ptr(),
694                where_clause.as_ptr(),
695                where_params.as_ptr(),
696            )
697        };
698        // SAFETY: r 有效
699        let r = unsafe { free_cpp_result(r) };
700        assert_eq!(
701            r.success, 1,
702            "cpp model_update should succeed, code={}",
703            r.error_code
704        );
705
706        // SAFETY: pool/table/where 有效
707        let ptr = unsafe {
708            sz_orm_cpp_model_find(
709                pool,
710                table.as_ptr(),
711                where_clause.as_ptr(),
712                where_params.as_ptr(),
713            )
714        };
715        // SAFETY: ptr 有效
716        let json = unsafe { free_cpp_str(ptr) };
717        assert!(
718            json.contains("26"),
719            "cpp find after update should contain age 26: {json}"
720        );
721
722        // SAFETY: pool/table/where 有效
723        let r = unsafe {
724            sz_orm_cpp_model_delete(
725                pool,
726                table.as_ptr(),
727                where_clause.as_ptr(),
728                where_params.as_ptr(),
729            )
730        };
731        // SAFETY: r 有效
732        let r = unsafe { free_cpp_result(r) };
733        assert_eq!(
734            r.success, 1,
735            "cpp model_delete should succeed, code={}",
736            r.error_code
737        );
738
739        // SAFETY: pool/table/where 有效
740        let ptr = unsafe {
741            sz_orm_cpp_model_find(
742                pool,
743                table.as_ptr(),
744                where_clause.as_ptr(),
745                where_params.as_ptr(),
746            )
747        };
748        // SAFETY: ptr 有效
749        let json = unsafe { free_cpp_str(ptr) };
750        assert!(
751            json == "[]" || json == "null" || json.is_empty(),
752            "cpp find after delete should be empty: {json}"
753        );
754        // SAFETY: pool 有效
755        unsafe { sz_orm_cpp_pool_free(pool) };
756    }
757
758    #[test]
759    fn test_cpp_model_insert_illegal_table() {
760        let pool = model_test_pool();
761        let table = CString::new("cm_t; DROP--").unwrap();
762        let fields = CString::new(r#"["name"]"#).unwrap();
763        let values = CString::new(r#"["X"]"#).unwrap();
764        // SAFETY: pool/table/fields/values 有效(table 含注入向量)
765        let r = unsafe {
766            sz_orm_cpp_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
767        };
768        // SAFETY: r 有效
769        let r = unsafe { free_cpp_result(r) };
770        assert_eq!(r.success, 0, "illegal table should fail");
771        assert_eq!(
772            r.error_code,
773            sz_orm_cabi::SzOrmErrorCode::InvalidArgument.as_i32()
774        );
775        // SAFETY: pool 有效
776        unsafe { sz_orm_cpp_pool_free(pool) };
777    }
778
779    #[test]
780    fn test_cpp_transaction_model_rollback() {
781        let pool = model_test_pool();
782        // SAFETY: pool 有效
783        let tx = unsafe { sz_orm_cpp_transaction_begin(pool) };
784        assert!(!tx.is_null());
785
786        let table = CString::new("cm_t").unwrap();
787        let fields = CString::new(r#"["name","age"]"#).unwrap();
788        let values = CString::new(r#"["TxUser",99]"#).unwrap();
789        // SAFETY: tx/table/fields/values 有效
790        let r = unsafe {
791            sz_orm_cpp_model_insert_tx(tx, table.as_ptr(), fields.as_ptr(), values.as_ptr())
792        };
793        // SAFETY: r 有效
794        let r = unsafe { free_cpp_result(r) };
795        assert_eq!(
796            r.success, 1,
797            "cpp model_insert_tx should succeed, code={}",
798            r.error_code
799        );
800
801        // SAFETY: tx 有效
802        assert_eq!(unsafe { sz_orm_cpp_transaction_rollback(tx) }, 1);
803        // SAFETY: tx 有效
804        unsafe { sz_orm_cpp_transaction_free(tx) };
805
806        let where_clause = CString::new("name = ?").unwrap();
807        let where_params = CString::new(r#"["TxUser"]"#).unwrap();
808        // SAFETY: pool/table/where 有效
809        let ptr = unsafe {
810            sz_orm_cpp_model_find(
811                pool,
812                table.as_ptr(),
813                where_clause.as_ptr(),
814                where_params.as_ptr(),
815            )
816        };
817        // SAFETY: ptr 有效
818        let json = unsafe { free_cpp_str(ptr) };
819        assert!(
820            json == "[]" || json == "null" || json.is_empty(),
821            "cpp find after rollback should be empty: {json}"
822        );
823        // SAFETY: pool 有效
824        unsafe { sz_orm_cpp_pool_free(pool) };
825    }
826
827    #[test]
828    fn test_cpp_transaction_model_commit() {
829        let pool = model_test_pool();
830        // SAFETY: pool 有效
831        let tx = unsafe { sz_orm_cpp_transaction_begin(pool) };
832        assert!(!tx.is_null());
833
834        let table = CString::new("cm_t").unwrap();
835        let fields = CString::new(r#"["name","age"]"#).unwrap();
836        let values = CString::new(r#"["CommitUser",50]"#).unwrap();
837        // SAFETY: tx/table/fields/values 有效
838        let r = unsafe {
839            sz_orm_cpp_model_insert_tx(tx, table.as_ptr(), fields.as_ptr(), values.as_ptr())
840        };
841        // SAFETY: r 有效
842        unsafe { free_cpp_result(r) };
843
844        // SAFETY: tx 有效
845        assert_eq!(unsafe { sz_orm_cpp_transaction_commit(tx) }, 1);
846        // SAFETY: tx 有效
847        unsafe { sz_orm_cpp_transaction_free(tx) };
848
849        let where_clause = CString::new("name = ?").unwrap();
850        let where_params = CString::new(r#"["CommitUser"]"#).unwrap();
851        // SAFETY: pool/table/where 有效
852        let ptr = unsafe {
853            sz_orm_cpp_model_find(
854                pool,
855                table.as_ptr(),
856                where_clause.as_ptr(),
857                where_params.as_ptr(),
858            )
859        };
860        // SAFETY: ptr 有效
861        let json = unsafe { free_cpp_str(ptr) };
862        assert!(
863            json.contains("CommitUser"),
864            "cpp find after commit should contain CommitUser: {json}"
865        );
866        // SAFETY: pool 有效
867        unsafe { sz_orm_cpp_pool_free(pool) };
868    }
869}