WIP: Refactor the IDatabase calls to write directly to the database #271

Draft
wojtek wants to merge 12 commits from 268---refactor-the-idatabase-calls-to-write-directly-to-the-database into main
6 changed files with 82 additions and 11 deletions
Showing only changes of commit aea710eb32 - Show all commits

View File

@ -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<Collection, LoadError>;
@ -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<Collection, LoadError> {
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;

View File

@ -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,

View File

@ -152,6 +152,15 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> 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> {
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 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)

View File

@ -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<PathBuf> =
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<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;
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();
fs::copy(path.into(), temp.path()).unwrap();
temp