Skip to content
This repository was archived by the owner on Sep 12, 2018. It is now read-only.

Commit d35fcbc

Browse files
author
Grisha Kruglov
committed
Reconcile incoming bootstrap transaction on first sync.
1 parent 85176a6 commit d35fcbc

11 files changed

Lines changed: 245 additions & 55 deletions

File tree

src/sync.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use conn::{
1818
};
1919

2020
use errors::{
21-
MentatError,
2221
Result,
2322
};
2423

@@ -46,8 +45,8 @@ use mentat_tolstoy::{
4645
SyncResult,
4746
Tx,
4847
TxMapper,
48+
TolstoyError,
4949
};
50-
use mentat_tolstoy::metadata::HeadTrackable;
5150

5251
pub trait Syncable {
5352
fn sync(&mut self, server_uri: &String, user_uuid: &String) -> Result<()>;
@@ -124,8 +123,17 @@ impl Conn {
124123
SyncResult::EmptyServer => (),
125124
SyncResult::NoChanges => (),
126125
SyncResult::ServerFastForward => (),
127-
SyncResult::Merge => bail!(MentatError::NotYetImplemented("Can't sync against diverged local.".into())),
126+
SyncResult::Merge => bail!(TolstoyError::NotYetImplemented(
127+
format!("Can't sync against diverged local.")
128+
)),
128129
SyncResult::LocalFastForward(txs) => fast_forward_local(&mut in_progress, txs)?,
130+
SyncResult::BadServerState => bail!(TolstoyError::NotYetImplemented(
131+
format!("Bad server state.")
132+
)),
133+
SyncResult::AdoptedRemoteOnFirstSync => (),
134+
SyncResult::IncompatibleBootstrapSchema => bail!(TolstoyError::NotYetImplemented(
135+
format!("IncompatibleBootstrapSchema.")
136+
)),
129137
}
130138

131139
in_progress.commit()

tests/tolstoy.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ fn test_reader() {
9999
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
100100
{
101101
let db_tx = c.transaction().expect("db tx");
102-
// Ensure that the first (bootstrap) transaction is skipped over.
102+
// Ensure that we see a bootstrap transaction.
103103
let mut receiver = TxCountingReceiver::new();
104104
assert_eq!(false, receiver.is_done);
105105
Processor::process(&db_tx, None, &mut receiver).expect("processor");
106106
assert_eq!(true, receiver.is_done);
107-
assert_eq!(0, receiver.tx_count);
107+
assert_eq!(1, receiver.tx_count);
108108
}
109109

110110
let ids = conn.transact(&mut c, r#"[
@@ -114,6 +114,11 @@ fn test_reader() {
114114
]"#).expect("successful transaction").tempids;
115115
let numba_entity_id = ids.get("s").unwrap();
116116

117+
let ids = conn.transact(&mut c, r#"[
118+
[:db/add "b" :foo/numba 123]
119+
]"#).expect("successful transaction").tempids;
120+
let _asserted_e = ids.get("b").unwrap();
121+
117122
let first_tx;
118123
{
119124
let db_tx = c.transaction().expect("db tx");
@@ -123,10 +128,11 @@ fn test_reader() {
123128

124129
println!("{:#?}", receiver);
125130

126-
assert_eq!(1, receiver.txes.keys().count());
127-
assert_tx_datoms_count(&receiver, 0, 4);
131+
// Three transactions: bootstrap, vocab, assertion.
132+
assert_eq!(3, receiver.txes.keys().count());
133+
assert_tx_datoms_count(&receiver, 2, 2);
128134

129-
first_tx = Some(*receiver.txes.keys().nth(0).expect("first tx"));
135+
first_tx = Some(*receiver.txes.keys().nth(1).expect("first non-bootstrap tx"));
130136
}
131137

132138
let ids = conn.transact(&mut c, r#"[
@@ -143,11 +149,13 @@ fn test_reader() {
143149
// Note that we're asking for the first transacted tx to be skipped by the processor.
144150
Processor::process(&db_tx, first_tx, &mut receiver).expect("processor");
145151

146-
assert_eq!(1, receiver.txes.keys().count());
147-
assert_tx_datoms_count(&receiver, 0, 2);
152+
// Vocab, assertion.
153+
assert_eq!(2, receiver.txes.keys().count());
154+
// Assertion datoms.
155+
assert_tx_datoms_count(&receiver, 1, 2);
148156

149-
// Inspect the transaction part.
150-
let tx_id = receiver.txes.keys().nth(0).expect("tx");
157+
// Inspect the assertion.
158+
let tx_id = receiver.txes.keys().nth(1).expect("tx");
151159
let datoms = receiver.txes.get(tx_id).expect("datoms");
152160
let part = datoms.iter().find(|&part| &part.e == asserted_e).expect("to find asserted datom");
153161

tolstoy/src/bootstrap.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2018 Mozilla
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4+
// this file except in compliance with the License. You may obtain a copy of the
5+
// License at http://www.apache.org/licenses/LICENSE-2.0
6+
// Unless required by applicable law or agreed to in writing, software distributed
7+
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8+
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
// specific language governing permissions and limitations under the License.
10+
11+
use mentat_core::{
12+
Keyword,
13+
};
14+
15+
use mentat_db::{
16+
CORE_SCHEMA_VERSION,
17+
};
18+
19+
use errors::{
20+
Result,
21+
TolstoyError,
22+
};
23+
use parts::{
24+
PartsHelper,
25+
};
26+
use types::{
27+
Tx,
28+
};
29+
30+
pub struct BootstrapHelper<'a> {
31+
parts: PartsHelper<'a>
32+
}
33+
34+
impl<'a> BootstrapHelper<'a> {
35+
pub fn new(assumed_bootstrap_tx: &Tx) -> BootstrapHelper {
36+
BootstrapHelper {
37+
parts: PartsHelper::new(&assumed_bootstrap_tx.parts),
38+
}
39+
}
40+
41+
// TODO we could also iterate through our own bootstrap schema definition and check that everything matches
42+
// "version" is used here as a proxy for doing that work
43+
pub fn is_compatible(&self) -> Result<bool> {
44+
Ok(self.core_schema_version()? == CORE_SCHEMA_VERSION as i64)
45+
}
46+
47+
pub fn core_schema_version(&self) -> Result<i64> {
48+
match self.parts.ea_lookup(
49+
Keyword::namespaced("db.schema", "core"),
50+
Keyword::namespaced("db.schema", "version"),
51+
) {
52+
Some(v) => {
53+
// TODO v is just a type tag and a Copy value, we shouldn't need to clone.
54+
match v.clone().into_long() {
55+
Some(v) => Ok(v),
56+
None => bail!(TolstoyError::BadServerState("incorrect type for core schema version".to_string()))
57+
}
58+
},
59+
None => bail!(TolstoyError::BadServerState("missing core schema version".to_string()))
60+
}
61+
}
62+
}

tolstoy/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ pub enum TolstoyError {
2626
#[fail(display = "Received bad response from the server: {}", _0)]
2727
BadServerResponse(String),
2828

29+
// TODO expand this into concrete error types
30+
#[fail(display = "Received bad server state: {}", _0)]
31+
BadServerState(String),
32+
2933
#[fail(display = "encountered more than one metadata value for key: {}", _0)]
3034
DuplicateMetadata(String),
3135

@@ -38,6 +42,9 @@ pub enum TolstoyError {
3842
#[fail(display = "encountered unexpected state: {}", _0)]
3943
UnexpectedState(String),
4044

45+
#[fail(display = "incompatible remote bootstrap schema. local: {}, remote: {}", _0, _1)]
46+
IncompatibleBootstrapSchema(i64, i64),
47+
4148
#[fail(display = "not yet implemented: {}", _0)]
4249
NotYetImplemented(String),
4350
}

tolstoy/src/lib.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,28 @@ extern crate rusqlite;
3232
extern crate uuid;
3333

3434
#[macro_use] pub mod errors;
35-
mod remote_client;
35+
pub use errors::{
36+
TolstoyError,
37+
};
3638

37-
pub mod schema;
39+
pub mod bootstrap;
3840
pub mod metadata;
39-
pub mod tx_processor;
40-
pub mod syncer;
41-
pub mod tx_mapper;
42-
pub use tx_mapper::{
43-
TxMapper,
41+
pub use metadata::{
42+
SyncMetadataClient,
4443
};
44+
mod parts;
45+
mod remote_client;
46+
pub mod schema;
47+
pub mod syncer;
4548
pub use syncer::{
4649
Syncer,
4750
SyncResult,
4851
};
49-
pub use metadata::SyncMetadataClient;
50-
pub use errors::{
51-
TolstoyError,
52-
Result,
52+
pub mod tx_mapper;
53+
pub use tx_mapper::{
54+
TxMapper,
5355
};
56+
pub mod tx_processor;
5457
pub mod types;
5558
pub use types::{
5659
Tx,

tolstoy/src/metadata.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,10 @@ use errors::{
1919
Result,
2020
};
2121

22-
pub trait HeadTrackable {
23-
fn remote_head(tx: &rusqlite::Transaction) -> Result<Uuid>;
24-
fn set_remote_head(tx: &rusqlite::Transaction, uuid: &Uuid) -> Result<()>;
25-
}
26-
2722
pub struct SyncMetadataClient {}
2823

29-
impl HeadTrackable for SyncMetadataClient {
30-
fn remote_head(tx: &rusqlite::Transaction) -> Result<Uuid> {
24+
impl SyncMetadataClient {
25+
pub fn remote_head(tx: &rusqlite::Transaction) -> Result<Uuid> {
3126
tx.query_row(
3227
"SELECT value FROM tolstoy_metadata WHERE key = ?",
3328
&[&schema::REMOTE_HEAD_KEY], |r| {
@@ -37,7 +32,7 @@ impl HeadTrackable for SyncMetadataClient {
3732
)?.map_err(|e| e.into())
3833
}
3934

40-
fn set_remote_head(tx: &rusqlite::Transaction, uuid: &Uuid) -> Result<()> {
35+
pub fn set_remote_head(tx: &rusqlite::Transaction, uuid: &Uuid) -> Result<()> {
4136
let uuid_bytes = uuid.as_bytes().to_vec();
4237
let updated = tx.execute("UPDATE tolstoy_metadata SET value = ? WHERE key = ?",
4338
&[&uuid_bytes, &schema::REMOTE_HEAD_KEY])?;

tolstoy/src/parts.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2018 Mozilla
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4+
// this file except in compliance with the License. You may obtain a copy of the
5+
// License at http://www.apache.org/licenses/LICENSE-2.0
6+
// Unless required by applicable law or agreed to in writing, software distributed
7+
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8+
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9+
// specific language governing permissions and limitations under the License.
10+
11+
use mentat_core::{
12+
Entid,
13+
TypedValue,
14+
Keyword,
15+
};
16+
17+
use types::TxPart;
18+
19+
pub struct PartsHelper<'a> {
20+
parts: &'a Vec<TxPart>,
21+
}
22+
23+
impl<'a> PartsHelper<'a> {
24+
pub fn new(parts: &'a Vec<TxPart>) -> PartsHelper {
25+
PartsHelper {
26+
parts: parts,
27+
}
28+
}
29+
30+
// TODO these are obviously quite inefficient
31+
pub fn e_lookup(&self, e: Keyword) -> Option<Entid> {
32+
// This wraps Keyword (e) in ValueRc (aliased Arc), which is rather expensive.
33+
let kw_e = TypedValue::Keyword(e.into());
34+
35+
for part in self.parts {
36+
if kw_e == part.v {
37+
return Some(part.e);
38+
}
39+
}
40+
41+
None
42+
}
43+
44+
pub fn ea_lookup(&self, e: Keyword, a: Keyword) -> Option<&TypedValue> {
45+
let e_e = self.e_lookup(e);
46+
let a_e = self.e_lookup(a);
47+
48+
if e_e.is_none() || a_e.is_none() {
49+
return None;
50+
}
51+
52+
let e_e = e_e.unwrap();
53+
let a_e = a_e.unwrap();
54+
55+
for part in self.parts {
56+
if part.e == e_e && part.a == a_e {
57+
return Some(&part.v);
58+
}
59+
}
60+
61+
None
62+
}
63+
}

tolstoy/src/remote_client.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ impl RemoteClient {
111111
let uri = uri.parse()?;
112112

113113
d(&format!("parsed uri {:?}", uri));
114-
115114
let work = client.get(uri).and_then(|res| {
116115
println!("Response: {}", res.status());
117116

tolstoy/src/schema.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
use rusqlite;
1212
use errors::Result;
1313

14-
pub static REMOTE_HEAD_KEY: &str = r#"remote_head"#;
14+
pub static REMOTE_HEAD_KEY: &str = r"remote_head";
1515

1616
lazy_static! {
1717
/// SQL statements to be executed, in order, to create the Tolstoy SQL schema (version 1).
1818
#[cfg_attr(rustfmt, rustfmt_skip)]
1919
static ref SCHEMA_STATEMENTS: Vec<&'static str> = { vec![
20-
r#"CREATE TABLE IF NOT EXISTS tolstoy_tu (tx INTEGER PRIMARY KEY, uuid BLOB NOT NULL UNIQUE) WITHOUT ROWID"#,
21-
r#"CREATE TABLE IF NOT EXISTS tolstoy_metadata (key BLOB NOT NULL UNIQUE, value BLOB NOT NULL)"#,
22-
r#"CREATE INDEX IF NOT EXISTS idx_tolstoy_tu_ut ON tolstoy_tu (uuid, tx)"#,
20+
"CREATE TABLE IF NOT EXISTS tolstoy_tu (tx INTEGER PRIMARY KEY, uuid BLOB NOT NULL UNIQUE) WITHOUT ROWID",
21+
"CREATE TABLE IF NOT EXISTS tolstoy_metadata (key BLOB NOT NULL UNIQUE, value BLOB NOT NULL)",
22+
"CREATE INDEX IF NOT EXISTS idx_tolstoy_tu_ut ON tolstoy_tu (uuid, tx)",
2323
]
2424
};
2525
}

0 commit comments

Comments
 (0)