Feature request: Rust API for registering user-defined SQL functions
Motivation
When using Turso as an embedded database from Rust, it would be useful to register a Rust function with a connection and invoke it from SQL.
For example:
let conn = /* ... */;
conn.create_scalar_function("handle_user_change", |user_id: i64| {
// Run application-defined Rust code.
handle_user_change(user_id)?;
Ok(())
})?;Then SQL could call that function directly:
CREATE TRIGGER user_updated
AFTER UPDATE ON users
BEGIN
SELECT handle_user_change(NEW.id);
END;Another strong use case for this is validating or transforming data using host code. This would provide a simple way for an embedded Turso database to call into its Rust host process when database changes occur.
Proposed API
Something conceptually similar to rusqlite::Connection::create_scalar_function:
conn.create_scalar_function(
"my_function",
/* flags/options */,
|ctx| {
// Read SQL arguments and return a SQL value.
},
)?;The exact API does not need to match rusqlite, but ideally it would support:
- registering a Rust function on a connection
- typed or ergonomic access to SQL arguments
- returning SQLite/Turso values
- deterministic / non-deterministic flags where applicable
Why not just use an outbox / CDC?
An outbox table or CDC works well when the desired behavior is asynchronous or is needed after a transaction. Otherwise, polling an outbox introduces an additional asynchronous boundary that is not otherwise necessary.
Prior art
SQLite exposes this functionality through sqlite3_create_function, and Rust libraries such as rusqlite expose it as a safe Rust API.
Question
- Should we support asynchronous scalar functions?
Source: tursodatabase/turso