Update tables without dropping
Some checks failed
Cargo CI / Build and Test (pull_request) Successful in 2m53s
Cargo CI / Lint (pull_request) Failing after 1m16s

This commit is contained in:
Wojciech Kozlowski 2025-01-14 21:49:05 +01:00
parent 64f3e2d8b7
commit aea710eb32
6 changed files with 82 additions and 11 deletions

View File

@ -10,6 +10,9 @@ use crate::core::collection::{self, Collection};
/// Trait for interacting with the database. /// Trait for interacting with the database.
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait IDatabase { pub trait IDatabase {
/// Reset all content.
fn reset(&mut self) -> Result<(), SaveError>;
/// Load collection from the database. /// Load collection from the database.
fn load(&mut self) -> Result<Collection, LoadError>; fn load(&mut self) -> Result<Collection, LoadError>;
@ -21,6 +24,10 @@ pub trait IDatabase {
pub struct NullDatabase; pub struct NullDatabase;
impl IDatabase for NullDatabase { impl IDatabase for NullDatabase {
fn reset(&mut self) -> Result<(), SaveError> {
Ok(())
}
fn load(&mut self) -> Result<Collection, LoadError> { fn load(&mut self) -> Result<Collection, LoadError> {
Ok(vec![]) Ok(vec![])
} }
@ -98,6 +105,12 @@ mod tests {
use super::*; use super::*;
#[test]
fn null_database_reset() {
let mut database = NullDatabase;
assert!(database.reset().is_ok());
}
#[test] #[test]
fn null_database_load() { fn null_database_load() {
let mut database = NullDatabase; let mut database = NullDatabase;

View File

@ -108,7 +108,8 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
name TEXT NOT NULL, name TEXT NOT NULL,
mbid JSON NOT NULL DEFAULT '\"None\"', mbid JSON NOT NULL DEFAULT '\"None\"',
sort TEXT NULL, sort TEXT NULL,
properties JSON NOT NULL DEFAULT '{}' properties JSON NOT NULL DEFAULT '{}',
UNIQUE(name, mbid)
)", )",
); );
Self::execute(&mut stmt, ()) Self::execute(&mut stmt, ())
@ -131,7 +132,8 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
day INT NULL, day INT NULL,
seq INT NOT NULL, seq INT NOT NULL,
primary_type JSON NOT NULL DEFAULT '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, ()) Self::execute(&mut stmt, ())
@ -145,7 +147,11 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
fn insert_database_version(&self, version: &str) -> Result<(), Error> { fn insert_database_version(&self, version: &str) -> Result<(), Error> {
let mut stmt = self.prepare_cached( let mut stmt = self.prepare_cached(
"INSERT INTO database_metadata (name, value) "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)) Self::execute(&mut stmt, ("version", version))
} }
@ -163,7 +169,11 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
fn insert_artist(&self, artist: &SerializeArtist<'_>) -> Result<(), Error> { fn insert_artist(&self, artist: &SerializeArtist<'_>) -> Result<(), Error> {
let mut stmt = self.prepare_cached( let mut stmt = self.prepare_cached(
"INSERT INTO artists (name, mbid, sort, properties) "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( Self::execute(
&mut stmt, &mut stmt,
@ -198,7 +208,15 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
let mut stmt = self.prepare_cached( let mut stmt = self.prepare_cached(
"INSERT INTO albums (title, lib_id, mbid, artist_name, "INSERT INTO albums (title, lib_id, mbid, artist_name,
year, month, day, seq, primary_type, secondary_types) 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( Self::execute(
&mut stmt, &mut stmt,

View File

@ -152,6 +152,15 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> SqlDatabase<SDB> {
} }
impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB> { impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB> {
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<Collection, LoadError> { fn load(&mut self) -> Result<Collection, LoadError> {
let tx = self.backend.transaction()?; let tx = self.backend.transaction()?;
@ -178,7 +187,6 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB>
let database: SerializeDatabase = collection.into(); let database: SerializeDatabase = collection.into();
let tx = self.backend.transaction()?; let tx = self.backend.transaction()?;
Self::drop_tables(&tx)?;
Self::create_tables(&tx)?; Self::create_tables(&tx)?;
match database { match database {
@ -300,13 +308,22 @@ mod tests {
SqlDatabase::new(backend).unwrap() 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] #[test]
fn save() { fn save() {
let write_data = FULL_COLLECTION.to_owned(); let write_data = FULL_COLLECTION.to_owned();
let mut tx = MockISqlTransactionBackend::new(); let mut tx = MockISqlTransactionBackend::new();
let mut seq = Sequence::new(); let mut seq = Sequence::new();
expect_drop!(tx, seq);
expect_create!(tx, seq); expect_create!(tx, seq);
then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103)); then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103));
for artist in write_data.iter() { for artist in write_data.iter() {
@ -396,7 +413,6 @@ mod tests {
fn save_backend_exec_error() { fn save_backend_exec_error() {
let mut tx = MockISqlTransactionBackend::new(); let mut tx = MockISqlTransactionBackend::new();
let mut seq = Sequence::new(); let mut seq = Sequence::new();
expect_drop!(tx, seq);
expect_create!(tx, seq); expect_create!(tx, seq);
then!(tx, seq, expect_insert_database_version) then!(tx, seq, expect_insert_database_version)
.with(predicate::eq(V20250103)) .with(predicate::eq(V20250103))
@ -426,7 +442,6 @@ mod tests {
let mut tx = MockISqlTransactionBackend::new(); let mut tx = MockISqlTransactionBackend::new();
let mut seq = Sequence::new(); let mut seq = Sequence::new();
expect_drop!(tx, seq);
expect_create!(tx, seq); expect_create!(tx, seq);
then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103)); then1!(tx, seq, expect_insert_database_version).with(predicate::eq(V20250103));
then!(tx, seq, expect_insert_artist) then!(tx, seq, expect_insert_artist)

View File

@ -9,7 +9,7 @@ use musichoard::{
}; };
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use crate::testlib::COLLECTION; use crate::{copy_file_into_temp, testlib::COLLECTION};
pub static DATABASE_TEST_FILE: Lazy<PathBuf> = pub static DATABASE_TEST_FILE: Lazy<PathBuf> =
Lazy::new(|| fs::canonicalize("./tests/files/database/database.db").unwrap()); Lazy::new(|| fs::canonicalize("./tests/files/database/database.db").unwrap());
@ -24,6 +24,16 @@ fn expected() -> Collection {
expected 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] #[test]
fn save() { fn save() {
let file = NamedTempFile::new().unwrap(); let file = NamedTempFile::new().unwrap();
@ -61,3 +71,18 @@ fn reverse() {
let expected = expected(); let expected = expected();
assert_eq!(read_data, 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<Artist> = database.load().unwrap();
database.reset().unwrap();
database.save(&expected).unwrap();
let read_data: Vec<Artist> = database.load().unwrap();
assert_eq!(read_data, expected);
}

Binary file not shown.

View File

@ -19,7 +19,7 @@ use tempfile::NamedTempFile;
use crate::testlib::COLLECTION; use crate::testlib::COLLECTION;
fn copy_file_into_temp<P: Into<PathBuf>>(path: P) -> NamedTempFile { pub fn copy_file_into_temp<P: Into<PathBuf>>(path: P) -> NamedTempFile {
let temp = NamedTempFile::new().unwrap(); let temp = NamedTempFile::new().unwrap();
fs::copy(path.into(), temp.path()).unwrap(); fs::copy(path.into(), temp.path()).unwrap();
temp temp