From aea710eb323fd12dbd0ad0b3dc9cc0ed78f1e483 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Tue, 14 Jan 2025 21:49:05 +0100 Subject: [PATCH] Update tables without dropping --- src/core/interface/database/mod.rs | 13 +++++++++++++ src/external/database/sql/backend.rs | 28 ++++++++++++++++++++++----- src/external/database/sql/mod.rs | 23 ++++++++++++++++++---- tests/database/sql.rs | 27 +++++++++++++++++++++++++- tests/files/database/database.db | Bin 20480 -> 28672 bytes tests/lib.rs | 2 +- 6 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/core/interface/database/mod.rs b/src/core/interface/database/mod.rs index 27293d0..cc300f3 100644 --- a/src/core/interface/database/mod.rs +++ b/src/core/interface/database/mod.rs @@ -10,6 +10,9 @@ use crate::core::collection::{self, Collection}; /// Trait for interacting with the database. #[cfg_attr(test, automock)] pub trait IDatabase { + /// Reset all content. + fn reset(&mut self) -> Result<(), SaveError>; + /// Load collection from the database. fn load(&mut self) -> Result; @@ -21,6 +24,10 @@ pub trait IDatabase { pub struct NullDatabase; impl IDatabase for NullDatabase { + fn reset(&mut self) -> Result<(), SaveError> { + Ok(()) + } + fn load(&mut self) -> Result { Ok(vec![]) } @@ -98,6 +105,12 @@ mod tests { use super::*; + #[test] + fn null_database_reset() { + let mut database = NullDatabase; + assert!(database.reset().is_ok()); + } + #[test] fn null_database_load() { let mut database = NullDatabase; diff --git a/src/external/database/sql/backend.rs b/src/external/database/sql/backend.rs index 3ad96db..3a87781 100644 --- a/src/external/database/sql/backend.rs +++ b/src/external/database/sql/backend.rs @@ -108,7 +108,8 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { name TEXT NOT NULL, mbid JSON NOT NULL DEFAULT '\"None\"', sort TEXT NULL, - properties JSON NOT NULL DEFAULT '{}' + properties JSON NOT NULL DEFAULT '{}', + UNIQUE(name, mbid) )", ); Self::execute(&mut stmt, ()) @@ -131,7 +132,8 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { day INT NULL, seq INT NOT NULL, primary_type JSON NOT NULL DEFAULT 'null', - secondary_types JSON NOT NULL DEFAULT '[]' + secondary_types JSON NOT NULL DEFAULT '[]', + UNIQUE(title, lib_id, mbid) )", ); Self::execute(&mut stmt, ()) @@ -145,7 +147,11 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { fn insert_database_version(&self, version: &str) -> Result<(), Error> { let mut stmt = self.prepare_cached( "INSERT INTO database_metadata (name, value) - VALUES (?1, ?2)", + VALUES (?1, ?2) + ON CONFLICT(name) DO UPDATE SET value = ?2 + WHERE EXISTS ( + SELECT 1 EXCEPT SELECT 1 WHERE value = ?2 + )", ); Self::execute(&mut stmt, ("version", version)) } @@ -163,7 +169,11 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { fn insert_artist(&self, artist: &SerializeArtist<'_>) -> Result<(), Error> { let mut stmt = self.prepare_cached( "INSERT INTO artists (name, mbid, sort, properties) - VALUES (?1, ?2, ?3, ?4)", + VALUES (?1, ?2, ?3, ?4) + ON CONFLICT(name, mbid) DO UPDATE SET sort = ?3, properties = ?4 + WHERE EXISTS ( + SELECT 1 EXCEPT SELECT 1 WHERE sort = ?3 AND properties = ?4 + )", ); Self::execute( &mut stmt, @@ -198,7 +208,15 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { let mut stmt = self.prepare_cached( "INSERT INTO albums (title, lib_id, mbid, artist_name, year, month, day, seq, primary_type, secondary_types) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) + ON CONFLICT(title, lib_id, mbid) DO UPDATE SET + artist_name = ?4, year = ?5, month = ?6, day = ?7, seq = ?8, primary_type = ?9, + secondary_types = ?10 + WHERE EXISTS ( + SELECT 1 EXCEPT SELECT 1 WHERE + artist_name = ?4 AND year = ?5 AND month = ?6 AND day = ?7 AND seq = ?8 AND + primary_type = ?9 AND secondary_types = ?10 + )", ); Self::execute( &mut stmt, diff --git a/src/external/database/sql/mod.rs b/src/external/database/sql/mod.rs index 78a3305..711ee86 100644 --- a/src/external/database/sql/mod.rs +++ b/src/external/database/sql/mod.rs @@ -152,6 +152,15 @@ impl ISqlDatabaseBackend<'conn>> SqlDatabase { } impl ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase { + fn reset(&mut self) -> Result<(),SaveError> { + let tx = self.backend.transaction()?; + + Self::drop_tables(&tx)?; + Self::create_tables(&tx)?; + + Ok(tx.commit()?) + } + fn load(&mut self) -> Result { let tx = self.backend.transaction()?; @@ -178,7 +187,6 @@ impl ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase let database: SerializeDatabase = collection.into(); let tx = self.backend.transaction()?; - Self::drop_tables(&tx)?; Self::create_tables(&tx)?; match database { @@ -300,13 +308,22 @@ mod tests { SqlDatabase::new(backend).unwrap() } + #[test] + fn reset() { + let mut tx = MockISqlTransactionBackend::new(); + let mut seq = Sequence::new(); + expect_drop!(tx, seq); + expect_create!(tx, seq); + then0!(tx, seq, expect_commit); + assert!(database(VecDeque::from([tx])).reset().is_ok()); + } + #[test] fn save() { let write_data = FULL_COLLECTION.to_owned(); let mut tx = MockISqlTransactionBackend::new(); let mut seq = Sequence::new(); - expect_drop!(tx, seq); expect_create!(tx, seq); then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103)); for artist in write_data.iter() { @@ -396,7 +413,6 @@ mod tests { fn save_backend_exec_error() { let mut tx = MockISqlTransactionBackend::new(); let mut seq = Sequence::new(); - expect_drop!(tx, seq); expect_create!(tx, seq); then!(tx, seq, expect_insert_database_version) .with(predicate::eq(V20250103)) @@ -426,7 +442,6 @@ mod tests { let mut tx = MockISqlTransactionBackend::new(); let mut seq = Sequence::new(); - expect_drop!(tx, seq); expect_create!(tx, seq); then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103)); then!(tx, seq, expect_insert_artist) diff --git a/tests/database/sql.rs b/tests/database/sql.rs index ea7461a..017167b 100644 --- a/tests/database/sql.rs +++ b/tests/database/sql.rs @@ -9,7 +9,7 @@ use musichoard::{ }; use tempfile::NamedTempFile; -use crate::testlib::COLLECTION; +use crate::{copy_file_into_temp, testlib::COLLECTION}; pub static DATABASE_TEST_FILE: Lazy = Lazy::new(|| fs::canonicalize("./tests/files/database/database.db").unwrap()); @@ -24,6 +24,16 @@ fn expected() -> Collection { expected } +#[test] +fn reset() { + let file = NamedTempFile::new().unwrap(); + + let backend = SqlDatabaseSqliteBackend::new(file.path()).unwrap(); + let mut database = SqlDatabase::new(backend).unwrap(); + + database.reset().unwrap(); +} + #[test] fn save() { let file = NamedTempFile::new().unwrap(); @@ -61,3 +71,18 @@ fn reverse() { let expected = expected(); assert_eq!(read_data, expected); } + +#[test] +fn reverse_with_reset() { + let file = copy_file_into_temp(&*DATABASE_TEST_FILE); + + let backend = SqlDatabaseSqliteBackend::new(file.path()).unwrap(); + let mut database = SqlDatabase::new(backend).unwrap(); + + let expected: Vec = database.load().unwrap(); + database.reset().unwrap(); + database.save(&expected).unwrap(); + let read_data: Vec = database.load().unwrap(); + + assert_eq!(read_data, expected); +} diff --git a/tests/files/database/database.db b/tests/files/database/database.db index d61193509f0fd6070ba013057c4233f9e4f96025..75559ccf868cca8eb0425869f86a4e620282c79a 100644 GIT binary patch delta 636 zcmZozz}WDBae}lU3j+fKI}pPF|3n>QNfrjZssdjA9}H~VaSVL___y+I;XBVA$J@TK z@fnv)iyAAtxTGXwhk8k3Qch}OPEu)ZF@$8>tjTkMaq>kT;rdWN&%jVujgrigoKzi! zoXn*7%oH7k+@#DDO)dpwpsCHqCT=gu$dH+rl3Gz*n3GwO8lPBNk`Ly`Lkx~LgwX6w z63p!4($b7A>=4U~N-~Q}iW4Cei*t~xV~DFlh@;bF1HSzrcLYG)k(ZbYbOtu-by?WN z9i?$v4>1{NKZL=$`5&L9!Q@LehLdO6t4ubxH<}!1XRd5vY+_;vo z4E#;}E_{FZ_VFKJVB!%PxfZCT{A87IeDTd=XZvls)J7B)_7ED%vE zTMIU32vgk7f{n?ZMKzmq@)tYnMGgWIAfq|>_A~IS^WEax&p(U*2|o+}4t{T-(aZT1 zrC5cPB^iU&e5;kh5_3vZm8{HamHhJaQk6I~Sq*_wljZ%Cwb+$dm4WQgyriPk#B5Lm z!j+pqm9r_cs)CfS@{?s}^<%9DvETc7sIthiN&?xzIf;3hiEv#;P+iR0tfnB96Z}Wae}lU69WSSD-go~=R_T2Q6>hxssdjA9}Fx!ybOH*__y+L@bGRH6qv~^ z(_F#AE-op_*zR4Dn3R*6n3GhRTMQvtH&=0AV3g!iKn9wP#mwyD($b90wv+eq-sMM= s-TaTw(qM9rz0u?;c7~HP>{TY8u{Gm1vM@F=u`snTREpjF-=9$c04MM&d;kCd diff --git a/tests/lib.rs b/tests/lib.rs index 551af37..6cceef8 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -19,7 +19,7 @@ use tempfile::NamedTempFile; use crate::testlib::COLLECTION; -fn copy_file_into_temp>(path: P) -> NamedTempFile { +pub fn copy_file_into_temp>(path: P) -> NamedTempFile { let temp = NamedTempFile::new().unwrap(); fs::copy(path.into(), temp.path()).unwrap(); temp