Add a SQLite database backend #265
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use rusqlite::{self, CachedStatement, Connection, Params, Statement};
|
use rusqlite::{self, CachedStatement, Connection, Params, Statement, Transaction};
|
||||||
|
|
||||||
use crate::external::database::{
|
use crate::external::database::{
|
||||||
serde::serialize::{SerializeAlbum, SerializeArtist, SerializeDatabase},
|
serde::serialize::{SerializeAlbum, SerializeArtist, SerializeDatabase},
|
||||||
sql::{Error, ISqlDatabaseBackend},
|
sql::{Error, ISqlDatabaseBackend, ISqlTransactionBackend},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// SQLite database backend that uses SQLite as the implementation.
|
/// SQLite database backend that uses SQLite as the implementation.
|
||||||
@ -14,6 +14,10 @@ pub struct SqlDatabaseSqliteBackend {
|
|||||||
conn: Connection,
|
conn: Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SqlTransactionSqliteBackend<'conn> {
|
||||||
|
tx: Transaction<'conn>,
|
||||||
|
}
|
||||||
|
|
||||||
impl SqlDatabaseSqliteBackend {
|
impl SqlDatabaseSqliteBackend {
|
||||||
/// Create a [`SqlDatabaseSqliteBackend`] that will read/write to the provided database.
|
/// Create a [`SqlDatabaseSqliteBackend`] that will read/write to the provided database.
|
||||||
pub fn new<P: Into<PathBuf>>(path: P) -> Result<Self, Error> {
|
pub fn new<P: Into<PathBuf>>(path: P) -> Result<Self, Error> {
|
||||||
@ -21,15 +25,17 @@ impl SqlDatabaseSqliteBackend {
|
|||||||
conn: Connection::open(path.into()).map_err(|err| Error::OpenError(err.to_string()))?,
|
conn: Connection::open(path.into()).map_err(|err| Error::OpenError(err.to_string()))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'conn> SqlTransactionSqliteBackend<'conn> {
|
||||||
fn prepare(&self, sql: &str) -> Result<Statement, Error> {
|
fn prepare(&self, sql: &str) -> Result<Statement, Error> {
|
||||||
self.conn
|
self.tx
|
||||||
.prepare(sql)
|
.prepare(sql)
|
||||||
.map_err(|err| Error::StmtError(err.to_string()))
|
.map_err(|err| Error::StmtError(err.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_cached(&self, sql: &str) -> Result<CachedStatement, Error> {
|
fn prepare_cached(&self, sql: &str) -> Result<CachedStatement, Error> {
|
||||||
self.conn
|
self.tx
|
||||||
.prepare_cached(sql)
|
.prepare_cached(sql)
|
||||||
.map_err(|err| Error::StmtError(err.to_string()))
|
.map_err(|err| Error::StmtError(err.to_string()))
|
||||||
}
|
}
|
||||||
@ -47,15 +53,22 @@ impl From<serde_json::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ISqlDatabaseBackend for SqlDatabaseSqliteBackend {
|
impl<'conn> ISqlDatabaseBackend<'conn> for SqlDatabaseSqliteBackend {
|
||||||
fn begin_transaction(&self) -> Result<(), Error> {
|
type Tx = SqlTransactionSqliteBackend<'conn>;
|
||||||
let mut stmt = self.prepare_cached("BEGIN TRANSACTION;")?;
|
|
||||||
Self::execute(&mut stmt, ())
|
fn transaction(&'conn mut self) -> Result<Self::Tx, Error> {
|
||||||
|
self.conn
|
||||||
|
.transaction()
|
||||||
|
.map(|tx| SqlTransactionSqliteBackend { tx })
|
||||||
|
.map_err(|err| Error::OpenError(err.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_transaction(&self) -> Result<(), Error> {
|
impl<'conn> ISqlTransactionBackend for SqlTransactionSqliteBackend<'conn> {
|
||||||
let mut stmt = self.prepare_cached("COMMIT;")?;
|
fn commit(self) -> Result<(), Error> {
|
||||||
Self::execute(&mut stmt, ())
|
self.tx
|
||||||
|
.commit()
|
||||||
|
.map_err(|err| Error::ExecError(err.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_database_metadata_table(&self) -> Result<(), Error> {
|
fn create_database_metadata_table(&self) -> Result<(), Error> {
|
||||||
|
@ -16,13 +16,18 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Trait for the SQL database backend.
|
/// Trait for the SQL database backend.
|
||||||
#[cfg_attr(test, automock)]
|
pub trait ISqlDatabaseBackend<'conn> {
|
||||||
pub trait ISqlDatabaseBackend {
|
type Tx: ISqlTransactionBackend + 'conn;
|
||||||
/// Begin an SQL transaction.
|
|
||||||
fn begin_transaction(&self) -> Result<(), Error>;
|
|
||||||
|
|
||||||
/// Commit ongoing transaction.
|
/// Begin an SQL transaction.
|
||||||
fn commit_transaction(&self) -> Result<(), Error>;
|
fn transaction(&'conn mut self) -> Result<Self::Tx, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for the SQL database backend.
|
||||||
|
#[cfg_attr(test, automock)]
|
||||||
|
pub trait ISqlTransactionBackend {
|
||||||
|
/// Commit transaction.
|
||||||
|
fn commit(self) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Create the database metadata table (if needed).
|
/// Create the database metadata table (if needed).
|
||||||
fn create_database_metadata_table(&self) -> Result<(), Error>;
|
fn create_database_metadata_table(&self) -> Result<(), Error>;
|
||||||
@ -95,65 +100,57 @@ pub struct SqlDatabase<SDB> {
|
|||||||
backend: SDB,
|
backend: SDB,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SDB: ISqlDatabaseBackend> SqlDatabase<SDB> {
|
impl<SDB: for<'c> ISqlDatabaseBackend<'c>> SqlDatabase<SDB> {
|
||||||
/// Create a new SQL database with the provided backend, e.g.
|
/// Create a new SQL database with the provided backend, e.g.
|
||||||
/// [`backend::SqlDatabaseSqliteBackend`].
|
/// [`backend::SqlDatabaseSqliteBackend`].
|
||||||
pub fn new(backend: SDB) -> Result<Self, Error> {
|
pub fn new(backend: SDB) -> Result<Self, Error> {
|
||||||
let db = SqlDatabase { backend };
|
let mut db = SqlDatabase { backend };
|
||||||
db.begin_transaction()?;
|
let tx = db.backend.transaction()?;
|
||||||
db.create_tables()?;
|
Self::create_tables(&tx)?;
|
||||||
db.commit_transaction()?;
|
tx.commit()?;
|
||||||
Ok(db)
|
Ok(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin_transaction(&self) -> Result<(), Error> {
|
fn create_tables<Tx: ISqlTransactionBackend>(tx: &Tx) -> Result<(), Error> {
|
||||||
self.backend.begin_transaction()
|
tx.create_database_metadata_table()?;
|
||||||
}
|
tx.create_artists_table()?;
|
||||||
|
tx.create_albums_table()?;
|
||||||
fn commit_transaction(&self) -> Result<(), Error> {
|
|
||||||
self.backend.commit_transaction()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_tables(&self) -> Result<(), Error> {
|
|
||||||
self.backend.create_database_metadata_table()?;
|
|
||||||
self.backend.create_artists_table()?;
|
|
||||||
self.backend.create_albums_table()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_tables(&self) -> Result<(), Error> {
|
fn drop_tables<Tx: ISqlTransactionBackend>(tx: &Tx) -> Result<(), Error> {
|
||||||
self.backend.drop_database_metadata_table()?;
|
tx.drop_database_metadata_table()?;
|
||||||
self.backend.drop_artists_table()?;
|
tx.drop_artists_table()?;
|
||||||
self.backend.drop_albums_table()?;
|
tx.drop_albums_table()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SDB: ISqlDatabaseBackend> IDatabase for SqlDatabase<SDB> {
|
impl<SDB: for<'c> ISqlDatabaseBackend<'c>> IDatabase for SqlDatabase<SDB> {
|
||||||
fn load(&self) -> Result<Collection, LoadError> {
|
fn load(&self) -> Result<Collection, LoadError> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, collection: &Collection) -> Result<(), SaveError> {
|
fn save(&mut self, collection: &Collection) -> Result<(), SaveError> {
|
||||||
let database: SerializeDatabase = collection.into();
|
let database: SerializeDatabase = collection.into();
|
||||||
self.begin_transaction()?;
|
let tx = self.backend.transaction()?;
|
||||||
|
|
||||||
self.drop_tables()?;
|
Self::drop_tables(&tx)?;
|
||||||
self.create_tables()?;
|
Self::create_tables(&tx)?;
|
||||||
|
|
||||||
self.backend.insert_database_version(&database)?;
|
tx.insert_database_version(&database)?;
|
||||||
match database {
|
match database {
|
||||||
SerializeDatabase::V20250103(artists) => {
|
SerializeDatabase::V20250103(artists) => {
|
||||||
for artist in artists.iter() {
|
for artist in artists.iter() {
|
||||||
self.backend.insert_artist(artist)?;
|
tx.insert_artist(artist)?;
|
||||||
for album in artist.albums.iter() {
|
for album in artist.albums.iter() {
|
||||||
self.backend.insert_album(artist.name, album)?;
|
tx.insert_album(artist.name, album)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.commit_transaction()?;
|
tx.commit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user