diff --git a/src/external/database/sql/backend.rs b/src/external/database/sql/backend.rs index 9c7de69..3bc9cb6 100644 --- a/src/external/database/sql/backend.rs +++ b/src/external/database/sql/backend.rs @@ -37,16 +37,14 @@ impl SqlDatabaseSqliteBackend { } impl SqlTransactionSqliteBackend<'_> { - fn prepare(&self, sql: &str) -> Result { - self.tx - .prepare(sql) - .map_err(|err| Error::StmtError(err.to_string())) + // We only prepare strings known at compile time so errors in prep are bugs. + fn prepare(&self, sql: &'static str) -> Statement { + self.tx.prepare(sql).unwrap() } - fn prepare_cached(&self, sql: &str) -> Result { - self.tx - .prepare_cached(sql) - .map_err(|err| Error::StmtError(err.to_string())) + // We only prepare strings known at compile time so errors in prep are bugs. + fn prepare_cached(&self, sql: &'static str) -> CachedStatement { + self.tx.prepare_cached(sql).unwrap() } fn execute(stmt: &mut Statement, params: P) -> Result<(), Error> { @@ -95,12 +93,12 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { name TEXT NOT NULL PRIMARY KEY, value TEXT NOT NULL )", - )?; + ); Self::execute(&mut stmt, ()) } 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, ()) } @@ -112,12 +110,12 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { mbid JSON NOT NULL DEFAULT '\"None\"', properties JSON NOT NULL DEFAULT '{}' )", - )?; + ); Self::execute(&mut stmt, ()) } 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, ()) } @@ -136,12 +134,12 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { secondary_types JSON NOT NULL DEFAULT '[]', FOREIGN KEY (artist_name) REFERENCES artists(name) ON DELETE CASCADE ON UPDATE NO ACTION )", - )?; + ); Self::execute(&mut stmt, ()) } 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, ()) } @@ -149,13 +147,13 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { let mut stmt = self.prepare_cached( "INSERT INTO database_metadata (name, value) VALUES (?1, ?2)", - )?; + ); Self::execute(&mut stmt, ("version", version)) } fn select_database_version<'a>(&self) -> Result, Error> { 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, ())?; Self::next_row(&mut rows)? @@ -167,7 +165,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { let mut stmt = self.prepare_cached( "INSERT INTO artists (name, sort, mbid, properties) VALUES (?1, ?2, ?3, ?4)", - )?; + ); Self::execute( &mut stmt, ( @@ -180,7 +178,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { } fn select_all_artists(&self) -> Result, 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 artists = vec![]; @@ -196,7 +194,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { "INSERT INTO albums (title, lib_id, mbid, artist_name, year, month, day, seq, primary_type, secondary_types) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", - )?; + ); Self::execute( &mut stmt, ( @@ -218,7 +216,7 @@ impl ISqlTransactionBackend for SqlTransactionSqliteBackend<'_> { let mut stmt = self.prepare_cached( "SELECT title, lib_id, year, month, day, seq, mbid, primary_type, secondary_types FROM albums WHERE artist_name = ?1", - )?; + ); let mut rows = Self::query(&mut stmt, [artist_name])?; let mut albums = vec![]; diff --git a/src/external/database/sql/mod.rs b/src/external/database/sql/mod.rs index 697cd63..85decb7 100644 --- a/src/external/database/sql/mod.rs +++ b/src/external/database/sql/mod.rs @@ -78,11 +78,9 @@ pub trait ISqlTransactionBackend { pub enum Error { /// An error occurred when connecting to the database. OpenError(String), - /// An error occurred when preparing a statement for execution. - StmtError(String), /// An error occurred during serialisation. SerDeError(String), - /// An error occurred during execution. + /// An error occurred during SQL execution. ExecError(String), } @@ -98,12 +96,8 @@ impl fmt::Display for Error { Self::OpenError(ref 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::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; fn transaction(&mut self) -> Result { - 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 mut tx = MockISqlTransactionBackend::new(); - let mut seq = Sequence::new(); expect_drop!(tx, seq); expect_create!(tx, seq); @@ -356,27 +351,96 @@ mod tests { assert_eq!(read_data, expected); } - // #[test] - // fn load_errors() { - // let json = String::from(""); - // let serde_err = serde_json::from_str::(&json); - // assert!(serde_err.is_err()); + #[test] + fn load_missing_database_version() { + let mut tx = MockISqlTransactionBackend::new(); + let mut seq = Sequence::new(); + 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(); - // assert!(!serde_err.to_string().is_empty()); - // assert!(!format!("{:?}", serde_err).is_empty()); - // } + #[test] + fn load_unknown_database_version() { + 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] - // fn save_errors() { - // // serde_json will raise an error as it has certain requirements on keys. - // let mut object = HashMap::, String>::new(); - // object.insert(Ok(()), String::from("string")); - // let serde_err = serde_json::to_string(&object); - // assert!(serde_err.is_err()); + #[test] + fn load_backend_open_error() { + let error = database(VecDeque::from([])).load().unwrap_err(); + assert!(matches!(error, LoadError::IoError(_))); + } - // let serde_err: SaveError = serde_err.unwrap_err().into(); - // assert!(!serde_err.to_string().is_empty()); - // assert!(!format!("{:?}", serde_err).is_empty()); - // } + #[test] + fn save_backend_open_error() { + 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::("").unwrap_err(); + let error: Error = error.into(); + assert!(matches!(error, Error::SerDeError(_))); + assert!(!error.to_string().is_empty()); + } }