diff --git a/Cargo.toml b/Cargo.toml index 49cb226..01a26a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,22 @@ [package] name = "akita" -version = "0.2.10" +version = "0.2.11" authors = ["mrpan <1049058427@qq.com>"] edition = "2018" -description = "Akita.Mini Database Helper For MySQL." +description = "Akita - Mini orm for rust." readme = "README.md" -keywords = ["akita", "mysql", "sql"] +keywords = ["akita", "orm", "mysql", "sqlite"] categories = ["data-structures", "database-implementations"] homepage = "https://github.com/wslongchen/akita" repository = "https://github.com/wslongchen/akita" +documentation = "https://docs.rs/akita" license = "MIT" [dependencies] akita_derive = {version = "0.2.5", path = "./akita_derive"} -mysql = {version = "19.0.1"} +mysql = {version = "20.1.0", optional = true} +rusqlite = {version = "0.21.0", optional = true} +bigdecimal = "0.3.0" r2d2 = {version = "0.8.9"} chrono = { version = "0.4", features = ["serde"]} uuid = {version = "0.8.2", features = ["serde", "v4"]} @@ -22,16 +25,15 @@ log = "0.4" twox-hash = "1" url = "2.2.2" serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + [dev-dependencies] akita_derive = { version = "0.2.0", path = "./akita_derive" } -[package.metadata.playground] -# features = ["akita-mysql"] - [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] -# features = ["akita-mysql"] +features = ["akita-mysql"] ### FEATURES ################################################################# @@ -39,8 +41,6 @@ targets = ["x86_64-unknown-linux-gnu"] # default = [] # Provide mysql pool with r2d2. -# akita-mysql = [] +akita-mysql = ["mysql"] -# Provide impls for HashMap. -# Requires a dependency on the Rust standard library. -std = [] +akita-sqlite = ["rusqlite"] \ No newline at end of file diff --git a/README.md b/README.md index 23404b5..f97c3c2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Akita   [![Build Status]][actions] [![Latest Version]][crates.io] [![akita: rustc 1.13+]][Rust 1.13] [![akita_derive: rustc 1.31+]][Rust 1.31] -[Build Status]: https://img.shields.io/docsrs/akita/0.2.10?style=plastic +[Build Status]: https://img.shields.io/docsrs/akita/0.2.11?style=plastic [actions]: https://github.com/wslongchen/akita/actions?query=branch%3Amaster [Latest Version]: https://img.shields.io/crates/v/akita?style=plastic [crates.io]: https://crates.io/crates/akita @@ -9,13 +9,22 @@ [Rust 1.13]: https://blog.rust-lang.org/2016/11/10/Rust-1.13.html [Rust 1.31]: https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html -**Akita is a mini orm framework for MySQL.** +**Akita - Mini orm for rust with SQLite & MySQL.** +This create offers: +* MySql database's helper in pure rust; +* SQLite database's helper in pure rust; +* A mini orm framework (With MySQL/SQLite)。 + +Features: + +* Other Database support, i.e. support Oracle, MSSQL...; +* support of named parameters for custom condition; --- You may be looking for: -- [An overview of Akita (Coming Soon...)]() +- [An overview of Akita](https://crates.io/crates/akita) - [Examples](https://github.com/wslongchen/akita/blob/0.2.0/example/simple.rs) - [API documentation](https://docs.rs/akita/0.1.6/akita/) - [Release notes](https://github.com/wslongchen/akita/releases) @@ -76,6 +85,11 @@ fn main() { ``` +## Feature. + +* ```akita-mysql``` - to use mysql +* ```akita-sqlite``` - to use sqlite + ## Annotions. * ```Table``` - to make Akita work with structs diff --git a/example/akita.sqlite3 b/example/akita.sqlite3 new file mode 100644 index 0000000..d9552cd Binary files /dev/null and b/example/akita.sqlite3 differ diff --git a/src/comm.rs b/src/comm.rs index b075534..3ceec9f 100644 --- a/src/comm.rs +++ b/src/comm.rs @@ -139,4 +139,87 @@ pub fn extract_datatype_with_capacity(data_type: &str) -> (String, Option &str { + arg.trim_start_matches('(').trim_end_matches(')') +} + +pub fn maybe_trim_parenthesis(arg: &str) -> &str { + if arg.starts_with('(') && arg.ends_with(')') { + trim_parenthesis(arg) + } else { + arg + } +} + + +#[macro_export] +macro_rules! cfg_if { + // match if/else chains with a final `else` + ( + $( + if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } + ) else+ + else { $( $e_tokens:tt )* } + ) => { + $crate::cfg_if! { + @__items () ; + $( + (( $i_meta ) ( $( $i_tokens )* )) , + )+ + (() ( $( $e_tokens )* )) , + } + }; + + // match if/else chains lacking a final `else` + ( + if #[cfg( $i_meta:meta )] { $( $i_tokens:tt )* } + $( + else if #[cfg( $e_meta:meta )] { $( $e_tokens:tt )* } + )* + ) => { + $crate::cfg_if! { + @__items () ; + (( $i_meta ) ( $( $i_tokens )* )) , + $( + (( $e_meta ) ( $( $e_tokens )* )) , + )* + } + }; + + // Internal and recursive macro to emit all the items + // + // Collects all the previous cfgs in a list at the beginning, so they can be + // negated. After the semicolon is all the remaining items. + (@__items ( $( $_:meta , )* ) ; ) => {}; + ( + @__items ( $( $no:meta , )* ) ; + (( $( $yes:meta )? ) ( $( $tokens:tt )* )) , + $( $rest:tt , )* + ) => { + // Emit all items within one block, applying an appropriate #[cfg]. The + // #[cfg] will require all `$yes` matchers specified and must also negate + // all previous matchers. + #[cfg(all( + $( $yes , )? + not(any( $( $no ),* )) + ))] + $crate::cfg_if! { @__identity $( $tokens )* } + + // Recurse to emit all other items in `$rest`, and when we do so add all + // our `$yes` matchers to the list of `$no` matchers as future emissions + // will have to negate everything we just matched as well. + $crate::cfg_if! { + @__items ( $( $no , )* $( $yes , )? ) ; + $( $rest , )* + } + }; + + // Internal macro to make __apply work out right for different match types, + // because of how macros match/expand stuff. + (@__identity $( $tokens:tt )* ) => { + $( $tokens )* + }; } \ No newline at end of file diff --git a/src/database.rs b/src/database.rs index d4b379e..430f7e4 100644 --- a/src/database.rs +++ b/src/database.rs @@ -2,7 +2,15 @@ use std::{convert::TryFrom, ops::Deref}; use url::Url; -use crate::{AkitaError, data::Rows, information::{DatabaseName, TableDef, TableName,}, mysql::MysqlDatabase, pool::LogLevel, value::Value}; +cfg_if! {if #[cfg(feature = "akita-sqlite")]{ + use crate::platform::sqlite::SqliteDatabase; +}} + +cfg_if! {if #[cfg(feature = "akita-mysql")]{ + use crate::platform::mysql::MysqlDatabase; +}} + +use crate::{AkitaError, cfg_if, data::Rows, information::{DatabaseName, TableDef, TableName}, value::Value}; pub trait Database { @@ -12,7 +20,7 @@ pub trait Database { fn rollback_transaction(&mut self) -> Result<(), AkitaError>; - fn execute_result(&mut self, sql: &str, param: &[&Value], log: Option) -> Result; + fn execute_result(&mut self, sql: &str, param: &[&Value]) -> Result; fn get_table(&mut self, table_name: &TableName) -> Result, AkitaError>; @@ -33,7 +41,10 @@ pub trait Database { pub enum DatabasePlatform { + #[cfg(feature = "akita-mysql")] Mysql(Box), + #[cfg(feature = "akita-sqlite")] + Sqlite(Box), } impl Deref for DatabasePlatform { @@ -41,7 +52,10 @@ impl Deref for DatabasePlatform { fn deref(&self) -> &Self::Target { match *self { + #[cfg(feature = "akita-mysql")] DatabasePlatform::Mysql(ref mysql) => mysql.deref(), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(ref sqlite) => sqlite.deref(), } } } @@ -49,13 +63,19 @@ impl Deref for DatabasePlatform { impl std::ops::DerefMut for DatabasePlatform { fn deref_mut(&mut self) -> &mut Self::Target { match *self { + #[cfg(feature = "akita-mysql")] DatabasePlatform::Mysql(ref mut mysql) => mysql.deref_mut(), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(ref mut sqlite) => sqlite.deref_mut(), } } } pub(crate) enum Platform { + #[cfg(feature = "akita-mysql")] Mysql, + #[cfg(feature = "akita-sqlite")] + Sqlite(String), Unsupported(String), } @@ -68,7 +88,16 @@ impl<'a> TryFrom<&'a str> for Platform { Ok(url) => { let scheme = url.scheme(); match scheme { + #[cfg(feature = "akita-mysql")] "mysql" => Ok(Platform::Mysql), + #[cfg(feature = "akita-sqlite")] + "sqlite" => { + let host = url.host_str().unwrap_or_default(); + let path = url.path(); + let path = if path == "/" { "" } else { path }; + let db_file = format!("{}{}", host, path); + Ok(Platform::Sqlite(db_file)) + }, _ => Ok(Platform::Unsupported(scheme.to_string())), } } diff --git a/src/errors.rs b/src/errors.rs index 1b7a33f..b460ded 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -11,6 +11,7 @@ pub enum AkitaError { MissingTable(String), MissingField(String), MySQLError(String), + SQLiteError(String), ExcuteSqlError(String, String), DataError(String), R2D2Error(String), @@ -35,6 +36,7 @@ impl fmt::Display for AkitaError { AkitaError::MissingField(ref err) => err.fmt(f), AkitaError::RedundantField(ref err) => err.fmt(f), AkitaError::MySQLError(ref err) => err.fmt(f), + AkitaError::SQLiteError(ref err) => err.fmt(f), AkitaError::R2D2Error(ref err) => err.fmt(f), } } @@ -56,6 +58,7 @@ impl std::error::Error for AkitaError { AkitaError::MissingField(ref err) => err, AkitaError::RedundantField(ref err) => err, AkitaError::MySQLError(ref err) => err, + AkitaError::SQLiteError(ref err) => err, AkitaError::R2D2Error(ref err) => err, } } @@ -75,7 +78,7 @@ impl From for AkitaError { } } - +#[cfg(feature = "akita-mysql")] impl From for AkitaError { fn from(err: mysql::Error) -> Self { AkitaError::MySQLError(err.to_string()) @@ -88,18 +91,28 @@ impl From for AkitaError { } } +#[cfg(feature = "akita-mysql")] impl From for AkitaError { fn from(err: mysql::UrlError) -> Self { AkitaError::MySQLError(err.to_string()) } } +#[cfg(feature = "akita-sqlite")] +impl From for AkitaError { + fn from(err: rusqlite::Error) -> Self { + AkitaError::SQLiteError(err.to_string()) + } +} + +#[cfg(feature = "akita-mysql")] impl From for AkitaError { fn from(err: mysql::FromValueError) -> Self { AkitaError::MySQLError(err.to_string()) } } +#[cfg(feature = "akita-mysql")] impl From for AkitaError { fn from(err: mysql::FromRowError) -> Self { AkitaError::MySQLError(err.to_string()) diff --git a/src/lib.rs b/src/lib.rs index 37a14b0..7bcb914 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2020 rust-mysql-simple contributors +// Copyright (c) 2020 akita contributors // // Licensed under the Apache License, Version 2.0 // or the MIT @@ -8,13 +8,12 @@ //! This create offers: //! -//! * MySql database's helper in pure rust; -//! * A mini orm framework (Just MySQL)。 +//! * MySql/SQLite database's helper in pure rust; +//! * A mini orm framework (Just MySQL/SQLite)。 //! //! Features: //! -//! * Other Database support, i.e. support SQLite, Oracle, MSSQL...; -//! * support of original SQL; +//! * Other Database support, i.e. support Oracle, MSSQL...; //! * support of named parameters for custom condition; //! //! ## Installation @@ -26,6 +25,13 @@ //! akita = "*" //! ``` //! +//! +//! ## Feature. +//! +//! * ```akita-mysql``` - to use mysql +//! * ```akita-sqlite``` - to use sqlite +//! +//! //! ## Annotions. //! * Table - to make Akita work with structs //! * column - to make struct field with own database. @@ -95,7 +101,7 @@ //! Err(err) => {println!("err:{}", err);} //! } //! ``` -//! Update At 2021.08.04 10:21 +//! Update At 2021.09.05 10:21 //! By Mr.Pan //! //! @@ -106,15 +112,16 @@ mod wrapper; mod segment; mod errors; mod mapper; -mod mysql; mod pool; mod information; mod value; mod types; mod database; +mod platform; mod data; mod manager; + #[doc(inline)] pub use wrapper::{QueryWrapper, UpdateWrapper, Wrapper}; #[doc(inline)] @@ -151,7 +158,6 @@ extern crate akita_derive; #[doc(hidden)] pub use akita_derive::*; -#[macro_use] extern crate log; diff --git a/src/manager.rs b/src/manager.rs index 63b516d..74234b4 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -1,12 +1,12 @@ -use crate::{self as akita, AkitaError, IPage, UpdateWrapper, Wrapper, database::{Database, DatabasePlatform}, information::{DatabaseName, FieldName, FieldType, GetFields, GetTableName, TableDef, TableName}, mapper::AkitaMapper, pool::AkitaConfig, value::{ToValue, Value}}; +use crate::{self as akita, AkitaError, IPage, UpdateWrapper, Wrapper, database::{Database, DatabasePlatform}, information::{DatabaseName, FieldName, FieldType, GetFields, GetTableName, TableDef, TableName}, mapper::AkitaMapper, value::{ToValue, Value}}; use crate::data::{FromAkita, Rows, AkitaData, ToAkita}; /// an interface executing sql statement and getting the results as generic Akita values /// without any further conversion. #[allow(unused)] -pub struct AkitaManager(pub DatabasePlatform, pub AkitaConfig); +pub struct AkitaManager(pub DatabasePlatform); #[allow(unused)] -pub struct AkitaEntityManager(pub DatabasePlatform, pub AkitaConfig); +pub struct AkitaEntityManager(pub DatabasePlatform); pub struct AkitaTransaction<'a> { pub(crate) conn: &'a mut AkitaEntityManager, @@ -135,15 +135,6 @@ impl AkitaMapper for AkitaTransaction <'_> { self.conn.save(entity) } - /// this is soly for use with sqlite since sqlite doesn't support bulk insert - fn save_batch_result(&mut self, entities: &[&T]) -> Result, AkitaError> - where - T: GetTableName + GetFields + ToAkita, - R: FromAkita + GetFields, - { - self.conn.save_batch_result(entities) - } - #[allow(clippy::redundant_closure)] fn execute_result<'a, R>( &mut self, @@ -208,7 +199,7 @@ impl AkitaManager { sql: &str, params: &[&Value], ) -> Result { - let rows = self.0.execute_result(sql, params,self.1.log_level.to_owned())?; + let rows = self.0.execute_result(sql, params)?; Ok(rows) } @@ -217,7 +208,7 @@ impl AkitaManager { sql: &str, params: &[&Value], ) -> Result, AkitaError> { - let rows = self.0.execute_result(sql, params,self.1.log_level.to_owned())?; + let rows = self.0.execute_result(sql, params)?; let datas: Vec = rows.iter().collect(); Ok(datas) } @@ -269,7 +260,7 @@ impl AkitaEntityManager{ pub fn set_session_user(&mut self, username: &str) -> Result<(), AkitaError> { let sql = format!("SET SESSION ROLE '{}'", username); - self.0.execute_result(&sql, &[],self.1.log_level.to_owned())?; + self.0.execute_result(&sql, &[])?; Ok(()) } @@ -361,7 +352,9 @@ impl AkitaEntityManager{ .map(|(x, _)| { #[allow(unreachable_patterns)] match self.0 { - // #[cfg(feature = "with-mysql")] + #[cfg(feature = "with-sqlite")] + DatabasePlatform::Sqlite(_) => format!("${}", y * columns_len + x + 1), + #[cfg(feature = "akita-mysql")] DatabasePlatform::Mysql(_) => "?".to_string(), _ => format!("${}", y * columns_len + x + 1), } @@ -382,6 +375,7 @@ impl AkitaEntityManager{ { let table = T::table_name(); let columns = T::fields(); + let columns_len = columns.len(); let set_fields = &wrapper.fields_set; let mut sql = String::new(); sql += &format!("update {} ", table.complete_name()); @@ -389,15 +383,17 @@ impl AkitaEntityManager{ if set_fields.is_empty() { sql += &format!( "set {}", - columns.iter().filter(|col| col.exist).collect::>() + columns.iter().filter(|col| col.exist && col.field_type == FieldType::TableField).collect::>() .iter() .enumerate() .map(|(x, col)| { #[allow(unreachable_patterns)] match self.0 { - // #[cfg(feature = "with-mysql")] + #[cfg(feature = "akita-mysql")] DatabasePlatform::Mysql(_) => format!("`{}` = ?", &col.name), - _ => format!("${}", x + 1), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(_) => format!("`{}` = ${}", &col.name, x + 1), + _ => format!("`{}` = ${}", &col.name, x + 1), } }) .collect::>() @@ -414,9 +410,11 @@ impl AkitaEntityManager{ .map(|(x, (col, value))| { #[allow(unreachable_patterns)] match self.0 { - // #[cfg(feature = "with-mysql")] + #[cfg(feature = "akita-mysql")] DatabasePlatform::Mysql(_) => format!("`{}` = {}", col, value.get_sql_segment()), - _ => format!("${}", x + 1), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(_) => format!("`{}` = ${}", col, x + 1), + _ => format!("`{}` = ${}", col, x + 1), } }) .collect::>() @@ -461,7 +459,7 @@ impl AkitaMapper for AkitaEntityManager{ let where_condition = wrapper.get_sql_segment(); let where_condition = if where_condition.trim().is_empty() { String::default() } else { format!("WHERE {}",where_condition) }; let sql = format!("SELECT {} FROM {} {}", &enumerated_columns, &table.complete_name(),where_condition); - let rows = self.0.execute_result(&sql, &[],self.1.log_level.to_owned())?; + let rows = self.0.execute_result(&sql, &[])?; let mut entities = vec![]; for data in rows.iter() { let entity = T::from_data(&data); @@ -495,7 +493,7 @@ impl AkitaMapper for AkitaEntityManager{ let where_condition = wrapper.get_sql_segment(); let where_condition = if where_condition.trim().is_empty() { String::default() } else { format!("WHERE {}",where_condition) }; let sql = format!("SELECT {} FROM {} {}", &enumerated_columns, &table.complete_name(), where_condition); - let rows = self.0.execute_result(&sql, &[],self.1.log_level.to_owned())?; + let rows = self.0.execute_result(&sql, &[])?; Ok(rows.iter().next().map(|data| T::from_data(&data))) } @@ -510,6 +508,7 @@ impl AkitaMapper for AkitaEntityManager{ return Err(AkitaError::MissingTable("Find Error, Missing Table Name !".to_string())) } let columns = T::fields(); + let col_len = columns.len(); let enumerated_columns = columns .iter().filter(|f| f.exist) .map(|c| format!("`{}`", c.name)) @@ -520,8 +519,14 @@ impl AkitaMapper for AkitaEntityManager{ FieldType::TableId(_) => true, FieldType::TableField => false, }) { - let sql = format!("SELECT {} FROM {} WHERE `{}` = ? limit 1", &enumerated_columns, &table.complete_name(), &field.name); - let rows = self.0.execute_result(&sql, &[&id.to_value()],self.1.log_level.to_owned())?; + let sql = match self.0 { + #[cfg(feature = "akita-mysql")] + DatabasePlatform::Mysql(_) => format!("SELECT {} FROM {} WHERE `{}` = ? limit 1", &enumerated_columns, &table.complete_name(), &field.name), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(_) => format!("SELECT {} FROM {} WHERE `{}` = ${} limit 1", &enumerated_columns, &table.complete_name(), &field.name, col_len + 1), + _ => format!("SELECT {} FROM {} WHERE `{}` = ${} limit 1", &enumerated_columns, &table.complete_name(), &field.name, col_len + 1), + }; + let rows = self.0.execute_result(&sql, &[&id.to_value()])?; Ok(rows.iter().next().map(|data| T::from_data(&data))) } else { Err(AkitaError::MissingIdent(format!("Table({}) Missing Ident...", &table.name))) @@ -561,7 +566,7 @@ impl AkitaMapper for AkitaEntityManager{ let mut page = IPage::new(page, size ,count.count as usize, vec![]); if page.total > 0 { let sql = format!("SELECT {} FROM {} {} limit {}, {}", &enumerated_columns, &table.complete_name(), where_condition,page.offset(), page.size); - let rows = self.0.execute_result(&sql, &[],self.1.log_level.to_owned())?; + let rows = self.0.execute_result(&sql, &[])?; let mut entities = vec![]; for dao in rows.iter() { let entity = T::from_data(&dao); @@ -608,7 +613,7 @@ impl AkitaMapper for AkitaEntityManager{ let where_condition = wrapper.get_sql_segment(); let where_condition = if where_condition.trim().is_empty() { String::default() } else { format!("WHERE {}",where_condition) }; let sql = format!("delete from {} {}", &table.complete_name(), where_condition); - let _ = self.0.execute_result(&sql, &[], self.1.log_level.to_owned())?; + let _ = self.0.execute_result(&sql, &[])?; Ok(()) } @@ -621,12 +626,20 @@ impl AkitaMapper for AkitaEntityManager{ if table.complete_name().is_empty() { return Err(AkitaError::MissingTable("Find Error, Missing Table Name !".to_string())) } - if let Some(field) = T::fields().iter().find(| field| match field.field_type { + let cols = T::fields(); + let col_len = cols.len(); + if let Some(field) = cols.iter().find(| field| match field.field_type { FieldType::TableId(_) => true, FieldType::TableField => false, }) { - let sql = format!("delete from {} where `{}` = ?", &table.name, &field.name); - let _ = self.0.execute_result(&sql, &[&id.to_value()],self.1.log_level.to_owned())?; + let sql = match self.0 { + #[cfg(feature = "akita-mysql")] + DatabasePlatform::Mysql(_) => format!("delete from {} where `{}` = ?", &table.name, &field.name), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(_) => format!("delete from {} where `{}` = ${}", &table.name, &field.name, col_len + 1), + _ => format!("delete from {} where `{}` = ${}", &table.name, &field.name, col_len + 1), + }; + let _ = self.0.execute_result(&sql, &[&id.to_value()])?; Ok(()) } else { Err(AkitaError::MissingIdent(format!("Table({}) Missing Ident...", &table.name))) @@ -662,9 +675,9 @@ impl AkitaMapper for AkitaEntityManager{ } } let v = values.iter().collect::>(); - self.0.execute_result(&sql, &v,self.1.log_level.to_owned())?; + self.0.execute_result(&sql, &v)?; } else { - self.0.execute_result(&sql, &[],self.1.log_level.to_owned())?; + self.0.execute_result(&sql, &[])?; } Ok(()) } @@ -679,6 +692,7 @@ impl AkitaMapper for AkitaEntityManager{ } let data = entity.to_data(); let columns = T::fields(); + let col_len = columns.len(); if let Some(field) = T::fields().iter().find(| field| match field.field_type { FieldType::TableId(_) => true, FieldType::TableField => false, @@ -689,14 +703,22 @@ impl AkitaMapper for AkitaEntityManager{ .map(|(x, col)| { #[allow(unreachable_patterns)] match self.0 { - // #[cfg(feature = "with-mysql")] + #[cfg(feature = "akita-mysql")] DatabasePlatform::Mysql(_) => format!("`{}` = ?", &col.name), - _ => format!("${}", x + 1), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(_) => format!("`{}` = ${}",&col.name, x + 1), + _ => format!("`{}` = ${}", &col.name, x + 1), } }) .collect::>() .join(", "); - let sql = format!("update {} set {} where `{}` = ?", &table.name, &set_fields, &field.name); + let sql = match self.0 { + #[cfg(feature = "akita-mysql")] + DatabasePlatform::Mysql(_) => format!("update {} set {} where `{}` = ?", &table.name, &set_fields, &field.name), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(_) => format!("update {} set {} where `{}` = ${}", &table.name, &set_fields, &field.name, col_len + 1), + _ => format!("update {} set {} where `{}` = ${}", &table.name, &set_fields, &field.name, col_len + 1), + }; let mut values: Vec = Vec::with_capacity(columns.len()); let id = data.get_value(&field.name); for col in columns.iter() { @@ -717,7 +739,7 @@ impl AkitaMapper for AkitaEntityManager{ } } let bvalues: Vec<&Value> = values.iter().collect(); - let _ = self.0.execute_result(&sql, &bvalues,self.1.log_level.to_owned())?; + let _ = self.0.execute_result(&sql, &bvalues)?; Ok(()) } else { Err(AkitaError::MissingIdent(format!("Table({}) Missing Ident...", &table.name))) @@ -731,7 +753,10 @@ impl AkitaMapper for AkitaEntityManager{ T: GetTableName + GetFields + ToAkita, { match self.0 { + #[cfg(feature = "akita-mysql")] DatabasePlatform::Mysql(_) => self.save_batch_inner(entities), + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(_) => self.save_batch_inner(entities), } } @@ -752,47 +777,15 @@ impl AkitaMapper for AkitaEntityManager{ } } let bvalues: Vec<&Value> = values.iter().collect(); - self.0.execute_result(&sql, &bvalues, self.1.log_level.to_owned())?; - let rows = self.0.execute_result("SELECT LAST_INSERT_ID();", &[], self.1.log_level.to_owned())?; - let count = rows.iter().next().map(|data| i64::from_data(&data)).unwrap_or_default(); - Ok(count as usize) - } - - /// this is soly for use with sqlite since sqlite doesn't support bulk insert - fn save_batch_result(&mut self, entities: &[&T]) -> Result, AkitaError> - where - T: GetTableName + GetFields + ToAkita, - R: FromAkita + GetFields, - { - let return_columns = R::fields(); - let return_column_names = return_columns - .iter().filter(|f| f.exist) - .map(|rc| format!("`{}`", rc.name)) - .collect::>() - .join(", "); - - let table = T::table_name(); - if table.complete_name().is_empty() { - return Err(AkitaError::MissingTable("Find Error, Missing Table Name !".to_string())) - } - //TODO: move this specific query to sqlite - let last_insert_sql = format!( - "\ - SELECT {} \ - FROM {} \ - WHERE ROWID = (\ - SELECT LAST_INSERT_ROWID() FROM {})", - return_column_names, - table.complete_name(), - table.complete_name() - ); - let mut retrieved_entities = vec![]; - for entity in entities { - self.save(*entity)?; - let retrieved = self.execute_result(&last_insert_sql, &[])?; - retrieved_entities.extend(retrieved); - } - Ok(retrieved_entities) + self.0.execute_result(&sql, &bvalues)?; + let rows: Rows = match self.0 { + #[cfg(feature = "akita-mysql")] + DatabasePlatform::Mysql(_) => self.0.execute_result("SELECT LAST_INSERT_ID();", &[])?, + #[cfg(feature = "akita-sqlite")] + DatabasePlatform::Sqlite(_) => self.0.execute_result("SELECT LAST_INSERT_ROWID();", &[])?, + }; + let last_insert_id = rows.iter().next().map(|data| usize::from_data(&data)).unwrap_or_default(); + Ok(last_insert_id) } #[allow(clippy::redundant_closure)] @@ -806,7 +799,7 @@ impl AkitaMapper for AkitaEntityManager{ { let values: Vec = params.iter().map(|p| p.to_value()).collect(); let bvalues: Vec<&Value> = values.iter().collect(); - let rows = self.0.execute_result(sql, &bvalues, self.1.log_level.to_owned())?; + let rows = self.0.execute_result(sql, &bvalues)?; Ok(rows.iter().map(|data| R::from_data(&data)).collect::>()) } @@ -880,20 +873,20 @@ mod test { fn get_table_info() { let s = params! { "test" => 1}; // let db_url = String::from("mysql://root:password@localhost:3306/akita"); - // let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + // let mut pool = Pool::new(AkitaConfig::default()).unwrap(); // let mut em = pool.entity_manager().expect("must be ok"); // let table = em // .get_table(&TableName::from("public.film")) // .expect("must have a table"); // println!("table: {:#?}", table); - let s = mysql::serde_json::to_value("[123,3455,556]").unwrap(); + let s = serde_json::to_value("[123,3455,556]").unwrap(); println!("{}", s) } #[test] fn remove() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let em = &mut pool.entity_manager().expect("must be ok"); let mut wrap = UpdateWrapper::new(); wrap.eq("username", "'ussd'"); @@ -910,7 +903,7 @@ mod test { #[test] fn count() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let mut wrap = UpdateWrapper::new(); wrap.eq("username", "'ussd'"); @@ -928,7 +921,7 @@ mod test { #[test] fn remove_by_id() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); match em.remove_by_id::("'fffsd'".to_string()) { Ok(res) => { @@ -943,7 +936,7 @@ mod test { #[test] fn update() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let user = SystemUser { id: 1.into(), username: "fff".to_string(), age: 1 }; let mut wrap = UpdateWrapper::new(); @@ -961,7 +954,7 @@ mod test { #[test] fn update_by_id() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let user = SystemUser { id: 1.into(), username: "fff".to_string(), age: 1 }; match em.update_by_id(&user) { @@ -978,7 +971,7 @@ mod test { #[test] fn save() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let user = SystemUser { id: 1.into(), username: "fff".to_string(), age: 1 }; match em.save(&user) { @@ -994,7 +987,7 @@ mod test { #[test] fn save_batch() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let user = SystemUser { id: 1.into(), username: "fff".to_string(), age: 1 }; match em.save_batch::<_>(&vec![&user]) { @@ -1010,7 +1003,7 @@ mod test { #[test] fn self_insert() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let user = SystemUser { id: 1.into(), username: "fff".to_string(), age: 1 }; match user.insert(&mut em) { @@ -1026,7 +1019,7 @@ mod test { #[test] fn select_by_id() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let mut wrapper = UpdateWrapper::new(); wrapper.eq("username", "'ussd'"); @@ -1043,7 +1036,7 @@ mod test { #[test] fn select_one() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let mut wrapper = UpdateWrapper::new(); wrapper.eq("username", "'ussd'"); @@ -1060,7 +1053,7 @@ mod test { #[test] fn list() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let mut wrapper = UpdateWrapper::new(); wrapper.eq("username", "'ussd'"); @@ -1077,7 +1070,7 @@ mod test { #[test] fn self_list() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let mut wrapper = UpdateWrapper::new(); wrapper.eq("username", "'ussd'"); @@ -1095,7 +1088,7 @@ mod test { #[test] fn page() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let mut wrapper = UpdateWrapper::new(); wrapper.eq( "username", "'ussd'"); @@ -1112,7 +1105,7 @@ mod test { #[test] fn self_page() { let db_url = String::from("mysql://root:password@localhost:3306/akita"); - let mut pool = Pool::new(AkitaConfig{ max_size: None, url: db_url, log_level: None }).unwrap(); + let mut pool = Pool::new(AkitaConfig::default()).unwrap(); let mut em = pool.entity_manager().expect("must be ok"); let mut wrapper = UpdateWrapper::new(); wrapper.eq("username", "'ussd'"); diff --git a/src/mapper.rs b/src/mapper.rs index 9e5a5cd..a58c787 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -126,12 +126,6 @@ pub trait AkitaMapper { where T: GetTableName + GetFields + ToAkita; - /// this is soly for use with sqlite since sqlite doesn't support bulk insert - fn save_batch_result(&mut self, entities: &[&T]) -> Result, AkitaError> - where - T: GetTableName + GetFields + ToAkita, - R: FromAkita + GetFields; - #[allow(clippy::redundant_closure)] fn execute_result<'a, R>( &mut self, diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 0000000..012a436 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,11 @@ +use crate::cfg_if; + + +cfg_if! {if #[cfg(feature = "akita-mysql")]{ + pub mod mysql; +}} + + +cfg_if! {if #[cfg(feature = "akita-sqlite")]{ + pub mod sqlite; +}} \ No newline at end of file diff --git a/src/mysql.rs b/src/platform/mysql.rs similarity index 90% rename from src/mysql.rs rename to src/platform/mysql.rs index e099f24..8c96c17 100644 --- a/src/mysql.rs +++ b/src/platform/mysql.rs @@ -1,12 +1,14 @@ //! //! MySQL modules. //! +use log::{debug, error, info}; use mysql::prelude::Protocol; use mysql::{Conn, Error, Opts, OptsBuilder, Row, prelude::Queryable}; use r2d2::{ManageConnection, Pool}; use std::result::Result; +use crate::AkitaConfig; use crate::database::Database; use crate::pool::LogLevel; use crate::types::SqlType; @@ -15,27 +17,27 @@ use crate::{self as akita, AkitaError, information::{ColumnDef, FieldName, Colum use crate::data::{FromAkita, Rows}; type R2d2Pool = Pool; -pub struct MysqlDatabase(pub r2d2::PooledConnection); +pub struct MysqlDatabase(pub r2d2::PooledConnection, pub AkitaConfig); /// TODO: 补全MYSQL数据操作 impl Database for MysqlDatabase { fn start_transaction(&mut self) -> Result<(), AkitaError> { - self.execute_result("BEGIN", &[], None)?; + self.execute_result("BEGIN", &[])?; Ok(()) } fn commit_transaction(&mut self) -> Result<(), AkitaError> { - self.execute_result("COMMIT", &[], None)?; + self.execute_result("COMMIT", &[])?; Ok(()) } fn rollback_transaction(&mut self) -> Result<(), AkitaError> { - self.execute_result("ROLLBACK", &[], None)?; + self.execute_result("ROLLBACK", &[])?; Ok(()) } - fn execute_result(&mut self, sql: &str, param: &[&crate::value::Value], log: Option) -> Result { - if let Some(log_level) = log { + fn execute_result(&mut self, sql: &str, param: &[&crate::value::Value]) -> Result { + if let Some(log_level) = &self.1.log_level() { match log_level { LogLevel::Debug => debug!("[Akita]: Prepare SQL: {} params: {:?}", &sql, param), LogLevel::Info => info!("[Akita]: Prepare SQL: {} params: {:?}", &sql, param), @@ -77,7 +79,7 @@ impl Database for MysqlDatabase { .map_err(|e| AkitaError::ExcuteSqlError(e.to_string(), sql.to_string()))?; let params: mysql::Params = param .iter() - .map(|v| MyValue(v)) + .map(|v| MySQLValue(v)) .map(|v| mysql::prelude::ToValue::to_value(&v)) .collect::>() .into(); @@ -116,7 +118,6 @@ impl Database for MysqlDatabase { &schema, &schema, &table_name, ], - None )? .iter() .map(|data| FromAkita::from_data(&data)) @@ -146,7 +147,7 @@ impl Database for MysqlDatabase { CAST(COLUMN_TYPE as CHAR(255)) AS type_ FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?"#, - &[table_schema, &table_name],None, + &[table_schema, &table_name], )? .iter() .map(|data| FromAkita::from_data(&data)) @@ -248,7 +249,7 @@ impl Database for MysqlDatabase { fn get_database_name(&mut self) -> Result, AkitaError> { let sql = "SELECT database() AS name"; let mut database_names: Vec> = - self.execute_result(&sql, &[], None).map(|rows| { + self.execute_result(&sql, &[]).map(|rows| { rows.iter() .map(|row| { row.get_opt("name") @@ -277,7 +278,7 @@ fn get_table_names(db: &mut dyn Database, kind: &str) -> Result, } let sql = "SELECT TABLE_NAME as table_name FROM information_schema.tables WHERE table_type= ?"; let result: Vec = db - .execute_result(sql, &[&kind.to_value()],None)? + .execute_result(sql, &[&kind.to_value()])? .iter() .map(|row| TableNameSimple { table_name: row.get("table_name").expect("must have a table name"), @@ -292,10 +293,10 @@ fn get_table_names(db: &mut dyn Database, kind: &str) -> Result, } #[derive(Debug)] -pub struct MyValue<'a>(&'a Value); +pub struct MySQLValue<'a>(&'a Value); -impl mysql::prelude::ToValue for MyValue<'_> { +impl mysql::prelude::ToValue for MySQLValue<'_> { fn to_value(&self) -> mysql::Value { match self.0 { Value::Bool(ref v) => v.into(), @@ -318,7 +319,7 @@ impl mysql::prelude::ToValue for MyValue<'_> { Value::Nil => mysql::Value::NULL, Value::Array(_) => unimplemented!("unsupported type"), Value::SerdeJson(ref v) => v.into(), - // Value::BigDecimal(_) => unimplemented!("we need to upgrade bigdecimal crate"), + Value::BigDecimal(_) => unimplemented!("we need to upgrade bigdecimal crate"), // Value::Point(_) | Value::Array(_) => unimplemented!("unsupported type"), } } @@ -344,12 +345,12 @@ fn into_record( } match column_type { - ColumnType::MYSQL_TYPE_DECIMAL | ColumnType::MYSQL_TYPE_NEWDECIMAL => fvo(cell).map(Value::Float), - // .and_then(|v: Vec| { - // bigdecimal::BigDecimal::parse_bytes(&v, 10) - // .ok_or(mysql::FromValueError(mysql::Value::Bytes(v))) - // }) - // .map(Value::BigDecimal), + ColumnType::MYSQL_TYPE_DECIMAL | ColumnType::MYSQL_TYPE_NEWDECIMAL => fvo(cell) + .and_then(|v: Vec| { + bigdecimal::BigDecimal::parse_bytes(&v, 10) + .ok_or(mysql::FromValueError(mysql::Value::Bytes(v))) + }) + .map(Value::BigDecimal), ColumnType::MYSQL_TYPE_TINY => fvo(cell).map(Value::Tinyint), ColumnType::MYSQL_TYPE_SHORT | ColumnType::MYSQL_TYPE_YEAR => { fvo(cell).map(Value::Smallint) @@ -413,12 +414,14 @@ pub fn from_long_row(row: Row) -> T { #[derive(Clone, Debug)] pub struct MysqlConnectionManager { params: Opts, + cfg: AkitaConfig, } impl MysqlConnectionManager { - pub fn new(params: OptsBuilder) -> MysqlConnectionManager { + pub fn new(params: OptsBuilder, cfg: AkitaConfig) -> MysqlConnectionManager { MysqlConnectionManager { params: Opts::from(params), + cfg, } } } @@ -442,24 +445,23 @@ impl r2d2::ManageConnection for MysqlConnectionManager { /// /// 创建连接池 -/// database_url 连接地址 -/// max_size 最大连接数量 +/// cfg 配置信息 /// -pub fn init_pool>(database_url: S, max_size: u32) -> Result { - let database_url: String = database_url.into(); - test_connection(&database_url)?; +pub fn init_pool(cfg: &AkitaConfig) -> Result { + let database_url = &cfg.url(); + test_connection(&database_url, cfg)?; let opts = Opts::from_url(&database_url)?; let builder = OptsBuilder::from_opts(opts); - let manager = MysqlConnectionManager::new(builder); - let pool = Pool::builder().max_size(max_size).build(manager)?; + let manager = MysqlConnectionManager::new(builder, cfg.to_owned()); + let pool = Pool::builder().connection_timeout(cfg.connection_timeout()).min_idle(cfg.min_idle()).max_size(cfg.max_size()).build(manager)?; Ok(pool) } /// 测试连接池连接 -fn test_connection(database_url: &str) -> Result<(), AkitaError> { +fn test_connection(database_url: &str, cfg: &AkitaConfig) -> Result<(), AkitaError> { let opts = mysql::Opts::from_url(&database_url)?; let builder = mysql::OptsBuilder::from_opts(opts); - let manager = MysqlConnectionManager::new(builder); + let manager = MysqlConnectionManager::new(builder, cfg.to_owned()); let mut conn = manager.connect()?; manager.is_valid(&mut conn)?; Ok(()) diff --git a/src/platform/sqlite.rs b/src/platform/sqlite.rs new file mode 100644 index 0000000..0d8202e --- /dev/null +++ b/src/platform/sqlite.rs @@ -0,0 +1,621 @@ +//! +//! SQLite modules. +//! +use bigdecimal::ToPrimitive; +use log::{debug, error, info}; +use r2d2::{ManageConnection, Pool}; +use rusqlite::{Connection, Error, OpenFlags}; +use uuid::Uuid; +use std::fmt; +use std::path::{Path, PathBuf}; +use std::result::Result; + +use crate::{AkitaConfig, ToValue}; +use crate::comm::{extract_datatype_with_capacity, maybe_trim_parenthesis}; +use crate::database::Database; +use crate::information::{Capacity, ColumnConstraint, ForeignKey, Key, Literal, TableKey}; +use crate::pool::LogLevel; +use crate::types::SqlType; +use crate::value::{Value}; +use crate::{self as akita, AkitaError, information::{ColumnDef, FieldName, ColumnSpecification, DatabaseName, TableDef, TableName}}; +use crate::data::{Rows}; +type R2d2Pool = Pool; + +pub struct SqliteDatabase(pub r2d2::PooledConnection, pub AkitaConfig); + +/// TODO: 补全SQLite数据操作 +impl Database for SqliteDatabase { + fn start_transaction(&mut self) -> Result<(), AkitaError> { + self.execute_result("BEGIN TRANSACTION", &[])?; + Ok(()) + } + + fn commit_transaction(&mut self) -> Result<(), AkitaError> { + self.execute_result("COMMIT TRANSACTION", &[])?; + Ok(()) + } + + fn rollback_transaction(&mut self) -> Result<(), AkitaError> { + self.execute_result("ROLLBACK TRANSACTION", &[])?; + Ok(()) + } + + fn execute_result(&mut self, sql: &str, params: &[&crate::value::Value]) -> Result { + if let Some(log_level) = &self.1.log_level() { + match log_level { + LogLevel::Debug => debug!("[Akita]: Prepare SQL: {} params: {:?}", &sql, params), + LogLevel::Info => info!("[Akita]: Prepare SQL: {} params: {:?}", &sql, params), + LogLevel::Error => error!("[Akita]: Prepare SQL: {} params: {:?}", &sql, params), + } + } + let stmt = self.0.prepare(&sql); + let column_names = if let Ok(ref stmt) = stmt { + stmt.column_names() + } else { + vec![] + }; + let column_names: Vec = column_names.iter().map(ToString::to_string).collect(); + match stmt { + Ok(mut stmt) => { + let column_count = stmt.column_count(); + let mut records = Rows::new(column_names); + let sql_values = to_sq_values(params); + // let v = sq_values.iter().map(|v| v.to_sql().unwrap()).collect::>(); + if let Ok(mut rows) = stmt.query(sql_values) { + while let Some(row) = rows.next()? { + let mut record: Vec = vec![]; + for i in 0..column_count { + let raw = row.get(i); + if let Ok(raw) = raw { + let value = match raw { + rusqlite::types::Value::Blob(v) => Value::Blob(v), + rusqlite::types::Value::Real(v) => Value::Double(v), + rusqlite::types::Value::Integer(v) => Value::Bigint(v), + rusqlite::types::Value::Text(v) => Value::Text(v), + rusqlite::types::Value::Null => Value::Nil, + }; + record.push(value); + } + } + records.push(record); + } + } + Ok(records) + } + Err(e) => Err(AkitaError::from(e)), + } + } + + fn get_table(&mut self, table_name: &TableName) -> Result, AkitaError> { + #[derive(Debug)] + struct ColumnSimple { + name: String, + data_type: String, + not_null: bool, + default: Option, + pk: bool, + } + impl ColumnSimple { + fn to_column(&self, table_name: &TableName) -> ColumnDef { + ColumnDef { + table: table_name.clone(), + name: FieldName::from(&self.name), + comment: None, + specification: self.to_column_specification(), + stat: None, + } + } + + fn to_column_specification(&self) -> ColumnSpecification { + let (sql_type, capacity) = self.get_sql_type_capacity(); + ColumnSpecification { + sql_type, + capacity, + constraints: self.to_column_constraints(), + } + } + + fn to_column_constraints(&self) -> Vec { + let (sql_type, _) = self.get_sql_type_capacity(); + let mut constraints = vec![]; + if self.not_null { + constraints.push(ColumnConstraint::NotNull); + } + if let Some(ref default) = self.default { + let ic_default = default.to_lowercase(); + let constraint = if ic_default == "null" { + ColumnConstraint::DefaultValue(Literal::Null) + } else if ic_default.starts_with("nextval") { + ColumnConstraint::AutoIncrement(None) + } else { + let literal = match sql_type { + SqlType::Bool => { + let v: bool = default.parse().unwrap(); + Literal::Bool(v) + } + SqlType::Int + | SqlType::Smallint + | SqlType::Tinyint + | SqlType::Bigint => { + let v: Result = default.parse(); + match v { + Ok(v) => Literal::Integer(v), + Err(e) => { + panic!("error parsing to integer: {} error: {}", default, e) + } + } + } + SqlType::Float | SqlType::Double | SqlType::Real | SqlType::Numeric => { + // some defaults have cast type example: (0)::numeric + let splinters = maybe_trim_parenthesis(&default) + .split("::") + .collect::>(); + let default_value = maybe_trim_parenthesis(splinters[0]); + if default_value.to_lowercase() == "null" { + Literal::Null + } else { + match default.parse::() { + Ok(val) => Literal::Double(val), + Err(e) => { + panic!( + "unable to evaluate default value expression: {}, error: {}", + default, e + ) + } + } + } + } + SqlType::Uuid => { + if ic_default == "uuid_generate_v4()" { + Literal::UuidGenerateV4 + } else { + let v: Result = Uuid::parse_str(&default); + match v { + Ok(v) => Literal::Uuid(v), + Err(e) => panic!( + "error parsing to uuid: {} error: {}", + default, e + ), + } + } + } + SqlType::Timestamp | SqlType::TimestampTz => { + if ic_default == "now()" + || ic_default == "timezone('utc'::text, now())" + || ic_default == "current_timestamp" + { + Literal::CurrentTimestamp + } else { + panic!( + "timestamp other than now is not covered, got: {}", + ic_default + ) + } + } + SqlType::Date => { + // timestamp converted to text then converted to date + // is equivalent to today() + if ic_default == "today()" + || ic_default == "now()" + || ic_default == "('now'::text)::date" + { + Literal::CurrentDate + } else { + panic!( + "date other than today, now is not covered in {:?}", + self + ) + } + } + SqlType::Varchar + | SqlType::Char + | SqlType::Tinytext + | SqlType::Mediumtext + | SqlType::Text => Literal::String(default.to_owned()), + SqlType::Enum(_name, _choices) => Literal::String(default.to_owned()), + _ => panic!("not convered: {:?}", sql_type), + }; + ColumnConstraint::DefaultValue(literal) + }; + constraints.push(constraint); + } + constraints + } + + fn get_sql_type_capacity(&self) -> (SqlType, Option) { + let (dtype, capacity) = extract_datatype_with_capacity(&self.data_type); + let sql_type = match &*dtype { + "int" | "integer" => SqlType::Int, + "smallint" => SqlType::Smallint, + "varchar" => SqlType::Text, + "character varying" => SqlType::Text, + "decimal" => SqlType::Double, + "timestamp" => SqlType::Timestamp, + "numeric" => SqlType::Numeric, + "char" => match capacity { + None => SqlType::Char, + Some(Capacity::Limit(1)) => SqlType::Char, + Some(_) => SqlType::Varchar, + }, + "blob" => SqlType::Blob, + "" => SqlType::Text, + _ => { + if dtype.contains("text") { + SqlType::Text + } else { + panic!("not yet handled: {:?}", dtype) + } + } + }; + (sql_type, capacity) + } + } + macro_rules! unwrap_ok_some { + ($var:ident) => { + match $var { + Ok($var) => match $var { + Some($var) => $var, + None => panic!("expecting {} to have a value", stringify!($var)), + }, + Err(_e) => panic!("expecting {} to be not error", stringify!($var)), + } + }; + } + let sql = format!("PRAGMA table_info({});", table_name.complete_name()); + let result = self.execute_result(&sql, &[])?; + let mut primary_columns = vec![]; + let mut columns = vec![]; + for dao in result.iter() { + let name: Result, _> = dao.get("name"); + let name = unwrap_ok_some!(name); + let data_type: Result, _> = dao.get("type"); + let data_type = unwrap_ok_some!(data_type).to_lowercase(); + let not_null: Result, _> = dao.get("notnull"); + let not_null = unwrap_ok_some!(not_null) != 0; + let pk: Result, _> = dao.get("pk"); + let pk = unwrap_ok_some!(pk) != 0; + if pk { + primary_columns.push(FieldName::from(&name)); + } + let default = dao.0.get("dflt_value").map(|v| match *v { + Value::Text(ref v) => v.to_owned(), + Value::Nil => "null".to_string(), + _ => panic!("Expecting a text value, got: {:?}", v), + }); + let simple = ColumnSimple { + name, + data_type, + default, + pk, + not_null, + }; + columns.push(simple.to_column(table_name)); + } + let primary_key = Key { + name: None, + columns: primary_columns, + }; + let foreign_keys = get_foreign_keys(&mut *self, table_name)?; + let table_key_foreign: Vec = + foreign_keys.into_iter().map(TableKey::ForeignKey).collect(); + let mut table_keys = vec![TableKey::PrimaryKey(primary_key)]; + table_keys.extend(table_key_foreign); + let table = TableDef { + name: table_name.clone(), + comment: None, // TODO: need to extract comment from the create_sql + columns, + is_view: false, + table_key: table_keys, + }; + Ok(Some(table)) + } + + fn set_autoincrement_value( + &mut self, + table_name: &TableName, + sequence_value: i64, + ) -> Result, AkitaError> { + let sql = "UPDATE sqlite_sequence SET seq = $2 WHERE name = $1"; + self.execute_result( + sql, + &[&table_name.complete_name().into(), &sequence_value.into()] + )?; + + Ok(None) + } + + fn get_autoincrement_last_value( + &mut self, + table_name: &TableName, + ) -> Result, AkitaError> { + let sql = "SELECT seq FROM sqlite_sequence where name = $1"; + let result: Vec> = self + .execute_result(sql, &[&table_name.complete_name().into()])? + .iter() + .filter_map(|row| row.get("seq").ok()) + .collect(); + + if let Some(first) = result.get(0) { + Ok(*first) + } else { + Ok(None) + } + } + + fn get_database_name(&mut self) -> Result, AkitaError> { + let sql = "SELECT database() AS name"; + let mut database_names: Vec> = + self.execute_result(&sql, &[]).map(|rows| { + rows.iter() + .map(|row| { + row.get_opt("name") + .expect("must not error") + .map(|name| DatabaseName { + name, + description: None, + }) + }) + .collect() + })?; + + if database_names.len() > 0 { + Ok(database_names.remove(0)) + } else { + Ok(None) + } + } +} + +#[allow(unused)] +fn get_table_names(db: &mut dyn Database, kind: &str) -> Result, AkitaError> { + #[derive(Debug, FromAkita)] + struct TableNameSimple { + tbl_name: String, + } + let sql = "SELECT tbl_name FROM sqlite_master WHERE type = ?"; + let result: Vec = db + .execute_result(sql, &[&kind.to_value()])? + .iter() + .map(|row| TableNameSimple { + tbl_name: row.get("tbl_name").expect("tbl_name"), + }) + .collect(); + let mut table_names = vec![]; + for r in result { + let table_name = TableName::from(&r.tbl_name); + table_names.push(table_name); + } + Ok(table_names) +} + +/// get the foreign keys of table +fn get_foreign_keys(db: &mut dyn Database, table: &TableName) -> Result, AkitaError> { + let sql = format!("PRAGMA foreign_key_list({});", table.complete_name()); + #[derive(Debug, FromAkita)] + struct ForeignSimple { + id: i64, + table: String, + from: String, + to: String, + } + let result: Vec = db + .execute_result(&sql, &[])? + .iter() + .map(|row| ForeignSimple { + id: row.get("id").expect("id"), + table: row.get("table").expect("table"), + from: row.get("from").expect("from"), + to: row.get("to").expect("to"), + }) + .collect(); + let mut foreign_tables: Vec<(i64, TableName)> = result + .iter() + .map(|f| (f.id, TableName::from(&f.table))) + .collect(); + foreign_tables.dedup(); + let mut foreign_keys = Vec::with_capacity(foreign_tables.len()); + for (id, foreign_table) in foreign_tables { + let foreigns: Vec<&ForeignSimple> = result.iter().filter(|f| f.id == id).collect(); + let (local_columns, referred_columns): (Vec, Vec) = foreigns + .iter() + .map(|f| (FieldName::from(&f.from), FieldName::from(&f.to))) + .unzip(); + let foreign_key = ForeignKey { + name: None, + columns: local_columns, + foreign_table, + referred_columns, + }; + foreign_keys.push(foreign_key); + } + Ok(foreign_keys) +} + +fn to_sq_values(params: &[&Value]) -> Vec { + let mut sql_values = Vec::with_capacity(params.len()); + for param in params { + let sq_val = to_sq_value(param); + sql_values.push(sq_val); + } + sql_values +} + +fn to_sq_value(val: &Value) -> rusqlite::types::Value { + match *val { + Value::Text(ref v) => rusqlite::types::Value::Text(v.to_owned()), + Value::Bool(v) => rusqlite::types::Value::Integer(if v { 1 } else { 0 }), + Value::Tinyint(v) => rusqlite::types::Value::Integer(i64::from(v)), + Value::Smallint(v) => rusqlite::types::Value::Integer(i64::from(v)), + Value::Int(v) => rusqlite::types::Value::Integer(i64::from(v)), + Value::Bigint(v) => rusqlite::types::Value::Integer(v), + + Value::Float(v) => rusqlite::types::Value::Real(f64::from(v)), + Value::Double(v) => rusqlite::types::Value::Real(v), + Value::BigDecimal(ref v) => match v.to_f64() { + Some(v) => rusqlite::types::Value::Real(v as f64), + None => panic!("unable to convert bigdecimal"), + }, + Value::Blob(ref v) => rusqlite::types::Value::Blob(v.clone()), + Value::Char(v) => rusqlite::types::Value::Text(format!("{}", v)), + Value::Json(ref v) => rusqlite::types::Value::Text(v.clone()), + Value::Uuid(ref v) => rusqlite::types::Value::Text(v.to_string()), + Value::Date(ref v) => rusqlite::types::Value::Text(v.to_string()), + Value::DateTime(ref v) => rusqlite::types::Value::Text(v.to_string()), + Value::Nil => rusqlite::types::Value::Null, + _ => panic!("not yet handled: {:?}", val), + } +} + + +#[derive(Debug)] +enum Source { + File(PathBuf), + Memory, +} + +type InitFn = dyn Fn(&mut Connection) -> Result<(), rusqlite::Error> + Send + Sync + 'static; + +pub struct SqliteConnectionManager { + source: Source, + flags: OpenFlags, + init: Option>, +} + +impl fmt::Debug for SqliteConnectionManager { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut builder = f.debug_struct("SqliteConnectionManager"); + let _ = builder.field("source", &self.source); + let _ = builder.field("flags", &self.source); + let _ = builder.field("init", &self.init.as_ref().map(|_| "InitFn")); + builder.finish() + } +} + +impl SqliteConnectionManager { + /// Creates a new `SqliteConnectionManager` from file. + /// + /// See `rusqlite::Connection::open` + pub fn file>(path: P) -> Self { + Self { + source: Source::File(path.as_ref().to_path_buf()), + flags: OpenFlags::default(), + init: None, + } + } + + /// Creates a new `SqliteConnectionManager` from memory. + pub fn memory() -> Self { + Self { + source: Source::Memory, + flags: OpenFlags::default(), + init: None, + } + } + + /// Converts `SqliteConnectionManager` into one that sets OpenFlags upon + /// connection creation. + /// + /// See `rustqlite::OpenFlags` for a list of available flags. + pub fn with_flags(self, flags: OpenFlags) -> Self { + Self { flags, ..self } + } + + /// Converts `SqliteConnectionManager` into one that calls an initialization + /// function upon connection creation. Could be used to set PRAGMAs, for + /// example. + /// + /// ### Example + /// + /// Make a `SqliteConnectionManager` that sets the `foreign_keys` pragma to + /// true for every connection. + /// + /// ```rust,no_run + /// # use r2d2_sqlite::{SqliteConnectionManager}; + /// let manager = SqliteConnectionManager::file("app.db") + /// .with_init(|c| c.execute_batch("PRAGMA foreign_keys=1;")); + /// ``` + pub fn with_init(self, init: F) -> Self + where + F: Fn(&mut Connection) -> Result<(), rusqlite::Error> + Send + Sync + 'static, + { + let init: Option> = Some(Box::new(init)); + Self { init, ..self } + } +} + +impl r2d2::ManageConnection for SqliteConnectionManager { + type Connection = Connection; + type Error = rusqlite::Error; + + fn connect(&self) -> Result { + match self.source { + Source::File(ref path) => Connection::open_with_flags(path, self.flags), + Source::Memory => Connection::open_in_memory_with_flags(self.flags), + } + .map_err(Into::into) + .and_then(|mut c| match self.init { + None => Ok(c), + Some(ref init) => init(&mut c).map(|_| c), + }) + } + + fn is_valid(&self, conn: &mut Connection) -> Result<(), Error> { + conn.execute_batch("").map_err(Into::into) + } + + fn has_broken(&self, _: &mut Connection) -> bool { + false + } +} + +/// +/// 创建连接池 +/// cfg 配置信息 +/// +pub fn init_pool(cfg: &AkitaConfig) -> Result { + let database_url = &cfg.url().to_owned(); + test_connection(&database_url)?; + let manager = SqliteConnectionManager::file(database_url); + let pool = Pool::builder().connection_timeout(cfg.to_owned().connection_timeout()).min_idle(cfg.min_idle()).max_size(cfg.max_size()).build(manager)?; + Ok(pool) +} + +/// 测试连接池连接 +fn test_connection(database_url: &str) -> Result<(), AkitaError> { + let database_url: String = database_url.into(); + let manager = SqliteConnectionManager::file(database_url); + let mut conn = manager.connect()?; + manager.is_valid(&mut conn)?; + Ok(()) +} + + +#[cfg(test)] +mod test { + use crate::{self as akita, AkitaConfig, AkitaMapper, FromAkita, Pool, QueryWrapper, Table, ToAkita, types::SqlType::{Int, Text, Timestamp}}; + + #[derive(Debug, FromAkita, ToAkita, Table, Clone)] + #[table(name="test")] + struct TestSqlite { + #[table_id] + id: i32, + name: String + } + + #[test] + fn test_conn() { + let db_url = "sqlite://./../../example/akita.sqlite3"; + let mut pool = Pool::new(AkitaConfig::new(db_url.to_string())).unwrap(); + let result = pool.connect(); + assert!(result.is_ok()); + } + + #[test] + fn test_list() { + let db_url = "sqlite://./../../example/akita.sqlite3"; + let mut pool = Pool::new(AkitaConfig::new(db_url.to_string())).unwrap(); + let mut em = pool.entity_manager().unwrap(); + let datas = em.list::(&mut QueryWrapper::new()).unwrap(); + println!("{:?}", datas); + } +} \ No newline at end of file diff --git a/src/pool.rs b/src/pool.rs index f13cfcc..bacd905 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -1,47 +1,97 @@ -use std::convert::TryFrom; - +use std::{convert::TryFrom, time::Duration}; use log::*; -use crate::{AkitaError, database::{DatabasePlatform, Platform}, manager::{AkitaEntityManager, AkitaManager}}; -use crate::mysql::{self, MysqlDatabase, MysqlConnectionManager}; +cfg_if! {if #[cfg(feature = "akita-mysql")]{ + use crate::platform::{mysql::{self, MysqlConnectionManager, MysqlDatabase}}; +}} + +cfg_if! {if #[cfg(feature = "akita-sqlite")]{ + use crate::platform::sqlite::{self, SqliteConnectionManager, SqliteDatabase}; +}} + +use crate::{AkitaError, cfg_if, database::{DatabasePlatform, Platform}, manager::{AkitaEntityManager, AkitaManager}}; #[allow(unused)] #[derive(Clone)] pub struct Pool(PlatformPool, AkitaConfig); -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct AkitaConfig { - pub max_size: Option, - pub url: String, - pub log_level: Option, + connection_timeout: Duration, + min_idle: Option, + max_size: u32, + url: String, + log_level: Option, } impl AkitaConfig { pub fn default() -> Self { AkitaConfig { - max_size: None, + max_size: 16, url: String::default(), - log_level: LogLevel::Info.into(), + log_level: None, + connection_timeout: Duration::from_secs(6), + min_idle: None, + } + } + + pub fn new(url: String) -> Self { + AkitaConfig { + max_size: 16, + url, + log_level: None, + connection_timeout: Duration::from_secs(6), + min_idle: None, } } - pub fn url(&mut self, url: String) -> &mut Self { + pub fn set_url(&mut self, url: String) -> &mut Self { self.url = url; self } + + pub fn url(&self) -> String { + self.url.to_owned() + } + + pub fn set_max_size(&mut self, max_size: u32) -> &mut Self { + self.max_size = max_size; + self + } - pub fn max_size(&mut self, max_size: usize) -> &mut Self { - self.max_size = max_size.into(); + pub fn max_size(&self) -> u32 { + self.max_size + } + + pub fn set_connection_timeout(&mut self, connection_timeout: Duration) -> &mut Self { + self.connection_timeout = connection_timeout; self } + + pub fn connection_timeout(&self) -> Duration { + self.connection_timeout + } - pub fn log_level(&mut self, level: LogLevel) -> &mut Self { + pub fn set_min_idle(&mut self, min_idle: Option) -> &mut Self { + self.min_idle = min_idle; + self + } + + pub fn min_idle(&self) -> Option { + self.min_idle + } + + pub fn set_log_level(&mut self, level: LogLevel) -> &mut Self { self.log_level = level.into(); self } + + pub fn log_level(&self) -> Option { + self.log_level.to_owned() + } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum LogLevel { Debug, Info, @@ -51,25 +101,38 @@ pub enum LogLevel { #[allow(unused)] #[derive(Clone)] pub enum PlatformPool { + #[cfg(feature = "akita-mysql")] MysqlPool(r2d2::Pool), + #[cfg(feature = "akita-sqlite")] + SqlitePool(r2d2::Pool), } #[allow(unused)] pub enum PooledConnection { + #[cfg(feature = "akita-mysql")] PooledMysql(Box>), + #[cfg(feature = "akita-sqlite")] + PooledSqlite(Box>), } #[allow(unused)] impl Pool { - pub fn new(cfg: AkitaConfig) -> Result { + pub fn new(mut cfg: AkitaConfig) -> Result { let database_url = &cfg.url; let platform: Result = TryFrom::try_from(database_url.as_str()); match platform { Ok(platform) => match platform { + #[cfg(feature = "akita-mysql")] Platform::Mysql => { - let pool_mysql = mysql::init_pool(database_url, 4)?; + let pool_mysql = mysql::init_pool(&cfg)?; Ok(Pool(PlatformPool::MysqlPool(pool_mysql), cfg)) } + #[cfg(feature = "akita-sqlite")] + Platform::Sqlite(path) => { + cfg.url = path; + let pool_sqlite = sqlite::init_pool(&cfg)?; + Ok(Pool(PlatformPool::SqlitePool(pool_sqlite), cfg)) + } Platform::Unsupported(scheme) => { info!("unsupported"); Err(AkitaError::UnknownDatabase(scheme)) @@ -86,6 +149,7 @@ impl Pool { /// get a usable database connection from pub fn connect(&mut self) -> Result { match self.0 { + #[cfg(feature = "akita-mysql")] PlatformPool::MysqlPool(ref pool_mysql) => { let pooled_conn = pool_mysql.get(); match pooled_conn { @@ -93,6 +157,14 @@ impl Pool { Err(e) => Err(AkitaError::MySQLError(e.to_string())), } } + #[cfg(feature = "akita-sqlite")] + PlatformPool::SqlitePool(ref pool_sqlite) => { + let pooled_conn = pool_sqlite.get(); + match pooled_conn { + Ok(pooled_conn) => Ok(PooledConnection::PooledSqlite(Box::new(pooled_conn))), + Err(e) => Err(AkitaError::MySQLError(e.to_string())), + } + } } } @@ -100,8 +172,7 @@ impl Pool { /// Data, Rows and Value pub fn akita_manager(&self) -> Result { let db = self.database()?; - let cfg = self.1.clone(); - Ok(AkitaManager(db, cfg)) + Ok(AkitaManager(db)) } fn get_pool_mut(&self) -> Result<&PlatformPool, AkitaError> { @@ -112,6 +183,7 @@ impl Pool { pub fn connect_mut(&self) -> Result { let pool = self.get_pool_mut()?; match *pool { + #[cfg(feature = "akita-mysql")] PlatformPool::MysqlPool(ref pool_mysql) => { let pooled_conn = pool_mysql.get(); match pooled_conn { @@ -119,6 +191,14 @@ impl Pool { Err(e) => Err(AkitaError::MySQLError(e.to_string())), } } + #[cfg(feature = "akita-sqlite")] + PlatformPool::SqlitePool(ref pool_sqlite) => { + let pooled_conn = pool_sqlite.get(); + match pooled_conn { + Ok(pooled_conn) => Ok(PooledConnection::PooledSqlite(Box::new(pooled_conn))), + Err(e) => Err(AkitaError::MySQLError(e.to_string())), + } + } } } @@ -126,14 +206,16 @@ impl Pool { pub fn database(&self) -> Result { let pooled_conn = self.connect_mut()?; match pooled_conn { - PooledConnection::PooledMysql(pooled_mysql) => Ok(DatabasePlatform::Mysql(Box::new(MysqlDatabase(*pooled_mysql)))), + #[cfg(feature = "akita-mysql")] + PooledConnection::PooledMysql(pooled_mysql) => Ok(DatabasePlatform::Mysql(Box::new(MysqlDatabase(*pooled_mysql, self.1.to_owned())))), + #[cfg(feature = "akita-sqlite")] + PooledConnection::PooledSqlite(pooled_sqlite) => Ok(DatabasePlatform::Sqlite(Box::new(SqliteDatabase(*pooled_sqlite, self.1.to_owned())))), } } /// return an entity manager which provides a higher level api pub fn entity_manager(&self) -> Result { let db = self.database()?; - let cfg = self.1.clone(); - Ok(AkitaEntityManager(db, cfg)) + Ok(AkitaEntityManager(db)) } } \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index 6cdb733..23e5440 100644 --- a/src/types.rs +++ b/src/types.rs @@ -139,7 +139,7 @@ impl HasType for Value { Value::Bigint(_) => Some(SqlType::Bigint), Value::Float(_) => Some(SqlType::Float), Value::Double(_) => Some(SqlType::Double), - // Value::BigDecimal(_) => Some(SqlType::Numeric), + Value::BigDecimal(_) => Some(SqlType::Numeric), Value::Blob(_) => Some(SqlType::Blob), Value::Char(_) => Some(SqlType::Char), Value::Text(_) => Some(SqlType::Text), diff --git a/src/value.rs b/src/value.rs index 0a06f7a..06f2507 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,5 @@ use std::fmt; +use bigdecimal::{BigDecimal, ToPrimitive}; use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc}; use uuid::Uuid; @@ -14,7 +15,7 @@ pub enum Value { Float(f32), Double(f64), - // BigDecimal(BigDecimal), + BigDecimal(BigDecimal), Blob(Vec), Char(char), @@ -27,7 +28,7 @@ pub enum Value { DateTime(NaiveDateTime), Timestamp(DateTime), Interval(Interval), - SerdeJson(mysql::serde_json::Value), + SerdeJson(serde_json::Value), // Point(Point), @@ -68,14 +69,14 @@ impl fmt::Display for Value { Value::Bigint(v) => write!(f, "{}", v), Value::Float(v) => write!(f, "{}", v), Value::Double(v) => write!(f, "{}", v), - // Value::BigDecimal(v) => write!(f, "{}", v), + Value::BigDecimal(v) => write!(f, "{}", v), Value::Char(v) => write!(f, "{}", v), Value::Text(v) => write!(f, "{}", v), Value::Json(v) => write!(f, "{}", v), Value::Uuid(v) => write!(f, "{}", v), Value::Date(v) => write!(f, "{}", v), Value::Time(v) => write!(f, "{}", v), - Value::SerdeJson(v) => write!(f, "{}", mysql::serde_json::to_string(v).unwrap_or_default()), + Value::SerdeJson(v) => write!(f, "{}", serde_json::to_string(v).unwrap_or_default()), Value::DateTime(v) => write!(f, "{}", v.format("%Y-%m-%d %H:%M:%S").to_string()), Value::Timestamp(v) => write!(f, "{}", v.to_rfc3339()), Value::Array(array) => array.fmt(f), @@ -228,8 +229,8 @@ pub enum ConvertError { NotSupported(String, String), } -impl From for ConvertError { - fn from(err: mysql::serde_json::Error) -> Self { +impl From for ConvertError { + fn from(err: serde_json::Error) -> Self { ConvertError::NotSupported(err.to_string(), "SerdeJson".to_string()) } } @@ -260,7 +261,7 @@ macro_rules! impl_from_value_numeric { match *v { $(Value::$variant(ref v) => Ok(v.to_owned() as $ty), )* - // Value::BigDecimal(ref v) => Ok(v.$method().unwrap()), + Value::BigDecimal(ref v) => Ok(v.$method().unwrap()), _ => Err(ConvertError::NotSupported(format!("{:?}", v), $ty_name.into())), } } @@ -334,27 +335,27 @@ impl FromValue for bool { } } -impl FromValue for mysql::serde_json::Value { +impl FromValue for serde_json::Value { fn from_value(v: &Value) -> Result { match v.clone() { - Value::Bool(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Tinyint(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Smallint(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Int(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Bigint(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Float(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Double(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Blob(v) => mysql::serde_json::to_value(String::from_utf8_lossy(&v)).map_err(ConvertError::from), - Value::Char(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Text(v) => mysql::serde_json::from_str(&v).map_err(ConvertError::from), - Value::Json(v) => mysql::serde_json::from_str(&v).map_err(ConvertError::from), - Value::Uuid(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Date(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Time(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::DateTime(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), - Value::Timestamp(v) => mysql::serde_json::to_value(v).map_err(ConvertError::from), + Value::Bool(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Tinyint(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Smallint(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Int(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Bigint(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Float(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Double(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Blob(v) => serde_json::to_value(String::from_utf8_lossy(&v)).map_err(ConvertError::from), + Value::Char(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Text(v) => serde_json::from_str(&v).map_err(ConvertError::from), + Value::Json(v) => serde_json::from_str(&v).map_err(ConvertError::from), + Value::Uuid(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Date(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Time(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::DateTime(v) => serde_json::to_value(v).map_err(ConvertError::from), + Value::Timestamp(v) => serde_json::to_value(v).map_err(ConvertError::from), Value::SerdeJson(v) => Ok(v.clone()), - // Value::Array(v) => mysql::serde_json::to_value(v).map_err(|err| ConvertError::from(err)), + // Value::Array(v) => serde_json::to_value(v).map_err(|err| ConvertError::from(err)), _ => Err(ConvertError::NotSupported( format!("{:?}", v), "SerdeJson".to_string(), diff --git a/tests/akita.rs b/tests/akita.rs index 8475a87..b6de1e1 100644 --- a/tests/akita.rs +++ b/tests/akita.rs @@ -3,7 +3,7 @@ //! use akita::prelude::*; use akita::*; -use mysql::chrono::NaiveDateTime; +use chrono::NaiveDateTime; #[derive(Table, Clone, ToAkita, FromAkita)] #[table(name = "t_system_user")] @@ -36,10 +36,10 @@ impl Default for User { id: "".to_string(), pk: 0, name: "".to_string(), - headline: mysql::chrono::Local::now().naive_local(), + headline: chrono::Local::now().naive_local(), avatar_url: "".to_string().into(), gender: 0, - birthday: mysql::chrono::Local::now().naive_local().date().into(), + birthday: chrono::Local::now().naive_local().date().into(), is_org: false, url_token: "".to_string(), user_type: "".to_string(), @@ -58,106 +58,4 @@ pub struct TestInnerStruct { #[derive(Clone)] pub enum TestInnerEnum { Field, -} - -// #[test] -// fn basic_test() { -// let mut wrapper = UpdateWrapper::new(); -// wrapper.like(true, "username", "ffff"); -// wrapper.eq(true, "username", 12); -// wrapper.eq(true, "username", "3333"); -// wrapper.in_(true, "username", vec![1, 44, 3]); -// wrapper.not_between(true, "username", 2, 8); -// wrapper.set(true, "username", 4); -// let opts = Opts::from_url("mysql://root:127.0.0.1:3306/test").expect("database url is empty."); -// let pool = new_pool("mysql://root:127.0.0.1:3306/test", 4).unwrap(); -// let mut conn = pool.get().unwrap(); -// let user = User { -// id: "2".to_string(), -// pk: 0, -// name: "name".to_string(), -// headline: mysql::chrono::Local::now().naive_local(), -// avatar_url: "name".to_string().into(), -// gender: 0, -// birthday: mysql::chrono::Local::now().naive_local().date().into(), -// is_org: false, -// url_token: "name".to_string(), -// user_type: "name".to_string(), -// status: 0, -// level: 1, -// data: vec![], -// inner_struct: Some(TestInnerStruct { id: "".to_string() }), -// inner_tuple: ("".to_string()), -// inner_enum: TestInnerEnum::Field, -// }; -// conn.start_transaction(TxOpts::default()) -// .map(|mut transaction| { -// match user.update(&mut wrapper, &mut ConnMut::TxMut(&mut transaction)) { -// Ok(res) => {} -// Err(err) => { -// println!("error : {:?}", err); -// } -// } -// }); -// let mut pool = ConnMut::R2d2Polled(conn); -// match user.update_by_id(&mut pool) { -// Ok(res) => {} -// Err(err) => { -// println!("error : {:?}", err); -// } -// } -// match user.delete_by_id(&mut pool) { -// Ok(res) => {} -// Err(err) => { -// println!("error : {:?}", err); -// } -// } -// match user.delete::(&mut wrapper, &mut pool) { -// Ok(res) => {} -// Err(err) => { -// println!("error : {:?}", err); -// } -// } -// match user.insert(&mut pool) { -// Ok(res) => {} -// Err(err) => { -// println!("error : {:?}", err); -// } -// } - -// match user.find_by_id(&mut pool) { -// Ok(res) => {} -// Err(err) => { -// println!("error : {:?}", err); -// } -// } - -// match User::find_one::(&mut wrapper, &mut pool) { -// Ok(res) => {} -// Err(err) => { -// println!("error : {:?}", err); -// } -// } -// match User::page::(1, 10, &mut wrapper, &mut pool) { -// Ok(res) => {} -// Err(err) => { -// println!("error : {:?}", err); -// } -// } -// } - -// #[test] -// fn basic_wrapper() { -// let mut wrapper = UpdateWrapper::new(); -// wrapper.like(true, "username", "ffff"); -// wrapper.eq(true, "username", 12); -// wrapper.eq(true, "username", "3333"); -// wrapper.in_(true, "username", vec![1, 44, 3]); -// // wrapper.not_between(true, "username", 2, 8); -// wrapper.set(true, "username", 4); -// wrapper.apply(true, "FIND_IN_SET(1,category_ids)"); -// wrapper.order_by(true, true, vec!["name","age"]); -// wrapper.group_by(true, vec!["name","age"]); -// let sql = wrapper.get_target_sql("table_name").unwrap(); -// println!("format sql: {}", sql); -// } \ No newline at end of file +} \ No newline at end of file