Persistent storage

Lets start with designing the disk storage format. The non-goal Writing low level code stears off designing a file format and using direct file access. A good alternative is SQLite. Lets set it up first.

Bootstrapping the project

I'll call the project sakkosekk, which is Norwegian for bean bag chair.

Bootstrapping with Cargo:

~/g/build-your-own-couchdb $ cargo init sakkosekk
     Created binary (application) package
~/g/build-your-own-couchdb $ cd sakkosekk/
~/g/b/sakkosekk $ cargo run
   Compiling sakkosekk v0.1.0 (/Users/arve/git/build-your-own-couchdb/sakkosekk)
    Finished dev [unoptimized + debuginfo] target(s) in 10.24s
     Running `target/debug/sakkosekk`
Hello, world!

Adding SQLite

Bindings for SQLite is available through the rusqlite crate:

~/g/b/sakkosekk $ echo '[dependencies]' >> Cargo.toml
~/g/b/sakkosekk $ echo 'rusqlite = { version = "0.20", features = ["bundled"] }' >> Cargo.toml

The bundled feature is enabled for hassle free sqlite3 linking.

Database schema

Documents in the database will have the columns:

  • indentifier,
  • revision,
  • hash and
  • document data.

Open database:

use rusqlite::{named_params, Connection};

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

Creating table:


# #![allow(unused_variables)]
#fn main() {
    db.execute_batch(
        "create table documents (
            id text primary key not null,
            revision integer not null,
            hash blob not null,
            data text not null
        )",
    )
    .expect("Unable to create documents table.");
#}

Inserting document:


# #![allow(unused_variables)]
#fn main() {
    db.execute_named(
        "insert into documents (id, revision, hash, data)
        values (:id, :revision, :hash, :data)",
        named_params!(
            ":id": "asdf",
            ":revision": 0,
            ":hash": vec![0u8],
            ":data": r#"{ "a": 1, "b": 123 }"#
        ),
    )
    .expect("Unable to insert document.");
#}

Reading document by the identifier:


# #![allow(unused_variables)]
#fn main() {
    let data: String = db
        .query_row_named(
            "select data from documents where id=:id",
            named_params!(":id": "asdf"),
            |row| row.get(0),
        )
        .expect("Unable to get document with id 'asdf'");

    println!("data: {}", data);
}
#}

Result:

~/g/b/sakkosekk $ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/sakkosekk`
data: { "a": 1, "b": 123 }

Next up: Abstractions around creating the database schema, inserting documents and reading documents.