Right now I have code that uses the rusqlite
sqlite bindings to open a db connection and do a bunch of db operations in my application like this:
extern crate rusqlite;
use rusqlite::SqliteConnection;
struct MyAppState {
db: SqliteConnection,
// ... pretend there's other fields here ...
}
impl MyAppState {
fn new() -> MyAppState {
let db = SqliteConnection::open(":memory:").unwrap();
MyAppState {
db: db
}
}
fn query_some_info(&mut self, arg: i64) -> i64 {
let mut stmt = self.db.prepare("SELECT ? + 1").unwrap();
let mut result_iter = stmt.query(&[&arg]).unwrap();
let result = result_iter.next().unwrap().unwrap().get(0);
result
}
}
fn main() {
let mut app = MyAppState::new();
for i in range(0, 100) {
let result = app.query_some_info(i);
println!("{}", result);
}
}
Since the prepared statement lives in a local variable, this seems to miss the point of prepared statements to some extent since I have to re-prepare it every time the function is called and the local variable comes into being. Ideally, I would prepare all my statements at most once and stash them in the MyAppState
struct for the duration of the db connection.
However, since the SqliteStatement
type is parameterized over the lifetime of the db connection, it borrows the connection and by extension the struct it lives in and I can't do anything with the struct anymore like return the struct by value or call &mut self
methods on it (query_some_info
doesn't really need to take &mut self
here, but some code in my actual program does unless everything goes on to live in RefCell
s, which isn't the worst, I guess, but still).
Usually when the borrow checker betrays me like that, my recourse is to give up on stack discipline and put some Rc<RefCell< >>
here and there until it all works out, but in this case there's some lifetimes in the types either way and I don't know how to word it in a way that appeases the borrow checker.
Ideally I'd like to write code that only prepares the statements right when the db gets opened, or maybe prepares them only once when they are first used, and then never calls prepare
again during the duration of the db connection, while mostly keeping the safety of the rusqlite bindings rather than writing code against the sqlite3 C API or breaking abstraction or whatever. How do I?
For prepared SQLite statements in Android there is SQLiteStatement. Prepared statements help you speed up performance (especially for statements that need to be executed multiple times) and also help avoid against injection attacks.
A prepared statement object is the compiled object code. All SQL must be converted into a prepared statement before it can be run. The life-cycle of a prepared statement object usually goes like this: Create the prepared statement object using sqlite3_prepare_v2().
SQLite doesn't support native variable syntax, but you can achieve virtually the same using an in-memory temp table.
If the value is temporal and not expected to outlive the eventual execution of the statement, SQLite needs to retain a copy of the value. This is indicated with the SQLITE_TRANSIENT constant.
struct statement { statement_handle handle; // ... }; While connections are opened, statements are prepared. SQLite provides the sqlite3_prepare_v2 function to compile a SQL statement and return the resulting statement object. More specifically, the result is a statement handle.
While connections are opened, statements are prepared. SQLite provides the sqlite3_prepare_v2 function to compile a SQL statement and return the resulting statement object. More specifically, the result is a statement handle.
This tells the SQLite database engine that values will be bound to this statement prior to execution. This means I can compile a SQL statement once and use it to insert a set of rows, perhaps using a loop, and certainly improve insertion speed. SQLite provides a collection of sqlite3_bind_* functions for binding values to prepared statements.
You are right, indeed, that sibling references are awkward in Rust. There is a good reason though, they are not easily modeled by the ownership system.
In this particular case, I would advise you to split the structure: you can keep the prepared statements in a dedicated cache also parametrized on the lifetime of the db
for example; the db
instead should be instantiated at the top of your program and passed down (think dependency injection) so that the cache that depends on it can outlive the program main function.
This does mean that the db
will remain borrowed, obviously.
The Statement
struct has a lifetime parameter, Statement<'conn>
. When you prepare the statement, you must have a reference to the Connection
that outlives the statement.
extern crate rusqlite;
use rusqlite::{Connection, Statement};
struct MyAppState {
db: Connection,
}
impl MyAppState {
fn new() -> MyAppState {
let db = Connection::open(":memory:").unwrap();
MyAppState { db: db }
}
}
struct PreparedStatement<'conn> {
statement: Statement<'conn>,
}
impl<'conn> PreparedStatement<'conn> {
pub fn new<'a>(conn: &'a Connection, sql: &str) -> PreparedStatement<'a> {
PreparedStatement {
statement: conn.prepare(sql).unwrap(),
}
}
fn query_some_info(&mut self, arg: i64) -> i64 {
let mut result_iter = self.statement.query(&[&arg]).unwrap();
let result = result_iter.next().unwrap().unwrap().get(0);
result
}
}
fn main() {
let app = MyAppState::new();
let mut prepared_stmt = PreparedStatement::new(&app.db, "SELECT ? + 1");
for i in 0..100 {
let result = prepared_stmt.query_some_info(i);
println!("{}", result);
}
}
In Rust, unlike some other languages, I have found that factoring something out into a function changes its meaning. It introduces new lifetimes, which usually works against you. But in this case, that's exactly what was needed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With