1use std::ffi::{c_char, CStr, CString};
9
10use sz_orm_cabi::{
11 PoolConfigC, PoolMetricsC, PoolStatsC, QueryJsonResult, QueryResultC, SzOrmPoolHandle,
12 SzOrmQueryBuilderHandle, SzOrmTransactionHandle,
13};
14
15#[no_mangle]
23pub unsafe extern "C" fn sz_orm_cpp_pool_new(
24 dsn: *const c_char,
25 config: *const PoolConfigC,
26) -> SzOrmPoolHandle {
27 unsafe { sz_orm_cabi::sz_orm_pool_new(dsn, config) }
29}
30
31#[no_mangle]
37pub unsafe extern "C" fn sz_orm_cpp_pool_free(handle: SzOrmPoolHandle) {
38 unsafe { sz_orm_cabi::sz_orm_pool_free(handle) }
40}
41
42#[no_mangle]
48pub unsafe extern "C" fn sz_orm_cpp_ping(handle: SzOrmPoolHandle) -> i32 {
49 unsafe { sz_orm_cabi::sz_orm_ping(handle) }
51}
52
53#[no_mangle]
60pub unsafe extern "C" fn sz_orm_cpp_query(
61 handle: SzOrmPoolHandle,
62 sql: *const c_char,
63) -> *mut c_char {
64 if handle.is_null() || sql.is_null() {
65 return std::ptr::null_mut();
66 }
67 let result = unsafe { sz_orm_cabi::sz_orm_query(handle, sql) };
69 if result.is_null() {
70 return std::ptr::null_mut();
71 }
72 let result_box = unsafe { Box::from_raw(result) };
74 let json = if result_box.success != 0 && !result_box.json.is_null() {
75 unsafe { CStr::from_ptr(result_box.json) }
77 .to_str()
78 .map(|s| s.to_string())
79 .unwrap_or_default()
80 } else {
81 unsafe {
83 sz_orm_cabi::sz_orm_query_result_free(Box::into_raw(result_box));
84 }
85 return std::ptr::null_mut();
86 };
87 unsafe {
89 sz_orm_cabi::sz_orm_query_result_free(Box::into_raw(result_box));
90 }
91 match CString::new(json) {
92 Ok(c) => c.into_raw(),
93 Err(_) => std::ptr::null_mut(),
94 }
95}
96
97#[no_mangle]
103pub unsafe extern "C" fn sz_orm_cpp_string_free(ptr: *mut c_char) {
104 if ptr.is_null() {
105 return;
106 }
107 unsafe {
109 drop(CString::from_raw(ptr));
110 }
111}
112
113#[no_mangle]
120pub unsafe extern "C" fn sz_orm_cpp_execute(
121 handle: SzOrmPoolHandle,
122 sql: *const c_char,
123) -> *mut QueryResultC {
124 if handle.is_null() || sql.is_null() {
125 return std::ptr::null_mut();
126 }
127 let result = unsafe { sz_orm_cabi::sz_orm_execute(handle, sql) };
129 Box::into_raw(Box::new(result))
130}
131
132#[no_mangle]
138pub unsafe extern "C" fn sz_orm_cpp_result_free(ptr: *mut QueryResultC) {
139 if ptr.is_null() {
140 return;
141 }
142 unsafe {
144 drop(Box::from_raw(ptr));
145 }
146}
147
148#[no_mangle]
150pub extern "C" fn sz_orm_cpp_version() -> u32 {
151 sz_orm_cabi::sz_orm_version()
152}
153
154#[no_mangle]
164pub unsafe extern "C" fn sz_orm_cpp_transaction_begin(
165 handle: SzOrmPoolHandle,
166) -> SzOrmTransactionHandle {
167 unsafe { sz_orm_cabi::sz_orm_transaction_begin(handle) }
169}
170
171#[no_mangle]
177pub unsafe extern "C" fn sz_orm_cpp_transaction_execute(
178 tx_handle: SzOrmTransactionHandle,
179 sql: *const c_char,
180) -> *mut QueryResultC {
181 if tx_handle.is_null() || sql.is_null() {
182 return std::ptr::null_mut();
183 }
184 let result = unsafe { sz_orm_cabi::sz_orm_transaction_execute(tx_handle, sql) };
186 Box::into_raw(Box::new(result))
187}
188
189#[no_mangle]
195pub unsafe extern "C" fn sz_orm_cpp_transaction_commit(tx_handle: SzOrmTransactionHandle) -> i32 {
196 unsafe { sz_orm_cabi::sz_orm_transaction_commit(tx_handle) }
198}
199
200#[no_mangle]
206pub unsafe extern "C" fn sz_orm_cpp_transaction_rollback(tx_handle: SzOrmTransactionHandle) -> i32 {
207 unsafe { sz_orm_cabi::sz_orm_transaction_rollback(tx_handle) }
209}
210
211#[no_mangle]
217pub unsafe extern "C" fn sz_orm_cpp_transaction_free(tx_handle: SzOrmTransactionHandle) {
218 unsafe { sz_orm_cabi::sz_orm_transaction_free(tx_handle) }
220}
221
222#[no_mangle]
232pub unsafe extern "C" fn sz_orm_cpp_model_insert(
233 handle: SzOrmPoolHandle,
234 table: *const c_char,
235 fields_json: *const c_char,
236 values_json: *const c_char,
237) -> *mut QueryResultC {
238 if handle.is_null() || table.is_null() || fields_json.is_null() || values_json.is_null() {
239 return std::ptr::null_mut();
240 }
241 let result =
243 unsafe { sz_orm_cabi::sz_orm_model_insert(handle, table, fields_json, values_json) };
244 Box::into_raw(Box::new(result))
245}
246
247#[no_mangle]
253pub unsafe extern "C" fn sz_orm_cpp_model_update(
254 handle: SzOrmPoolHandle,
255 table: *const c_char,
256 set_json: *const c_char,
257 where_clause: *const c_char,
258 where_params_json: *const c_char,
259) -> *mut QueryResultC {
260 if handle.is_null() || table.is_null() || set_json.is_null() {
261 return std::ptr::null_mut();
262 }
263 let result = unsafe {
265 sz_orm_cabi::sz_orm_model_update(handle, table, set_json, where_clause, where_params_json)
266 };
267 Box::into_raw(Box::new(result))
268}
269
270#[no_mangle]
276pub unsafe extern "C" fn sz_orm_cpp_model_delete(
277 handle: SzOrmPoolHandle,
278 table: *const c_char,
279 where_clause: *const c_char,
280 where_params_json: *const c_char,
281) -> *mut QueryResultC {
282 if handle.is_null() || table.is_null() {
283 return std::ptr::null_mut();
284 }
285 let result =
287 unsafe { sz_orm_cabi::sz_orm_model_delete(handle, table, where_clause, where_params_json) };
288 Box::into_raw(Box::new(result))
289}
290
291#[no_mangle]
297pub unsafe extern "C" fn sz_orm_cpp_model_find(
298 handle: SzOrmPoolHandle,
299 table: *const c_char,
300 where_clause: *const c_char,
301 where_params_json: *const c_char,
302) -> *mut c_char {
303 if handle.is_null() || table.is_null() {
304 return std::ptr::null_mut();
305 }
306 unsafe { sz_orm_cabi::sz_orm_model_find(handle, table, where_clause, where_params_json) }
308}
309
310#[no_mangle]
316pub unsafe extern "C" fn sz_orm_cpp_model_insert_tx(
317 tx_handle: SzOrmTransactionHandle,
318 table: *const c_char,
319 fields_json: *const c_char,
320 values_json: *const c_char,
321) -> *mut QueryResultC {
322 if tx_handle.is_null() || table.is_null() || fields_json.is_null() || values_json.is_null() {
323 return std::ptr::null_mut();
324 }
325 let result =
327 unsafe { sz_orm_cabi::sz_orm_model_insert_tx(tx_handle, table, fields_json, values_json) };
328 Box::into_raw(Box::new(result))
329}
330
331#[no_mangle]
337pub unsafe extern "C" fn sz_orm_cpp_model_update_tx(
338 tx_handle: SzOrmTransactionHandle,
339 table: *const c_char,
340 set_json: *const c_char,
341 where_clause: *const c_char,
342 where_params_json: *const c_char,
343) -> *mut QueryResultC {
344 if tx_handle.is_null() || table.is_null() || set_json.is_null() {
345 return std::ptr::null_mut();
346 }
347 let result = unsafe {
349 sz_orm_cabi::sz_orm_model_update_tx(
350 tx_handle,
351 table,
352 set_json,
353 where_clause,
354 where_params_json,
355 )
356 };
357 Box::into_raw(Box::new(result))
358}
359
360#[no_mangle]
366pub unsafe extern "C" fn sz_orm_cpp_model_delete_tx(
367 tx_handle: SzOrmTransactionHandle,
368 table: *const c_char,
369 where_clause: *const c_char,
370 where_params_json: *const c_char,
371) -> *mut QueryResultC {
372 if tx_handle.is_null() || table.is_null() {
373 return std::ptr::null_mut();
374 }
375 let result = unsafe {
377 sz_orm_cabi::sz_orm_model_delete_tx(tx_handle, table, where_clause, where_params_json)
378 };
379 Box::into_raw(Box::new(result))
380}
381
382#[no_mangle]
388pub unsafe extern "C" fn sz_orm_cpp_model_find_tx(
389 tx_handle: SzOrmTransactionHandle,
390 table: *const c_char,
391 where_clause: *const c_char,
392 where_params_json: *const c_char,
393) -> *mut c_char {
394 if tx_handle.is_null() || table.is_null() {
395 return std::ptr::null_mut();
396 }
397 unsafe { sz_orm_cabi::sz_orm_model_find_tx(tx_handle, table, where_clause, where_params_json) }
399}
400
401#[no_mangle]
411pub unsafe extern "C" fn sz_orm_cpp_qb_new(db_type: u32) -> SzOrmQueryBuilderHandle {
412 unsafe { sz_orm_cabi::sz_orm_qb_new(db_type) }
414}
415
416#[no_mangle]
422pub unsafe extern "C" fn sz_orm_cpp_qb_table(
423 qb: SzOrmQueryBuilderHandle,
424 table: *const c_char,
425) -> i32 {
426 unsafe { sz_orm_cabi::sz_orm_qb_table(qb, table) }
428}
429
430#[no_mangle]
436pub unsafe extern "C" fn sz_orm_cpp_qb_where_eq(
437 qb: SzOrmQueryBuilderHandle,
438 field: *const c_char,
439 value_json: *const c_char,
440) -> i32 {
441 unsafe { sz_orm_cabi::sz_orm_qb_where_eq(qb, field, value_json) }
443}
444
445#[no_mangle]
451pub unsafe extern "C" fn sz_orm_cpp_qb_order_by(
452 qb: SzOrmQueryBuilderHandle,
453 field: *const c_char,
454 desc: i32,
455) -> i32 {
456 unsafe { sz_orm_cabi::sz_orm_qb_order_by(qb, field, desc) }
458}
459
460#[no_mangle]
466pub unsafe extern "C" fn sz_orm_cpp_qb_limit(qb: SzOrmQueryBuilderHandle, limit: u64) -> i32 {
467 unsafe { sz_orm_cabi::sz_orm_qb_limit(qb, limit) }
469}
470
471#[no_mangle]
477pub unsafe extern "C" fn sz_orm_cpp_qb_build(qb: SzOrmQueryBuilderHandle) -> *mut c_char {
478 unsafe { sz_orm_cabi::sz_orm_qb_build(qb) }
480}
481
482#[no_mangle]
488pub unsafe extern "C" fn sz_orm_cpp_qb_free(qb: SzOrmQueryBuilderHandle) {
489 unsafe { sz_orm_cabi::sz_orm_qb_free(qb) }
491}
492
493#[no_mangle]
499pub unsafe extern "C" fn sz_orm_cpp_qb_result_free(result: *mut c_char) {
500 unsafe { sz_orm_cabi::sz_orm_qb_result_free(result) }
502}
503
504#[no_mangle]
514pub unsafe extern "C" fn sz_orm_cpp_active_model_create(
515 handle: SzOrmPoolHandle,
516 table: *const c_char,
517 fields_json: *const c_char,
518 values_json: *const c_char,
519) -> *mut QueryResultC {
520 if handle.is_null() || table.is_null() || fields_json.is_null() || values_json.is_null() {
521 return std::ptr::null_mut();
522 }
523 let result =
525 unsafe { sz_orm_cabi::sz_orm_model_insert(handle, table, fields_json, values_json) };
526 Box::into_raw(Box::new(result))
527}
528
529#[no_mangle]
535pub unsafe extern "C" fn sz_orm_cpp_active_model_set(
536 handle: SzOrmPoolHandle,
537 table: *const c_char,
538 set_json: *const c_char,
539 where_clause: *const c_char,
540 where_params_json: *const c_char,
541) -> *mut QueryResultC {
542 if handle.is_null() || table.is_null() || set_json.is_null() {
543 return std::ptr::null_mut();
544 }
545 let result = unsafe {
547 sz_orm_cabi::sz_orm_model_update(handle, table, set_json, where_clause, where_params_json)
548 };
549 Box::into_raw(Box::new(result))
550}
551
552#[no_mangle]
558pub unsafe extern "C" fn sz_orm_cpp_active_model_get(
559 handle: SzOrmPoolHandle,
560 table: *const c_char,
561 where_clause: *const c_char,
562 where_params_json: *const c_char,
563) -> *mut c_char {
564 if handle.is_null() || table.is_null() {
565 return std::ptr::null_mut();
566 }
567 unsafe { sz_orm_cabi::sz_orm_model_find(handle, table, where_clause, where_params_json) }
569}
570
571#[no_mangle]
577pub unsafe extern "C" fn sz_orm_cpp_active_model_save(
578 handle: SzOrmPoolHandle,
579 table: *const c_char,
580 fields_json: *const c_char,
581 values_json: *const c_char,
582) -> *mut QueryResultC {
583 if handle.is_null() || table.is_null() || fields_json.is_null() || values_json.is_null() {
584 return std::ptr::null_mut();
585 }
586 let result =
588 unsafe { sz_orm_cabi::sz_orm_model_insert(handle, table, fields_json, values_json) };
589 Box::into_raw(Box::new(result))
590}
591
592#[no_mangle]
598pub unsafe extern "C" fn sz_orm_cpp_active_model_delete(
599 handle: SzOrmPoolHandle,
600 table: *const c_char,
601 where_clause: *const c_char,
602 where_params_json: *const c_char,
603) -> *mut QueryResultC {
604 if handle.is_null() || table.is_null() {
605 return std::ptr::null_mut();
606 }
607 let result =
609 unsafe { sz_orm_cabi::sz_orm_model_delete(handle, table, where_clause, where_params_json) };
610 Box::into_raw(Box::new(result))
611}
612
613#[no_mangle]
619pub unsafe extern "C" fn sz_orm_cpp_pool_stats(handle: SzOrmPoolHandle) -> PoolStatsC {
620 unsafe { sz_orm_cabi::sz_orm_pool_stats(handle) }
622}
623
624#[no_mangle]
630pub unsafe extern "C" fn sz_orm_cpp_pool_metrics(handle: SzOrmPoolHandle) -> PoolMetricsC {
631 unsafe { sz_orm_cabi::sz_orm_pool_metrics(handle) }
633}
634
635#[no_mangle]
641pub unsafe extern "C" fn sz_orm_cpp_query_one(
642 handle: SzOrmPoolHandle,
643 sql: *const c_char,
644) -> *mut QueryJsonResult {
645 unsafe { sz_orm_cabi::sz_orm_query_one(handle, sql) }
647}
648
649#[no_mangle]
655pub unsafe extern "C" fn sz_orm_cpp_execute_batch(
656 handle: SzOrmPoolHandle,
657 sql: *const c_char,
658) -> QueryResultC {
659 unsafe { sz_orm_cabi::sz_orm_execute_batch(handle, sql) }
661}
662
663#[no_mangle]
669pub unsafe extern "C" fn sz_orm_cpp_query_result_free(result: *mut QueryJsonResult) {
670 unsafe { sz_orm_cabi::sz_orm_query_result_free(result) }
672}
673
674#[no_mangle]
680pub unsafe extern "C" fn sz_orm_cpp_error_description(code: i32) -> *mut c_char {
681 unsafe { sz_orm_cabi::sz_orm_error_description(code) }
683}
684
685#[no_mangle]
691pub unsafe extern "C" fn sz_orm_cpp_table_exists(
692 handle: SzOrmPoolHandle,
693 table: *const c_char,
694) -> i32 {
695 unsafe { sz_orm_cabi::sz_orm_table_exists(handle, table) }
697}
698
699#[no_mangle]
705pub unsafe extern "C" fn sz_orm_cpp_count(handle: SzOrmPoolHandle, table: *const c_char) -> i64 {
706 unsafe { sz_orm_cabi::sz_orm_count(handle, table) }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713
714 fn test_pool() -> SzOrmPoolHandle {
715 let dsn = CString::new("sqlite::memory:").unwrap();
716 unsafe { sz_orm_cpp_pool_new(dsn.as_ptr(), std::ptr::null()) }
718 }
719
720 #[test]
721 fn test_cpp_pool_new_creates_real_pool() {
722 let pool = test_pool();
723 assert!(!pool.is_null(), "pool_new should create a real pool");
724 unsafe { sz_orm_cpp_pool_free(pool) };
726 }
727
728 #[test]
729 fn test_cpp_ping_healthy() {
730 let pool = test_pool();
731 assert!(!pool.is_null());
732 let healthy = unsafe { sz_orm_cpp_ping(pool) };
734 assert_eq!(healthy, 1, "fresh pool should be healthy");
735 unsafe { sz_orm_cpp_pool_free(pool) };
737 }
738
739 #[test]
740 fn test_cpp_execute_and_query_roundtrip() {
741 let pool = test_pool();
742 assert!(!pool.is_null());
743
744 let create =
745 CString::new("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)").unwrap();
746 let rp = unsafe { sz_orm_cpp_execute(pool, create.as_ptr()) };
748 assert!(!rp.is_null(), "CREATE should succeed");
749 let r = unsafe { Box::from_raw(rp) };
751 assert_eq!(
752 r.success, 1,
753 "CREATE should succeed, got code {}",
754 r.error_code
755 );
756 unsafe { sz_orm_cpp_result_free(Box::into_raw(r)) };
758
759 let insert = CString::new("INSERT INTO users (name) VALUES ('Alice'), ('Bob')").unwrap();
760 let rp = unsafe { sz_orm_cpp_execute(pool, insert.as_ptr()) };
762 assert!(!rp.is_null(), "INSERT should succeed");
763 let r = unsafe { Box::from_raw(rp) };
765 assert_eq!(r.success, 1);
766 assert_eq!(r.rows_affected, 2, "two rows inserted");
767 unsafe { sz_orm_cpp_result_free(Box::into_raw(r)) };
769
770 let q = CString::new("SELECT id, name FROM users ORDER BY id").unwrap();
771 let json_ptr = unsafe { sz_orm_cpp_query(pool, q.as_ptr()) };
773 assert!(!json_ptr.is_null(), "query should return JSON");
774 let json = unsafe { CStr::from_ptr(json_ptr) }
776 .to_string_lossy()
777 .into_owned();
778 assert!(
779 json.contains("Alice"),
780 "query rows should contain Alice: {json}"
781 );
782 assert!(
783 json.contains("Bob"),
784 "query rows should contain Bob: {json}"
785 );
786 unsafe { sz_orm_cpp_string_free(json_ptr) };
788
789 unsafe { sz_orm_cpp_pool_free(pool) };
791 }
792
793 #[test]
794 fn test_cpp_query_invalid_sql_returns_null() {
795 let pool = test_pool();
796 assert!(!pool.is_null());
797 let q = CString::new("SELECT * FROM nonexistent").unwrap();
798 let json_ptr = unsafe { sz_orm_cpp_query(pool, q.as_ptr()) };
800 assert!(json_ptr.is_null(), "invalid SQL should return null");
801 unsafe { sz_orm_cpp_pool_free(pool) };
803 }
804
805 #[test]
806 fn test_cpp_null_args() {
807 let pool = unsafe { sz_orm_cpp_pool_new(std::ptr::null(), std::ptr::null()) };
809 assert!(pool.is_null());
810 let r = unsafe { sz_orm_cpp_execute(std::ptr::null_mut(), std::ptr::null()) };
812 assert!(r.is_null());
813 let q = unsafe { sz_orm_cpp_query(std::ptr::null_mut(), std::ptr::null()) };
815 assert!(q.is_null());
816 }
817
818 #[test]
819 fn test_cpp_version() {
820 let v = sz_orm_cpp_version();
821 assert!(v >= 1, "version should be >= 1, got {v}");
822 }
823
824 #[test]
825 fn test_cpp_free_null_safe() {
826 unsafe { sz_orm_cpp_string_free(std::ptr::null_mut()) };
828 unsafe { sz_orm_cpp_result_free(std::ptr::null_mut()) };
830 }
831
832 #[test]
833 fn test_cpp_ping_after_free_returns_zero() {
834 let healthy = unsafe { sz_orm_cpp_ping(std::ptr::null_mut()) };
837 assert_eq!(healthy, 0, "null pool should not be healthy");
838 }
839
840 #[test]
841 fn test_cpp_multiple_pools_independent() {
842 let dsn1 = CString::new("sqlite::memory:").unwrap();
843 let pool1 = unsafe { sz_orm_cpp_pool_new(dsn1.as_ptr(), std::ptr::null()) };
845 let dsn2 = CString::new("sqlite::memory:").unwrap();
846 let pool2 = unsafe { sz_orm_cpp_pool_new(dsn2.as_ptr(), std::ptr::null()) };
848 assert!(!pool1.is_null());
849 assert!(!pool2.is_null());
850 assert_ne!(pool1, pool2, "pools should have distinct handles");
851 unsafe {
853 sz_orm_cpp_pool_free(pool1);
854 sz_orm_cpp_pool_free(pool2);
855 }
856 }
857
858 #[test]
859 fn test_cpp_execute_empty_sql() {
860 let pool = test_pool();
861 assert!(!pool.is_null());
862 let create = CString::new("CREATE TABLE cpp_e (id INTEGER PRIMARY KEY)").unwrap();
863 let r = unsafe { sz_orm_cpp_execute(pool, create.as_ptr()) };
865 unsafe { sz_orm_cpp_result_free(r) };
867 unsafe { sz_orm_cpp_pool_free(pool) };
869 }
870
871 #[test]
872 fn test_cpp_query_empty_table() {
873 let pool = test_pool();
874 assert!(!pool.is_null());
875 let create = CString::new("CREATE TABLE empty_t (id INTEGER PRIMARY KEY)").unwrap();
876 let r = unsafe { sz_orm_cpp_execute(pool, create.as_ptr()) };
878 unsafe { sz_orm_cpp_result_free(r) };
880 let q = CString::new("SELECT id FROM empty_t").unwrap();
881 let json_ptr = unsafe { sz_orm_cpp_query(pool, q.as_ptr()) };
883 assert!(
884 !json_ptr.is_null(),
885 "empty table query should still return JSON"
886 );
887 let json = unsafe { CStr::from_ptr(json_ptr) }
889 .to_string_lossy()
890 .into_owned();
891 assert!(
892 json.contains("[]") || json.is_empty() || json.contains("id"),
893 "empty result should be empty array: {json}"
894 );
895 unsafe { sz_orm_cpp_string_free(json_ptr) };
897 unsafe { sz_orm_cpp_pool_free(pool) };
899 }
900
901 fn model_test_pool() -> SzOrmPoolHandle {
904 let pool = test_pool();
905 assert!(!pool.is_null());
906 let create =
907 CString::new("CREATE TABLE cm_t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
908 .unwrap();
909 let r = unsafe { sz_orm_cpp_execute(pool, create.as_ptr()) };
911 unsafe { sz_orm_cpp_result_free(r) };
913 pool
914 }
915
916 unsafe fn free_cpp_result(ptr: *mut QueryResultC) -> QueryResultC {
917 if ptr.is_null() {
918 return QueryResultC {
919 success: 0,
920 error_code: sz_orm_cabi::SzOrmErrorCode::InvalidArgument.as_i32(),
921 rows_affected: 0,
922 last_insert_id: 0,
923 };
924 }
925 let r = unsafe { Box::from_raw(ptr) };
927 *r
928 }
929
930 unsafe fn free_cpp_str(ptr: *mut c_char) -> String {
931 if ptr.is_null() {
932 return String::new();
933 }
934 let s = unsafe { CStr::from_ptr(ptr) }
936 .to_string_lossy()
937 .into_owned();
938 unsafe { sz_orm_cpp_string_free(ptr) };
940 s
941 }
942
943 #[test]
944 fn test_cpp_model_insert_find_roundtrip() {
945 let pool = model_test_pool();
946 let table = CString::new("cm_t").unwrap();
947 let fields = CString::new(r#"["name","age"]"#).unwrap();
948 let values = CString::new(r#"["Alice",30]"#).unwrap();
949 let r = unsafe {
951 sz_orm_cpp_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
952 };
953 let r = unsafe { free_cpp_result(r) };
955 assert_eq!(
956 r.success, 1,
957 "cpp model_insert should succeed, code={}",
958 r.error_code
959 );
960 assert_eq!(r.rows_affected, 1);
961
962 let where_clause = CString::new("name = ?").unwrap();
963 let where_params = CString::new(r#"["Alice"]"#).unwrap();
964 let ptr = unsafe {
966 sz_orm_cpp_model_find(
967 pool,
968 table.as_ptr(),
969 where_clause.as_ptr(),
970 where_params.as_ptr(),
971 )
972 };
973 assert!(!ptr.is_null());
974 let json = unsafe { free_cpp_str(ptr) };
976 assert!(
977 json.contains("Alice"),
978 "cpp model_find should contain Alice: {json}"
979 );
980 unsafe { sz_orm_cpp_pool_free(pool) };
982 }
983
984 #[test]
985 fn test_cpp_model_update_delete_find() {
986 let pool = model_test_pool();
987 let table = CString::new("cm_t").unwrap();
988 let fields = CString::new(r#"["name","age"]"#).unwrap();
989 let values = CString::new(r#"["Bob",25]"#).unwrap();
990 let r = unsafe {
992 sz_orm_cpp_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
993 };
994 unsafe { free_cpp_result(r) };
996
997 let set_json = CString::new(r#"{"age":26}"#).unwrap();
998 let where_clause = CString::new("name = ?").unwrap();
999 let where_params = CString::new(r#"["Bob"]"#).unwrap();
1000 let r = unsafe {
1002 sz_orm_cpp_model_update(
1003 pool,
1004 table.as_ptr(),
1005 set_json.as_ptr(),
1006 where_clause.as_ptr(),
1007 where_params.as_ptr(),
1008 )
1009 };
1010 let r = unsafe { free_cpp_result(r) };
1012 assert_eq!(
1013 r.success, 1,
1014 "cpp model_update should succeed, code={}",
1015 r.error_code
1016 );
1017
1018 let ptr = unsafe {
1020 sz_orm_cpp_model_find(
1021 pool,
1022 table.as_ptr(),
1023 where_clause.as_ptr(),
1024 where_params.as_ptr(),
1025 )
1026 };
1027 let json = unsafe { free_cpp_str(ptr) };
1029 assert!(
1030 json.contains("26"),
1031 "cpp find after update should contain age 26: {json}"
1032 );
1033
1034 let r = unsafe {
1036 sz_orm_cpp_model_delete(
1037 pool,
1038 table.as_ptr(),
1039 where_clause.as_ptr(),
1040 where_params.as_ptr(),
1041 )
1042 };
1043 let r = unsafe { free_cpp_result(r) };
1045 assert_eq!(
1046 r.success, 1,
1047 "cpp model_delete should succeed, code={}",
1048 r.error_code
1049 );
1050
1051 let ptr = unsafe {
1053 sz_orm_cpp_model_find(
1054 pool,
1055 table.as_ptr(),
1056 where_clause.as_ptr(),
1057 where_params.as_ptr(),
1058 )
1059 };
1060 let json = unsafe { free_cpp_str(ptr) };
1062 assert!(
1063 json == "[]" || json == "null" || json.is_empty(),
1064 "cpp find after delete should be empty: {json}"
1065 );
1066 unsafe { sz_orm_cpp_pool_free(pool) };
1068 }
1069
1070 #[test]
1071 fn test_cpp_model_insert_illegal_table() {
1072 let pool = model_test_pool();
1073 let table = CString::new("cm_t; DROP--").unwrap();
1074 let fields = CString::new(r#"["name"]"#).unwrap();
1075 let values = CString::new(r#"["X"]"#).unwrap();
1076 let r = unsafe {
1078 sz_orm_cpp_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
1079 };
1080 let r = unsafe { free_cpp_result(r) };
1082 assert_eq!(r.success, 0, "illegal table should fail");
1083 assert_eq!(
1084 r.error_code,
1085 sz_orm_cabi::SzOrmErrorCode::InvalidArgument.as_i32()
1086 );
1087 unsafe { sz_orm_cpp_pool_free(pool) };
1089 }
1090
1091 #[test]
1092 fn test_cpp_transaction_model_rollback() {
1093 let pool = model_test_pool();
1094 let tx = unsafe { sz_orm_cpp_transaction_begin(pool) };
1096 assert!(!tx.is_null());
1097
1098 let table = CString::new("cm_t").unwrap();
1099 let fields = CString::new(r#"["name","age"]"#).unwrap();
1100 let values = CString::new(r#"["TxUser",99]"#).unwrap();
1101 let r = unsafe {
1103 sz_orm_cpp_model_insert_tx(tx, table.as_ptr(), fields.as_ptr(), values.as_ptr())
1104 };
1105 let r = unsafe { free_cpp_result(r) };
1107 assert_eq!(
1108 r.success, 1,
1109 "cpp model_insert_tx should succeed, code={}",
1110 r.error_code
1111 );
1112
1113 assert_eq!(unsafe { sz_orm_cpp_transaction_rollback(tx) }, 1);
1115 unsafe { sz_orm_cpp_transaction_free(tx) };
1117
1118 let where_clause = CString::new("name = ?").unwrap();
1119 let where_params = CString::new(r#"["TxUser"]"#).unwrap();
1120 let ptr = unsafe {
1122 sz_orm_cpp_model_find(
1123 pool,
1124 table.as_ptr(),
1125 where_clause.as_ptr(),
1126 where_params.as_ptr(),
1127 )
1128 };
1129 let json = unsafe { free_cpp_str(ptr) };
1131 assert!(
1132 json == "[]" || json == "null" || json.is_empty(),
1133 "cpp find after rollback should be empty: {json}"
1134 );
1135 unsafe { sz_orm_cpp_pool_free(pool) };
1137 }
1138
1139 #[test]
1140 fn test_cpp_transaction_model_commit() {
1141 let pool = model_test_pool();
1142 let tx = unsafe { sz_orm_cpp_transaction_begin(pool) };
1144 assert!(!tx.is_null());
1145
1146 let table = CString::new("cm_t").unwrap();
1147 let fields = CString::new(r#"["name","age"]"#).unwrap();
1148 let values = CString::new(r#"["CommitUser",50]"#).unwrap();
1149 let r = unsafe {
1151 sz_orm_cpp_model_insert_tx(tx, table.as_ptr(), fields.as_ptr(), values.as_ptr())
1152 };
1153 unsafe { free_cpp_result(r) };
1155
1156 assert_eq!(unsafe { sz_orm_cpp_transaction_commit(tx) }, 1);
1158 unsafe { sz_orm_cpp_transaction_free(tx) };
1160
1161 let where_clause = CString::new("name = ?").unwrap();
1162 let where_params = CString::new(r#"["CommitUser"]"#).unwrap();
1163 let ptr = unsafe {
1165 sz_orm_cpp_model_find(
1166 pool,
1167 table.as_ptr(),
1168 where_clause.as_ptr(),
1169 where_params.as_ptr(),
1170 )
1171 };
1172 let json = unsafe { free_cpp_str(ptr) };
1174 assert!(
1175 json.contains("CommitUser"),
1176 "cpp find after commit should contain CommitUser: {json}"
1177 );
1178 unsafe { sz_orm_cpp_pool_free(pool) };
1180 }
1181
1182 #[test]
1185 fn test_cpp_qb_new_and_free() {
1186 let qb = unsafe { sz_orm_cpp_qb_new(0) };
1188 assert!(!qb.is_null(), "qb_new should return non-null handle");
1189 unsafe { sz_orm_cpp_qb_free(qb) };
1191 }
1192
1193 #[test]
1194 fn test_cpp_qb_full_chain() {
1195 let qb = unsafe { sz_orm_cpp_qb_new(0) };
1197 assert!(!qb.is_null());
1198
1199 let table = CString::new("users").unwrap();
1200 assert_eq!(
1202 unsafe { sz_orm_cpp_qb_table(qb, table.as_ptr()) },
1203 sz_orm_cabi::SzOrmErrorCode::Ok.as_i32()
1204 );
1205
1206 let field = CString::new("status").unwrap();
1207 let value = CString::new(r#""active""#).unwrap();
1208 assert_eq!(
1210 unsafe { sz_orm_cpp_qb_where_eq(qb, field.as_ptr(), value.as_ptr()) },
1211 sz_orm_cabi::SzOrmErrorCode::Ok.as_i32()
1212 );
1213
1214 let order_field = CString::new("id").unwrap();
1215 assert_eq!(
1217 unsafe { sz_orm_cpp_qb_order_by(qb, order_field.as_ptr(), 1) },
1218 sz_orm_cabi::SzOrmErrorCode::Ok.as_i32()
1219 );
1220
1221 assert_eq!(
1223 unsafe { sz_orm_cpp_qb_limit(qb, 10) },
1224 sz_orm_cabi::SzOrmErrorCode::Ok.as_i32()
1225 );
1226
1227 let result_ptr = unsafe { sz_orm_cpp_qb_build(qb) };
1229 assert!(!result_ptr.is_null(), "qb_build should return JSON");
1230 let json = unsafe { CStr::from_ptr(result_ptr) }
1232 .to_string_lossy()
1233 .into_owned();
1234 unsafe { sz_orm_cpp_qb_result_free(result_ptr) };
1236
1237 assert!(json.contains("users"), "SQL should contain table: {json}");
1238 assert!(
1239 json.contains("status"),
1240 "SQL should contain where field: {json}"
1241 );
1242
1243 unsafe { sz_orm_cpp_qb_free(qb) };
1245 }
1246
1247 #[test]
1248 fn test_cpp_qb_invalid_db_type() {
1249 let qb = unsafe { sz_orm_cpp_qb_new(999) };
1251 assert!(qb.is_null(), "invalid db_type should return null");
1252 }
1253
1254 #[test]
1255 fn test_cpp_qb_free_null_is_noop() {
1256 unsafe { sz_orm_cpp_qb_free(std::ptr::null_mut()) };
1258 }
1259
1260 #[test]
1261 fn test_cpp_active_model_create_get_delete() {
1262 let pool = model_test_pool();
1263
1264 let table = CString::new("cm_t").unwrap();
1265 let fields = CString::new(r#"["name","age"]"#).unwrap();
1266 let values = CString::new(r#"["AMUser",42]"#).unwrap();
1267 let r = unsafe {
1269 sz_orm_cpp_active_model_create(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
1270 };
1271 let r = unsafe { free_cpp_result(r) };
1273 assert_eq!(r.success, 1, "active_model create should succeed");
1274
1275 let where_clause = CString::new("name = ?").unwrap();
1276 let where_params = CString::new(r#"["AMUser"]"#).unwrap();
1277 let ptr = unsafe {
1279 sz_orm_cpp_active_model_get(
1280 pool,
1281 table.as_ptr(),
1282 where_clause.as_ptr(),
1283 where_params.as_ptr(),
1284 )
1285 };
1286 let json = unsafe { free_cpp_str(ptr) };
1288 assert!(json.contains("AMUser"), "get should find AMUser: {json}");
1289
1290 let r = unsafe {
1292 sz_orm_cpp_active_model_delete(
1293 pool,
1294 table.as_ptr(),
1295 where_clause.as_ptr(),
1296 where_params.as_ptr(),
1297 )
1298 };
1299 let r = unsafe { free_cpp_result(r) };
1301 assert_eq!(r.success, 1, "active_model delete should succeed");
1302
1303 unsafe { sz_orm_cpp_pool_free(pool) };
1305 }
1306
1307 #[test]
1308 fn test_cpp_active_model_set_update() {
1309 let pool = model_test_pool();
1310
1311 let table = CString::new("cm_t").unwrap();
1312 let fields = CString::new(r#"["name","age"]"#).unwrap();
1313 let values = CString::new(r#"["SetUser",20]"#).unwrap();
1314 let r = unsafe {
1316 sz_orm_cpp_active_model_save(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
1317 };
1318 unsafe { free_cpp_result(r) };
1320
1321 let set_json = CString::new(r#"{"age":21}"#).unwrap();
1322 let where_clause = CString::new("name = ?").unwrap();
1323 let where_params = CString::new(r#"["SetUser"]"#).unwrap();
1324 let r = unsafe {
1326 sz_orm_cpp_active_model_set(
1327 pool,
1328 table.as_ptr(),
1329 set_json.as_ptr(),
1330 where_clause.as_ptr(),
1331 where_params.as_ptr(),
1332 )
1333 };
1334 let r = unsafe { free_cpp_result(r) };
1336 assert_eq!(r.success, 1, "active_model set should succeed");
1337
1338 let ptr = unsafe {
1340 sz_orm_cpp_active_model_get(
1341 pool,
1342 table.as_ptr(),
1343 where_clause.as_ptr(),
1344 where_params.as_ptr(),
1345 )
1346 };
1347 let json = unsafe { free_cpp_str(ptr) };
1349 assert!(
1350 json.contains("21"),
1351 "get after set should contain age 21: {json}"
1352 );
1353
1354 unsafe { sz_orm_cpp_pool_free(pool) };
1356 }
1357}