Skip to content
Extraits de code Groupes Projets
Vérifiée Valider f97142ac rédigé par Kubat's avatar Kubat
Parcourir les fichiers

RUST: Add functions in the new db to add/delete karas

parent 12b1f987
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
...@@ -5,12 +5,17 @@ ...@@ -5,12 +5,17 @@
extern "C" { extern "C" {
#endif #endif
#include <stdint.h>
struct lkt_sqlite_connection; struct lkt_sqlite_connection;
typedef struct lkt_sqlite_connection lkt_sqlite_connection; typedef struct lkt_sqlite_connection lkt_sqlite_connection;
lkt_sqlite_connection *lkt_database_establish_connection(const char *); lkt_sqlite_connection *lkt_database_establish_connection(const char *);
void lkt_database_close_connection(lkt_sqlite_connection *const); void lkt_database_close_connection(lkt_sqlite_connection *const);
bool lkt_database_delete_kara_by_repo(lkt_sqlite_connection *const, int64_t, int64_t);
bool lkt_database_delete_kara_by_local_id(lkt_sqlite_connection *const, int64_t);
#if defined(__cplusplus) #if defined(__cplusplus)
} }
#endif #endif
......
use super::*;
pub enum LktDatabaseError {
DieselConnection(diesel::ConnectionError),
DieselResult(diesel::result::Error),
IntegerOverflow,
String(String),
}
pub type LktDatabaseResult<T> = Result<T, LktDatabaseError>;
impl From<diesel::ConnectionError> for LktDatabaseError {
fn from(err: diesel::ConnectionError) -> Self {
LktDatabaseError::DieselConnection(err)
}
}
impl From<diesel::result::Error> for LktDatabaseError {
fn from(err: diesel::result::Error) -> Self {
LktDatabaseError::DieselResult(err)
}
}
impl From<std::num::TryFromIntError> for LktDatabaseError {
fn from(_: std::num::TryFromIntError) -> Self {
LktDatabaseError::IntegerOverflow
}
}
impl std::fmt::Display for LktDatabaseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use LktDatabaseError::*;
match self {
DieselConnection(err) => write!(f, "diesel connection error: {err}"),
DieselResult(err) => write!(f, "diesel result error: {err}"),
IntegerOverflow => f.write_str("integer overflow when casting to i32"),
String(str) => f.write_str(str),
}
}
}
...@@ -2,15 +2,15 @@ ...@@ -2,15 +2,15 @@
pub(self) use diesel::prelude::*; pub(self) use diesel::prelude::*;
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
pub(self) use error::*;
pub(self) use log::*; pub(self) use log::*;
pub mod error;
pub mod models; pub mod models;
pub mod schema; pub mod schema;
pub mod unsafe_interface; pub mod unsafe_interface;
use self::models::*; use crate::{database::models::*, kurisu_api::v1 as api_v1};
use crate::{database::schema::kara_tags, kurisu_api::v1 as api_v1};
/// The migrations! /// The migrations!
const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
...@@ -32,10 +32,10 @@ pub fn establish_connection(path: impl AsRef<str>) -> Result<SqliteConnection, S ...@@ -32,10 +32,10 @@ pub fn establish_connection(path: impl AsRef<str>) -> Result<SqliteConnection, S
/// All the information needed to add a kara recieved from a repo! /// All the information needed to add a kara recieved from a repo!
pub type NewKaraRequest<'a> = ( pub type NewKaraRequest<'a> = (
models::NewKaraId, models::KaraId,
models::NewKara<'a>, models::NewKara<'a>,
models::NewLanguage<'a>, models::Language<'a>,
Vec<models::AddKaraMaker<'a>>, Vec<models::KaraMaker<'a>>,
Vec<models::AddKaraTag>, Vec<models::AddKaraTag>,
); );
...@@ -43,47 +43,119 @@ pub struct LktDatabaseConnection { ...@@ -43,47 +43,119 @@ pub struct LktDatabaseConnection {
sqlite: SqliteConnection, sqlite: SqliteConnection,
} }
/// Load the diesel DSL for a given table. With the loaded dsl execute the
/// expression...
macro_rules! with_dsl {
($table: ident => $expr: expr) => {{
use self::schema::$table::dsl::*;
use diesel::dsl::*;
$expr
}};
}
impl LktDatabaseConnection { impl LktDatabaseConnection {
/// Get a tag id by its name. /// Get a tag id by its name.
pub fn get_tag_id_by_name(&mut self, tag_name: impl AsRef<str>) -> i32 { pub fn get_tag_id_by_name(&mut self, tag_name: impl AsRef<str>) -> LktDatabaseResult<i32> {
use self::schema::tag::dsl::*; Ok(with_dsl!(tag => tag.filter(name.is(tag_name.as_ref()))
tag.filter(name.is(tag_name.as_ref())) .first::<Tag>(&mut self.sqlite)?.id
.first::<Tag>(&mut self.sqlite) ))
.unwrap()
.id
} }
/// Get a free local id for all karas. /// Get a free local id for all karas. Note that using this function might
pub fn get_kara_new_local_id(&mut self) -> i32 { /// be unsafe because there is no guarenties that the returned ID will be
use self::schema::kara::dsl::*; /// free by the time a kara is inserted with the said id...
use diesel::dsl::*; pub fn get_kara_new_local_id(&mut self) -> LktDatabaseResult<i32> {
kara.select(max(id)) let max = with_dsl!(kara => kara.select(max(id))
.first::<Option<i32>>(&mut self.sqlite) .first::<Option<i32>>(&mut self.sqlite)?
.unwrap()
.unwrap_or(0) .unwrap_or(0)
);
Ok(max + 1)
}
/// Delete a kara with its id in a repo.
pub fn delete_kara_by_repo(
&mut self,
arg_repo_id: u64,
arg_kara_id: u64,
) -> LktDatabaseResult<()> {
let arg_repo_id = i32::try_from(arg_repo_id)?;
let arg_kara_id = i32::try_from(arg_kara_id)?;
self.sqlite.exclusive_transaction(|c| {
let local_id = with_dsl!(repo_kara => repo_kara
.filter(repo_id.eq(arg_repo_id))
.filter(repo_kara_id.eq(arg_kara_id))
.first::<KaraId>(c)?
.local_kara_id
);
with_dsl!(repo_kara => diesel::delete(repo_kara.filter(local_kara_id.eq(local_id))).execute(c)?);
with_dsl!(kara => diesel::delete(kara.filter(id.eq(local_id))).execute(c)?);
Ok(())
})
}
/// Delete a kara by its local ID.
pub fn delete_kara_by_local_id(&mut self, kara_id: u64) -> LktDatabaseResult<()> {
let local_id = i32::try_from(kara_id)?;
self.sqlite.exclusive_transaction(|c| {
with_dsl!(repo_kara => diesel::delete(repo_kara.filter(local_kara_id.eq(local_id))).execute(c)?);
with_dsl!(kara => diesel::delete(kara.filter(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.
pub fn ensure_language_exists(&mut self, lang: &Language) -> LktDatabaseResult<()> {
self.sqlite.exclusive_transaction(|c| {
with_dsl!(iso_639_1 => match iso_639_1.filter(code.eq(lang.code)).count().get_result(c)? {
1 => Ok(()),
0 => { diesel::insert_into(iso_639_1).values(lang).execute(c)?; Ok(()) }
count => Err(LktDatabaseError::String(format!("language `{lang:?}` has {count} occurences in the database..."))),
})
})
}
/// Add a kara with a request.
pub fn add_kara_from_request<'a>(&mut self, kara: NewKaraRequest<'a>) -> LktDatabaseResult<()> {
let (id, new_kara, lang, karamakers, tags) = kara;
self.ensure_language_exists(&lang)?;
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_makers => diesel::insert_or_ignore_into(kara_makers).values(karamakers).execute(c)?);
with_dsl!(kara_tags => {
diesel::delete(kara_tags.filter(kara_id.eq(new_kara.id))).execute(c)?;
diesel::insert_into(kara_tags).values(tags).execute(c)?;
});
Ok(())
})
} }
/// Create a series of models from a kara signature from Kurisu's V1 API. /// Create a series of models from a kara signature from Kurisu's V1 API.
pub fn new_kara<'a>(&mut self, repo_id: u64, kara: api_v1::Kara<'a>) -> NewKaraRequest<'a> { pub fn new_kara_v1<'a>(
error!("todo: query the database for a new local id"); &mut self,
let local_id = self.get_kara_new_local_id(); repo_id: u64,
let id = NewKaraId { kara: api_v1::Kara<'a>,
repo_id: repo_id as i32, ) -> LktDatabaseResult<NewKaraRequest<'a>> {
local_kara_id: local_id as i32, let local_id = self.get_kara_new_local_id()?;
repo_kara_id: kara.id as i32, let repo_id = i32::try_from(repo_id)?;
let id = KaraId {
repo_id,
local_kara_id: local_id,
repo_kara_id: i32::try_from(kara.id)?,
}; };
let lang = NewLanguage::from(kara.get_language()); let lang = Language::from(kara.get_language());
let kara_makers = vec![AddKaraMaker { let kara_makers = vec![KaraMaker {
id: local_id as i32, id: local_id,
name: kara.author_name, name: kara.author_name,
}]; }];
let tags = vec![AddKaraTag { let tags = vec![AddKaraTag {
kara_id: local_id as i32, kara_id: local_id,
tag_id: self.get_tag_id_by_name("number"), tag_id: self.get_tag_id_by_name("number")?,
value: Some(format!("{}", kara.song_number)), value: Some(format!("{}", kara.song_number)),
}]; }];
let kara = NewKara { let kara = NewKara {
id: local_id as i32, id: local_id,
song_title: kara.song_name, song_title: kara.song_name,
song_type: kara.song_type, song_type: kara.song_type,
song_origin: kara.category, song_origin: kara.category,
...@@ -91,6 +163,6 @@ impl LktDatabaseConnection { ...@@ -91,6 +163,6 @@ impl LktDatabaseConnection {
language: lang.code, language: lang.code,
file_hash: format!("{}", kara.unix_timestamp), file_hash: format!("{}", kara.unix_timestamp),
}; };
(id, kara, lang, kara_makers, tags) Ok((id, kara, lang, kara_makers, tags))
} }
} }
...@@ -14,15 +14,15 @@ pub struct NewRepo<'a> { ...@@ -14,15 +14,15 @@ pub struct NewRepo<'a> {
pub name: &'a str, pub name: &'a str,
} }
#[derive(Insertable)] #[derive(Debug, Insertable, Queryable, Selectable)]
#[diesel(table_name = repo_kara)] #[diesel(table_name = repo_kara)]
pub struct NewKaraId { pub struct KaraId {
pub repo_id: i32, pub repo_id: i32,
pub repo_kara_id: i32, pub repo_kara_id: i32,
pub local_kara_id: i32, pub local_kara_id: i32,
} }
#[derive(Insertable)] #[derive(Debug, Insertable)]
#[diesel(table_name = kara)] #[diesel(table_name = kara)]
pub struct NewKara<'a> { pub struct NewKara<'a> {
pub id: i32, pub id: i32,
...@@ -34,16 +34,16 @@ pub struct NewKara<'a> { ...@@ -34,16 +34,16 @@ pub struct NewKara<'a> {
pub file_hash: String, pub file_hash: String,
} }
#[derive(Insertable)] #[derive(Debug, Insertable, Queryable, Selectable)]
#[diesel(table_name = kara_makers)] #[diesel(table_name = kara_makers)]
pub struct AddKaraMaker<'a> { pub struct KaraMaker<'a> {
pub id: i32, pub id: i32,
pub name: &'a str, pub name: &'a str,
} }
#[derive(Insertable)] #[derive(Debug, Insertable, Queryable, Selectable)]
#[diesel(table_name = iso_639_1)] #[diesel(table_name = iso_639_1)]
pub struct NewLanguage<'a> { pub struct Language<'a> {
pub code: &'a str, pub code: &'a str,
pub name_en: &'a str, pub name_en: &'a str,
pub is_iso: bool, pub is_iso: bool,
...@@ -58,7 +58,7 @@ pub struct AddKaraTag { ...@@ -58,7 +58,7 @@ pub struct AddKaraTag {
pub value: Option<String>, pub value: Option<String>,
} }
impl<'a> From<api_v1::Language<'a>> for NewLanguage<'a> { impl<'a> From<api_v1::Language<'a>> for Language<'a> {
fn from(lang: api_v1::Language<'a>) -> Self { fn from(lang: api_v1::Language<'a>) -> Self {
Self { Self {
code: lang.code, code: lang.code,
...@@ -70,7 +70,8 @@ impl<'a> From<api_v1::Language<'a>> for NewLanguage<'a> { ...@@ -70,7 +70,8 @@ impl<'a> From<api_v1::Language<'a>> for NewLanguage<'a> {
} }
// Then the queriable things // Then the queriable things
#[derive(Queryable)]
#[derive(Queryable, Selectable)]
#[diesel(table_name = tag)] #[diesel(table_name = tag)]
pub struct Tag { pub struct Tag {
pub id: i32, pub id: i32,
......
...@@ -41,3 +41,33 @@ pub unsafe extern "C" fn lkt_database_close_connection(db: *mut LktDatabaseConne ...@@ -41,3 +41,33 @@ pub unsafe extern "C" fn lkt_database_close_connection(db: *mut LktDatabaseConne
drop(db); drop(db);
} }
} }
/// Delete a kara by its repo and id in the repo ids. The passed database
/// pointer must be allocated by the [lkt_database_establish_connection]
/// function. The function will return `true` on success, `false` otherwise. Any
/// error will be logged.
#[no_mangle]
pub unsafe extern "C" fn lkt_database_delete_kara_by_repo(
db: *mut LktDatabaseConnection,
repo: u64,
kara: u64,
) -> bool {
let db = db.as_mut().expect("passing a nullptr as db handle");
db.delete_kara_by_repo(repo, kara)
.map_err(|err| error!(target: "DB", "delete_kara_by_repo failed: {err}"))
.is_ok()
}
/// Delete a kara by its local id. The passed database pointer must be allocated
/// by the [lkt_database_establish_connection] function. The function will
/// return `true` on success, `false` otherwise. Any error will be logged.
#[no_mangle]
pub unsafe extern "C" fn lkt_database_delete_kara_by_local_id(
db: *mut LktDatabaseConnection,
local_id: u64,
) -> bool {
let db = db.as_mut().expect("passing a nullptr as db handle");
db.delete_kara_by_local_id(local_id)
.map_err(|err| error!(target: "DB", "delete_kara_by_local_id failed: {err}"))
.is_ok()
}
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Veuillez vous inscrire ou vous pour commenter