Ergonomic bindings to SQLite for Rust
Go to file
John Gallagher 699141df22 Merge pull request #99 from gwenn/simple-exec
Simplify execute_batch implementation.
2015-12-13 08:35:31 -05:00
libsqlite3-sys Merge branch 'functions' of https://github.com/gwenn/rusqlite into gwenn-functions 2015-12-11 11:31:25 -05:00
src Simplify execute_batch implementation. 2015-12-13 14:15:56 +01:00
tests Change config_log to take a Rust fn instead of an extern "C" fn. 2015-11-30 21:08:39 -05:00
.gitignore gitignore doc/ 2014-10-20 20:22:15 -04:00
.travis.yml Add functions feature to travis and Changelog. 2015-12-11 16:28:46 -05:00
build.rs Use new hyphen-less extern crate name 2015-03-26 15:49:13 -04:00
Cargo.toml Merge branch 'functions' of https://github.com/gwenn/rusqlite into gwenn-functions 2015-12-11 11:31:25 -05:00
Changelog.md BREAKING CHANGE: Remove common prefix on TransactionBehavior case names. 2015-12-12 14:22:50 -05:00
CONTRIBUTORS.md Add to CONTRIBUTORS 2015-12-12 15:50:44 -05:00
LICENSE Add LICENSE and README 2014-11-04 11:32:06 -05:00
README.md Add description of features to README 2015-12-12 16:04:11 -05:00

Rusqlite

Build Status

Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose an interface similar to rust-postgres. View the full API documentation.

extern crate rusqlite;
extern crate time;

use time::Timespec;
use rusqlite::Connection;

#[derive(Debug)]
struct Person {
    id: i32,
    name: String,
    time_created: Timespec,
    data: Option<Vec<u8>>
}

fn main() {
    let conn = Connection::open_in_memory().unwrap();

    conn.execute("CREATE TABLE person (
                  id              INTEGER PRIMARY KEY,
                  name            TEXT NOT NULL,
                  time_created    TEXT NOT NULL,
                  data            BLOB
                  )", &[]).unwrap();
    let me = Person {
        id: 0,
        name: "Steven".to_string(),
        time_created: time::get_time(),
        data: None
    };
    conn.execute("INSERT INTO person (name, time_created, data)
                  VALUES ($1, $2, $3)",
                 &[&me.name, &me.time_created, &me.data]).unwrap();

    let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person").unwrap();
    let mut person_iter = stmt.query_map(&[], |row| {
        Person {
            id: row.get(0),
            name: row.get(1),
            time_created: row.get(2),
            data: row.get(3)
        }
    }).unwrap();

    for person in person_iter {
        println!("Found person {:?}", person.unwrap());
    }
}

Optional Features

Rusqlite provides several features that are behind Cargo features. They are:

  • load_extension allows loading dynamic library-based SQLite extensions.
  • backup allows use of SQLite's online backup API.
  • functions allows you to load Rust closures into SQLite connections for use in queries.
  • trace allows hooks into SQLite's tracing and profiling APIs.

Design of Rows and Row

To retrieve the result rows from a query, SQLite requires you to call sqlite3_step() on a prepared statement. You can only retrieve the values of the "current" row. From the Rust point of view, this means that each row is only valid until the next row is fetched. rust-sqlite3 solves this the correct way with lifetimes. However, this means that the result rows do not satisfy the Iterator trait, which means you cannot (as easily) loop over the rows, or use many of the helpful Iterator methods like map and filter.

Instead, Rusqlite's Rows handle does conform to Iterator. It ensures safety by performing checks at runtime to ensure you do not try to retrieve the values of a "stale" row, and will panic if you do so. A specific example that will panic:

fn bad_function_will_panic(conn: &Connection) -> Result<i64> {
    let mut stmt = try!(conn.prepare("SELECT id FROM my_table"));
    let mut rows = try!(stmt.query(&[]));

    let row0 = try!(rows.next().unwrap());
    // row 0 is valid now...

    let row1 = try!(rows.next().unwrap());
    // row 0 is now STALE, and row 1 is valid

    let my_id = row0.get(0); // WILL PANIC because row 0 is stale
    Ok(my_id)
}

There are other, less obvious things that may result in a panic as well, such as calling collect() on a Rows and then trying to use the collected rows.

Strongly consider using the method query_map() instead, if you can. query_map() returns an iterator over rows-mapped-to-some-type. This iterator does not have any of the above issues with panics due to attempting to access stale rows.

Author

John Gallagher, johnkgallagher@gmail.com

License

Rusqlite is available under the MIT license. See the LICENSE file for more info.