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
4 changed files with 98 additions and 51 deletions
Showing only changes of commit 634a17c2bf - Show all commits

View File

@ -13,7 +13,7 @@ pub enum AlbumLibIdDef {
None, None,
} }
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] #[derive(Clone, 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, PartialEq, Eq)] #[derive(Clone, 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, PartialEq, Eq)] #[derive(Clone, 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, PartialEq, Eq)] #[derive(Clone, 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

@ -30,7 +30,7 @@ impl From<DeserializeDatabase> for Collection {
} }
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct DeserializeArtist { pub struct DeserializeArtist {
pub name: String, pub name: String,
pub sort: Option<String>, pub sort: Option<String>,
@ -39,7 +39,7 @@ pub struct DeserializeArtist {
pub albums: Vec<DeserializeAlbum>, pub albums: Vec<DeserializeAlbum>,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct DeserializeAlbum { pub struct DeserializeAlbum {
pub title: String, pub title: String,
pub lib_id: SerdeAlbumLibId, pub lib_id: SerdeAlbumLibId,
@ -50,7 +50,7 @@ pub struct DeserializeAlbum {
pub secondary_types: Vec<SerdeAlbumSecondaryType>, pub secondary_types: Vec<SerdeAlbumSecondaryType>,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct DeserializeMbRefOption(#[serde(with = "MbRefOptionDef")] MbRefOption<DeserializeMbid>); pub struct DeserializeMbRefOption(#[serde(with = "MbRefOptionDef")] MbRefOption<DeserializeMbid>);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -191,13 +191,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
let mut artists = vec![]; let mut artists = vec![];
while let Some(row) = Self::next_row(&mut rows)? { while let Some(row) = Self::next_row(&mut rows)? {
artists.push(DeserializeArtist { artists.push(row.try_into()?);
name: Self::get_value(row, 0)?,
sort: Self::get_value(row, 1)?,
musicbrainz: serde_json::from_str(&Self::get_value::<String>(row, 2)?)?,
properties: serde_json::from_str(&Self::get_value::<String>(row, 3)?)?,
albums: vec![],
});
} }
Ok(artists) Ok(artists)
@ -235,21 +229,45 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
let mut albums = vec![]; let mut albums = vec![];
while let Some(row) = Self::next_row(&mut rows)? { while let Some(row) = Self::next_row(&mut rows)? {
albums.push(DeserializeAlbum { albums.push(row.try_into()?);
title: Self::get_value(row, 0)?,
lib_id: serde_json::from_str(&Self::get_value::<String>(row, 1)?)?,
date: SerdeAlbumDate(AlbumDate::new(
Self::get_value(row, 2)?,
Self::get_value(row, 3)?,
Self::get_value(row, 4)?,
)),
seq: Self::get_value(row, 5)?,
musicbrainz: serde_json::from_str(&Self::get_value::<String>(row, 6)?)?,
primary_type: serde_json::from_str(&Self::get_value::<String>(row, 7)?)?,
secondary_types: serde_json::from_str(&Self::get_value::<String>(row, 8)?)?,
});
} }
Ok(albums) Ok(albums)
} }
} }
impl TryFrom<&Row<'_>> for DeserializeArtist {
type Error = Error;
fn try_from(row: &Row<'_>) -> Result<Self, Self::Error> {
type Backend<'a> = SqlTransactionSqliteBackend<'a>;
Ok(DeserializeArtist {
name: Backend::get_value(row, 0)?,
sort: Backend::get_value(row, 1)?,
musicbrainz: serde_json::from_str(&Backend::get_value::<String>(row, 2)?)?,
properties: serde_json::from_str(&Backend::get_value::<String>(row, 3)?)?,
albums: vec![],
})
}
}
impl TryFrom<&Row<'_>> for DeserializeAlbum {
type Error = Error;
fn try_from(row: &Row<'_>) -> Result<Self, Self::Error> {
type Backend<'a> = SqlTransactionSqliteBackend<'a>;
Ok(DeserializeAlbum {
title: Backend::get_value(row, 0)?,
lib_id: serde_json::from_str(&Backend::get_value::<String>(row, 1)?)?,
date: SerdeAlbumDate(AlbumDate::new(
Backend::get_value(row, 2)?,
Backend::get_value(row, 3)?,
Backend::get_value(row, 4)?,
)),
seq: Backend::get_value(row, 5)?,
musicbrainz: serde_json::from_str(&Backend::get_value::<String>(row, 6)?)?,
primary_type: serde_json::from_str(&Backend::get_value::<String>(row, 7)?)?,
secondary_types: serde_json::from_str(&Backend::get_value::<String>(row, 8)?)?,
})
}
}

View File

@ -203,28 +203,26 @@ impl<SDB: for<'conn> ISqlDatabaseBackend<'conn>> IDatabase for SqlDatabase<SDB>
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// use std::collections::HashMap; use std::fs;
use std::collections::VecDeque; use std::collections::VecDeque;
use mockall::{predicate, Sequence}; use mockall::{predicate, Sequence};
use rusqlite::Connection;
use crate::core::{ use crate::core::{collection::Collection, testmod::FULL_COLLECTION};
// collection::{artist::Artist, Collection},
testmod::FULL_COLLECTION,
};
use super::*; use super::*;
// fn expected() -> Collection { fn expected() -> Collection {
// let mut expected = FULL_COLLECTION.to_owned(); let mut expected = FULL_COLLECTION.to_owned();
// for artist in expected.iter_mut() { for artist in expected.iter_mut() {
// for album in artist.albums.iter_mut() { for album in artist.albums.iter_mut() {
// album.tracks.clear(); album.tracks.clear();
// } }
// } }
// expected expected
// } }
struct SqlDatabaseTestBackend { struct SqlDatabaseTestBackend {
pub txs: VecDeque<MockISqlTransactionBackend>, pub txs: VecDeque<MockISqlTransactionBackend>,
@ -323,19 +321,50 @@ mod tests {
assert!(database(VecDeque::from([tx])).save(&write_data).is_ok()); assert!(database(VecDeque::from([tx])).save(&write_data).is_ok());
} }
// #[test] #[test]
// fn load() { fn load() {
// let expected = expected(); let path = fs::canonicalize("./src/external/database/sql/testmod.db").unwrap();
// let result = Ok(DATABASE_JSON.to_owned()); let db = Connection::open(path).unwrap();
// eprintln!("{DATABASE_JSON}");
// let mut backend = MockISqlDatabaseBackend::new(); let mut tx = MockISqlTransactionBackend::new();
// backend.expect_read().times(1).return_once(|| result); let mut seq = Sequence::new();
// let read_data: Vec<Artist> = SqlDatabase::new(backend).load().unwrap(); let version_query = "SELECT value FROM database_metadata WHERE name = 'version'";
let version: String = db.query_row(&version_query, (), |row| row.get(0)).unwrap();
then!(tx, seq, expect_select_database_version).return_once(|| Ok(Some(version)));
// assert_eq!(read_data, expected); let artist_query = "SELECT name, sort, mbid, properties FROM artists";
// } let mut artist_query = db.prepare(&artist_query).unwrap();
let mut artist_rows = artist_query.query(()).unwrap();
let mut de_artists = vec![];
while let Some(row) = artist_rows.next().unwrap() {
de_artists.push(row.try_into().unwrap());
}
let artists: Collection = de_artists.iter().cloned().map(Into::into).collect();
then!(tx, seq, expect_select_all_artists).return_once(|| Ok(de_artists));
for artist in artists.iter() {
let album_query =
"SELECT title, lib_id, year, month, day, seq, mbid, primary_type, secondary_types
FROM albums WHERE artist_name = ?1";
let mut album_query = db.prepare(&album_query).unwrap();
let mut album_rows = album_query.query([artist.meta.id.name.clone()]).unwrap();
let mut de_albums = vec![];
while let Some(row) = album_rows.next().unwrap() {
de_albums.push(row.try_into().unwrap());
}
then!(tx, seq, expect_select_artist_albums)
.with(predicate::eq(artist.meta.id.name.clone()))
.return_once(|_| Ok(de_albums));
}
then0!(tx, seq, expect_commit);
let read_data = database(VecDeque::from([tx])).load().unwrap();
let expected = expected();
assert_eq!(read_data, expected);
}
// #[test] // #[test]
// fn reverse() { // fn reverse() {