├── scripts ├── dump_all.sh ├── dump_schema.sh ├── setup.sh └── setup.bat ├── Changelog.md ├── Contributors.md ├── Progress.md ├── update-docs.sh ├── .gitignore ├── MySQL.md ├── rustorm-derive ├── Cargo.toml ├── Cargo.lock └── src │ └── lib.rs ├── appveyor.yml ├── Sqlite.md ├── examples ├── simple_query.rs ├── simple_insert.rs ├── em_delete.rs ├── derive_test.rs ├── em_get_all_sqlite.rs ├── em_get_all.rs ├── em_insert_sqlite.rs ├── em_insert.rs └── em_update.rs ├── src ├── lib.rs ├── query │ ├── mod.rs │ ├── join.rs │ ├── function.rs │ ├── order.rs │ ├── source.rs │ ├── table_name.rs │ ├── column_name.rs │ ├── field.rs │ ├── filter.rs │ ├── operand.rs │ ├── builder.rs │ └── query.rs ├── platform │ ├── mod.rs │ ├── pool.rs │ ├── sqlite.rs │ └── mysql.rs ├── entity.rs ├── writer.rs ├── config.rs └── dao.rs ├── .travis.yml ├── LICENSE ├── tests ├── test_crud.rs └── test_simple.rs ├── Cargo.toml ├── README.md ├── TODO.md └── Notes.md /scripts/dump_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | pg_dump -U postgres -h localhost -p 5432 "$1" > "$1_all.sql" 3 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## 0.1.3 2 | - Formatting changes 3 | - to_string() to to_owned() 4 | - A more comprehensive DbError 5 | -------------------------------------------------------------------------------- /scripts/dump_schema.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | pg_dump -U postgres -h localhost -p 5432 --schema-only "$1" > "$1_dump_schema.sql" 3 | -------------------------------------------------------------------------------- /Contributors.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | contradictioned - Manuel Hoffmann 4 | 5 | Fiedzia - Maciej Dziardziel 6 | 7 | mtorromeo - Massimiliano Torromeo 8 | 9 | kyeah - Kevin Yeh 10 | -------------------------------------------------------------------------------- /Progress.md: -------------------------------------------------------------------------------- 1 | ## July 19,2015 2 | Implemented a custom encoding for Dao to get rid of unecessary information 3 | 4 | 5 | 6 | ## August 29, 2015 7 | * Made sqlite an optional dependency package 8 | 9 | 10 | ## September 9, 2015 11 | * Added DatabaseDev implementation for sqlite 12 | -------------------------------------------------------------------------------- /update-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cargo clean 3 | cargo doc --no-deps 4 | cd target/doc 5 | git init 6 | git add . -A 7 | git commit -m "Commiting docs to github pages" 8 | git remote add origin https://github.com/ivanceras/rustorm 9 | git checkout -b gh-pages 10 | git push --force origin gh-pages 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | #Generated by rustorm 7 | #/examples/gen/** 8 | /gen/** 9 | # Executables 10 | *.exe 11 | 12 | # Generated by Cargo 13 | target*/ 14 | 15 | #generated by eclipse 16 | /.project 17 | /.DS_Store 18 | 19 | #swap files 20 | *~ 21 | *.swp 22 | *.swo 23 | *.bk 24 | -------------------------------------------------------------------------------- /MySQL.md: -------------------------------------------------------------------------------- 1 | ## connecting to mysql 2 | 3 | mysql -u root -h localhost -p 4 | 5 | 6 | create user 'test'@'localhost' identified by 'test'; 7 | 8 | create user 'bazaar'@'localhost' identified by 'b4z44r'; 9 | 10 | SET PASSWORD FOR 'test'@'localhost' = password('test'); 11 | 12 | create database bazaar_v6; 13 | 14 | 15 | GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP on bazaar.* to 'bazaar'@'localhost'; -------------------------------------------------------------------------------- /rustorm-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustorm-derive" 3 | version = "0.1.0" 4 | authors = ["Jovansonlee Cesar "] 5 | license = "MIT" 6 | description = "procedural macro code generator for rustorm" 7 | repository = "https://github.com/ivanceras/rustorm" 8 | documentation = "https://docs.rs/rustorm-derive" 9 | keywords = ["rustorm"] 10 | 11 | [dependencies] 12 | syn = "0.10.5" 13 | quote = "0.3.10" 14 | 15 | [lib] 16 | proc-macro = true 17 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - ps: Start-FileDownload 'https://static.rust-lang.org/dist/rust-nightly-i686-pc-windows-gnu.exe' 3 | - rust-nightly-i686-pc-windows-gnu.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" 4 | - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin 5 | - SET PATH=%PATH%;C:\MinGW\bin 6 | - rustc -V 7 | - cargo -V 8 | - git submodule update --init --recursive 9 | 10 | build: false 11 | 12 | services: 13 | - postgresql 14 | 15 | before_test: 16 | - SET PGUSER=postgres 17 | - SET PGPASSWORD=Password12! 18 | - PATH=C:\Program Files\PostgreSQL\9.4\bin\;%PATH% 19 | - .\scripts\setup.bat 20 | 21 | test_script: 22 | - cargo test --verbose 23 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## create a new database, will prompt for password 4 | psql -U postgres -h localhost -p 5432 -d postgres -c "create database bazaar_v8 with owner postgres encoding 'utf8';" 5 | 6 | ## create role bazaar 7 | psql -U postgres -h localhost -p 5432 -d postgres -c "create role bazaar with login password 'b4z44r'" 8 | 9 | 10 | ## fill the newly created database, will prompt password again 11 | psql -U postgres -h localhost -p 5432 -d bazaar_v8 -f ./scripts/bazaar_v8_all.sql 12 | 13 | 14 | ## change the password of postgresql as what is used in the examples and tests 15 | psql -U postgres -h localhost -p 5432 -d postgres -c "alter role postgres with password 'p0stgr3s'" 16 | -------------------------------------------------------------------------------- /scripts/setup.bat: -------------------------------------------------------------------------------- 1 | 2 | REM create a new database, will prompt for password 3 | psql.exe -U postgres -h localhost -p 5432 -d postgres -c "create database bazaar_v8 with owner postgres encoding 'utf8';" 4 | 5 | REM create role bazaar 6 | psql.exe -U postgres -h localhost -p 5432 -d postgres -c "create role bazaar with login password 'b4z44r'" 7 | 8 | 9 | REM fill the newly created database, will prompt password again 10 | psql.exe -U postgres -h localhost -p 5432 -d bazaar_v8 -f .\scripts\bazaar_v8_all.sql 11 | 12 | 13 | REM change the password of postgresql as what is used in the examples and tests 14 | psql.exe -U postgres -h localhost -p 5432 -d postgres -c "alter role postgres with password 'p0stgr3s'" 15 | -------------------------------------------------------------------------------- /Sqlite.md: -------------------------------------------------------------------------------- 1 | A sqlite rough equivalent for table with uuid 2 | and timestamp with time zone now() 3 | 4 | ```sql 5 | CREATE TABLE users 6 | ( 7 | user_id uuid NOT NULL PRIMARY KEY DEFAULT (substr(lower(hex(randomblob(16))),1,8)||'-'||substr(lower(hex(randomblob(16))),9,4)||'-4'||substr(lower(hex(randomblob(16))),13,3)|| '-'||substr('89ab',abs(random()) % 4 + 1, 1)||substr(lower(hex(randomblob(16))),17,3)||'-'||substr(lower(hex(randomblob(16))),21,12)) 8 | , 9 | username text, 10 | password text, 11 | email text, 12 | created date NOT NULL DEFAULT current_timestamp, 13 | created_by uuid, 14 | updated date NOT NULL DEFAULT current_timestamp, 15 | updated_by uuid, 16 | active boolean NOT NULL DEFAULT true 17 | ) 18 | ``` 19 | -------------------------------------------------------------------------------- /examples/simple_query.rs: -------------------------------------------------------------------------------- 1 | extern crate rustorm; 2 | 3 | use rustorm::query::Query; 4 | use rustorm::query::Filter; 5 | use rustorm::query::Select; 6 | use rustorm::query::Equality::EQ; 7 | use rustorm::database::BuildMode; 8 | use rustorm::platform::pool::Platform; 9 | use rustorm::platform::Postgres; 10 | use rustorm::platform::pool; 11 | 12 | fn main(){ 13 | println!("connecting...."); 14 | let db = pool::db_with_url("postgres://postgres:p0stgr3s@localhost/bazaar_v8").unwrap(); 15 | println!("got connection"); 16 | let mut query = Select::new(); 17 | query.columns(vec!["username", "email"]); 18 | query.from(&"bazaar.users".to_string()); 19 | query.set_limit(1); 20 | 21 | let ret = db.select(&query); 22 | println!("{:#?}", ret); 23 | } 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rustorm is a simple ORM implemented in rust. 2 | //! 3 | //! 4 | //! 5 | //! 6 | 7 | #![deny(warnings)] 8 | #![allow(non_snake_case)] 9 | #[cfg(test)] #[macro_use] extern crate pretty_assertions; 10 | extern crate rustc_serialize; 11 | extern crate postgres; 12 | #[cfg(feature = "sqlite")] 13 | extern crate rusqlite; 14 | #[cfg(feature = "mysql")] 15 | extern crate mysql; 16 | extern crate uuid; 17 | extern crate chrono; 18 | extern crate regex; 19 | extern crate url; 20 | extern crate r2d2; 21 | extern crate r2d2_postgres; 22 | #[cfg(feature = "sqlite")] 23 | extern crate r2d2_sqlite; 24 | extern crate time; 25 | #[macro_use] extern crate log; 26 | #[macro_use] extern crate lazy_static; 27 | 28 | 29 | 30 | // pub mod em; 31 | pub mod query; 32 | pub mod dao; 33 | pub mod database; 34 | pub mod platform; 35 | pub mod table; 36 | pub mod writer; 37 | pub mod config; 38 | pub mod entity; 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | 5 | language: rust 6 | 7 | rust: 8 | - nightly 9 | - beta 10 | - stable 11 | addons: 12 | postgresql: 9.4 13 | apt: 14 | packages: 15 | - libcurl4-openssl-dev 16 | - libelf-dev 17 | - libdw-dev 18 | - binutils-dev # optional: only required for the --verify flag of coveralls 19 | install: 20 | - if [[ $(uname -s) == 'Darwin' ]]; then 21 | brew rm postgis --force; 22 | brew install postgis --force; 23 | pg_ctl -w start -l postgres.log --pgdata /usr/local/var/postgres; 24 | createuser -s postgres; 25 | else 26 | echo "Not on darwin"; 27 | fi 28 | before_script: 29 | - "./scripts/setup.sh" 30 | - | 31 | pip install 'travis-cargo<0.2' --user && 32 | export PATH=$HOME/.local/bin:$PATH 33 | 34 | after_success: 35 | - travis-cargo coveralls --no-sudo --verify 36 | script: 37 | - cargo test --features "postgres sqlite mysql" 38 | -------------------------------------------------------------------------------- /examples/simple_insert.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate rustorm; 3 | 4 | use rustorm::query::Query; 5 | use rustorm::query::Filter; 6 | use rustorm::query::Select; 7 | use rustorm::query::Equality::EQ; 8 | use rustorm::database::BuildMode; 9 | use rustorm::platform::pool::Platform; 10 | use rustorm::platform::Postgres; 11 | use rustorm::platform::pool; 12 | use rustorm::query::Insert; 13 | 14 | 15 | fn main(){ 16 | println!("connecting...."); 17 | let db = pool::db_with_url("postgres://postgres:p0stgr3s@localhost/mock").unwrap(); 18 | println!("got connection"); 19 | let mut query = Insert::into(&"users"); 20 | query.columns(vec!["username", "email"]); 21 | query.values(vec![&"Lee".to_string(), 22 | &"ivanceras@gmail.com".to_string()]); 23 | query.return_columns(vec!["*"]); 24 | let sql = query.debug_build(db.as_ref()); 25 | println!("sql: {}", sql); 26 | let ret = db.as_ref().insert(&query); 27 | println!("{:#?}", ret); 28 | } 29 | -------------------------------------------------------------------------------- /src/query/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod column_name; 2 | pub mod table_name; 3 | pub mod filter; 4 | pub mod function; 5 | pub mod join; 6 | pub mod operand; 7 | pub mod order; 8 | pub mod field; 9 | pub mod source; 10 | pub mod query; 11 | 12 | pub use self::column_name::{ColumnName, ToColumnName}; 13 | pub use self::table_name::{TableName, ToTableName,IsTable}; 14 | pub use self::filter::{Filter, Condition, Equality, Connector, HasEquality}; 15 | pub use self::function::COUNT; 16 | pub use self::function::Function; 17 | pub use self::join::{Join, JoinType, Modifier}; 18 | pub use self::operand::Operand; 19 | pub use self::order::{Order, ToOrder, HasDirection, NullsWhere, Direction}; 20 | pub use self::field::{Field, ToField}; 21 | pub use self::source::SourceField; 22 | pub use self::source::{QuerySource, ToSourceField}; 23 | 24 | pub use self::query::{Range,DeclaredQuery,Error}; 25 | pub use self::query::{Select,Insert,Update,Delete}; 26 | pub use self::query::Query; 27 | pub use self::query::IsQuery; 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/em_delete.rs: -------------------------------------------------------------------------------- 1 | extern crate rustorm; 2 | #[macro_use] 3 | extern crate rustorm_derive; 4 | use rustorm::dao::Dao; 5 | use rustorm::dao::IsDao; 6 | use rustorm::query::TableName; 7 | use rustorm::query::IsTable; 8 | use rustorm::query::ColumnName; 9 | use rustorm::dao::ToValue; 10 | use rustorm::dao::FromValue; 11 | use rustorm::platform::pool; 12 | use rustorm::entity::EntityManager; 13 | use rustorm::query::Filter; 14 | use rustorm::query::Equality; 15 | 16 | 17 | #[derive(IsDao)] 18 | #[derive(IsTable)] 19 | #[derive(Debug)] 20 | struct Users{ 21 | username: String, 22 | email: String, 23 | } 24 | 25 | fn main() { 26 | let user = Users{ 27 | username : "ivanceras".to_string(), 28 | email: "ivanceras@gmail.com".to_string() 29 | }; 30 | let db = pool::db_with_url("postgres://postgres:p0stgr3s@localhost/mock").unwrap(); 31 | let em = EntityManager::new(&*db); 32 | let filter = Filter::new("email", Equality::EQ, &"ivanceras@gmail.com".to_string()); 33 | let ret:usize = em.delete::(&filter).unwrap(); 34 | println!("deleted : {} record(s)", ret); 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jovansonlee Cesar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tests/test_crud.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate rustorm; 3 | #[macro_use] 4 | extern crate rustorm_derive; 5 | use rustorm::dao::Dao; 6 | use rustorm::dao::IsDao; 7 | use rustorm::query::TableName; 8 | use rustorm::query::IsTable; 9 | use rustorm::query::ColumnName; 10 | use rustorm::dao::ToValue; 11 | use rustorm::dao::FromValue; 12 | use rustorm::platform::pool; 13 | use rustorm::entity::EntityManager; 14 | use rustorm::query::Filter; 15 | use rustorm::query::Equality; 16 | 17 | 18 | #[derive(IsDao)] 19 | #[derive(IsTable)] 20 | #[derive(Debug)] 21 | struct Users{ 22 | username: String, 23 | email: String, 24 | } 25 | 26 | fn test_delete() { 27 | let user = Users{ 28 | username : "ivanceras".to_string(), 29 | email: "ivanceras@gmail.com".to_string() 30 | }; 31 | let db = pool::db_with_url("postgres://postgres:p0stgr3s@localhost/mock").unwrap(); 32 | let em = EntityManager::new(&*db); 33 | let filter = Filter::new("email", Equality::EQ, &"ivanceras@gmail.com".to_string()); 34 | em.insert::(&user); 35 | let ret:usize = em.delete::(&filter).unwrap(); 36 | assert_eq!(ret, 1); 37 | println!("deleted : {} record(s)", ret); 38 | } 39 | -------------------------------------------------------------------------------- /examples/derive_test.rs: -------------------------------------------------------------------------------- 1 | extern crate rustorm; 2 | #[macro_use] 3 | extern crate rustorm_derive; 4 | use rustorm::dao::Dao; 5 | use rustorm::dao::IsDao; 6 | use rustorm::query::TableName; 7 | use rustorm::query::IsTable; 8 | use rustorm::query::ColumnName; 9 | use rustorm::dao::ToValue; 10 | use rustorm::dao::FromValue; 11 | use rustorm::platform::pool; 12 | use rustorm::entity::EntityManager; 13 | use rustorm::query::Filter; 14 | use rustorm::query::Equality; 15 | 16 | 17 | #[derive(IsDao)] 18 | #[derive(IsTable)] 19 | #[derive(Debug)] 20 | struct Users{ 21 | username: String, 22 | email: String, 23 | } 24 | 25 | fn main() { 26 | let user = Users{ 27 | username : "ivanceras".to_string(), 28 | email: "ivanceras@gmail.com".to_string() 29 | }; 30 | let db = pool::db_with_url("postgres://postgres:p0stgr3s@localhost/mock").unwrap(); 31 | let em = EntityManager::new(&*db); 32 | let filter = Filter::new("email", Equality::EQ, &"asdsadasd".to_string()); 33 | let users:Vec = em.get_all_with_filter(&filter).unwrap(); 34 | for i in users{ 35 | println!("{:?}", i); 36 | } 37 | 38 | let u = em.get_one::(&filter).unwrap(); 39 | println!("got: {:?}", u); 40 | } 41 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustorm" 3 | version = "0.7.0" 4 | authors = [ "Jovansonlee Cesar " ] 5 | license = "MIT" 6 | description = "An ORM for rust" 7 | readme = "README.md" 8 | repository = "https://github.com/ivanceras/rustorm" 9 | documentation = "https://docs.rs/rustorm" 10 | keywords = ["orm", "database", "sql"] 11 | 12 | [lib] 13 | name = "rustorm" 14 | 15 | [features] 16 | sqlite = ["rusqlite","r2d2_sqlite"] 17 | 18 | [dependencies.chrono] 19 | version = "0.2" 20 | features = ["rustc-serialize"] 21 | 22 | [dependencies.postgres] 23 | version = "0.11" 24 | features = ["rustc-serialize", "uuid", "chrono"] 25 | 26 | 27 | [dependencies] 28 | rustc-serialize = "0.3" 29 | serde_json = "0.7" 30 | uuid = {version = "0.4", features = ["serde", "rustc-serialize"] } 31 | log = "0.3" 32 | env_logger = "0.3" 33 | regex = "0.1" 34 | url = "1.1" 35 | time = "0.1" 36 | r2d2 = "0.7" 37 | r2d2_postgres = {version = "0.10"} 38 | rusqlite = { version = "0.10", optional = true } 39 | mysql = {version = "1.2", optional = true } 40 | r2d2_sqlite = {version = "0.1", optional = true} 41 | lazy_static = "0.2" 42 | rustorm-derive = {path = "rustorm-derive", version = "0.1"} 43 | 44 | [dev-dependencies] 45 | pretty_assertions = "0.1" 46 | -------------------------------------------------------------------------------- /examples/em_get_all_sqlite.rs: -------------------------------------------------------------------------------- 1 | extern crate rustorm; 2 | #[macro_use] 3 | extern crate rustorm_derive; 4 | extern crate uuid; 5 | extern crate chrono; 6 | use rustorm::dao::Dao; 7 | use rustorm::dao::IsDao; 8 | use rustorm::query::TableName; 9 | use rustorm::query::IsTable; 10 | use rustorm::query::ColumnName; 11 | use rustorm::dao::ToValue; 12 | use rustorm::dao::FromValue; 13 | use rustorm::platform::pool; 14 | use rustorm::entity::EntityManager; 15 | use rustorm::query::Filter; 16 | use rustorm::query::Equality; 17 | use uuid::Uuid; 18 | use chrono::DateTime; 19 | use chrono::UTC; 20 | 21 | 22 | 23 | 24 | #[derive(IsDao)] 25 | #[derive(IsTable)] 26 | #[derive(Debug)] 27 | struct Users{ 28 | user_id: Uuid, 29 | username: String, 30 | email: String, 31 | created: DateTime, 32 | updated: DateTime, 33 | active: bool 34 | } 35 | 36 | fn main() { 37 | let db = pool::db_with_url("sqlite:///file.db").unwrap(); 38 | let em = EntityManager::new(&*db); 39 | let filter = Filter::new("email", Equality::EQ, &"ivanceras@gmail.com".to_string()); 40 | let ret:Vec = em.get_all().unwrap(); 41 | println!("got : {:#?}", ret); 42 | let filtered:Vec = em.get_all_with_filter(&filter).unwrap(); 43 | println!("filtered: {:#?}", filtered); 44 | } 45 | -------------------------------------------------------------------------------- /src/query/join.rs: -------------------------------------------------------------------------------- 1 | use table::Table; 2 | use query::Filter; 3 | use query::table_name::{TableName,ToTableName}; 4 | 5 | 6 | #[derive(Debug)] 7 | #[derive(Clone)] 8 | pub enum JoinType { 9 | CROSS, 10 | INNER, 11 | OUTER, 12 | NATURAL, 13 | } 14 | #[derive(Debug)] 15 | #[derive(Clone)] 16 | pub enum Modifier { 17 | LEFT, 18 | RIGHT, 19 | FULL, 20 | } 21 | 22 | #[derive(Debug)] 23 | #[derive(Clone)] 24 | pub struct Join { 25 | pub modifier: Option, 26 | pub join_type: Option, 27 | pub table_name: TableName, 28 | pub on: Filter, 29 | } 30 | 31 | 32 | pub trait ToJoin { 33 | fn ON(&self, filter: Filter) -> Join; 34 | } 35 | 36 | impl ToJoin for F 37 | where F: Fn() -> Table 38 | { 39 | fn ON(&self, filter: Filter) -> Join { 40 | let table = self(); 41 | Join { 42 | modifier: None, 43 | join_type: None, 44 | table_name: table.to_table_name(), 45 | on: filter, 46 | } 47 | } 48 | } 49 | 50 | impl<'a> ToJoin for &'a str { 51 | fn ON(&self, filter: Filter) -> Join { 52 | Join { 53 | modifier: None, 54 | join_type: None, 55 | table_name: self.to_table_name(), 56 | on: filter, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/em_get_all.rs: -------------------------------------------------------------------------------- 1 | extern crate rustorm; 2 | #[macro_use] 3 | extern crate rustorm_derive; 4 | extern crate uuid; 5 | extern crate chrono; 6 | use rustorm::dao::Dao; 7 | use rustorm::dao::IsDao; 8 | use rustorm::query::TableName; 9 | use rustorm::query::IsTable; 10 | use rustorm::query::ColumnName; 11 | use rustorm::dao::ToValue; 12 | use rustorm::dao::FromValue; 13 | use rustorm::platform::pool; 14 | use rustorm::entity::EntityManager; 15 | use rustorm::query::Filter; 16 | use rustorm::query::Equality; 17 | use uuid::Uuid; 18 | use chrono::DateTime; 19 | use chrono::UTC; 20 | 21 | 22 | 23 | 24 | #[derive(IsDao)] 25 | #[derive(IsTable)] 26 | #[derive(Debug)] 27 | struct Users{ 28 | user_id: Uuid, 29 | username: String, 30 | email: String, 31 | created: DateTime, 32 | updated: DateTime, 33 | active: bool 34 | } 35 | 36 | fn main() { 37 | let db = pool::db_with_url("postgres://postgres:p0stgr3s@localhost/mock").unwrap(); 38 | let em = EntityManager::new(&*db); 39 | let filter = Filter::new("email", Equality::EQ, &"ivanceras@gmail.com".to_string()); 40 | let ret:Vec = em.get_all().unwrap(); 41 | println!("got : {:#?}", ret); 42 | let filtered:Vec = em.get_all_with_filter(&filter).unwrap(); 43 | println!("filtered: {:#?}", filtered); 44 | } 45 | -------------------------------------------------------------------------------- /rustorm-derive/Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "rustorm-derive" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "syn 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "quote" 11 | version = "0.3.15" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "syn" 16 | version = "0.10.8" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | dependencies = [ 19 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "unicode-xid" 25 | version = "0.0.4" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | 28 | [metadata] 29 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 30 | "checksum syn 0.10.8 (registry+https://github.com/rust-lang/crates.io-index)" = "58fd09df59565db3399efbba34ba8a2fec1307511ebd245d0061ff9d42691673" 31 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 32 | -------------------------------------------------------------------------------- /src/query/function.rs: -------------------------------------------------------------------------------- 1 | use query::Operand; 2 | use query::operand::ToOperand; 3 | use query::source::QuerySource; 4 | 5 | 6 | /// function in a sql statement 7 | #[derive(Debug)] 8 | #[derive(Clone)] 9 | pub struct Function { 10 | pub function: String, 11 | pub params: Vec, 12 | } 13 | 14 | 15 | /// A database function COUNT 16 | pub fn COUNT(to_operand: &ToOperand) -> Operand { 17 | Operand::QuerySource(QuerySource::Function(Function { 18 | function: "COUNT".to_owned(), 19 | params: vec![to_operand.to_operand()], 20 | })) 21 | } 22 | 23 | /// database function MAX 24 | pub fn MAX(to_operand: &ToOperand) -> Operand { 25 | Operand::QuerySource(QuerySource::Function(Function { 26 | function: "MAX".to_owned(), 27 | params: vec![to_operand.to_operand()], 28 | })) 29 | } 30 | /// database function MIN 31 | pub fn MIN(to_operand: &ToOperand) -> Operand { 32 | Operand::QuerySource(QuerySource::Function(Function { 33 | function: "MIN".to_owned(), 34 | params: vec![to_operand.to_operand()], 35 | })) 36 | } 37 | 38 | /// A database date function which returns the time 39 | /// when the query is executed 40 | pub fn NOW() -> Operand { 41 | Operand::QuerySource(QuerySource::Function(Function { 42 | function: "NOW".to_owned(), 43 | params: vec![], 44 | })) 45 | } 46 | -------------------------------------------------------------------------------- /examples/em_insert_sqlite.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_attribute)] 2 | extern crate rustorm; 3 | #[macro_use] 4 | extern crate rustorm_derive; 5 | extern crate uuid; 6 | extern crate chrono; 7 | use rustorm::dao::Dao; 8 | use rustorm::dao::IsDao; 9 | use rustorm::query::TableName; 10 | use rustorm::query::IsTable; 11 | use rustorm::query::ColumnName; 12 | use rustorm::dao::ToValue; 13 | use rustorm::dao::FromValue; 14 | use rustorm::platform::pool; 15 | use rustorm::entity::EntityManager; 16 | use rustorm::query::Filter; 17 | use rustorm::query::Equality; 18 | use uuid::Uuid; 19 | use chrono::DateTime; 20 | use chrono::UTC; 21 | 22 | 23 | #[derive(IsDao)] 24 | #[derive(IsTable)] 25 | #[derive(Debug)] 26 | #[table="users"] 27 | struct NewUser{ 28 | username: String, 29 | email: String, 30 | } 31 | 32 | 33 | #[derive(IsDao)] 34 | #[derive(IsTable)] 35 | #[derive(Debug)] 36 | struct Users{ 37 | user_id: Uuid, 38 | username: String, 39 | email: String, 40 | //created: DateTime 41 | } 42 | 43 | fn main() { 44 | let user = NewUser{ 45 | username : "ivanceras".to_string(), 46 | email: "ivanceras@gmail.com".to_string() 47 | }; 48 | let db = pool::db_with_url("sqlite:///file.db").unwrap(); 49 | let em = EntityManager::new(&*db); 50 | let filter = Filter::new("email", Equality::EQ, &"ivanceras@gmail.com".to_string()); 51 | let ret:Users = em.insert(&user).unwrap(); 52 | println!("inserted : {:?}", ret); 53 | } 54 | -------------------------------------------------------------------------------- /examples/em_insert.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_attribute)] 2 | extern crate rustorm; 3 | #[macro_use] 4 | extern crate rustorm_derive; 5 | extern crate uuid; 6 | extern crate chrono; 7 | use rustorm::dao::Dao; 8 | use rustorm::dao::IsDao; 9 | use rustorm::query::TableName; 10 | use rustorm::query::IsTable; 11 | use rustorm::query::ColumnName; 12 | use rustorm::dao::ToValue; 13 | use rustorm::dao::FromValue; 14 | use rustorm::platform::pool; 15 | use rustorm::entity::EntityManager; 16 | use rustorm::query::Filter; 17 | use rustorm::query::Equality; 18 | use uuid::Uuid; 19 | use chrono::DateTime; 20 | use chrono::UTC; 21 | 22 | 23 | #[derive(IsDao)] 24 | #[derive(IsTable)] 25 | #[derive(Debug)] 26 | #[table="users"] 27 | struct NewUser{ 28 | username: String, 29 | email: String, 30 | } 31 | 32 | 33 | #[derive(IsDao)] 34 | #[derive(IsTable)] 35 | #[derive(Debug)] 36 | struct Users{ 37 | user_id: Uuid, 38 | username: String, 39 | email: String, 40 | //created: DateTime 41 | } 42 | 43 | fn main() { 44 | let user = NewUser{ 45 | username : "ivanceras".to_string(), 46 | email: "ivanceras@gmail.com".to_string() 47 | }; 48 | let db = pool::db_with_url("postgres://postgres:p0stgr3s@localhost/mock").unwrap(); 49 | let em = EntityManager::new(&*db); 50 | let filter = Filter::new("email", Equality::EQ, &"ivanceras@gmail.com".to_string()); 51 | let ret:Users = em.insert(&user).unwrap(); 52 | println!("inserted : {:?}", ret); 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rustorm 2 | [A big Rewrite is on-going](https://gitlab.com/ivanceras/rustorm-rewrite) 3 | 4 | [![Latest Version](https://img.shields.io/crates/v/rustorm.svg)](https://crates.io/crates/rustorm) 5 | [![Build Status](https://api.travis-ci.org/ivanceras/rustorm.svg)](https://travis-ci.org/ivanceras/rustorm) 6 | ![Downloads](https://img.shields.io/crates/d/rustorm.svg) 7 | [![Coverage Status](https://coveralls.io/repos/ivanceras/rustorm/badge.svg?branch=master&service=github)](https://coveralls.io/github/ivanceras/rustorm?branch=master) 8 | [![Build status](https://ci.appveyor.com/api/projects/status/v48jf16of0n56nm2?svg=true)](https://ci.appveyor.com/project/ivanceras/rustorm) 9 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 10 | 11 | 12 | An ORM for rust 13 | 14 | ## Design 15 | Rustorm is design to get data from dynamic tables, i.e tables that are created at run-time. 16 | 17 | 18 | ## Features 19 | * Fluent high-level and low-level API 20 | * Composable queries 21 | * Automatic renames of conflicted colum names in the query. 22 | * Easy to reason out generated SQL 23 | 24 | #### [docs](http://docs.rs/rustorm) 25 | 26 | ## Suppported database 27 | - [X] Postgresql 28 | - [X] Sqlite 29 | - [ ] Mysql 30 | 31 | ## Roadmap 32 | 33 | * Support for Mysql 34 | 35 | 36 | # For Updates 37 | Follow me on twitter: [@ivanceras](https://twitter.com/ivanceras) 38 | 39 | 40 | ## Related project 41 | 42 | 43 | * [diesel](https://github.com/sgrif/diesel) 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/em_update.rs: -------------------------------------------------------------------------------- 1 | #![feature(custom_attribute)] 2 | extern crate rustorm; 3 | #[macro_use] 4 | extern crate rustorm_derive; 5 | extern crate uuid; 6 | extern crate chrono; 7 | use rustorm::dao::Dao; 8 | use rustorm::dao::IsDao; 9 | use rustorm::query::TableName; 10 | use rustorm::query::IsTable; 11 | use rustorm::query::ColumnName; 12 | use rustorm::dao::ToValue; 13 | use rustorm::dao::FromValue; 14 | use rustorm::platform::pool; 15 | use rustorm::entity::EntityManager; 16 | use rustorm::query::Filter; 17 | use rustorm::query::Equality; 18 | use uuid::Uuid; 19 | use chrono::DateTime; 20 | use chrono::UTC; 21 | 22 | 23 | #[derive(IsDao)] 24 | #[derive(IsTable)] 25 | #[derive(Debug)] 26 | #[table="users"] 27 | struct NewUser{ 28 | username: String, 29 | email: String, 30 | } 31 | 32 | 33 | #[derive(IsDao)] 34 | #[derive(IsTable)] 35 | #[derive(Debug)] 36 | struct Users{ 37 | user_id: Uuid, 38 | username: String, 39 | email: String, 40 | created: DateTime 41 | } 42 | 43 | fn main() { 44 | let user = NewUser{ 45 | username : "ivanceras111".to_string(), 46 | email: "ivanceras111@gmail.com".to_string() 47 | }; 48 | let db = pool::db_with_url("postgres://postgres:p0stgr3s@localhost/mock").unwrap(); 49 | let em = EntityManager::new(&*db); 50 | let filter = Filter::new("email", Equality::EQ, &"ivanceras@gmail.com".to_string()); 51 | let ret:Users = em.update_with_filter(&user, filter).unwrap(); 52 | println!("updated : {:?}", ret); 53 | } 54 | -------------------------------------------------------------------------------- /tests/test_simple.rs: -------------------------------------------------------------------------------- 1 | extern crate rustorm; 2 | 3 | #[cfg(test)] #[macro_use] extern crate pretty_assertions; 4 | 5 | 6 | use rustorm::query::Query; 7 | use rustorm::query::Filter; 8 | use rustorm::query::Select; 9 | use rustorm::query::Equality::EQ; 10 | use rustorm::database::BuildMode; 11 | use rustorm::platform::pool::Platform; 12 | use rustorm::platform::Postgres; 13 | 14 | #[cfg(test)] 15 | #[test] 16 | #[cfg(feature = "postgres")] 17 | fn test_pg(){ 18 | let pg = Platform::pg(); 19 | let mut query = Select::new(); 20 | query.columns(vec!["username", "email"]); 21 | query.from(&"users".to_string()); 22 | let filter = Filter::new("username", EQ, &"Hello".to_string()); 23 | query.add_filter(&filter); 24 | let sql = pg.build_select(&query, &BuildMode::Debug); 25 | println!("{}", sql); 26 | let expected = r#" 27 | SELECT username, email 28 | FROM users 29 | WHERE username = 'Hello' 30 | "#; 31 | assert_eq!(sql.sql.trim(), expected.trim()); 32 | } 33 | 34 | #[test] 35 | #[cfg(feature = "sqlite")] 36 | fn test_sqlite(){ 37 | let pg = Platform::sqlite(); 38 | let mut query = Select::new(); 39 | query.from(&"users".to_string()); 40 | let filter = Filter::new("username", EQ, &"Hello".to_string()); 41 | query.add_filter(&filter); 42 | let sql = pg.build_select(&query, &BuildMode::Debug); 43 | println!("{}", sql); 44 | let expected = r#" 45 | SELECT 46 | FROM users 47 | WHERE username = 'Hello' 48 | "#; 49 | assert_eq!(sql.sql.trim(), expected.trim()); 50 | } 51 | 52 | 53 | #[test] 54 | #[cfg(feature = "mysql")] 55 | fn test_mysql(){ 56 | let pg = Platform::mysql(); 57 | let mut query = Select::new(); 58 | query.from(&"users".to_string()); 59 | let filter = Filter::new("username", EQ, &"Hello".to_string()); 60 | query.add_filter(&filter); 61 | let sql = pg.build_select(&query, &BuildMode::Debug); 62 | println!("{}", sql); 63 | let expected = r#" 64 | SELECT 65 | FROM users 66 | WHERE username = 'Hello' 67 | "#; 68 | assert_eq!(sql.sql.trim(), expected.trim()); 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/query/order.rs: -------------------------------------------------------------------------------- 1 | use query::Operand; 2 | use query::operand::ToOperand; 3 | 4 | 5 | #[derive(Debug)] 6 | #[derive(Clone)] 7 | pub enum Direction { 8 | ASC, 9 | DESC, 10 | } 11 | 12 | 13 | #[derive(Debug)] 14 | #[derive(Clone)] 15 | pub enum NullsWhere { 16 | FIRST, 17 | LAST, 18 | } 19 | 20 | #[derive(Debug)] 21 | #[derive(Clone)] 22 | pub struct Order { 23 | pub operand: Operand, 24 | pub direction: Option, 25 | pub nulls_where: Option, 26 | } 27 | 28 | impl Order { 29 | pub fn NULLS_FIRST(mut self) -> Order { 30 | self.nulls_where = Some(NullsWhere::FIRST); 31 | self 32 | } 33 | pub fn NULLS_LAST(mut self) -> Order { 34 | self.nulls_where = Some(NullsWhere::FIRST); 35 | self 36 | } 37 | } 38 | 39 | pub trait ToOrder { 40 | fn to_order(&self) -> Vec; 41 | } 42 | 43 | 44 | macro_rules! impl_to_order_for_order{ 45 | ($x:expr) => ( 46 | impl ToOrder for [Order;$x]{ 47 | fn to_order(&self)->Vec{ 48 | let mut orders = vec![]; 49 | for o in self{ 50 | orders.push(o.to_owned()) 51 | } 52 | orders 53 | } 54 | } 55 | ); 56 | } 57 | 58 | impl_to_order_for_order!(1); 59 | impl_to_order_for_order!(2); 60 | impl_to_order_for_order!(3); 61 | impl_to_order_for_order!(4); 62 | impl_to_order_for_order!(5); 63 | impl_to_order_for_order!(6); 64 | impl_to_order_for_order!(7); 65 | impl_to_order_for_order!(8); 66 | impl_to_order_for_order!(9); 67 | impl_to_order_for_order!(10); 68 | impl_to_order_for_order!(11); 69 | impl_to_order_for_order!(12); 70 | 71 | 72 | pub trait HasDirection { 73 | fn ASC(&self) -> Order; 74 | fn DESC(&self) -> Order; 75 | fn ASC_NULLS_FIRST(self) -> Order; 76 | fn ASC_NULLS_LAST(self) -> Order; 77 | fn DESC_NULLS_FIRST(self) -> Order; 78 | fn DESC_NULLS_LAST(self) -> Order; 79 | } 80 | 81 | impl HasDirection for T 82 | where T: ToOperand 83 | { 84 | fn ASC(&self) -> Order { 85 | Order { 86 | operand: self.to_operand(), 87 | direction: Some(Direction::ASC), 88 | nulls_where: None, 89 | } 90 | } 91 | 92 | fn DESC(&self) -> Order { 93 | Order { 94 | operand: self.to_operand(), 95 | direction: Some(Direction::DESC), 96 | nulls_where: None, 97 | } 98 | } 99 | 100 | fn ASC_NULLS_FIRST(self) -> Order { 101 | self.ASC().NULLS_FIRST() 102 | } 103 | 104 | fn ASC_NULLS_LAST(self) -> Order { 105 | self.ASC().NULLS_LAST() 106 | } 107 | 108 | fn DESC_NULLS_FIRST(self) -> Order { 109 | self.DESC().NULLS_FIRST() 110 | } 111 | 112 | fn DESC_NULLS_LAST(self) -> Order { 113 | self.DESC().NULLS_LAST() 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/query/source.rs: -------------------------------------------------------------------------------- 1 | use query::TableName; 2 | use query::Select; 3 | use query::Function; 4 | use query::table_name::ToTableName; 5 | use table::Table; 6 | 7 | 8 | /// This fields can be used in the FROM field 9 | #[derive(Debug)] 10 | #[derive(Clone)] 11 | pub enum QuerySource { 12 | TableName(TableName), 13 | Query(Select), 14 | Function(Function), 15 | } 16 | 17 | /// QuerySource fields can be renamed 18 | #[derive(Debug)] 19 | #[derive(Clone)] 20 | pub struct SourceField { 21 | pub source: QuerySource, 22 | pub rename: Option, 23 | } 24 | 25 | 26 | pub trait ToSourceField { 27 | fn to_source_field(&self) -> Vec; 28 | } 29 | 30 | 31 | impl ToSourceField for &'static str { 32 | fn to_source_field(&self) -> Vec { 33 | let table_name = TableName::from_str(self); 34 | let qs = QuerySource::TableName(table_name); 35 | vec![SourceField{ 36 | source: qs, 37 | rename: None, 38 | }] 39 | } 40 | } 41 | 42 | impl ToSourceField for String { 43 | fn to_source_field(&self) -> Vec { 44 | let table_name = TableName::from_str(self); 45 | let qs = QuerySource::TableName(table_name); 46 | vec![SourceField{ 47 | source: qs, 48 | rename: None, 49 | }] 50 | } 51 | } 52 | impl ToSourceField for Table { 53 | fn to_source_field(&self) -> Vec { 54 | let table_name = self.to_table_name(); 55 | let qs = QuerySource::TableName(table_name); 56 | vec![SourceField{ 57 | source: qs, 58 | rename: None, 59 | }] 60 | } 61 | } 62 | 63 | impl ToSourceField for TableName { 64 | fn to_source_field(&self) -> Vec { 65 | let qs = QuerySource::TableName(self.to_owned()); 66 | vec![SourceField{ 67 | source: qs, 68 | rename: None, 69 | }] 70 | } 71 | } 72 | 73 | impl ToSourceField for QuerySource { 74 | fn to_source_field(&self) -> Vec { 75 | vec![ 76 | SourceField{ 77 | source: self.to_owned(), 78 | rename: None 79 | } 80 | ] 81 | } 82 | } 83 | 84 | macro_rules! impl_to_source_field_for_static_str{ 85 | ($x:expr) => ( 86 | impl ToSourceField for [&'static str;$x]{ 87 | fn to_source_field(&self)->Vec{ 88 | let mut sources = vec![]; 89 | for s in self{ 90 | let table_name = TableName::from_str(s); 91 | let qs = QuerySource::TableName(table_name); 92 | let sf = SourceField{ 93 | source: qs, 94 | rename: None 95 | }; 96 | sources.push(sf); 97 | } 98 | sources 99 | } 100 | } 101 | ); 102 | } 103 | 104 | impl_to_source_field_for_static_str!(1); 105 | impl_to_source_field_for_static_str!(2); 106 | impl_to_source_field_for_static_str!(3); 107 | 108 | impl ToSourceField for F 109 | where F: Fn() -> Table 110 | { 111 | fn to_source_field(&self) -> Vec { 112 | let table_name = self().to_table_name(); 113 | let qs = QuerySource::TableName(table_name); 114 | let sf = SourceField { 115 | source: qs, 116 | rename: None, 117 | }; 118 | vec![sf] 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/platform/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pool; 2 | #[cfg(feature = "postgres")] 3 | pub mod postgres; 4 | #[cfg(feature = "sqlite")] 5 | pub mod sqlite; 6 | #[cfg(feature = "mysql")] 7 | pub mod mysql; 8 | 9 | #[cfg(feature = "postgres")] 10 | pub use self::postgres::Postgres; 11 | #[cfg(feature = "sqlite")] 12 | pub use self::sqlite::Sqlite; 13 | #[cfg(feature = "mysql")] 14 | pub use self::mysql::Mysql; 15 | 16 | use std::error::Error; 17 | use std::fmt; 18 | use postgres::error::Error as PgError; 19 | use postgres::error::ConnectError as PgConnectError; 20 | #[cfg(feature = "mysql")] 21 | use mysql::error::MyError; 22 | #[cfg(feature = "sqlite")] 23 | use rusqlite::Error as SqliteError; 24 | 25 | #[derive(Debug)] 26 | pub enum PlatformError { 27 | PostgresError(PgError), 28 | PostgresConnectError(PgConnectError), 29 | #[cfg(feature = "mysql")] 30 | MySQLError(MyError), 31 | #[cfg(feature = "sqlite")] 32 | SqliteError(SqliteError), 33 | } 34 | 35 | impl Error for PlatformError { 36 | fn description(&self) -> &str { 37 | match *self { 38 | PlatformError::PostgresError(ref err) => err.description(), 39 | PlatformError::PostgresConnectError(ref err) => err.description(), 40 | #[cfg(feature = "mysql")] 41 | PlatformError::MySQLError(ref err) => err.description(), 42 | #[cfg(feature = "sqlite")] 43 | PlatformError::SqliteError(ref err) => err.description(), 44 | } 45 | } 46 | 47 | fn cause(&self) -> Option<&Error> { 48 | match *self { 49 | PlatformError::PostgresError(ref err) => Some(err), 50 | PlatformError::PostgresConnectError(ref err) => Some(err), 51 | #[cfg(feature = "mysql")] 52 | PlatformError::MySQLError(ref err) => Some(err), 53 | #[cfg(feature = "sqlite")] 54 | PlatformError::SqliteError(ref err) => Some(err), 55 | } 56 | } 57 | } 58 | 59 | impl fmt::Display for PlatformError { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | match *self { 62 | PlatformError::PostgresError(ref err) => write!(f, "PostgreSQL error: {}", err), 63 | PlatformError::PostgresConnectError(ref err) => { 64 | write!(f, "PostgreSQL connection error: {}", err) 65 | } 66 | #[cfg(feature = "mysql")] 67 | PlatformError::MySQLError(ref err) => write!(f, "MySQL error: {}", err), 68 | #[cfg(feature = "sqlite")] 69 | PlatformError::SqliteError(ref err) => write!(f, "SQlite error: {}", err), 70 | } 71 | } 72 | } 73 | 74 | impl From for PlatformError { 75 | fn from(err: PgError) -> Self { 76 | PlatformError::PostgresError(err) 77 | } 78 | } 79 | 80 | impl From for PlatformError { 81 | fn from(err: PgConnectError) -> Self { 82 | PlatformError::PostgresConnectError(err) 83 | } 84 | } 85 | 86 | #[cfg(feature = "mysql")] 87 | impl From for PlatformError { 88 | fn from(err: MyError) -> Self { 89 | PlatformError::MySQLError(err) 90 | } 91 | } 92 | 93 | #[cfg(feature = "sqlite")] 94 | impl From for PlatformError { 95 | fn from(err: SqliteError) -> Self { 96 | PlatformError::SqliteError(err) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/query/table_name.rs: -------------------------------------------------------------------------------- 1 | use query::ColumnName; 2 | use std::fmt; 3 | use table::Table; 4 | 5 | 6 | #[derive(Clone)] 7 | #[derive(Debug)] 8 | #[derive(Default)] 9 | pub struct TableName { 10 | pub schema: Option, 11 | pub name: String, 12 | /// optional columns needed when rename for conflicting columns are needed 13 | pub columns: Vec, 14 | } 15 | 16 | impl TableName { 17 | pub fn from_str(str: &str) -> Self { 18 | if str.contains(".") { 19 | let splinters = str.split(".").collect::>(); 20 | assert!(splinters.len() == 2, "There should only be 2 splinters"); 21 | let schema_split = splinters[0].to_owned(); 22 | let table_split = splinters[1].to_owned(); 23 | 24 | TableName { 25 | schema: Some(schema_split), 26 | name: table_split, 27 | columns: vec![], 28 | } 29 | 30 | } else { 31 | TableName { 32 | schema: None, 33 | name: str.to_owned(), 34 | columns: vec![], 35 | } 36 | } 37 | } 38 | 39 | pub fn complete_name(&self) -> String { 40 | match self.schema { 41 | Some(ref schema) => format!("{}.{}", schema, self.name), 42 | None => self.name.to_owned(), 43 | } 44 | } 45 | } 46 | 47 | impl PartialEq for TableName { 48 | fn eq(&self, other: &Self) -> bool { 49 | self.name == other.name && self.schema == other.schema 50 | } 51 | 52 | fn ne(&self, other: &Self) -> bool { 53 | self.name != other.name || self.schema != other.schema 54 | } 55 | } 56 | 57 | impl fmt::Display for TableName { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 59 | write!(f, "{}", self.complete_name()) 60 | } 61 | } 62 | 63 | /// convert str, IsTable to TableName 64 | pub trait ToTableName { 65 | fn to_table_name(&self) -> TableName; 66 | } 67 | 68 | pub trait IsTable{ 69 | fn table_name() -> TableName; 70 | } 71 | 72 | impl ToTableName for TableName{ 73 | fn to_table_name(&self) -> TableName{ 74 | self.clone() 75 | } 76 | } 77 | 78 | impl<'a> ToTableName for &'a str { 79 | fn to_table_name(&self) -> TableName { 80 | TableName::from_str(self) 81 | } 82 | } 83 | 84 | /* 85 | impl ToTableName for F 86 | where F: Fn() -> Table 87 | { 88 | fn to_table_name(&self) -> TableName { 89 | let table = self(); 90 | debug!("table: {:?}", table); 91 | table.to_table_name() 92 | } 93 | } 94 | */ 95 | 96 | impl ToTableName for Table { 97 | /// contain the columns for later use when renaming is necessary 98 | fn to_table_name(&self) -> TableName { 99 | let mut columns = vec![]; 100 | for c in &self.columns { 101 | let column_name = ColumnName { 102 | schema: self.schema.clone(), 103 | table: Some(self.name.to_owned()), 104 | column: c.name.to_owned(), 105 | }; 106 | columns.push(column_name); 107 | } 108 | TableName { 109 | schema: self.schema.clone(), 110 | name: self.name.to_owned(), 111 | columns: columns, 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## May 26, 2015 2 | * Dump a sample database content to the bazaar 3 | 4 | ## June 9, 2015 5 | * create an implementation fn from_dao(dao:Dao) for each model, this will be handy for converting records to rust objects 6 | 7 | ## June 12, 2015 8 | * Improve the implementation of table methods to 9 | get table references to have a unified logic 10 | 11 | `` 12 | 13 | fn get_references()->RefTable 14 | RefTable { 15 | table, 16 | is_has_one, 17 | is_has_many, 18 | is_direct, 19 | is_ext, 20 | } 21 | 22 | impl RefTable{ 23 | 24 | fn name(){ 25 | //checks to avoid conflicting columns 26 | //checks to see if conflicts to other has_ones, has_many, ext 27 | } 28 | } 29 | `` 30 | 31 | ## June 16, 2015 32 | * Make the query api with filter work 33 | 34 | ## June 30, 2015 35 | * Implement COPY from stdin 36 | http://sfackler.github.io/rust-postgres/doc/v0.9.2/postgres/struct.Statement.html#method.copy_in 37 | 38 | * Add support for sqlite 39 | https://github.com/jgallagher/rusqlite 40 | 41 | ## July 1, 2015 42 | * Support for deleting children on records that restrict deletion of referred records 43 | 44 | ## July 7, 2015 45 | * Use r2d2 connection pooling 46 | * use connection pooling for sqlite in memory database, such that only 1 instance of in-memory database with the same configuration will exist. 47 | 48 | ## July 17, 2015 49 | * Add support for from_hashmap for DAO 50 | * Add support for to_json for decoding the representation of the object when used as a primary object or an extension object 51 | * Extension object doesn't involve the both foreign and primary key values, null values etc. 52 | * Conver the object to hashmap, then remove the undesired column, then serialize fn serialize_compact()->String, fn concize_serialize() 53 | fn compact_hashmap()->HashMap; 54 | fn compact_json()->Json; 55 | to dao then serialize the json 56 | 57 | 58 | ## July 19, 2015 59 | 60 | ```rust 61 | 62 | impl ProductAvailability{ 63 | 64 | compact_json(&self)->Json{ 65 | let mut dao = self.to_dao(); 66 | dao.remove("product_id"); 67 | dao.encode() 68 | } 69 | } 70 | 71 | ``` 72 | 73 | ## July 21, 2015 74 | * Make compilation of underlying supported platform optional. Using the "feature" in Cargo.toml 75 | 76 | ## July 27, 2015 77 | 78 | * Use const, 79 | * add column module, list of all columns 80 | * add table module list of all tables 81 | * add schema modile list of all schema 82 | 83 | ## July 28, 2015 84 | 85 | * Support for views 86 | http://dba.stackexchange.com/questions/23836/how-to-list-all-views-in-sql-in-postgresql 87 | 88 | ```sql 89 | 90 | select schemaname, viewname from pg_catalog.pg_views 91 | where schemaname NOT IN ('pg_catalog', 'information_schema') 92 | order by schemaname, viewname; 93 | 94 | ``` 95 | 96 | ## September 9, 2015 97 | * Replace rustc_serialize with serde-rs 98 | 99 | 100 | ## October 21, 2015 101 | * Make mysql as optional dependency 102 | 103 | 104 | ## Macro 105 | 106 | #[derive(IsDao,IsTable)] 107 | pub struct Product{ 108 | #[column(rename="product_name")] 109 | name: String, 110 | #[render(image_base64)] 111 | base64: String, 112 | } 113 | 114 | 115 | 116 | 117 | ## April 12, 2016 118 | 119 | * Refactor DAO to alias only to type Dao = BTreeMap 120 | -------------------------------------------------------------------------------- /src/query/column_name.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use table::Column; 3 | 4 | #[derive(Clone)] 5 | #[derive(Debug)] 6 | #[derive(RustcEncodable, RustcDecodable)] 7 | pub struct ColumnName { 8 | pub column: String, 9 | pub table: Option, 10 | // //optional schema, if ever there are same tables resideing in different schema/namespace 11 | pub schema: Option, 12 | } 13 | 14 | 15 | impl >From for ColumnName{ 16 | 17 | fn from(s: S) -> Self { 18 | let column: String = s.into(); 19 | if column.contains(".") { 20 | let splinters = column.split(".").collect::>(); 21 | assert!(splinters.len() == 2, "There should only be 2 splinters"); 22 | let table_split = splinters[0].to_owned(); 23 | let column_split = splinters[1].to_owned(); 24 | ColumnName { 25 | column: column_split.to_owned(), 26 | table: Some(table_split.to_owned()), 27 | schema: None, 28 | } 29 | } else { 30 | ColumnName { 31 | column: column.to_owned(), 32 | table: None, 33 | schema: None, 34 | } 35 | } 36 | } 37 | } 38 | 39 | 40 | impl ColumnName { 41 | 42 | 43 | pub fn default_rename(&self) -> String { 44 | match self.table { 45 | Some(ref s) => format!("{}.{}", s, self.column), 46 | None => { 47 | panic!("Unable to rename {} since table is not specified", 48 | self.column) 49 | } 50 | } 51 | } 52 | 53 | /// table name and column name 54 | pub fn complete_name(&self) -> String { 55 | match self.table { 56 | Some(ref s) => format!("{}.{}", s, self.column), 57 | None => self.column.to_owned(), 58 | } 59 | } 60 | /// includes the schema, table name and column name 61 | pub fn super_complete_name(&self) -> String { 62 | match self.schema { 63 | Some(ref s) => format!("{}.{}", s, self.complete_name()), 64 | None => self.complete_name(), 65 | } 66 | } 67 | 68 | /// is this column conflicts the other column 69 | /// conflicts means, when used both in a SQL query, it will result to ambiguous columns 70 | pub fn is_conflicted(&self, other: &ColumnName) -> bool { 71 | self.column == other.column 72 | } 73 | } 74 | 75 | 76 | pub trait ToColumnName { 77 | fn to_column_name(&self) -> ColumnName; 78 | } 79 | 80 | impl ToColumnName for Column { 81 | fn to_column_name(&self) -> ColumnName { 82 | ColumnName { 83 | table: self.table.to_owned(), 84 | column: self.name.to_owned(), 85 | schema: None, 86 | } 87 | } 88 | } 89 | 90 | impl<'a> ToColumnName for &'a str { 91 | fn to_column_name(&self) -> ColumnName { 92 | ColumnName::from(*self) 93 | } 94 | } 95 | 96 | 97 | impl fmt::Display for ColumnName { 98 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 99 | write!(f, "{}", self.complete_name()) 100 | } 101 | } 102 | 103 | impl PartialEq for ColumnName { 104 | fn eq(&self, other: &Self) -> bool { 105 | self.column == other.column && self.table == other.table 106 | } 107 | 108 | fn ne(&self, other: &Self) -> bool { 109 | self.column != other.column || self.table != other.table || self.schema != other.schema 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/query/field.rs: -------------------------------------------------------------------------------- 1 | use query::operand::ToOperand; 2 | use query::source::{SourceField, QuerySource, ToSourceField}; 3 | use query::table_name::TableName; 4 | use query::operand::Operand; 5 | 6 | 7 | 8 | #[derive(Debug)] 9 | #[derive(Clone)] 10 | pub struct Field { 11 | /// the field 12 | pub operand: Operand, 13 | /// when renamed as field 14 | pub name: Option, 15 | } 16 | 17 | impl Field { 18 | pub fn rename(&self) -> Field { 19 | match self.operand { 20 | Operand::ColumnName(ref column_name) => { 21 | let rename = column_name.default_rename(); 22 | Field { 23 | operand: Operand::ColumnName(column_name.clone()), 24 | name: Some(rename), 25 | } 26 | } 27 | // renames should only be called on column names 28 | _ => unreachable!(), 29 | } 30 | } 31 | } 32 | 33 | pub trait ToField { 34 | fn to_field(&self) -> Vec; 35 | } 36 | 37 | 38 | 39 | pub trait Rename { 40 | fn AS(&self, s: &str) -> SourceField; 41 | } 42 | 43 | impl Rename for &'static str { 44 | fn AS(&self, s: &str) -> SourceField { 45 | let table_name = TableName::from_str(self); 46 | let qs = QuerySource::TableName(table_name); 47 | SourceField { 48 | source: qs, 49 | rename: Some(s.to_owned()), 50 | } 51 | } 52 | } 53 | 54 | impl ToSourceField for SourceField { 55 | fn to_source_field(&self) -> Vec { 56 | vec![self.to_owned()] 57 | } 58 | } 59 | 60 | 61 | // TODO use Iterator for implementing all other loopable types 62 | macro_rules! impl_to_source_field_for_source_field{ 63 | ($x:expr) => ( 64 | impl ToSourceField for [SourceField;$x]{ 65 | fn to_source_field(&self)->Vec{ 66 | let mut sources = vec![]; 67 | for s in self{ 68 | sources.push(s.to_owned()); 69 | } 70 | sources 71 | } 72 | } 73 | ); 74 | } 75 | 76 | impl_to_source_field_for_source_field!(1); 77 | impl_to_source_field_for_source_field!(2); 78 | impl_to_source_field_for_source_field!(3); 79 | impl_to_source_field_for_source_field!(4); 80 | impl_to_source_field_for_source_field!(5); 81 | impl_to_source_field_for_source_field!(6); 82 | impl_to_source_field_for_source_field!(7); 83 | impl_to_source_field_for_source_field!(8); 84 | impl_to_source_field_for_source_field!(9); 85 | impl_to_source_field_for_source_field!(10); 86 | impl_to_source_field_for_source_field!(11); 87 | impl_to_source_field_for_source_field!(12); 88 | 89 | 90 | impl ToField for Field { 91 | fn to_field(&self) -> Vec { 92 | vec![self.to_owned()] 93 | } 94 | } 95 | 96 | macro_rules! impl_to_field_for_field{ 97 | ($x:expr) => ( 98 | impl ToField for [Field;$x]{ 99 | fn to_field(&self)->Vec{ 100 | let mut fields = vec![]; 101 | for s in self{ 102 | fields.push(s.to_owned()) 103 | } 104 | fields 105 | } 106 | } 107 | ); 108 | } 109 | 110 | impl ToField for T 111 | where T: ToOperand 112 | { 113 | fn to_field(&self) -> Vec { 114 | let operand = self.to_operand(); 115 | vec![Field { 116 | operand: operand, 117 | name: None, 118 | }] 119 | } 120 | } 121 | 122 | impl Into for Operand{ 123 | 124 | fn into(self) -> Field{ 125 | Field { 126 | operand: self, 127 | name: None 128 | } 129 | } 130 | } 131 | 132 | impl_to_field_for_field!(1); 133 | impl_to_field_for_field!(2); 134 | impl_to_field_for_field!(3); 135 | impl_to_field_for_field!(4); 136 | impl_to_field_for_field!(5); 137 | impl_to_field_for_field!(6); 138 | impl_to_field_for_field!(7); 139 | impl_to_field_for_field!(8); 140 | impl_to_field_for_field!(9); 141 | impl_to_field_for_field!(10); 142 | impl_to_field_for_field!(11); 143 | impl_to_field_for_field!(12); 144 | -------------------------------------------------------------------------------- /src/entity.rs: -------------------------------------------------------------------------------- 1 | use database::{Database, DbError}; 2 | use query::IsTable; 3 | use dao::IsDao; 4 | use query::Select; 5 | use query::Filter; 6 | use query::Delete; 7 | use query::Insert; 8 | use query::Update; 9 | use dao::Value; 10 | 11 | /// A higher level API for manipulating objects in the database 12 | /// This serves as a helper function for the query api 13 | pub struct EntityManager<'a> { 14 | pub db: &'a Database, 15 | } 16 | 17 | impl <'a>EntityManager<'a> { 18 | 19 | /// Create an entity manager with the database connection provided 20 | pub fn new(db: &'a Database) -> Self { 21 | EntityManager { db: db } 22 | } 23 | 24 | /// delete records of the table that passes the provided filter 25 | pub fn delete(&self, filter: &Filter) -> Result 26 | where T:IsTable{ 27 | let table_name = T::table_name(); 28 | let mut query = Delete::from(&table_name); 29 | query.add_filter(filter); 30 | query.execute(self.db) 31 | } 32 | 33 | /// get all the records of this table 34 | pub fn get_all(&self) -> Result, DbError> 35 | where T: IsTable + IsDao { 36 | let table = T::table_name(); 37 | let mut q = Select::all(); 38 | q.from(&table); 39 | q.collect(self.db) 40 | } 41 | 42 | 43 | 44 | /// get all the records on this table which passed thru the filters 45 | /// any query that specified more than the parameters should use the query api 46 | pub fn get_all_with_filter(&self, filter: &Filter) -> Result, DbError> 47 | where T: IsTable + IsDao { 48 | let table = T::table_name(); 49 | let mut q = Select::all(); 50 | q.from(&table); 51 | q.add_filter(filter); 52 | q.collect(self.db) 53 | } 54 | 55 | /// get the first records of this table that passed thru the filter 56 | pub fn get_one(&self, filter: &Filter) -> Result 57 | where T: IsTable + IsDao { 58 | let table = T::table_name(); 59 | let mut q = Select::all(); 60 | q.from(&table); 61 | q.add_filter(filter); 62 | q.collect_one(self.db) 63 | } 64 | 65 | 66 | /// insert a record into the database 67 | pub fn insert(&self, t: &T) -> Result 68 | where T: IsTable + IsDao, D: IsDao 69 | { 70 | let table = T::table_name(); 71 | let dao = t.to_dao(); 72 | let mut q = Insert::into(&table); 73 | for c in &table.columns { 74 | q.column(&c.column); 75 | } 76 | q.return_all(); 77 | for c in &table.columns { 78 | let value:Option<&Value> = dao.get(&c.column); 79 | match value { 80 | Some(value) => { 81 | q.value(value); 82 | } 83 | None => (), 84 | } 85 | } 86 | q.insert(self.db) 87 | } 88 | 89 | /// starts a database transaction 90 | /// the next succedding function calls will be 91 | /// wrapped in a transaction and will not effect the database 92 | /// until the commit at the end is called 93 | pub fn begin(&self) { 94 | self.db.begin() 95 | } 96 | 97 | /// commits a database transaction 98 | pub fn commit(&self) { 99 | self.db.commit() 100 | } 101 | /// when there is a problem with the transaction process, this can be called 102 | pub fn rollback(&self) { 103 | self.db.rollback() 104 | } 105 | 106 | 107 | 108 | /// update the Dao with filter, return the updated Dao 109 | pub fn update_with_filter(&self, t: &T, filter: Filter) -> Result 110 | where T: IsTable + IsDao, D: IsTable + IsDao { 111 | 112 | let table = T::table_name(); 113 | let dao = t.to_dao(); 114 | 115 | let mut query = Update::table(&table); 116 | query.columns(&table.columns); 117 | for c in &table.columns{ 118 | let v = dao.get(&c.column); 119 | match v{ 120 | Some(v) => { 121 | query.value(v); 122 | }, 123 | None => (), 124 | } 125 | } 126 | query.add_filter(&filter); 127 | query.return_all(); 128 | query.update(self.db) 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | use dao::Value; 2 | use database::{SqlOption, BuildMode}; 3 | use std::fmt; 4 | 5 | /// sql fragment 6 | /// use this for writing SQL statements 7 | pub struct SqlFrag { 8 | pub sql: String, 9 | pub params: Vec, 10 | pub sql_options: Vec, 11 | pub build_mode: BuildMode, 12 | } 13 | 14 | impl fmt::Display for SqlFrag { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | try!(write!(f, "{}", self.sql)); 17 | let mut do_comma = false; 18 | try!(write!(f, "[")); 19 | for param in &self.params { 20 | if do_comma { 21 | try!(write!(f, ", ")); 22 | } else { 23 | do_comma = true; 24 | } 25 | try!(write!(f, "{}", param)); 26 | } 27 | write!(f, "]") 28 | } 29 | } 30 | 31 | impl SqlFrag { 32 | #[inline] 33 | pub fn new(sql_options: Vec, build_mode: &BuildMode) -> Self { 34 | SqlFrag { 35 | sql: String::new(), 36 | params: vec![], 37 | sql_options: sql_options, 38 | build_mode: build_mode.clone(), 39 | } 40 | } 41 | 42 | #[inline] 43 | pub fn append(&mut self, str: &str) -> &mut Self { 44 | self.sql.push_str(str); 45 | self 46 | } 47 | 48 | #[inline] 49 | pub fn appendln(&mut self, str: &str) -> &mut Self { 50 | self.append(str); 51 | self.ln() 52 | } 53 | 54 | #[inline] 55 | pub fn tab(&mut self) -> &mut Self { 56 | self.append(" ") 57 | } 58 | #[inline] 59 | pub fn tabs(&mut self, n: u32) -> &mut Self { 60 | for _ in 0..n { 61 | self.tab(); 62 | } 63 | self 64 | } 65 | #[inline] 66 | pub fn ln(&mut self) -> &mut Self { 67 | self.append("\n") 68 | } 69 | #[inline] 70 | pub fn ln_tab(&mut self) -> &mut Self { 71 | self.ln(); 72 | self.tab() 73 | } 74 | #[inline] 75 | pub fn ln_tabs(&mut self, n: u32) -> &mut Self { 76 | self.ln(); 77 | self.tabs(n) 78 | } 79 | #[inline] 80 | pub fn comma(&mut self) -> &mut Self { 81 | self.append(",") 82 | } 83 | #[inline] 84 | pub fn sp(&mut self) -> &mut Self { 85 | self.append(" ") 86 | } 87 | #[inline] 88 | pub fn spaces(&mut self, n: i32) -> &mut Self { 89 | for _ in 0..n { 90 | self.sp(); 91 | } 92 | self 93 | } 94 | /// river is the line in the SQL statment which makes it more readable 95 | /// * http://www.sqlstyle.guide/ 96 | /// river size is 9, `RETURNING` 97 | #[inline] 98 | fn river(&mut self, str: &str) -> &mut Self { 99 | let river_size: i32 = 9; 100 | let trim = str.trim(); 101 | let diff: i32 = river_size - trim.len() as i32; 102 | if diff > 0 { 103 | self.spaces(diff); 104 | } 105 | self.append(trim); 106 | self.sp() 107 | } 108 | /// write the string, aligning to the left side of the middle space (river) 109 | #[inline] 110 | pub fn left_river(&mut self, str: &str) -> &mut Self { 111 | self.ln(); 112 | self.river(str) 113 | } 114 | /// write the string, aligning to the right side of the middle space (river), leaving the left with empty string 115 | #[inline] 116 | pub fn right_river(&mut self, str: &str) -> &mut Self { 117 | self.ln(); 118 | self.river(""); 119 | self.append(str) 120 | } 121 | 122 | #[inline] 123 | pub fn commasp(&mut self) -> &mut Self { 124 | self.comma().sp() 125 | } 126 | 127 | #[inline] 128 | pub fn comment(&mut self, comment: &str) -> &mut Self { 129 | self.append("-- "); 130 | self.append(comment) 131 | } 132 | /// append parameter including the needed sql keywords 133 | pub fn parameter(&mut self, param: Value) { 134 | match self.build_mode { 135 | BuildMode::Standard => { 136 | self.params.push(param); 137 | if self.sql_options.contains(&SqlOption::UsesNumberedParam) { 138 | let numbered_param = format!("${} ", self.params.len()); 139 | self.append(&numbered_param); 140 | } else if self.sql_options.contains(&SqlOption::UsesQuestionMark) { 141 | self.append("?"); 142 | } 143 | } 144 | BuildMode::Debug => { 145 | // use fmt::Display 146 | self.append(&format!("{}",¶m)); 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/query/filter.rs: -------------------------------------------------------------------------------- 1 | use query::Operand; 2 | use dao::ToValue; 3 | use query::{ToColumnName}; 4 | use query::operand::ToOperand; 5 | 6 | /// expression has left operand, 7 | /// equality and right operand 8 | #[derive(Debug)] 9 | #[derive(Clone)] 10 | pub struct Condition { 11 | pub left: Operand, 12 | pub equality: Equality, 13 | pub right: Operand, 14 | } 15 | /// 16 | /// Filter struct merged to query 17 | /// 18 | #[derive(Debug)] 19 | #[derive(Clone)] 20 | pub enum Connector { 21 | And, 22 | Or, 23 | } 24 | 25 | #[derive(Debug)] 26 | #[derive(Clone)] 27 | #[allow(non_camel_case_types)] 28 | pub enum Equality { 29 | EQ, // EQUAL, 30 | NEQ, // NOT_EQUAL, 31 | LT, // LESS_THAN, 32 | LTE, // LESS_THAN_OR_EQUAL, 33 | GT, // GREATER_THAN, 34 | GTE, // GREATER_THAN_OR_EQUAL, 35 | IN, 36 | NOT_IN, // NOT_IN, 37 | LIKE, 38 | ILIKE, // add ILIKE 39 | IS_NOT_NULL, // NOT_NULL, 40 | IS_NULL, // IS_NULL, 41 | } 42 | 43 | #[derive(Debug)] 44 | #[derive(Clone)] 45 | pub struct Filter { 46 | pub connector: Connector, 47 | pub condition: Condition, 48 | pub sub_filters: Vec, 49 | } 50 | 51 | impl Filter { 52 | /// user friendly, commonly use API 53 | pub fn new(column: &str, equality: Equality, value: &ToValue) -> Self { 54 | let right = Operand::Value(value.to_db_type()); 55 | Filter { 56 | connector: Connector::And, 57 | condition: Condition { 58 | left: Operand::ColumnName(column.to_column_name()), 59 | equality: equality, 60 | right: right, 61 | }, 62 | sub_filters: vec![], 63 | } 64 | } 65 | 66 | 67 | pub fn and(mut self, filter: Filter) -> Self { 68 | let mut filter = filter.clone(); 69 | filter.connector = Connector::And; 70 | self.sub_filters.push(filter); 71 | self 72 | } 73 | pub fn or(mut self, filter: Filter) -> Self { 74 | let mut filter = filter.clone(); 75 | filter.connector = Connector::Or; 76 | self.sub_filters.push(filter); 77 | self 78 | } 79 | } 80 | 81 | 82 | 83 | pub trait HasEquality { 84 | fn EQ(&self, to_operand: &ToOperand) -> Filter; 85 | fn NEQ(&self, to_operand: &ToOperand) -> Filter; 86 | fn GT(&self, to_operand: &ToOperand) -> Filter; 87 | fn GTE(&self, to_operand: &ToOperand) -> Filter; 88 | fn LT(&self, to_operand: &ToOperand) -> Filter; 89 | fn LTE(&self, to_operand: &ToOperand) -> Filter; 90 | fn LIKE(&self, to_value: &ToValue) -> Filter; 91 | fn ILIKE(&self, to_value: &ToValue) -> Filter; 92 | fn IS_NULL(&self) -> Filter; 93 | fn IS_NOT_NULL(&self) -> Filter; 94 | fn IN(&self, to_operand: &ToOperand) -> Filter; 95 | fn NOT_IN(&self, to_operand: &ToOperand) -> Filter; 96 | } 97 | 98 | macro_rules! fn_has_equality_operand{ 99 | ($f:ident, $eq:expr) => ( 100 | fn $f(&self, to_operand: &ToOperand)->Filter{ 101 | let cond = Condition{ 102 | left: self.to_operand(), 103 | equality: $eq, 104 | right: to_operand.to_operand() 105 | }; 106 | Filter{ 107 | connector: Connector::And, 108 | condition:cond, 109 | sub_filters: vec![] 110 | } 111 | } 112 | ) 113 | } 114 | 115 | macro_rules! fn_has_equality_to_value{ 116 | ($f:ident, $eq:expr) => ( 117 | fn $f(&self, to_value: &ToValue)->Filter{ 118 | let cond = Condition{ 119 | left: self.to_operand(), 120 | equality: $eq, 121 | right: Operand::Value(to_value.to_db_type()) 122 | }; 123 | Filter{ 124 | connector: Connector::And, 125 | condition:cond, 126 | sub_filters: vec![] 127 | } 128 | } 129 | ) 130 | } 131 | 132 | macro_rules! fn_has_equality_nulls{ 133 | ($f:ident, $eq: expr) => ( 134 | fn $f(&self)->Filter{ 135 | let cond = Condition{ 136 | left: self.to_operand(), 137 | equality: $eq, 138 | right: Operand::None, 139 | }; 140 | Filter{ 141 | connector: Connector::And, 142 | condition:cond, 143 | sub_filters: vec![] 144 | } 145 | } 146 | ) 147 | } 148 | 149 | /// implementation of HasEquality for objects that can yield Operand 150 | impl HasEquality for T 151 | where T: ToOperand 152 | { 153 | fn_has_equality_operand!(EQ, Equality::EQ); 154 | fn_has_equality_operand!(NEQ, Equality::NEQ); 155 | fn_has_equality_operand!(GT, Equality::GT); 156 | fn_has_equality_operand!(GTE, Equality::GTE); 157 | fn_has_equality_operand!(LT, Equality::LT); 158 | fn_has_equality_operand!(LTE, Equality::LTE); 159 | fn_has_equality_operand!(IN, Equality::IN); 160 | fn_has_equality_operand!(NOT_IN, Equality::NOT_IN); 161 | fn_has_equality_to_value!(LIKE, Equality::LIKE); 162 | fn_has_equality_to_value!(ILIKE, Equality::ILIKE); 163 | fn_has_equality_nulls!(IS_NULL, Equality::IS_NULL); 164 | fn_has_equality_nulls!(IS_NOT_NULL, Equality::IS_NOT_NULL); 165 | } 166 | -------------------------------------------------------------------------------- /src/query/operand.rs: -------------------------------------------------------------------------------- 1 | use query::ColumnName; 2 | use dao::Value; 3 | use table::Column; 4 | use dao::ToValue; 5 | use query::source::QuerySource; 6 | use query::column_name::ToColumnName; 7 | use uuid::Uuid; 8 | use chrono::datetime::DateTime; 9 | use rustc_serialize::json::Json; 10 | use chrono::offset::fixed::FixedOffset; 11 | 12 | pub trait ToOperand { 13 | fn to_operand(&self) -> Operand; 14 | } 15 | 16 | 17 | /// Operands can be columns, values, and query sources such as tables, functions, and other queries 18 | #[derive(Debug)] 19 | #[derive(Clone)] 20 | pub enum Operand { 21 | ColumnName(ColumnName), 22 | QuerySource(QuerySource), 23 | Value(Value), 24 | Vec(Vec), 25 | None, 26 | } 27 | /// work around for &ToOperand argument for Operand 28 | impl ToOperand for Operand { 29 | fn to_operand(&self) -> Operand { 30 | self.to_owned() 31 | } 32 | } 33 | 34 | impl ToOperand for Value{ 35 | 36 | fn to_operand(&self) -> Operand { 37 | Operand::Value(self.to_owned()) 38 | } 39 | } 40 | 41 | 42 | /// implementation to convert Function that returns Column to yield an Operand 43 | macro_rules! impl_to_operand_for_fn_column{ 44 | ($x:expr) => { 45 | impl ToOperand for [F;$x] where F:Fn()->Column{ 46 | fn to_operand(&self)->Operand{ 47 | let mut operands = vec![]; 48 | for c in self{ 49 | let column = c(); 50 | let operand = Operand::ColumnName(column.to_column_name()); 51 | operands.push(operand); 52 | } 53 | Operand::Vec(operands) 54 | } 55 | } 56 | } 57 | } 58 | 59 | 60 | impl_to_operand_for_fn_column!(1); 61 | impl_to_operand_for_fn_column!(2); 62 | impl_to_operand_for_fn_column!(3); 63 | impl_to_operand_for_fn_column!(4); 64 | impl_to_operand_for_fn_column!(5); 65 | impl_to_operand_for_fn_column!(6); 66 | impl_to_operand_for_fn_column!(7); 67 | impl_to_operand_for_fn_column!(8); 68 | impl_to_operand_for_fn_column!(9); 69 | impl_to_operand_for_fn_column!(10); 70 | impl_to_operand_for_fn_column!(11); 71 | impl_to_operand_for_fn_column!(12); 72 | 73 | 74 | 75 | impl ToOperand for [&'static str; 1] { 76 | fn to_operand(&self) -> Operand { 77 | Operand::ColumnName(self[0].to_column_name()) 78 | } 79 | } 80 | 81 | macro_rules! impl_to_operand_for_static_str_array{ 82 | ($x:expr) => ( 83 | impl ToOperand for [&'static str;$x]{ 84 | fn to_operand(&self)->Operand{ 85 | let mut operands = vec![]; 86 | for s in self{ 87 | let col = s.to_column_name(); 88 | operands.push(Operand::ColumnName(col)); 89 | } 90 | Operand::Vec(operands) 91 | } 92 | } 93 | ); 94 | } 95 | 96 | impl_to_operand_for_static_str_array!(2); 97 | impl_to_operand_for_static_str_array!(3); 98 | impl_to_operand_for_static_str_array!(4); 99 | impl_to_operand_for_static_str_array!(5); 100 | impl_to_operand_for_static_str_array!(6); 101 | impl_to_operand_for_static_str_array!(7); 102 | impl_to_operand_for_static_str_array!(8); 103 | impl_to_operand_for_static_str_array!(9); 104 | impl_to_operand_for_static_str_array!(10); 105 | impl_to_operand_for_static_str_array!(11); 106 | impl_to_operand_for_static_str_array!(12); 107 | 108 | // TODO: Determine why does conflict to impl ToOperand for Fn()->Column 109 | // 110 | // impl ToOperand for T where T:ToValue{ 111 | // fn to_operand(&self)->Operand{ 112 | // Operand::Value(self.to_db_type()) 113 | // } 114 | // } 115 | // 116 | 117 | 118 | 119 | 120 | /// static str is threaded as column 121 | // all other types are values 122 | 123 | // Note: &'static str is treated as Column 124 | impl ToOperand for &'static str { 125 | fn to_operand(&self) -> Operand { 126 | Operand::ColumnName(self.to_column_name()) 127 | } 128 | } 129 | 130 | /// String is treated as Value::String 131 | impl ToOperand for String { 132 | fn to_operand(&self) -> Operand { 133 | Operand::Value(Value::String(self.to_owned())) 134 | } 135 | } 136 | 137 | impl ToOperand for Json { 138 | fn to_operand(&self) -> Operand { 139 | Operand::Value(self.to_db_type()) 140 | } 141 | } 142 | 143 | /// A workaround for the conflicts in ToOperand for 144 | 145 | macro_rules! impl_to_operand_for_to_value{ 146 | ($t:ty, $i:ident) => ( 147 | impl ToOperand for $t{ 148 | fn to_operand(&self)->Operand{ 149 | Operand::Value(Value::$i(self.to_owned())) 150 | } 151 | } 152 | 153 | ); 154 | } 155 | 156 | impl_to_operand_for_to_value!(bool, Bool); 157 | impl_to_operand_for_to_value!(i8, I8); 158 | impl_to_operand_for_to_value!(i16, I16); 159 | impl_to_operand_for_to_value!(i32, I32); 160 | impl_to_operand_for_to_value!(i64, I64); 161 | impl_to_operand_for_to_value!(u8, U8); 162 | impl_to_operand_for_to_value!(u16, U16); 163 | impl_to_operand_for_to_value!(u32, U32); 164 | impl_to_operand_for_to_value!(u64, U64); 165 | impl_to_operand_for_to_value!(f32, F32); 166 | impl_to_operand_for_to_value!(f64, F64); 167 | impl_to_operand_for_to_value!(Vec, VecU8); 168 | impl_to_operand_for_to_value!(Uuid, Uuid); 169 | impl_to_operand_for_to_value!(DateTime, DateTime); 170 | -------------------------------------------------------------------------------- /rustorm-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | extern crate proc_macro; 3 | extern crate syn; 4 | #[macro_use] 5 | extern crate quote; 6 | 7 | use proc_macro::TokenStream; 8 | use syn::MetaItem::*; 9 | 10 | #[proc_macro_derive(IsDao)] 11 | pub fn is_dao(input: TokenStream) -> TokenStream { 12 | // Construct a string representation of the type definition 13 | let s = input.to_string(); 14 | 15 | // Parse the string representation 16 | let ast = syn::parse_macro_input(&s).unwrap(); 17 | 18 | // Build the impl 19 | let gen = impl_is_dao(&ast); 20 | 21 | // Return the generated impl 22 | gen.parse().unwrap() 23 | } 24 | 25 | fn impl_is_dao(ast: &syn::MacroInput) -> quote::Tokens { 26 | let name = &ast.ident; 27 | let fields:Vec<(&syn::Ident, &syn::Ty)> = match ast.body { 28 | syn::Body::Struct(ref data) => { 29 | match *data{ 30 | syn::VariantData::Struct(ref fields) => { 31 | fields.iter().map(|f| { 32 | let ident = f.ident.as_ref().unwrap(); 33 | let ty = &f.ty; 34 | (ident,ty) 35 | }).collect::>() 36 | }, 37 | _ => panic!("tuples and unit are not covered") 38 | } 39 | }, 40 | syn::Body::Enum(_) => panic!("#[derive(NumFields)] can only be used with structs"), 41 | }; 42 | let from_fields:Vec = 43 | fields.iter().map(|&(field,_ty)| { 44 | quote!{ 45 | #field: { 46 | let v = dao.get(stringify!(#field)).unwrap(); 47 | FromValue::from_type(v.to_owned()) 48 | }, 49 | } 50 | }).collect::>(); 51 | 52 | let to_dao:Vec = 53 | fields.iter().map(|&(field,_ty)| { 54 | quote!{ 55 | dao.insert(stringify!(#field).to_string(), self.#field.to_db_type()); 56 | } 57 | }).collect::>(); 58 | quote! { 59 | impl IsDao for #name { 60 | 61 | fn from_dao(dao: &Dao) -> Self{ 62 | #name{ 63 | #(#from_fields)* 64 | } 65 | } 66 | 67 | fn to_dao(&self) -> Dao { 68 | let mut dao = Dao::new(); 69 | #(#to_dao)* 70 | dao 71 | } 72 | } 73 | } 74 | } 75 | 76 | #[proc_macro_derive(IsTable)] 77 | pub fn to_table_name(input: TokenStream) -> TokenStream { 78 | // Construct a string representation of the type definition 79 | let s = input.to_string(); 80 | 81 | // Parse the string representation 82 | let ast = syn::parse_macro_input(&s).unwrap(); 83 | 84 | // Build the impl 85 | let gen = impl_to_table_name(&ast); 86 | 87 | // Return the generated impl 88 | gen.parse().unwrap() 89 | } 90 | 91 | fn get_table_attr(attrs: &Vec)->Option{ 92 | for att in attrs{ 93 | println!("{:?}", att); 94 | match att.value{ 95 | Word(_) => continue, 96 | List(_,_) => continue, 97 | NameValue(ref name, ref value) => { 98 | if name == "table"{ 99 | match *value{ 100 | syn::Lit::Str(ref s,ref _style) => { 101 | return Some(s.to_owned()) 102 | } 103 | _ => continue 104 | } 105 | }else{continue} 106 | } 107 | }; 108 | } 109 | None 110 | } 111 | 112 | fn impl_to_table_name(ast: &syn::MacroInput) -> quote::Tokens { 113 | let name = &ast.ident; 114 | let attrs = &ast.attrs; 115 | let tbl = get_table_attr(attrs); 116 | let table_name = match tbl{ 117 | Some(tbl) => tbl, 118 | None => format!("{}",name).to_lowercase() 119 | }; 120 | let fields:Vec<&syn::Ident> = match ast.body { 121 | syn::Body::Struct(ref data) => { 122 | match *data{ 123 | syn::VariantData::Struct(ref fields) => { 124 | fields.iter().map(|f| { 125 | let ident = f.ident.as_ref().unwrap(); 126 | let _ty = &f.ty; 127 | ident 128 | }).collect::>() 129 | }, 130 | _ => panic!("tuples and unit are not covered") 131 | } 132 | }, 133 | syn::Body::Enum(_) => panic!("#[derive(NumFields)] can only be used with structs"), 134 | }; 135 | let from_fields:Vec = 136 | fields.iter().map(|field| { 137 | quote!{ 138 | ColumnName{ 139 | column: stringify!(#field).to_string(), 140 | table: Some(#table_name.to_string()), 141 | schema: None 142 | } 143 | } 144 | }).collect::>(); 145 | 146 | quote! { 147 | impl IsTable for #name { 148 | 149 | fn table_name() -> TableName{ 150 | TableName{ 151 | schema: None, 152 | name: #table_name.to_string(), 153 | columns: vec![#(#from_fields),*], 154 | } 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use url::{Url}; 2 | 3 | 4 | 5 | #[derive(Debug)] 6 | #[derive(PartialEq)] 7 | #[derive(Clone)] 8 | pub struct DbConfig { 9 | /// postgres, sqlite, mysql 10 | /// some fields are optional since sqlite is not applicable for those 11 | pub platform: String, 12 | pub username: Option, 13 | pub password: Option, 14 | /// localhost 15 | pub host: Option, 16 | /// 5432 17 | pub port: Option, 18 | pub database: String, 19 | pub ssl: bool, 20 | } 21 | 22 | impl DbConfig { 23 | /// TODO: get rid of the hacky way parsing database url 24 | /// https://github.com/servo/rust-url/issues/40 25 | pub fn from_url(url: &str) -> Option { 26 | let parsed = Url::parse(url); 27 | match parsed { 28 | Ok(parsed) => { 29 | Some(DbConfig { 30 | platform: parsed.scheme().to_owned(), 31 | username: { 32 | let username = parsed.username(); 33 | if username.is_empty() { 34 | None 35 | } else { 36 | Some(username.to_owned()) 37 | } 38 | }, 39 | password: parsed.password().map(|s| s.to_owned()), 40 | host: { 41 | let host_str = parsed.host_str(); 42 | match host_str { 43 | Some(ref host_str) => { 44 | if host_str.is_empty() { 45 | None 46 | } else { 47 | Some(host_str.to_string()) 48 | } 49 | } 50 | None => None, 51 | } 52 | }, 53 | port: parsed.port(), 54 | database: parsed.path().to_string().trim_left_matches("/").to_owned(), 55 | ssl: false, 56 | }) 57 | } 58 | Err(_) => None, 59 | } 60 | } 61 | 62 | pub fn get_url(&self) -> String { 63 | let mut url = String::new(); 64 | url.push_str(&self.platform.to_owned()); 65 | url.push_str("://"); 66 | 67 | if let Some(ref username) = self.username { 68 | url.push_str(username); 69 | } 70 | 71 | if let Some(ref password) = self.password { 72 | url.push_str(":"); 73 | url.push_str(password); 74 | } 75 | 76 | if let Some(ref host) = self.host { 77 | url.push_str("@"); 78 | url.push_str(&host); 79 | } 80 | 81 | if let Some(ref port) = self.port { 82 | url.push_str(":"); 83 | url.push_str(&format!("{}", port)); 84 | } 85 | 86 | url.push_str("/"); 87 | url.push_str(&self.database); 88 | url 89 | } 90 | } 91 | 92 | #[test] 93 | fn test_config_url() { 94 | let url = "postgres://postgres:p0stgr3s@localhost/bazaar_v8"; 95 | let config = DbConfig { 96 | platform: "postgres".to_owned(), 97 | username: Some("postgres".to_owned()), 98 | password: Some("p0stgr3s".to_owned()), 99 | host: Some("localhost".to_owned()), 100 | port: None, 101 | ssl: false, 102 | database: "bazaar_v8".to_owned(), 103 | }; 104 | 105 | assert_eq!(config.get_url(), url.to_owned()); 106 | } 107 | 108 | #[test] 109 | fn test_config_from_url() { 110 | let url = "postgres://postgres:p0stgr3s@localhost/bazaar_v8"; 111 | let config = DbConfig::from_url(url).unwrap(); 112 | assert_eq!(config.get_url(), url.to_owned()); 113 | } 114 | 115 | 116 | #[test] 117 | fn test_config_url_with_port() { 118 | let url = "postgres://postgres:p0stgr3s@localhost:5432/bazaar_v8"; 119 | let config = DbConfig { 120 | platform: "postgres".to_owned(), 121 | username: Some("postgres".to_owned()), 122 | password: Some("p0stgr3s".to_owned()), 123 | host: Some("localhost".to_owned()), 124 | port: Some(5432), 125 | database: "bazaar_v8".to_owned(), 126 | ssl: false, 127 | }; 128 | 129 | assert_eq!(config.get_url(), url.to_owned()); 130 | } 131 | 132 | #[test] 133 | fn test_config_sqlite_url_with_port() { 134 | let url = "sqlite:///bazaar_v8.db"; 135 | let parsed_config = DbConfig::from_url(url).unwrap(); 136 | let expected_config = DbConfig { 137 | platform: "sqlite".to_owned(), 138 | username: None, 139 | password: None, 140 | host: None, 141 | port: None, 142 | database: "bazaar_v8.db".to_owned(), 143 | ssl: false, 144 | }; 145 | println!("{:?}", parsed_config); 146 | assert_eq!(parsed_config, expected_config); 147 | } 148 | 149 | 150 | #[test] 151 | fn test_config_sqlite_url_with_path() { 152 | let url = "sqlite:///home/some/path/file.db"; 153 | let parsed_config = DbConfig::from_url(url).unwrap(); 154 | let expected_config = DbConfig { 155 | platform: "sqlite".to_owned(), 156 | username: None, 157 | password: None, 158 | host: None, 159 | port: None, 160 | database: "home/some/path/file.db".to_owned(), 161 | ssl: false, 162 | }; 163 | println!("{:?}", parsed_config); 164 | assert_eq!(parsed_config, expected_config); 165 | } 166 | 167 | 168 | #[test] 169 | fn sqlite_in_memory() { 170 | let url = "sqlite:///:memory:"; 171 | let parsed_config = DbConfig::from_url(url).unwrap(); 172 | let expected_config = DbConfig { 173 | platform: "sqlite".to_owned(), 174 | username: None, 175 | password: None, 176 | host: None, 177 | port: None, 178 | database: ":memory:".to_owned(), 179 | ssl: false, 180 | }; 181 | println!("{:?}", parsed_config); 182 | assert_eq!(parsed_config, expected_config); 183 | } 184 | -------------------------------------------------------------------------------- /Notes.md: -------------------------------------------------------------------------------- 1 | How to properly construct builder pattern in rust 2 | 3 | https://aturon.github.io/ownership/builders.html 4 | 5 | 6 | 7 | 8 | ##Postgres specific foreign key 9 | * This is needed because information_schema is a lot slower 10 | 11 | http://stackoverflow.com/questions/1152260/postgres-sql-to-list-table-foreign-keys 12 | 13 | ```sql 14 | SELECT 15 | o.conname AS constraint_name, 16 | (SELECT nspname FROM pg_namespace WHERE oid=m.relnamespace) AS source_schema, 17 | m.relname AS source_table, 18 | (SELECT a.attname FROM pg_attribute a WHERE a.attrelid = m.oid AND a.attnum = o.conkey[1] AND a.attisdropped = false) AS source_column, 19 | (SELECT nspname FROM pg_namespace WHERE oid=f.relnamespace) AS target_schema, 20 | f.relname AS target_table, 21 | (SELECT a.attname FROM pg_attribute a WHERE a.attrelid = f.oid AND a.attnum = o.confkey[1] AND a.attisdropped = false) AS target_column 22 | FROM 23 | pg_constraint o LEFT JOIN pg_class c ON c.oid = o.conrelid 24 | LEFT JOIN pg_class f ON f.oid = o.confrelid LEFT JOIN pg_class m ON m.oid = o.conrelid 25 | WHERE 26 | o.contype = 'f' AND o.conrelid IN (SELECT oid FROM pg_class c WHERE c.relkind = 'r'); 27 | 28 | ``` 29 | 30 | ##Show tables in postgresql 31 | 32 | ```sql 33 | select 34 | tablename as table 35 | from 36 | pg_tables 37 | where schemaname = 'public' 38 | ``` 39 | 40 | 41 | A simple Good resource for reading about lifetimes 42 | http://hermanradtke.com/2015/05/03/string-vs-str-in-rust-functions.html 43 | 44 | 45 | ##Select the parent table 46 | ```sql 47 | select relname, 48 | ( select relname from pg_class where oid = pg_inherits.inhparent ) as parent 49 | from pg_class 50 | left join pg_inherits 51 | on pg_class.oid = pg_inherits.inhrelid 52 | where relname = 'product' 53 | ``` 54 | 55 | 56 | ##Select the subclass 57 | 58 | ```sql 59 | select relname, 60 | ( select relname from pg_class where oid = pg_inherits.inhrelid ) as subclass 61 | from pg_inherits 62 | left join pg_class on pg_class.oid = pg_inherits.inhparent 63 | where relname = 'base' ; 64 | ``` 65 | 66 | 67 | ## Useful projects: 68 | 69 | ### text editor 70 | https://github.com/gchp/iota 71 | 72 | ## csv parsing 73 | https://github.com/BurntSushi/rust-csv 74 | 75 | ## window tiling for the win 76 | https://github.com/Kintaro/wtftw 77 | 78 | ## for text base interface, a wrapper for termbox 79 | 80 | https://github.com/gchp/rustbox 81 | 82 | 83 | ##How to deal with nullable columns in the database 84 | * Most database columns has null values, can be optional 85 | * If nullable make the field Optional 86 | 87 | 88 | ## Check to see if extension are already installed in the database 89 | select * from pg_extension 90 | 91 | You may need to create the schema before installing the extensions 92 | 93 | 94 | ## releasing: 95 | ``` 96 | cargo publish 97 | ``` 98 | 99 | Make sure license, github code, documentation is properly linked in the Cargo.toml file 100 | 101 | ## Publishing the documents 102 | 103 | ``` 104 | cargo clean 105 | cargo doc --no-deps 106 | cd target/doc 107 | git init 108 | git add . -A 109 | git commit -m "Commiting docs to github pages" 110 | git remote add origin https://github.com/ivanceras/rustorm 111 | git checkout -b gh-pages 112 | git push --force origin gh-pages 113 | 114 | 115 | SHOW server_version; 116 | select version(); 117 | 118 | 119 | ## Uuid 120 | * uuid has 16 bytes -> (128 bits/8) 121 | * 32 characters (hex digit) 122 | * 36 when including hyphens 123 | * 22 character when encododed to base64 124 | 125 | gotcha: need to put & to borrow immutable from mutable 126 | ``` 127 | fn execute_with_return(&mut self, query:&Query)->DaoResult{ 128 | let sql_frag = &self.build_query(query); 129 | ``` 130 | 131 | 132 | 133 | ### Get view columns 134 | ```sql 135 | 136 | SELECT 137 | pg_attribute.attnum AS number, 138 | pg_attribute.attname AS name, 139 | pg_attribute.attnotnull AS notnull, 140 | pg_catalog.format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS data_type, 141 | CASE 142 | WHEN pg_constraint.contype = 'p' THEN true 143 | ELSE false 144 | END AS is_primary, 145 | CASE 146 | WHEN pg_constraint.contype = 'u' THEN true 147 | ELSE false 148 | END AS is_unique, 149 | CASE 150 | WHEN pg_constraint.contype = 'f' THEN g.relname 151 | END AS foreign_table, 152 | CASE 153 | WHEN pg_attribute.atthasdef = true THEN pg_attrdef.adsrc 154 | END as default 155 | ,pg_description.description as comment 156 | ,(SELECT nspname FROM pg_namespace WHERE oid=g.relnamespace) AS foreign_schema 157 | ,(SELECT pg_attribute.attname FROM pg_attribute 158 | WHERE pg_attribute.attrelid = pg_constraint.confrelid 159 | AND pg_attribute.attnum = pg_constraint.confkey[1] 160 | AND pg_attribute.attisdropped = false) AS foreign_column 161 | ,pg_constraint.conname 162 | 163 | FROM pg_attribute 164 | JOIN pg_class 165 | ON pg_class.oid = pg_attribute.attrelid 166 | JOIN pg_type 167 | ON pg_type.oid = pg_attribute.atttypid 168 | LEFT JOIN pg_attrdef 169 | ON pg_attrdef.adrelid = pg_class.oid 170 | AND pg_attrdef.adnum = pg_attribute.attnum 171 | LEFT JOIN pg_namespace 172 | ON pg_namespace.oid = pg_class.relnamespace 173 | LEFT JOIN pg_constraint 174 | ON pg_constraint.conrelid = pg_class.oid 175 | AND pg_attribute.attnum = ANY (pg_constraint.conkey) 176 | LEFT JOIN pg_class AS g 177 | ON pg_constraint.confrelid = g.oid 178 | LEFT JOIN pg_description 179 | ON pg_description.objoid = pg_class.oid 180 | AND pg_description.objsubid = pg_attribute.attnum 181 | WHERE pg_class.relkind = 'v'::char 182 | AND pg_namespace.nspname = 'views' 183 | AND pg_class.relname = 'vw_device_ssh' 184 | AND pg_attribute.attnum > 0 185 | ORDER BY number 186 | ``` 187 | 188 | ## Sqlite meta data extractions 189 | https://www.sqlite.org/pragma.html#pragma_foreign_key_list 190 | 191 | ### extract table columns 192 | PRAGMA database.table_info(table-name); 193 | PRAGMA table_info(product); 194 | 195 | ### foreign keys 196 | PRAGMA foreign_key_list(table-name); 197 | PRAGMA foreign_key_list(product_availability); 198 | 199 | ## indexes 200 | 201 | ``` 202 | PRAGMA index_list(table-name); 203 | 204 | 205 | CREATE TABLE product_availability ( 206 | product_id uuid NOT NULL, 207 | available boolean, 208 | always_available boolean, 209 | stocks numeric DEFAULT 1, 210 | available_from timestamp with time zone, 211 | available_until timestamp with time zone, 212 | available_day json, 213 | open_time time with time zone, 214 | close_time time with time zone, 215 | FOREIGN KEY(product_id) REFERENCES product(product_id) 216 | ) 217 | 218 | ## extract columns in sqlite, 219 | http://stackoverflow.com/questions/2785702/use-javascript-regex-to-extract-column-names-from-sqlite-create-table-sql 220 | 221 | ## Run test in the project using 222 | 223 | ``` 224 | cargo test --features "mysql sqlite" 225 | ``` 226 | 227 | 228 | https://en.wikipedia.org/wiki/Comparison_of_relational_database_management_systems 229 | 230 | 231 | -------------------------------------------------------------------------------- /src/query/builder.rs: -------------------------------------------------------------------------------- 1 | use query::Query; 2 | use query::Modifier; 3 | use query::Direction; 4 | use query::Equality; 5 | use query::SqlType; 6 | use query::Operand; 7 | use query::{ColumnName, ToColumnName}; 8 | use query::NullsWhere; 9 | use query::{Join, JoinType}; 10 | use query::{TableName, ToTableName}; 11 | use query::Filter; 12 | use dao::{Value, ToValue}; 13 | use query::Field; 14 | use query::Order; 15 | use query::operand::ToOperand; 16 | use query::order::{ToOrder, HasDirection}; 17 | use database::Database; 18 | use writer::SqlFrag; 19 | use database::BuildMode; 20 | use query::join::ToJoin; 21 | use dao::IsDao; 22 | use table::IsTable; 23 | use database::DbError; 24 | use query::ToField; 25 | use query::source::{QuerySource, ToSourceField, SourceField}; 26 | use query::DeclaredQuery; 27 | 28 | 29 | pub struct QueryBuilder { 30 | query: Query, 31 | } 32 | 33 | 34 | pub fn SELECT_ALL() -> QueryBuilder { 35 | QueryBuilder::SELECT_ALL() 36 | } 37 | 38 | pub fn SELECT() -> QueryBuilder { 39 | QueryBuilder::SELECT() 40 | } 41 | 42 | pub fn INSERT() -> QueryBuilder { 43 | QueryBuilder::INSERT() 44 | } 45 | 46 | pub fn UPDATE(to_table_name: &ToTableName) -> QueryBuilder { 47 | QueryBuilder::UPDATE(to_table_name) 48 | } 49 | 50 | pub fn DELETE() -> QueryBuilder { 51 | QueryBuilder::DELETE() 52 | } 53 | 54 | impl QueryBuilder { 55 | /// if the database support CTE declareted query i.e WITH, 56 | /// then this query will be declared 57 | /// if database doesn't support WITH queries, then this query will be 58 | /// wrapped in the from_query 59 | /// build a builder for this 60 | pub fn WITH(&mut self, query: Query, alias: &str) -> &mut Self { 61 | let declared_query = DeclaredQuery{ 62 | name: alias.into(), 63 | fields: vec![], 64 | query: query, 65 | is_recursive: false, 66 | }; 67 | self.query.declared_query.push(declared_query); 68 | self 69 | } 70 | 71 | pub fn WITH_RECURSIVE(&mut self, query: Query, alias: &str) -> &mut Self { 72 | let declared_query = DeclaredQuery{ 73 | name: alias.into(), 74 | fields: vec![], 75 | query: query, 76 | is_recursive: true, 77 | }; 78 | self.query.declared_query.push(declared_query); 79 | self 80 | } 81 | 82 | pub fn SELECT() -> Self { 83 | let mut q = Query::select(); 84 | QueryBuilder { query: q } 85 | } 86 | pub fn SELECT_ALL() -> Self { 87 | let mut qb = Self::SELECT(); 88 | qb.ALL(); 89 | qb 90 | } 91 | 92 | pub fn INSERT() -> Self { 93 | let mut q = Query::insert(); 94 | QueryBuilder { query: q } 95 | } 96 | pub fn UPDATE(to_table_name: &ToTableName) -> Self { 97 | let mut q = Query::update(); 98 | let qs = QuerySource::TableName(to_table_name.to_table_name()); 99 | let sf = SourceField { 100 | source: qs, 101 | rename: None, 102 | }; 103 | q.from.push(sf); 104 | QueryBuilder { query: q } 105 | } 106 | pub fn DELETE() -> Self { 107 | let mut q = Query::delete(); 108 | QueryBuilder { query: q } 109 | } 110 | 111 | /// add DISTINCT ie: SELECT DISTINCT 112 | pub fn DISTINCT(&mut self) -> &mut Self { 113 | self.query.distinct = true; 114 | self 115 | } 116 | 117 | 118 | pub fn ALL(&mut self) -> &mut Self { 119 | self.query.all(); 120 | self 121 | } 122 | 123 | pub fn SET(&mut self, column: &str, to_value: &ToValue) -> &mut Self { 124 | self.query.set(column, to_value); 125 | self 126 | } 127 | 128 | pub fn COLUMNS(&mut self, to_operand: &[&ToOperand]) -> &mut Self { 129 | for to in to_operand { 130 | let field = Field { 131 | operand: to.to_operand(), 132 | name: None, 133 | }; 134 | self.query.enumerated_fields.push(field); 135 | } 136 | self 137 | } 138 | 139 | pub fn VALUES(&mut self, to_values: &[&ToValue]) -> &mut Self { 140 | for tov in to_values { 141 | let v = tov.to_db_type(); 142 | let operand = Operand::Value(v); 143 | self.query.values.push(operand); 144 | } 145 | self 146 | } 147 | 148 | /// A more terse way to write the query 149 | /// only 1 table is supported yet 150 | pub fn FROM(&mut self, to_source_field: &ToSourceField) -> &mut Self { 151 | self.query.from(to_source_field); 152 | self 153 | } 154 | 155 | 156 | /// `into` is used in rust, os settled with `into_` 157 | pub fn INTO(&mut self, table: &ToTableName) -> &mut Self { 158 | assert_eq!(self.query.sql_type, SqlType::INSERT); 159 | self.TABLE(table); 160 | self 161 | } 162 | /// can be used in behalf of into_, from, 163 | pub fn TABLE(&mut self, table: &ToTableName) -> &mut Self { 164 | let table_name = table.to_table_name(); 165 | let qs = QuerySource::TableName(table_name); 166 | let sf = SourceField { 167 | source: qs, 168 | rename: None, 169 | }; 170 | self.query.from.push(sf); 171 | self 172 | } 173 | 174 | 175 | 176 | /// join a table on this query 177 | /// 178 | pub fn JOIN(&mut self, join: Join) -> &mut Self { 179 | self.query.joins.push(join); 180 | self 181 | } 182 | 183 | /// join a table on this query 184 | /// 185 | pub fn LEFT_JOIN(&mut self, join: Join) -> &mut Self { 186 | let mut join = join.clone(); 187 | join.modifier = Some(Modifier::LEFT); 188 | self.JOIN(join); 189 | self 190 | } 191 | pub fn RIGHT_JOIN(&mut self, join: Join) -> &mut Self { 192 | let mut join = join.clone(); 193 | join.modifier = Some(Modifier::RIGHT); 194 | self.JOIN(join); 195 | self 196 | } 197 | pub fn FULL_JOIN(&mut self, join: Join) -> &mut Self { 198 | let mut join = join.clone(); 199 | join.modifier = Some(Modifier::FULL); 200 | self.JOIN(join); 201 | self 202 | } 203 | pub fn INNER_JOIN(&mut self, join: Join) -> &mut Self { 204 | let mut join = join.clone(); 205 | join.join_type = Some(JoinType::INNER); 206 | self.JOIN(join); 207 | self 208 | } 209 | 210 | pub fn WHERE(&mut self, filter: Filter) -> &mut Self { 211 | self.query.filters.push(filter); 212 | self 213 | } 214 | 215 | pub fn GROUP_BY(&mut self, to_operand: &ToOperand) -> &mut Self { 216 | let operand = to_operand.to_operand(); 217 | // put in the parent vector if there are multiple operands 218 | match operand { 219 | Operand::Vec(ref operands) => { 220 | for op in operands { 221 | self.query.group_by.push(op.to_owned()); 222 | } 223 | } 224 | _ => { 225 | self.query.group_by.push(to_operand.to_operand()); 226 | } 227 | } 228 | self 229 | } 230 | 231 | pub fn HAVING(&mut self, filter: Filter) -> &mut Self { 232 | self.query.having.push(filter); 233 | self 234 | } 235 | 236 | 237 | pub fn ORDER_BY(&mut self, to_order: &ToOrder) -> &mut Self { 238 | let mut orders = to_order.to_order(); 239 | self.query.order_by.append(&mut orders); 240 | self 241 | } 242 | 243 | pub fn LIMIT(&mut self, n: usize) -> &mut Self { 244 | self.query.set_limit(n); 245 | self 246 | } 247 | 248 | pub fn OFFSET(&mut self, o: usize) -> &mut Self { 249 | self.query.set_offset(o); 250 | self 251 | } 252 | 253 | 254 | /// build the query only, not executed, useful when debugging 255 | pub fn build(&mut self, db: &Database) -> SqlFrag { 256 | self.query.build(db) 257 | } 258 | 259 | pub fn collect_one(&mut self, db: &Database) -> Result { 260 | self.query.collect_one::(db) 261 | } 262 | pub fn collect(&mut self, db: &Database) -> Result, DbError> { 263 | self.query.collect::(db) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/platform/pool.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | use r2d2::Pool; 3 | #[allow(unused)] 4 | use r2d2::Config; 5 | use config::DbConfig; 6 | use database::{Database, DatabaseDDL, DatabaseDev}; 7 | #[cfg(feature = "postgres")] 8 | use platform::Postgres; 9 | #[cfg(feature = "postgres")] 10 | use r2d2_postgres::PostgresConnectionManager; 11 | #[cfg(feature = "postgres")] 12 | use r2d2_postgres::SslMode; 13 | #[cfg(feature = "sqlite")] 14 | use platform::Sqlite; 15 | #[cfg(feature = "mysql")] 16 | use platform::Mysql; 17 | #[cfg(feature = "mysql")] 18 | use mysql::conn::pool::MyPool; 19 | #[cfg(feature = "mysql")] 20 | use mysql::conn::MyOpts; 21 | use database::DbError; 22 | use std::ops::Deref; 23 | use std::collections::HashMap; 24 | use std::sync::{Arc, RwLock}; 25 | 26 | #[cfg(feature = "sqlite")] 27 | use r2d2_sqlite::SqliteConnectionManager; 28 | 29 | 30 | lazy_static! { 31 | pub static ref DB_POOL: Arc>> = 32 | Arc::new(RwLock::new(HashMap::new())); 33 | } 34 | 35 | // 1 pool per connection name 36 | // check the connection name supplied 37 | // has the same db_url configuration 38 | // different connection name 39 | // will have different connection pool 40 | #[derive(PartialEq,Eq)] 41 | #[derive(Hash)] 42 | #[derive(Clone)] 43 | pub struct PoolConfig{ 44 | connection_name: String, 45 | db_url: String, 46 | pool_size: u32, 47 | } 48 | 49 | 50 | /// no connection name supplied 51 | /// pool size is 10; 52 | #[cfg(any(feature = "postgres",feature = "sqlite", feature ="mysql"))] 53 | pub fn db_with_url(db_url: &str) -> Result { 54 | let config = PoolConfig{ 55 | connection_name: "GLOBAL".to_string(), 56 | db_url: db_url.to_string(), 57 | pool_size: 10 58 | }; 59 | db_with_config(&config) 60 | } 61 | 62 | #[cfg(any(feature = "postgres",feature = "sqlite", feature ="mysql"))] 63 | pub fn test_connection(db_url: &str) -> Result<(), DbError>{ 64 | let config = DbConfig::from_url(db_url); 65 | match config { 66 | Some(config) => { 67 | let platform: &str = &config.platform; 68 | match platform { 69 | #[cfg(feature = "postgres")] 70 | "postgres" => { 71 | ::platform::postgres::establish_connection(db_url)?; 72 | Ok(()) 73 | } 74 | 75 | #[cfg(feature = "sqlite")] 76 | "sqlite" => { 77 | ::platform::sqlite::establish_connection(db_url)?; 78 | Ok(()) 79 | } 80 | #[cfg(feature = "mysql")] 81 | "mysql" => { 82 | ::platform::mysql::establish_connection(&config)?; 83 | Ok(()) 84 | } 85 | 86 | _ => unimplemented!(), 87 | } 88 | } 89 | None => { 90 | println!("Unable to parse url"); 91 | Err(DbError::new("Error parsing url")) 92 | } 93 | } 94 | } 95 | 96 | #[cfg(any(feature = "postgres",feature = "sqlite", feature ="mysql"))] 97 | pub fn db_with_config(config: &PoolConfig) -> Result { 98 | let has_pool = DB_POOL.read().unwrap().get(&config).is_some(); 99 | if has_pool{ 100 | DB_POOL.read().unwrap().get(&config).unwrap().connect() 101 | }else{ 102 | create_new(&config) 103 | } 104 | } 105 | 106 | /// creates a new ManagedPool for this database platform 107 | #[cfg(any(feature = "postgres",feature = "sqlite", feature ="mysql"))] 108 | fn create_new(config: &PoolConfig) -> Result { 109 | println!("not an existing pool, creating one"); 110 | let pool = ManagedPool::init(&config.db_url, config.pool_size as usize)?; 111 | let conn = pool.connect(); 112 | println!("inserting to the Pool"); 113 | DB_POOL.write().unwrap().insert(config.clone(), pool); 114 | println!("inserted!"); 115 | conn 116 | } 117 | 118 | /// the sql builder for each of the database platform 119 | pub enum Platform { 120 | #[cfg(feature = "postgres")] 121 | Postgres(Postgres), 122 | #[cfg(feature = "sqlite")] 123 | Sqlite(Sqlite), 124 | #[cfg(feature = "mysql")] 125 | Mysql(Mysql), 126 | } 127 | 128 | impl Platform { 129 | 130 | /// create a postgresql 131 | /// database instance 132 | /// without doing a connection 133 | #[cfg(feature = "postgres")] 134 | pub fn pg() -> Self { 135 | Platform::Postgres(Postgres::new()) 136 | } 137 | 138 | #[cfg(feature = "sqlite")] 139 | pub fn sqlite() -> Self { 140 | Platform::Sqlite(Sqlite::new()) 141 | } 142 | 143 | #[cfg(feature = "mysql")] 144 | pub fn mysql() -> Self { 145 | Platform::Mysql(Mysql::new()) 146 | } 147 | 148 | pub fn as_ref(&self) -> &Database { 149 | match *self { 150 | #[cfg(feature = "postgres")] 151 | Platform::Postgres(ref pg) => pg, 152 | #[cfg(feature = "sqlite")] 153 | Platform::Sqlite(ref lite) => lite, 154 | #[cfg(feature = "mysql")] 155 | Platform::Mysql(ref my) => my, 156 | } 157 | } 158 | 159 | pub fn as_ddl(&self) -> &DatabaseDDL { 160 | match *self { 161 | #[cfg(feature = "postgres")] 162 | Platform::Postgres(ref pg) => pg, 163 | #[cfg(feature = "sqlite")] 164 | Platform::Sqlite(ref lite) => lite, 165 | #[cfg(feature = "mysql")] 166 | Platform::Mysql(ref my) => my, 167 | } 168 | } 169 | 170 | pub fn as_dev(&self) -> &DatabaseDev { 171 | match *self { 172 | #[cfg(feature = "postgres")] 173 | Platform::Postgres(ref pg) => pg, 174 | #[cfg(feature = "sqlite")] 175 | Platform::Sqlite(ref lite) => lite, 176 | #[cfg(feature = "mysql")] 177 | Platform::Mysql(ref my) => my, 178 | } 179 | } 180 | } 181 | 182 | impl Deref for Platform { 183 | type Target = Database; 184 | 185 | fn deref(&self) -> &Self::Target { 186 | debug!("using deref..."); 187 | match *self { 188 | #[cfg(feature = "postgres")] 189 | Platform::Postgres(ref pg) => pg, 190 | #[cfg(feature = "sqlite")] 191 | Platform::Sqlite(ref lite) => lite, 192 | #[cfg(feature = "mysql")] 193 | Platform::Mysql(ref my) => my, 194 | } 195 | } 196 | } 197 | 198 | 199 | /// Postgres, Sqlite uses r2d2 connection manager, 200 | /// Mysql has its own connection pooling 201 | pub enum ManagedPool { 202 | #[cfg(feature = "postgres")] 203 | Postgres(Pool), 204 | #[cfg(feature = "sqlite")] 205 | Sqlite(Pool), 206 | #[cfg(feature = "mysql")] 207 | Mysql(Option), 208 | } 209 | 210 | impl ManagedPool { 211 | /// initialize the pool 212 | #[allow(unused)] 213 | pub fn init(url: &str, pool_size: usize) -> Result { 214 | let config = DbConfig::from_url(url); 215 | let pool_size = pool_size as u32; 216 | match config { 217 | Some(config) => { 218 | let platform: &str = &config.platform; 219 | match platform { 220 | #[cfg(feature = "postgres")] 221 | "postgres" => { 222 | let manager = try!(PostgresConnectionManager::new(url, SslMode::None)); 223 | debug!("Creating a connection with a pool size of {}", pool_size); 224 | let config = Config::builder().pool_size(pool_size).build(); 225 | let pool = try!(Pool::new(config, manager)); 226 | Ok(ManagedPool::Postgres(pool)) 227 | } 228 | 229 | #[cfg(feature = "sqlite")] 230 | "sqlite" => { 231 | let manager = SqliteConnectionManager::new(&config.database); 232 | let config = Config::builder().pool_size(pool_size).build(); 233 | let pool = try!(Pool::new(config, manager)); 234 | Ok(ManagedPool::Sqlite(pool)) 235 | } 236 | #[cfg(feature = "mysql")] 237 | "mysql" => { 238 | let opts = MyOpts { 239 | user: config.username, 240 | pass: config.password, 241 | db_name: Some(config.database), 242 | tcp_addr: Some(config.host.unwrap().to_string()), 243 | tcp_port: config.port.unwrap_or(3306), 244 | ..Default::default() 245 | }; 246 | let pool = try!(MyPool::new_manual(0, pool_size as usize, opts)); 247 | Ok(ManagedPool::Mysql(Some(pool))) 248 | } 249 | 250 | _ => unimplemented!(), 251 | } 252 | } 253 | None => { 254 | println!("Unable to parse url"); 255 | Err(DbError::new("Error parsing url")) 256 | } 257 | } 258 | 259 | } 260 | 261 | /// a conection is created here 262 | pub fn connect(&self) -> Result { 263 | match *self { 264 | #[cfg(feature = "postgres")] 265 | ManagedPool::Postgres(ref pool) => { 266 | match pool.get() { 267 | Ok(conn) => { 268 | let pg = Postgres::with_pooled_connection(conn); 269 | Ok(Platform::Postgres(pg)) 270 | } 271 | Err(e) => Err(DbError::new(&format!("Unable to connect due to {}", e))), 272 | } 273 | } 274 | #[cfg(feature = "sqlite")] 275 | ManagedPool::Sqlite(ref pool) => { 276 | match pool.get() { 277 | Ok(conn) => { 278 | let lite = Sqlite::with_pooled_connection(conn); 279 | Ok(Platform::Sqlite(lite)) 280 | } 281 | Err(e) => Err(DbError::new(&format!("Unable to connect due to {}", e))), 282 | } 283 | } 284 | #[cfg(feature = "mysql")] 285 | ManagedPool::Mysql(ref pool) => { 286 | let my = Mysql::with_pooled_connection(pool.clone().unwrap());// I hope cloning doesn't really clone the pool, just the Arc 287 | Ok(Platform::Mysql(my)) 288 | } 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/dao.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use uuid::Uuid; 3 | use chrono::datetime::DateTime; 4 | use std::fmt; 5 | use query::ColumnName; 6 | use rustc_serialize::{Encodable, Encoder, Decodable, Decoder}; 7 | use rustc_serialize::json::Json; 8 | use rustc_serialize::base64::STANDARD; 9 | use rustc_serialize::base64::ToBase64; 10 | use rustc_serialize::base64::FromBase64; 11 | use query::IsTable; 12 | use chrono::UTC; 13 | use chrono::FixedOffset; 14 | 15 | 16 | #[derive(Debug)] 17 | #[derive(Clone)] 18 | #[derive(PartialEq)] 19 | #[derive(RustcEncodable)] 20 | /// supported generic datatypes for an ORM 21 | pub enum Type { 22 | Bool, 23 | I8, 24 | I16, 25 | I32, 26 | I64, 27 | U8, 28 | U16, 29 | U32, 30 | U64, 31 | F32, 32 | F64, 33 | String, 34 | VecU8, 35 | Json, 36 | Uuid, 37 | DateTime, 38 | } 39 | 40 | /* 41 | impl Type { 42 | /// get the string representation when used in rust code 43 | pub fn to_str_repr(&self) -> String { 44 | match *self { 45 | Type::Bool => "bool".to_owned(), 46 | Type::I8 => "i8".to_owned(), 47 | Type::I16 => "i16".to_owned(), 48 | Type::I32 => "i32".to_owned(), 49 | Type::I64 => "i64".to_owned(), 50 | Type::U8 => "u8".to_owned(), 51 | Type::U16 => "u16".to_owned(), 52 | Type::U32 => "u32".to_owned(), 53 | Type::U64 => "u64".to_owned(), 54 | Type::F32 => "f32".to_owned(), 55 | Type::F64 => "f64".to_owned(), 56 | Type::String => "String".to_owned(), 57 | Type::VecU8 => "Vec".to_owned(), 58 | Type::Json => "Json".to_owned(), 59 | Type::Uuid => "Uuid".to_owned(), 60 | Type::DateTime => "DateTime".to_owned(), 61 | } 62 | } 63 | } 64 | */ 65 | 66 | 67 | #[derive(Debug)] 68 | #[derive(Clone)] 69 | #[derive(PartialEq)] 70 | /// supported generic datatypes for an ORM 71 | pub enum Value { 72 | Bool(bool), 73 | I8(i8), 74 | I16(i16), 75 | I32(i32), 76 | I64(i64), 77 | U8(u8), 78 | U16(u16), 79 | U32(u32), 80 | U64(u64), 81 | F32(f32), 82 | F64(f64), 83 | String(String), 84 | VecU8(Vec), 85 | Json(Json), 86 | Uuid(Uuid), 87 | DateTime(DateTime), 88 | } 89 | 90 | impl Value { 91 | pub fn get_type(&self) -> Type { 92 | match *self { 93 | Value::Bool(_) => Type::Bool, 94 | Value::I8(_) => Type::I8, 95 | Value::I16(_) => Type::I16, 96 | Value::I32(_) => Type::I32, 97 | Value::I64(_) => Type::I64, 98 | Value::U8(_) => Type::U8, 99 | Value::U16(_) => Type::U16, 100 | Value::U32(_) => Type::U32, 101 | Value::U64(_) => Type::U64, 102 | Value::F32(_) => Type::F32, 103 | Value::F64(_) => Type::F64, 104 | Value::String(_) => Type::String, 105 | Value::VecU8(_) => Type::VecU8, 106 | Value::Uuid(_) => Type::Uuid, 107 | Value::DateTime(_) => Type::DateTime, 108 | Value::Json(_) => Type::Json, 109 | } 110 | } 111 | 112 | fn from_ser_value(ser_value: &SerValue) -> Self { 113 | match *ser_value { 114 | SerValue::Bool(x) => Value::Bool(x), 115 | SerValue::I8(x) => Value::I8(x), 116 | SerValue::I16(x) => Value::I16(x), 117 | SerValue::I32(x) => Value::I32(x), 118 | SerValue::I64(x) => Value::I64(x), 119 | SerValue::U8(x) => Value::U8(x), 120 | SerValue::U16(x) => Value::U16(x), 121 | SerValue::U32(x) => Value::U32(x), 122 | SerValue::U64(x) => Value::U64(x), 123 | SerValue::F32(x) => Value::F32(x), 124 | SerValue::F64(x) => Value::F64(x), 125 | SerValue::String(ref x) => Value::String(x.to_owned()), 126 | SerValue::VecU8(ref x) => { 127 | let vecu8 = x.from_base64().unwrap(); 128 | Value::VecU8(vecu8) 129 | } 130 | SerValue::Uuid(x) => Value::Uuid(x), 131 | SerValue::DateTime(ref x) => { 132 | let date = DateTime::parse_from_rfc3339(x).unwrap(); 133 | Value::DateTime(date) 134 | } 135 | SerValue::Json(ref json) => { 136 | let json = Json::from_str(json).unwrap(); 137 | Value::Json(json) 138 | } 139 | } 140 | } 141 | } 142 | 143 | 144 | 145 | /// custom implementation for value encoding to json, 146 | /// does not include unnecessary enum variants fields. 147 | impl Encodable for Value { 148 | fn encode(&self, s: &mut S) -> Result<(), S::Error> { 149 | let ser_value = SerValue::from_value(&self); 150 | ser_value.encode(s) 151 | } 152 | } 153 | 154 | /// serializable value to json, to avoid complexity 155 | /// in manipulating the clienside json things such as date, 156 | #[derive(RustcEncodable)] 157 | #[derive(RustcDecodable)] 158 | enum SerValue { 159 | Bool(bool), 160 | I8(i8), 161 | I16(i16), 162 | I32(i32), 163 | I64(i64), 164 | U8(u8), 165 | U16(u16), 166 | U32(u32), 167 | U64(u64), 168 | F32(f32), 169 | F64(f64), 170 | String(String), 171 | VecU8(String), // blob in 64 bit 172 | Uuid(Uuid), 173 | DateTime(String), // in standard format string 174 | Json(String), 175 | } 176 | 177 | impl SerValue { 178 | fn from_value(value: &Value) -> Self { 179 | match value { 180 | &Value::Bool(x) => SerValue::Bool(x), 181 | &Value::I8(x) => SerValue::I8(x), 182 | &Value::I16(x) => SerValue::I16(x), 183 | &Value::I32(x) => SerValue::I32(x), 184 | &Value::I64(x) => SerValue::I64(x), 185 | &Value::U8(x) => SerValue::U8(x), 186 | &Value::U16(x) => SerValue::U16(x), 187 | &Value::U32(x) => SerValue::U32(x), 188 | &Value::U64(x) => SerValue::U64(x), 189 | &Value::F32(x) => SerValue::F32(x), 190 | &Value::F64(x) => SerValue::F64(x), 191 | &Value::String(ref x) => SerValue::String(x.to_owned()), 192 | &Value::VecU8(ref x) => { 193 | let base64 = x.to_base64(STANDARD); 194 | SerValue::VecU8(base64) 195 | } 196 | &Value::Uuid(x) => SerValue::Uuid(x), 197 | &Value::DateTime(ref x) => { 198 | let date_str = x.to_rfc3339(); 199 | SerValue::DateTime(date_str) 200 | } 201 | &Value::Json(ref json) => { 202 | let json_text = format!("{}", json.pretty()); 203 | SerValue::Json(json_text) 204 | } 205 | } 206 | 207 | } 208 | } 209 | 210 | /// A quick solution to controlling the output of the decoded 211 | /// json value at right amount of data structure nesting... 212 | impl Decodable for Value { 213 | fn decode(d: &mut D) -> Result { 214 | let ser_value = try!(SerValue::decode(d)); 215 | let value = Value::from_ser_value(&ser_value); 216 | Ok(value) 217 | } 218 | } 219 | 220 | #[test] 221 | fn test_decode_value() { 222 | extern crate rustc_serialize; 223 | let mut dao = Dao::new(); 224 | dao.insert("hello".to_owned(), Value::String("hi".to_owned())); 225 | let dao_json = rustc_serialize::json::encode(&dao).unwrap(); 226 | println!("{:#?}", dao_json); 227 | let dec: Dao = rustc_serialize::json::decode(&dao_json).unwrap(); 228 | println!("{:#?}", dec); 229 | assert_eq!(dao, dec); 230 | } 231 | 232 | 233 | 234 | impl fmt::Display for Value { 235 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 236 | match *self { 237 | Value::Bool(ref x) => write!(f, "'{}'", x), 238 | Value::I8(ref x) => write!(f, "'{}'", x), 239 | Value::I16(ref x) => write!(f, "'{}'", x), 240 | Value::I32(ref x) => write!(f, "'{}'", x), 241 | Value::I64(ref x) => write!(f, "'{}'", x), 242 | Value::U8(ref x) => write!(f, "'{}'", x), 243 | Value::U16(ref x) => write!(f, "'{}'", x), 244 | Value::U32(ref x) => write!(f, "'{}'", x), 245 | Value::U64(ref x) => write!(f, "'{}'", x), 246 | Value::F32(ref x) => write!(f, "'{}'", x), 247 | Value::F64(ref x) => write!(f, "'{}'", x), 248 | Value::String(ref x) => write!(f, "'{}'", x), 249 | Value::VecU8(ref x) => write!(f, "'{:?}'", x), 250 | Value::Uuid(ref x) => write!(f, "'{}'", x), 251 | Value::DateTime(ref x) => write!(f, "'{}'", x), 252 | Value::Json(ref x) => write!(f, "'{:?}'", x), 253 | } 254 | } 255 | } 256 | 257 | 258 | /// trait for converting dao to model 259 | /// sized and clonable 260 | pub trait IsDao { 261 | /// convert dao to an instance of the corresponding struct of the model 262 | /// taking into considerating the renamed columns 263 | fn from_dao(dao: &Dao) -> Self; 264 | 265 | /// convert from an instance of the struct to a dao representation 266 | /// to be saved into the database 267 | fn to_dao(&self) -> Dao; 268 | } 269 | 270 | /// Ignore Column are columns that are redundant when displaying as API results 271 | pub trait ToCompact { 272 | /// list of redundant fields that will be removed when doing a compact serialization 273 | fn redundant_fields(&self) -> Vec<&str>; 274 | 275 | /// compact BTreeMap represetation 276 | fn compact_map(&self) -> BTreeMap; 277 | 278 | /// compact dao representation 279 | fn compact_dao(&self) -> Dao; 280 | 281 | /// compact dao representation 282 | fn compact_json(&self) -> Json; 283 | } 284 | 285 | /// meta result of a query useful when doing complex query, and also with paging 286 | /// TODO: good name: DaoRows 287 | #[derive(Debug,Clone)] 288 | #[derive(RustcEncodable)] 289 | #[derive(RustcDecodable)] 290 | pub struct DaoResult { 291 | pub dao: Vec, 292 | /// renamed columns for each table 293 | /// ie. product => [(name, product_name),..]; 294 | pub renamed_columns: Vec<(ColumnName, String)>, 295 | 296 | /// the total number of records 297 | pub total: Option, 298 | /// page of the query 299 | pub page: Option, 300 | /// page size 301 | pub page_size: Option, 302 | } 303 | 304 | 305 | impl DaoResult { 306 | /// get the list of renamed column name in matching table name 307 | fn get_renamed_columns(&self, table: &str) -> Vec<(String, String)> { 308 | let mut columns = vec![]; 309 | for &(ref col, ref rename) in &self.renamed_columns { 310 | if let Some(ref s) = col.table { 311 | if s == table { 312 | columns.push((col.column.to_owned(), rename.to_owned())); 313 | } 314 | } 315 | } 316 | columns 317 | } 318 | 319 | /// cast the dao to the specific struct instance 320 | /// do not include if non nullable parts contains null 321 | pub fn cast(&self) -> Vec { 322 | let table = T::table_name(); 323 | let mut obj = vec![]; 324 | let renamed_columns = self.get_renamed_columns(&table.name); 325 | for dao in &self.dao { 326 | let mut dao_clone = dao.clone(); 327 | dao_clone.correct_renamed_columns(&renamed_columns); 328 | let p = T::from_dao(&dao_clone); 329 | obj.push(p); 330 | } 331 | obj 332 | } 333 | 334 | /// FIXME: should return an error when there are more than 1 to be casted 335 | pub fn cast_one(&self) -> Option { 336 | let mut casted = self.cast::(); 337 | if casted.len() < 1 { 338 | return None; 339 | } 340 | Some(casted.remove(0)) 341 | } 342 | } 343 | 344 | /// TODO: optimization, used enum types for the key values 345 | /// This will save allocation of string to enum keys which is a few bytes, int 346 | pub type Dao = BTreeMap; 347 | 348 | 349 | pub type ParseError = String; 350 | 351 | 352 | trait DaoCorrections { 353 | fn correct_renamed_columns(&mut self, renamed_columns: &Vec<(String, String)>); 354 | 355 | fn all_has_values(&self, non_nulls: &Vec) -> bool; 356 | } 357 | 358 | impl DaoCorrections for Dao { 359 | fn correct_renamed_columns(&mut self, renamed_columns: &Vec<(String, String)>) { 360 | for &(ref column, ref rename) in renamed_columns { 361 | let value: Option = match self.get(rename) { 362 | Some(value) => Some(value.to_owned()), 363 | None => None, 364 | }; 365 | if let Some(value) = value { 366 | self.insert(column.to_owned(), value.clone()); 367 | } 368 | } 369 | } 370 | 371 | fn all_has_values(&self, non_nulls: &Vec) -> bool { 372 | for column in non_nulls { 373 | let value = self.get(column); 374 | if let Some(_) = value { 375 | // has value 376 | } else { 377 | return false; 378 | } 379 | } 380 | true 381 | } 382 | } 383 | 384 | 385 | /// rename to ToValue 386 | pub trait ToValue { 387 | fn to_db_type(&self) -> Value; 388 | } 389 | 390 | impl ToValue for Value { 391 | fn to_db_type(&self) -> Value { 392 | self.to_owned() 393 | } 394 | } 395 | 396 | 397 | impl ToValue for bool { 398 | fn to_db_type(&self) -> Value { 399 | Value::Bool(self.clone()) 400 | } 401 | } 402 | 403 | /// signed INTs 404 | impl ToValue for i8 { 405 | fn to_db_type(&self) -> Value { 406 | Value::I8(self.clone()) 407 | } 408 | } 409 | 410 | impl ToValue for i16 { 411 | fn to_db_type(&self) -> Value { 412 | Value::I16(self.clone()) 413 | } 414 | } 415 | impl ToValue for i32 { 416 | fn to_db_type(&self) -> Value { 417 | Value::I32(self.clone()) 418 | } 419 | } 420 | 421 | impl ToValue for i64 { 422 | fn to_db_type(&self) -> Value { 423 | Value::I64(self.clone()) 424 | } 425 | } 426 | /// unsigned INTs 427 | impl ToValue for u8 { 428 | fn to_db_type(&self) -> Value { 429 | Value::U8(self.clone()) 430 | } 431 | } 432 | 433 | impl ToValue for u16 { 434 | fn to_db_type(&self) -> Value { 435 | Value::U16(self.clone()) 436 | } 437 | } 438 | impl ToValue for u32 { 439 | fn to_db_type(&self) -> Value { 440 | Value::U32(self.clone()) 441 | } 442 | } 443 | 444 | impl ToValue for u64 { 445 | fn to_db_type(&self) -> Value { 446 | Value::U64(self.clone()) 447 | } 448 | } 449 | 450 | impl ToValue for f32 { 451 | fn to_db_type(&self) -> Value { 452 | Value::F32(self.clone()) 453 | } 454 | } 455 | 456 | impl ToValue for f64 { 457 | fn to_db_type(&self) -> Value { 458 | Value::F64(self.clone()) 459 | } 460 | } 461 | 462 | impl ToValue for String { 463 | fn to_db_type(&self) -> Value { 464 | Value::String(self.clone()) 465 | } 466 | } 467 | 468 | impl ToValue for Uuid { 469 | fn to_db_type(&self) -> Value { 470 | Value::Uuid(self.clone()) 471 | } 472 | } 473 | 474 | impl ToValue for DateTime { 475 | fn to_db_type(&self) -> Value { 476 | Value::DateTime(self.clone()) 477 | } 478 | } 479 | 480 | impl ToValue for DateTime { 481 | fn to_db_type(&self) -> Value { 482 | let offset = FixedOffset::east(0); 483 | let ndt = self.with_timezone(&offset); 484 | Value::DateTime(ndt) 485 | } 486 | } 487 | 488 | impl ToValue for Json { 489 | fn to_db_type(&self) -> Value { 490 | Value::Json(self.clone()) 491 | } 492 | } 493 | /// 494 | /// 495 | /// 496 | /// 497 | /// 498 | /// 499 | /// 500 | /// 501 | /// 502 | /// 503 | /// 504 | /// 505 | /// 506 | pub trait FromValue { 507 | fn from_type(ty: Value) -> Self; 508 | } 509 | 510 | impl FromValue for bool { 511 | fn from_type(ty: Value) -> Self { 512 | match ty { 513 | Value::Bool(x) => x, 514 | _ => panic!("error!"), 515 | } 516 | } 517 | } 518 | 519 | impl FromValue for i8 { 520 | fn from_type(ty: Value) -> Self { 521 | match ty { 522 | Value::I8(x) => x, 523 | _ => panic!("error!"), 524 | } 525 | } 526 | } 527 | impl FromValue for i16 { 528 | fn from_type(ty: Value) -> Self { 529 | match ty { 530 | Value::I16(x) => x, 531 | _ => panic!("error!"), 532 | } 533 | } 534 | } 535 | impl FromValue for i32 { 536 | fn from_type(ty: Value) -> Self { 537 | match ty { 538 | Value::I32(x) => x, 539 | _ => panic!("error!"), 540 | } 541 | } 542 | } 543 | impl FromValue for i64 { 544 | fn from_type(ty: Value) -> Self { 545 | match ty { 546 | Value::I64(x) => x, 547 | _ => panic!("error!"), 548 | } 549 | } 550 | } 551 | impl FromValue for u8 { 552 | fn from_type(ty: Value) -> Self { 553 | match ty { 554 | Value::U8(x) => x, 555 | _ => panic!("error!"), 556 | } 557 | } 558 | } 559 | impl FromValue for u16 { 560 | fn from_type(ty: Value) -> Self { 561 | match ty { 562 | Value::U16(x) => x, 563 | _ => panic!("error!"), 564 | } 565 | } 566 | } 567 | impl FromValue for u32 { 568 | fn from_type(ty: Value) -> Self { 569 | match ty { 570 | Value::U32(x) => x, 571 | _ => panic!("error!"), 572 | } 573 | } 574 | } 575 | impl FromValue for u64 { 576 | fn from_type(ty: Value) -> Self { 577 | match ty { 578 | Value::U64(x) => x, 579 | _ => panic!("error!"), 580 | } 581 | } 582 | } 583 | 584 | impl FromValue for f32 { 585 | fn from_type(ty: Value) -> Self { 586 | match ty { 587 | Value::F32(x) => x, 588 | _ => panic!("error!"), 589 | } 590 | } 591 | } 592 | impl FromValue for f64 { 593 | fn from_type(ty: Value) -> Self { 594 | match ty { 595 | Value::F64(x) => x, 596 | _ => panic!("error! {:?}", ty), 597 | } 598 | } 599 | } 600 | 601 | impl FromValue for String { 602 | fn from_type(ty: Value) -> Self { 603 | match ty { 604 | Value::String(x) => x, 605 | _ => panic!("error! {:?}", ty), 606 | } 607 | } 608 | } 609 | 610 | impl FromValue for Uuid { 611 | fn from_type(ty: Value) -> Self { 612 | match ty { 613 | Value::Uuid(x) => x, 614 | _ => panic!("error!"), 615 | } 616 | } 617 | } 618 | 619 | impl FromValue for DateTime { 620 | fn from_type(ty: Value) -> Self { 621 | match ty { 622 | Value::DateTime(x) => x, 623 | _ => panic!("error!"), 624 | } 625 | } 626 | } 627 | 628 | impl FromValue for DateTime { 629 | fn from_type(ty: Value) -> Self { 630 | match ty { 631 | Value::DateTime(x) => { 632 | let ndt = x.naive_utc(); 633 | DateTime::from_utc(ndt,UTC) 634 | }, 635 | _ => panic!("error!"), 636 | } 637 | } 638 | } 639 | 640 | impl FromValue for Json { 641 | fn from_type(ty: Value) -> Self { 642 | match ty { 643 | Value::Json(x) => x, 644 | _ => panic!("error!"), 645 | } 646 | } 647 | } 648 | -------------------------------------------------------------------------------- /src/query/query.rs: -------------------------------------------------------------------------------- 1 | use query::Operand; 2 | use query::operand::ToOperand; 3 | use dao::ToValue; 4 | use database::Database; 5 | use dao::DaoResult; 6 | use dao::IsDao; 7 | use dao::Dao; 8 | use writer::SqlFrag; 9 | use database::DbError; 10 | use database::BuildMode; 11 | use query::ColumnName; 12 | use query::column_name::ToColumnName; 13 | use query::{TableName, ToTableName}; 14 | use query::{Filter, Equality}; 15 | //use query::QueryBuilder; 16 | use query::Function; 17 | use query::Join; 18 | use query::Order; 19 | use query::Field; 20 | use query::SourceField; 21 | use query::{QuerySource, ToSourceField}; 22 | use table::Column; 23 | use query::IsTable; 24 | use std::convert::From; 25 | 26 | 27 | pub enum Query{ 28 | Select(Select), 29 | Insert(Insert), 30 | Update(Update), 31 | Delete(Delete), 32 | } 33 | 34 | pub trait IsQuery{ 35 | /// build the query only, not executed, useful when debugging 36 | fn build(&mut self, db: &Database) -> SqlFrag; 37 | 38 | /// Warning: don't use this in production 39 | fn debug_build(&mut self, db: &Database) -> SqlFrag; 40 | 41 | } 42 | 43 | 44 | /// Query Error 45 | pub enum Error { 46 | NoTableSpecified(String), 47 | NoColumnSpecified(String), 48 | SqlError(String), 49 | } 50 | 51 | 52 | 53 | #[derive(Debug)] 54 | #[derive(PartialEq)] 55 | #[derive(Default)] 56 | #[derive(Clone)] 57 | pub struct Range { 58 | pub limit: Option, 59 | pub offset: Option, 60 | } 61 | 62 | impl Range { 63 | pub fn new() -> Self { 64 | Range { 65 | limit: None, 66 | offset: None, 67 | } 68 | } 69 | 70 | pub fn set_limit(&mut self, limit: usize) { 71 | self.limit = Some(limit); 72 | } 73 | pub fn set_offset(&mut self, offset: usize) { 74 | self.offset = Some(offset); 75 | } 76 | } 77 | 78 | 79 | #[derive(Debug)] 80 | #[derive(Clone)] 81 | pub struct DeclaredQuery{ 82 | pub name: String, 83 | pub fields: Vec, 84 | pub query: Select, 85 | pub is_recursive: bool 86 | } 87 | 88 | 89 | #[derive(Debug)] 90 | #[derive(Clone)] 91 | pub struct Select { 92 | /// whether to select the records distinct 93 | pub distinct: bool, 94 | 95 | /// whether to enumate all columns in involved models 96 | pub enumerate_all: bool, 97 | 98 | pub declared_query: Vec, 99 | 100 | /// fields can be functions, column sql query, and even columns 101 | pub enumerated_fields: Vec, 102 | 103 | /// specify to use distinct ON set of columns 104 | pub distinct_on_columns: Vec, 105 | 106 | /// where the focus of values of column selection 107 | /// this is the table to insert to, update to delete, create, drop 108 | /// whe used in select, this is the 109 | /// pub from_table:Option, 110 | 111 | /// from field, where field can be a query, table, or function 112 | pub from: Vec, 113 | 114 | /// joining multiple tables 115 | pub joins: Vec, 116 | 117 | /// filter records, ~ where statement of the query 118 | pub filters: Vec, 119 | 120 | /// ordering of the records via the columns specified 121 | pub order_by: Vec, 122 | 123 | /// grouping columns to create an aggregate 124 | pub group_by: Vec, 125 | 126 | /// having field, 127 | pub having: Vec, 128 | 129 | /// exclude the mention of the columns in the SQL query, useful when ignoring changes in update/insert records 130 | pub excluded_columns: Vec, 131 | 132 | /// caters both limit->offset and page->page_size 133 | /// setting page and page_size can not interchange the order 134 | pub range: Range, 135 | /// The data values, used in bulk inserting, updating, 136 | pub values: Vec, 137 | 138 | /// the returning clause of the query when supported, 139 | pub enumerated_returns: Vec, 140 | 141 | /// enable query stats 142 | pub enable_query_stat: bool, 143 | } 144 | 145 | impl Select { 146 | /// the default query is select 147 | pub fn new() -> Self { 148 | Select{ 149 | distinct: false, 150 | enumerate_all: false, 151 | declared_query: vec![], 152 | enumerated_fields: vec![], 153 | distinct_on_columns: vec![], 154 | filters: vec![], 155 | joins: vec![], 156 | order_by: vec![], 157 | group_by: vec![], 158 | having: vec![], 159 | excluded_columns: vec![], 160 | range: Range::new(), 161 | from: vec![], 162 | values: vec![], 163 | enumerated_returns: vec![], 164 | enable_query_stat: true, 165 | } 166 | } 167 | 168 | 169 | pub fn enumerate_all(&mut self) { 170 | self.enumerate_all = true; 171 | } 172 | 173 | pub fn all() -> Self { 174 | let mut q = Self::new(); 175 | q.column_star(); 176 | q 177 | } 178 | 179 | fn column_star(&mut self){ 180 | self.column("*"); 181 | } 182 | 183 | fn enumerate(&mut self, column_name: C) where C: Into{ 184 | let operand = Operand::ColumnName(column_name.into()); 185 | let field = Field { 186 | operand: operand, 187 | name: None, 188 | }; 189 | self.enumerated_fields.push(field); 190 | } 191 | 192 | /// all enumerated columns shall be called from this 193 | /// any conflict of columns from some other table will be automatically renamed 194 | /// columns that are not conflicts from some other table, 195 | /// but is the other conflicting column is not explicityly enumerated will not be renamed 196 | /// 197 | pub fn column(&mut self, column: &str) { 198 | let column_name = ColumnName::from(column); 199 | self.enumerate(column_name); 200 | } 201 | 202 | 203 | pub fn columns(&mut self, columns: Vec<&str>) { 204 | for c in columns { 205 | self.column(c); 206 | } 207 | } 208 | 209 | 210 | /// exclude columns when inserting/updating data 211 | /// also ignores the column when selecting records 212 | /// useful for manipulating thin records by excluding huge binary blobs such as images 213 | pub fn exclude_column(&mut self, column: &str) { 214 | let c = ColumnName::from(column); 215 | self.excluded_columns.push(c); 216 | } 217 | pub fn exclude_columns(&mut self, columns: Vec<&str>) { 218 | for c in columns { 219 | self.exclude_column(c); 220 | } 221 | } 222 | 223 | pub fn distinct_on_columns(&mut self, columns: &Vec) { 224 | let columns = columns.clone(); 225 | for c in columns { 226 | self.distinct_on_columns.push(c); 227 | } 228 | } 229 | 230 | pub fn value(&mut self, value: &ToValue) { 231 | let value = value.to_db_type(); 232 | self.values.push(Operand::Value(value)); 233 | } 234 | 235 | pub fn set_limit(&mut self, limit: usize) { 236 | self.range.set_limit(limit); 237 | } 238 | pub fn set_offset(&mut self, offset: usize) { 239 | self.range.set_offset(offset); 240 | } 241 | 242 | pub fn get_range(&self) -> Range { 243 | self.range.to_owned() 244 | } 245 | 246 | /// enumerate only the columns that is coming from this table 247 | /// this will invalidate enumerate_all 248 | pub fn only_from(&mut self, table: &ToTableName) { 249 | self.enumerate_all = false; 250 | self.enumerate_from_table(&table.to_table_name()); 251 | } 252 | 253 | 254 | 255 | /// a query to query from 256 | /// use WITH (query) t1 SELECT from t1 declaration in postgresql, sqlite 257 | /// use SELECT FROM (query) in oracle, mysql, others 258 | /// alias of the table 259 | pub fn from_query(&mut self, query: Select, alias: &str) { 260 | let sf = SourceField { 261 | source: QuerySource::Query(query), 262 | rename: Some(alias.to_owned()), 263 | }; 264 | self.from.push(sf); 265 | } 266 | 267 | pub fn from(&mut self, to_source_field: &ToSourceField) { 268 | self.from.append(&mut to_source_field.to_source_field()); 269 | } 270 | 271 | pub fn table(&mut self, to_source_field: &ToSourceField) { 272 | self.from(to_source_field); 273 | } 274 | /// returns the first table in the from clause 275 | pub fn get_from_table(&self) -> Option { 276 | for fr in &self.from { 277 | match &fr.source { 278 | &QuerySource::TableName(ref table_name) => { 279 | return Some(table_name.to_owned()); 280 | } 281 | _ => {} 282 | } 283 | } 284 | None 285 | } 286 | 287 | 288 | /// get the indexes of the fields that matches the the column name 289 | fn match_fields_indexes(&self, column: &str) -> Vec { 290 | let mut indexes = vec![]; 291 | let mut cnt = 0; 292 | for field in &self.enumerated_fields { 293 | if let Operand::ColumnName(ref column_name) = field.operand { 294 | if column_name.column == column { 295 | indexes.push(cnt); 296 | } 297 | } 298 | cnt += 1; 299 | } 300 | indexes 301 | } 302 | 303 | /// take the enumerated field that is a column that matched the name 304 | fn rename_fields(&mut self, column: &str) { 305 | let matched_indexes = self.match_fields_indexes(column); 306 | for index in matched_indexes { 307 | let field = self.enumerated_fields.remove(index);//remove it 308 | let field = field.rename(); //rename it 309 | self.enumerated_fields.insert(index, field); //insert it back to the same location 310 | } 311 | } 312 | 313 | pub fn get_involved_tables(&self) -> Vec { 314 | let mut tables = vec![]; 315 | let from_table = self.get_from_table(); 316 | if let Some(from) = from_table { 317 | tables.push(from.clone()); 318 | } 319 | for j in &self.joins { 320 | if !tables.contains(&&j.table_name) { 321 | tables.push(j.table_name.clone()); 322 | } 323 | } 324 | tables 325 | } 326 | 327 | /// preprocess the missing fields of the query, 328 | /// such as mentioning the columns of the from_table 329 | /// enumerate the columns of the involved tables 330 | /// skipping those which are explicitly ignored 331 | /// the query will then be built and ready to be executed 332 | /// if no enumerated fields and no excluded columns 333 | /// do a select all 334 | pub fn finalize(&mut self) -> &Self { 335 | 336 | let involved_models = self.get_involved_tables(); 337 | if involved_models.len() > 1 { 338 | // enumerate all columns when there is a join 339 | if self.enumerate_all { 340 | self.enumerate_involved_tables_columns(&involved_models); 341 | } 342 | self.rename_conflicting_columns(); // rename an enumerated columns that conflicts 343 | } 344 | let excluded_columns = &self.excluded_columns.clone(); 345 | for i in excluded_columns { 346 | self.remove_from_enumerated(&i); 347 | } 348 | if self.excluded_columns.is_empty() && self.enumerated_fields.is_empty() { 349 | self.column_star(); 350 | } 351 | self 352 | } 353 | 354 | fn enumerate_involved_tables_columns(&mut self, involved_models: &Vec) { 355 | for m in involved_models { 356 | for c in &m.columns { 357 | self.enumerate(c.clone()); 358 | } 359 | } 360 | } 361 | 362 | pub fn enumerate_from_table(&mut self, table: &TableName) { 363 | for c in &table.columns { 364 | self.enumerate(c.clone()); 365 | } 366 | } 367 | 368 | fn get_renamed_fields(&self) -> Vec<&Field> { 369 | let mut renamed = vec![]; 370 | for field in &self.enumerated_fields { 371 | if field.name.is_some() { 372 | renamed.push(field); 373 | } 374 | } 375 | renamed 376 | } 377 | 378 | /// return the list of renamed columns, used in dao conversion to struc types 379 | pub fn get_renamed_columns(&self) -> Vec<(ColumnName, String)> { 380 | let mut renamed_columns = vec![]; 381 | let renamed_fields = self.get_renamed_fields(); 382 | for field in &renamed_fields { 383 | if let Operand::ColumnName(ref column_name) = field.operand { 384 | if let Some(ref rename) = field.name { 385 | renamed_columns.push((column_name.clone(), rename.to_owned())); 386 | } 387 | } 388 | } 389 | renamed_columns 390 | } 391 | 392 | /// determine which columns are conflicting and rename it accordingly 393 | /// rename only the columns that are in the enumerated list 394 | fn get_conflicting_columns(&self) -> Vec { 395 | let mut conflicts = vec![]; 396 | let enumerated_columns = self.get_enumerated_columns(); 397 | for c in &enumerated_columns { 398 | for d in &enumerated_columns { 399 | if c != d && c.is_conflicted(d) { 400 | conflicts.push(c.column.to_owned()); 401 | } 402 | } 403 | } 404 | conflicts 405 | } 406 | /// rename the fields that has a conflicting column 407 | fn rename_conflicting_columns(&mut self) { 408 | let conflicts = self.get_conflicting_columns(); 409 | for c in conflicts { 410 | self.rename_fields(&c); 411 | } 412 | } 413 | 414 | /// used by removed_from_enumerated 415 | fn index_of_field(&self, column: &ColumnName) -> Option { 416 | let mut cnt = 0; 417 | for field in &self.enumerated_fields { 418 | if let Operand::ColumnName(ref column_name) = field.operand { 419 | if column_name == column { 420 | return Some(cnt); 421 | } 422 | } 423 | cnt += 1; 424 | } 425 | None 426 | } 427 | 428 | fn remove_from_enumerated(&mut self, column_name: &ColumnName) { 429 | let index = self.index_of_field(column_name); 430 | if let Some(idx) = index { 431 | self.enumerated_fields.remove(idx); 432 | } 433 | } 434 | 435 | /// return the list of enumerated columns 436 | /// will be used for updating records 437 | pub fn get_enumerated_columns(&self) -> Vec<&ColumnName> { 438 | let mut columns = vec![]; 439 | for field in &self.enumerated_fields { 440 | if let Operand::ColumnName(ref column_name) = field.operand { 441 | columns.push(column_name); 442 | } 443 | } 444 | columns 445 | } 446 | 447 | 448 | pub fn add_filter(&mut self, filter: &Filter) { 449 | self.filters.push(filter.clone()); 450 | } 451 | pub fn add_filters(&mut self, filters: &Vec) { 452 | self.filters.extend_from_slice(filters) 453 | } 454 | 455 | /// column = value 456 | pub fn filter_eq(&mut self, column: &str, value: &ToValue) { 457 | self.add_filter(&Filter::new(column, Equality::EQ, value)); 458 | } 459 | /// column < value 460 | pub fn filter_lt(&mut self, column: &str, value: &ToValue) { 461 | self.add_filter(&Filter::new(column, Equality::LT, value)); 462 | } 463 | /// column <= value 464 | pub fn filter_lte(&mut self, column: &str, value: &ToValue) { 465 | self.add_filter(&Filter::new(column, Equality::LTE, value)); 466 | } 467 | 468 | /// column > value 469 | pub fn filter_gt(&mut self, column: &str, value: &ToValue) { 470 | self.add_filter(&Filter::new(column, Equality::GT, value)); 471 | } 472 | /// column <= value 473 | pub fn filter_gte(&mut self, column: &str, value: &ToValue) { 474 | self.add_filter(&Filter::new(column, Equality::GTE, value)); 475 | } 476 | 477 | 478 | pub fn return_all(&mut self) { 479 | self.enumerate_column_as_return("*"); 480 | } 481 | 482 | pub fn returns(&mut self, columns: Vec<&str>) { 483 | for c in columns { 484 | self.enumerate_column_as_return(c); 485 | } 486 | } 487 | 488 | pub fn enumerate_column_as_return(&mut self, column: &str) { 489 | let column_name = ColumnName::from(column); 490 | let operand = Operand::ColumnName(column_name); 491 | let field = Field { 492 | operand: operand, 493 | name: None, 494 | }; 495 | self.enumerated_returns.push(field); 496 | } 497 | 498 | 499 | /// retrieve a generic types, type is unknown at runtime 500 | /// expects a return, such as select, insert/update with returning clause 501 | pub fn retrieve(&mut self, db: &Database) -> Result { 502 | self.finalize(); 503 | db.execute_with_return(self) 504 | } 505 | 506 | /// expects a return, such as select, insert/update with returning clause 507 | /// no casting of data to structs is done 508 | /// This is used when retrieving multiple models in 1 query, then casting the records to its equivalent structs 509 | pub fn retrieve_one(&mut self, db: &Database) -> Result, DbError> { 510 | self.finalize(); 511 | db.execute_with_one_return(self) 512 | } 513 | 514 | 515 | /// execute the query, then convert the result 516 | pub fn collect(&mut self, db: &Database) -> Result, DbError> { 517 | let result = try!(self.retrieve(db)); 518 | Ok(result.cast()) 519 | } 520 | 521 | /// execute the query then collect only 1 record 522 | pub fn collect_one(&mut self, db: &Database) -> Result { 523 | let result = try!(self.retrieve(db)); 524 | match result.cast_one() { 525 | Some(res) => Ok(res), 526 | None => Err(DbError::new("No entry to collect found.")), 527 | } 528 | } 529 | } 530 | 531 | impl IsQuery for Select{ 532 | 533 | fn build(&mut self, db: &Database) -> SqlFrag{ 534 | self.finalize(); 535 | db.build_select(self, &BuildMode::Standard) 536 | } 537 | 538 | fn debug_build(&mut self, db: &Database) -> SqlFrag{ 539 | self.finalize(); 540 | db.build_select(self, &BuildMode::Debug) 541 | } 542 | } 543 | 544 | pub enum Data{ 545 | Values(Vec), 546 | Query(Select), 547 | } 548 | 549 | pub struct Insert{ 550 | pub into: TableName, 551 | pub columns: Vec, 552 | pub data: Data, 553 | pub return_columns: Vec 554 | } 555 | 556 | impl Insert{ 557 | 558 | pub fn into(table: &ToTableName) -> Self{ 559 | Insert{ 560 | into: table.to_table_name(), 561 | columns: vec![], 562 | data: Data::Values(vec![]), 563 | return_columns: vec![] 564 | } 565 | } 566 | 567 | pub fn columns(&mut self, columns: Vec<&str>){ 568 | for c in columns{ 569 | self.column(c); 570 | } 571 | } 572 | 573 | pub fn column(&mut self, column: &str){ 574 | self.columns.push(column.to_column_name()) 575 | } 576 | 577 | pub fn values(&mut self, operands: Vec<&ToOperand>){ 578 | for op in operands{ 579 | self.value(op); 580 | } 581 | } 582 | 583 | pub fn value(&mut self, operand: &ToOperand){ 584 | match self.data{ 585 | Data::Values(ref mut values) => { 586 | (*values).push(operand.to_operand()); 587 | }, 588 | Data::Query(_) => panic!("can not add value to query") 589 | } 590 | } 591 | 592 | pub fn return_columns(&mut self, columns: Vec<&str>){ 593 | self.return_columns.clear(); 594 | for c in columns{ 595 | self.return_column(c); 596 | } 597 | } 598 | 599 | fn return_column(&mut self, column: &str){ 600 | self.return_columns.push(column.to_column_name()); 601 | } 602 | 603 | pub fn return_all(&mut self){ 604 | self.return_columns(vec!["*"]); 605 | } 606 | 607 | pub fn debug_build(&mut self, db: &Database) -> SqlFrag { 608 | db.build_insert(&self, &BuildMode::Debug) 609 | } 610 | 611 | pub fn insert(&mut self, db: &Database) -> Result{ 612 | let result = db.insert(self); 613 | match result{ 614 | Ok(res) => Ok(D::from_dao(&res)), 615 | Err(e) => Err(e), 616 | } 617 | } 618 | 619 | /// set a value of a column when inserting/updating records 620 | pub fn set(&mut self, column: &str, value: &ToValue) { 621 | self.column(&column); 622 | self.value(&Operand::Value(value.to_db_type())); 623 | } 624 | } 625 | 626 | pub struct Update{ 627 | pub table: TableName, 628 | pub columns: Vec, 629 | pub values: Vec, 630 | pub filters: Vec, 631 | pub return_columns: Vec 632 | } 633 | 634 | impl Update{ 635 | 636 | pub fn table(table: &ToTableName) -> Self{ 637 | Update{ 638 | table: table.to_table_name(), 639 | columns: vec![], 640 | values: vec![], 641 | filters: vec![], 642 | return_columns: vec![], 643 | } 644 | } 645 | 646 | pub fn add_filter(&mut self, filter: &Filter){ 647 | self.filters.push(filter.clone()); 648 | } 649 | 650 | pub fn add_filters(&mut self, filters: &Vec){ 651 | self.filters.extend_from_slice(filters); 652 | } 653 | 654 | pub fn columns(&mut self, columns: &Vec){ 655 | self.columns = columns.to_owned(); 656 | } 657 | 658 | pub fn value(&mut self, operand: &ToOperand){ 659 | self.values.push(operand.to_operand()); 660 | } 661 | 662 | pub fn return_all(&mut self){ 663 | self.return_columns = vec![ColumnName::from("*")]; 664 | } 665 | 666 | pub fn column(&mut self, column: &ToColumnName){ 667 | self.columns.push(column.to_column_name()); 668 | } 669 | 670 | /// set a value of a column when inserting/updating records 671 | pub fn set(&mut self, column: &str, value: &ToValue) { 672 | self.column(&column); 673 | self.value(&Operand::Value(value.to_db_type())); 674 | } 675 | pub fn update(&mut self, db: &Database) -> Result{ 676 | let result = db.update(self); 677 | match result{ 678 | Ok(res) => Ok(D::from_dao(&res)), 679 | Err(e) => Err(e), 680 | } 681 | } 682 | } 683 | 684 | pub struct Delete{ 685 | pub from_table: TableName, 686 | pub filters: Vec 687 | } 688 | 689 | impl Delete{ 690 | 691 | pub fn from(table: &ToTableName) -> Self{ 692 | Delete{ 693 | from_table: table.to_table_name(), 694 | filters: vec![] 695 | } 696 | } 697 | 698 | pub fn add_filter(&mut self, filter: &Filter){ 699 | self.filters.push(filter.clone()) 700 | } 701 | pub fn add_filters(&mut self, filter: Vec){ 702 | self.filters.extend(filter) 703 | } 704 | 705 | pub fn execute(&self, db: &Database) -> Result { 706 | db.delete(self) 707 | } 708 | } 709 | 710 | impl IsQuery for Delete{ 711 | fn build(&mut self, db: &Database) -> SqlFrag{ 712 | db.build_delete(self, &BuildMode::Standard) 713 | } 714 | 715 | fn debug_build(&mut self, db: &Database) -> SqlFrag { 716 | db.build_delete(self, &BuildMode::Debug) 717 | } 718 | } 719 | 720 | pub struct CreateTable{ 721 | pub table: TableName, 722 | pub column: Vec 723 | } 724 | 725 | pub struct DropTable{ 726 | pub table: TableName, 727 | pub force: bool, 728 | } 729 | 730 | pub struct CreateSchema{ 731 | pub schema: String, 732 | } 733 | 734 | pub struct DropSchema{ 735 | pub schema: String, 736 | pub force: bool 737 | } 738 | 739 | pub struct CreateFunction{ 740 | pub function: Function 741 | } 742 | 743 | //TODO: add/drop index 744 | // on update, on delete, on insert 745 | pub struct AlterTable{ 746 | pub table: String, 747 | } 748 | 749 | pub enum TableOrColumn{ 750 | Table(TableName), 751 | Column(ColumnName) 752 | } 753 | 754 | pub struct Comment{ 755 | pub target: TableOrColumn, 756 | pub comment: String 757 | } 758 | 759 | pub struct CopyTable{ 760 | pub table: String, 761 | pub dao: Vec 762 | } 763 | 764 | 765 | pub struct CreateExtension{ 766 | pub extension: String, 767 | } 768 | 769 | -------------------------------------------------------------------------------- /src/platform/sqlite.rs: -------------------------------------------------------------------------------- 1 | use dao::Dao; 2 | use dao::Value; 3 | use database::{Database, DatabaseDev, BuildMode}; 4 | use writer::SqlFrag; 5 | use database::SqlOption; 6 | use rusqlite::Connection as SqliteConnection; 7 | use rusqlite::types::ToSql; 8 | use rusqlite::Row as SqliteRow; 9 | use table::{Table, Column, Foreign}; 10 | use database::DatabaseDDL; 11 | use database::DbError; 12 | use r2d2::PooledConnection; 13 | use r2d2_sqlite::SqliteConnectionManager; 14 | use regex::Regex; 15 | use std::collections::BTreeMap; 16 | use dao::Type; 17 | use query::Operand; 18 | use query::Insert; 19 | use query::Update; 20 | use query::Delete; 21 | 22 | 23 | pub fn establish_connection(db_path: &str) -> Result { 24 | SqliteConnection::open(db_path) 25 | .map_err(|e|{e.into()}) 26 | } 27 | 28 | pub struct Sqlite { 29 | pool: Option>, 30 | } 31 | 32 | impl Sqlite { 33 | pub fn new() -> Self { 34 | Sqlite { pool: None } 35 | } 36 | 37 | pub fn with_pooled_connection(pool: PooledConnection) -> Self { 38 | Sqlite { pool: Some(pool) } 39 | } 40 | 41 | fn from_rust_type_tosql<'a>(&self, types: &'a [Value]) -> Vec<&'a ToSql> { 42 | let mut params: Vec<&ToSql> = vec![]; 43 | for t in types { 44 | match t { 45 | &Value::String(ref x) => { 46 | params.push(x); 47 | } 48 | _ => panic!("not yet here {:?}", t), 49 | } 50 | } 51 | params 52 | } 53 | 54 | pub fn get_connection(&self) -> &SqliteConnection { 55 | match self.pool { 56 | Some(ref pool) => &pool, 57 | None => panic!("No connection for this database"), 58 | } 59 | } 60 | 61 | /// convert a record of a row into rust type 62 | fn from_sql_to_rust_type(&self, row: &SqliteRow, index: usize) -> Option { 63 | let value = row.get_checked(index as i32); 64 | match value { 65 | Ok(value) => Some(Value::String(value)), 66 | Err(_) => None, 67 | } 68 | } 69 | 70 | /// 71 | /// convert rust data type names to database data type names 72 | /// will be used in generating SQL for table creation 73 | /// FIXME, need to restore the exact data type as before 74 | fn rust_type_to_dbtype(&self, rust_type: &Type) -> String { 75 | 76 | let rust_type = match *rust_type { 77 | Type::Bool => "boolean".to_owned(), 78 | Type::I8 => "integer".to_owned(), 79 | Type::I16 => "integer".to_owned(), 80 | Type::I32 => "integer".to_owned(), 81 | Type::U32 => "integer".to_owned(), 82 | Type::I64 => "integer".to_owned(), 83 | Type::F32 => "real".to_owned(), 84 | Type::F64 => "real".to_owned(), 85 | Type::String => "text".to_owned(), 86 | Type::VecU8 => "blob".to_owned(), 87 | Type::Json => "text".to_owned(), 88 | Type::Uuid => "text".to_owned(), 89 | Type::DateTime => "datetime".to_owned(), 90 | _ => { 91 | panic!("Unable to get the equivalent database data type for {:?}", 92 | rust_type) 93 | } 94 | }; 95 | rust_type 96 | 97 | } 98 | 99 | /// get the foreign keys of table 100 | fn get_foreign_keys(&self, _schema: &str, table: &str) -> Vec { 101 | debug!("Extracting foreign keys..."); 102 | let sql = format!("PRAGMA foreign_key_list({});", table); 103 | let result: Result, DbError> = self.execute_sql_with_return(&sql, &vec![]); 104 | 105 | match result { 106 | Ok(result) => { 107 | let mut foreigns = vec![]; 108 | for r in result { 109 | let table: Option<&Value> = r.get("table"); 110 | let table = match table { 111 | Some(table) => { 112 | match table { 113 | &Value::String(ref table) => table.to_owned(), 114 | _ => unreachable!(), 115 | } 116 | } 117 | None => "".to_owned(), 118 | }; 119 | let from: Option<&Value> = r.get("from"); 120 | let from = match from { 121 | Some(from) => { 122 | match from { 123 | &Value::String(ref from) => from.to_owned(), 124 | _ => unreachable!(), 125 | } 126 | } 127 | None => "".to_owned(), 128 | }; 129 | let to: Option<&Value> = r.get("to"); 130 | let to = match to { 131 | Some(to) => { 132 | match to { 133 | &Value::String(ref to) => to.to_owned(), 134 | _ => unreachable!(), 135 | } 136 | } 137 | None => "".to_owned(), 138 | }; 139 | debug!("table: {}", table); 140 | debug!("from: {}", from); 141 | debug!("to: {}", to); 142 | 143 | let foreign = Foreign { 144 | schema: None, 145 | table: table.to_owned(), 146 | column: to.to_owned(), 147 | }; 148 | foreigns.push(foreign); 149 | 150 | } 151 | foreigns 152 | } 153 | Err(_) => vec![], 154 | } 155 | 156 | } 157 | 158 | pub fn extract_comments 159 | (create_sql: &str) 160 | -> Result<(Option, BTreeMap>), DbError> { 161 | let re = try!(Regex::new(r".*CREATE\s+TABLE\s+(\S+)\s*\((?s)(.*)\).*")); 162 | debug!("create_sql: {:?}", create_sql); 163 | if re.is_match(&create_sql) { 164 | debug!("matched..."); 165 | let cap = re.captures(&create_sql).unwrap(); 166 | let all_columns = cap.at(2).unwrap(); 167 | 168 | let line_comma_re = try!(Regex::new(r"[,\n]")); 169 | debug!("All columns.. {}", all_columns); 170 | let splinters: Vec<&str> = line_comma_re.split(all_columns).collect(); 171 | debug!("splinters: {:#?}", splinters); 172 | let splinters: Vec<&str> = splinters.into_iter() 173 | .map(|i| i.trim()) 174 | .filter(|&i| i != "") 175 | .collect(); 176 | debug!("filtered: {:#?}", splinters); 177 | let mut columns: Vec = vec![]; 178 | let mut comments: Vec> = vec![]; 179 | let mut index = 0; 180 | for splinter in splinters { 181 | if splinter.starts_with("--") { 182 | if comments.len() < index { 183 | for _ in comments.len()..index { 184 | comments.push(None); 185 | } 186 | } 187 | comments.push(Some(splinter.to_owned())); 188 | } else if splinter.starts_with("FOREIGN") { 189 | 190 | } else if splinter.starts_with("CHECK") { 191 | 192 | } else { 193 | let line: Vec<&str> = splinter.split_whitespace().collect(); 194 | let column = line[0]; 195 | debug!("column: {}", column); 196 | columns.push(column.to_owned()); 197 | index += 1 198 | } 199 | } 200 | debug!("columns: {:#?}", columns); 201 | debug!("comments: {:#?}", comments); 202 | let table_comment = if comments.len() > 0 { 203 | comments[0].clone() //first comment is the table comment 204 | } else { 205 | None 206 | }; 207 | let mut column_comments = BTreeMap::new(); 208 | let mut index = 0; 209 | for column in columns { 210 | let comment = if comments.len() > 0 { 211 | comments[index + 1].clone() 212 | } else { 213 | None 214 | }; 215 | column_comments.insert(column, comment); 216 | index += 1; 217 | } 218 | Ok((table_comment, column_comments)) 219 | } else { 220 | Err(DbError::new("Unable to parse sql statement")) 221 | } 222 | } 223 | /// extract the comment of the table 224 | /// Don't support multi-line comment 225 | fn get_table_comment(&self, _schema: &str, table: &str) -> Option { 226 | let sql = format!("SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = '{}'", 227 | table); 228 | let result = self.execute_sql_with_return(&sql, &vec![]); 229 | match result { 230 | Ok(result) => { 231 | assert_eq!(result.len(), 1); 232 | let ref dao = result[0]; 233 | match dao.get("sql") { 234 | Some(create_sql) => { 235 | match create_sql { 236 | &Value::String(ref create_sql) => { 237 | match Sqlite::extract_comments(&create_sql) { 238 | Ok((table_comment, _column_comments)) => { 239 | debug!("table_comment: {:?}", table_comment); 240 | table_comment 241 | } 242 | Err(_) => None, 243 | } 244 | } 245 | _ => None, 246 | } 247 | } 248 | None => None, 249 | } 250 | 251 | } 252 | Err(_) => None, 253 | } 254 | } 255 | /// extract the comments for each column 256 | /// Don't support multi-line comment 257 | fn get_column_comments(&self, _schema: &str, table: &str) -> BTreeMap> { 258 | let sql = format!("SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = '{}'", 259 | table); 260 | let result = self.execute_sql_with_return(&sql, &vec![]); 261 | let map = BTreeMap::new(); 262 | match result { 263 | Ok(result) => { 264 | assert_eq!(result.len(), 1); 265 | let ref dao = result[0]; 266 | match dao.get("sql") { 267 | Some(create_sql) => { 268 | match create_sql { 269 | &Value::String(ref create_sql) => { 270 | match Sqlite::extract_comments(&create_sql) { 271 | Ok((_table_comment, column_comments)) => { 272 | debug!("column_comments: {:?}", column_comments); 273 | column_comments 274 | } 275 | Err(_) => map, 276 | } 277 | } 278 | _ => map, 279 | } 280 | } 281 | None => map, 282 | } 283 | } 284 | Err(_) => map, 285 | } 286 | } 287 | 288 | fn get_column_comment(&self, 289 | column_comments: &BTreeMap>, 290 | column: &str) 291 | -> Option { 292 | match column_comments.get(column) { 293 | Some(comment) => comment.clone(), 294 | None => None, 295 | } 296 | 297 | } 298 | fn get_column_foreign(&self, all_foreign: &[Foreign], column: &str) -> Option { 299 | debug!("foreign: {:#?} ", all_foreign); 300 | for foreign in all_foreign { 301 | if foreign.column == column { 302 | return Some(foreign.clone()); 303 | } 304 | } 305 | None 306 | } 307 | } 308 | 309 | impl Database for Sqlite { 310 | fn version(&self) -> Result { 311 | let sql = "SELECT sqlite_version() AS version"; 312 | let dao = try!(self.execute_sql_with_one_return(sql, &vec![])); 313 | match dao { 314 | Some(dao) => { 315 | let version: Option<&Value> = dao.get("version"); 316 | match version { 317 | Some(version) => { 318 | match version { 319 | &Value::String(ref version) => Ok(version.to_owned()), 320 | _ => Err(DbError::new("no version")), 321 | } 322 | } 323 | None => Err(DbError::new("no version")), 324 | } 325 | } 326 | None => Err(DbError::new("Unable to get database version")), 327 | } 328 | } 329 | 330 | fn begin(&self) { 331 | let _ = self.execute_sql("BEGIN TRANSACTION", &[]); 332 | } 333 | fn commit(&self) { 334 | let _ = self.execute_sql("COMMIT TRANSACTION", &[]); 335 | } 336 | fn rollback(&self) { 337 | let _ = self.execute_sql("ROLLBACK TRANSACTION", &[]); 338 | } 339 | 340 | /// return this list of options, supported features in the database 341 | fn sql_options(&self) -> Vec { 342 | vec![ 343 | SqlOption::UsesNumberedParam, // uses numbered parameters 344 | SqlOption::SupportsCTE, 345 | ] 346 | } 347 | 348 | fn insert(&self, query: &Insert) -> Result { 349 | let sql_frag = self.build_insert(query, &BuildMode::Standard); 350 | match self.execute_sql_with_one_return(&sql_frag.sql, &sql_frag.params) { 351 | Ok(Some(result)) => Ok(result), 352 | Ok(None) => Err(DbError::new("No result from insert")), 353 | Err(e) => Err(e), 354 | } 355 | } 356 | fn update(&self, query: &Update) -> Result { 357 | let sql_frag = self.build_update(query, &BuildMode::Standard); 358 | match self.execute_sql_with_one_return(&sql_frag.sql, &sql_frag.params) { 359 | Ok(Some(result)) => Ok(result), 360 | Ok(None) => Err(DbError::new("No result from insert")), 361 | Err(e) => Err(e), 362 | } 363 | } 364 | fn delete(&self, query: &Delete) -> Result { 365 | let sql_frag = &self.build_delete(query, &BuildMode::Standard); 366 | self.execute_sql(&sql_frag.sql, &sql_frag.params) 367 | } 368 | 369 | /// sqlite does not return the columns mentioned in the query, 370 | /// you have to specify it yourself 371 | /// TODO: found this 372 | /// http://jgallagher.github.io/rusqlite/rusqlite/struct.SqliteStatement.html#method.column_names 373 | fn execute_sql_with_return(&self, sql: &str, params: &[Value]) -> Result, DbError> { 374 | debug!("SQL: \n{}", sql); 375 | debug!("param: {:?}", params); 376 | let conn = self.get_connection(); 377 | let mut stmt = conn.prepare(sql).unwrap(); 378 | let mut daos = vec![]; 379 | let param = self.from_rust_type_tosql(params); 380 | let mut columns = vec![]; 381 | for c in stmt.column_names() { 382 | columns.push(c.to_owned()); 383 | } 384 | debug!("columns : {:?}", columns); 385 | let mut rows = try!(stmt.query(¶m)); 386 | while let Some(row) = rows.next() { 387 | let row = try!(row); 388 | let mut index = 0; 389 | let mut dao = Dao::new(); 390 | for col in &columns { 391 | let rtype = self.from_sql_to_rust_type(&row, index); 392 | debug!("{:?}", rtype); 393 | if let Some(rtype) = rtype { 394 | dao.insert(col.to_owned(), rtype); 395 | } 396 | index += 1; 397 | } 398 | daos.push(dao); 399 | } 400 | Ok(daos) 401 | } 402 | 403 | 404 | fn execute_sql_with_one_return(&self, 405 | sql: &str, 406 | params: &[Value]) 407 | -> Result, DbError> { 408 | let dao = try!(self.execute_sql_with_return(sql, params)); 409 | if dao.len() >= 1 { 410 | return Ok(Some(dao[0].clone())); 411 | } else { 412 | return Ok(None); 413 | } 414 | } 415 | 416 | /// generic execute sql which returns not much information, 417 | /// returns only the number of affected records or errors 418 | /// can be used with DDL operations (CREATE, DELETE, ALTER, DROP) 419 | fn execute_sql(&self, sql: &str, params: &[Value]) -> Result { 420 | debug!("SQL: \n{}", sql); 421 | debug!("param: {:?}", params); 422 | let to_sql_types = self.from_rust_type_tosql(params); 423 | let conn = self.get_connection(); 424 | let result = conn.execute(sql, &to_sql_types); 425 | match result { 426 | Ok(result) => Ok(result as usize), 427 | Err(e) => Err(DbError::new(&format!("Something is wrong, {}", e))), 428 | } 429 | } 430 | } 431 | 432 | impl DatabaseDDL for Sqlite { 433 | fn create_schema(&self, _schema: &str) { 434 | panic!("sqlite does not support schema") 435 | } 436 | 437 | fn drop_schema(&self, _schema: &str) { 438 | panic!("sqlite does not support schema") 439 | } 440 | 441 | fn build_create_table(&self, table: &Table) -> SqlFrag { 442 | 443 | 444 | let mut w = SqlFrag::new(self.sql_options(), &BuildMode::Standard); 445 | w.append("CREATE TABLE "); 446 | w.append(&table.name); 447 | w.append("("); 448 | w.ln_tab(); 449 | let mut do_comma = false; 450 | for c in &table.columns { 451 | if do_comma { 452 | w.commasp(); 453 | w.ln_tab(); 454 | } else { 455 | do_comma = true; 456 | } 457 | w.append(&c.name); 458 | w.append(" "); 459 | let dt = self.rust_type_to_dbtype(&c.data_type); 460 | w.append(&dt); 461 | if c.is_primary { 462 | w.append(" PRIMARY KEY "); 463 | } 464 | } 465 | let mut do_comma = true;//there has been colcommentsumns mentioned 466 | for c in &table.columns { 467 | if let Some(ref foreign) = c.foreign { 468 | if do_comma { 469 | w.commasp(); 470 | } else { 471 | do_comma = true; 472 | } 473 | w.ln_tab(); 474 | w.append("FOREIGN KEY"); 475 | w.append(&format!("({})", c.name)); 476 | w.append(" REFERENCES "); 477 | w.append(&format!("{}", foreign.table)); 478 | w.append(&format!("({})", foreign.column)); 479 | } 480 | } 481 | w.ln(); 482 | w.append(")"); 483 | w 484 | } 485 | fn create_table(&self, table: &Table) { 486 | let frag = self.build_create_table(table); 487 | match self.execute_sql(&frag.sql, &vec![]) { 488 | Ok(_) => debug!("created table.."), 489 | Err(e) => panic!("table not created {}", e), 490 | } 491 | } 492 | 493 | fn rename_table(&self, _table: &Table, _new_tablename: String) { 494 | unimplemented!() 495 | } 496 | 497 | fn drop_table(&self, _table: &Table) { 498 | unimplemented!() 499 | } 500 | 501 | fn set_foreign_constraint(&self, _model: &Table) { 502 | unimplemented!() 503 | } 504 | 505 | fn set_primary_constraint(&self, _model: &Table) { 506 | unimplemented!() 507 | } 508 | } 509 | 510 | impl DatabaseDev for Sqlite { 511 | fn get_table_sub_class(&self, _schema: &str, _table: &str) -> Vec { 512 | unimplemented!() 513 | } 514 | 515 | fn get_parent_table(&self, _schema: &str, _table: &str) -> Option { 516 | unimplemented!() 517 | } 518 | 519 | fn get_row_count_estimate(&self, _: &str, table: &str) -> Option { 520 | let sql = format!("SELECT count (*) as count from {}",table); 521 | let result = self.execute_sql_with_one_return(&sql, &vec![]); 522 | match result { 523 | Ok(Some(result)) => { 524 | let value = result.get("count"); 525 | match value { 526 | Some(&Value::I64(v)) => Some(v as usize), 527 | Some(&Value::I32(v)) => Some(v as usize), 528 | Some(&Value::I16(v)) => Some(v as usize), 529 | Some(&Value::U64(v)) => Some(v as usize), 530 | Some(&Value::U32(v)) => Some(v as usize), 531 | Some(&Value::U16(v)) => Some(v as usize), 532 | _ => None, 533 | } 534 | } 535 | _ => None, 536 | } 537 | } 538 | 539 | fn get_table_metadata(&self, schema: &str, table: &str, _is_view: bool) -> Table { 540 | debug!("extracting table meta data in sqlite"); 541 | let sql = format!("PRAGMA table_info({});", table); 542 | let result = self.execute_sql_with_return(&sql, &vec![]); 543 | let row_count = self.get_row_count_estimate(schema, table); 544 | debug!("result: {:#?}", result); 545 | match result { 546 | Ok(result) => { 547 | let foreign = self.get_foreign_keys(schema, table); 548 | let table_comment = self.get_table_comment(schema, table); 549 | let column_comments = self.get_column_comments(schema, table); 550 | 551 | let mut columns = vec![]; 552 | for r in result { 553 | let column: String = match r.get("name") { 554 | Some(name) => { 555 | match name { 556 | &Value::String(ref name) => name.to_owned(), 557 | _ => unreachable!(), 558 | } 559 | } 560 | None => "".to_owned(), 561 | }; 562 | let db_data_type: String = match r.get("type") { 563 | Some(db_data_type) => { 564 | match db_data_type { 565 | &Value::String(ref db_data_type) => db_data_type.to_owned(), 566 | _ => unreachable!(), 567 | } 568 | } 569 | None => "".to_owned(), 570 | }; 571 | 572 | let default_value: String = match r.get("dflt_value") { 573 | Some(default_value) => { 574 | match default_value { 575 | &Value::String(ref default_value) => default_value.to_owned(), 576 | _ => unreachable!(), 577 | } 578 | } 579 | None => "".to_owned(), 580 | }; 581 | let not_null: String = match r.get("notnull") { 582 | Some(not_null) => { 583 | match not_null { 584 | &Value::String(ref not_null) => not_null.to_owned(), 585 | _ => unreachable!(), 586 | } 587 | } 588 | None => "".to_owned(), 589 | }; 590 | let pk: String = match r.get("pk") { 591 | Some(pk) => { 592 | match pk { 593 | &Value::String(ref pk) => pk.to_owned(), 594 | _ => unreachable!(), 595 | } 596 | } 597 | None => "".to_owned(), 598 | }; 599 | debug!("column: {}", column); 600 | debug!("data_type: {}", db_data_type); 601 | debug!("not null: {}", not_null); 602 | debug!("pk: {}", pk); 603 | debug!("default_value: {}", default_value); 604 | 605 | let column_comment = self.get_column_comment(&column_comments, &column); 606 | let column_foreign = self.get_column_foreign(&foreign, &column); 607 | let (_, data_type) = self.dbtype_to_rust_type(&db_data_type); 608 | let column = Column { 609 | table: Some(table.to_owned()), 610 | name: column, 611 | data_type: data_type, 612 | db_data_type: db_data_type, 613 | is_primary: pk != "0", 614 | is_unique: false, 615 | default: Some(Operand::Value(Value::String(default_value))), 616 | comment: column_comment, 617 | not_null: not_null != "0", 618 | is_inherited: false, 619 | foreign: column_foreign, 620 | }; 621 | columns.push(column); 622 | } 623 | Table { 624 | schema: None, 625 | name: table.to_owned(), 626 | parent_table: None, 627 | sub_table: vec![], 628 | comment: table_comment, 629 | columns: columns, 630 | is_view: false, 631 | estimated_row_count: row_count, 632 | } 633 | } 634 | Err(e) => { 635 | panic!("No matching table found {}", e); 636 | } 637 | } 638 | } 639 | 640 | fn get_all_tables(&self) -> Vec<(String, String, bool)> { 641 | let sql = "SELECT type, name, tbl_name, sql FROM sqlite_master WHERE type = 'table'"; 642 | let result = self.execute_sql_with_return(&sql, &vec![]); 643 | match result { 644 | Ok(result) => { 645 | let mut tables: Vec<(String, String, bool)> = Vec::new(); 646 | for r in result { 647 | let schema = "".to_owned(); 648 | let table: String = match r.get("tbl_name") { 649 | Some(table) => { 650 | match table { 651 | &Value::String(ref table) => table.to_owned(), 652 | _ => unreachable!(), 653 | } 654 | } 655 | None => "".to_owned(), 656 | }; 657 | let is_view = false; 658 | tables.push((schema, table, is_view)) 659 | } 660 | tables 661 | } 662 | Err(e) => panic!("Unable to get tables due to {}", e), 663 | } 664 | } 665 | 666 | fn get_inherited_columns(&self, _schema: &str, _table: &str) -> Vec { 667 | vec![] 668 | } 669 | 670 | fn dbtype_to_rust_type(&self, _db_type: &str) -> (Vec, Type) { 671 | unimplemented!() 672 | } 673 | 674 | fn rust_type_to_dbtype(&self, _rust_type: &Type) -> String { 675 | unimplemented!() 676 | } 677 | } 678 | 679 | 680 | #[test] 681 | fn test_comment_extract() { 682 | let create_sql = r" 683 | CREATE TABLE product_availability ( 684 | --Each product has its own product availability which determines when can it be available for purchase 685 | product_id uuid NOT NULL , --this is the id of the product 686 | available boolean, 687 | always_available boolean, 688 | stocks numeric DEFAULT 1, 689 | available_from timestamp with time zone, 690 | available_until timestamp with time zone, 691 | available_day json, 692 | open_time time with time zone, 693 | close_time time with time zone, --closing time 694 | FOREIGN KEY(product_id) REFERENCES product(product_id) 695 | ) 696 | "; 697 | let _ = Sqlite::extract_comments(create_sql); 698 | } 699 | -------------------------------------------------------------------------------- /src/platform/mysql.rs: -------------------------------------------------------------------------------- 1 | use dao::Dao; 2 | use table::{Table, Column, Foreign}; 3 | 4 | use dao::Value; 5 | use writer::SqlFrag; 6 | use database::{SqlOption, BuildMode}; 7 | use regex::Regex; 8 | 9 | use mysql::value::Value as MyValue; 10 | use mysql::consts::ColumnType; 11 | use mysql::value::FromValue; 12 | use mysql::value::IntoValue; 13 | use mysql::error::MyResult; 14 | use mysql::value::from_row; 15 | use mysql::conn::Stmt; 16 | use mysql::conn::pool::MyPool; 17 | use chrono::naive::datetime::NaiveDateTime; 18 | use chrono::datetime::DateTime; 19 | use chrono::offset::fixed::FixedOffset; 20 | use mysql::conn::{MyConn,MyOpts}; 21 | use config::DbConfig; 22 | 23 | use query::Operand; 24 | 25 | 26 | use database::{Database, DatabaseDev, DatabaseDDL, DbError}; 27 | use time::Timespec; 28 | use dao::Type; 29 | use query::Update; 30 | use query::Delete; 31 | 32 | 33 | pub fn establish_connection(config: &DbConfig) -> Result { 34 | let opts = MyOpts { 35 | user: config.username.clone(), 36 | pass: config.password.clone(), 37 | db_name: Some(config.database.clone()), 38 | tcp_addr: Some(config.host.clone().unwrap().to_string()), 39 | tcp_port: config.port.unwrap_or(3306), 40 | ..Default::default() 41 | }; 42 | MyConn::new(opts) 43 | .map_err(|e|{e.into()}) 44 | } 45 | 46 | pub struct Mysql { 47 | pool: Option, 48 | } 49 | impl Mysql { 50 | pub fn new() -> Self { 51 | Mysql { pool: None } 52 | } 53 | 54 | pub fn with_pooled_connection(pool: MyPool) -> Self { 55 | Mysql { pool: Some(pool) } 56 | } 57 | 58 | fn from_rust_type_tosql(types: &[Value]) -> Vec { 59 | let mut params: Vec = vec![]; 60 | for t in types { 61 | match *t { 62 | Value::Bool(ref x) => { 63 | let v = x.into_value(); 64 | params.push(v); 65 | } 66 | Value::String(ref x) => { 67 | params.push(MyValue::Bytes(x.as_bytes().to_owned())); 68 | } 69 | Value::I8(ref x) => { 70 | let v = x.into_value(); 71 | params.push(v); 72 | } 73 | Value::I16(ref x) => { 74 | let v = x.into_value(); 75 | params.push(v); 76 | } 77 | Value::I32(ref x) => { 78 | let v = x.into_value(); 79 | params.push(v); 80 | } 81 | Value::I64(ref x) => { 82 | let v = x.into_value(); 83 | params.push(v); 84 | } 85 | Value::U8(ref x) => { 86 | let v = x.into_value(); 87 | params.push(v); 88 | } 89 | Value::U32(ref x) => { 90 | let v = x.into_value(); 91 | params.push(v); 92 | } 93 | Value::U64(ref x) => { 94 | let v = x.into_value(); 95 | params.push(v); 96 | } 97 | Value::F32(ref x) => { 98 | let v = x.into_value(); 99 | params.push(v); 100 | } 101 | Value::F64(ref x) => { 102 | let v = x.into_value(); 103 | params.push(v); 104 | } 105 | _ => panic!("not yet here {:?}", t), 106 | } 107 | } 108 | params 109 | } 110 | 111 | /// convert a record of a row into rust type 112 | fn from_sql_to_rust_type(row: &[MyValue], 113 | index: usize, 114 | column_type: &ColumnType) 115 | -> Option { 116 | let value = row.get(index); 117 | match value { 118 | Some(value) => { 119 | match *column_type { 120 | ColumnType::MYSQL_TYPE_DECIMAL => { 121 | let v: f64 = FromValue::from_value(value.clone()); 122 | Some(Value::F64(v)) 123 | } 124 | ColumnType::MYSQL_TYPE_TINY => { 125 | let v: i8 = FromValue::from_value(value.clone()); 126 | Some(Value::I8(v)) 127 | } 128 | ColumnType::MYSQL_TYPE_SHORT => { 129 | let v: i16 = FromValue::from_value(value.clone()); 130 | Some(Value::I16(v)) 131 | } 132 | ColumnType::MYSQL_TYPE_LONG => { 133 | let v: i64 = FromValue::from_value(value.clone()); 134 | Some(Value::I64(v)) 135 | } 136 | ColumnType::MYSQL_TYPE_FLOAT => { 137 | let v: f32 = FromValue::from_value(value.clone()); 138 | Some(Value::F32(v)) 139 | } 140 | ColumnType::MYSQL_TYPE_DOUBLE => { 141 | let v: f64 = FromValue::from_value(value.clone()); 142 | Some(Value::F64(v)) 143 | } 144 | ColumnType::MYSQL_TYPE_NULL => None, 145 | ColumnType::MYSQL_TYPE_TIMESTAMP => { 146 | let v: Timespec = FromValue::from_value(value.clone()); 147 | let t = NaiveDateTime::from_timestamp(v.sec, v.nsec as u32); 148 | let fix = FixedOffset::east(1); 149 | let t2 = DateTime::from_utc(t, fix); 150 | Some(Value::DateTime(t2)) 151 | } 152 | ColumnType::MYSQL_TYPE_LONGLONG => { 153 | let v: i64 = FromValue::from_value(value.clone()); 154 | Some(Value::I64(v)) 155 | } 156 | ColumnType::MYSQL_TYPE_INT24 => { 157 | let v: i32 = FromValue::from_value(value.clone()); 158 | Some(Value::I32(v)) 159 | } 160 | ColumnType::MYSQL_TYPE_DATE => { 161 | let v: Timespec = FromValue::from_value(value.clone()); 162 | let t = NaiveDateTime::from_timestamp(v.sec, v.nsec as u32); 163 | let fix = FixedOffset::east(1); 164 | let t2 = DateTime::from_utc(t, fix); 165 | Some(Value::DateTime(t2)) 166 | } 167 | ColumnType::MYSQL_TYPE_TIME => { 168 | let v: Timespec = FromValue::from_value(value.clone()); 169 | let t = NaiveDateTime::from_timestamp(v.sec, v.nsec as u32); 170 | let fix = FixedOffset::east(1); 171 | let t2 = DateTime::from_utc(t, fix); 172 | Some(Value::DateTime(t2)) 173 | } 174 | ColumnType::MYSQL_TYPE_DATETIME => { 175 | let v: Timespec = FromValue::from_value(value.clone()); 176 | let t = NaiveDateTime::from_timestamp(v.sec, v.nsec as u32); 177 | let fix = FixedOffset::east(1); 178 | let t2 = DateTime::from_utc(t, fix); 179 | Some(Value::DateTime(t2)) 180 | } 181 | ColumnType::MYSQL_TYPE_YEAR => { 182 | let v: Timespec = FromValue::from_value(value.clone()); 183 | let t = NaiveDateTime::from_timestamp(v.sec, v.nsec as u32); 184 | let fix = FixedOffset::east(1); 185 | let t2 = DateTime::from_utc(t, fix); 186 | Some(Value::DateTime(t2)) 187 | } 188 | ColumnType::MYSQL_TYPE_VARCHAR => { 189 | let v: String = FromValue::from_value(value.clone()); 190 | Some(Value::String(v)) 191 | } 192 | ColumnType::MYSQL_TYPE_BIT => unimplemented!(), 193 | ColumnType::MYSQL_TYPE_NEWDECIMAL => { 194 | let v: f64 = FromValue::from_value(value.clone()); 195 | Some(Value::F64(v)) 196 | } 197 | ColumnType::MYSQL_TYPE_ENUM => { 198 | let v: String = FromValue::from_value(value.clone()); 199 | Some(Value::String(v)) 200 | } 201 | ColumnType::MYSQL_TYPE_SET => { 202 | let v: String = FromValue::from_value(value.clone()); 203 | Some(Value::String(v)) 204 | } 205 | ColumnType::MYSQL_TYPE_TINY_BLOB => { 206 | let v: String = FromValue::from_value(value.clone()); 207 | Some(Value::String(v)) 208 | } 209 | ColumnType::MYSQL_TYPE_MEDIUM_BLOB => { 210 | let v: String = FromValue::from_value(value.clone()); 211 | Some(Value::String(v)) 212 | } 213 | ColumnType::MYSQL_TYPE_LONG_BLOB => { 214 | let v: String = FromValue::from_value(value.clone()); 215 | Some(Value::String(v)) 216 | } 217 | ColumnType::MYSQL_TYPE_BLOB => { 218 | let v: String = FromValue::from_value(value.clone()); 219 | Some(Value::String(v)) 220 | } 221 | ColumnType::MYSQL_TYPE_VAR_STRING => { 222 | let v: String = FromValue::from_value(value.clone()); 223 | Some(Value::String(v)) 224 | } 225 | ColumnType::MYSQL_TYPE_STRING => { 226 | let v: String = FromValue::from_value(value.clone()); 227 | Some(Value::String(v)) 228 | } 229 | ColumnType::MYSQL_TYPE_GEOMETRY => { 230 | let v: String = FromValue::from_value(value.clone()); 231 | Some(Value::String(v)) 232 | } 233 | }//<--match column_type 234 | } //<-- Some(Value) 235 | None => None, 236 | } 237 | } 238 | 239 | /// 240 | /// convert rust data type names to database data type names 241 | /// will be used in generating SQL for table creation 242 | /// FIXME, need to restore the exact data type as before 243 | fn rust_type_to_dbtype(&self, rust_type: &Type) -> String { 244 | match *rust_type { 245 | Type::Bool => "bool".to_owned(), 246 | Type::I8 => "tinyint(1)".to_owned(), 247 | Type::I16 => "integer".to_owned(), 248 | Type::I32 => "integer".to_owned(), 249 | Type::U32 => "integer".to_owned(), 250 | Type::I64 => "integer".to_owned(), 251 | Type::F32 => "real".to_owned(), 252 | Type::F64 => "real".to_owned(), 253 | Type::String => "text".to_owned(), 254 | Type::VecU8 => "blob".to_owned(), 255 | Type::Json => "text".to_owned(), 256 | Type::Uuid => "varchar(36)".to_owned(), 257 | Type::DateTime => "timestamp".to_owned(), 258 | _ => { 259 | panic!("Unable to get the equivalent database data type for {:?}", 260 | rust_type) 261 | } 262 | } 263 | } 264 | 265 | fn get_table_columns(&self, schema: &str, table: &str) -> Vec { 266 | let sql = format!("select column_name, data_type from information_schema.columns where table_schema='{}' and table_name='{}'", schema, table); 267 | assert!(self.pool.is_some()); 268 | let mut stmt = match self.get_prepared_statement(&sql) { 269 | Ok(stmt) => stmt, 270 | Err(_) => panic!("prepare statement error."), 271 | }; 272 | let mut columns = Vec::new(); 273 | for row in stmt.execute(()).unwrap() { 274 | // println!("{:?}", row); 275 | let (name, db_data_type) = from_row::<(String, String)>(row.unwrap()); 276 | let not_null = false; 277 | // let name: String = row.get("column_name"); 278 | // let not_null: bool = row.get("is_nullable"); 279 | // let db_data_type: String = row.get("data_type"); 280 | // TODO: temporarily regex the data type to extract the size as well 281 | let re = match Regex::new("(.+)\\((.+)\\)") { 282 | Ok(re) => re, 283 | Err(err) => panic!("{}", err), 284 | }; 285 | 286 | let db_data_type = if re.is_match(&db_data_type) { 287 | let cap = re.captures(&db_data_type).unwrap(); 288 | let data_type = cap.at(1).unwrap().to_owned(); 289 | // TODO::can be use in the later future 290 | // let size = cap.at(2).unwrap().to_owned(); 291 | data_type 292 | } else { 293 | db_data_type 294 | }; 295 | 296 | // let is_primary: bool = row.get("is_primary"); 297 | // let is_unique: bool = row.get("is_unique"); 298 | let is_primary: bool = false; 299 | let is_unique: bool = false; 300 | 301 | // let default: Option = match row.get_opt("default") { 302 | // Ok(x) => Some(Operand::Value(Value::String(x))), 303 | // Err(_) => None, 304 | // }; 305 | let default: Option = None; 306 | // let comment: Option = match row.get_opt("comment") { 307 | // Ok(x) => Some(x), 308 | // Err(_) => None, 309 | // }; 310 | let comment: Option = None; 311 | 312 | // let foreign_schema: Option = match row.get_opt("foreign_schema") { 313 | // Ok(x) => Some(x), 314 | // Err(_) => None, 315 | // }; 316 | let foreign_schema: Option = None; 317 | 318 | // let foreign_column: Option = match row.get_opt("foreign_column") { 319 | // Ok(x) => Some(x), 320 | // Err(_) => None, 321 | // }; 322 | let foreign_column: Option = None; 323 | 324 | // let foreign_table: Option = match row.get_opt("foreign_table") { 325 | // Ok(x) => Some(x), 326 | // Err(_) => None, 327 | // }; 328 | let foreign_table: Option = None; 329 | 330 | let foreign = if foreign_table.is_some() && foreign_column.is_some() && 331 | foreign_schema.is_some() { 332 | Some(Foreign { 333 | schema: foreign_schema, 334 | table: foreign_table.unwrap(), 335 | column: foreign_column.unwrap(), 336 | }) 337 | 338 | } else { 339 | None 340 | }; 341 | let (_, data_type) = self.dbtype_to_rust_type(&db_data_type); 342 | let column = Column { 343 | table: Some(table.to_owned()), 344 | name: name, 345 | data_type: data_type, 346 | db_data_type: db_data_type, 347 | comment: comment, 348 | is_primary: is_primary, 349 | is_unique: is_unique, 350 | default: default, 351 | not_null: not_null, 352 | foreign: foreign, 353 | is_inherited: false, // will be corrected later in the get_meta_data 354 | }; 355 | columns.push(column); 356 | } 357 | 358 | columns 359 | 360 | } 361 | 362 | fn get_table_comment(&self, schema: &str, table: &str) -> Option { 363 | println!("{} {}", schema, table); 364 | None 365 | } 366 | 367 | 368 | fn get_prepared_statement<'a>(&'a self, sql: &'a str) -> MyResult { 369 | self.pool.as_ref().unwrap().prepare(sql) 370 | } 371 | } 372 | 373 | impl Database for Mysql { 374 | fn version(&self) -> Result { 375 | let sql = "SELECT version()"; 376 | let dao = try!(self.execute_sql_with_one_return(sql, &vec![])); 377 | match dao { 378 | Some(dao) => { 379 | match dao.get("version()") { 380 | Some(version) => { 381 | match version { 382 | &Value::String(ref version) => Ok(version.to_owned()), 383 | _ => unreachable!(), 384 | } 385 | } 386 | None => Err(DbError::new("Unable to get database version")), 387 | } 388 | } 389 | None => Err(DbError::new("Unable to get database version")), 390 | } 391 | } 392 | 393 | fn begin(&self) { 394 | let _ = self.execute_sql("START TRANSACTION", &[]); 395 | } 396 | fn commit(&self) { 397 | let _ = self.execute_sql("COMMIT", &[]); 398 | } 399 | fn rollback(&self) { 400 | let _ = self.execute_sql("ROLLBACK", &[]); 401 | } 402 | 403 | 404 | /// return this list of options, supported features in the database 405 | fn sql_options(&self) -> Vec { 406 | vec![ 407 | SqlOption::UsesQuestionMark,//mysql uses question mark instead of the numbered params 408 | ] 409 | } 410 | 411 | fn update(&self, _query: &Update) -> Result { 412 | unimplemented!() 413 | } 414 | 415 | fn delete(&self, _query: &Delete) -> Result { 416 | unimplemented!() 417 | } 418 | 419 | fn execute_sql_with_return(&self, sql: &str, params: &[Value]) -> Result, DbError> { 420 | debug!("SQL: \n{}", sql); 421 | debug!("param: {:?}", params); 422 | assert!(self.pool.is_some()); 423 | let mut stmt = try!(self.get_prepared_statement(sql)); 424 | let mut columns = vec![]; 425 | for col in stmt.columns_ref().unwrap() { 426 | let column_name = String::from_utf8(col.name.clone()).unwrap(); 427 | debug!("column type: {:?}", col.column_type); 428 | columns.push((column_name, col.column_type)); 429 | } 430 | let mut daos = vec![]; 431 | let param = Mysql::from_rust_type_tosql(params); 432 | let rows = try!(stmt.execute(¶m)); 433 | for row in rows { 434 | let row = try!(row); 435 | let mut index = 0; 436 | let mut dao = Dao::new(); 437 | for &(ref column_name, ref column_type) in &columns { 438 | let rtype = Mysql::from_sql_to_rust_type(&row, index, column_type); 439 | if let Some(rtype) = rtype { 440 | dao.insert(column_name.to_owned(), rtype); 441 | } 442 | index += 1; 443 | } 444 | daos.push(dao); 445 | } 446 | Ok(daos) 447 | } 448 | 449 | fn execute_sql_with_one_return(&self, 450 | sql: &str, 451 | params: &[Value]) 452 | -> Result, DbError> { 453 | let dao = try!(self.execute_sql_with_return(sql, params)); 454 | if dao.len() >= 1 { 455 | Ok(Some(dao[0].clone())) 456 | } else { 457 | Ok(None) 458 | } 459 | } 460 | 461 | /// generic execute sql which returns not much information, 462 | /// returns only the number of affected records or errors 463 | /// can be used with DDL operations (CREATE, DELETE, ALTER, DROP) 464 | fn execute_sql(&self, sql: &str, params: &[Value]) -> Result { 465 | debug!("SQL: \n{}", sql); 466 | debug!("param: {:?}", params); 467 | let to_sql_types = Mysql::from_rust_type_tosql(params); 468 | assert!(self.pool.is_some()); 469 | let result = try!(self.pool.as_ref().unwrap().prep_exec(sql, &to_sql_types)); 470 | Ok(result.affected_rows() as usize) 471 | } 472 | } 473 | 474 | impl DatabaseDDL for Mysql { 475 | fn create_schema(&self, _schema: &str) { 476 | unimplemented!() 477 | } 478 | 479 | fn drop_schema(&self, _schema: &str) { 480 | unimplemented!() 481 | } 482 | 483 | fn build_create_table(&self, table: &Table) -> SqlFrag { 484 | let mut w = SqlFrag::new(self.sql_options(), &BuildMode::Standard); 485 | w.append("CREATE TABLE "); 486 | w.append(&table.name); 487 | w.append("("); 488 | w.ln_tab(); 489 | let mut do_comma = false; 490 | for c in &table.columns { 491 | if do_comma { 492 | w.commasp(); 493 | } else { 494 | do_comma = true; 495 | } 496 | w.append(&c.name); 497 | w.append(" "); 498 | let dt = self.rust_type_to_dbtype(&c.data_type); 499 | w.append(&dt); 500 | if c.is_primary { 501 | w.append(" PRIMARY KEY "); 502 | } 503 | } 504 | w.append(")"); 505 | w 506 | } 507 | fn create_table(&self, table: &Table) { 508 | let frag = self.build_create_table(table); 509 | match self.execute_sql(&frag.sql, &vec![]) { 510 | Ok(_) => debug!("created table.."), 511 | Err(e) => panic!("table not created {}", e), 512 | } 513 | } 514 | 515 | fn rename_table(&self, _table: &Table, _new_tablename: String) { 516 | unimplemented!() 517 | } 518 | 519 | fn drop_table(&self, _table: &Table) { 520 | unimplemented!() 521 | } 522 | 523 | fn set_foreign_constraint(&self, _model: &Table) { 524 | unimplemented!() 525 | } 526 | 527 | fn set_primary_constraint(&self, _model: &Table) { 528 | unimplemented!() 529 | } 530 | } 531 | 532 | 533 | // TODO: need to implement trait DatabaseDev for Mysql 534 | // Mysql can be used as development database 535 | 536 | 537 | 538 | 539 | 540 | impl DatabaseDev for Mysql { 541 | fn get_parent_table(&self, schema: &str, table: &str) -> Option { 542 | println!("{} {}", schema, table); 543 | None 544 | } 545 | fn get_row_count_estimate(&self, _: &str, table: &str) -> Option { 546 | let sql = format!("SELECT TABLE_ROWS as count FROM INFORMATION_SCHEMA.TABLES 547 | WHERE TABLE_NAME = '{}';",table); 548 | 549 | match self.execute_sql_with_one_return(&sql, &vec![]) { 550 | Ok(dao) => { 551 | match dao { 552 | Some(dao) => { 553 | match dao.get("count") { 554 | Some(schema) => { 555 | match *schema { 556 | Value::I64(count) => Some(count as usize), 557 | Value::I32(count) => Some(count as usize), 558 | Value::I16(count) => Some(count as usize), 559 | _ => unreachable!(), 560 | } 561 | } 562 | None => None, 563 | } 564 | } 565 | None => None, 566 | } 567 | } 568 | Err(_) => None, 569 | } 570 | } 571 | 572 | fn get_table_sub_class(&self, schema: &str, table: &str) -> Vec { 573 | println!("{} {}", schema, table); 574 | vec![] 575 | } 576 | 577 | fn get_table_metadata(&self, schema: &str, table: &str, is_view: bool) -> Table { 578 | 579 | let mut columns = self.get_table_columns(schema, table); 580 | let comment = self.get_table_comment(schema, table); 581 | let parent = self.get_parent_table(schema, table); 582 | let subclass = self.get_table_sub_class(schema, table); 583 | let estimated_row_count = self.get_row_count_estimate(schema, table); 584 | 585 | // mutate columns to mark those which are inherited 586 | if parent.is_some() { 587 | let inherited_columns = self.get_inherited_columns(schema, table); 588 | for i in inherited_columns { 589 | for c in &mut columns { 590 | if i == c.name { 591 | c.is_inherited = true; 592 | } 593 | } 594 | } 595 | } 596 | 597 | Table { 598 | schema: Some(schema.to_owned()), 599 | name: table.to_owned(), 600 | parent_table: parent, 601 | sub_table: subclass, 602 | comment: comment, 603 | columns: columns, 604 | is_view: is_view, 605 | estimated_row_count: estimated_row_count, 606 | } 607 | } 608 | 609 | fn get_all_tables(&self) -> Vec<(String, String, bool)> { 610 | let sql = "SELECT schema()"; 611 | let schema_name: String = match self.execute_sql_with_one_return(sql, &vec![]) { 612 | Ok(dao) => { 613 | match dao { 614 | Some(dao) => { 615 | match dao.get("schema()") { 616 | Some(schema) => { 617 | match schema { 618 | &Value::String(ref schema) => schema.to_owned(), 619 | _ => unreachable!(), 620 | } 621 | } 622 | None => "".to_owned(), 623 | } 624 | } 625 | None => panic!("Unable to get current schema."), 626 | } 627 | } 628 | Err(_) => panic!("can not get current schema."), 629 | }; 630 | 631 | let sql = format!("select table_name from information_schema.tables where table_schema='{}' and table_type='base table'", schema_name); 632 | assert!(self.pool.is_some()); 633 | let mut stmt = match self.get_prepared_statement(&sql) { 634 | Ok(stmt) => stmt, 635 | Err(_) => panic!("prepare statement error."), 636 | }; 637 | let mut tables: Vec<(String, String, bool)> = Vec::new(); 638 | for row in stmt.execute(()).unwrap() { 639 | let (table_name,) = from_row::<(String,)>(row.unwrap()); 640 | tables.push((schema_name.clone(), table_name, false)); 641 | } 642 | tables 643 | } 644 | 645 | 646 | 647 | fn get_inherited_columns(&self, schema: &str, table: &str) -> Vec { 648 | println!("{} {}", schema, table); 649 | vec![] 650 | } 651 | 652 | 653 | /// get the rust data type names from database data type names 654 | /// will be used in source code generation 655 | fn dbtype_to_rust_type(&self, db_type: &str) -> (Vec, Type) { 656 | match db_type { 657 | "bool" => (vec![], Type::Bool), 658 | "tinyint" => (vec![], Type::I8), 659 | "smallint" => (vec![], Type::I16), 660 | "integer" | "int" => (vec![], Type::I32), 661 | "bigint" => (vec![], Type::I64), 662 | "float" => (vec![], Type::F32), 663 | "double" | "decimal" => (vec![], Type::F64), 664 | "char" | "varchar" | "text" => (vec![], Type::String), 665 | "blob" => (vec![], Type::VecU8), 666 | "timestamp" | "datetime" => { 667 | (vec!["chrono::datetime::DateTime".to_owned(), 668 | "chrono::offset::utc::UTC".to_owned()], 669 | Type::DateTime) 670 | // (vec!["chrono::naive::date::NaiveDateTime".to_owned()], 671 | // Type::NaiveDateTime) 672 | } 673 | _ => panic!("Unable to get the equivalent data type for {}", db_type), 674 | } 675 | } 676 | 677 | /// 678 | /// convert rust data type names to database data type names 679 | /// will be used in generating SQL for table creation 680 | /// FIXME, need to restore the exact data type as before 681 | fn rust_type_to_dbtype(&self, rust_type: &Type) -> String { 682 | match *rust_type { 683 | Type::Bool => "bool".to_owned(), 684 | Type::I8 => "tinyint(1)".to_owned(), 685 | Type::I16 => "integer".to_owned(), 686 | Type::I32 => "integer".to_owned(), 687 | Type::U32 => "integer".to_owned(), 688 | Type::I64 => "integer".to_owned(), 689 | Type::F32 => "real".to_owned(), 690 | Type::F64 => "real".to_owned(), 691 | Type::String => "text".to_owned(), 692 | Type::VecU8 => "blob".to_owned(), 693 | Type::Json => "text".to_owned(), 694 | Type::Uuid => "varchar(36)".to_owned(), 695 | Type::DateTime => "timestamp".to_owned(), 696 | _ => { 697 | panic!("Unable to get the equivalent database data type for {:?}", 698 | rust_type) 699 | } 700 | } 701 | } 702 | } 703 | --------------------------------------------------------------------------------