diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 84400d0..9a23381 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -659,7 +659,7 @@ dependencies = [ [[package]] name = "database" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "chrono", diff --git a/backend/database/Cargo.toml b/backend/database/Cargo.toml index ec40e0d..beebb98 100644 --- a/backend/database/Cargo.toml +++ b/backend/database/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "database" -version = "0.5.0" +version = "0.5.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/backend/database/src/changes.rs b/backend/database/src/changes.rs index affefd4..f3464ed 100644 --- a/backend/database/src/changes.rs +++ b/backend/database/src/changes.rs @@ -1,5 +1,7 @@ //! Contains general modification structs and enums. +use std::fmt::Display; + use itertools::Itertools; use tracing::instrument; @@ -51,13 +53,13 @@ where A: ItemMod, { #[instrument(skip_all)] - pub async fn submit_changes_with( + pub async fn submit_changes_with

( &self, parent: &P, - pool: &sqlx::Pool, + pool: &sqlx::Pool<>::Engine>, ) -> sqlx::Result<()> where - A: ItemMod + AttachmentItemMod, + A: ItemMod + AttachmentItemMod

, P: Item, { for attached in self.attached.iter() { @@ -124,29 +126,31 @@ pub trait CompareAttachable, A: ItemMod } } -impl CompareAttachable for KonsepItem { +impl CompareAttachable for KonsepItem { fn items(&self) -> Vec { Vec::clone(&self.cakupans) } } -impl CompareAttachable for KonsepItem { +impl CompareAttachable for KonsepItem { fn items(&self) -> Vec { Vec::clone(&self.kata_asing) } } -impl CompareAttachable for LemmaItem { - fn items(&self) -> Vec { +impl + CompareAttachable, KonsepItemMod> for LemmaItem +{ + fn items(&self) -> Vec> { Vec::clone(&self.konseps) } - fn find_attached(&self, other: &Vec) -> Vec { + fn find_attached(&self, other: &Vec>) -> Vec> { Vec::clone(other) .into_iter() .filter(|item| item.id == AutoGen::Unknown) .map(|item| KonsepItemMod::from_item(&item)) .collect_vec() } - fn find_detached(&self, other: &Vec) -> Vec { + fn find_detached(&self, other: &Vec>) -> Vec> { let other_ids = Vec::clone(other) .into_iter() .filter(|item| item.id != AutoGen::Unknown) @@ -158,7 +162,7 @@ impl CompareAttachable for LemmaItem { .map(|item| KonsepItemMod::from_item(&item)) .collect_vec() } - fn find_modified(&self, other: &Vec) -> Vec { + fn find_modified(&self, other: &Vec>) -> Vec> { let detached_id = self .find_detached(other) .iter() diff --git a/backend/database/src/data/items/cakupan.rs b/backend/database/src/data/items/cakupan.rs index 49b0261..63b6683 100644 --- a/backend/database/src/data/items/cakupan.rs +++ b/backend/database/src/data/items/cakupan.rs @@ -1,5 +1,7 @@ //! Contains the [CakupanItem]. +use std::fmt::{Debug, Display}; + use crate::io::interface::{AttachmentItemMod, FromView, Item, ItemMod, SubmitItem}; use crate::prelude::*; use tracing::instrument; @@ -91,12 +93,15 @@ impl From for CakupanItem { #[cfg(feature = "sqlite")] #[async_trait::async_trait] -impl AttachmentItemMod for CakupanItem { +impl AttachmentItemMod> + for CakupanItem +{ + type Engine = sqlx::Sqlite; #[instrument(skip_all)] async fn submit_attachment_to( &self, - parent: &KonsepItem, - pool: &sqlx::Pool, + parent: &KonsepItem, + pool: &sqlx::Pool, ) -> sqlx::Result<()> { tracing::trace!( "Attaching to <{}:{}>", @@ -123,8 +128,8 @@ impl AttachmentItemMod for CakupanItem { } async fn submit_detachment_from( &self, - parent: &KonsepItem, - pool: &sqlx::Pool, + parent: &KonsepItem, + pool: &sqlx::Pool, ) -> sqlx::Result<()> { tracing::trace!( "Detaching from <{}:{}>", @@ -150,8 +155,90 @@ impl AttachmentItemMod for CakupanItem { async fn submit_modification_with( &self, - parent: &KonsepItem, - _pool: &sqlx::Pool, + parent: &KonsepItem, + _pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + tracing::trace!( + "Modifying with <{}:{}>", + self.0, + parent.id, + parent.keterangan + ); + todo!() + } +} + +#[cfg(feature = "postgres")] +#[async_trait::async_trait] +impl AttachmentItemMod> + for CakupanItem +{ + type Engine = sqlx::Postgres; + #[instrument(skip_all)] + async fn submit_attachment_to( + &self, + parent: &KonsepItem, + pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + tracing::trace!( + "Attaching to <{}:{}>", + self.0, + parent.id, + parent.keterangan + ); + sqlx::query! { + r#"INSERT INTO cakupan (nama) VALUES ($1) ON CONFLICT (nama) DO NOTHING;"#, + self.0 + } + .execute(pool) + .await + .expect("Error attaching cakupan to konsep"); + + sqlx::query! { + r#"INSERT INTO cakupan_x_konsep (cakupan_id, konsep_id) + VALUES ( + (SELECT id FROM cakupan WHERE cakupan.nama = $1), + (SELECT id FROM konsep WHERE konsep.keterangan = $2) + ) ON CONFLICT (cakupan_id, konsep_id) DO NOTHING;"#, + self.0, + parent.keterangan + } + .execute(pool) + .await + .expect("Error attaching cakupan to konsep"); + Ok(()) + } + async fn submit_detachment_from( + &self, + parent: &KonsepItem, + pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + tracing::trace!( + "Detaching from <{}:{}>", + self.0, + parent.id, + parent.keterangan + ); + sqlx::query! { + r#" DELETE FROM cakupan_x_konsep AS cxk + WHERE ( + cxk.cakupan_id = (SELECT id FROM cakupan WHERE cakupan.nama = $1) + AND + cxk.konsep_id = (SELECT id FROM konsep WHERE konsep.keterangan = $2) + );"#, + self.0, + parent.keterangan + } + .execute(pool) + .await + .expect("Error detaching cakupan from konsep"); + Ok(()) + } + + async fn submit_modification_with( + &self, + parent: &KonsepItem, + _pool: &sqlx::Pool, ) -> sqlx::Result<()> { tracing::trace!( "Modifying with <{}:{}>", diff --git a/backend/database/src/data/items/kata_asing.rs b/backend/database/src/data/items/kata_asing.rs index 633bc58..ad40a42 100644 --- a/backend/database/src/data/items/kata_asing.rs +++ b/backend/database/src/data/items/kata_asing.rs @@ -1,5 +1,7 @@ //! Contains struct [KataAsingItem] which map a foreign word with its language of origin. +use std::fmt::Display; + use crate::io::interface::{AttachmentItemMod, FromView, Item, ItemMod, SubmitItem}; use crate::prelude::*; @@ -97,11 +99,15 @@ impl SubmitItem for KataAsingItem { #[cfg(feature = "sqlite")] #[async_trait::async_trait] -impl AttachmentItemMod for KataAsingItem { +impl AttachmentItemMod> + for KataAsingItem +{ + type Engine = sqlx::Sqlite; + async fn submit_attachment_to( &self, - parent: &KonsepItem, - pool: &sqlx::Pool, + parent: &KonsepItem, + pool: &sqlx::Pool, ) -> sqlx::Result<()> { tracing::trace!( "Attaching to <{}:{}>", @@ -130,8 +136,8 @@ impl AttachmentItemMod for KataAsingItem { } async fn submit_detachment_from( &self, - parent: &KonsepItem, - pool: &sqlx::Pool, + parent: &KonsepItem, + pool: &sqlx::Pool, ) -> sqlx::Result<()> { tracing::trace!( "Detaching from <{}:{}>", @@ -159,8 +165,90 @@ impl AttachmentItemMod for KataAsingItem { async fn submit_modification_with( &self, - parent: &KonsepItem, - _pool: &sqlx::Pool, + parent: &KonsepItem, + _pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + tracing::trace!( + "Modifying with <{}:{}>", + self.bahasa, + self.nama, + parent.id, + parent.keterangan + ); + todo!() + } +} + +#[cfg(feature = "postgres")] +#[async_trait::async_trait] +impl AttachmentItemMod> + for KataAsingItem +{ + type Engine = sqlx::Postgres; + + async fn submit_attachment_to( + &self, + parent: &KonsepItem, + pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + tracing::trace!( + "Attaching to <{}:{}>", + self.bahasa, + self.nama, + parent.id, + parent.keterangan + ); + sqlx::query! { + r#"INSERT INTO kata_asing (nama, bahasa) VALUES ($1,$2) ON CONFLICT (nama, bahasa) DO NOTHING;"#, self.nama, self.bahasa + }.execute(pool).await.expect("Error attaching kata_asing to konsep"); + sqlx::query! { + r#"INSERT INTO kata_asing_x_konsep (kata_asing_id, konsep_id) + VALUES ( + (SELECT id FROM kata_asing WHERE kata_asing.nama = $1 AND kata_asing.bahasa = $2), + (SELECT id FROM konsep WHERE konsep.keterangan = $3) + ) ON CONFLICT (kata_asing_id, konsep_id) DO NOTHING;"#, + self.nama, + self.bahasa, + parent.keterangan + } + .execute(pool) + .await + .expect("Error attaching kata_asing to konsep"); + Ok(()) + } + async fn submit_detachment_from( + &self, + parent: &KonsepItem, + pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + tracing::trace!( + "Detaching from <{}:{}>", + self.bahasa, + self.nama, + parent.id, + parent.keterangan + ); + sqlx::query! { + r#"DELETE FROM kata_asing_x_konsep AS kaxk + WHERE ( + kaxk.kata_asing_id = (SELECT id FROM kata_asing WHERE kata_asing.nama = $1 AND kata_asing.bahasa = $2) + AND + kaxk.konsep_id = (SELECT id FROM konsep WHERE konsep.keterangan = $3) + );"#, + self.nama, + self.bahasa, + parent.keterangan + } + .execute(pool) + .await + .expect("Error detaching cakupan from konsep"); + Ok(()) + } + + async fn submit_modification_with( + &self, + parent: &KonsepItem, + _pool: &sqlx::Pool, ) -> sqlx::Result<()> { tracing::trace!( "Modifying with <{}:{}>", diff --git a/backend/database/src/data/items/konsep.rs b/backend/database/src/data/items/konsep.rs index bde4276..8c3520d 100644 --- a/backend/database/src/data/items/konsep.rs +++ b/backend/database/src/data/items/konsep.rs @@ -4,6 +4,7 @@ use crate::changes::{AttachmentMod, CompareAttachable, FieldMod}; use crate::io::interface::{AttachmentItemMod, FromView, FromViewMap, Item, ItemMod}; use crate::prelude::*; use std::collections::HashMap; +use std::fmt::{Debug, Display}; use tracing::instrument; use crate::data::items::cakupan::CakupanItem; @@ -19,10 +20,10 @@ use crate::data::items::lemma::LemmaItem; /// The following are the tags implemented in this struct: /// - [cakupans](KonsepItem#structfield.cakupans): the communication contexts of the definition. /// - [kata_asing](KonsepItem#structfield.kata_asing): words with equivalent meaning in other languages. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)] +#[derive(Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)] #[ts(export, export_to = "../../src/bindings/")] -pub struct KonsepItem { - pub id: AutoGen, +pub struct KonsepItem { + pub id: AutoGen, pub keterangan: String, pub golongan_kata: String, #[ts(type = "Array")] @@ -30,20 +31,44 @@ pub struct KonsepItem { pub kata_asing: Vec, } +impl Debug for KonsepItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KonsepItem") + .field("id", &self.id) + .field("keterangan", &self.keterangan) + .field("golongan_kata", &self.golongan_kata) + .field("cakupans", &self.cakupans) + .field("kata_asing", &self.kata_asing) + .finish() + } +} + /// A modified [KonsepItem]. /// /// Its usage is similar to [LemmaItemMod](crate::data::LemmaItemMod). -#[derive(Debug, Clone, PartialEq)] -pub struct KonsepItemMod { - pub id: AutoGen, +#[derive(Clone, PartialEq)] +pub struct KonsepItemMod { + pub id: AutoGen, pub keterangan: FieldMod, pub golongan_kata: FieldMod, pub cakupans: AttachmentMod, pub kata_asing: AttachmentMod, } -impl ItemMod for KonsepItemMod { - type FromItem = KonsepItem; +impl Debug for KonsepItemMod { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KonsepItemMod") + .field("id", &self.id) + .field("keterangan", &self.keterangan) + .field("golongan_kata", &self.golongan_kata) + .field("cakupans", &self.cakupans) + .field("kata_asing", &self.kata_asing) + .finish() + } +} + +impl ItemMod for KonsepItemMod { + type FromItem = KonsepItem; fn from_item(value: &Self::FromItem) -> Self { Self { @@ -55,10 +80,10 @@ impl ItemMod for KonsepItemMod { } } } -impl KonsepItem { +impl KonsepItem { pub fn null() -> Self { Self { - id: AutoGen::Unknown, + id: AutoGen::::Unknown, keterangan: "".into(), golongan_kata: "".into(), cakupans: vec![], @@ -67,7 +92,7 @@ impl KonsepItem { } } -impl PartialEq for KonsepItem { +impl PartialEq for KonsepItem { fn eq(&self, other: &Self) -> bool { self.keterangan == other.keterangan && self.golongan_kata == other.golongan_kata @@ -89,12 +114,14 @@ impl PartialEq for KonsepItem { #[cfg(feature = "sqlite")] #[async_trait::async_trait] -impl AttachmentItemMod for KonsepItemMod { +impl AttachmentItemMod> for KonsepItemMod { + type Engine = sqlx::Sqlite; + #[instrument(skip_all)] async fn submit_attachment_to( &self, - parent: &LemmaItem, - pool: &sqlx::Pool, + parent: &LemmaItem, + pool: &sqlx::Pool, ) -> sqlx::Result<()> { let konsep = KonsepItem::partial_from_mod(self); tracing::trace!( @@ -124,8 +151,8 @@ impl AttachmentItemMod for KonsepItemMod { } async fn submit_detachment_from( &self, - parent: &LemmaItem, - pool: &sqlx::Pool, + parent: &LemmaItem, + pool: &sqlx::Pool, ) -> sqlx::Result<()> { tracing::trace!( "Detaching <{}:{}> from <{}:{}>", @@ -146,8 +173,8 @@ impl AttachmentItemMod for KonsepItemMod { async fn submit_modification_with( &self, - parent: &LemmaItem, - pool: &sqlx::Pool, + parent: &LemmaItem, + pool: &sqlx::Pool, ) -> sqlx::Result<()> { let konsep = KonsepItem::partial_from_mod(self); tracing::trace!( @@ -179,16 +206,119 @@ impl AttachmentItemMod for KonsepItemMod { } } -type Key = (i64, Option, Option); +#[cfg(feature = "postgres")] +#[async_trait::async_trait] +impl AttachmentItemMod> for KonsepItemMod { + type Engine = sqlx::Postgres; + + #[instrument(skip_all)] + async fn submit_attachment_to( + &self, + parent: &LemmaItem, + pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + let konsep = KonsepItem::partial_from_mod(self); + tracing::trace!( + "Attaching <{}:{}> to <{}:{}>", + konsep.id, + konsep.keterangan, + parent.id, + parent.lemma + ); + sqlx::query! { + r#" INSERT INTO konsep (keterangan, lemma_id, golongan_id) + VALUES ( + $1, + (SELECT id FROM lemma WHERE lemma.nama = $2), + (SELECT id FROM golongan_kata WHERE golongan_kata.nama = $3) + ) ON CONFLICT (id) DO NOTHING + "#, + konsep.keterangan, + parent.lemma, + konsep.golongan_kata + } + .execute(pool) + .await?; + self.cakupans.submit_changes_with(&konsep, pool).await?; + self.kata_asing.submit_changes_with(&konsep, pool).await?; + Ok(()) + } + async fn submit_detachment_from( + &self, + parent: &LemmaItem, + pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + tracing::trace!( + "Detaching <{}:{}> from <{}:{}>", + self.id, + self.keterangan.value(), + parent.id, + parent.lemma + ); + match (self.id, parent.id) { + (AutoGen::Known(i), AutoGen::Known(p)) => { + sqlx::query! { + r#" DELETE FROM konsep WHERE (id = $1 AND lemma_id = $2)"#, + i, + p + } + .execute(pool) + .await? + } + (_, _) => todo!(), + }; + Ok(()) + } + + async fn submit_modification_with( + &self, + parent: &LemmaItem, + pool: &sqlx::Pool, + ) -> sqlx::Result<()> { + let konsep = KonsepItem::partial_from_mod(self); + tracing::trace!( + "Modifying <{}:{}> with <{}:{}>", + konsep.id, + konsep.keterangan, + parent.id, + parent.lemma + ); + match (konsep.id, parent.id) { + (AutoGen::Known(i), AutoGen::Known(p)) => sqlx::query! { + r#" UPDATE konsep + SET keterangan = $1, golongan_id = (SELECT id FROM golongan_kata WHERE golongan_kata.nama = $2) + WHERE ( + id = $3 + AND + lemma_id = $4 + ) + "#, + konsep.keterangan, + konsep.golongan_kata, + i, + p, + } + .execute(pool) + .await?, + (_,_) => todo!() + }; + self.cakupans.submit_changes_with(&konsep, pool).await?; + self.kata_asing.submit_changes_with(&konsep, pool).await?; + Ok(()) + } +} + +type Key = (I, Option, Option); type Value = Vec; -pub(crate) type KonsepHashMap = HashMap; +pub(crate) type KonsepHashMap = HashMap, Value>; -impl Item for KonsepItem { - type IntoMod = KonsepItemMod; +impl Item for KonsepItem { + type IntoMod = KonsepItemMod; fn modify_into(&self, other: &Self) -> Result { if self.id != other.id { return Err(BackendError { - message: format!("ID Assertion error, {} != {}", self.id, other.id), + // message: format!("ID Assertion error, {} != {}", self.id, other.id), + message: format!("ID Assertion error"), }); } Ok(KonsepItemMod { @@ -214,10 +344,10 @@ impl Item for KonsepItem { } } -impl FromViewMap for KonsepItem { - type KEY = Key; +impl FromViewMap for KonsepItem { + type KEY = Key; type VALUE = Value; - fn from_viewmap(value: &KonsepHashMap) -> Vec { + fn from_viewmap(value: &KonsepHashMap) -> Vec { let mut data = Vec::new(); for (konsep, views) in value.iter().filter(|((_, kon, _), _)| kon.is_some()) { data.push(KonsepItem { diff --git a/backend/database/src/data/items/lemma.rs b/backend/database/src/data/items/lemma.rs index 028fe0a..b804043 100644 --- a/backend/database/src/data/items/lemma.rs +++ b/backend/database/src/data/items/lemma.rs @@ -10,7 +10,7 @@ use crate::{ }; use crate::io::interface::SubmitMod; -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Display}; use tracing::instrument; use super::konsep::KonsepHashMap; @@ -42,10 +42,10 @@ use super::konsep::KonsepHashMap; /// ``` #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)] #[ts(export, export_to = "../../src/bindings/")] -pub struct LemmaItem { - pub id: AutoGen, +pub struct LemmaItem { + pub id: AutoGen, pub lemma: String, - pub konseps: Vec, + pub konseps: Vec>, } /// A modified [LemmaItem]. @@ -131,14 +131,14 @@ pub struct LemmaItem { /// assert_eq!(lemma_modded.konseps.detached, vec![]); /// ``` #[derive(Debug, Clone)] -pub struct LemmaItemMod { - pub id: AutoGen, +pub struct LemmaItemMod { + pub id: AutoGen, pub lemma: FieldMod, - pub konseps: AttachmentMod, + pub konseps: AttachmentMod>, } -impl Item for LemmaItem { - type IntoMod = LemmaItemMod; +impl Item for LemmaItem { + type IntoMod = LemmaItemMod; fn modify_into(&self, other: &Self) -> Result { if self.id != other.id { Err(BackendError { @@ -153,7 +153,7 @@ impl Item for LemmaItem { } } - fn partial_from_mod(other: &LemmaItemMod) -> Self { + fn partial_from_mod(other: &LemmaItemMod) -> Self { LemmaItem { id: other.id, lemma: other.lemma.value().to_string(), @@ -162,8 +162,8 @@ impl Item for LemmaItem { } } -impl ItemMod for LemmaItemMod { - type FromItem = LemmaItem; +impl ItemMod for LemmaItemMod { + type FromItem = LemmaItem; fn from_item(value: &Self::FromItem) -> Self { Self { @@ -176,7 +176,7 @@ impl ItemMod for LemmaItemMod { #[cfg(feature = "sqlite")] #[async_trait::async_trait] -impl SubmitMod for LemmaItemMod { +impl SubmitMod for LemmaItemMod { type Engine = sqlx::Sqlite; #[instrument(skip_all)] async fn submit_mod(&self, pool: &sqlx::Pool) -> sqlx::Result<()> { @@ -188,7 +188,7 @@ impl SubmitMod for LemmaItemMod { } } -impl PartialEq for LemmaItem { +impl PartialEq for LemmaItem { fn eq(&self, other: &Self) -> bool { let konseps = Vec::from_iter(self.konseps.clone()); self.lemma == other.lemma @@ -203,7 +203,7 @@ impl PartialEq for LemmaItem { #[cfg(feature = "sqlite")] #[async_trait::async_trait] -impl SubmitItem for LemmaItem { +impl SubmitItem for LemmaItem { type Engine = sqlx::Sqlite; async fn submit_full(&self, pool: &sqlx::Pool) -> sqlx::Result<()> { let _ = self.submit_partial(pool).await?; @@ -244,7 +244,7 @@ impl SubmitItem for LemmaItem { #[cfg(feature = "postgres")] #[async_trait::async_trait] -impl SubmitItem for LemmaItem { +impl SubmitItem for LemmaItem { type Engine = sqlx::Postgres; async fn submit_full(&self, pool: &sqlx::Pool) -> sqlx::Result<()> { @@ -258,10 +258,16 @@ impl SubmitItem for LemmaItem { } async fn submit_partial(&self, pool: &sqlx::Pool) -> sqlx::Result<()> { - sqlx::query! { - r#"INSERT or IGNORE INTO lemma (id, nama) VALUES (?, ?)"#, - self.id, - self.lemma + match self.id { + AutoGen::Known(i) => sqlx::query! { + r#"INSERT INTO lemma (id, nama) VALUES ($1, $2) ON CONFLICT (id, nama) DO NOTHING;"#, + i, + self.lemma + }, + AutoGen::Unknown => sqlx::query!{ + r#"INSERT INTO lemma (nama) VALUES ($1);"#, + self.lemma + } } .execute(pool) .await?; @@ -273,23 +279,28 @@ impl SubmitItem for LemmaItem { } async fn submit_partial_removal(&self, pool: &sqlx::Pool) -> sqlx::Result<()> { - sqlx::query! { - r#"DELETE FROM lemma WHERE (lemma.id = ? AND lemma.nama = ?)"#, - self.id, - self.lemma - } - .execute(pool) - .await?; + match self.id { + AutoGen::Known(i) => { + sqlx::query! { + r#"DELETE FROM lemma WHERE (lemma.id = $1 AND lemma.nama = $2)"#, + i, + self.lemma + } + .execute(pool) + .await? + } + AutoGen::Unknown => todo!(), + }; Ok(()) } } -impl FromViewMap for LemmaItem { +impl FromViewMap for LemmaItem { type KEY = (i64, String); - type VALUE = KonsepHashMap; + type VALUE = KonsepHashMap; - fn from_viewmap(value: &HashMap) -> Vec { - let mut data = Vec::::new(); + fn from_viewmap(value: &HashMap) -> Vec> { + let mut data = Vec::>::new(); for (lemma, konsep_map) in value.iter() { data.push(LemmaItem { id: AutoGen::Known(lemma.0), @@ -300,10 +311,33 @@ impl FromViewMap for LemmaItem { data } } -impl FromView for LemmaItem { +impl FromViewMap for LemmaItem { + type KEY = (i32, String); + type VALUE = KonsepHashMap; + + fn from_viewmap(value: &HashMap) -> Vec> { + let mut data = Vec::>::new(); + for (lemma, konsep_map) in value.iter() { + data.push(LemmaItem { + id: AutoGen::Known(lemma.0), + lemma: lemma.1.clone(), + konseps: KonsepItem::from_viewmap(konsep_map), + }) + } + data + } +} +// impl FromView for LemmaItem { +// type VIEW = LemmaWithKonsepView; + +// fn from_views(views: &Vec) -> Vec> { +// Self::from_viewmap(&(views.clone().into_viewmap())) +// } +// } +impl FromView for LemmaItem { type VIEW = LemmaWithKonsepView; - fn from_views(views: &Vec) -> Vec { + fn from_views(views: &Vec) -> Vec> { Self::from_viewmap(&(views.clone().into_viewmap())) } } diff --git a/backend/database/src/io/interface.rs b/backend/database/src/io/interface.rs index 0d73d48..82cd55b 100644 --- a/backend/database/src/io/interface.rs +++ b/backend/database/src/io/interface.rs @@ -13,7 +13,7 @@ pub trait View { } /// A trait to convert Vec<[View]> into Vec<[Item]> -pub trait FromView: Item { +pub trait FromView: Sized { /// The view that is to be converted type VIEW; @@ -22,7 +22,7 @@ pub trait FromView: Item { } /// A trait that converts [HashMap] into Vec<[Item]>. -pub trait FromViewMap: Item { +pub trait FromViewMap: Sized { /// Keys of the input [HashMap], typically the ID but can also be a tuple of identifiers. type KEY; /// The value of the input [HashMap]. @@ -52,7 +52,7 @@ pub trait IntoViewMap: IntoIterator { /// A [View] handles the querying from SQL which contains flat tabular data. /// While and [Item] represents the intended data which is often nested. /// Traits like [FromView] and [FromViewMap] provides the translation layer between [View] and [Item]. -pub trait Item: Sized { +pub trait Item { /// The Modified version of [Self]. type IntoMod: ItemMod; @@ -69,7 +69,7 @@ pub trait Item: Sized { /// A trait which allows item data to be submitted into SQL databases. #[async_trait::async_trait] -pub trait SubmitItem: Item { +pub trait SubmitItem { type Engine: sqlx::Database; /// Inserts item with corresponding children. async fn submit_full(&self, pool: &sqlx::Pool) -> sqlx::Result<()>; @@ -103,14 +103,27 @@ pub trait SubmitMod: ItemMod { /// A trait which allows attachment items to be submitted into SQL database as [ItemMod]. #[async_trait::async_trait] -pub trait AttachmentItemMod: ItemMod { +pub trait AttachmentItemMod: ItemMod { + type Engine: sqlx::Database; + /// Submit [Self] to parent item. - async fn submit_attachment_to(&self, parent: &P, pool: &sqlx::Pool) -> sqlx::Result<()>; + async fn submit_attachment_to( + &self, + parent: &P, + pool: &sqlx::Pool, + ) -> sqlx::Result<()>; /// Detaches [Self] to parent item. - async fn submit_detachment_from(&self, parent: &P, pool: &sqlx::Pool) -> sqlx::Result<()>; + async fn submit_detachment_from( + &self, + parent: &P, + pool: &sqlx::Pool, + ) -> sqlx::Result<()>; /// Modifies [Self]. - async fn submit_modification_with(&self, parent: &P, pool: &sqlx::Pool) - -> sqlx::Result<()>; + async fn submit_modification_with( + &self, + parent: &P, + pool: &sqlx::Pool, + ) -> sqlx::Result<()>; } diff --git a/backend/database/src/states.rs b/backend/database/src/states.rs index 67f3070..0d73434 100644 --- a/backend/database/src/states.rs +++ b/backend/database/src/states.rs @@ -2,33 +2,30 @@ use sqlx::migrate::MigrateDatabase; use sqlx::migrate::{Migrate, MigrateError}; +use sqlx::FromRow; use crate::errors::Result; use crate::prelude::BackendError; -pub use sqlx::Pool; -#[cfg(feature = "sqlite")] -pub use sqlx::Sqlite; - // TODO: Refactor this module /// Connection to database #[derive(Debug, Clone)] pub struct Connection { #[allow(missing_docs)] - pub pool: Pool, + pub pool: sqlx::Pool, } /// Counts of selected items. #[allow(missing_docs)] #[derive(Debug, Clone, Default, serde::Serialize, PartialEq, ts_rs::TS)] #[ts(export, export_to = "../../src/bindings/")] -pub struct Counts { - lemmas: i32, - konseps: i32, - golongan_katas: i32, - cakupans: i32, - kata_asings: i32, +pub struct Counts { + lemmas: I, + konseps: I, + golongan_katas: I, + cakupans: I, + kata_asings: I, } /// A helper struct for when a single string value is queried. @@ -37,6 +34,41 @@ pub struct StringItem { pub item: String, } +impl Counts> { + fn unwrap_fields(self) -> Counts { + Counts { + lemmas: self.lemmas.unwrap_or(0), + konseps: self.konseps.unwrap_or(0), + golongan_katas: self.golongan_katas.unwrap_or(0), + cakupans: self.cakupans.unwrap_or(0), + kata_asings: self.kata_asings.unwrap_or(0), + } + } +} + +#[cfg(feature = "postgres")] +impl Connection { + pub async fn from(url: String) -> Self { + Self { + pool: sqlx::PgPool::connect(&url).await.unwrap(), + } + } + /// Queries the counts of each items as [Counts]. + /// + /// The function calls the following query: + /// ```sql + #[doc = include_str!("../transactions/postgres/count_items.sql")] + /// ``` + pub async fn statistics(self) -> Result> { + let inter: Counts> = + sqlx::query_file_as!(Counts, "transactions/postgres/count_items.sql") + .fetch_one(&self.pool) + .await + .map_err(BackendError::from)?; + Ok(inter.unwrap_fields()) + } +} + #[cfg(feature = "sqlite")] impl Connection { /// Forcefully reconnect to the specified url. @@ -63,28 +95,28 @@ impl Connection { } } - /// Queries all `golongan_kata.nama` and returns Result> + /// Queries the counts of each items as [Counts]. /// /// The function calls the following query: /// ```sql - /// SELECT nama AS item FROM golongan_kata + #[doc = include_str!("../transactions/sqlite/count_items.sql")] /// ``` - pub async fn get_golongan_kata_enumeration(self) -> Result> { - sqlx::query_as!(StringItem, "SELECT nama AS item FROM golongan_kata") - .fetch_all(&self.pool) + pub async fn statistics(self) -> Result> { + sqlx::query_file_as!(Counts, "transactions/sqlite/count_items.sql") + .fetch_one(&self.pool) .await .map_err(BackendError::from) } - /// Queries the counts of each items as [Counts]. + /// Queries all `golongan_kata.nama` and returns Result> /// /// The function calls the following query: /// ```sql - #[doc = include_str!("../transactions/count_items.sql")] + /// SELECT nama AS item FROM golongan_kata /// ``` - pub async fn statistics(self) -> Result { - sqlx::query_file_as!(Counts, "transactions/count_items.sql") - .fetch_one(&self.pool) + pub async fn get_golongan_kata_enumeration(self) -> Result> { + sqlx::query_as!(StringItem, "SELECT nama AS item FROM golongan_kata") + .fetch_all(&self.pool) .await .map_err(BackendError::from) } @@ -96,7 +128,7 @@ impl Connection { Ok(Self::migrate(pool).await) } - async fn migrate(pool: Pool) -> Self { + async fn migrate(pool: sqlx::Pool) -> Self { match sqlx::migrate!("migrations/sqlite").run(&pool).await { Ok(_) => Self { pool }, // Err(MigrateError::VersionMismatch(v)) => {println!("{}", v); Self{pool}} diff --git a/backend/database/src/types.rs b/backend/database/src/types.rs index c46a492..0a4c10a 100644 --- a/backend/database/src/types.rs +++ b/backend/database/src/types.rs @@ -9,22 +9,36 @@ use std::{cmp::Ordering, fmt::Debug}; /// For values automatically generated by database. /// /// It works similar to [Option] but conveys a clearer meaning. -#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq)] +#[derive(Clone, serde::Serialize, serde::Deserialize)] #[serde(untagged)] -pub enum AutoGen -where - T: core::fmt::Debug + PartialEq, -{ +pub enum AutoGen { /// The value is already provided by the database Known(T), /// The value is not yet provided by the database Unknown, } -impl Display for AutoGen -where - T: core::fmt::Debug + PartialEq + Display, -{ +impl Copy for AutoGen {} + +impl PartialEq for AutoGen { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Known(l0), Self::Known(r0)) => l0 == r0, + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + +impl Debug for AutoGen { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Known(arg0) => f.debug_tuple("Known").field(arg0).finish(), + Self::Unknown => write!(f, "Unknown"), + } + } +} + +impl Display for AutoGen { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, @@ -41,10 +55,7 @@ where } } -impl From> for Option -where - T: core::fmt::Debug + PartialEq, -{ +impl From> for Option { fn from(value: AutoGen) -> Option { match value { AutoGen::Known(v) => Some(v), @@ -55,7 +66,7 @@ where impl Encode<'_, DB> for AutoGen where - T: core::fmt::Debug + PartialEq + for<'a> sqlx::Encode<'a, DB>, + T: for<'a> sqlx::Encode<'a, DB>, Self: Copy, Option: for<'a> Encode<'a, DB>, { @@ -66,17 +77,14 @@ where impl Type for AutoGen where - T: core::fmt::Debug + PartialEq + Type, + T: Type, { fn type_info() -> DB::TypeInfo { as Type>::type_info() } } -impl PartialOrd for AutoGen -where - T: PartialOrd + Debug, -{ +impl PartialOrd for AutoGen { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (AutoGen::Known(a), AutoGen::Known(b)) => a.partial_cmp(b), @@ -87,10 +95,7 @@ where } } -impl From> for AutoGen -where - T: ts_rs::TS + core::fmt::Debug + PartialEq, -{ +impl From> for AutoGen { fn from(value: Option) -> Self { match value { Some(t) => Self::Known(t), @@ -100,10 +105,7 @@ where } /// `AutoGen` acts like an option in JS/TS -impl ts_rs::TS for AutoGen -where - T: ts_rs::TS + core::fmt::Debug + PartialEq, -{ +impl ts_rs::TS for AutoGen { fn name() -> String { as ts_rs::TS>::name() } diff --git a/backend/database/src/views.rs b/backend/database/src/views.rs index 3272b33..270b557 100644 --- a/backend/database/src/views.rs +++ b/backend/database/src/views.rs @@ -89,7 +89,7 @@ impl LemmaWithKonsepView { impl IntoViewMap for Vec { type KEY = (i64, String); - type VALUE = KonsepHashMap; + type VALUE = KonsepHashMap; fn into_viewmap(self) -> HashMap { self.into_iter() diff --git a/backend/database/transactions/postgres/count_items.sql b/backend/database/transactions/postgres/count_items.sql new file mode 100644 index 0000000..3127a61 --- /dev/null +++ b/backend/database/transactions/postgres/count_items.sql @@ -0,0 +1,7 @@ +SELECT + COUNT(DISTINCT lemma.id) AS lemmas, + COUNT(DISTINCT konsep.id) AS konseps, + COUNT(DISTINCT golongan_kata.id) as golongan_katas, + COUNT(DISTINCT cakupan.id) as cakupans, + COUNT(DISTINCT kata_asing.id) as kata_asings +FROM lemma, konsep, golongan_kata, cakupan, kata_asing \ No newline at end of file diff --git a/backend/database/transactions/count_items.sql b/backend/database/transactions/sqlite/count_items.sql similarity index 100% rename from backend/database/transactions/count_items.sql rename to backend/database/transactions/sqlite/count_items.sql