diff --git a/src/rust/lektor_db/src/connexion.rs b/src/rust/lektor_db/src/connexion.rs index 26c30f7ba1beac6d1646d3ca9466846a9dddfc9e..943540e9b00986b4c36c2c314e7d19f937fda0ed 100644 --- a/src/rust/lektor_db/src/connexion.rs +++ b/src/rust/lektor_db/src/connexion.rs @@ -115,33 +115,24 @@ impl LktDatabaseConnection { }) } - /// Delete a kara with its id in a repo. - pub fn delete_kara_by_repo( - &mut self, - arg_repo_id: i64, - arg_kara_id: i64, - ) -> LktDatabaseResult<()> { - self.sqlite.exclusive_transaction(|c| { + /// Delete a kara. + pub fn delete_kara(&mut self, filter: DeleteKaraFilter) -> LktDatabaseResult<()> { + self.sqlite.exclusive_transaction(|conn| { with_dsl!(repo_kara => { - let local_id = repo_kara - .filter(repo_id.eq(arg_repo_id)) - .filter(repo_kara_id.eq(arg_kara_id)) - .first::<KaraId>(c)? - .local_kara_id; - diesel::delete(repo_kara.filter(local_kara_id.eq(local_id))).execute(c)?; + let local_id = match filter { + DeleteKaraFilter::ByLocalId(KaraFilterByLocalId { local_id }) => local_id, + DeleteKaraFilter::ByRepoId(KaraFilterByRepoId { repo_id: rid, kara_id: kid }) => repo_kara + .filter(repo_id.eq(rid)) + .filter(repo_kara_id.eq(kid)) + .first::<KaraId>(conn)? + .local_kara_id + }; + diesel::delete(repo_kara.filter(local_kara_id.eq(local_id))).execute(conn)?; Ok(()) }) }) } - /// Delete a kara by its local ID. - pub fn delete_kara_by_local_id(&mut self, local_id: i64) -> LktDatabaseResult<()> { - self.sqlite.exclusive_transaction(|c| { - with_dsl!(repo_kara => diesel::delete(repo_kara.filter(local_kara_id.eq(local_id))).execute(c)?); - Ok(()) - }) - } - /// Ensure that a given language is present in the database. If it's not /// insert it. Existence test is done on the code of the language. fn ensure_languages_exist<'a>(&mut self, langs: &[NewLanguage<'a>]) -> LktDatabaseResult<()> { @@ -169,18 +160,17 @@ impl LktDatabaseConnection { }) } - /// Add a kara with a request. + /// Add a kara with a request. At this point any tag and language and + /// karamaker must exist in the database. pub fn add_kara_from_request(&mut self, kara: NewKaraRequest) -> LktDatabaseResult<()> { let (id, new_kara, karamakers, langs, tags) = kara; - self.ensure_languages_exist(&langs)?; self.sqlite.exclusive_transaction(|c| { with_dsl!(kara => diesel::insert_into(kara).values(&new_kara).execute(c)?); with_dsl!(repo_kara => diesel::insert_into(repo_kara).values(id).execute(c)?); with_dsl!(kara_maker => diesel::insert_or_ignore_into(kara_maker).values(karamakers).execute(c)?); with_dsl!(kara_lang => { - let langs = langs.into_iter().map(|NewLanguage { code: lang, .. }| NewKaraLanguage { id: new_kara.id, code: lang } ); diesel::delete(kara_lang.filter(id.eq(new_kara.id))).execute(c)?; - diesel::insert_into(kara_lang).values(langs.collect::<Vec<_>>()).execute(c)?; + diesel::insert_into(kara_lang).values(langs).execute(c)?; }); with_dsl!(kara_tag => { diesel::delete(kara_tag.filter(kara_id.eq(new_kara.id))).execute(c)?; @@ -202,7 +192,13 @@ impl LktDatabaseConnection { local_kara_id: local_id, repo_kara_id: kara.id, }; - let lang = NewLanguage::from(kara.get_language()); + let lang = { + self.ensure_languages_exist(&[NewLanguage::from(kara.get_language())])?; + AddKaraLanguage { + id: kara.id, + code: kara.language, + } + }; let kara_maker = vec![NewKaraMaker { id: local_id, name: kara.author_name, diff --git a/src/rust/lektor_db/src/lib.rs b/src/rust/lektor_db/src/lib.rs index 9767296a3514c3d536855b90563d8e995ca8caf1..379f5329c9d53ac8348bb8509318ea15c304bb3c 100644 --- a/src/rust/lektor_db/src/lib.rs +++ b/src/rust/lektor_db/src/lib.rs @@ -4,9 +4,9 @@ pub mod connexion; pub mod error; +pub mod models; pub mod queue; -pub(self) mod models; pub(self) mod schema; pub(self) use diesel::prelude::*; @@ -14,10 +14,10 @@ pub(self) use error::*; pub(self) use std::{collections::VecDeque, ops::Range, path::Path}; /// All the information needed to add a kara recieved from a repo! -pub(self) type NewKaraRequest<'a> = ( +pub type NewKaraRequest<'a> = ( models::KaraId, models::NewKara<'a>, Vec<models::NewKaraMaker<'a>>, - Vec<models::NewLanguage<'a>>, + Vec<models::AddKaraLanguage<'a>>, Vec<models::AddKaraTag<'a>>, ); diff --git a/src/rust/lektor_db/src/models.rs b/src/rust/lektor_db/src/models.rs index c4b5305218d9900b161f95d151d635885e56fa41..b42f25edebc82387fb70732815dc6d20747e5065 100644 --- a/src/rust/lektor_db/src/models.rs +++ b/src/rust/lektor_db/src/models.rs @@ -1,7 +1,13 @@ //! Models used for querying, inserting or updating the database. use crate::{schema::*, *}; -use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types}; +use diesel::{ + deserialize::{self, FromSqlRow}, + expression::AsExpression, + serialize::{self, ToSql}, + sql_types, + sqlite::Sqlite, +}; use kurisu_api::v1 as api_v1; #[derive(Debug, Insertable, Queryable, Selectable)] @@ -24,7 +30,7 @@ pub struct HistoryRecord { #[derive(Insertable)] #[diesel(table_name = repo)] -pub struct NewRepo<'a> { +pub(crate) struct NewRepo<'a> { pub id: i64, pub name: &'a str, } @@ -50,7 +56,7 @@ pub struct NewKaraMaker<'a> { /// Add a new language to a kara. The language must exists in the database. #[derive(Debug, Insertable)] #[diesel(table_name = kara_lang)] -pub struct NewKaraLanguage<'a> { +pub struct AddKaraLanguage<'a> { pub id: i64, pub code: &'a str, } @@ -58,7 +64,7 @@ pub struct NewKaraLanguage<'a> { /// Add a new language to the database. Will be deprecated. #[derive(Debug, Insertable)] #[diesel(table_name = iso_639_1)] -pub struct NewLanguage<'a> { +pub(crate) struct NewLanguage<'a> { pub code: &'a str, pub name_en: &'a str, pub is_iso: bool, @@ -92,25 +98,28 @@ pub struct AddKaraTag<'a> { #[derive(Debug, Insertable)] #[diesel(table_name = tag)] -pub struct NewTag<'a> { +pub(crate) struct NewTag<'a> { pub id: i64, pub name: &'a str, } -impl<'a> diesel::serialize::ToSql<sql_types::Nullable<sql_types::Text>, diesel::sqlite::Sqlite> - for KaraTagValue<'a> -{ - fn to_sql<'b>( - &'b self, - out: &mut diesel::serialize::Output<'b, '_, diesel::sqlite::Sqlite>, - ) -> diesel::serialize::Result { +impl<'a> ToSql<sql_types::Nullable<sql_types::Text>, Sqlite> for KaraTagValue<'a> { + fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, Sqlite>) -> serialize::Result { match self { - KaraTagValue::None => return Ok(diesel::serialize::IsNull::Yes), + KaraTagValue::None => return Ok(serialize::IsNull::Yes), KaraTagValue::Str(str) => out.set_value(*str), KaraTagValue::String(str) => out.set_value(str.as_str()), KaraTagValue::Integer(int) => out.set_value(format!("{int}")), }; - Ok(diesel::serialize::IsNull::No) + Ok(serialize::IsNull::No) + } +} + +impl<'a> FromSqlRow<sql_types::Nullable<sql_types::Text>, Sqlite> for KaraTagValue<'a> { + fn build_from_row<'b>(row: &impl diesel::row::Row<'b, Sqlite>) -> deserialize::Result<Self> { + Ok(row + .get_value::<sql_types::Nullable<sql_types::Text>, Option<String>, usize>(0)? + .map_or(KaraTagValue::None, KaraTagValue::String)) } } @@ -129,14 +138,14 @@ impl<'a> From<api_v1::Language<'a>> for NewLanguage<'a> { #[derive(Queryable, Selectable)] #[diesel(table_name = tag)] -pub struct Tag { +pub(crate) struct Tag { pub id: i64, pub name: String, } #[derive(Queryable, Selectable)] #[diesel(table_name = kara)] -pub struct Kara { +pub(crate) struct Kara { pub id: i64, pub is_dl: bool, pub song_title: String, @@ -145,3 +154,25 @@ pub struct Kara { pub source_name: String, pub file_hash: String, } + +#[derive(Debug, Queryable)] +#[diesel(table_name = repo_kara)] +pub struct KaraFilterByRepoId { + pub repo_id: i64, + + #[diesel(column_name = "repo_kara_id")] + pub kara_id: i64, +} + +#[derive(Debug, Queryable)] +#[diesel(table_name = repo_kara)] +pub struct KaraFilterByLocalId { + #[diesel(column_name = "local_kara_id")] + pub local_id: i64, +} + +#[derive(Debug)] +pub enum DeleteKaraFilter { + ByLocalId(KaraFilterByLocalId), + ByRepoId(KaraFilterByRepoId), +} diff --git a/src/rust/lektor_unsafe/src/db.rs b/src/rust/lektor_unsafe/src/db.rs index a17a4fe57014f00724296483cd1e700182d5a663..7ad17c31a04db81b26cba305c40e2a2b5ed2961d 100644 --- a/src/rust/lektor_unsafe/src/db.rs +++ b/src/rust/lektor_unsafe/src/db.rs @@ -5,7 +5,10 @@ //! defined in lektor's C code... use crate::*; -use lektor_db::connexion::LktDatabaseConnection; +use lektor_db::{ + connexion::LktDatabaseConnection, + models::{DeleteKaraFilter, KaraFilterByLocalId, KaraFilterByRepoId}, +}; /// Wrap the [`establish_connection`] function. On error log the message and /// return a [`std::ptr::null_mut`]. @@ -60,8 +63,12 @@ pub unsafe extern "C" fn lkt_database_delete_kara_by_repo( repo: i64, kara: i64, ) -> bool { - let db = db.as_mut().expect("passing a nullptr as db handle"); - db.delete_kara_by_repo(repo, kara) + db.as_mut() + .expect("passing a nullptr as db handle") + .delete_kara(DeleteKaraFilter::ByRepoId(KaraFilterByRepoId { + repo_id: repo, + kara_id: kara, + })) .map_err(|err| error!(target: "DB", "delete_kara_by_repo failed: {err}")) .is_ok() } @@ -77,8 +84,11 @@ pub unsafe extern "C" fn lkt_database_delete_kara_by_local_id( db: *mut LktDatabaseConnection, local_id: i64, ) -> bool { - let db = db.as_mut().expect("passing a nullptr as db handle"); - db.delete_kara_by_local_id(local_id) + db.as_mut() + .expect("passing a nullptr as db handle") + .delete_kara(DeleteKaraFilter::ByLocalId(KaraFilterByLocalId { + local_id, + })) .map_err(|err| error!(target: "DB", "delete_kara_by_local_id failed: {err}")) .is_ok() }