diff --git a/Cargo.toml b/Cargo.toml index 02a11051..2773730f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,36 +19,44 @@ license = "MIT OR Apache-2.0" documentation = "https://docs.rs/sea-orm" repository = "https://github.com/SeaQL/sea-orm" categories = [ "database" ] -keywords = [ "orm", "database", "sql", "mysql", "postgres", "sqlite" ] +keywords = [ "orm", "database", "sql", "mysql", "postgres", "sqlite", "async" ] publish = false +[package.metadata.docs.rs] +features = ["default", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "runtime-async-std-native-tls"] +rustdoc-args = ["--cfg", "docsrs"] + [lib] name = "sea_orm" path = "src/lib.rs" [dependencies] async-stream = { version = "^0.3" } +chrono = { version = "^0", optional = true } futures = { version = "^0.3" } futures-util = { version = "^0.3" } sea-query = { version = "^0.12" } sea-orm-macros = { path = "sea-orm-macros", optional = true } serde = { version = "^1.0", features = [ "derive" ] } sqlx = { version = "^0.5", optional = true } -strum = { version = "^0.20", features = [ "derive" ] } +strum = { git = "https://github.com/SeaQL/strum.git", branch = "sea-orm", version = "^0.21", features = [ "derive", "sea-orm" ] } serde_json = { version = "^1", optional = true } [dev-dependencies] async-std = { version = "^1.9", features = [ "attributes" ] } maplit = { version = "^1" } -sea-orm = { path = ".", features = ["sqlx-sqlite", "runtime-async-std-native-tls"] } +sea-orm = { path = ".", features = ["sqlx-sqlite", "sqlx-json", "sqlx-chrono", "runtime-async-std-native-tls"] } [features] debug-print = [] -default = [ "macros", "with-json", "mock" ] +default = [ "macros", "with-json", "with-chrono", "mock" ] macros = [ "sea-orm-macros" ] mock = [] with-json = [ "serde_json", "sea-query/with-json" ] -sqlx-dep = [ "sqlx", "sqlx/json" ] +with-chrono = [ "chrono", "sea-query/with-chrono" ] +sqlx-dep = [ "sqlx" ] +sqlx-json = [ "sqlx/json", "with-json" ] +sqlx-chrono = [ "sqlx/chrono", "with-chrono" ] sqlx-mysql = [ "sqlx-dep", "sea-query/sqlx-mysql", "sqlx/mysql" ] sqlx-postgres = [ "sqlx-dep", "sea-query/sqlx-postgres", "sqlx/postgres" ] sqlx-sqlite = [ "sqlx-dep", "sea-query/sqlx-sqlite", "sqlx/sqlite" ] diff --git a/Diesel.md b/Diesel.md deleted file mode 100644 index 080c7154..00000000 --- a/Diesel.md +++ /dev/null @@ -1,39 +0,0 @@ -# Comparison with Diesel - -SeaORM and Diesel shares the same goal: to offer you a complete solution in interfacing with databases. - -Both SeaORM and Diesel works with MySQL, Postgres and SQLite, so you aren't constrained going with either. - -However, there are things we chose to do differently. - -## Architecture - -First off, perhaps the number one requested feature, async Rust support. While using async may not offer you better performance today, programming in async is an architectural decision you have to make early on. By choosing SeaORM, we together look forward to Rust's async ecosystem maturing. - -Under the hood, SeaORM together with SQLx offers you a pure Rust technology stack. While Diesel is tied to native drivers. Each side has their pros and cons, so it's up to your preference. - -SeaORM has a modular design. If you don't like the idea of ORM, you'll definitely still want to use SeaQuery, the underlying query builder. It is light weight and can be easily integrated into any project. The SeaQuery API is also available to you when using SeaORM, so you receive the benefits of high level abstraction while still having the power of a flexible query builder when you need it. - -SeaSchema is our schema discovery library, but it is not sealed inside SeaORM. So you can reuse our data structures for developing libraries inter-operating with SeaORM. - -## Programming paradigm - -In addition to the sync vs async foundation, the biggest distinction between Diesel and SeaORM is static vs dynamic. Diesel has an everything-compile-time design which has its pros and cons. SeaORM is dynamic, in which things are established runtime. It offers more flexibility. While you loses some compile-time guarantee, SeaORM helps you to prove correctness by unit testing instead. - -Both libraries make heavy use of traits and generics, but SeaORM generate less types (each column in Diesel is a struct, while each column in SeaORM is a enum variant) and less depthness (there won't be `A>>`). That probably means looser type/lifetime constraints and faster compilation. - -You don't have to use macros when using SeaORM. We provide some derive macros for convenience, but they are entirely optional. - -## Schema Builder - -While in the Diesel ecosystem there are awesome libraries like barrel, SeaQL maintain the tooling for schema building. That means a familiar API and a more unified experience. - -## Similarities - -Both Diesel and SeaORM are schema first, meaning it all starts from your existing database schema, instead of starting from your OOP classes. - -Both Diesel and SeaORM are relational, meaning you can do complex joins with defined relations. - -## Final words - -Diesel is a well established library in the Rust ecosystem. SeaORM is very new. Neither can replace the other. We hope that the Rust community will grow stronger together. \ No newline at end of file diff --git a/README.md b/README.md index b02a2070..c5345d22 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,12 @@ # SeaORM -Inspired by ActiveRecord, Eloquent and TypeORM, SeaORM aims to provide you an intuitive and ergonomic +Inspired by ActiveRecord, Eloquent and TypeORM, SeaORM aims to provide you an intuitive and ergonomic API to make working with databases in Rust a first-class experience. -> This is an early WIP of SeaORM, and is not yet published. See [example](examples/sqlx-mysql/src) for demo usage. +```rust +This is a preview of SeaORM, and is not yet released. +``` ## Features @@ -27,12 +29,130 @@ Relying on SQLx, SeaORM is a new library with async support from day 1. 2. Dynamic -Built upon SeaQuery, a dynamic query builder, SeaORM allows you to build complex queries without 'fighting the ORM'. +Built upon SeaQuery, SeaORM allows you to build complex queries without 'fighting the ORM'. 3. Testable Use mock connections to write unit tests for your logic. -4. API oriented +4. Service oriented -Quickly build search models that help you join, filter, sort and paginate data in APIs. +Quickly build services that join, filter, sort and paginate data in APIs. + +## A quick taste of SeaORM + +### Select +```rust +// find all models +let cakes: Vec = Cake::find().all(db).await?; + +// find and filter +let chocolate: Vec = Cake::find() + .filter(cake::Column::Name.contains("chocolate")) + .all(db) + .await?; + +// find one model +let cheese: Option = Cake::find_by_id(1).one(db).await?; +let cheese: cake::Model = cheese.unwrap(); + +// find related models (lazy) +let fruits: Vec = cheese.find_related(Fruit).all(db).await?; + +// find related models (eager) +let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() + .find_with_related(Fruit) + .all(db) + .await?; + +``` +### Insert +```rust +let apple = fruit::ActiveModel { + name: Set("Apple".to_owned()), + ..Default::default() // no need to set primary key +}; + +let pear = fruit::ActiveModel { + name: Set("Pear".to_owned()), + ..Default::default() +}; + +// insert one +let res: InsertResult = Fruit::insert(pear).exec(db).await?; + +println!("InsertResult: {}", res.last_insert_id); + +// insert many +Fruit::insert_many(vec![apple, pear]).exec(db).await?; +``` +### Update +```rust +use sea_orm::sea_query::{Expr, Value}; + +let pear: Option = Fruit::find_by_id(1).one(db).await?; +let mut pear: fruit::ActiveModel = pear.unwrap().into(); + +pear.name = Set("Sweet pear".to_owned()); + +// update one +let pear: fruit::ActiveModel = Fruit::update(pear).exec(db).await?; + +// update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%' +Fruit::update_many() + .col_expr(fruit::Column::CakeId, Expr::value(Value::Null)) + .filter(fruit::Column::Name.contains("Apple")) + .exec(db) + .await?; + +``` +### Save +```rust +let banana = fruit::ActiveModel { + id: Unset(None), + name: Set("Banana".to_owned()), + ..Default::default() +}; + +// create, because primary key `id` is `Unset` +let mut banana = banana.save(db).await?; + +banana.name = Set("Banana Mongo".to_owned()); + +// update, because primary key `id` is `Set` +let banana = banana.save(db).await?; + +``` +### Delete +```rust +let orange: Option = Fruit::find_by_id(1).one(db).await?; +let orange: fruit::ActiveModel = orange.unwrap().into(); + +// delete one +fruit::Entity::delete(orange).exec(db).await?; +// or simply +orange.delete(db).await?; + +// delete many: DELETE FROM "fruit" WHERE "fruit"."name" LIKE 'Orange' +fruit::Entity::delete_many() + .filter(fruit::Column::Name.contains("Orange")) + .exec(db) + .await?; + +``` +## License + +Licensed under either of + +- Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/build-tools/generate-readme.sh b/build-tools/generate-readme.sh new file mode 100644 index 00000000..9b871ef4 --- /dev/null +++ b/build-tools/generate-readme.sh @@ -0,0 +1,3 @@ +# Run `sh develop/cargo-readme.sh` on project root to generate `README.md` from `src/lib.rs` +# cargo install cargo-readme +cargo readme --no-badges --no-indent-headings --no-license --no-template --no-title > README.md \ No newline at end of file diff --git a/examples/cli/src/entity/cake.rs b/examples/cli/src/entity/cake.rs index 9b786a5f..12e7c4ab 100644 --- a/examples/cli/src/entity/cake.rs +++ b/examples/cli/src/entity/cake.rs @@ -71,13 +71,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake_filling(&self) -> Select { - Entity::find_related().belongs_to::(self) - } - pub fn find_fruit(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/cli/src/entity/cake_filling.rs b/examples/cli/src/entity/cake_filling.rs index d5b4b8b6..1ac64920 100644 --- a/examples/cli/src/entity/cake_filling.rs +++ b/examples/cli/src/entity/cake_filling.rs @@ -78,13 +78,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake(&self) -> Select { - Entity::find_related().belongs_to::(self) - } - pub fn find_filling(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/cli/src/entity/filling.rs b/examples/cli/src/entity/filling.rs index e4563e55..752317a4 100644 --- a/examples/cli/src/entity/filling.rs +++ b/examples/cli/src/entity/filling.rs @@ -63,10 +63,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake_filling(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/cli/src/entity/fruit.rs b/examples/cli/src/entity/fruit.rs index f65d5b38..7ff7d9b8 100644 --- a/examples/cli/src/entity/fruit.rs +++ b/examples/cli/src/entity/fruit.rs @@ -39,6 +39,7 @@ impl PrimaryKeyTrait for PrimaryKey { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Cake, + Vendor, } impl ColumnTrait for Column { @@ -59,6 +60,7 @@ impl RelationTrait for Relation { .from(Column::CakeId) .to(super::cake::Column::Id) .into(), + Self::Vendor => Entity::has_many(super::vendor::Entity).into(), } } } @@ -69,9 +71,9 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake(&self) -> Select { - Entity::find_related().belongs_to::(self) +impl Related for Entity { + fn to() -> RelationDef { + Relation::Vendor.def() } } diff --git a/examples/cli/src/entity/mod.rs b/examples/cli/src/entity/mod.rs index 006f7e80..395d29f9 100644 --- a/examples/cli/src/entity/mod.rs +++ b/examples/cli/src/entity/mod.rs @@ -4,3 +4,4 @@ pub mod cake; pub mod cake_filling; pub mod filling; pub mod fruit; +pub mod vendor; diff --git a/examples/cli/src/entity/prelude.rs b/examples/cli/src/entity/prelude.rs index b4c1c94f..b4e85c78 100644 --- a/examples/cli/src/entity/prelude.rs +++ b/examples/cli/src/entity/prelude.rs @@ -4,3 +4,4 @@ pub use super::cake::Entity as Cake; pub use super::cake_filling::Entity as CakeFilling; pub use super::filling::Entity as Filling; pub use super::fruit::Entity as Fruit; +pub use super::vendor::Entity as Vendor; diff --git a/examples/cli/src/entity/vendor.rs b/examples/cli/src/entity/vendor.rs index 9c4ca7dd..2262519f 100644 --- a/examples/cli/src/entity/vendor.rs +++ b/examples/cli/src/entity/vendor.rs @@ -47,7 +47,7 @@ impl ColumnTrait for Column { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(Some(255u32)).def(), - Self::FruitId => ColumnType::Integer.def(), + Self::FruitId => ColumnType::Integer.def().null(), } } } @@ -55,7 +55,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Fruit => Entity::has_one(super::fruit::Entity) + Self::Fruit => Entity::belongs_to(super::fruit::Entity) .from(Column::FruitId) .to(super::fruit::Column::Id) .into(), @@ -69,10 +69,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_fruit(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/cake.rs b/examples/codegen/src/entity/cake.rs index 87ee5f1a..12e7c4ab 100644 --- a/examples/codegen/src/entity/cake.rs +++ b/examples/codegen/src/entity/cake.rs @@ -53,14 +53,8 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) - .from(Column::Id) - .to(super::cake_filling::Column::CakeId) - .into(), - Self::Fruit => Entity::has_many(super::fruit::Entity) - .from(Column::Id) - .to(super::fruit::Column::CakeId) - .into(), + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), } } } @@ -77,13 +71,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake_filling(&self) -> Select { - Entity::find_related().belongs_to::(self) - } - pub fn find_fruit(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/cake_filling.rs b/examples/codegen/src/entity/cake_filling.rs index b35279d4..1ac64920 100644 --- a/examples/codegen/src/entity/cake_filling.rs +++ b/examples/codegen/src/entity/cake_filling.rs @@ -54,11 +54,11 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::has_one(super::cake::Entity) + Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), - Self::Filling => Entity::has_one(super::filling::Entity) + Self::Filling => Entity::belongs_to(super::filling::Entity) .from(Column::FillingId) .to(super::filling::Column::Id) .into(), @@ -78,13 +78,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake(&self) -> Select { - Entity::find_related().belongs_to::(self) - } - pub fn find_filling(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/filling.rs b/examples/codegen/src/entity/filling.rs index 4134652f..752317a4 100644 --- a/examples/codegen/src/entity/filling.rs +++ b/examples/codegen/src/entity/filling.rs @@ -52,10 +52,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) - .from(Column::Id) - .to(super::cake_filling::Column::FillingId) - .into(), + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity).into(), } } } @@ -66,10 +63,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake_filling(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/fruit.rs b/examples/codegen/src/entity/fruit.rs index d3f21bf9..7ff7d9b8 100644 --- a/examples/codegen/src/entity/fruit.rs +++ b/examples/codegen/src/entity/fruit.rs @@ -48,7 +48,7 @@ impl ColumnTrait for Column { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(Some(255u32)).def(), - Self::CakeId => ColumnType::Integer.def(), + Self::CakeId => ColumnType::Integer.def().null(), } } } @@ -56,14 +56,11 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Cake => Entity::has_one(super::cake::Entity) + Self::Cake => Entity::belongs_to(super::cake::Entity) .from(Column::CakeId) .to(super::cake::Column::Id) .into(), - Self::Vendor => Entity::has_many(super::vendor::Entity) - .from(Column::Id) - .to(super::vendor::Column::FruitId) - .into(), + Self::Vendor => Entity::has_many(super::vendor::Entity).into(), } } } @@ -80,13 +77,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_cake(&self) -> Select { - Entity::find_related().belongs_to::(self) - } - pub fn find_vendor(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/codegen/src/entity/vendor.rs b/examples/codegen/src/entity/vendor.rs index 9c4ca7dd..2262519f 100644 --- a/examples/codegen/src/entity/vendor.rs +++ b/examples/codegen/src/entity/vendor.rs @@ -47,7 +47,7 @@ impl ColumnTrait for Column { match self { Self::Id => ColumnType::Integer.def(), Self::Name => ColumnType::String(Some(255u32)).def(), - Self::FruitId => ColumnType::Integer.def(), + Self::FruitId => ColumnType::Integer.def().null(), } } } @@ -55,7 +55,7 @@ impl ColumnTrait for Column { impl RelationTrait for Relation { fn def(&self) -> RelationDef { match self { - Self::Fruit => Entity::has_one(super::fruit::Entity) + Self::Fruit => Entity::belongs_to(super::fruit::Entity) .from(Column::FruitId) .to(super::fruit::Column::Id) .into(), @@ -69,10 +69,4 @@ impl Related for Entity { } } -impl Model { - pub fn find_fruit(&self) -> Select { - Entity::find_related().belongs_to::(self) - } -} - impl ActiveModelBehavior for ActiveModel {} diff --git a/examples/sqlx-mysql/Cargo.toml b/examples/sqlx-mysql/Cargo.toml index a9e1d9ae..4b39809c 100644 --- a/examples/sqlx-mysql/Cargo.toml +++ b/examples/sqlx-mysql/Cargo.toml @@ -6,8 +6,7 @@ publish = false [dependencies] async-std = { version = "^1.9", features = [ "attributes" ] } -sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print", "with-json", "macros" ], default-features = false } -strum = { version = "^0.20", features = [ "derive" ] } +sea-orm = { path = "../../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print", "sqlx-json", "macros" ], default-features = false } serde_json = { version = "^1" } futures = { version = "^0.3" } async-stream = { version = "^0.3" } diff --git a/examples/sqlx-mysql/Readme.md b/examples/sqlx-mysql/Readme.md index 77a90bd6..98a14ada 100644 --- a/examples/sqlx-mysql/Readme.md +++ b/examples/sqlx-mysql/Readme.md @@ -14,7 +14,7 @@ cargo run All about selects: ```sh -Database { connection: SqlxMySqlPoolConnection } +SqlxMySqlPoolConnection ===== ===== diff --git a/examples/sqlx-mysql/src/operation.rs b/examples/sqlx-mysql/src/operation.rs index d7a5ed78..b1273e10 100644 --- a/examples/sqlx-mysql/src/operation.rs +++ b/examples/sqlx-mysql/src/operation.rs @@ -1,7 +1,7 @@ use super::*; -use sea_orm::{entity::*, query::*, DbConn}; +use sea_orm::{entity::*, error::*, query::*, DbConn}; -pub async fn all_about_operation(db: &DbConn) -> Result<(), ExecErr> { +pub async fn all_about_operation(db: &DbConn) -> Result<(), DbErr> { insert_and_update(db).await?; println!("===== =====\n"); @@ -15,7 +15,7 @@ pub async fn all_about_operation(db: &DbConn) -> Result<(), ExecErr> { Ok(()) } -pub async fn insert_and_update(db: &DbConn) -> Result<(), ExecErr> { +pub async fn insert_and_update(db: &DbConn) -> Result<(), DbErr> { let pear = fruit::ActiveModel { name: Set("pear".to_owned()), ..Default::default() @@ -25,10 +25,7 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), ExecErr> { println!(); println!("Inserted: last_insert_id = {}\n", res.last_insert_id); - let pear: Option = Fruit::find_by_id(res.last_insert_id) - .one(db) - .await - .map_err(|_| ExecErr)?; + let pear: Option = Fruit::find_by_id(res.last_insert_id).one(db).await?; println!(); println!("Pear: {:?}\n", pear); @@ -44,7 +41,7 @@ pub async fn insert_and_update(db: &DbConn) -> Result<(), ExecErr> { Ok(()) } -pub async fn save_active_model(db: &DbConn) -> Result<(), ExecErr> { +pub async fn save_active_model(db: &DbConn) -> Result<(), DbErr> { let banana = fruit::ActiveModel { name: Set("Banana".to_owned()), ..Default::default() @@ -82,7 +79,7 @@ mod form { } } -async fn save_custom_active_model(db: &DbConn) -> Result<(), ExecErr> { +async fn save_custom_active_model(db: &DbConn) -> Result<(), DbErr> { let pineapple = form::ActiveModel { id: Unset(None), name: Set("Pineapple".to_owned()), diff --git a/examples/sqlx-mysql/src/select.rs b/examples/sqlx-mysql/src/select.rs index c344bae6..9b2cf15c 100644 --- a/examples/sqlx-mysql/src/select.rs +++ b/examples/sqlx-mysql/src/select.rs @@ -1,7 +1,7 @@ use super::*; -use sea_orm::{entity::*, query::*, DbConn, FromQueryResult}; +use sea_orm::{entity::*, error::*, query::*, DbConn, FromQueryResult}; -pub async fn all_about_select(db: &DbConn) -> Result<(), QueryErr> { +pub async fn all_about_select(db: &DbConn) -> Result<(), DbErr> { find_all(db).await?; println!("===== =====\n"); @@ -41,7 +41,7 @@ pub async fn all_about_select(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn find_all(db: &DbConn) -> Result<(), QueryErr> { +async fn find_all(db: &DbConn) -> Result<(), DbErr> { print!("find all cakes: "); let cakes: Vec = Cake::find().all(db).await?; @@ -63,7 +63,7 @@ async fn find_all(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn find_together(db: &DbConn) -> Result<(), QueryErr> { +async fn find_together(db: &DbConn) -> Result<(), DbErr> { print!("find cakes and fruits: "); let both = Cake::find().find_also_related(Fruit).all(db).await?; @@ -82,7 +82,7 @@ impl Cake { } } -async fn find_one(db: &DbConn) -> Result<(), QueryErr> { +async fn find_one(db: &DbConn) -> Result<(), DbErr> { print!("find one by primary key: "); let cheese: Option = Cake::find_by_id(1).one(db).await?; @@ -112,7 +112,7 @@ async fn find_one(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn count_fruits_by_cake(db: &DbConn) -> Result<(), QueryErr> { +async fn count_fruits_by_cake(db: &DbConn) -> Result<(), DbErr> { #[derive(Debug, FromQueryResult)] struct SelectResult { name: String, @@ -138,7 +138,7 @@ async fn count_fruits_by_cake(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { +async fn find_many_to_many(db: &DbConn) -> Result<(), DbErr> { print!("find cakes and fillings: "); let both: Vec<(cake::Model, Vec)> = @@ -178,7 +178,7 @@ async fn find_many_to_many(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn all_about_select_json(db: &DbConn) -> Result<(), QueryErr> { +async fn all_about_select_json(db: &DbConn) -> Result<(), DbErr> { find_all_json(&db).await?; println!("===== =====\n"); @@ -192,7 +192,7 @@ async fn all_about_select_json(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn find_all_json(db: &DbConn) -> Result<(), QueryErr> { +async fn find_all_json(db: &DbConn) -> Result<(), DbErr> { print!("find all cakes: "); let cakes = Cake::find().into_json().all(db).await?; @@ -208,7 +208,7 @@ async fn find_all_json(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn find_together_json(db: &DbConn) -> Result<(), QueryErr> { +async fn find_together_json(db: &DbConn) -> Result<(), DbErr> { print!("find cakes and fruits: "); let cakes_fruits = Cake::find() @@ -225,7 +225,7 @@ async fn find_together_json(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn count_fruits_by_cake_json(db: &DbConn) -> Result<(), QueryErr> { +async fn count_fruits_by_cake_json(db: &DbConn) -> Result<(), DbErr> { print!("count fruits by cake: "); let count = Cake::find() @@ -243,7 +243,7 @@ async fn count_fruits_by_cake_json(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn find_all_stream(db: &DbConn) -> Result<(), QueryErr> { +async fn find_all_stream(db: &DbConn) -> Result<(), DbErr> { use async_std::task::sleep; use futures::TryStreamExt; use std::time::Duration; @@ -291,7 +291,7 @@ async fn find_all_stream(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn find_first_page(db: &DbConn) -> Result<(), QueryErr> { +async fn find_first_page(db: &DbConn) -> Result<(), DbErr> { println!("fruits first page: "); let page = fruit::Entity::find().paginate(db, 2).fetch_page(0).await?; for fruit in page { @@ -301,7 +301,7 @@ async fn find_first_page(db: &DbConn) -> Result<(), QueryErr> { Ok(()) } -async fn find_num_pages(db: &DbConn) -> Result<(), QueryErr> { +async fn find_num_pages(db: &DbConn) -> Result<(), DbErr> { println!("fruits number of page: "); let num_pages = fruit::Entity::find().paginate(db, 2).num_pages().await?; println!("{:?}", num_pages); diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index 474dec5f..f66b2aa6 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -40,4 +40,5 @@ pub fn build_cli() -> App<'static, 'static> { .version(env!("CARGO_PKG_VERSION")) .setting(AppSettings::VersionlessSubcommands) .subcommand(entity_subcommand) + .setting(AppSettings::SubcommandRequiredElseHelp) } diff --git a/sea-orm-codegen/Cargo.toml b/sea-orm-codegen/Cargo.toml index bfae08ed..a72a366a 100644 --- a/sea-orm-codegen/Cargo.toml +++ b/sea-orm-codegen/Cargo.toml @@ -16,7 +16,6 @@ name = "sea_orm_codegen" path = "src/lib.rs" [dependencies] -sea-orm = { path = "../", features = [ "sqlx-mysql", "runtime-async-std-native-tls", "debug-print", "with-json", "macros" ], default-features = false } sea-schema = { version = "^0.2", default-features = false, features = [ "sqlx-mysql", "runtime-async-std-native-tls", "discovery", "writer" ] } sea-query = { version = "^0.12" } sqlx = { version = "^0.5", features = [ "mysql", "runtime-async-std-native-tls" ] } diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index e8a0b543..e077c0a6 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -61,21 +61,21 @@ impl Column { None => quote! { ColumnType::String(None).def() }, }, ColumnType::Text => quote! { ColumnType::Text.def() }, - ColumnType::TinyInteger(s) => quote! { ColumnType::TinyInteger.def() }, - ColumnType::SmallInteger(s) => quote! { ColumnType::SmallInteger.def() }, - ColumnType::Integer(s) => quote! { ColumnType::Integer.def() }, - ColumnType::BigInteger(s) => quote! { ColumnType::BigInteger.def() }, - ColumnType::Float(s) => quote! { ColumnType::Float.def() }, - ColumnType::Double(s) => quote! { ColumnType::Double.def() }, + ColumnType::TinyInteger(_) => quote! { ColumnType::TinyInteger.def() }, + ColumnType::SmallInteger(_) => quote! { ColumnType::SmallInteger.def() }, + ColumnType::Integer(_) => quote! { ColumnType::Integer.def() }, + ColumnType::BigInteger(_) => quote! { ColumnType::BigInteger.def() }, + ColumnType::Float(_) => quote! { ColumnType::Float.def() }, + ColumnType::Double(_) => quote! { ColumnType::Double.def() }, ColumnType::Decimal(s) => match s { Some((s1, s2)) => quote! { ColumnType::Decimal(Some((#s1, #s2))).def() }, None => quote! { ColumnType::Decimal(None).def() }, }, - ColumnType::DateTime(s) => quote! { ColumnType::DateTime.def() }, - ColumnType::Timestamp(s) => quote! { ColumnType::Timestamp.def() }, - ColumnType::Time(s) => quote! { ColumnType::Time.def() }, + ColumnType::DateTime(_) => quote! { ColumnType::DateTime.def() }, + ColumnType::Timestamp(_) => quote! { ColumnType::Timestamp.def() }, + ColumnType::Time(_) => quote! { ColumnType::Time.def() }, ColumnType::Date => quote! { ColumnType::Date.def() }, - ColumnType::Binary(s) => quote! { ColumnType::Binary.def() }, + ColumnType::Binary(_) => quote! { ColumnType::Binary.def() }, ColumnType::Boolean => quote! { ColumnType::Boolean.def() }, ColumnType::Money(s) => match s { Some((s1, s2)) => quote! { ColumnType::Money(Some((#s1, #s2))).def() }, diff --git a/sea-orm-codegen/src/entity/transformer.rs b/sea-orm-codegen/src/entity/transformer.rs index 02c7fb82..c49a871e 100644 --- a/sea-orm-codegen/src/entity/transformer.rs +++ b/sea-orm-codegen/src/entity/transformer.rs @@ -1,7 +1,7 @@ use crate::{Column, Entity, EntityWriter, Error, PrimaryKey, Relation, RelationType}; use sea_query::TableStatement; use sea_schema::mysql::def::Schema; -use std::{collections::HashMap, mem::swap}; +use std::collections::HashMap; #[derive(Clone, Debug)] pub struct EntityTransformer { diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 01dd4a4c..3bd8fb59 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -117,7 +117,6 @@ impl EntityWriter { ]; code_blocks.extend(Self::gen_impl_related(entity)); code_blocks.extend(vec![ - Self::gen_impl_model(entity), Self::gen_impl_active_model_behavior(), ]); code_blocks @@ -256,18 +255,6 @@ impl EntityWriter { .collect() } - pub fn gen_impl_model(entity: &Entity) -> TokenStream { - let relation_ref_tables_snake_case = entity.get_relation_ref_tables_snake_case(); - let relation_rel_find_helpers = entity.get_relation_rel_find_helpers(); - quote! { - impl Model { - #(pub fn #relation_rel_find_helpers(&self) -> Select { - Entity::find_related().belongs_to::(self) - })* - } - } - } - pub fn gen_impl_active_model_behavior() -> TokenStream { quote! { impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 701c0d71..76e80a02 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -37,11 +37,11 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result Result { + pub async fn save(self, db: &sea_orm::DatabaseConnection) -> Result { sea_orm::save_active_model::(self, db).await } - pub async fn delete(self, db: &sea_orm::DatabaseConnection) -> Result { + pub async fn delete(self, db: &sea_orm::DatabaseConnection) -> Result { sea_orm::delete_active_model::(self, db).await } } diff --git a/sea-orm-macros/src/derives/from_query_result.rs b/sea-orm-macros/src/derives/from_query_result.rs index 65ef5a93..ef24c18d 100644 --- a/sea-orm-macros/src/derives/from_query_result.rs +++ b/sea-orm-macros/src/derives/from_query_result.rs @@ -30,7 +30,7 @@ pub fn expand_derive_from_query_result(ident: Ident, data: Data) -> syn::Result< Ok(quote!( impl sea_orm::FromQueryResult for #ident { - fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> Result { + fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> Result { Ok(Self { #(#field: row.try_get(pre, #name)?),* }) diff --git a/sea-orm-macros/src/derives/model.rs b/sea-orm-macros/src/derives/model.rs index c304e4c4..1faf3642 100644 --- a/sea-orm-macros/src/derives/model.rs +++ b/sea-orm-macros/src/derives/model.rs @@ -47,7 +47,7 @@ pub fn expand_derive_model(ident: Ident, data: Data) -> syn::Result } impl sea_orm::FromQueryResult for #ident { - fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> Result { + fn from_query_result(row: &sea_orm::QueryResult, pre: &str) -> Result { Ok(Self { #(#field: row.try_get(pre, <::Entity as EntityTrait>::Column::#name.as_str().into())?),* }) diff --git a/src/database/connection.rs b/src/database/connection.rs index 9727125b..58a5bba8 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -1,9 +1,8 @@ -use crate::{ExecErr, ExecResult, QueryErr, QueryResult, Statement, Transaction}; +use crate::{error::*, ExecResult, QueryResult, Statement}; use sea_query::{ MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementBuilder, SchemaStatementBuilder, SqliteQueryBuilder, }; -use std::{error::Error, fmt}; pub enum DatabaseConnection { #[cfg(feature = "sqlx-mysql")] @@ -29,17 +28,6 @@ pub enum SchemaBuilderBackend { Sqlite, } -#[derive(Debug)] -pub struct ConnectionErr; - -impl Error for ConnectionErr {} - -impl fmt::Display for ConnectionErr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - impl Default for DatabaseConnection { fn default() -> Self { Self::Disconnected @@ -89,7 +77,7 @@ impl DatabaseConnection { } } - pub async fn execute(&self, stmt: Statement) -> Result { + pub async fn execute(&self, stmt: Statement) -> Result { match self { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.execute(stmt).await, @@ -101,7 +89,7 @@ impl DatabaseConnection { } } - pub async fn query_one(&self, stmt: Statement) -> Result, QueryErr> { + pub async fn query_one(&self, stmt: Statement) -> Result, DbErr> { match self { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt).await, @@ -113,7 +101,7 @@ impl DatabaseConnection { } } - pub async fn query_all(&self, stmt: Statement) -> Result, QueryErr> { + pub async fn query_all(&self, stmt: Statement) -> Result, DbErr> { match self { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt).await, @@ -139,7 +127,7 @@ impl DatabaseConnection { } #[cfg(feature = "mock")] - pub fn into_transaction_log(self) -> Vec { + pub fn into_transaction_log(self) -> Vec { let mut mocker = self.as_mock_connection().get_mocker_mutex().lock().unwrap(); mocker.drain_transaction_log() } diff --git a/src/database/mock.rs b/src/database/mock.rs index 4088990a..7b208a72 100644 --- a/src/database/mock.rs +++ b/src/database/mock.rs @@ -1,7 +1,7 @@ use crate::{ - DatabaseConnection, EntityTrait, ExecErr, ExecResult, ExecResultHolder, Iden, Iterable, - MockDatabaseConnection, MockDatabaseTrait, ModelTrait, QueryErr, QueryResult, QueryResultRow, - Statement, Transaction, TypeErr, + error::*, DatabaseConnection, EntityTrait, ExecResult, ExecResultHolder, Iden, Iterable, + MockDatabaseConnection, MockDatabaseTrait, ModelTrait, QueryResult, QueryResultRow, Statement, + Transaction, }; use sea_query::{Value, ValueType}; use std::collections::BTreeMap; @@ -68,14 +68,14 @@ impl MockDatabase { } impl MockDatabaseTrait for MockDatabase { - fn execute(&mut self, counter: usize, statement: Statement) -> Result { + fn execute(&mut self, counter: usize, statement: Statement) -> Result { self.transaction_log.push(Transaction::one(statement)); if counter < self.exec_results.len() { Ok(ExecResult { result: ExecResultHolder::Mock(std::mem::take(&mut self.exec_results[counter])), }) } else { - Err(ExecErr) + Err(DbErr::Exec("`exec_results` buffer is empty.".to_owned())) } } @@ -83,7 +83,7 @@ impl MockDatabaseTrait for MockDatabase { &mut self, counter: usize, statement: Statement, - ) -> Result, QueryErr> { + ) -> Result, DbErr> { self.transaction_log.push(Transaction::one(statement)); if counter < self.query_results.len() { Ok(std::mem::take(&mut self.query_results[counter]) @@ -93,7 +93,7 @@ impl MockDatabaseTrait for MockDatabase { }) .collect()) } else { - Err(QueryErr) + Err(DbErr::Query("`query_results` buffer is empty.".to_owned())) } } @@ -103,7 +103,7 @@ impl MockDatabaseTrait for MockDatabase { } impl MockRow { - pub fn try_get(&self, col: &str) -> Result + pub fn try_get(&self, col: &str) -> Result where T: ValueType, { diff --git a/src/database/mod.rs b/src/database/mod.rs index ffcdb1c0..4ad1b347 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -10,23 +10,25 @@ pub use mock::*; pub use statement::*; pub use transaction::*; +use crate::DbErr; + #[derive(Debug, Default)] pub struct Database; impl Database { - pub async fn connect(string: &str) -> Result { + pub async fn connect(string: &str) -> Result { #[cfg(feature = "sqlx-mysql")] if crate::SqlxMySqlConnector::accepts(string) { - return Ok(crate::SqlxMySqlConnector::connect(string).await?); + return crate::SqlxMySqlConnector::connect(string).await; } #[cfg(feature = "sqlx-sqlite")] if crate::SqlxSqliteConnector::accepts(string) { - return Ok(crate::SqlxSqliteConnector::connect(string).await?); + return crate::SqlxSqliteConnector::connect(string).await; } #[cfg(feature = "mock")] if crate::MockDatabaseConnector::accepts(string) { - return Ok(crate::MockDatabaseConnector::connect(string).await?); + return crate::MockDatabaseConnector::connect(string).await; } - Err(ConnectionErr) + Err(DbErr::Conn(format!("The connection string '{}' has no supporting driver.", string))) } } diff --git a/src/driver/mock.rs b/src/driver/mock.rs index 2622fdde..96224c38 100644 --- a/src/driver/mock.rs +++ b/src/driver/mock.rs @@ -1,6 +1,6 @@ use crate::{ - debug_print, ConnectionErr, DatabaseConnection, ExecErr, ExecResult, MockDatabase, QueryErr, - QueryResult, Statement, Transaction, + debug_print, error::*, DatabaseConnection, ExecResult, MockDatabase, QueryResult, Statement, + Transaction, }; use std::sync::{ atomic::{AtomicUsize, Ordering}, @@ -15,9 +15,9 @@ pub struct MockDatabaseConnection { } pub trait MockDatabaseTrait: Send { - fn execute(&mut self, counter: usize, stmt: Statement) -> Result; + fn execute(&mut self, counter: usize, stmt: Statement) -> Result; - fn query(&mut self, counter: usize, stmt: Statement) -> Result, QueryErr>; + fn query(&mut self, counter: usize, stmt: Statement) -> Result, DbErr>; fn drain_transaction_log(&mut self) -> Vec; } @@ -27,7 +27,7 @@ impl MockDatabaseConnector { string.starts_with("mock://") } - pub async fn connect(_string: &str) -> Result { + pub async fn connect(_string: &str) -> Result { Ok(DatabaseConnection::MockDatabaseConnection( MockDatabaseConnection::new(MockDatabase::new()), )) @@ -49,20 +49,20 @@ impl MockDatabaseConnection { &self.mocker } - pub async fn execute(&self, statement: Statement) -> Result { + pub async fn execute(&self, statement: Statement) -> Result { debug_print!("{}", statement); let counter = self.counter.fetch_add(1, Ordering::SeqCst); self.mocker.lock().unwrap().execute(counter, statement) } - pub async fn query_one(&self, statement: Statement) -> Result, QueryErr> { + pub async fn query_one(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); let counter = self.counter.fetch_add(1, Ordering::SeqCst); let result = self.mocker.lock().unwrap().query(counter, statement)?; Ok(result.into_iter().next()) } - pub async fn query_all(&self, statement: Statement) -> Result, QueryErr> { + pub async fn query_all(&self, statement: Statement) -> Result, DbErr> { debug_print!("{}", statement); let counter = self.counter.fetch_add(1, Ordering::SeqCst); self.mocker.lock().unwrap().query(counter, statement) diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 2158f441..904e06a9 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -1,17 +1,17 @@ #[cfg(feature = "mock")] mod mock; +#[cfg(feature = "sqlx-dep")] +mod sqlx_common; #[cfg(feature = "sqlx-mysql")] mod sqlx_mysql; #[cfg(feature = "sqlx-sqlite")] mod sqlx_sqlite; -#[cfg(feature = "sqlx-dep")] -mod sqlx_types; #[cfg(feature = "mock")] pub use mock::*; +#[cfg(feature = "sqlx-dep")] +pub use sqlx_common::*; #[cfg(feature = "sqlx-mysql")] pub use sqlx_mysql::*; #[cfg(feature = "sqlx-sqlite")] pub use sqlx_sqlite::*; -#[cfg(feature = "sqlx-dep")] -pub use sqlx_types::*; diff --git a/src/driver/sqlx_common.rs b/src/driver/sqlx_common.rs new file mode 100644 index 00000000..5b1f7b35 --- /dev/null +++ b/src/driver/sqlx_common.rs @@ -0,0 +1,9 @@ +use crate::DbErr; + +pub fn sqlx_error_to_exec_err(err: sqlx::Error) -> DbErr { + DbErr::Exec(err.to_string()) +} + +pub fn sqlx_error_to_query_err(err: sqlx::Error) -> DbErr { + DbErr::Query(err.to_string()) +} diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index 8b609d61..ebea0a32 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -6,7 +6,9 @@ use sqlx::{ sea_query::sea_query_driver_mysql!(); use sea_query_driver_mysql::bind_query; -use crate::{debug_print, executor::*, ConnectionErr, DatabaseConnection, Statement}; +use crate::{debug_print, error::*, executor::*, DatabaseConnection, Statement}; + +use super::sqlx_common::*; pub struct SqlxMySqlConnector; @@ -19,13 +21,13 @@ impl SqlxMySqlConnector { string.starts_with("mysql://") } - pub async fn connect(string: &str) -> Result { + pub async fn connect(string: &str) -> Result { if let Ok(pool) = MySqlPool::connect(string).await { Ok(DatabaseConnection::SqlxMySqlPoolConnection( SqlxMySqlPoolConnection { pool }, )) } else { - Err(ConnectionErr) + Err(DbErr::Conn("Failed to connect.".to_owned())) } } } @@ -37,43 +39,49 @@ impl SqlxMySqlConnector { } impl SqlxMySqlPoolConnection { - pub async fn execute(&self, stmt: Statement) -> Result { + pub async fn execute(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); let query = sqlx_query(&stmt); if let Ok(conn) = &mut self.pool.acquire().await { - if let Ok(res) = query.execute(conn).await { - return Ok(res.into()); - } - } - Err(ExecErr) - } - - pub async fn query_one(&self, stmt: Statement) -> Result, QueryErr> { - debug_print!("{}", stmt); - - let query = sqlx_query(&stmt); - if let Ok(conn) = &mut self.pool.acquire().await { - if let Ok(row) = query.fetch_one(conn).await { - Ok(Some(row.into())) - } else { - Ok(None) + match query.execute(conn).await { + Ok(res) => Ok(res.into()), + Err(err) => Err(sqlx_error_to_exec_err(err)), } } else { - Err(QueryErr) + Err(DbErr::Exec("Failed to acquire connection from pool.".to_owned())) } } - pub async fn query_all(&self, stmt: Statement) -> Result, QueryErr> { + pub async fn query_one(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); let query = sqlx_query(&stmt); if let Ok(conn) = &mut self.pool.acquire().await { - if let Ok(rows) = query.fetch_all(conn).await { - return Ok(rows.into_iter().map(|r| r.into()).collect()); + match query.fetch_one(conn).await { + Ok(row) => Ok(Some(row.into())), + Err(err) => match err { + sqlx::Error::RowNotFound => Ok(None), + _ => Err(DbErr::Query(err.to_string())), + }, } + } else { + Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) + } + } + + pub async fn query_all(&self, stmt: Statement) -> Result, DbErr> { + debug_print!("{}", stmt); + + let query = sqlx_query(&stmt); + if let Ok(conn) = &mut self.pool.acquire().await { + match query.fetch_all(conn).await { + Ok(rows) => Ok(rows.into_iter().map(|r| r.into()).collect()), + Err(err) => Err(sqlx_error_to_query_err(err)), + } + } else { + Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) } - Err(QueryErr) } } diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs index 021cc919..ecd0ba4f 100644 --- a/src/driver/sqlx_sqlite.rs +++ b/src/driver/sqlx_sqlite.rs @@ -6,7 +6,9 @@ use sqlx::{ sea_query::sea_query_driver_sqlite!(); use sea_query_driver_sqlite::bind_query; -use crate::{debug_print, executor::*, ConnectionErr, DatabaseConnection, Statement}; +use crate::{debug_print, error::*, executor::*, DatabaseConnection, Statement}; + +use super::sqlx_common::*; pub struct SqlxSqliteConnector; @@ -19,13 +21,13 @@ impl SqlxSqliteConnector { string.starts_with("sqlite:") } - pub async fn connect(string: &str) -> Result { + pub async fn connect(string: &str) -> Result { if let Ok(pool) = SqlitePool::connect(string).await { Ok(DatabaseConnection::SqlxSqlitePoolConnection( SqlxSqlitePoolConnection { pool }, )) } else { - Err(ConnectionErr) + Err(DbErr::Conn("Failed to connect.".to_owned())) } } } @@ -37,43 +39,49 @@ impl SqlxSqliteConnector { } impl SqlxSqlitePoolConnection { - pub async fn execute(&self, stmt: Statement) -> Result { + pub async fn execute(&self, stmt: Statement) -> Result { debug_print!("{}", stmt); let query = sqlx_query(&stmt); if let Ok(conn) = &mut self.pool.acquire().await { - if let Ok(res) = query.execute(conn).await { - return Ok(res.into()); - } - } - Err(ExecErr) - } - - pub async fn query_one(&self, stmt: Statement) -> Result, QueryErr> { - debug_print!("{}", stmt); - - let query = sqlx_query(&stmt); - if let Ok(conn) = &mut self.pool.acquire().await { - if let Ok(row) = query.fetch_one(conn).await { - Ok(Some(row.into())) - } else { - Ok(None) + match query.execute(conn).await { + Ok(res) => Ok(res.into()), + Err(err) => Err(sqlx_error_to_exec_err(err)), } } else { - Err(QueryErr) + Err(DbErr::Exec("Failed to acquire connection from pool.".to_owned())) } } - pub async fn query_all(&self, stmt: Statement) -> Result, QueryErr> { + pub async fn query_one(&self, stmt: Statement) -> Result, DbErr> { debug_print!("{}", stmt); let query = sqlx_query(&stmt); if let Ok(conn) = &mut self.pool.acquire().await { - if let Ok(rows) = query.fetch_all(conn).await { - return Ok(rows.into_iter().map(|r| r.into()).collect()); + match query.fetch_one(conn).await { + Ok(row) => Ok(Some(row.into())), + Err(err) => match err { + sqlx::Error::RowNotFound => Ok(None), + _ => Err(DbErr::Query(err.to_string())), + }, } + } else { + Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) + } + } + + pub async fn query_all(&self, stmt: Statement) -> Result, DbErr> { + debug_print!("{}", stmt); + + let query = sqlx_query(&stmt); + if let Ok(conn) = &mut self.pool.acquire().await { + match query.fetch_all(conn).await { + Ok(rows) => Ok(rows.into_iter().map(|r| r.into()).collect()), + Err(err) => Err(sqlx_error_to_query_err(err)), + } + } else { + Err(DbErr::Query("Failed to acquire connection from pool.".to_owned())) } - Err(QueryErr) } } diff --git a/src/driver/sqlx_types.rs b/src/driver/sqlx_types.rs deleted file mode 100644 index 5a52572e..00000000 --- a/src/driver/sqlx_types.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::{ExecErr, TypeErr}; - -impl From for TypeErr { - fn from(_: sqlx::Error) -> TypeErr { - TypeErr - } -} - -impl From for ExecErr { - fn from(_: sqlx::Error) -> ExecErr { - ExecErr - } -} diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index dd35a423..96486dda 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -1,5 +1,5 @@ use crate::{ - DatabaseConnection, DeleteResult, EntityTrait, ExecErr, Iterable, PrimaryKeyToColumn, + error::*, DatabaseConnection, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, Value, }; use std::fmt::Debug; @@ -66,8 +66,8 @@ pub trait ActiveModelTrait: Clone + Debug { fn default() -> Self; // below is not yet possible. right now we define these methods in DeriveActiveModel - // fn save(self, db: &DatabaseConnection) -> impl Future>; - // fn delete(self, db: &DatabaseConnection) -> impl Future>; + // fn save(self, db: &DatabaseConnection) -> impl Future>; + // fn delete(self, db: &DatabaseConnection) -> impl Future>; } /// Behaviors for users to override @@ -188,7 +188,7 @@ where /// Insert the model if primary key is unset, update otherwise. /// Only works if the entity has auto increment primary key. -pub async fn save_active_model(mut am: A, db: &DatabaseConnection) -> Result +pub async fn save_active_model(mut am: A, db: &DatabaseConnection) -> Result where A: ActiveModelBehavior + ActiveModelTrait, E::Model: IntoActiveModel, @@ -212,7 +212,7 @@ where Ok(am) } -async fn insert_and_select_active_model(am: A, db: &DatabaseConnection) -> Result +async fn insert_and_select_active_model(am: A, db: &DatabaseConnection) -> Result where A: ActiveModelTrait, E::Model: IntoActiveModel, @@ -223,18 +223,18 @@ where // TODO: if the entity does not have auto increment primary key, then last_insert_id is a wrong value if ::auto_increment() && res.last_insert_id != 0 { let find = E::find_by_id(res.last_insert_id).one(db); - let res = find.await; - let model: Option = res.map_err(|_| ExecErr)?; + let found = find.await; + let model: Option = found?; match model { Some(model) => Ok(model.into_active_model()), - None => Err(ExecErr), + None => Err(DbErr::Exec(format!("Failed to find inserted item: {} {}", E::default().to_string(), res.last_insert_id))), } } else { Ok(A::default()) } } -async fn update_active_model(am: A, db: &DatabaseConnection) -> Result +async fn update_active_model(am: A, db: &DatabaseConnection) -> Result where A: ActiveModelTrait, E: EntityTrait, @@ -246,7 +246,7 @@ where pub async fn delete_active_model( mut am: A, db: &DatabaseConnection, -) -> Result +) -> Result where A: ActiveModelBehavior + ActiveModelTrait, E: EntityTrait, diff --git a/src/entity/model.rs b/src/entity/model.rs index 6161538b..73dd66ae 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, QueryFilter, QueryResult, Related, Select, TypeErr}; +use crate::{EntityTrait, DbErr, QueryFilter, QueryResult, Related, Select}; pub use sea_query::Value; use std::fmt::Debug; @@ -19,11 +19,11 @@ pub trait ModelTrait: Clone + Debug { } pub trait FromQueryResult { - fn from_query_result(res: &QueryResult, pre: &str) -> Result + fn from_query_result(res: &QueryResult, pre: &str) -> Result where Self: Sized; - fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result, TypeErr> + fn from_query_result_optional(res: &QueryResult, pre: &str) -> Result, DbErr> where Self: Sized, { diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index f7eb5cc1..77ef63f6 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -1,7 +1,7 @@ pub use crate::{ - ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType, DeriveActiveModel, - DeriveActiveModelBehavior, DeriveColumn, DeriveEntity, DeriveModel, DerivePrimaryKey, - EntityName, EntityTrait, EnumIter, Iden, IdenStatic, ModelTrait, PrimaryKeyToColumn, - PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, RelationTrait, Select, - TypeErr, Value, + error::*, ActiveModelBehavior, ActiveModelTrait, ColumnDef, ColumnTrait, ColumnType, + DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveEntity, DeriveModel, + DerivePrimaryKey, EntityName, EntityTrait, EnumIter, Iden, IdenStatic, ModelTrait, + PrimaryKeyToColumn, PrimaryKeyTrait, QueryFilter, QueryResult, Related, RelationDef, + RelationTrait, Select, Value, }; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..68f2313f --- /dev/null +++ b/src/error.rs @@ -0,0 +1,16 @@ +#[derive(Debug)] +pub enum DbErr { + Conn(String), + Exec(String), + Query(String), +} + +impl std::fmt::Display for DbErr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Conn(s) => write!(f, "Connection Error: {}", s), + Self::Exec(s) => write!(f, "Execution Error: {}", s), + Self::Query(s) => write!(f, "Query Error: {}", s), + } + } +} diff --git a/src/executor/delete.rs b/src/executor/delete.rs index ac051ab5..eebd0fc9 100644 --- a/src/executor/delete.rs +++ b/src/executor/delete.rs @@ -1,5 +1,5 @@ use crate::{ - ActiveModelTrait, DatabaseConnection, DeleteMany, DeleteOne, EntityTrait, ExecErr, Statement, + error::*, ActiveModelTrait, DatabaseConnection, DeleteMany, DeleteOne, EntityTrait, Statement, }; use sea_query::DeleteStatement; use std::future::Future; @@ -21,7 +21,7 @@ where pub fn exec( self, db: &'a DatabaseConnection, - ) -> impl Future> + 'a { + ) -> impl Future> + 'a { // so that self is dropped before entering await exec_delete_only(self.query, db) } @@ -34,7 +34,7 @@ where pub fn exec( self, db: &'a DatabaseConnection, - ) -> impl Future> + 'a { + ) -> impl Future> + 'a { // so that self is dropped before entering await exec_delete_only(self.query, db) } @@ -48,7 +48,7 @@ impl Deleter { pub fn exec( self, db: &DatabaseConnection, - ) -> impl Future> + '_ { + ) -> impl Future> + '_ { let builder = db.get_query_builder_backend(); exec_delete(builder.build(&self.query), db) } @@ -57,7 +57,7 @@ impl Deleter { async fn exec_delete_only( query: DeleteStatement, db: &DatabaseConnection, -) -> Result { +) -> Result { Deleter::new(query).exec(db).await } @@ -65,7 +65,7 @@ async fn exec_delete_only( async fn exec_delete( statement: Statement, db: &DatabaseConnection, -) -> Result { +) -> Result { let result = db.execute(statement).await?; Ok(DeleteResult { rows_affected: result.rows_affected(), diff --git a/src/executor/execute.rs b/src/executor/execute.rs index c1a7cd86..b795466a 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -1,5 +1,3 @@ -use std::{error::Error, fmt}; - #[derive(Debug)] pub struct ExecResult { pub(crate) result: ExecResultHolder, @@ -15,9 +13,6 @@ pub(crate) enum ExecResultHolder { Mock(crate::MockExecResult), } -#[derive(Debug)] -pub struct ExecErr; - // ExecResult // impl ExecResult { @@ -50,13 +45,3 @@ impl ExecResult { } } } - -// ExecErr // - -impl Error for ExecErr {} - -impl fmt::Display for ExecErr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} diff --git a/src/executor/insert.rs b/src/executor/insert.rs index 0c1132c3..cf36d5ff 100644 --- a/src/executor/insert.rs +++ b/src/executor/insert.rs @@ -1,4 +1,4 @@ -use crate::{ActiveModelTrait, DatabaseConnection, ExecErr, Insert, QueryTrait, Statement}; +use crate::{error::*, ActiveModelTrait, DatabaseConnection, Insert, QueryTrait, Statement}; use sea_query::InsertStatement; use std::future::Future; @@ -19,7 +19,7 @@ where pub fn exec( self, db: &DatabaseConnection, - ) -> impl Future> + '_ { + ) -> impl Future> + '_ { // so that self is dropped before entering await Inserter::new(self.into_query()).exec(db) } @@ -33,7 +33,7 @@ impl Inserter { pub fn exec( self, db: &DatabaseConnection, - ) -> impl Future> + '_ { + ) -> impl Future> + '_ { let builder = db.get_query_builder_backend(); exec_insert(builder.build(&self.query), db) } @@ -43,7 +43,7 @@ impl Inserter { async fn exec_insert( statement: Statement, db: &DatabaseConnection, -) -> Result { +) -> Result { let result = db.execute(statement).await?; // TODO: Postgres instead use query_one + returning clause Ok(InsertResult { diff --git a/src/executor/paginator.rs b/src/executor/paginator.rs index ead5114b..8ea2af9f 100644 --- a/src/executor/paginator.rs +++ b/src/executor/paginator.rs @@ -1,4 +1,4 @@ -use crate::{DatabaseConnection, QueryErr, SelectorTrait}; +use crate::{error::*, DatabaseConnection, SelectorTrait}; use async_stream::stream; use futures::Stream; use sea_query::{Alias, Expr, SelectStatement}; @@ -23,7 +23,7 @@ where S: SelectorTrait + 'db, { /// Fetch a specific page - pub async fn fetch_page(&self, page: usize) -> Result, QueryErr> { + pub async fn fetch_page(&self, page: usize) -> Result, DbErr> { let query = self .query .clone() @@ -36,18 +36,18 @@ where let mut buffer = Vec::with_capacity(rows.len()); for row in rows.into_iter() { // TODO: Error handling - buffer.push(S::from_raw_query_result(row).map_err(|_e| QueryErr)?); + buffer.push(S::from_raw_query_result(row)?); } Ok(buffer) } /// Fetch the current page - pub async fn fetch(&self) -> Result, QueryErr> { + pub async fn fetch(&self) -> Result, DbErr> { self.fetch_page(self.page).await } /// Get the total number of pages - pub async fn num_pages(&self) -> Result { + pub async fn num_pages(&self) -> Result { let builder = self.db.get_query_builder_backend(); let stmt = builder.build( SelectStatement::new() @@ -61,9 +61,7 @@ where Some(res) => res, None => return Ok(0), }; - let num_rows = result - .try_get::("", "num_rows") - .map_err(|_e| QueryErr)? as usize; + let num_rows = result.try_get::("", "num_rows")? as usize; let num_pages = (num_rows / self.page_size) + (num_rows % self.page_size > 0) as usize; Ok(num_pages) } @@ -79,7 +77,7 @@ where } /// Fetch one page and increment the page counter - pub async fn fetch_and_next(&mut self) -> Result>, QueryErr> { + pub async fn fetch_and_next(&mut self) -> Result>, DbErr> { let vec = self.fetch().await?; self.next(); let opt = if !vec.is_empty() { Some(vec) } else { None }; @@ -87,7 +85,7 @@ where } /// Convert self into an async stream - pub fn into_stream(mut self) -> PinBoxStream<'db, Result, QueryErr>> { + pub fn into_stream(mut self) -> PinBoxStream<'db, Result, DbErr>> { Box::pin(stream! { loop { if let Some(vec) = self.fetch_and_next().await? { @@ -105,7 +103,7 @@ where mod tests { use crate::entity::prelude::*; use crate::tests_cfg::*; - use crate::{DatabaseConnection, MockDatabase, QueryErr, Transaction}; + use crate::{DatabaseConnection, MockDatabase, Transaction}; use futures::TryStreamExt; use sea_query::{Alias, Expr, SelectStatement, Value}; @@ -150,7 +148,7 @@ mod tests { } #[async_std::test] - async fn fetch_page() -> Result<(), QueryErr> { + async fn fetch_page() -> Result<(), DbErr> { let (db, pages) = setup(); let paginator = fruit::Entity::find().paginate(&db, 2); @@ -180,7 +178,7 @@ mod tests { } #[async_std::test] - async fn fetch() -> Result<(), QueryErr> { + async fn fetch() -> Result<(), DbErr> { let (db, pages) = setup(); let mut paginator = fruit::Entity::find().paginate(&db, 2); @@ -214,7 +212,7 @@ mod tests { } #[async_std::test] - async fn num_pages() -> Result<(), QueryErr> { + async fn num_pages() -> Result<(), DbErr> { let (db, num_rows) = setup_num_rows(); let num_rows = num_rows as usize; @@ -246,7 +244,7 @@ mod tests { } #[async_std::test] - async fn next_and_cur_page() -> Result<(), QueryErr> { + async fn next_and_cur_page() -> Result<(), DbErr> { let (db, _) = setup(); let mut paginator = fruit::Entity::find().paginate(&db, 2); @@ -262,7 +260,7 @@ mod tests { } #[async_std::test] - async fn fetch_and_next() -> Result<(), QueryErr> { + async fn fetch_and_next() -> Result<(), DbErr> { let (db, pages) = setup(); let mut paginator = fruit::Entity::find().paginate(&db, 2); @@ -297,7 +295,7 @@ mod tests { } #[async_std::test] - async fn into_stream() -> Result<(), QueryErr> { + async fn into_stream() -> Result<(), DbErr> { let (db, pages) = setup(); let mut fruit_stream = fruit::Entity::find().paginate(&db, 2).into_stream(); diff --git a/src/executor/query.rs b/src/executor/query.rs index 330c8748..1d23a013 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -1,4 +1,5 @@ -use std::{error::Error, fmt}; +use crate::DbErr; +use std::fmt; #[derive(Debug)] pub struct QueryResult { @@ -14,14 +15,8 @@ pub(crate) enum QueryResultRow { Mock(crate::MockRow), } -#[derive(Debug)] -pub struct QueryErr; - -#[derive(Debug)] -pub struct TypeErr; - pub trait TryGetable { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result where Self: Sized; } @@ -29,7 +24,7 @@ pub trait TryGetable { // QueryResult // impl QueryResult { - pub fn try_get(&self, pre: &str, col: &str) -> Result + pub fn try_get(&self, pre: &str, col: &str) -> Result where T: TryGetable, { @@ -50,49 +45,23 @@ impl fmt::Debug for QueryResultRow { } } -// QueryErr // - -impl Error for QueryErr {} - -impl fmt::Display for QueryErr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From for QueryErr { - fn from(_: TypeErr) -> QueryErr { - QueryErr - } -} - -// TypeErr // - -impl Error for TypeErr {} - -impl fmt::Display for TypeErr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - // TryGetable // macro_rules! try_getable_all { ( $type: ty ) => { impl TryGetable for $type { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { let column = format!("{}{}", pre, col); match &res.row { #[cfg(feature = "sqlx-mysql")] QueryResultRow::SqlxMySql(row) => { use sqlx::Row; - Ok(row.try_get(column.as_str())?) + row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) } #[cfg(feature = "sqlx-sqlite")] QueryResultRow::SqlxSqlite(row) => { use sqlx::Row; - Ok(row.try_get(column.as_str())?) + row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) } #[cfg(feature = "mock")] QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?), @@ -101,7 +70,7 @@ macro_rules! try_getable_all { } impl TryGetable for Option<$type> { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { let column = format!("{}{}", pre, col); match &res.row { #[cfg(feature = "sqlx-mysql")] @@ -134,13 +103,13 @@ macro_rules! try_getable_all { macro_rules! try_getable_mysql { ( $type: ty ) => { impl TryGetable for $type { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { let column = format!("{}{}", pre, col); match &res.row { #[cfg(feature = "sqlx-mysql")] QueryResultRow::SqlxMySql(row) => { use sqlx::Row; - Ok(row.try_get(column.as_str())?) + row.try_get(column.as_str()).map_err(crate::sqlx_error_to_query_err) } #[cfg(feature = "sqlx-sqlite")] QueryResultRow::SqlxSqlite(_) => { @@ -153,7 +122,7 @@ macro_rules! try_getable_mysql { } impl TryGetable for Option<$type> { - fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { let column = format!("{}{}", pre, col); match &res.row { #[cfg(feature = "sqlx-mysql")] diff --git a/src/executor/select.rs b/src/executor/select.rs index 6c74c8cb..607e0de0 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -1,7 +1,7 @@ use crate::{ - query::combine, DatabaseConnection, EntityTrait, FromQueryResult, Iterable, JsonValue, - ModelTrait, Paginator, PrimaryKeyToColumn, QueryErr, QueryResult, Select, SelectTwo, - SelectTwoMany, TypeErr, + error::*, query::combine, DatabaseConnection, EntityTrait, FromQueryResult, Iterable, + JsonValue, ModelTrait, Paginator, PrimaryKeyToColumn, QueryResult, Select, SelectTwo, + SelectTwoMany, }; use sea_query::SelectStatement; use std::marker::PhantomData; @@ -18,7 +18,7 @@ where pub trait SelectorTrait { type Item: Sized; - fn from_raw_query_result(res: QueryResult) -> Result; + fn from_raw_query_result(res: QueryResult) -> Result; } pub struct SelectModel @@ -43,7 +43,7 @@ where { type Item = M; - fn from_raw_query_result(res: QueryResult) -> Result { + fn from_raw_query_result(res: QueryResult) -> Result { M::from_query_result(&res, "") } } @@ -55,7 +55,7 @@ where { type Item = (M, Option); - fn from_raw_query_result(res: QueryResult) -> Result { + fn from_raw_query_result(res: QueryResult) -> Result { Ok(( M::from_query_result(&res, combine::SELECT_A)?, N::from_query_result_optional(&res, combine::SELECT_B)?, @@ -85,11 +85,11 @@ where } } - pub async fn one(self, db: &DatabaseConnection) -> Result, QueryErr> { + pub async fn one(self, db: &DatabaseConnection) -> Result, DbErr> { self.into_model::().one(db).await } - pub async fn all(self, db: &DatabaseConnection) -> Result, QueryErr> { + pub async fn all(self, db: &DatabaseConnection) -> Result, DbErr> { self.into_model::().all(db).await } @@ -129,14 +129,14 @@ where pub async fn one( self, db: &DatabaseConnection, - ) -> Result)>, QueryErr> { + ) -> Result)>, DbErr> { self.into_model::().one(db).await } pub async fn all( self, db: &DatabaseConnection, - ) -> Result)>, QueryErr> { + ) -> Result)>, DbErr> { self.into_model::().all(db).await } } @@ -168,14 +168,14 @@ where pub async fn one( self, db: &DatabaseConnection, - ) -> Result)>, QueryErr> { + ) -> Result)>, DbErr> { self.into_model::().one(db).await } pub async fn all( self, db: &DatabaseConnection, - ) -> Result)>, QueryErr> { + ) -> Result)>, DbErr> { let rows = self.into_model::().all(db).await?; Ok(consolidate_query_result::(rows)) } @@ -185,7 +185,7 @@ impl Selector where S: SelectorTrait, { - pub async fn one(mut self, db: &DatabaseConnection) -> Result, QueryErr> { + pub async fn one(mut self, db: &DatabaseConnection) -> Result, DbErr> { let builder = db.get_query_builder_backend(); self.query.limit(1); let row = db.query_one(builder.build(&self.query)).await?; @@ -195,7 +195,7 @@ where } } - pub async fn all(self, db: &DatabaseConnection) -> Result, QueryErr> { + pub async fn all(self, db: &DatabaseConnection) -> Result, DbErr> { let builder = db.get_query_builder_backend(); let rows = db.query_all(builder.build(&self.query)).await?; let mut models = Vec::new(); diff --git a/src/executor/update.rs b/src/executor/update.rs index df4fabd4..0a486afc 100644 --- a/src/executor/update.rs +++ b/src/executor/update.rs @@ -1,5 +1,5 @@ use crate::{ - ActiveModelTrait, DatabaseConnection, EntityTrait, ExecErr, Statement, UpdateMany, UpdateOne, + error::*, ActiveModelTrait, DatabaseConnection, EntityTrait, Statement, UpdateMany, UpdateOne, }; use sea_query::UpdateStatement; use std::future::Future; @@ -18,7 +18,10 @@ impl<'a, A: 'a> UpdateOne where A: ActiveModelTrait, { - pub fn exec(self, db: &'a DatabaseConnection) -> impl Future> + 'a { + pub fn exec( + self, + db: &'a DatabaseConnection, + ) -> impl Future> + 'a { // so that self is dropped before entering await exec_update_and_return_original(self.query, self.model, db) } @@ -31,7 +34,7 @@ where pub fn exec( self, db: &'a DatabaseConnection, - ) -> impl Future> + 'a { + ) -> impl Future> + 'a { // so that self is dropped before entering await exec_update_only(self.query, db) } @@ -45,7 +48,7 @@ impl Updater { pub fn exec( self, db: &DatabaseConnection, - ) -> impl Future> + '_ { + ) -> impl Future> + '_ { let builder = db.get_query_builder_backend(); exec_update(builder.build(&self.query), db) } @@ -54,7 +57,7 @@ impl Updater { async fn exec_update_only( query: UpdateStatement, db: &DatabaseConnection, -) -> Result { +) -> Result { Updater::new(query).exec(db).await } @@ -62,7 +65,7 @@ async fn exec_update_and_return_original( query: UpdateStatement, model: A, db: &DatabaseConnection, -) -> Result +) -> Result where A: ActiveModelTrait, { @@ -74,7 +77,7 @@ where async fn exec_update( statement: Statement, db: &DatabaseConnection, -) -> Result { +) -> Result { let result = db.execute(statement).await?; Ok(UpdateResult { rows_affected: result.rows_affected(), diff --git a/src/lib.rs b/src/lib.rs index 3214dc89..ae71656e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ //! API to make working with databases in Rust a first-class experience. //! //! ```ignore -//! This is an early WIP of SeaORM, and is not yet published. See [example](examples/sqlx-mysql/src) for demo usage. +//! This is a preview of SeaORM, and is not yet released. //! ``` //! //! ## Features @@ -29,23 +29,22 @@ //! //! 2. Dynamic //! -//! Built upon SeaQuery, a dynamic query builder, SeaORM allows you to build complex queries without 'fighting the ORM'. +//! Built upon SeaQuery, SeaORM allows you to build complex queries without 'fighting the ORM'. //! //! 3. Testable //! //! Use mock connections to write unit tests for your logic. //! -//! 4. API oriented +//! 4. Service oriented //! -//! Quickly build search models that help you join, filter, sort and paginate data in APIs. +//! Quickly build services that join, filter, sort and paginate data in APIs. //! -//! # A quick taste of SeaORM +//! ## A quick taste of SeaORM //! -//! ## Select +//! ### Select //! ``` -//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*}; -//! # async fn function(db: &DbConn) -> Result<(), QueryErr> { -//! # +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: &DbConn) -> Result<(), DbErr> { //! // find all models //! let cakes: Vec = Cake::find().all(db).await?; //! @@ -71,11 +70,10 @@ //! # Ok(()) //! # } //! ``` -//! ## Insert +//! ### Insert //! ``` -//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*}; -//! # async fn function(db: &DbConn) -> Result<(), ExecErr> { -//! # +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: &DbConn) -> Result<(), DbErr> { //! let apple = fruit::ActiveModel { //! name: Set("Apple".to_owned()), //! ..Default::default() // no need to set primary key @@ -90,16 +88,13 @@ //! let res: InsertResult = Fruit::insert(pear).exec(db).await?; //! //! println!("InsertResult: {}", res.last_insert_id); -//! # //! # Ok(()) //! # } -//! # -//! # async fn function2(db: &DbConn) -> Result<(), ExecErr> { +//! # async fn function2(db: &DbConn) -> Result<(), DbErr> { //! # let apple = fruit::ActiveModel { //! # name: Set("Apple".to_owned()), //! # ..Default::default() // no need to set primary key //! # }; -//! # //! # let pear = fruit::ActiveModel { //! # name: Set("Pear".to_owned()), //! # ..Default::default() @@ -107,25 +102,18 @@ //! //! // insert many //! Fruit::insert_many(vec![apple, pear]).exec(db).await?; -//! # //! # Ok(()) //! # } //! ``` -//! ## Update +//! ### Update //! ``` -//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*}; -//! # +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; //! use sea_orm::sea_query::{Expr, Value}; //! -//! # async fn function(db: &DbConn) -> Result<(), QueryErr> { +//! # async fn function(db: &DbConn) -> Result<(), DbErr> { //! let pear: Option = Fruit::find_by_id(1).one(db).await?; -//! # Ok(()) -//! # } -//! # -//! # async fn function2(db: &DbConn) -> Result<(), ExecErr> { -//! # let pear: Option = Fruit::find_by_id(1).one(db).await.unwrap(); -//! //! let mut pear: fruit::ActiveModel = pear.unwrap().into(); +//! //! pear.name = Set("Sweet pear".to_owned()); //! //! // update one @@ -141,21 +129,39 @@ //! # Ok(()) //! # } //! ``` -//! ## Delete +//! ### Save //! ``` -//! # use sea_orm::{DbConn, entity::*, query::*, tests_cfg::*}; -//! # -//! # async fn function(db: &DbConn) -> Result<(), QueryErr> { -//! let orange: Option = Fruit::find_by_id(1).one(db).await?; +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: &DbConn) -> Result<(), DbErr> { +//! let banana = fruit::ActiveModel { +//! id: Unset(None), +//! name: Set("Banana".to_owned()), +//! ..Default::default() +//! }; +//! +//! // create, because primary key `id` is `Unset` +//! let mut banana = banana.save(db).await?; +//! +//! banana.name = Set("Banana Mongo".to_owned()); +//! +//! // update, because primary key `id` is `Set` +//! let banana = banana.save(db).await?; +//! //! # Ok(()) //! # } -//! # -//! # async fn function2(db: &DbConn) -> Result<(), ExecErr> { -//! # let orange: Option = Fruit::find_by_id(1).one(db).await.unwrap(); +//! ``` +//! ### Delete +//! ``` +//! # use sea_orm::{DbConn, error::*, entity::*, query::*, tests_cfg::*}; +//! # async fn function(db: &DbConn) -> Result<(), DbErr> { +//! let orange: Option = Fruit::find_by_id(1).one(db).await?; //! let orange: fruit::ActiveModel = orange.unwrap().into(); //! //! // delete one //! fruit::Entity::delete(orange).exec(db).await?; +//! // or simply +//! # let orange: fruit::ActiveModel = Fruit::find_by_id(1).one(db).await.unwrap().unwrap().into(); +//! orange.delete(db).await?; //! //! // delete many: DELETE FROM "fruit" WHERE "fruit"."name" LIKE 'Orange' //! fruit::Entity::delete_many() @@ -166,9 +172,30 @@ //! # Ok(()) //! # } //! ``` +//! ## License +//! +//! Licensed under either of +//! +//! - Apache License, Version 2.0 +//! ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +//! - MIT license +//! ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +//! +//! at your option. +//! +//! ## Contribution +//! +//! Unless you explicitly state otherwise, any contribution intentionally submitted +//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +//! dual licensed as above, without any additional terms or conditions. +#![doc( + html_logo_url = "https://raw.githubusercontent.com/SeaQL/sea-query/master/docs/SeaQL icon dark.png" +)] + mod database; mod driver; pub mod entity; +pub mod error; mod executor; pub mod query; #[doc(hidden)] @@ -178,6 +205,7 @@ mod util; pub use database::*; pub use driver::*; pub use entity::*; +pub use error::*; pub use executor::*; pub use query::*; @@ -188,3 +216,4 @@ pub use sea_orm_macros::{ pub use sea_query; pub use sea_query::Iden; pub use strum::EnumIter; +pub use strum; diff --git a/src/query/json.rs b/src/query/json.rs index b41309d4..aa3d9c6c 100644 --- a/src/query/json.rs +++ b/src/query/json.rs @@ -1,9 +1,9 @@ -use crate::{FromQueryResult, QueryResult, QueryResultRow, TypeErr}; +use crate::{FromQueryResult, DbErr, QueryResult, QueryResultRow}; use serde_json::Map; pub use serde_json::Value as JsonValue; impl FromQueryResult for JsonValue { - fn from_query_result(res: &QueryResult, pre: &str) -> Result { + fn from_query_result(res: &QueryResult, pre: &str) -> Result { match &res.row { #[cfg(feature = "sqlx-mysql")] QueryResultRow::SqlxMySql(row) => { diff --git a/src/query/mod.rs b/src/query/mod.rs index 51060a1e..86668231 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -20,4 +20,4 @@ pub use select::*; pub use traits::*; pub use update::*; -pub use crate::executor::{ExecErr, InsertResult, QueryErr, UpdateResult}; +pub use crate::executor::{InsertResult, UpdateResult}; diff --git a/tests/basic.rs b/tests/basic.rs index f4c4ef74..641fc1aa 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,4 +1,4 @@ -use sea_orm::{entity::*, query::*, sea_query, tests_cfg::*, DbConn}; +use sea_orm::{entity::*, error::*, sea_query, tests_cfg::*, DbConn}; mod setup; @@ -31,7 +31,7 @@ async fn setup_schema(db: &DbConn) { println!("Create table cake: {:?}", result); } -async fn crud_cake(db: &DbConn) -> Result<(), ExecErr> { +async fn crud_cake(db: &DbConn) -> Result<(), DbErr> { let apple = cake::ActiveModel { name: Set("Apple Pie".to_owned()), ..Default::default() @@ -57,10 +57,7 @@ async fn crud_cake(db: &DbConn) -> Result<(), ExecErr> { println!(); println!("Updated: {:?}", apple); - let apple = cake::Entity::find_by_id(1) - .one(db) - .await - .map_err(|_| ExecErr)?; + let apple = cake::Entity::find_by_id(1).one(db).await?; assert_eq!( Some(cake::Model { @@ -77,10 +74,7 @@ async fn crud_cake(db: &DbConn) -> Result<(), ExecErr> { println!(); println!("Deleted: {:?}", result); - let apple = cake::Entity::find_by_id(1) - .one(db) - .await - .map_err(|_| ExecErr)?; + let apple = cake::Entity::find_by_id(1).one(db).await?; assert_eq!(None, apple);