Change FnMut to Fn in create_scalar_function#1387
Conversation
|
Just for reference: |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #1387 +/- ##
=======================================
Coverage 85.87% 85.87%
=======================================
Files 58 58
Lines 10765 10765
=======================================
Hits 9244 9244
Misses 1521 1521 ☔ View full report in Codecov by Sentry. |
|
All relevant |
|
Is there any safety issue ? |
A recursive
This example might demostrate the unsafeness (multiple mutable borrows) more clearly: let conn = Connection::open_in_memory().unwrap();
let mut num = 0;
conn.create_scalar_function("recursive_fn", 1, FunctionFlags::default(), move |ctx| {
let num = &mut num;
println!("num is mutably borrowed");
let param = ctx.get::<i64>(0)?;
let conn_ref = unsafe { ctx.get_connection()? };
if param >= 1 {
conn_ref.query_row("SELECT recursive_fn(?)", params![ param - 1 ], |_| Ok(()))?;
}
drop(num);
println!("num's mutable reference is dropped");
Ok(0)
}).unwrap();
conn.execute_batch("SELECT recursive_fn(3)").unwrap();output: |
|
Your example relies on an Currently, there is no safe way to obtain a connection reference within the callback function. This is because However, the documentation for So, strictly speaking, your example isn't unsound... ;) I propose two alternative solutions:
The second option is non-trivial: the constraints are complex. For practical reasons, broad restrictions should be included to cover all cases comprehensively. As I understand, the restrictions concern thread safety and prohibit any handle usage that could trigger callbacks to Rust code from SQLite. In practice, I suspect that most crate users won’t fully understand these safety requirements, leading to a high risk of misuse. Consequently, software depending on |
Well, we can get the connection in the callback using thread-local storage without any thread_local! {
static CONN: RefCell<Option<Connection>> = RefCell::new(None);
}
CONN.with_borrow_mut(|conn_mut| *conn_mut = Some(Connection::open_in_memory().unwrap()));
CONN.with_borrow(|conn| {
let conn = conn.as_ref().unwrap();
let mut num = 0;
conn.create_scalar_function("recursive_fn", 1, FunctionFlags::default(), move |ctx| {
let num = &mut num;
println!("num is mutably borrowed");
let param = ctx.get::<i64>(0)?;
CONN.with_borrow(|conn| -> rusqlite::Result<()> {
let conn = conn.as_ref().unwrap();
if param >= 1 {
conn.query_row("SELECT recursive_fn(?)", params![ param - 1 ], |_| Ok(()))?;
}
Ok(())
})?;
drop(num);
println!("num's mutable reference is dropped");
Ok(0)
}).unwrap();
conn.execute_batch("SELECT recursive_fn(3)").unwrap();
})My point is, custom functions are by design reentrant. I believe the developers of SQLite have never felt the need to add any check or lock to prevent them from being re-entered, and that makes them |
Ah, thanks! I hadn’t thought of that...
Yes, you're right. The only solution is to make the callback Fn. Thanks for the insight! |
Change FnMut to Fn in InnerConnection::create_scalar_function cast user data as shared reference
The
x_funcparameter ofcreate_scalar_functioncan be re-entered, so it must be anFn.Code Example:
Output: