Add a SQLite database backend #265

Merged
wojtek merged 20 commits from 248---replace-json-file-as-a-database-with-sqlite into main 2025-01-12 10:24:53 +01:00
3 changed files with 174 additions and 91 deletions
Showing only changes of commit e24f776896 - Show all commits

View File

@ -13,7 +13,7 @@ pub enum AlbumLibIdDef {
None, None,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct SerdeAlbumLibId(#[serde(with = "AlbumLibIdDef")] AlbumLibId); pub struct SerdeAlbumLibId(#[serde(with = "AlbumLibIdDef")] AlbumLibId);
impl From<SerdeAlbumLibId> for AlbumLibId { impl From<SerdeAlbumLibId> for AlbumLibId {
@ -36,7 +36,7 @@ pub struct AlbumDateDef {
day: Option<u8>, day: Option<u8>,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct SerdeAlbumDate(#[serde(with = "AlbumDateDef")] pub AlbumDate); pub struct SerdeAlbumDate(#[serde(with = "AlbumDateDef")] pub AlbumDate);
impl From<SerdeAlbumDate> for AlbumDate { impl From<SerdeAlbumDate> for AlbumDate {
@ -69,7 +69,7 @@ pub enum AlbumPrimaryTypeDef {
Other, Other,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct SerdeAlbumPrimaryType(#[serde(with = "AlbumPrimaryTypeDef")] AlbumPrimaryType); pub struct SerdeAlbumPrimaryType(#[serde(with = "AlbumPrimaryTypeDef")] AlbumPrimaryType);
impl From<SerdeAlbumPrimaryType> for AlbumPrimaryType { impl From<SerdeAlbumPrimaryType> for AlbumPrimaryType {
@ -101,7 +101,7 @@ pub enum AlbumSecondaryTypeDef {
FieldRecording, FieldRecording,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct SerdeAlbumSecondaryType(#[serde(with = "AlbumSecondaryTypeDef")] AlbumSecondaryType); pub struct SerdeAlbumSecondaryType(#[serde(with = "AlbumSecondaryTypeDef")] AlbumSecondaryType);
impl From<SerdeAlbumSecondaryType> for AlbumSecondaryType { impl From<SerdeAlbumSecondaryType> for AlbumSecondaryType {

View File

@ -22,7 +22,7 @@ impl<'a> From<&'a Collection> for SerializeDatabase<'a> {
} }
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, PartialEq, Eq)]
pub struct SerializeArtist<'a> { pub struct SerializeArtist<'a> {
pub name: &'a str, pub name: &'a str,
pub sort: Option<&'a str>, pub sort: Option<&'a str>,
@ -31,7 +31,7 @@ pub struct SerializeArtist<'a> {
pub albums: Vec<SerializeAlbum<'a>>, pub albums: Vec<SerializeAlbum<'a>>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, PartialEq, Eq)]
pub struct SerializeAlbum<'a> { pub struct SerializeAlbum<'a> {
pub title: &'a str, pub title: &'a str,
pub lib_id: SerdeAlbumLibId, pub lib_id: SerdeAlbumLibId,
@ -42,12 +42,12 @@ pub struct SerializeAlbum<'a> {
pub secondary_types: Vec<SerdeAlbumSecondaryType>, pub secondary_types: Vec<SerdeAlbumSecondaryType>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, PartialEq, Eq)]
pub struct SerializeMbRefOption<'a>( pub struct SerializeMbRefOption<'a>(
#[serde(with = "MbRefOptionDef")] MbRefOption<SerializeMbid<'a>>, #[serde(with = "MbRefOptionDef")] MbRefOption<SerializeMbid<'a>>,
); );
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct SerializeMbid<'a>(&'a Mbid); pub struct SerializeMbid<'a>(&'a Mbid);
impl<'a, T: IMusicBrainzRef> From<&'a MbRefOption<T>> for SerializeMbRefOption<'a> { impl<'a, T: IMusicBrainzRef> From<&'a MbRefOption<T>> for SerializeMbRefOption<'a> {

View File

@ -144,9 +144,9 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> SqlDatabase<SDB> {
} }
fn drop_tables<Tx: ISqlTransactionBackend>(tx: &Tx) -> Result<(), Error> { fn drop_tables<Tx: ISqlTransactionBackend>(tx: &Tx) -> Result<(), Error> {
tx.drop_database_metadata_table()?;
tx.drop_artists_table()?;
tx.drop_albums_table()?; tx.drop_albums_table()?;
tx.drop_artists_table()?;
tx.drop_database_metadata_table()?;
Ok(()) Ok(())
} }
} }
@ -201,103 +201,186 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB>
// #[cfg(test)] // #[cfg(test)]
// pub mod testmod; // pub mod testmod;
// #[cfg(test)] #[cfg(test)]
// mod tests { mod tests {
// use std::collections::HashMap; // use std::collections::HashMap;
// use mockall::predicate; use std::collections::VecDeque;
// use crate::core::{ use mockall::{predicate, Sequence};
// collection::{artist::Artist, Collection},
// testmod::FULL_COLLECTION,
// };
// use super::*; use crate::core::{
// use testmod::DATABASE_JSON; // collection::{artist::Artist, Collection},
testmod::FULL_COLLECTION,
};
// fn expected() -> Collection { use super::*;
// let mut expected = FULL_COLLECTION.to_owned();
// for artist in expected.iter_mut() {
// for album in artist.albums.iter_mut() {
// album.tracks.clear();
// }
// }
// expected
// }
// #[test] // fn expected() -> Collection {
// fn save() { // let mut expected = FULL_COLLECTION.to_owned();
// let write_data = FULL_COLLECTION.to_owned(); // for artist in expected.iter_mut() {
// let input = DATABASE_JSON.to_owned(); // for album in artist.albums.iter_mut() {
// album.tracks.clear();
// }
// }
// expected
// }
// let mut backend = MockISqlDatabaseBackend::new(); struct SqlDatabaseTestBackend {
// backend pub txs: VecDeque<MockISqlTransactionBackend>,
// .expect_write() }
// .with(predicate::eq(input))
// .times(1)
// .return_once(|_| Ok(()));
// SqlDatabase::new(backend).save(&write_data).unwrap(); impl SqlDatabaseTestBackend {
// } fn new(txs: VecDeque<MockISqlTransactionBackend>) -> Self {
SqlDatabaseTestBackend { txs }
}
}
// #[test] impl ISqlDatabaseBackend<'_> for SqlDatabaseTestBackend {
// fn load() { type Tx = MockISqlTransactionBackend;
// let expected = expected();
// let result = Ok(DATABASE_JSON.to_owned());
// eprintln!("{DATABASE_JSON}");
// let mut backend = MockISqlDatabaseBackend::new(); fn transaction(&mut self) -> Result<Self::Tx, Error> {
// backend.expect_read().times(1).return_once(|| result); Ok(self.txs.pop_front().unwrap())
}
}
// let read_data: Vec<Artist> = SqlDatabase::new(backend).load().unwrap(); macro_rules! then {
($tx:ident, $seq:ident, $expect:ident) => {
$tx.$expect().times(1).in_sequence(&mut $seq)
};
}
// assert_eq!(read_data, expected); macro_rules! then0 {
// } ($tx:ident, $seq:ident, $expect:ident) => {
then!($tx, $seq, $expect).return_once(|| Ok(()))
};
}
// #[test] macro_rules! then1 {
// fn reverse() { ($tx:ident, $seq:ident, $expect:ident) => {
// let input = DATABASE_JSON.to_owned(); then!($tx, $seq, $expect).return_once(|_| Ok(()))
// let result = Ok(input.clone()); };
}
// let mut backend = MockISqlDatabaseBackend::new(); macro_rules! then2 {
// backend ($tx:ident, $seq:ident, $expect:ident) => {
// .expect_write() then!($tx, $seq, $expect).return_once(|_, _| Ok(()))
// .with(predicate::eq(input)) };
// .times(1) }
// .return_once(|_| Ok(()));
// backend.expect_read().times(1).return_once(|| result);
// let mut database = SqlDatabase::new(backend);
// let write_data = FULL_COLLECTION.to_owned(); macro_rules! expect_create {
// database.save(&write_data).unwrap(); ($tx:ident, $seq:ident) => {
// let read_data: Vec<Artist> = database.load().unwrap(); let mut seq = Sequence::new();
then0!($tx, seq, expect_create_database_metadata_table);
then0!($tx, seq, expect_create_artists_table);
then0!($tx, seq, expect_create_albums_table);
};
}
// // Album information is not saved to disk. macro_rules! expect_drop {
// let expected = expected(); ($tx:ident, $seq:ident) => {
// assert_eq!(read_data, expected); let mut seq = Sequence::new();
// } then0!($tx, seq, expect_drop_albums_table);
then0!($tx, seq, expect_drop_artists_table);
then0!($tx, seq, expect_drop_database_metadata_table);
};
}
// #[test] fn database(txs: VecDeque<MockISqlTransactionBackend>) -> SqlDatabase<SqlDatabaseTestBackend> {
// fn load_errors() { let mut backend = SqlDatabaseTestBackend::new(txs);
// let json = String::from(""); let mut tx = MockISqlTransactionBackend::new();
// let serde_err = serde_json::from_str::<DeserializeDatabase>(&json);
// assert!(serde_err.is_err());
// let serde_err: LoadError = serde_err.unwrap_err().into(); let mut seq = Sequence::new();
// assert!(!serde_err.to_string().is_empty()); expect_create!(tx, seq);
// assert!(!format!("{:?}", serde_err).is_empty()); then0!(tx, seq, expect_commit);
// }
// #[test] backend.txs.push_front(tx);
// fn save_errors() { SqlDatabase::new(backend).unwrap()
// // serde_json will raise an error as it has certain requirements on keys. }
// let mut object = HashMap::<Result<(), ()>, String>::new();
// object.insert(Ok(()), String::from("string"));
// let serde_err = serde_json::to_string(&object);
// assert!(serde_err.is_err());
// let serde_err: SaveError = serde_err.unwrap_err().into(); #[test]
// assert!(!serde_err.to_string().is_empty()); fn save() {
// assert!(!format!("{:?}", serde_err).is_empty()); 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() {
let ac = artist.clone();
then1!(tx, seq, expect_insert_artist)
.withf(move |a| a == &Into::<SerializeArtist>::into(&ac));
for album in artist.albums.iter() {
let (nc, ac) = (artist.meta.id.name.clone(), album.clone());
then2!(tx, seq, expect_insert_album)
.withf(move |n, a| n == nc && a == &Into::<SerializeAlbum>::into(&ac));
}
}
then0!(tx, seq, expect_commit);
assert!(database(VecDeque::from([tx])).save(&write_data).is_ok());
}
// #[test]
// fn load() {
// let expected = expected();
// let result = Ok(DATABASE_JSON.to_owned());
// eprintln!("{DATABASE_JSON}");
// let mut backend = MockISqlDatabaseBackend::new();
// backend.expect_read().times(1).return_once(|| result);
// let read_data: Vec<Artist> = SqlDatabase::new(backend).load().unwrap();
// assert_eq!(read_data, expected);
// }
// #[test]
// fn reverse() {
// let input = DATABASE_JSON.to_owned();
// let result = Ok(input.clone());
// let mut backend = MockISqlDatabaseBackend::new();
// backend
// .expect_write()
// .with(predicate::eq(input))
// .times(1)
// .return_once(|_| Ok(()));
// backend.expect_read().times(1).return_once(|| result);
// let mut database = SqlDatabase::new(backend);
// let write_data = FULL_COLLECTION.to_owned();
// database.save(&write_data).unwrap();
// let read_data: Vec<Artist> = database.load().unwrap();
// // Album information is not saved to disk.
// let expected = expected();
// assert_eq!(read_data, expected);
// }
// #[test]
// fn load_errors() {
// let json = String::from("");
// let serde_err = serde_json::from_str::<DeserializeDatabase>(&json);
// assert!(serde_err.is_err());
// let serde_err: LoadError = serde_err.unwrap_err().into();
// assert!(!serde_err.to_string().is_empty());
// assert!(!format!("{:?}", serde_err).is_empty());
// }
// #[test]
// fn save_errors() {
// // serde_json will raise an error as it has certain requirements on keys.
// let mut object = HashMap::<Result<(), ()>, String>::new();
// object.insert(Ok(()), String::from("string"));
// let serde_err = serde_json::to_string(&object);
// assert!(serde_err.is_err());
// let serde_err: SaveError = serde_err.unwrap_err().into();
// assert!(!serde_err.to_string().is_empty());
// assert!(!format!("{:?}", serde_err).is_empty());
// }
}