Add a SQLite database backend #265
@ -37,16 +37,14 @@ impl SqlDatabaseSqliteBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SqlTransactionSqliteBackend<'_> {
|
impl SqlTransactionSqliteBackend<'_> {
|
||||||
fn prepare(&self, sql: &str) -> Result<Statement, Error> {
|
// We only prepare strings known at compile time so errors in prep are bugs.
|
||||||
self.tx
|
fn prepare(&self, sql: &'static str) -> Statement {
|
||||||
.prepare(sql)
|
self.tx.prepare(sql).unwrap()
|
||||||
.map_err(|err| Error::StmtError(err.to_string()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_cached(&self, sql: &str) -> Result<CachedStatement, Error> {
|
// We only prepare strings known at compile time so errors in prep are bugs.
|
||||||
self.tx
|
fn prepare_cached(&self, sql: &'static str) -> CachedStatement {
|
||||||
.prepare_cached(sql)
|
self.tx.prepare_cached(sql).unwrap()
|
||||||
.map_err(|err| Error::StmtError(err.to_string()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute<P: Params>(stmt: &mut Statement, params: P) -> Result<(), Error> {
|
fn execute<P: Params>(stmt: &mut Statement, params: P) -> Result<(), Error> {
|
||||||
@ -95,12 +93,12 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
name TEXT NOT NULL PRIMARY KEY,
|
name TEXT NOT NULL PRIMARY KEY,
|
||||||
value TEXT NOT NULL
|
value TEXT NOT NULL
|
||||||
)",
|
)",
|
||||||
)?;
|
);
|
||||||
Self::execute(&mut stmt, ())
|
Self::execute(&mut stmt, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_database_metadata_table(&self) -> Result<(), Error> {
|
fn drop_database_metadata_table(&self) -> Result<(), Error> {
|
||||||
let mut stmt = self.prepare_cached("DROP TABLE database_metadata")?;
|
let mut stmt = self.prepare_cached("DROP TABLE database_metadata");
|
||||||
Self::execute(&mut stmt, ())
|
Self::execute(&mut stmt, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,12 +110,12 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
mbid JSON NOT NULL DEFAULT '\"None\"',
|
mbid JSON NOT NULL DEFAULT '\"None\"',
|
||||||
properties JSON NOT NULL DEFAULT '{}'
|
properties JSON NOT NULL DEFAULT '{}'
|
||||||
)",
|
)",
|
||||||
)?;
|
);
|
||||||
Self::execute(&mut stmt, ())
|
Self::execute(&mut stmt, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_artists_table(&self) -> Result<(), Error> {
|
fn drop_artists_table(&self) -> Result<(), Error> {
|
||||||
let mut stmt = self.prepare_cached("DROP TABLE artists")?;
|
let mut stmt = self.prepare_cached("DROP TABLE artists");
|
||||||
Self::execute(&mut stmt, ())
|
Self::execute(&mut stmt, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,12 +134,12 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
secondary_types JSON NOT NULL DEFAULT '[]',
|
secondary_types JSON NOT NULL DEFAULT '[]',
|
||||||
FOREIGN KEY (artist_name) REFERENCES artists(name) ON DELETE CASCADE ON UPDATE NO ACTION
|
FOREIGN KEY (artist_name) REFERENCES artists(name) ON DELETE CASCADE ON UPDATE NO ACTION
|
||||||
)",
|
)",
|
||||||
)?;
|
);
|
||||||
Self::execute(&mut stmt, ())
|
Self::execute(&mut stmt, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_albums_table(&self) -> Result<(), Error> {
|
fn drop_albums_table(&self) -> Result<(), Error> {
|
||||||
let mut stmt = self.prepare_cached("DROP TABLE albums")?;
|
let mut stmt = self.prepare_cached("DROP TABLE albums");
|
||||||
Self::execute(&mut stmt, ())
|
Self::execute(&mut stmt, ())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,13 +147,13 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
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)",
|
||||||
)?;
|
);
|
||||||
Self::execute(&mut stmt, ("version", version))
|
Self::execute(&mut stmt, ("version", version))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_database_version<'a>(&self) -> Result<Option<String>, Error> {
|
fn select_database_version<'a>(&self) -> Result<Option<String>, Error> {
|
||||||
let mut stmt =
|
let mut stmt =
|
||||||
self.prepare_cached("SELECT value FROM database_metadata WHERE name = 'version'")?;
|
self.prepare_cached("SELECT value FROM database_metadata WHERE name = 'version'");
|
||||||
let mut rows = Self::query(&mut stmt, ())?;
|
let mut rows = Self::query(&mut stmt, ())?;
|
||||||
|
|
||||||
Self::next_row(&mut rows)?
|
Self::next_row(&mut rows)?
|
||||||
@ -167,7 +165,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
let mut stmt = self.prepare_cached(
|
let mut stmt = self.prepare_cached(
|
||||||
"INSERT INTO artists (name, sort, mbid, properties)
|
"INSERT INTO artists (name, sort, mbid, properties)
|
||||||
VALUES (?1, ?2, ?3, ?4)",
|
VALUES (?1, ?2, ?3, ?4)",
|
||||||
)?;
|
);
|
||||||
Self::execute(
|
Self::execute(
|
||||||
&mut stmt,
|
&mut stmt,
|
||||||
(
|
(
|
||||||
@ -180,7 +178,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn select_all_artists(&self) -> Result<Vec<DeserializeArtist>, Error> {
|
fn select_all_artists(&self) -> Result<Vec<DeserializeArtist>, Error> {
|
||||||
let mut stmt = self.prepare_cached("SELECT name, sort, mbid, properties FROM artists")?;
|
let mut stmt = self.prepare_cached("SELECT name, sort, mbid, properties FROM artists");
|
||||||
let mut rows = Self::query(&mut stmt, ())?;
|
let mut rows = Self::query(&mut stmt, ())?;
|
||||||
|
|
||||||
let mut artists = vec![];
|
let mut artists = vec![];
|
||||||
@ -196,7 +194,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
"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)",
|
||||||
)?;
|
);
|
||||||
Self::execute(
|
Self::execute(
|
||||||
&mut stmt,
|
&mut stmt,
|
||||||
(
|
(
|
||||||
@ -218,7 +216,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> {
|
|||||||
let mut stmt = self.prepare_cached(
|
let mut stmt = self.prepare_cached(
|
||||||
"SELECT title, lib_id, year, month, day, seq, mbid, primary_type, secondary_types
|
"SELECT title, lib_id, year, month, day, seq, mbid, primary_type, secondary_types
|
||||||
FROM albums WHERE artist_name = ?1",
|
FROM albums WHERE artist_name = ?1",
|
||||||
)?;
|
);
|
||||||
let mut rows = Self::query(&mut stmt, [artist_name])?;
|
let mut rows = Self::query(&mut stmt, [artist_name])?;
|
||||||
|
|
||||||
let mut albums = vec![];
|
let mut albums = vec![];
|
||||||
|
@ -78,11 +78,9 @@ pub trait ISqlTransactionBackend {
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// An error occurred when connecting to the database.
|
/// An error occurred when connecting to the database.
|
||||||
OpenError(String),
|
OpenError(String),
|
||||||
/// An error occurred when preparing a statement for execution.
|
|
||||||
StmtError(String),
|
|
||||||
/// An error occurred during serialisation.
|
/// An error occurred during serialisation.
|
||||||
SerDeError(String),
|
SerDeError(String),
|
||||||
/// An error occurred during execution.
|
/// An error occurred during SQL execution.
|
||||||
ExecError(String),
|
ExecError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,12 +96,8 @@ impl fmt::Display for Error {
|
|||||||
Self::OpenError(ref s) => {
|
Self::OpenError(ref s) => {
|
||||||
write!(f, "an error occurred when connecting to the database: {s}")
|
write!(f, "an error occurred when connecting to the database: {s}")
|
||||||
}
|
}
|
||||||
Self::StmtError(ref s) => write!(
|
|
||||||
f,
|
|
||||||
"an error occurred when preparing a statement for execution: {s}"
|
|
||||||
),
|
|
||||||
Self::SerDeError(ref s) => write!(f, "an error occurred during serialisation : {s}"),
|
Self::SerDeError(ref s) => write!(f, "an error occurred during serialisation : {s}"),
|
||||||
Self::ExecError(ref s) => write!(f, "an error occurred during execution: {s}"),
|
Self::ExecError(ref s) => write!(f, "an error occurred during SQL execution: {s}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,7 +240,9 @@ mod tests {
|
|||||||
type Tx = MockISqlTransactionBackend;
|
type Tx = MockISqlTransactionBackend;
|
||||||
|
|
||||||
fn transaction(&mut self) -> Result<Self::Tx, Error> {
|
fn transaction(&mut self) -> Result<Self::Tx, Error> {
|
||||||
Ok(self.txs.pop_front().unwrap())
|
self.txs
|
||||||
|
.pop_front()
|
||||||
|
.ok_or_else(|| Error::OpenError(String::from("lol")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,7 +305,6 @@ mod tests {
|
|||||||
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_drop!(tx, seq);
|
||||||
expect_create!(tx, seq);
|
expect_create!(tx, seq);
|
||||||
@ -356,27 +351,96 @@ mod tests {
|
|||||||
assert_eq!(read_data, expected);
|
assert_eq!(read_data, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn load_errors() {
|
fn load_missing_database_version() {
|
||||||
// let json = String::from("");
|
let mut tx = MockISqlTransactionBackend::new();
|
||||||
// let serde_err = serde_json::from_str::<DeserializeDatabase>(&json);
|
let mut seq = Sequence::new();
|
||||||
// assert!(serde_err.is_err());
|
then!(tx, seq, expect_select_database_version).return_once(|| Ok(None));
|
||||||
|
let error = database(VecDeque::from([tx])).load().unwrap_err();
|
||||||
|
assert!(matches!(error, LoadError::SerDeError(_)));
|
||||||
|
}
|
||||||
|
|
||||||
// let serde_err: LoadError = serde_err.unwrap_err().into();
|
#[test]
|
||||||
// assert!(!serde_err.to_string().is_empty());
|
fn load_unknown_database_version() {
|
||||||
// assert!(!format!("{:?}", serde_err).is_empty());
|
let mut tx = MockISqlTransactionBackend::new();
|
||||||
// }
|
let mut seq = Sequence::new();
|
||||||
|
then!(tx, seq, expect_select_database_version)
|
||||||
|
.return_once(|| Ok(Some(String::from("no u"))));
|
||||||
|
let error = database(VecDeque::from([tx])).load().unwrap_err();
|
||||||
|
assert!(matches!(error, LoadError::SerDeError(_)));
|
||||||
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn save_errors() {
|
fn load_backend_open_error() {
|
||||||
// // serde_json will raise an error as it has certain requirements on keys.
|
let error = database(VecDeque::from([])).load().unwrap_err();
|
||||||
// let mut object = HashMap::<Result<(), ()>, String>::new();
|
assert!(matches!(error, LoadError::IoError(_)));
|
||||||
// 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_backend_open_error() {
|
||||||
// assert!(!format!("{:?}", serde_err).is_empty());
|
let error = database(VecDeque::from([])).save(&vec![]).unwrap_err();
|
||||||
// }
|
assert!(matches!(error, SaveError::IoError(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_backend_exec_error() {
|
||||||
|
let mut tx = MockISqlTransactionBackend::new();
|
||||||
|
let mut seq = Sequence::new();
|
||||||
|
then!(tx, seq, expect_select_database_version)
|
||||||
|
.return_once(|| Err(Error::ExecError(String::from("serde"))));
|
||||||
|
let error = database(VecDeque::from([tx])).load().unwrap_err();
|
||||||
|
assert!(matches!(error, LoadError::IoError(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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))
|
||||||
|
.return_once(|_| Err(Error::ExecError(String::from("exec"))));
|
||||||
|
|
||||||
|
let error = database(VecDeque::from([tx])).save(&vec![]).unwrap_err();
|
||||||
|
assert!(matches!(error, SaveError::IoError(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_backend_serde_error() {
|
||||||
|
let mut tx = MockISqlTransactionBackend::new();
|
||||||
|
let mut seq = Sequence::new();
|
||||||
|
|
||||||
|
then!(tx, seq, expect_select_database_version)
|
||||||
|
.return_once(|| Ok(Some(DATABASE_SQL_VERSION.to_string())));
|
||||||
|
then!(tx, seq, expect_select_all_artists)
|
||||||
|
.return_once(|| Err(Error::SerDeError(String::from("serde"))));
|
||||||
|
|
||||||
|
let error = database(VecDeque::from([tx])).load().unwrap_err();
|
||||||
|
assert!(matches!(error, LoadError::SerDeError(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn save_backend_serde_error() {
|
||||||
|
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));
|
||||||
|
then!(tx, seq, expect_insert_artist)
|
||||||
|
.return_once(|_| Err(Error::SerDeError(String::from("serde"))));
|
||||||
|
|
||||||
|
let error = database(VecDeque::from([tx])).save(&write_data).unwrap_err();
|
||||||
|
assert!(matches!(error, SaveError::SerDeError(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serde_json_error() {
|
||||||
|
let error = serde_json::from_str::<DeserializeArtist>("").unwrap_err();
|
||||||
|
let error: Error = error.into();
|
||||||
|
assert!(matches!(error, Error::SerDeError(_)));
|
||||||
|
assert!(!error.to_string().is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user