Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store SQLite prepared statements for later?

Tags:

sqlite

rust

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 RefCells, 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?

like image 217
ben Avatar asked Dec 18 '14 17:12

ben


People also ask

Does SQLite support prepared statements?

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.

What is SQLite prepared statement?

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().

Does SQLite have variables?

SQLite doesn't support native variable syntax, but you can achieve virtually the same using an in-memory temp table.

When does SQLite need to retain a copy of the value?

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.

What is a statement handle in SQLite?

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.

How do I compile a SQL statement in SQLite?

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.

What does sqlite3_bind_* do?

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.


2 Answers

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.

like image 194
Matthieu M. Avatar answered Sep 22 '22 07:09

Matthieu M.


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.

like image 22
Jonathan Tran Avatar answered Sep 22 '22 07:09

Jonathan Tran