rename Operation to ReplicaOp for clarity

This commit is contained in:
Dustin J. Mitchell 2021-12-19 20:03:01 +00:00
parent 6f7794c7de
commit 0b29efab31
9 changed files with 120 additions and 119 deletions

View file

@ -1,6 +1,6 @@
use crate::errors::Error; use crate::errors::Error;
use crate::server::Server; use crate::server::Server;
use crate::storage::{Operation, Storage, TaskMap}; use crate::storage::{ReplicaOp, Storage, TaskMap};
use crate::task::{Status, Task}; use crate::task::{Status, Task};
use crate::taskdb::TaskDb; use crate::taskdb::TaskDb;
use crate::workingset::WorkingSet; use crate::workingset::WorkingSet;
@ -56,7 +56,7 @@ impl Replica {
S1: Into<String>, S1: Into<String>,
S2: Into<String>, S2: Into<String>,
{ {
self.taskdb.apply(Operation::Update { self.taskdb.apply(ReplicaOp::Update {
uuid, uuid,
property: property.into(), property: property.into(),
value: value.map(|v| v.into()), value: value.map(|v| v.into()),
@ -100,7 +100,7 @@ impl Replica {
/// Create a new task. The task must not already exist. /// Create a new task. The task must not already exist.
pub fn new_task(&mut self, status: Status, description: String) -> anyhow::Result<Task> { pub fn new_task(&mut self, status: Status, description: String) -> anyhow::Result<Task> {
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
self.taskdb.apply(Operation::Create { uuid })?; self.taskdb.apply(ReplicaOp::Create { uuid })?;
trace!("task {} created", uuid); trace!("task {} created", uuid);
let mut task = Task::new(uuid, TaskMap::new()).into_mut(self); let mut task = Task::new(uuid, TaskMap::new()).into_mut(self);
task.set_description(description)?; task.set_description(description)?;
@ -118,7 +118,7 @@ impl Replica {
if self.taskdb.get_task(uuid)?.is_none() { if self.taskdb.get_task(uuid)?.is_none() {
return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); return Err(Error::Database(format!("Task {} does not exist", uuid)).into());
} }
self.taskdb.apply(Operation::Delete { uuid })?; self.taskdb.apply(ReplicaOp::Delete { uuid })?;
trace!("task {} deleted", uuid); trace!("task {} deleted", uuid);
Ok(()) Ok(())
} }

View file

@ -1,6 +1,6 @@
#![allow(clippy::new_without_default)] #![allow(clippy::new_without_default)]
use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use crate::storage::{ReplicaOp, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION};
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
use uuid::Uuid; use uuid::Uuid;
@ -9,7 +9,7 @@ use uuid::Uuid;
struct Data { struct Data {
tasks: HashMap<Uuid, TaskMap>, tasks: HashMap<Uuid, TaskMap>,
base_version: VersionId, base_version: VersionId,
operations: Vec<Operation>, operations: Vec<ReplicaOp>,
working_set: Vec<Option<Uuid>>, working_set: Vec<Option<Uuid>>,
} }
@ -87,16 +87,16 @@ impl<'t> StorageTxn for Txn<'t> {
Ok(()) Ok(())
} }
fn operations(&mut self) -> anyhow::Result<Vec<Operation>> { fn operations(&mut self) -> anyhow::Result<Vec<ReplicaOp>> {
Ok(self.data_ref().operations.clone()) Ok(self.data_ref().operations.clone())
} }
fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { fn add_operation(&mut self, op: ReplicaOp) -> anyhow::Result<()> {
self.mut_data_ref().operations.push(op); self.mut_data_ref().operations.push(op);
Ok(()) Ok(())
} }
fn set_operations(&mut self, ops: Vec<Operation>) -> anyhow::Result<()> { fn set_operations(&mut self, ops: Vec<ReplicaOp>) -> anyhow::Result<()> {
self.mut_data_ref().operations = ops; self.mut_data_ref().operations = ops;
Ok(()) Ok(())
} }

View file

@ -11,14 +11,14 @@ use uuid::Uuid;
mod config; mod config;
mod inmemory; mod inmemory;
mod operation; mod op;
pub(crate) mod sqlite; pub(crate) mod sqlite;
pub use config::StorageConfig; pub use config::StorageConfig;
pub use inmemory::InMemoryStorage; pub use inmemory::InMemoryStorage;
pub use sqlite::SqliteStorage; pub use sqlite::SqliteStorage;
pub use operation::Operation; pub use op::ReplicaOp;
/// An in-memory representation of a task as a simple hashmap /// An in-memory representation of a task as a simple hashmap
pub type TaskMap = HashMap<String, String>; pub type TaskMap = HashMap<String, String>;
@ -80,14 +80,14 @@ pub trait StorageTxn {
/// Get the current set of outstanding operations (operations that have not been sync'd to the /// Get the current set of outstanding operations (operations that have not been sync'd to the
/// server yet) /// server yet)
fn operations(&mut self) -> Result<Vec<Operation>>; fn operations(&mut self) -> Result<Vec<ReplicaOp>>;
/// Add an operation to the end of the list of operations in the storage. Note that this /// Add an operation to the end of the list of operations in the storage. Note that this
/// merely *stores* the operation; it is up to the TaskDb to apply it. /// merely *stores* the operation; it is up to the TaskDb to apply it.
fn add_operation(&mut self, op: Operation) -> Result<()>; fn add_operation(&mut self, op: ReplicaOp) -> Result<()>;
/// Replace the current list of operations with a new list. /// Replace the current list of operations with a new list.
fn set_operations(&mut self, ops: Vec<Operation>) -> Result<()>; fn set_operations(&mut self, ops: Vec<ReplicaOp>) -> Result<()>;
/// Get the entire working set, with each task UUID at its appropriate (1-based) index. /// Get the entire working set, with each task UUID at its appropriate (1-based) index.
/// Element 0 is always None. /// Element 0 is always None.

View file

@ -2,9 +2,10 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
/// An Operation defines a single change to the task database /// A ReplicaOp defines a single change to the task database, as stored locally in the replica.
/// This contains additional information not included in SyncOp.
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum Operation { pub enum ReplicaOp {
/// Create a new task. /// Create a new task.
/// ///
/// On application, if the task already exists, the operation does nothing. /// On application, if the task already exists, the operation does nothing.
@ -27,9 +28,9 @@ pub enum Operation {
}, },
} }
use Operation::*; use ReplicaOp::*;
impl Operation { impl ReplicaOp {
// Transform takes two operations A and B that happened concurrently and produces two // Transform takes two operations A and B that happened concurrently and produces two
// operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This
// function is used to serialize operations in a process similar to a Git "rebase". // function is used to serialize operations in a process similar to a Git "rebase".
@ -53,9 +54,9 @@ impl Operation {
// reached different states, to return to the same state by applying op2' and op1', // reached different states, to return to the same state by applying op2' and op1',
// respectively. // respectively.
pub fn transform( pub fn transform(
operation1: Operation, operation1: ReplicaOp,
operation2: Operation, operation2: ReplicaOp,
) -> (Option<Operation>, Option<Operation>) { ) -> (Option<ReplicaOp>, Option<ReplicaOp>) {
match (&operation1, &operation2) { match (&operation1, &operation2) {
// Two creations or deletions of the same uuid reach the same state, so there's no need // Two creations or deletions of the same uuid reach the same state, so there's no need
// for any further operations to bring the state together. // for any further operations to bring the state together.
@ -135,13 +136,13 @@ mod test {
// thoroughly, so this testing is light. // thoroughly, so this testing is light.
fn test_transform( fn test_transform(
setup: Option<Operation>, setup: Option<ReplicaOp>,
o1: Operation, o1: ReplicaOp,
o2: Operation, o2: ReplicaOp,
exp1p: Option<Operation>, exp1p: Option<ReplicaOp>,
exp2p: Option<Operation>, exp2p: Option<ReplicaOp>,
) { ) {
let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); let (o1p, o2p) = ReplicaOp::transform(o1.clone(), o2.clone());
assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); assert_eq!((&o1p, &o2p), (&exp1p, &exp2p));
// check that the two operation sequences have the same effect, enforcing the invariant of // check that the two operation sequences have the same effect, enforcing the invariant of
@ -349,12 +350,12 @@ mod test {
] ]
} }
fn operation_strategy() -> impl Strategy<Value = Operation> { fn operation_strategy() -> impl Strategy<Value = ReplicaOp> {
prop_oneof![ prop_oneof![
uuid_strategy().prop_map(|uuid| Operation::Create { uuid }), uuid_strategy().prop_map(|uuid| ReplicaOp::Create { uuid }),
uuid_strategy().prop_map(|uuid| Operation::Delete { uuid }), uuid_strategy().prop_map(|uuid| ReplicaOp::Delete { uuid }),
(uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| {
Operation::Update { ReplicaOp::Update {
uuid, uuid,
property, property,
value: Some("true".into()), value: Some("true".into()),
@ -372,30 +373,30 @@ mod test {
// check that the two operation sequences have the same effect, enforcing the invariant of // check that the two operation sequences have the same effect, enforcing the invariant of
// the transform function. // the transform function.
fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) {
let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); let (o1p, o2p) = ReplicaOp::transform(o1.clone(), o2.clone());
let mut db1 = TaskDb::new(Box::new(InMemoryStorage::new())); let mut db1 = TaskDb::new(Box::new(InMemoryStorage::new()));
let mut db2 = TaskDb::new(Box::new(InMemoryStorage::new())); let mut db2 = TaskDb::new(Box::new(InMemoryStorage::new()));
// Ensure that any expected tasks already exist // Ensure that any expected tasks already exist
if let Operation::Update{ ref uuid, .. } = o1 { if let ReplicaOp::Update{ ref uuid, .. } = o1 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()});
} }
if let Operation::Update{ ref uuid, .. } = o2 { if let ReplicaOp::Update{ ref uuid, .. } = o2 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()});
} }
if let Operation::Delete{ ref uuid } = o1 { if let ReplicaOp::Delete{ ref uuid } = o1 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()});
} }
if let Operation::Delete{ ref uuid } = o2 { if let ReplicaOp::Delete{ ref uuid } = o2 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()}); let _ = db1.apply(ReplicaOp::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()}); let _ = db2.apply(ReplicaOp::Create{uuid: uuid.clone()});
} }
// if applying the initial operations fail, that indicates the operation was invalid // if applying the initial operations fail, that indicates the operation was invalid

View file

@ -1,4 +1,4 @@
use crate::storage::{Operation, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use crate::storage::{ReplicaOp, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION};
use anyhow::Context; use anyhow::Context;
use rusqlite::types::{FromSql, ToSql}; use rusqlite::types::{FromSql, ToSql};
use rusqlite::{params, Connection, OptionalExtension}; use rusqlite::{params, Connection, OptionalExtension};
@ -52,17 +52,17 @@ impl ToSql for StoredTaskMap {
} }
} }
/// Stores [`Operation`] in SQLite /// Stores [`ReplicaOp`] in SQLite
impl FromSql for Operation { impl FromSql for ReplicaOp {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> { fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
let o: Operation = serde_json::from_str(value.as_str()?) let o: ReplicaOp = serde_json::from_str(value.as_str()?)
.map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?;
Ok(o) Ok(o)
} }
} }
/// Parsers Operation stored as JSON in string column /// Parses ReplicaOp stored as JSON in string column
impl ToSql for Operation { impl ToSql for ReplicaOp {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> { fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
let s = serde_json::to_string(&self) let s = serde_json::to_string(&self)
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
@ -241,12 +241,12 @@ impl<'t> StorageTxn for Txn<'t> {
Ok(()) Ok(())
} }
fn operations(&mut self) -> anyhow::Result<Vec<Operation>> { fn operations(&mut self) -> anyhow::Result<Vec<ReplicaOp>> {
let t = self.get_txn()?; let t = self.get_txn()?;
let mut q = t.prepare("SELECT data FROM operations ORDER BY id ASC")?; let mut q = t.prepare("SELECT data FROM operations ORDER BY id ASC")?;
let rows = q.query_map([], |r| { let rows = q.query_map([], |r| {
let data: Operation = r.get("data")?; let data: ReplicaOp = r.get("data")?;
Ok(data) Ok(data)
})?; })?;
@ -257,7 +257,7 @@ impl<'t> StorageTxn for Txn<'t> {
Ok(ret) Ok(ret)
} }
fn add_operation(&mut self, op: Operation) -> anyhow::Result<()> { fn add_operation(&mut self, op: ReplicaOp) -> anyhow::Result<()> {
let t = self.get_txn()?; let t = self.get_txn()?;
t.execute("INSERT INTO operations (data) VALUES (?)", params![&op]) t.execute("INSERT INTO operations (data) VALUES (?)", params![&op])
@ -265,7 +265,7 @@ impl<'t> StorageTxn for Txn<'t> {
Ok(()) Ok(())
} }
fn set_operations(&mut self, ops: Vec<Operation>) -> anyhow::Result<()> { fn set_operations(&mut self, ops: Vec<ReplicaOp>) -> anyhow::Result<()> {
let t = self.get_txn()?; let t = self.get_txn()?;
t.execute("DELETE FROM operations", []) t.execute("DELETE FROM operations", [])
.context("Clear all existing operations")?; .context("Clear all existing operations")?;
@ -611,8 +611,8 @@ mod test {
// create some operations // create some operations
{ {
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
txn.add_operation(Operation::Create { uuid: uuid1 })?; txn.add_operation(ReplicaOp::Create { uuid: uuid1 })?;
txn.add_operation(Operation::Create { uuid: uuid2 })?; txn.add_operation(ReplicaOp::Create { uuid: uuid2 })?;
txn.commit()?; txn.commit()?;
} }
@ -623,8 +623,8 @@ mod test {
assert_eq!( assert_eq!(
ops, ops,
vec![ vec![
Operation::Create { uuid: uuid1 }, ReplicaOp::Create { uuid: uuid1 },
Operation::Create { uuid: uuid2 }, ReplicaOp::Create { uuid: uuid2 },
] ]
); );
} }
@ -633,8 +633,8 @@ mod test {
{ {
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
txn.set_operations(vec![ txn.set_operations(vec![
Operation::Delete { uuid: uuid2 }, ReplicaOp::Delete { uuid: uuid2 },
Operation::Delete { uuid: uuid1 }, ReplicaOp::Delete { uuid: uuid1 },
])?; ])?;
txn.commit()?; txn.commit()?;
} }
@ -642,8 +642,8 @@ mod test {
// create some more operations (to test adding operations after clearing) // create some more operations (to test adding operations after clearing)
{ {
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
txn.add_operation(Operation::Create { uuid: uuid3 })?; txn.add_operation(ReplicaOp::Create { uuid: uuid3 })?;
txn.add_operation(Operation::Delete { uuid: uuid3 })?; txn.add_operation(ReplicaOp::Delete { uuid: uuid3 })?;
txn.commit()?; txn.commit()?;
} }
@ -654,10 +654,10 @@ mod test {
assert_eq!( assert_eq!(
ops, ops,
vec![ vec![
Operation::Delete { uuid: uuid2 }, ReplicaOp::Delete { uuid: uuid2 },
Operation::Delete { uuid: uuid1 }, ReplicaOp::Delete { uuid: uuid1 },
Operation::Create { uuid: uuid3 }, ReplicaOp::Create { uuid: uuid3 },
Operation::Delete { uuid: uuid3 }, ReplicaOp::Delete { uuid: uuid3 },
] ]
); );
} }

View file

@ -1,5 +1,5 @@
use crate::server::{Server, SyncOp}; use crate::server::{Server, SyncOp};
use crate::storage::{Operation, Storage, TaskMap}; use crate::storage::{ReplicaOp, Storage, TaskMap};
use uuid::Uuid; use uuid::Uuid;
mod ops; mod ops;
@ -31,7 +31,7 @@ impl TaskDb {
/// Apply an operation to the TaskDb. Aside from synchronization operations, this is the only way /// Apply an operation to the TaskDb. Aside from synchronization operations, this is the only way
/// to modify the TaskDb. In cases where an operation does not make sense, this function will do /// to modify the TaskDb. In cases where an operation does not make sense, this function will do
/// nothing and return an error (but leave the TaskDb in a consistent state). /// nothing and return an error (but leave the TaskDb in a consistent state).
pub fn apply(&mut self, op: Operation) -> anyhow::Result<()> { pub fn apply(&mut self, op: ReplicaOp) -> anyhow::Result<()> {
// TODO: differentiate error types here? // TODO: differentiate error types here?
let mut txn = self.storage.txn()?; let mut txn = self.storage.txn()?;
if let err @ Err(_) = ops::apply_op(txn.as_mut(), &op) { if let err @ Err(_) = ops::apply_op(txn.as_mut(), &op) {
@ -46,14 +46,14 @@ impl TaskDb {
pub fn apply_sync_tmp(&mut self, op: SyncOp) -> anyhow::Result<()> { pub fn apply_sync_tmp(&mut self, op: SyncOp) -> anyhow::Result<()> {
// create an op from SyncOp // create an op from SyncOp
let op = match op { let op = match op {
SyncOp::Create { uuid } => Operation::Create { uuid }, SyncOp::Create { uuid } => ReplicaOp::Create { uuid },
SyncOp::Delete { uuid } => Operation::Delete { uuid }, SyncOp::Delete { uuid } => ReplicaOp::Delete { uuid },
SyncOp::Update { SyncOp::Update {
uuid, uuid,
property, property,
value, value,
timestamp, timestamp,
} => Operation::Update { } => ReplicaOp::Update {
uuid, uuid,
property, property,
value, value,
@ -158,7 +158,7 @@ impl TaskDb {
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn operations(&mut self) -> Vec<Operation> { pub(crate) fn operations(&mut self) -> Vec<ReplicaOp> {
let mut txn = self.storage.txn().unwrap(); let mut txn = self.storage.txn().unwrap();
txn.operations() txn.operations()
.unwrap() .unwrap()
@ -184,7 +184,7 @@ mod tests {
// operations; more detailed tests are in the `ops` module. // operations; more detailed tests are in the `ops` module.
let mut db = TaskDb::new_inmemory(); let mut db = TaskDb::new_inmemory();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let op = Operation::Create { uuid }; let op = ReplicaOp::Create { uuid };
db.apply(op.clone()).unwrap(); db.apply(op.clone()).unwrap();
assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]);
@ -197,7 +197,7 @@ mod tests {
#[derive(Debug)] #[derive(Debug)]
enum Action { enum Action {
Op(Operation), Op(ReplicaOp),
Sync, Sync,
} }
@ -209,14 +209,14 @@ mod tests {
.chunks(2) .chunks(2)
.map(|action_on| { .map(|action_on| {
let action = match action_on[0] { let action = match action_on[0] {
b'C' => Action::Op(Operation::Create { uuid }), b'C' => Action::Op(ReplicaOp::Create { uuid }),
b'U' => Action::Op(Operation::Update { b'U' => Action::Op(ReplicaOp::Update {
uuid, uuid,
property: "title".into(), property: "title".into(),
value: Some("foo".into()), value: Some("foo".into()),
timestamp: Utc::now(), timestamp: Utc::now(),
}), }),
b'D' => Action::Op(Operation::Delete { uuid }), b'D' => Action::Op(ReplicaOp::Delete { uuid }),
b'S' => Action::Sync, b'S' => Action::Sync,
_ => unreachable!(), _ => unreachable!(),
}; };

View file

@ -1,20 +1,20 @@
use crate::errors::Error; use crate::errors::Error;
use crate::storage::{Operation, StorageTxn}; use crate::storage::{ReplicaOp, StorageTxn};
pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &Operation) -> anyhow::Result<()> { pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &ReplicaOp) -> anyhow::Result<()> {
match op { match op {
Operation::Create { uuid } => { ReplicaOp::Create { uuid } => {
// insert if the task does not already exist // insert if the task does not already exist
if !txn.create_task(*uuid)? { if !txn.create_task(*uuid)? {
return Err(Error::Database(format!("Task {} already exists", uuid)).into()); return Err(Error::Database(format!("Task {} already exists", uuid)).into());
} }
} }
Operation::Delete { ref uuid } => { ReplicaOp::Delete { ref uuid } => {
if !txn.delete_task(*uuid)? { if !txn.delete_task(*uuid)? {
return Err(Error::Database(format!("Task {} does not exist", uuid)).into()); return Err(Error::Database(format!("Task {} does not exist", uuid)).into());
} }
} }
Operation::Update { ReplicaOp::Update {
ref uuid, ref uuid,
ref property, ref property,
ref value, ref value,
@ -49,7 +49,7 @@ mod tests {
fn test_apply_create() -> anyhow::Result<()> { fn test_apply_create() -> anyhow::Result<()> {
let mut db = TaskDb::new_inmemory(); let mut db = TaskDb::new_inmemory();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let op = Operation::Create { uuid }; let op = ReplicaOp::Create { uuid };
{ {
let mut txn = db.storage.txn()?; let mut txn = db.storage.txn()?;
@ -65,7 +65,7 @@ mod tests {
fn test_apply_create_exists() -> anyhow::Result<()> { fn test_apply_create_exists() -> anyhow::Result<()> {
let mut db = TaskDb::new_inmemory(); let mut db = TaskDb::new_inmemory();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let op = Operation::Create { uuid }; let op = ReplicaOp::Create { uuid };
{ {
let mut txn = db.storage.txn()?; let mut txn = db.storage.txn()?;
apply_op(txn.as_mut(), &op)?; apply_op(txn.as_mut(), &op)?;
@ -86,7 +86,7 @@ mod tests {
fn test_apply_create_update() -> anyhow::Result<()> { fn test_apply_create_update() -> anyhow::Result<()> {
let mut db = TaskDb::new_inmemory(); let mut db = TaskDb::new_inmemory();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let op1 = Operation::Create { uuid }; let op1 = ReplicaOp::Create { uuid };
{ {
let mut txn = db.storage.txn()?; let mut txn = db.storage.txn()?;
@ -94,7 +94,7 @@ mod tests {
txn.commit()?; txn.commit()?;
} }
let op2 = Operation::Update { let op2 = ReplicaOp::Update {
uuid, uuid,
property: String::from("title"), property: String::from("title"),
value: Some("my task".into()), value: Some("my task".into()),
@ -118,14 +118,14 @@ mod tests {
fn test_apply_create_update_delete_prop() -> anyhow::Result<()> { fn test_apply_create_update_delete_prop() -> anyhow::Result<()> {
let mut db = TaskDb::new_inmemory(); let mut db = TaskDb::new_inmemory();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let op1 = Operation::Create { uuid }; let op1 = ReplicaOp::Create { uuid };
{ {
let mut txn = db.storage.txn()?; let mut txn = db.storage.txn()?;
apply_op(txn.as_mut(), &op1)?; apply_op(txn.as_mut(), &op1)?;
txn.commit()?; txn.commit()?;
} }
let op2 = Operation::Update { let op2 = ReplicaOp::Update {
uuid, uuid,
property: String::from("title"), property: String::from("title"),
value: Some("my task".into()), value: Some("my task".into()),
@ -137,7 +137,7 @@ mod tests {
txn.commit()?; txn.commit()?;
} }
let op3 = Operation::Update { let op3 = ReplicaOp::Update {
uuid, uuid,
property: String::from("priority"), property: String::from("priority"),
value: Some("H".into()), value: Some("H".into()),
@ -149,7 +149,7 @@ mod tests {
txn.commit()?; txn.commit()?;
} }
let op4 = Operation::Update { let op4 = ReplicaOp::Update {
uuid, uuid,
property: String::from("title"), property: String::from("title"),
value: None, value: None,
@ -177,7 +177,7 @@ mod tests {
fn test_apply_update_does_not_exist() -> anyhow::Result<()> { fn test_apply_update_does_not_exist() -> anyhow::Result<()> {
let mut db = TaskDb::new_inmemory(); let mut db = TaskDb::new_inmemory();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let op = Operation::Update { let op = ReplicaOp::Update {
uuid, uuid,
property: String::from("title"), property: String::from("title"),
value: Some("my task".into()), value: Some("my task".into()),
@ -199,8 +199,8 @@ mod tests {
fn test_apply_create_delete() -> anyhow::Result<()> { fn test_apply_create_delete() -> anyhow::Result<()> {
let mut db = TaskDb::new_inmemory(); let mut db = TaskDb::new_inmemory();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let op1 = Operation::Create { uuid }; let op1 = ReplicaOp::Create { uuid };
let op2 = Operation::Delete { uuid }; let op2 = ReplicaOp::Delete { uuid };
{ {
let mut txn = db.storage.txn()?; let mut txn = db.storage.txn()?;
@ -218,7 +218,7 @@ mod tests {
fn test_apply_delete_not_present() -> anyhow::Result<()> { fn test_apply_delete_not_present() -> anyhow::Result<()> {
let mut db = TaskDb::new_inmemory(); let mut db = TaskDb::new_inmemory();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let op = Operation::Delete { uuid }; let op = ReplicaOp::Delete { uuid };
{ {
let mut txn = db.storage.txn()?; let mut txn = db.storage.txn()?;
assert_eq!( assert_eq!(

View file

@ -1,6 +1,6 @@
use super::{ops, snapshot}; use super::{ops, snapshot};
use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency}; use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency};
use crate::storage::{Operation, StorageTxn}; use crate::storage::{ReplicaOp, StorageTxn};
use crate::Error; use crate::Error;
use log::{info, trace, warn}; use log::{info, trace, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,7 +8,7 @@ use std::str;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct Version { struct Version {
operations: Vec<Operation>, operations: Vec<ReplicaOp>,
} }
/// Sync to the given server, pulling remote changes and pushing local changes. /// Sync to the given server, pulling remote changes and pushing local changes.
@ -58,7 +58,7 @@ pub(super) fn sync(
} }
} }
let operations: Vec<Operation> = txn.operations()?.to_vec(); let operations: Vec<ReplicaOp> = txn.operations()?.to_vec();
if operations.is_empty() { if operations.is_empty() {
info!("no changes to push to server"); info!("no changes to push to server");
// nothing to sync back to the server.. // nothing to sync back to the server..
@ -136,7 +136,7 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu
// This is slightly complicated by the fact that the transform function can return None, // This is slightly complicated by the fact that the transform function can return None,
// indicating no operation is required. If this happens for a local op, we can just omit // indicating no operation is required. If this happens for a local op, we can just omit
// it. If it happens for server op, then we must copy the remaining local ops. // it. If it happens for server op, then we must copy the remaining local ops.
let mut local_operations: Vec<Operation> = txn.operations()?; let mut local_operations: Vec<ReplicaOp> = txn.operations()?;
for server_op in version.operations.drain(..) { for server_op in version.operations.drain(..) {
trace!( trace!(
"rebasing local operations onto server operation {:?}", "rebasing local operations onto server operation {:?}",
@ -146,7 +146,7 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu
let mut svr_op = Some(server_op); let mut svr_op = Some(server_op);
for local_op in local_operations.drain(..) { for local_op in local_operations.drain(..) {
if let Some(o) = svr_op { if let Some(o) = svr_op {
let (new_server_op, new_local_op) = Operation::transform(o, local_op.clone()); let (new_server_op, new_local_op) = ReplicaOp::transform(o, local_op.clone());
trace!("local operation {:?} -> {:?}", local_op, new_local_op); trace!("local operation {:?} -> {:?}", local_op, new_local_op);
svr_op = new_server_op; svr_op = new_server_op;
if let Some(o) = new_local_op { if let Some(o) = new_local_op {
@ -175,7 +175,7 @@ fn apply_version(txn: &mut dyn StorageTxn, mut version: Version) -> anyhow::Resu
mod test { mod test {
use super::*; use super::*;
use crate::server::test::TestServer; use crate::server::test::TestServer;
use crate::storage::{InMemoryStorage, Operation}; use crate::storage::{InMemoryStorage, ReplicaOp};
use crate::taskdb::{snapshot::SnapshotTasks, TaskDb}; use crate::taskdb::{snapshot::SnapshotTasks, TaskDb};
use chrono::Utc; use chrono::Utc;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -197,8 +197,8 @@ mod test {
// make some changes in parallel to db1 and db2.. // make some changes in parallel to db1 and db2..
let uuid1 = Uuid::new_v4(); let uuid1 = Uuid::new_v4();
db1.apply(Operation::Create { uuid: uuid1 }).unwrap(); db1.apply(ReplicaOp::Create { uuid: uuid1 }).unwrap();
db1.apply(Operation::Update { db1.apply(ReplicaOp::Update {
uuid: uuid1, uuid: uuid1,
property: "title".into(), property: "title".into(),
value: Some("my first task".into()), value: Some("my first task".into()),
@ -207,8 +207,8 @@ mod test {
.unwrap(); .unwrap();
let uuid2 = Uuid::new_v4(); let uuid2 = Uuid::new_v4();
db2.apply(Operation::Create { uuid: uuid2 }).unwrap(); db2.apply(ReplicaOp::Create { uuid: uuid2 }).unwrap();
db2.apply(Operation::Update { db2.apply(ReplicaOp::Update {
uuid: uuid2, uuid: uuid2,
property: "title".into(), property: "title".into(),
value: Some("my second task".into()), value: Some("my second task".into()),
@ -223,14 +223,14 @@ mod test {
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
// now make updates to the same task on both sides // now make updates to the same task on both sides
db1.apply(Operation::Update { db1.apply(ReplicaOp::Update {
uuid: uuid2, uuid: uuid2,
property: "priority".into(), property: "priority".into(),
value: Some("H".into()), value: Some("H".into()),
timestamp: Utc::now(), timestamp: Utc::now(),
}) })
.unwrap(); .unwrap();
db2.apply(Operation::Update { db2.apply(ReplicaOp::Update {
uuid: uuid2, uuid: uuid2,
property: "project".into(), property: "project".into(),
value: Some("personal".into()), value: Some("personal".into()),
@ -259,8 +259,8 @@ mod test {
// create and update a task.. // create and update a task..
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
db1.apply(Operation::Create { uuid }).unwrap(); db1.apply(ReplicaOp::Create { uuid }).unwrap();
db1.apply(Operation::Update { db1.apply(ReplicaOp::Update {
uuid, uuid,
property: "title".into(), property: "title".into(),
value: Some("my first task".into()), value: Some("my first task".into()),
@ -275,9 +275,9 @@ mod test {
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
// delete and re-create the task on db1 // delete and re-create the task on db1
db1.apply(Operation::Delete { uuid }).unwrap(); db1.apply(ReplicaOp::Delete { uuid }).unwrap();
db1.apply(Operation::Create { uuid }).unwrap(); db1.apply(ReplicaOp::Create { uuid }).unwrap();
db1.apply(Operation::Update { db1.apply(ReplicaOp::Update {
uuid, uuid,
property: "title".into(), property: "title".into(),
value: Some("my second task".into()), value: Some("my second task".into()),
@ -286,7 +286,7 @@ mod test {
.unwrap(); .unwrap();
// and on db2, update a property of the task // and on db2, update a property of the task
db2.apply(Operation::Update { db2.apply(ReplicaOp::Update {
uuid, uuid,
property: "project".into(), property: "project".into(),
value: Some("personal".into()), value: Some("personal".into()),
@ -310,8 +310,8 @@ mod test {
let mut db1 = newdb(); let mut db1 = newdb();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
db1.apply(Operation::Create { uuid })?; db1.apply(ReplicaOp::Create { uuid })?;
db1.apply(Operation::Update { db1.apply(ReplicaOp::Update {
uuid, uuid,
property: "title".into(), property: "title".into(),
value: Some("my first task".into()), value: Some("my first task".into()),
@ -332,7 +332,7 @@ mod test {
assert_eq!(tasks[0].0, uuid); assert_eq!(tasks[0].0, uuid);
// update the taskdb and sync again // update the taskdb and sync again
db1.apply(Operation::Update { db1.apply(ReplicaOp::Update {
uuid, uuid,
property: "title".into(), property: "title".into(),
value: Some("my first task, updated".into()), value: Some("my first task, updated".into()),
@ -362,7 +362,7 @@ mod test {
let mut db1 = newdb(); let mut db1 = newdb();
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
db1.apply(Operation::Create { uuid }).unwrap(); db1.apply(ReplicaOp::Create { uuid }).unwrap();
test_server.set_snapshot_urgency(SnapshotUrgency::Low); test_server.set_snapshot_urgency(SnapshotUrgency::Low);
sync(&mut server, db1.storage.txn()?.as_mut(), true).unwrap(); sync(&mut server, db1.storage.txn()?.as_mut(), true).unwrap();

View file

@ -63,7 +63,7 @@ where
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::storage::Operation; use crate::storage::ReplicaOp;
use crate::taskdb::TaskDb; use crate::taskdb::TaskDb;
use chrono::Utc; use chrono::Utc;
use uuid::Uuid; use uuid::Uuid;
@ -94,10 +94,10 @@ mod test {
// add everything to the TaskDb // add everything to the TaskDb
for uuid in &uuids { for uuid in &uuids {
db.apply(Operation::Create { uuid: *uuid })?; db.apply(ReplicaOp::Create { uuid: *uuid })?;
} }
for i in &[0usize, 1, 4] { for i in &[0usize, 1, 4] {
db.apply(Operation::Update { db.apply(ReplicaOp::Update {
uuid: uuids[*i].clone(), uuid: uuids[*i].clone(),
property: String::from("status"), property: String::from("status"),
value: Some("pending".into()), value: Some("pending".into()),