Update tables without dropping
This commit is contained in:
parent
64f3e2d8b7
commit
aea710eb32
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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.
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user