Abstract database access

In the previous chapter, we inserted and read documents directly with rusqlite. To gain cleaner code, lets abstract it away with a type and some methods.

Data fields

Rust provides structs to gather data fields:


# #![allow(unused_variables)]
#fn main() {
struct Document {
    id: String,
    revision: i64,
    hash: Vec<u8>,
    data: String,
}
#}

Document methods

Methods are implementet on the struct, which is more or less a copy of the previous main function:


# #![allow(unused_variables)]
#fn main() {
impl Document {
    fn create_table(db: &Connection) -> Result<(), SqliteError> {
        db.execute_batch(
            "create table documents (
            id text primary key not null,
            revision integer not null,
            hash blob not null,
            data text not null
        )",
        )
    }

    fn insert(&self, db: &Connection) -> Result<usize, SqliteError> {
        db.execute_named(
            "insert into documents (id, revision, hash, data)
        values (:id, :revision, :hash, :data)",
            named_params!(
                ":id": &self.id,
                ":revision": self.revision,
                ":hash": &self.hash,
                ":data": &self.data,
            ),
        )
    }

    fn get_by_id(id: &str, db: &Connection) -> Result<Self, SqliteError> {
        db.query_row_named(
            "select id, revision, hash, data from documents where id=:id",
            named_params!(":id": id),
            Document::row_mapper,
        )
    }

    fn row_mapper(row: &Row) -> Result<Self, SqliteError> {
        Ok(Self {
            id: row.get(0)?,
            revision: row.get(1)?,
            hash: row.get(2)?,
            data: row.get(3)?,
        })
    }
}
#}

Row and SqliteError are imported from rusqlite:


# #![allow(unused_variables)]
#fn main() {
use rusqlite::{named_params, Connection, Error as SqliteError, Row};
#}

Using the Document data type

The main function now reduces to:

fn main() {
    let db = Connection::open("database.sqlite").expect("Unable to open 'database.sqlite'.");

    Document::create_table(&db).expect("Unable to create documents table.");

    let document = Document {
        id: String::from("asdf"),
        revision: 0,
        hash: vec![0u8],
        data: String::from(r#"{ "a": 1, "b": 123 }"#),
    };

    document.insert(&db).expect("Unable to insert document.");

    let document_from_db = Document::get_by_id("asdf", &db)
        .expect("Unable to get document with id 'asdf'");

    println!("data: {}", &document_from_db.data);
}

thread 'main' panicked

Running the code gives:

~/g/b/sakkosekk $ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/sakkosekk`
thread 'main' panicked at 'Unable to create documents table.: SqliteFailure(Error { code: Unknown, extended_code: 1 }, Some("table documents already exists"))', src/libcore/result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
~/g/b/sakkosekk [101] $

The code assumes an empty database and fails with exit code 101.

Removing the database before running works:

~/g/b/sakkosekk (master|✚2) [101] $ rm database.sqlite && cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/sakkosekk`
data: { "a": 1, "b": 123 }

Next up: Fixing this error.