From 8f1b313522029cf1db6238235220f882992279b5 Mon Sep 17 00:00:00 2001 From: chris Mitchell Date: Wed, 18 Sep 2024 10:26:07 -0400 Subject: [PATCH 1/5] Branch movement --- docs/commands.md | 19 +++ migrations/operations/01-initial/up.sql | 22 ++- src/main.rs | 108 +++++++++++++- src/models/operations.rs | 181 ++++++++++++++++++++++-- src/operation_management.rs | 14 +- 5 files changed, 325 insertions(+), 19 deletions(-) create mode 100644 docs/commands.md diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 0000000..f0bd58b --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,19 @@ +# Branches + +Creating a new branch can be accomplished via `gen --db db_name.db branch --create branch_name`. +Deleting a branch can be accomplished via `gen --db db_name.db branch --delete branch_name`. +To list all available branches, `gen --db db_name.db branch --list`. The current branch will be marked with a `>` before it. +To checkout a branch, `gen --db db_name.db branch --checkout branch_name`. This will migrate the database to the last change +applied in a given branch. + +# Operations + +Operations are changes that have been made to the database. Commands such as `import` and `update` create a new operation. +To see all operations, `gen --db db_name.db operations` will list operations. The operation the database currently is on +will be prefixed with a `>`. + +# Checkout + +Checkouts allow a user to migrate the database to different states. To move the database to a given operation, the +command in `gen --db db_name.db checkout -b branch_name operation_id`. If no branch name is specified, the current +branch will be used. The operation_id corresponds to the operation in `operations`. \ No newline at end of file diff --git a/migrations/operations/01-initial/up.sql b/migrations/operations/01-initial/up.sql index 846a4cd..e852c0e 100644 --- a/migrations/operations/01-initial/up.sql +++ b/migrations/operations/01-initial/up.sql @@ -1,7 +1,9 @@ CREATE TABLE operation_state ( db_uuid TEXT PRIMARY KEY NOT NULL, operation_id INTEGER, - FOREIGN KEY(operation_id) REFERENCES operation(id) + branch_id INTEGER, + FOREIGN KEY(operation_id) REFERENCES operation(id), + FOREIGN KEY(branch_id) REFERENCES branch(id) ) STRICT; CREATE TABLE operation ( @@ -26,3 +28,21 @@ CREATE TABLE operation_summary ( summary TEXT NOT NULL, FOREIGN KEY(operation_id) REFERENCES operation(id) ) STRICT; + +CREATE TABLE branch ( + id INTEGER PRIMARY KEY NOT NULL, + db_uuid TEXT NOT NULL, + name TEXT NOT NULL, + start_operation_id INTEGER, + current_operation_id INTEGER +) STRICT; +CREATE UNIQUE INDEX branch_uidx ON branch(db_uuid, name); + +CREATE TABLE branch_operation ( + id INTEGER PRIMARY KEY NOT NULL, + branch_id INTEGER NOT NULL, + operation_id INTEGER NOT NULL, + FOREIGN KEY(branch_id) REFERENCES branch(id), + FOREIGN KEY(operation_id) REFERENCES operation(id) +) STRICT; +CREATE UNIQUE INDEX branch_operation_uidx ON branch_operation(branch_id, operation_id); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b04f3fa..e6b52cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use gen::get_connection; use gen::imports::fasta::import_fasta; use gen::imports::gfa::import_gfa; use gen::models::metadata; -use gen::models::operations::{Operation, OperationState}; +use gen::models::operations::{setup_db, Branch, Operation, OperationState}; use gen::operation_management; use gen::updates::vcf::update_with_vcf; use rusqlite::types::Value; @@ -63,8 +63,29 @@ enum Commands { }, /// Initialize a gen repository Init {}, + /// Manage and create branches + Branch { + /// Create a branch with the given name + #[arg(long, action)] + create: bool, + /// Delete a given branch + #[arg(short, long, action)] + delete: bool, + /// Checkout a given branch + #[arg(long, action)] + checkout: bool, + /// List all branches + #[arg(short, long, action)] + list: bool, + /// The branch name + #[clap(index = 1)] + branch_name: Option, + }, /// Migrate a database to a given operation Checkout { + /// The branch identifier to migrate to + #[arg(short, long)] + branch: Option, /// The operation id to move to #[clap(index = 1)] id: i32, @@ -96,6 +117,9 @@ fn main() { let db_uuid = metadata::get_db_uuid(&conn); let operation_conn = get_operation_connection(); + // initialize the selected database if needed. + setup_db(&operation_conn, &db_uuid); + match &cli.command { Some(Commands::Import { fasta, @@ -171,8 +195,86 @@ fn main() { ); } } - Some(Commands::Checkout { id }) => { - operation_management::move_to(&conn, &Operation::get_by_id(&operation_conn, *id)); + Some(Commands::Branch { + create, + delete, + checkout, + list, + branch_name, + }) => { + if *create { + Branch::create( + &operation_conn, + &db_uuid, + &branch_name + .clone() + .expect("Must provide a branch name to create."), + ); + } else if *delete { + Branch::delete( + &operation_conn, + &db_uuid, + &branch_name + .clone() + .expect("Must provide a branch name to delete."), + ); + } else if *checkout { + OperationState::set_branch( + &operation_conn, + &db_uuid, + &branch_name + .clone() + .expect("Must provide a branch name to checkout."), + ); + let branch = + Branch::get_by_name(&operation_conn, &db_uuid, &branch_name.clone().unwrap()) + .unwrap(); + operation_management::move_to( + &conn, + &operation_conn, + &Operation::get_by_id(&operation_conn, branch.current_operation_id.unwrap()), + ); + } else if *list { + let current_branch = OperationState::get_current_branch(&operation_conn, &db_uuid); + let mut indicator = ""; + println!( + "{indicator:<3}{col1:<30} {col2:<20}", + col1 = "Name", + col2 = "Operation", + ); + for branch in Branch::query( + &operation_conn, + "select * from branch where db_uuid = ?1", + vec![Value::from(db_uuid.to_string())], + ) + .iter() + { + if let Some(current_branch_id) = current_branch { + if current_branch_id == branch.id { + indicator = ">"; + } else { + indicator = ""; + } + } + println!( + "{indicator:<3}{col1:<30} {col2:<20}", + col1 = branch.name, + col2 = branch.current_operation_id.unwrap_or(-1) + ); + } + } else { + println!("No options selected."); + } + } + Some(Commands::Checkout { branch, id }) => { + if let Some(branch_name) = branch { + OperationState::set_branch(&operation_conn, &db_uuid, branch_name); + } + operation_management::move_to( + &conn, + &operation_conn, + &Operation::get_by_id(&operation_conn, *id), + ); } Some(Commands::Export { name, gfa }) => { conn.execute("BEGIN TRANSACTION", []).unwrap(); diff --git a/src/models/operations.rs b/src/models/operations.rs index bb9944e..1e65c25 100644 --- a/src/models/operations.rs +++ b/src/models/operations.rs @@ -19,7 +19,7 @@ pub struct Operation { impl Operation { pub fn create( conn: &Connection, - db_uuid: &String, + db_uuid: &str, collection_name: &str, change_type: &str, change_id: i32, @@ -30,7 +30,7 @@ impl Operation { let mut rows = stmt .query_map( params_from_iter(vec![ - Value::from(db_uuid.clone()), + Value::from(db_uuid.to_string()), Value::from(collection_name.to_string()), Value::from(change_type.to_string()), Value::from(change_id), @@ -39,7 +39,7 @@ impl Operation { |row| { Ok(Operation { id: row.get(0)?, - db_uuid: db_uuid.clone(), + db_uuid: db_uuid.to_string(), parent_id: current_op, collection_name: collection_name.to_string(), change_type: change_type.to_string(), @@ -247,12 +247,118 @@ impl OperationSummary { } } +#[derive(Clone, Debug)] +pub struct Branch { + pub id: i32, + pub db_uuid: String, + pub name: String, + pub start_operation_id: Option, + pub current_operation_id: Option, +} + +impl Branch { + pub fn create(conn: &Connection, db_uuid: &str, branch_name: &str) -> Branch { + let current_operation_id = OperationState::get_operation(conn, db_uuid); + let mut stmt = conn.prepare_cached("insert into branch (db_uuid, name, start_operation_id, current_operation_id) values (?1, ?2, ?3, ?3) returning (id);").unwrap(); + + let mut rows = stmt + .query_map((db_uuid, branch_name, current_operation_id), |row| { + Ok(Branch { + id: row.get(0)?, + db_uuid: db_uuid.to_string(), + name: branch_name.to_string(), + start_operation_id: current_operation_id, + current_operation_id, + }) + }) + .unwrap(); + match rows.next().unwrap() { + Ok(res) => res, + Err(rusqlite::Error::SqliteFailure(err, details)) => { + if err.code == rusqlite::ErrorCode::ConstraintViolation { + panic!("Branch already exists"); + } else { + panic!("something bad happened querying the database {err:?} {details:?}"); + } + } + Err(_) => { + panic!("something bad happened querying the database"); + } + } + } + + pub fn delete(conn: &Connection, db_uuid: &str, branch_name: &str) { + if let Some(branch) = Branch::get_by_name(conn, db_uuid, branch_name) { + let branch_id = branch.id; + if let Some(current_branch) = OperationState::get_current_branch(conn, db_uuid) { + if current_branch == branch_id { + panic!("Unable to delete the branch that is currently active."); + } + } + conn.execute( + "delete from branch_operation where branch_id = ?1", + (branch_id,), + ) + .expect("Error deleting from branch_operation table."); + conn.execute("delete from branch where id = ?1", (branch_id,)) + .expect("Error deleting from branch table."); + } else { + panic!("No branch named {branch_name} in database."); + } + } + + pub fn query(conn: &Connection, query: &str, placeholders: Vec) -> Vec { + let mut stmt = conn.prepare(query).unwrap(); + let rows = stmt + .query_map(params_from_iter(placeholders), |row| { + Ok(Branch { + id: row.get(0)?, + db_uuid: row.get(1)?, + name: row.get(2)?, + start_operation_id: row.get(3)?, + current_operation_id: row.get(4)?, + }) + }) + .unwrap(); + let mut objs = vec![]; + for row in rows { + objs.push(row.unwrap()); + } + objs + } + + pub fn get_by_name(conn: &Connection, db_uuid: &str, branch_name: &str) -> Option { + let mut branch: Option = None; + for result in Branch::query( + conn, + "select * from branch where db_uuid = ?1 and name = ?2", + vec![ + Value::from(db_uuid.to_string()), + Value::from(branch_name.to_string()), + ], + ) + .iter() + { + branch = Some(result.clone()); + } + branch + } + + pub fn set_current_operation(conn: &Connection, branch_id: i32, operation_id: i32) { + conn.execute( + "UPDATE branch set current_operation_id = ?2 where id = ?1", + (branch_id, operation_id), + ) + .unwrap(); + } +} + pub struct OperationState { operation_id: i32, } impl OperationState { - pub fn set_operation(conn: &Connection, db_uuid: &String, op_id: i32) { + pub fn set_operation(conn: &Connection, db_uuid: &str, op_id: i32) { let mut stmt = conn .prepare( "INSERT INTO operation_state (db_uuid, operation_id) @@ -261,18 +367,77 @@ impl OperationState { UPDATE SET operation_id=excluded.operation_id;", ) .unwrap(); - stmt.execute((db_uuid, op_id)).unwrap(); + stmt.execute((db_uuid.to_string(), op_id)).unwrap(); + let branch_id = + OperationState::get_current_branch(conn, db_uuid).expect("No current branch set."); + Branch::set_current_operation(conn, branch_id, op_id); } - pub fn get_operation(conn: &Connection, db_uuid: &String) -> Option { + pub fn get_operation(conn: &Connection, db_uuid: &str) -> Option { let mut id: Option = None; let mut stmt = conn .prepare("SELECT operation_id from operation_state where db_uuid = ?1;") .unwrap(); - let rows = stmt.query_map((db_uuid,), |row| row.get(0)).unwrap(); + let rows = stmt + .query_map((db_uuid.to_string(),), |row| row.get(0)) + .unwrap(); + for row in rows { + id = row.unwrap(); + } + id + } + + pub fn set_branch(conn: &Connection, db_uuid: &str, branch_name: &str) { + let branch = Branch::get_by_name(conn, db_uuid, branch_name) + .unwrap_or_else(|| panic!("No branch named {branch_name}.")); + let mut stmt = conn + .prepare( + "INSERT INTO operation_state (db_uuid, branch_id) + VALUES (?1, ?2) + ON CONFLICT (db_uuid) DO + UPDATE SET branch_id=excluded.branch_id;", + ) + .unwrap(); + println!("setting branc to {branch_name}"); + stmt.execute(params_from_iter(vec![ + Value::from(db_uuid.to_string()), + Value::from(branch.id), + ])) + .unwrap(); + if let Some(current_branch_id) = OperationState::get_current_branch(conn, db_uuid) { + if current_branch_id != branch.id { + panic!("Failed to set branch to {branch_name}"); + } + } else { + panic!("Failed to set branch."); + } + } + + pub fn get_current_branch(conn: &Connection, db_uuid: &str) -> Option { + let mut id: Option = None; + let mut stmt = conn + .prepare("SELECT branch_id from operation_state where db_uuid = ?1;") + .unwrap(); + let rows = stmt + .query_map((db_uuid.to_string(),), |row| row.get(0)) + .unwrap(); for row in rows { - id = Some(row.unwrap()); + id = row.unwrap(); } id } } + +pub fn setup_db(conn: &Connection, db_uuid: &str) { + // check if the database is known. If not, initialize it. + if Branch::query( + conn, + "select * from branch where db_uuid = ?1", + vec![Value::from(db_uuid.to_string())], + ) + .is_empty() + { + Branch::create(conn, db_uuid, "main"); + OperationState::set_branch(conn, db_uuid, "main"); + } +} diff --git a/src/operation_management.rs b/src/operation_management.rs index ce566bf..e763ec4 100644 --- a/src/operation_management.rs +++ b/src/operation_management.rs @@ -71,10 +71,10 @@ pub fn revert_changeset(conn: &Connection, operation: &Operation) { conn.pragma_update(None, "foreign_keys", "1").unwrap(); } -pub fn move_to(conn: &Connection, operation: &Operation) { - let current_op_id = OperationState::get_operation(conn, &operation.db_uuid).unwrap(); +pub fn move_to(conn: &Connection, operation_conn: &Connection, operation: &Operation) { + let current_op_id = OperationState::get_operation(operation_conn, &operation.db_uuid).unwrap(); let op_id = operation.id; - let path = Operation::get_path_between(conn, current_op_id, op_id); + let path = Operation::get_path_between(operation_conn, current_op_id, op_id); if path.is_empty() { println!("No path exists from {current_op_id} to {op_id}."); return; @@ -83,13 +83,13 @@ pub fn move_to(conn: &Connection, operation: &Operation) { match direction { Direction::Outgoing => { println!("Reverting operation {operation_id}"); - revert_changeset(conn, &Operation::get_by_id(conn, *operation_id)); - OperationState::set_operation(conn, &operation.db_uuid, *next_op); + revert_changeset(conn, &Operation::get_by_id(operation_conn, *operation_id)); + OperationState::set_operation(operation_conn, &operation.db_uuid, *next_op); } Direction::Incoming => { println!("Applying operation {operation_id}"); - apply_changeset(conn, &Operation::get_by_id(conn, *operation_id)); - OperationState::set_operation(conn, &operation.db_uuid, *operation_id); + apply_changeset(conn, &Operation::get_by_id(operation_conn, *operation_id)); + OperationState::set_operation(operation_conn, &operation.db_uuid, *operation_id); } } } From 2173497f966b767d1368b5a3ef00117f16a05117 Mon Sep 17 00:00:00 2001 From: chris Mitchell Date: Wed, 18 Sep 2024 11:14:04 -0400 Subject: [PATCH 2/5] Tests for checkout --- fixtures/simple2.vcf | 63 +++++++++++++++ src/main.rs | 39 +++++---- src/operation_management.rs | 154 +++++++++++++++++++++++++++++++++++- 3 files changed, 234 insertions(+), 22 deletions(-) create mode 100644 fixtures/simple2.vcf diff --git a/fixtures/simple2.vcf b/fixtures/simple2.vcf new file mode 100644 index 0000000..ee75ad4 --- /dev/null +++ b/fixtures/simple2.vcf @@ -0,0 +1,63 @@ +##fileformat=VCFv4.1 +##filedate=Tue Sep 4 13:12:57 2018 +##reference=simple.fa +##contig= +##phasing=none +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##INFO= +##filter="QUAL > 1" +##FILTER= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +##FORMAT= +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT foo +m123 13 . ATC AC 1611.92 . AB=0;ABP=0;AC=2;AF=1;AN=2;AO=53;CIGAR=1M1D1M;DP=56;DPB=38;DPRA=0;EPP=3.37904;EPPR=0;GTI=0;LEN=1;MEANALT=3;MQM=60;MQMR=0;NS=1;NUMALT=1;ODDS=76.9802;PAIRED=1;PAIREDR=0;PAO=0;PQA=0;PQR=0;PRO=0;QA=1837;QR=0;RO=0;RPL=18;RPP=14.851;RPPR=0;RPR=35;RUN=1;SAF=27;SAP=3.05127;SAR=26;SRF=0;SRP=0;SRR=0;TYPE=del GT:DP:AD:RO:QR:AO:QA:GL 1/1:56:0,53:0:0:53:1837:-165.301,-15.9546,0 diff --git a/src/main.rs b/src/main.rs index e6b52cd..7543b37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -219,20 +219,17 @@ fn main() { .expect("Must provide a branch name to delete."), ); } else if *checkout { - OperationState::set_branch( - &operation_conn, - &db_uuid, - &branch_name - .clone() - .expect("Must provide a branch name to checkout."), - ); - let branch = - Branch::get_by_name(&operation_conn, &db_uuid, &branch_name.clone().unwrap()) - .unwrap(); - operation_management::move_to( + operation_management::checkout( &conn, &operation_conn, - &Operation::get_by_id(&operation_conn, branch.current_operation_id.unwrap()), + &db_uuid, + &Some( + branch_name + .clone() + .expect("Must provide a branch name to checkout.") + .to_string(), + ), + None, ); } else if *list { let current_branch = OperationState::get_current_branch(&operation_conn, &db_uuid); @@ -267,14 +264,16 @@ fn main() { } } Some(Commands::Checkout { branch, id }) => { - if let Some(branch_name) = branch { - OperationState::set_branch(&operation_conn, &db_uuid, branch_name); - } - operation_management::move_to( - &conn, - &operation_conn, - &Operation::get_by_id(&operation_conn, *id), - ); + operation_management::checkout(&conn, &operation_conn, &db_uuid, branch, Some(*id)); + // + // if let Some(branch_name) = branch { + // OperationState::set_branch(&operation_conn, &db_uuid, branch_name); + // } + // operation_management::move_to( + // &conn, + // &operation_conn, + // &Operation::get_by_id(&operation_conn, *id), + // ); } Some(Commands::Export { name, gfa }) => { conn.execute("BEGIN TRANSACTION", []).unwrap(); diff --git a/src/operation_management.rs b/src/operation_management.rs index e763ec4..7b37bf2 100644 --- a/src/operation_management.rs +++ b/src/operation_management.rs @@ -4,7 +4,8 @@ use std::io::{IsTerminal, Read, Write}; use std::{env, fs, path::PathBuf}; use crate::config::get_changeset_path; -use crate::models::operations::{Operation, OperationState}; +use crate::models::operations::{Branch, Operation, OperationState}; +use crate::operation_management; enum FileMode { Read, @@ -110,15 +111,48 @@ pub fn attach_session(session: &mut session::Session) { } } +pub fn checkout( + conn: &Connection, + operation_conn: &Connection, + db_uuid: &str, + branch_name: &Option, + operation_id: Option, +) { + let mut branch_id = 0; + let mut dest_op_id = operation_id.unwrap_or(0); + if let Some(name) = branch_name { + let current_branch = OperationState::get_current_branch(operation_conn, db_uuid) + .expect("No current branch set"); + let branch = Branch::get_by_name(operation_conn, db_uuid, name) + .unwrap_or_else(|| panic!("No branch named {name}")); + branch_id = branch.id; + if current_branch != branch_id { + OperationState::set_branch(operation_conn, db_uuid, name); + } + if dest_op_id == 0 { + dest_op_id = branch.current_operation_id.unwrap(); + } + } + if dest_op_id == 0 { + panic!("No operation defined."); + } + move_to( + conn, + operation_conn, + &Operation::get_by_id(operation_conn, dest_op_id), + ); +} + #[cfg(test)] mod tests { use super::*; use crate::imports::fasta::import_fasta; use crate::models::file_types::FileTypes; - use crate::models::operations::{FileAddition, Operation, OperationState}; + use crate::models::operations::{setup_db, Branch, FileAddition, Operation, OperationState}; use crate::models::{edge::Edge, metadata, Sample}; use crate::test_helpers::{get_connection, get_operation_connection, setup_gen_dir}; use crate::updates::vcf::update_with_vcf; + use std::path::{Path, PathBuf}; #[test] fn test_writes_operation_id() { @@ -126,6 +160,7 @@ mod tests { let conn = &get_connection(None); let db_uuid = metadata::get_db_uuid(conn); let op_conn = &get_operation_connection(None); + setup_db(op_conn, &db_uuid); let change = FileAddition::create(op_conn, "test", FileTypes::Fasta); let operation = Operation::create(op_conn, &db_uuid, "test", "test", change.id); OperationState::set_operation(op_conn, &db_uuid, operation.id); @@ -142,6 +177,7 @@ mod tests { let conn = &mut get_connection(None); let db_uuid = metadata::get_db_uuid(conn); let operation_conn = &get_operation_connection(None); + setup_db(operation_conn, &db_uuid); let collection = "test".to_string(); import_fasta( &fasta_path.to_str().unwrap().to_string(), @@ -205,4 +241,118 @@ mod tests { assert_eq!(sample_count, 3); assert_eq!(op_count, 2); } + + #[test] + fn test_branch_movement() { + setup_gen_dir(); + let fasta_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures/simple.fa"); + let vcf_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures/simple.vcf"); + let vcf2_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures/simple2.vcf"); + let conn = &mut get_connection(None); + let db_uuid = metadata::get_db_uuid(conn); + let operation_conn = &get_operation_connection(None); + setup_db(operation_conn, &db_uuid); + let collection = "test".to_string(); + import_fasta( + &fasta_path.to_str().unwrap().to_string(), + &collection, + false, + conn, + operation_conn, + ); + let edge_count = Edge::query(conn, "select * from edges", vec![]).len() as i32; + let sample_count = Sample::query(conn, "select * from sample", vec![]).len() as i32; + let op_count = + Operation::query(operation_conn, "select * from operation", vec![]).len() as i32; + assert_eq!(edge_count, 2); + assert_eq!(sample_count, 0); + assert_eq!(op_count, 1); + + let branch_1 = Branch::create(operation_conn, &db_uuid, "branch_1"); + + let branch_2 = Branch::create(operation_conn, &db_uuid, "branch_2"); + + OperationState::set_branch(operation_conn, &db_uuid, "branch_1"); + assert_eq!( + OperationState::get_current_branch(operation_conn, &db_uuid).unwrap(), + branch_1.id + ); + + update_with_vcf( + &vcf_path.to_str().unwrap().to_string(), + &collection, + "".to_string(), + "".to_string(), + conn, + operation_conn, + ); + let edge_count = Edge::query(conn, "select * from edges", vec![]).len() as i32; + let sample_count = Sample::query(conn, "select * from sample", vec![]).len() as i32; + let op_count = + Operation::query(operation_conn, "select * from operation", vec![]).len() as i32; + assert_eq!(edge_count, 10); + assert_eq!(sample_count, 3); + assert_eq!(op_count, 2); + + // checkout branch 2 + checkout( + conn, + operation_conn, + &db_uuid, + &Some("branch_2".to_string()), + None, + ); + + assert_eq!( + OperationState::get_current_branch(operation_conn, &db_uuid).unwrap(), + branch_2.id + ); + + // ensure branch 1 operations have been undone + let edge_count = Edge::query(conn, "select * from edges", vec![]).len() as i32; + let sample_count = Sample::query(conn, "select * from sample", vec![]).len() as i32; + let op_count = + Operation::query(operation_conn, "select * from operation", vec![]).len() as i32; + assert_eq!(edge_count, 2); + assert_eq!(sample_count, 0); + assert_eq!(op_count, 2); + + // apply vcf2 + update_with_vcf( + &vcf2_path.to_str().unwrap().to_string(), + &collection, + "".to_string(), + "".to_string(), + conn, + operation_conn, + ); + let edge_count = Edge::query(conn, "select * from edges", vec![]).len() as i32; + let sample_count = Sample::query(conn, "select * from sample", vec![]).len() as i32; + let op_count = + Operation::query(operation_conn, "select * from operation", vec![]).len() as i32; + assert_eq!(edge_count, 6); + assert_eq!(sample_count, 1); + assert_eq!(op_count, 3); + + // migrate to branch 1 again + checkout( + conn, + operation_conn, + &db_uuid, + &Some("branch_1".to_string()), + None, + ); + assert_eq!( + OperationState::get_current_branch(operation_conn, &db_uuid).unwrap(), + branch_1.id + ); + + let edge_count = Edge::query(conn, "select * from edges", vec![]).len() as i32; + let sample_count = Sample::query(conn, "select * from sample", vec![]).len() as i32; + let op_count = + Operation::query(operation_conn, "select * from operation", vec![]).len() as i32; + assert_eq!(edge_count, 10); + assert_eq!(sample_count, 3); + assert_eq!(op_count, 3); + } } From d3b1fab2cf09aafa012f0376022329a1458efa6e Mon Sep 17 00:00:00 2001 From: chris Mitchell Date: Wed, 18 Sep 2024 11:15:51 -0400 Subject: [PATCH 3/5] Remove unused table --- migrations/operations/01-initial/up.sql | 9 --------- src/models/operations.rs | 5 ----- 2 files changed, 14 deletions(-) diff --git a/migrations/operations/01-initial/up.sql b/migrations/operations/01-initial/up.sql index e852c0e..161da3d 100644 --- a/migrations/operations/01-initial/up.sql +++ b/migrations/operations/01-initial/up.sql @@ -37,12 +37,3 @@ CREATE TABLE branch ( current_operation_id INTEGER ) STRICT; CREATE UNIQUE INDEX branch_uidx ON branch(db_uuid, name); - -CREATE TABLE branch_operation ( - id INTEGER PRIMARY KEY NOT NULL, - branch_id INTEGER NOT NULL, - operation_id INTEGER NOT NULL, - FOREIGN KEY(branch_id) REFERENCES branch(id), - FOREIGN KEY(operation_id) REFERENCES operation(id) -) STRICT; -CREATE UNIQUE INDEX branch_operation_uidx ON branch_operation(branch_id, operation_id); \ No newline at end of file diff --git a/src/models/operations.rs b/src/models/operations.rs index 1e65c25..2710d02 100644 --- a/src/models/operations.rs +++ b/src/models/operations.rs @@ -295,11 +295,6 @@ impl Branch { panic!("Unable to delete the branch that is currently active."); } } - conn.execute( - "delete from branch_operation where branch_id = ?1", - (branch_id,), - ) - .expect("Error deleting from branch_operation table."); conn.execute("delete from branch where id = ?1", (branch_id,)) .expect("Error deleting from branch table."); } else { From 6f928e1eb6b69f3e42dd6c7e3100eefe02062f87 Mon Sep 17 00:00:00 2001 From: chris Mitchell Date: Wed, 18 Sep 2024 11:17:06 -0400 Subject: [PATCH 4/5] Remove comment --- src/main.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7543b37..fabf707 100644 --- a/src/main.rs +++ b/src/main.rs @@ -265,15 +265,6 @@ fn main() { } Some(Commands::Checkout { branch, id }) => { operation_management::checkout(&conn, &operation_conn, &db_uuid, branch, Some(*id)); - // - // if let Some(branch_name) = branch { - // OperationState::set_branch(&operation_conn, &db_uuid, branch_name); - // } - // operation_management::move_to( - // &conn, - // &operation_conn, - // &Operation::get_by_id(&operation_conn, *id), - // ); } Some(Commands::Export { name, gfa }) => { conn.execute("BEGIN TRANSACTION", []).unwrap(); From acef1ad05e93a28068fd71cc41369dc199441521 Mon Sep 17 00:00:00 2001 From: chris Mitchell Date: Wed, 18 Sep 2024 11:19:22 -0400 Subject: [PATCH 5/5] Protect main branch --- src/models/operations.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/models/operations.rs b/src/models/operations.rs index 2710d02..629a268 100644 --- a/src/models/operations.rs +++ b/src/models/operations.rs @@ -288,6 +288,9 @@ impl Branch { } pub fn delete(conn: &Connection, db_uuid: &str, branch_name: &str) { + if branch_name == "main" { + panic!("Main branch cannot be deleted"); + } if let Some(branch) = Branch::get_by_name(conn, db_uuid, branch_name) { let branch_id = branch.id; if let Some(current_branch) = OperationState::get_current_branch(conn, db_uuid) {