Sync metadata schema and SyncMetadataClient. (#502) r=rnewman

This commit is contained in:
Grisha Kruglov 2017-12-13 14:19:05 -06:00 committed by Richard Newman
parent e8ec59e464
commit c61bc79b99
6 changed files with 267 additions and 0 deletions

View file

@ -66,5 +66,8 @@ path = "tx"
[dependencies.mentat_tx_parser] [dependencies.mentat_tx_parser]
path = "tx-parser" path = "tx-parser"
[dependencies.mentat_tolstoy]
path = "tolstoy"
[profile.release] [profile.release]
debug = true debug = true

View file

@ -33,6 +33,7 @@ pub use uuid::Uuid;
// Export from our modules. // Export from our modules.
pub use parse::ParseError; pub use parse::ParseError;
pub use uuid::ParseError as UuidParseError;
pub use types::{ pub use types::{
FromMicros, FromMicros,
Span, Span,

28
tolstoy/Cargo.toml Normal file
View 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
View 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
View 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
View 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."); },
}
}
}