Skip to main content

sz_orm_java/
lib.rs

1//! # SZ-ORM Java Bindings
2//!
3//! 通过 JNI 调用 sz-orm-cabi 的 C ABI,为 Java 提供 sz-orm-core 的
4//! Pool/Query API(SQLite 后端,真实可用)。
5//!
6//! JNI 符号名遵循 javac -h 生成的 `sz_orm_java_SzOrmPool.h`:
7//! 包名 `sz_orm_java` 中的下划线转义为 `_1`。
8
9use jni::objects::{JClass, JString};
10use jni::sys::{jint, jlong, jstring};
11use jni::EnvUnowned;
12
13use std::ffi::CString;
14
15/// JNI 入口:创建连接池(真实创建,SQLite 后端)
16///
17/// 返回句柄(jlong),0 表示失败。
18#[no_mangle]
19pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_poolNew<'local>(
20    mut unowned_env: EnvUnowned<'local>,
21    _class: JClass<'local>,
22    dsn: JString<'local>,
23    max_connections: jint,
24) -> jlong {
25    unowned_env
26        .with_env(|_env| -> jni::errors::Result<jlong> {
27            let dsn_str: String = dsn.to_string();
28            let c_dsn = CString::new(dsn_str).map_err(|_| jni::errors::Error::JavaException)?;
29            let config = sz_orm_cabi::PoolConfigC {
30                max_connections: max_connections.max(1) as u32,
31                min_connections: 1,
32                connect_timeout_ms: 5000,
33                idle_timeout_ms: 300000,
34            };
35            // SAFETY: c_dsn 是有效的 NUL 结尾 C 字符串
36            let handle = unsafe { sz_orm_cabi::sz_orm_pool_new(c_dsn.as_ptr(), &config) };
37            Ok(handle as jlong)
38        })
39        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
40}
41
42/// JNI 入口:释放连接池
43///
44/// # Safety
45///
46/// SAFETY: handle 必须是 `poolNew` 返回的有效句柄。
47#[no_mangle]
48pub unsafe extern "system" fn Java_sz_1orm_1java_SzOrmPool_poolFree<'local>(
49    mut unowned_env: EnvUnowned<'local>,
50    _class: JClass<'local>,
51    handle: jlong,
52) {
53    if handle == 0 {
54        return;
55    }
56    let _ = unowned_env.with_env(|_env| -> jni::errors::Result<()> {
57        // SAFETY: 调用方保证 handle 来自 poolNew
58        unsafe {
59            sz_orm_cabi::sz_orm_pool_free(handle as sz_orm_cabi::SzOrmPoolHandle);
60        }
61        Ok(())
62    });
63}
64
65/// JNI 入口:健康检查(真实 acquire + ping)
66///
67/// # Safety
68///
69/// SAFETY: `handle` 必须是 `poolNew` 返回的有效句柄,且未被 `poolFree` 释放。
70#[no_mangle]
71pub unsafe extern "system" fn Java_sz_1orm_1java_SzOrmPool_ping<'local>(
72    mut unowned_env: EnvUnowned<'local>,
73    _class: JClass<'local>,
74    handle: jlong,
75) -> jint {
76    if handle == 0 {
77        return 0;
78    }
79    unowned_env
80        .with_env(|_env| -> jni::errors::Result<jint> {
81            // SAFETY: 调用方保证 handle 来自 poolNew
82            let healthy =
83                unsafe { sz_orm_cabi::sz_orm_ping(handle as sz_orm_cabi::SzOrmPoolHandle) };
84            Ok(healthy)
85        })
86        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
87}
88
89/// JNI 入口:执行查询,返回 JSON 行数组字符串
90///
91/// 失败返回 null(Java 侧抛 IllegalStateException)。
92///
93/// # Safety
94///
95/// SAFETY: `handle` 必须是 `poolNew` 返回的有效句柄,且未被 `poolFree` 释放;
96/// `sql` 必须是 JNI 传入的有效 `JString`。
97#[no_mangle]
98pub unsafe extern "system" fn Java_sz_1orm_1java_SzOrmPool_query<'local>(
99    mut unowned_env: EnvUnowned<'local>,
100    _class: JClass<'local>,
101    handle: jlong,
102    sql: JString<'local>,
103) -> jstring {
104    if handle == 0 {
105        return std::ptr::null_mut();
106    }
107    unowned_env
108        .with_env(|env| -> jni::errors::Result<jstring> {
109            let sql_str: String = sql.to_string();
110            let c_sql = CString::new(sql_str).map_err(|_| jni::errors::Error::JavaException)?;
111            // SAFETY: handle 来自 poolNew,c_sql 有效
112            let result = unsafe {
113                sz_orm_cabi::sz_orm_query(handle as sz_orm_cabi::SzOrmPoolHandle, c_sql.as_ptr())
114            };
115            if result.is_null() {
116                return Ok(std::ptr::null_mut());
117            }
118            // SAFETY: result 由 sz_orm_query 分配
119            let result_box = unsafe { Box::from_raw(result) };
120            let json = if result_box.success != 0 && !result_box.json.is_null() {
121                // SAFETY: json 是有效 NUL 结尾 C 字符串
122                unsafe { std::ffi::CStr::from_ptr(result_box.json) }
123                    .to_str()
124                    .map(|s| s.to_string())
125                    .unwrap_or_default()
126            } else {
127                String::new()
128            };
129            // SAFETY: result_box 由 sz_orm_query 分配,配对释放
130            unsafe {
131                sz_orm_cabi::sz_orm_query_result_free(Box::into_raw(result_box));
132            }
133            let jstr = env.new_string(&json)?;
134            Ok(jstr.into_raw())
135        })
136        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
137}
138
139/// JNI 入口:执行写语句,返回影响行数(-1 表示失败)
140///
141/// # Safety
142///
143/// SAFETY: `handle` 必须是 `poolNew` 返回的有效句柄,且未被 `poolFree` 释放;
144/// `sql` 必须是 JNI 传入的有效 `JString`。
145#[no_mangle]
146pub unsafe extern "system" fn Java_sz_1orm_1java_SzOrmPool_execute<'local>(
147    mut unowned_env: EnvUnowned<'local>,
148    _class: JClass<'local>,
149    handle: jlong,
150    sql: JString<'local>,
151) -> jlong {
152    if handle == 0 {
153        return -1;
154    }
155    unowned_env
156        .with_env(|_env| -> jni::errors::Result<jlong> {
157            let sql_str: String = sql.to_string();
158            let c_sql = CString::new(sql_str).map_err(|_| jni::errors::Error::JavaException)?;
159            // SAFETY: handle 来自 poolNew,c_sql 有效
160            let result = unsafe {
161                sz_orm_cabi::sz_orm_execute(handle as sz_orm_cabi::SzOrmPoolHandle, c_sql.as_ptr())
162            };
163            if result.success == 0 {
164                Ok(-1)
165            } else {
166                Ok(result.rows_affected as jlong)
167            }
168        })
169        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
170}
171
172/// JNI 入口:绑定版本号
173#[no_mangle]
174pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_version<'local>(
175    mut unowned_env: EnvUnowned<'local>,
176    _class: JClass<'local>,
177) -> jint {
178    unowned_env
179        .with_env(|_env| -> jni::errors::Result<jint> { Ok(sz_orm_cabi::sz_orm_version() as jint) })
180        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
181}
182
183// ============================================================================
184// 事务 JNI 入口(REQ-BND-006)
185// ============================================================================
186
187/// JNI 入口:开始事务,返回事务句柄(0 表示失败)
188#[no_mangle]
189pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_beginTransaction<'local>(
190    mut unowned_env: EnvUnowned<'local>,
191    _class: JClass<'local>,
192    pool_handle: jlong,
193) -> jlong {
194    if pool_handle == 0 {
195        return 0;
196    }
197    unowned_env
198        .with_env(|_env| -> jni::errors::Result<jlong> {
199            // SAFETY: 调用方保证 pool_handle 来自 poolNew
200            let tx = unsafe {
201                sz_orm_cabi::sz_orm_transaction_begin(pool_handle as sz_orm_cabi::SzOrmPoolHandle)
202            };
203            Ok(tx as jlong)
204        })
205        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
206}
207
208/// JNI 入口:提交事务,返回 1=成功 0=失败
209#[no_mangle]
210pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_commitTransaction<'local>(
211    mut unowned_env: EnvUnowned<'local>,
212    _class: JClass<'local>,
213    tx_handle: jlong,
214) -> jint {
215    if tx_handle == 0 {
216        return 0;
217    }
218    unowned_env
219        .with_env(|_env| -> jni::errors::Result<jint> {
220            // SAFETY: 调用方保证 tx_handle 来自 beginTransaction
221            let r = unsafe {
222                sz_orm_cabi::sz_orm_transaction_commit(
223                    tx_handle as sz_orm_cabi::SzOrmTransactionHandle,
224                )
225            };
226            Ok(r)
227        })
228        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
229}
230
231/// JNI 入口:回滚事务,返回 1=成功 0=失败
232#[no_mangle]
233pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_rollbackTransaction<'local>(
234    mut unowned_env: EnvUnowned<'local>,
235    _class: JClass<'local>,
236    tx_handle: jlong,
237) -> jint {
238    if tx_handle == 0 {
239        return 0;
240    }
241    unowned_env
242        .with_env(|_env| -> jni::errors::Result<jint> {
243            // SAFETY: 调用方保证 tx_handle 来自 beginTransaction
244            let r = unsafe {
245                sz_orm_cabi::sz_orm_transaction_rollback(
246                    tx_handle as sz_orm_cabi::SzOrmTransactionHandle,
247                )
248            };
249            Ok(r)
250        })
251        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
252}
253
254/// JNI 入口:释放事务句柄(若仍活跃则自动回滚)
255///
256/// # Safety
257///
258/// SAFETY: `tx_handle` 必须是 `beginTransaction` 返回的有效句柄。
259#[no_mangle]
260pub unsafe extern "system" fn Java_sz_1orm_1java_SzOrmPool_freeTransaction<'local>(
261    mut unowned_env: EnvUnowned<'local>,
262    _class: JClass<'local>,
263    tx_handle: jlong,
264) {
265    if tx_handle == 0 {
266        return;
267    }
268    let _ = unowned_env.with_env(|_env| -> jni::errors::Result<()> {
269        // SAFETY: 调用方保证 tx_handle 来自 beginTransaction
270        unsafe {
271            sz_orm_cabi::sz_orm_transaction_free(tx_handle as sz_orm_cabi::SzOrmTransactionHandle);
272        }
273        Ok(())
274    });
275}
276
277// ============================================================================
278// 模型级 JNI 入口(REQ-BND-013)
279// ============================================================================
280
281/// JNI 入口:在 pool 上插入行,返回受影响行数(-1 表示失败)
282#[no_mangle]
283pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_modelInsert<'local>(
284    mut unowned_env: EnvUnowned<'local>,
285    _class: JClass<'local>,
286    pool_handle: jlong,
287    table: JString<'local>,
288    fields_json: JString<'local>,
289    values_json: JString<'local>,
290) -> jlong {
291    if pool_handle == 0 {
292        return -1;
293    }
294    unowned_env
295        .with_env(|_env| -> jni::errors::Result<jlong> {
296            let table_s: String = table.to_string();
297            let fields_s: String = fields_json.to_string();
298            let values_s: String = values_json.to_string();
299            let c_table = CString::new(table_s).map_err(|_| jni::errors::Error::JavaException)?;
300            let c_fields = CString::new(fields_s).map_err(|_| jni::errors::Error::JavaException)?;
301            let c_values = CString::new(values_s).map_err(|_| jni::errors::Error::JavaException)?;
302            // SAFETY: pool_handle 来自 poolNew,C 字符串有效
303            let r = unsafe {
304                sz_orm_cabi::sz_orm_model_insert(
305                    pool_handle as sz_orm_cabi::SzOrmPoolHandle,
306                    c_table.as_ptr(),
307                    c_fields.as_ptr(),
308                    c_values.as_ptr(),
309                )
310            };
311            if r.success == 0 {
312                Ok(-1)
313            } else {
314                Ok(r.rows_affected as jlong)
315            }
316        })
317        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
318}
319
320/// JNI 入口:在 pool 上更新行,返回受影响行数(-1 表示失败)
321#[no_mangle]
322pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_modelUpdate<'local>(
323    mut unowned_env: EnvUnowned<'local>,
324    _class: JClass<'local>,
325    pool_handle: jlong,
326    table: JString<'local>,
327    set_json: JString<'local>,
328    where_clause: JString<'local>,
329    where_params_json: JString<'local>,
330) -> jlong {
331    if pool_handle == 0 {
332        return -1;
333    }
334    unowned_env
335        .with_env(|_env| -> jni::errors::Result<jlong> {
336            let table_s: String = table.to_string();
337            let set_s: String = set_json.to_string();
338            let where_s: String = where_clause.to_string();
339            let where_params_s: String = where_params_json.to_string();
340            let c_table = CString::new(table_s).map_err(|_| jni::errors::Error::JavaException)?;
341            let c_set = CString::new(set_s).map_err(|_| jni::errors::Error::JavaException)?;
342            let c_where = CString::new(where_s).map_err(|_| jni::errors::Error::JavaException)?;
343            let c_where_params =
344                CString::new(where_params_s).map_err(|_| jni::errors::Error::JavaException)?;
345            // SAFETY: pool_handle 来自 poolNew,C 字符串有效
346            let r = unsafe {
347                sz_orm_cabi::sz_orm_model_update(
348                    pool_handle as sz_orm_cabi::SzOrmPoolHandle,
349                    c_table.as_ptr(),
350                    c_set.as_ptr(),
351                    c_where.as_ptr(),
352                    c_where_params.as_ptr(),
353                )
354            };
355            if r.success == 0 {
356                Ok(-1)
357            } else {
358                Ok(r.rows_affected as jlong)
359            }
360        })
361        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
362}
363
364/// JNI 入口:在 pool 上删除行,返回受影响行数(-1 表示失败)
365#[no_mangle]
366pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_modelDelete<'local>(
367    mut unowned_env: EnvUnowned<'local>,
368    _class: JClass<'local>,
369    pool_handle: jlong,
370    table: JString<'local>,
371    where_clause: JString<'local>,
372    where_params_json: JString<'local>,
373) -> jlong {
374    if pool_handle == 0 {
375        return -1;
376    }
377    unowned_env
378        .with_env(|_env| -> jni::errors::Result<jlong> {
379            let table_s: String = table.to_string();
380            let where_s: String = where_clause.to_string();
381            let where_params_s: String = where_params_json.to_string();
382            let c_table = CString::new(table_s).map_err(|_| jni::errors::Error::JavaException)?;
383            let c_where = CString::new(where_s).map_err(|_| jni::errors::Error::JavaException)?;
384            let c_where_params =
385                CString::new(where_params_s).map_err(|_| jni::errors::Error::JavaException)?;
386            // SAFETY: pool_handle 来自 poolNew,C 字符串有效
387            let r = unsafe {
388                sz_orm_cabi::sz_orm_model_delete(
389                    pool_handle as sz_orm_cabi::SzOrmPoolHandle,
390                    c_table.as_ptr(),
391                    c_where.as_ptr(),
392                    c_where_params.as_ptr(),
393                )
394            };
395            if r.success == 0 {
396                Ok(-1)
397            } else {
398                Ok(r.rows_affected as jlong)
399            }
400        })
401        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
402}
403
404/// JNI 入口:在 pool 上查询行,返回 JSON 行数组字符串(失败返回空字符串)
405#[no_mangle]
406pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_modelFind<'local>(
407    mut unowned_env: EnvUnowned<'local>,
408    _class: JClass<'local>,
409    pool_handle: jlong,
410    table: JString<'local>,
411    where_clause: JString<'local>,
412    where_params_json: JString<'local>,
413) -> jstring {
414    if pool_handle == 0 {
415        return std::ptr::null_mut();
416    }
417    unowned_env
418        .with_env(|env| -> jni::errors::Result<jstring> {
419            let table_s: String = table.to_string();
420            let where_s: String = where_clause.to_string();
421            let where_params_s: String = where_params_json.to_string();
422            let c_table = CString::new(table_s).map_err(|_| jni::errors::Error::JavaException)?;
423            let c_where = CString::new(where_s).map_err(|_| jni::errors::Error::JavaException)?;
424            let c_where_params =
425                CString::new(where_params_s).map_err(|_| jni::errors::Error::JavaException)?;
426            // SAFETY: pool_handle 来自 poolNew,C 字符串有效
427            let ptr = unsafe {
428                sz_orm_cabi::sz_orm_model_find(
429                    pool_handle as sz_orm_cabi::SzOrmPoolHandle,
430                    c_table.as_ptr(),
431                    c_where.as_ptr(),
432                    c_where_params.as_ptr(),
433                )
434            };
435            let json = if ptr.is_null() {
436                String::new()
437            } else {
438                // SAFETY: ptr 有效
439                let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
440                    .to_str()
441                    .map(|s| s.to_string())
442                    .unwrap_or_default();
443                // SAFETY: ptr 配对释放
444                unsafe { sz_orm_cabi::sz_orm_string_free(ptr) };
445                s
446            };
447            let jstr = env.new_string(&json)?;
448            Ok(jstr.into_raw())
449        })
450        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
451}
452
453/// JNI 入口:在事务内插入行,返回受影响行数(-1 表示失败)
454#[no_mangle]
455pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_modelInsertTx<'local>(
456    mut unowned_env: EnvUnowned<'local>,
457    _class: JClass<'local>,
458    tx_handle: jlong,
459    table: JString<'local>,
460    fields_json: JString<'local>,
461    values_json: JString<'local>,
462) -> jlong {
463    if tx_handle == 0 {
464        return -1;
465    }
466    unowned_env
467        .with_env(|_env| -> jni::errors::Result<jlong> {
468            let table_s: String = table.to_string();
469            let fields_s: String = fields_json.to_string();
470            let values_s: String = values_json.to_string();
471            let c_table = CString::new(table_s).map_err(|_| jni::errors::Error::JavaException)?;
472            let c_fields = CString::new(fields_s).map_err(|_| jni::errors::Error::JavaException)?;
473            let c_values = CString::new(values_s).map_err(|_| jni::errors::Error::JavaException)?;
474            // SAFETY: tx_handle 来自 beginTransaction,C 字符串有效
475            let r = unsafe {
476                sz_orm_cabi::sz_orm_model_insert_tx(
477                    tx_handle as sz_orm_cabi::SzOrmTransactionHandle,
478                    c_table.as_ptr(),
479                    c_fields.as_ptr(),
480                    c_values.as_ptr(),
481                )
482            };
483            if r.success == 0 {
484                Ok(-1)
485            } else {
486                Ok(r.rows_affected as jlong)
487            }
488        })
489        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
490}
491
492/// JNI 入口:在事务内更新行,返回受影响行数(-1 表示失败)
493#[no_mangle]
494pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_modelUpdateTx<'local>(
495    mut unowned_env: EnvUnowned<'local>,
496    _class: JClass<'local>,
497    tx_handle: jlong,
498    table: JString<'local>,
499    set_json: JString<'local>,
500    where_clause: JString<'local>,
501    where_params_json: JString<'local>,
502) -> jlong {
503    if tx_handle == 0 {
504        return -1;
505    }
506    unowned_env
507        .with_env(|_env| -> jni::errors::Result<jlong> {
508            let table_s: String = table.to_string();
509            let set_s: String = set_json.to_string();
510            let where_s: String = where_clause.to_string();
511            let where_params_s: String = where_params_json.to_string();
512            let c_table = CString::new(table_s).map_err(|_| jni::errors::Error::JavaException)?;
513            let c_set = CString::new(set_s).map_err(|_| jni::errors::Error::JavaException)?;
514            let c_where = CString::new(where_s).map_err(|_| jni::errors::Error::JavaException)?;
515            let c_where_params =
516                CString::new(where_params_s).map_err(|_| jni::errors::Error::JavaException)?;
517            // SAFETY: tx_handle 来自 beginTransaction,C 字符串有效
518            let r = unsafe {
519                sz_orm_cabi::sz_orm_model_update_tx(
520                    tx_handle as sz_orm_cabi::SzOrmTransactionHandle,
521                    c_table.as_ptr(),
522                    c_set.as_ptr(),
523                    c_where.as_ptr(),
524                    c_where_params.as_ptr(),
525                )
526            };
527            if r.success == 0 {
528                Ok(-1)
529            } else {
530                Ok(r.rows_affected as jlong)
531            }
532        })
533        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
534}
535
536/// JNI 入口:在事务内删除行,返回受影响行数(-1 表示失败)
537#[no_mangle]
538pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_modelDeleteTx<'local>(
539    mut unowned_env: EnvUnowned<'local>,
540    _class: JClass<'local>,
541    tx_handle: jlong,
542    table: JString<'local>,
543    where_clause: JString<'local>,
544    where_params_json: JString<'local>,
545) -> jlong {
546    if tx_handle == 0 {
547        return -1;
548    }
549    unowned_env
550        .with_env(|_env| -> jni::errors::Result<jlong> {
551            let table_s: String = table.to_string();
552            let where_s: String = where_clause.to_string();
553            let where_params_s: String = where_params_json.to_string();
554            let c_table = CString::new(table_s).map_err(|_| jni::errors::Error::JavaException)?;
555            let c_where = CString::new(where_s).map_err(|_| jni::errors::Error::JavaException)?;
556            let c_where_params =
557                CString::new(where_params_s).map_err(|_| jni::errors::Error::JavaException)?;
558            // SAFETY: tx_handle 来自 beginTransaction,C 字符串有效
559            let r = unsafe {
560                sz_orm_cabi::sz_orm_model_delete_tx(
561                    tx_handle as sz_orm_cabi::SzOrmTransactionHandle,
562                    c_table.as_ptr(),
563                    c_where.as_ptr(),
564                    c_where_params.as_ptr(),
565                )
566            };
567            if r.success == 0 {
568                Ok(-1)
569            } else {
570                Ok(r.rows_affected as jlong)
571            }
572        })
573        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
574}
575
576/// JNI 入口:在事务内查询行,返回 JSON 行数组字符串(失败返回空字符串)
577#[no_mangle]
578pub extern "system" fn Java_sz_1orm_1java_SzOrmPool_modelFindTx<'local>(
579    mut unowned_env: EnvUnowned<'local>,
580    _class: JClass<'local>,
581    tx_handle: jlong,
582    table: JString<'local>,
583    where_clause: JString<'local>,
584    where_params_json: JString<'local>,
585) -> jstring {
586    if tx_handle == 0 {
587        return std::ptr::null_mut();
588    }
589    unowned_env
590        .with_env(|env| -> jni::errors::Result<jstring> {
591            let table_s: String = table.to_string();
592            let where_s: String = where_clause.to_string();
593            let where_params_s: String = where_params_json.to_string();
594            let c_table = CString::new(table_s).map_err(|_| jni::errors::Error::JavaException)?;
595            let c_where = CString::new(where_s).map_err(|_| jni::errors::Error::JavaException)?;
596            let c_where_params =
597                CString::new(where_params_s).map_err(|_| jni::errors::Error::JavaException)?;
598            // SAFETY: tx_handle 来自 beginTransaction,C 字符串有效
599            let ptr = unsafe {
600                sz_orm_cabi::sz_orm_model_find_tx(
601                    tx_handle as sz_orm_cabi::SzOrmTransactionHandle,
602                    c_table.as_ptr(),
603                    c_where.as_ptr(),
604                    c_where_params.as_ptr(),
605                )
606            };
607            let json = if ptr.is_null() {
608                String::new()
609            } else {
610                // SAFETY: ptr 有效
611                let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
612                    .to_str()
613                    .map(|s| s.to_string())
614                    .unwrap_or_default();
615                // SAFETY: ptr 配对释放
616                unsafe { sz_orm_cabi::sz_orm_string_free(ptr) };
617                s
618            };
619            let jstr = env.new_string(&json)?;
620            Ok(jstr.into_raw())
621        })
622        .resolve::<jni::errors::ThrowRuntimeExAndDefault>()
623}
624
625#[cfg(test)]
626mod tests {
627    use std::ffi::CString;
628    use sz_orm_cabi::{PoolConfigC, SzOrmPoolHandle};
629
630    fn test_pool() -> SzOrmPoolHandle {
631        let dsn = CString::new("sqlite::memory:").unwrap();
632        let config = PoolConfigC {
633            max_connections: 1,
634            min_connections: 1,
635            connect_timeout_ms: 5000,
636            idle_timeout_ms: 300000,
637        };
638        // SAFETY: dsn 有效
639        unsafe { sz_orm_cabi::sz_orm_pool_new(dsn.as_ptr(), &config) }
640    }
641
642    #[test]
643    fn test_java_binding_pool_create() {
644        let pool = test_pool();
645        assert!(!pool.is_null(), "pool should be created via cabi");
646        // SAFETY: pool 有效
647        unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
648    }
649
650    #[test]
651    fn test_java_binding_ping() {
652        let pool = test_pool();
653        assert!(!pool.is_null());
654        // SAFETY: pool 有效
655        let healthy = unsafe { sz_orm_cabi::sz_orm_ping(pool) };
656        assert_eq!(healthy, 1);
657        // SAFETY: pool 有效
658        unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
659    }
660
661    #[test]
662    fn test_java_binding_version() {
663        let v = sz_orm_cabi::sz_orm_version();
664        assert!(v >= 1, "version should be >= 1, got {v}");
665    }
666
667    #[test]
668    fn test_java_binding_execute_and_query() {
669        let pool = test_pool();
670        assert!(!pool.is_null());
671
672        let create = CString::new("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)").unwrap();
673        // SAFETY: pool/sql 有效
674        let r = unsafe { sz_orm_cabi::sz_orm_execute(pool, create.as_ptr()) };
675        assert_eq!(r.success, 1);
676
677        let insert = CString::new("INSERT INTO t (v) VALUES ('x')").unwrap();
678        // SAFETY: pool/sql 有效
679        let r = unsafe { sz_orm_cabi::sz_orm_execute(pool, insert.as_ptr()) };
680        assert_eq!(r.success, 1);
681        assert_eq!(r.rows_affected, 1);
682
683        let q = CString::new("SELECT v FROM t").unwrap();
684        // SAFETY: pool/sql 有效
685        let result_ptr = unsafe { sz_orm_cabi::sz_orm_query(pool, q.as_ptr()) };
686        assert!(!result_ptr.is_null());
687        // SAFETY: result_ptr 由 sz_orm_query 分配
688        let result_box = unsafe { Box::from_raw(result_ptr) };
689        assert_eq!(result_box.success, 1);
690        // SAFETY: result_box 配对释放
691        unsafe { sz_orm_cabi::sz_orm_query_result_free(Box::into_raw(result_box)) };
692
693        // SAFETY: pool 有效
694        unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
695    }
696
697    #[test]
698    fn test_java_binding_null_dsn_returns_null() {
699        // SAFETY: null dsn 合法调用
700        let pool = unsafe { sz_orm_cabi::sz_orm_pool_new(std::ptr::null(), std::ptr::null()) };
701        assert!(pool.is_null(), "null dsn should return null pool");
702    }
703
704    #[test]
705    fn test_java_binding_pool_config_clamped() {
706        let dsn = CString::new("sqlite::memory:").unwrap();
707        let config = PoolConfigC {
708            max_connections: 0,
709            min_connections: 0,
710            connect_timeout_ms: 100,
711            idle_timeout_ms: 100,
712        };
713        // SAFETY: dsn 有效
714        let pool = unsafe { sz_orm_cabi::sz_orm_pool_new(dsn.as_ptr(), &config) };
715        // cabi 内部会 clamp max_connections 到 >= 1
716        if !pool.is_null() {
717            // SAFETY: pool 有效
718            unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
719        }
720    }
721
722    // ===== 新增测试:事务 + 模型级 API 转发(REQ-BND-006/013/014)=====
723
724    fn model_test_pool() -> SzOrmPoolHandle {
725        let pool = test_pool();
726        assert!(!pool.is_null());
727        let create =
728            CString::new("CREATE TABLE jm_t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
729                .unwrap();
730        // SAFETY: pool/sql 有效
731        let r = unsafe { sz_orm_cabi::sz_orm_execute(pool, create.as_ptr()) };
732        assert_eq!(r.success, 1, "CREATE should succeed");
733        pool
734    }
735
736    #[test]
737    fn test_java_model_insert_find_roundtrip() {
738        let pool = model_test_pool();
739        let table = CString::new("jm_t").unwrap();
740        let fields = CString::new(r#"["name","age"]"#).unwrap();
741        let values = CString::new(r#"["Alice",30]"#).unwrap();
742        // SAFETY: pool/table/fields/values 有效
743        let r = unsafe {
744            sz_orm_cabi::sz_orm_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
745        };
746        assert_eq!(
747            r.success, 1,
748            "java model_insert should succeed, code={}",
749            r.error_code
750        );
751        assert_eq!(r.rows_affected, 1);
752
753        let where_clause = CString::new("name = ?").unwrap();
754        let where_params = CString::new(r#"["Alice"]"#).unwrap();
755        // SAFETY: pool/table/where 有效
756        let ptr = unsafe {
757            sz_orm_cabi::sz_orm_model_find(
758                pool,
759                table.as_ptr(),
760                where_clause.as_ptr(),
761                where_params.as_ptr(),
762            )
763        };
764        assert!(!ptr.is_null());
765        // SAFETY: ptr 有效
766        let json = unsafe { std::ffi::CStr::from_ptr(ptr) }
767            .to_string_lossy()
768            .into_owned();
769        // SAFETY: ptr 配对释放
770        unsafe { sz_orm_cabi::sz_orm_string_free(ptr) };
771        assert!(
772            json.contains("Alice"),
773            "java model_find should contain Alice: {json}"
774        );
775        // SAFETY: pool 有效
776        unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
777    }
778
779    #[test]
780    fn test_java_model_update_delete_find() {
781        let pool = model_test_pool();
782        let table = CString::new("jm_t").unwrap();
783        let fields = CString::new(r#"["name","age"]"#).unwrap();
784        let values = CString::new(r#"["Bob",25]"#).unwrap();
785        // SAFETY: pool/table/fields/values 有效
786        unsafe {
787            sz_orm_cabi::sz_orm_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
788        };
789
790        let set_json = CString::new(r#"{"age":26}"#).unwrap();
791        let where_clause = CString::new("name = ?").unwrap();
792        let where_params = CString::new(r#"["Bob"]"#).unwrap();
793        // SAFETY: pool/table/set/where 有效
794        let r = unsafe {
795            sz_orm_cabi::sz_orm_model_update(
796                pool,
797                table.as_ptr(),
798                set_json.as_ptr(),
799                where_clause.as_ptr(),
800                where_params.as_ptr(),
801            )
802        };
803        assert_eq!(
804            r.success, 1,
805            "java model_update should succeed, code={}",
806            r.error_code
807        );
808
809        // SAFETY: pool/table/where 有效
810        let ptr = unsafe {
811            sz_orm_cabi::sz_orm_model_find(
812                pool,
813                table.as_ptr(),
814                where_clause.as_ptr(),
815                where_params.as_ptr(),
816            )
817        };
818        // SAFETY: ptr 有效
819        let json = unsafe { std::ffi::CStr::from_ptr(ptr) }
820            .to_string_lossy()
821            .into_owned();
822        unsafe { sz_orm_cabi::sz_orm_string_free(ptr) };
823        assert!(
824            json.contains("26"),
825            "java find after update should contain age 26: {json}"
826        );
827
828        // SAFETY: pool/table/where 有效
829        let r = unsafe {
830            sz_orm_cabi::sz_orm_model_delete(
831                pool,
832                table.as_ptr(),
833                where_clause.as_ptr(),
834                where_params.as_ptr(),
835            )
836        };
837        assert_eq!(
838            r.success, 1,
839            "java model_delete should succeed, code={}",
840            r.error_code
841        );
842
843        // SAFETY: pool/table/where 有效
844        let ptr = unsafe {
845            sz_orm_cabi::sz_orm_model_find(
846                pool,
847                table.as_ptr(),
848                where_clause.as_ptr(),
849                where_params.as_ptr(),
850            )
851        };
852        // SAFETY: ptr 有效
853        let json = unsafe { std::ffi::CStr::from_ptr(ptr) }
854            .to_string_lossy()
855            .into_owned();
856        unsafe { sz_orm_cabi::sz_orm_string_free(ptr) };
857        assert!(
858            json == "[]" || json == "null" || json.is_empty(),
859            "java find after delete should be empty: {json}"
860        );
861        // SAFETY: pool 有效
862        unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
863    }
864
865    #[test]
866    fn test_java_model_insert_illegal_table() {
867        let pool = model_test_pool();
868        let table = CString::new("jm_t; DROP--").unwrap();
869        let fields = CString::new(r#"["name"]"#).unwrap();
870        let values = CString::new(r#"["X"]"#).unwrap();
871        // SAFETY: pool/table/fields/values 有效(table 含注入向量)
872        let r = unsafe {
873            sz_orm_cabi::sz_orm_model_insert(pool, table.as_ptr(), fields.as_ptr(), values.as_ptr())
874        };
875        assert_eq!(r.success, 0, "illegal table should fail");
876        assert_eq!(
877            r.error_code,
878            sz_orm_cabi::SzOrmErrorCode::InvalidArgument.as_i32()
879        );
880        // SAFETY: pool 有效
881        unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
882    }
883
884    #[test]
885    fn test_java_transaction_model_rollback() {
886        let pool = model_test_pool();
887        // SAFETY: pool 有效
888        let tx = unsafe { sz_orm_cabi::sz_orm_transaction_begin(pool) };
889        assert!(!tx.is_null());
890
891        let table = CString::new("jm_t").unwrap();
892        let fields = CString::new(r#"["name","age"]"#).unwrap();
893        let values = CString::new(r#"["TxUser",99]"#).unwrap();
894        // SAFETY: tx/table/fields/values 有效
895        let r = unsafe {
896            sz_orm_cabi::sz_orm_model_insert_tx(
897                tx,
898                table.as_ptr(),
899                fields.as_ptr(),
900                values.as_ptr(),
901            )
902        };
903        assert_eq!(
904            r.success, 1,
905            "java model_insert_tx should succeed, code={}",
906            r.error_code
907        );
908
909        // SAFETY: tx 有效
910        assert_eq!(unsafe { sz_orm_cabi::sz_orm_transaction_rollback(tx) }, 1);
911        // SAFETY: tx 有效
912        unsafe { sz_orm_cabi::sz_orm_transaction_free(tx) };
913
914        let where_clause = CString::new("name = ?").unwrap();
915        let where_params = CString::new(r#"["TxUser"]"#).unwrap();
916        // SAFETY: pool/table/where 有效
917        let ptr = unsafe {
918            sz_orm_cabi::sz_orm_model_find(
919                pool,
920                table.as_ptr(),
921                where_clause.as_ptr(),
922                where_params.as_ptr(),
923            )
924        };
925        // SAFETY: ptr 有效
926        let json = unsafe { std::ffi::CStr::from_ptr(ptr) }
927            .to_string_lossy()
928            .into_owned();
929        unsafe { sz_orm_cabi::sz_orm_string_free(ptr) };
930        assert!(
931            json == "[]" || json == "null" || json.is_empty(),
932            "java find after rollback should be empty: {json}"
933        );
934        // SAFETY: pool 有效
935        unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
936    }
937
938    #[test]
939    fn test_java_transaction_model_commit() {
940        let pool = model_test_pool();
941        // SAFETY: pool 有效
942        let tx = unsafe { sz_orm_cabi::sz_orm_transaction_begin(pool) };
943        assert!(!tx.is_null());
944
945        let table = CString::new("jm_t").unwrap();
946        let fields = CString::new(r#"["name","age"]"#).unwrap();
947        let values = CString::new(r#"["CommitUser",50]"#).unwrap();
948        // SAFETY: tx/table/fields/values 有效
949        unsafe {
950            sz_orm_cabi::sz_orm_model_insert_tx(
951                tx,
952                table.as_ptr(),
953                fields.as_ptr(),
954                values.as_ptr(),
955            )
956        };
957
958        // SAFETY: tx 有效
959        assert_eq!(unsafe { sz_orm_cabi::sz_orm_transaction_commit(tx) }, 1);
960        // SAFETY: tx 有效
961        unsafe { sz_orm_cabi::sz_orm_transaction_free(tx) };
962
963        let where_clause = CString::new("name = ?").unwrap();
964        let where_params = CString::new(r#"["CommitUser"]"#).unwrap();
965        // SAFETY: pool/table/where 有效
966        let ptr = unsafe {
967            sz_orm_cabi::sz_orm_model_find(
968                pool,
969                table.as_ptr(),
970                where_clause.as_ptr(),
971                where_params.as_ptr(),
972            )
973        };
974        // SAFETY: ptr 有效
975        let json = unsafe { std::ffi::CStr::from_ptr(ptr) }
976            .to_string_lossy()
977            .into_owned();
978        unsafe { sz_orm_cabi::sz_orm_string_free(ptr) };
979        assert!(
980            json.contains("CommitUser"),
981            "java find after commit should contain CommitUser: {json}"
982        );
983        // SAFETY: pool 有效
984        unsafe { sz_orm_cabi::sz_orm_pool_free(pool) };
985    }
986}