Sync metadata schema and SyncMetadataClient. (#502) r=rnewman
This commit is contained in:
parent
e8ec59e464
commit
c61bc79b99
6 changed files with 267 additions and 0 deletions
|
@ -66,5 +66,8 @@ path = "tx"
|
|||
[dependencies.mentat_tx_parser]
|
||||
path = "tx-parser"
|
||||
|
||||
[dependencies.mentat_tolstoy]
|
||||
path = "tolstoy"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
|
|
@ -33,6 +33,7 @@ pub use uuid::Uuid;
|
|||
|
||||
// Export from our modules.
|
||||
pub use parse::ParseError;
|
||||
pub use uuid::ParseError as UuidParseError;
|
||||
pub use types::{
|
||||
FromMicros,
|
||||
Span,
|
||||
|
|
28
tolstoy/Cargo.toml
Normal file
28
tolstoy/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "mentat_tolstoy"
|
||||
version = "0.0.1"
|
||||
workspace = ".."
|
||||
authors = ["Grisha Kruglov <gkruglov@mozilla.com>"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1"
|
||||
hyper = "0.11"
|
||||
tokio-core = "0.1"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
lazy_static = "0.2"
|
||||
uuid = { version = "0.5", features = ["v4", "serde"] }
|
||||
|
||||
error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" }
|
||||
|
||||
[dependencies.mentat_db]
|
||||
path = "../db"
|
||||
|
||||
[dependencies.edn]
|
||||
path = "../edn"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.12"
|
||||
# System sqlite might be very old.
|
||||
features = ["bundled", "limits"]
|
41
tolstoy/src/lib.rs
Normal file
41
tolstoy/src/lib.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2016 Mozilla
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
// this file except in compliance with the License. You may obtain a copy of the
|
||||
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
extern crate hyper;
|
||||
extern crate tokio_core;
|
||||
extern crate futures;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate mentat_db;
|
||||
extern crate rusqlite;
|
||||
extern crate edn;
|
||||
extern crate uuid;
|
||||
|
||||
pub mod schema;
|
||||
pub mod metadata;
|
||||
|
||||
error_chain! {
|
||||
types {
|
||||
Error, ErrorKind, ResultExt, Result;
|
||||
}
|
||||
|
||||
foreign_links {
|
||||
IOError(std::io::Error);
|
||||
HttpError(hyper::Error);
|
||||
SqlError(rusqlite::Error);
|
||||
UuidParseError(edn::UuidParseError);
|
||||
}
|
||||
}
|
74
tolstoy/src/metadata.rs
Normal file
74
tolstoy/src/metadata.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2016 Mozilla
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
// this file except in compliance with the License. You may obtain a copy of the
|
||||
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use rusqlite;
|
||||
use uuid::Uuid;
|
||||
|
||||
use schema;
|
||||
use Result;
|
||||
|
||||
trait HeadTrackable {
|
||||
fn remote_head(&self) -> Result<Uuid>;
|
||||
fn set_remote_head(&mut self, uuid: &Uuid) -> Result<()>;
|
||||
}
|
||||
|
||||
struct SyncMetadataClient {
|
||||
conn: rusqlite::Connection
|
||||
}
|
||||
|
||||
impl SyncMetadataClient {
|
||||
fn new(conn: rusqlite::Connection) -> Self {
|
||||
SyncMetadataClient {
|
||||
conn: conn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeadTrackable for SyncMetadataClient {
|
||||
fn remote_head(&self) -> Result<Uuid> {
|
||||
self.conn.query_row(
|
||||
"SELECT value FROM tolstoy_metadata WHERE key = ?",
|
||||
&[&schema::REMOTE_HEAD_KEY], |r| {
|
||||
let bytes: Vec<u8> = r.get(0);
|
||||
Uuid::from_bytes(bytes.as_slice())
|
||||
}
|
||||
)?.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn set_remote_head(&mut self, uuid: &Uuid) -> Result<()> {
|
||||
let tx = self.conn.transaction()?;
|
||||
let uuid_bytes = uuid.as_bytes().to_vec();
|
||||
tx.execute("UPDATE tolstoy_metadata SET value = ? WHERE key = ?", &[&uuid_bytes, &schema::REMOTE_HEAD_KEY])?;
|
||||
tx.commit().map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_remote_head_default() {
|
||||
let conn = schema::tests::setup_conn();
|
||||
let metadata_client: SyncMetadataClient = SyncMetadataClient::new(conn);
|
||||
assert_eq!(Uuid::nil(), metadata_client.remote_head().expect("fetch succeeded"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_and_get_remote_head() {
|
||||
let conn = schema::tests::setup_conn();
|
||||
let uuid = Uuid::new_v4();
|
||||
let mut metadata_client: SyncMetadataClient = SyncMetadataClient::new(conn);
|
||||
metadata_client.set_remote_head(&uuid).expect("update succeeded");
|
||||
assert_eq!(uuid, metadata_client.remote_head().expect("fetch succeeded"));
|
||||
}
|
||||
}
|
120
tolstoy/src/schema.rs
Normal file
120
tolstoy/src/schema.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
// Copyright 2016 Mozilla
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
// this file except in compliance with the License. You may obtain a copy of the
|
||||
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed
|
||||
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
use rusqlite;
|
||||
use Result;
|
||||
|
||||
pub static REMOTE_HEAD_KEY: &str = r#"remote_head"#;
|
||||
|
||||
lazy_static! {
|
||||
/// SQL statements to be executed, in order, to create the Tolstoy SQL schema (version 1).
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
static ref SCHEMA_STATEMENTS: Vec<&'static str> = { vec![
|
||||
r#"CREATE TABLE IF NOT EXISTS tolstoy_tu (tx INTEGER PRIMARY KEY, uuid BLOB NOT NULL UNIQUE) WITHOUT ROWID"#,
|
||||
r#"CREATE TABLE IF NOT EXISTS tolstoy_metadata (key BLOB NOT NULL UNIQUE, value BLOB NOT NULL)"#,
|
||||
r#"CREATE INDEX IF NOT EXISTS idx_tolstoy_tu_ut ON tolstoy_tu (uuid, tx)"#,
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ensure_current_version(conn: &mut rusqlite::Connection) -> Result<()> {
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
for statement in (&SCHEMA_STATEMENTS).iter() {
|
||||
tx.execute(statement, &[])?;
|
||||
}
|
||||
|
||||
tx.execute("INSERT OR IGNORE INTO tolstoy_metadata (key, value) VALUES (?, zeroblob(16))", &[&REMOTE_HEAD_KEY])?;
|
||||
tx.commit().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn setup_conn_bare() -> rusqlite::Connection {
|
||||
let conn = rusqlite::Connection::open_in_memory().unwrap();
|
||||
|
||||
conn.execute_batch("
|
||||
PRAGMA page_size=32768;
|
||||
PRAGMA journal_mode=wal;
|
||||
PRAGMA wal_autocheckpoint=32;
|
||||
PRAGMA journal_size_limit=3145728;
|
||||
PRAGMA foreign_keys=ON;
|
||||
").expect("success");
|
||||
|
||||
conn
|
||||
}
|
||||
|
||||
pub fn setup_conn() -> rusqlite::Connection {
|
||||
let mut conn = setup_conn_bare();
|
||||
ensure_current_version(&mut conn).expect("connection setup");
|
||||
conn
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
let mut conn = setup_conn_bare();
|
||||
|
||||
assert!(ensure_current_version(&mut conn).is_ok());
|
||||
|
||||
let mut stmt = conn.prepare("SELECT key FROM tolstoy_metadata WHERE value = zeroblob(16)").unwrap();
|
||||
let mut keys_iter = stmt.query_map(&[], |r| r.get(0)).expect("query works");
|
||||
|
||||
let first: Result<String> = keys_iter.next().unwrap().map_err(|e| e.into());
|
||||
let second: Option<_> = keys_iter.next();
|
||||
match (first, second) {
|
||||
(Ok(key), None) => {
|
||||
assert_eq!(key, REMOTE_HEAD_KEY);
|
||||
},
|
||||
(_, _) => { panic!("Wrong number of results."); },
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_non_empty() {
|
||||
let mut conn = setup_conn_bare();
|
||||
|
||||
assert!(ensure_current_version(&mut conn).is_ok());
|
||||
|
||||
let test_uuid = Uuid::new_v4();
|
||||
{
|
||||
let tx = conn.transaction().unwrap();
|
||||
let uuid_bytes = test_uuid.as_bytes().to_vec();
|
||||
match tx.execute("UPDATE tolstoy_metadata SET value = ? WHERE key = ?", &[&uuid_bytes, &REMOTE_HEAD_KEY]) {
|
||||
Err(e) => panic!("Error running an update: {}", e),
|
||||
_ => ()
|
||||
}
|
||||
match tx.commit() {
|
||||
Err(e) => panic!("Error committing an update: {}", e),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
assert!(ensure_current_version(&mut conn).is_ok());
|
||||
|
||||
// Check that running ensure_current_version on an initialized conn doesn't change anything.
|
||||
let mut stmt = conn.prepare("SELECT value FROM tolstoy_metadata").unwrap();
|
||||
let mut values_iter = stmt.query_map(&[], |r| {
|
||||
let raw_uuid: Vec<u8> = r.get(0);
|
||||
Uuid::from_bytes(raw_uuid.as_slice()).unwrap()
|
||||
}).expect("query works");
|
||||
|
||||
let first: Result<Uuid> = values_iter.next().unwrap().map_err(|e| e.into());
|
||||
let second: Option<_> = values_iter.next();
|
||||
match (first, second) {
|
||||
(Ok(uuid), None) => {
|
||||
assert_eq!(test_uuid, uuid);
|
||||
},
|
||||
(_, _) => { panic!("Wrong number of results."); },
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue