diff --git a/SeaORM/docs/02-install-and-config/01-database-and-async-runtime.md b/SeaORM/docs/02-install-and-config/01-database-and-async-runtime.md index f2255bb2ada..d97685f2389 100644 --- a/SeaORM/docs/02-install-and-config/01-database-and-async-runtime.md +++ b/SeaORM/docs/02-install-and-config/01-database-and-async-runtime.md @@ -41,6 +41,14 @@ Basically, they are in the form of `runtime-ASYNC_RUNTIME-TLS_LIB`: ## Extra features -`debug-print` - print every SQL statement to logger - -`mock` - mock interface for unit testing ++ `debug-print` - print every SQL statement to logger ++ `mock` - mock interface for unit testing ++ `macros` - procedural macros for your convenient ++ `with-chrono` - support [`chrono`](https://crates.io/crates/chrono) types ++ `with-time` - support [`time`](https://crates.io/crates/time) types ++ `with-json` - support [`serde-json`](https://crates.io/crates/serde-json) types ++ `with-rust_decimal` - support [`rust_decimal`](https://crates.io/crates/rust_decimal) types ++ `with-bigdecimal` - support [`bigdecimal`](https://crates.io/crates/bigdecimal) types ++ `with-uuid` - support [`uuid`](https://crates.io/crates/uuid) types ++ `postgres-array` - support array types in Postgres ++ `sea-orm-internal` - opt-in unstable internal APIs (for accessing re-export SQLx types) diff --git a/SeaORM/docs/02-install-and-config/02-connection.md b/SeaORM/docs/02-install-and-config/02-connection.md index 4a212cd9770..e7c573bbe35 100644 --- a/SeaORM/docs/02-install-and-config/02-connection.md +++ b/SeaORM/docs/02-install-and-config/02-connection.md @@ -34,3 +34,14 @@ opt.max_connections(100) let db = Database::connect(opt).await?; ``` + +## Closing Connection + +The connection will be automatically closed on drop. To close the connection explicitly, call the `close` method. + +```rust +let db = Database::connect(url).await?; + +// Closing connection here +db.close().await?; +``` diff --git a/SeaORM/docs/03-migration/01-setting-up-migration.md b/SeaORM/docs/03-migration/01-setting-up-migration.md index 42a0c39e9dd..42f76c8c2f1 100644 --- a/SeaORM/docs/03-migration/01-setting-up-migration.md +++ b/SeaORM/docs/03-migration/01-setting-up-migration.md @@ -76,7 +76,7 @@ use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; -#[async_trait::async_trait] +#[async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { // Replace the sample below with your own migration scripts diff --git a/SeaORM/docs/03-migration/02-writing-migration.md b/SeaORM/docs/03-migration/02-writing-migration.md index c3c58ce7dea..a50a0525bec 100644 --- a/SeaORM/docs/03-migration/02-writing-migration.md +++ b/SeaORM/docs/03-migration/02-writing-migration.md @@ -21,7 +21,7 @@ use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; -#[async_trait::async_trait] +#[async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager @@ -46,7 +46,7 @@ mod m20220101_000001_create_table; pub struct Migrator; -#[async_trait::async_trait] +#[async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ @@ -212,26 +212,48 @@ use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; -#[async_trait::async_trait] +#[async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let sql = r#" - CREATE TABLE `cake` ( - `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, - `name` varchar(255) NOT NULL - )"#; - let stmt = Statement::from_string(manager.get_database_backend(), sql.to_owned()); - manager.get_connection().execute(stmt).await.map(|_| ()) + let db = manager.get_connection(); + + // Use `execute_unprepared` if the SQL statement doesn't have value bindings + db.execute_unprepared( + "CREATE TABLE `cake` ( + `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` varchar(255) NOT NULL + )" + ) + .await?; + + // Construct a `Statement` if the SQL contains value bindings + let stmt = Statement::from_sql_and_values( + manager.get_database_backend(), + r#"INSERT INTO `cake` (`name`) VALUES (?)"#, + ["Cheese Cake".into()] + ); + db.execute(stmt).await?; + + Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let sql = "DROP TABLE `cake`"; - let stmt = Statement::from_string(manager.get_database_backend(), sql.to_owned()); - manager.get_connection().execute(stmt).await.map(|_| ()) + manager + .get_connection() + .execute_unprepared("DROP TABLE `cake`") + .await?; + + Ok(()) } } ``` +## Atomic Migration + +Migration will be executed in Postgres atomically that means migration scripts will be executed inside a transaction. Changes done to the database will be rolled back if the migration failed. However, atomic migration is not supported in MySQL and SQLite. + +You can start a transaction inside each migration to perform operations like [seeding sample data](03-migration/04-seeding-data.md#seeding-data-transactionally) for a newly created table. + ## Schema first or Entity first? In the grand scheme of things, we recommend a schema first approach: you write migrations first and then generate entities from a live database. diff --git a/SeaORM/docs/03-migration/04-seeding-data.md b/SeaORM/docs/03-migration/04-seeding-data.md index a1e14a3e27a..fd43b4ba698 100644 --- a/SeaORM/docs/03-migration/04-seeding-data.md +++ b/SeaORM/docs/03-migration/04-seeding-data.md @@ -7,7 +7,7 @@ use sea_orm_migration::sea_orm::{entity::*, query::*}; // ... -#[async_trait::async_trait] +#[async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let db = manager.get_connection(); @@ -31,7 +31,7 @@ use sea_orm_migration::sea_orm::{entity::*, query::*}; // ... -#[async_trait::async_trait] +#[async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { let insert = Query::insert() @@ -54,3 +54,35 @@ pub enum Cake { Name, } ``` + +## Seeding Data Transactionally + +Starts a transaction and execute SQL inside migration up and down. + +```rust +use sea_orm_migration::sea_orm::{entity::*, query::*}; + +// ... + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Get the connection and start a transaction + let db = manager.get_connection(); + let transaction = db.begin().await?; + + // Insert with the transaction connection + cake::ActiveModel { + name: Set("Cheesecake".to_owned()), + ..Default::default() + } + .insert(&transaction) + .await?; + + // Commit it + transaction.commit().await?; + + Ok(()) + } +} +``` diff --git a/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md b/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md index 8632c1bb1fa..d730a3f4fb0 100644 --- a/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md +++ b/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md @@ -51,8 +51,12 @@ Command line options: - `--compact-format`: generate entity file of [compact format](04-generate-entity/02-entity-structure.md) (default: true) - `--expanded-format`: generate entity file of [expanded format](04-generate-entity/03-expanded-entity-structure.md) - `--with-serde`: automatically derive serde Serialize / Deserialize traits for the entity (`none`, `serialize`, `deserialize`, `both`) (default: `none`) + - `--serde-skip-deserializing-primary-key`: generate entity model with primary key field labeled as `#[serde(skip_deserializing)]` + - `--serde-skip-hidden-column`: generate entity model with hidden column (column name starts with `_`) field labeled as `#[serde(skip)]` - `--date-time-crate`: the datetime crate to use for generating entities (`chrono`, `time`) (default: `chrono`) - `--max-connections`: maximum number of database connections to be initialized in the connection pool (default: `1`) +- `--model-extra-derives`: append extra derive macros to the generated model struct +- `--model-extra-attributes`: append extra attributes to generated model struct ```shell # Generate entity files of database `bakery` to `entity/src` diff --git a/SeaORM/docs/04-generate-entity/02-entity-structure.md b/SeaORM/docs/04-generate-entity/02-entity-structure.md index 928a2d22a3e..7b1dde8edf5 100644 --- a/SeaORM/docs/04-generate-entity/02-entity-structure.md +++ b/SeaORM/docs/04-generate-entity/02-entity-structure.md @@ -86,7 +86,7 @@ For the mappings of Rust non-primitive data types. You can check [`entity/prelud | `DateTime`: chrono::NaiveDateTime
`TimeDateTime`: time::PrimitiveDateTime | DateTime | text | datetime | timestamp | | `DateTimeLocal`: chrono::DateTime<Local>
`DateTimeUtc`: chrono::DateTime<Utc> | Timestamp | text | timestamp | N/A | | `DateTimeWithTimeZone`: chrono::DateTime<FixedOffset>
`TimeDateTimeWithTimeZone`: time::OffsetDateTime | TimestampWithTimeZone | text | timestamp | timestamp with time zone | -| `Uuid`: uuid::Uuid | Uuid | text | binary(16) | uuid | +| `Uuid`: uuid::Uuid, uuid::fmt::Braced, uuid::fmt::Hyphenated, uuid::fmt::Simple, uuid::fmt::Urn | Uuid | text | binary(16) | uuid | | `Json`: serde_json::Value | Json | text | json | json | | `Decimal`: rust_decimal::Decimal | Decimal | real | decimal | decimal | @@ -162,6 +162,15 @@ If you want to ignore a particular model attribute such that it maps to no datab pub ignore_me: String ``` +### Cast Column Type on Select and Save + +If you need to select a column as one type but save it into the database as another, you can specify the `select_as` and the `save_as` attributes to perform the casting. A typical use case is selecting a column of type `citext` (case-insensitive text) as `String` in Rust and saving it into the database as `citext`. One should define the model field as below: + +```rust +#[sea_orm(select_as = "text", save_as = "citext")] +pub case_insensitive_text: String +``` + ## Primary Key Use the `primary_key` attribute to mark a column as the primary key. diff --git a/SeaORM/docs/04-generate-entity/03-expanded-entity-structure.md b/SeaORM/docs/04-generate-entity/03-expanded-entity-structure.md index 5431ddebde3..5eb7401a7a2 100644 --- a/SeaORM/docs/04-generate-entity/03-expanded-entity-structure.md +++ b/SeaORM/docs/04-generate-entity/03-expanded-entity-structure.md @@ -72,6 +72,30 @@ To specify the datatype of each column, the [`ColumnType`](https://docs.rs/sea-o ColumnType::String(None).def().default_value("Sam").unique().indexed().nullable() ``` +### Cast Column Type on Select and Save + +If you need to select a column as one type but save it into the database as another, you can override the `select_as` and the `save_as` methods to perform the casting. A typical use case is selecting a column of type `citext` (case-insensitive text) as `String` in Rust and saving it into the database as `citext`. One should override the `ColumnTrait`'s methods as below: + +```rust +use sea_orm::sea_query::{Expr, SimpleExpr, Alias} + +impl ColumnTrait for Column { + // Snipped... + + /// Cast column expression used in select statement. + fn select_as(&self, expr: Expr) -> SimpleExpr { + Column::CaseInsensitiveText => expr.cast_as(Alias::new("text")), + _ => self.select_enum_as(expr), + } + + /// Cast value of a column into the correct type for database storage. + fn save_as(&self, val: Expr) -> SimpleExpr { + Column::CaseInsensitiveText => val.cast_as(Alias::new("citext")), + _ => self.save_enum_as(val), + } +} +``` + ## Primary Key An enum representing the primary key of this table. A composite key is represented by an enum with multiple variants. @@ -153,6 +177,7 @@ pub struct ActiveModel { Handlers for different triggered actions on an `ActiveModel`. For example, you can perform custom validation logic, preventing a model from saving into database. You can abort an action even after it is done, if you are inside a transaction. ```rust +#[async_trait] impl ActiveModelBehavior for ActiveModel { /// Create a new ActiveModel with default values. Also used by `Default::default()`. fn new() -> Self { @@ -163,7 +188,10 @@ impl ActiveModelBehavior for ActiveModel { } /// Will be triggered before insert / update - fn before_save(self, insert: bool) -> Result { + async fn before_save(self, db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { if self.price.as_ref() <= &0.0 { Err(DbErr::Custom(format!( "[before_save] Invalid Price, insert: {}", @@ -175,17 +203,26 @@ impl ActiveModelBehavior for ActiveModel { } /// Will be triggered after insert / update - fn after_save(model: Model, insert: bool) -> Result { + async fn after_save(model: Model, db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { Ok(model) } /// Will be triggered before delete - fn before_delete(self) -> Result { + async fn before_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { Ok(self) } /// Will be triggered after delete - fn after_delete(self) -> Result { + async fn after_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { Ok(self) } } diff --git a/SeaORM/docs/05-basic-crud/03-insert.md b/SeaORM/docs/05-basic-crud/03-insert.md index 9ed11418dc3..3c27626060d 100644 --- a/SeaORM/docs/05-basic-crud/03-insert.md +++ b/SeaORM/docs/05-basic-crud/03-insert.md @@ -7,13 +7,19 @@ Before diving into SeaORM insert we have to introduce `ActiveValue` and `ActiveM A wrapper struct to capture the changes made to `ActiveModel` attributes. ```rust -use sea_orm::ActiveValue::NotSet; +use sea_orm::ActiveValue::{Set, NotSet, Unchanged}; // Set value let _: ActiveValue = Set(10); // NotSet value let _: ActiveValue = NotSet; + +// An `Unchanged` value +let v: ActiveValue = Unchanged(10); + +// Convert `Unchanged` active value as `Set` +assert!(v.reset(), Set(10)); ``` ## Model & ActiveModel @@ -177,7 +183,7 @@ let orange = fruit::ActiveModel { ..Default::default() }; -let res: InsertResult = Fruit::insert_many(vec![apple, orange]).exec(db).await?; +let res: InsertResult = Fruit::insert_many([apple, orange]).exec(db).await?; assert_eq!(res.last_insert_id, 30) ``` diff --git a/SeaORM/docs/05-basic-crud/04-update.md b/SeaORM/docs/05-basic-crud/04-update.md index 5747fe766b1..31e89662edb 100644 --- a/SeaORM/docs/05-basic-crud/04-update.md +++ b/SeaORM/docs/05-basic-crud/04-update.md @@ -13,7 +13,25 @@ let mut pear: fruit::ActiveModel = pear.unwrap().into(); // Update name attribute pear.name = Set("Sweet pear".to_owned()); -// Update corresponding row in database using primary key value +// SQL: `UPDATE "fruit" SET "name" = 'Sweet pear' WHERE "id" = 28` +let pear: fruit::Model = pear.update(db).await?; +``` + +To update all attributes, you can convert `Unchanged` into `Set`. + +```rust +// Into ActiveModel +let mut pear: fruit::ActiveModel = pear.into(); + +// Update name attribute +pear.name = Set("Sweet pear".to_owned()); + +// Set a specific attribute as "dirty" (force update) +pear.reset(fruit::Column::CakeId); +// Or, set all attributes as "dirty" (force update) +pear.reset_all(); + +// SQL: `UPDATE "fruit" SET "name" = 'Sweet pear', "cake_id" = 10 WHERE "id" = 28` let pear: fruit::Model = pear.update(db).await?; ``` diff --git a/SeaORM/docs/05-basic-crud/07-json.md b/SeaORM/docs/05-basic-crud/07-json.md index d794d657015..5778e1e43c5 100644 --- a/SeaORM/docs/05-basic-crud/07-json.md +++ b/SeaORM/docs/05-basic-crud/07-json.md @@ -29,7 +29,7 @@ let cakes: Vec = Cake::find() assert_eq!( cakes, - vec![ + [ serde_json::json!({ "id": 2, "name": "Chocolate Forest" diff --git a/SeaORM/docs/05-basic-crud/08-raw-sql.md b/SeaORM/docs/05-basic-crud/08-raw-sql.md index 72ce686b2a7..d07fa8c0963 100644 --- a/SeaORM/docs/05-basic-crud/08-raw-sql.md +++ b/SeaORM/docs/05-basic-crud/08-raw-sql.md @@ -9,7 +9,7 @@ let cheese: Option = cake::Entity::find() .from_raw_sql(Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, - vec![1.into()], + [1.into()], )) .one(&db) .await?; @@ -26,7 +26,7 @@ pub struct UniqueCake { let unique: Vec = UniqueCake::find_by_statement(Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name"#, - vec![], + [], )) .all(&db) .await?; @@ -38,7 +38,7 @@ If you do not know what your model looks like beforehand, you can use `JsonValue let unique: Vec = JsonValue::find_by_statement(Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name"#, - vec![], + [], )) .all(&db) .await?; @@ -51,7 +51,7 @@ let mut cake_pages = cake::Entity::find() .from_raw_sql(Statement::from_sql_and_values( DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, - vec![1.into()], + [1.into()], )) .paginate(db, 50); @@ -71,7 +71,7 @@ assert_eq!( cake_filling::Entity::find_by_id((6, 8)) .build(DatabaseBackend::MySql) .to_string(), - vec![ + [ "SELECT `cake_filling`.`cake_id`, `cake_filling`.`filling_id` FROM `cake_filling`", "WHERE `cake_filling`.`cake_id` = 6 AND `cake_filling`.`filling_id` = 8", ].join(" ") @@ -113,3 +113,12 @@ let exec_res: ExecResult = db .await?; assert_eq!(exec_res.rows_affected(), 1); ``` + +## Execute Unprepared SQL Statement + +You can execute an unprepared SQL statement with [`ConnectionTrait::execute_unprepared`](https://docs.rs/sea-orm/*/sea_orm/trait.ConnectionTrait.html#tymethod.execute_unprepared). + +```rust +let exec_res: ExecResult = + db.execute_unprepared("CREATE EXTENSION IF NOT EXISTS citext").await?; +``` diff --git a/SeaORM/docs/07-write-test/02-mock.md b/SeaORM/docs/07-write-test/02-mock.md index 35126723851..4f07bcbdf60 100644 --- a/SeaORM/docs/07-write-test/02-mock.md +++ b/SeaORM/docs/07-write-test/02-mock.md @@ -28,7 +28,7 @@ mod tests { async fn test_find_cake() -> Result<(), DbErr> { // Create MockDatabase with mock query results let db = MockDatabase::new(DatabaseBackend::Postgres) - .append_query_results(vec![ + .append_query_results([ // First query result vec![cake::Model { id: 1, @@ -46,9 +46,9 @@ mod tests { }, ], ]) - .append_query_results(vec![ + .append_query_results([ // Third query result - vec![( + [( cake::Model { id: 1, name: "Apple Cake".to_owned(), @@ -76,7 +76,7 @@ mod tests { // Return the second query result assert_eq!( cake::Entity::find().all(&db).await?, - vec![ + [ cake::Model { id: 1, name: "New York Cheese".to_owned(), @@ -94,7 +94,7 @@ mod tests { .find_also_related(fruit::Entity) .all(&db) .await?, - vec![( + [( cake::Model { id: 1, name: "Apple Cake".to_owned(), @@ -110,21 +110,21 @@ mod tests { // Checking transaction log assert_eq!( db.into_transaction_log(), - vec![ + [ Transaction::from_sql_and_values( DatabaseBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#, - vec![1u64.into()] + [1u64.into()] ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, - vec![] + [] ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name", "fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id" FROM "cake" LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#, - vec![] + [] ), ] ); @@ -150,17 +150,17 @@ mod tests { async fn test_insert_cake() -> Result<(), DbErr> { // Create MockDatabase with mock execution result let db = MockDatabase::new(DatabaseBackend::Postgres) - .append_query_results(vec![ - vec![cake::Model { + .append_query_results([ + [cake::Model { id: 15, name: "Apple Pie".to_owned(), }], - vec![cake::Model { + [cake::Model { id: 16, name: "Apple Pie".to_owned(), }], ]) - .append_exec_results(vec![ + .append_exec_results([ MockExecResult { last_insert_id: 15, rows_affected: 1, @@ -194,16 +194,16 @@ mod tests { // Checking transaction log assert_eq!( db.into_transaction_log(), - vec![ + [ Transaction::from_sql_and_values( DatabaseBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id", "name""#, - vec!["Apple Pie".into()] + ["Apple Pie".into()] ), Transaction::from_sql_and_values( DatabaseBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#, - vec!["Apple Pie".into()] + ["Apple Pie".into()] ), ] ); diff --git a/SeaORM/docs/08-advanced-query/01-custom-select.md b/SeaORM/docs/08-advanced-query/01-custom-select.md index 8e6b3e6960e..e320f4a0c64 100644 --- a/SeaORM/docs/08-advanced-query/01-custom-select.md +++ b/SeaORM/docs/08-advanced-query/01-custom-select.md @@ -2,10 +2,6 @@ By default, SeaORM will select all columns defined in the `Column` enum. You can override the defaults if you wish to select certain columns only. -## Clear Default Selection - -Clear the default selection by calling the `select_only` method if needed. Then, you can select some of the attributes or even custom expressions after it. - ```rust // Selecting all columns assert_eq!( @@ -16,9 +12,9 @@ assert_eq!( ); ``` -## Select Some Attributes Only +## Select Partial Attributes -Use `select_only` and `column` methods together to select only the attributes you want. +Clear the default selection by calling the `select_only` method. Then, you can select some of the attributes or custom expressions afterwards. ```rust // Selecting the name column only @@ -32,6 +28,35 @@ assert_eq!( ); ``` +If you want to select multiple attributes at once, you can supply an array. + +```rust +assert_eq!( + cake::Entity::find() + .select_only() + .columns([cake::Column::Id, cake::Column::Name]) + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."id", "cake"."name" FROM "cake""# +); +``` + +Advanced example: conditionally select all columns except a specific column. + +```rust +assert_eq!( + cake::Entity::find() + .select_only() + .columns(cake::Column::iter().filter(|col| match col { + cake::Column::Id => false, + _ => true, + })) + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."name" FROM "cake""# +); +``` + ## Select Custom Expressions Select any custom expression with `column_as` method, it takes any [`sea_query::SimpleExpr`](https://docs.rs/sea-query/*/sea_query/expr/enum.SimpleExpr.html) and an alias. Use [`sea_query::Expr`](https://docs.rs/sea-query/*/sea_query/expr/struct.Expr.html) helper to build `SimpleExpr`. @@ -49,7 +74,9 @@ assert_eq!( ); ``` -## Handling Custom Selects +## Handling Select Results + +### Custom Struct You can use a custom `struct` derived from the `FromQueryResult` trait to handle the result of a complex query. It is especially useful when dealing with custom columns or multiple joins which cannot directly be converted into models. It may be used to receive the result of any query, even raw SQL. @@ -82,48 +109,19 @@ let cake_counts: Vec = cake::Entity::find() .await?; ``` -Selecting a single value without a custom `struct` is also possible. - -```rust -use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter}; - -#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] -enum QueryAs { - CakeName, -} - -let res: Vec = cake::Entity::find() - .select_only() - .column_as(cake::Column::Name, QueryAs::CakeName) - .into_values::<_, QueryAs>() - .all(&db) - .await?; - -assert_eq!( - res, - vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned()] -); -``` +### Unstructured Tuple -You can even select a tuple value. +You can select a tuple (or single value) with the `into_tuple` method. ```rust use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter}; -#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] -enum QueryAs { - CakeName, - NumOfCakes, -} - let res: Vec<(String, i64)> = cake::Entity::find() .select_only() - .column_as(cake::Column::Name, QueryAs::CakeName) - .column_as(cake::Column::Id.count(), QueryAs::NumOfCakes) + .column(cake::Column::Name) + .column(cake::Column::Id.count()) .group_by(cake::Column::Name) - .into_values::<_, QueryAs>() + .into_tuple() .all(&db) .await?; - -assert_eq!(res, vec![("Chocolate Forest".to_owned(), 2i64)]); ``` diff --git a/SeaORM/docs/08-advanced-query/02-conditional-expression.md b/SeaORM/docs/08-advanced-query/02-conditional-expression.md index 06f61fc30c7..11bedc8eadb 100644 --- a/SeaORM/docs/08-advanced-query/02-conditional-expression.md +++ b/SeaORM/docs/08-advanced-query/02-conditional-expression.md @@ -73,3 +73,23 @@ assert_eq!( ].join(" ") ); ``` + +## Fluent conditional query + +Apply an operation on the QueryStatement if the given `Option` is `Some(_)`. It keeps your query expression fluent! + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend}; + +assert_eq!( + cake::Entity::find() + .apply_if(Some(3), |mut query, v| { + query.filter(cake::Column::Id.eq(v)) + }) + .apply_if(Some(100), QuerySelect::limit) + .apply_if(None, QuerySelect::offset::>) // no-op + .build(DbBackend::Postgres) + .to_string(), + r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "cake"."id" = 3 LIMIT 100"# +); +``` diff --git a/SeaORM/docs/09-generate-sea-query-statement/01-create-table.md b/SeaORM/docs/09-generate-sea-query-statement/01-create-table.md index c727013a236..a4d21639593 100644 --- a/SeaORM/docs/09-generate-sea-query-statement/01-create-table.md +++ b/SeaORM/docs/09-generate-sea-query-statement/01-create-table.md @@ -67,7 +67,7 @@ To further illustrate it, we will show the SQL statement as string below. db_postgres.build(&schema.create_table_from_entity(CakeFillingPrice)), Statement::from_string( db_postgres, - vec![ + [ r#"CREATE TABLE "public"."cake_filling_price" ("#, r#""cake_id" integer NOT NULL,"#, r#""filling_id" integer NOT NULL,"#, @@ -92,7 +92,7 @@ To further illustrate it, we will show the SQL statement as string below. db_mysql.build(&schema.create_table_from_entity(CakeFillingPrice)), Statement::from_string( db_mysql, - vec![ + [ "CREATE TABLE `cake_filling_price` (", "`cake_id` int NOT NULL,", "`filling_id` int NOT NULL,", @@ -117,7 +117,7 @@ To further illustrate it, we will show the SQL statement as string below. db_sqlite.build(&schema.create_table_from_entity(CakeFillingPrice)), Statement::from_string( db_sqlite, - vec![ + [ "CREATE TABLE `cake_filling_price` (", "`cake_id` integer NOT NULL,", "`filling_id` integer NOT NULL,", diff --git a/SeaORM/docs/09-generate-sea-query-statement/02-create-enum.md b/SeaORM/docs/09-generate-sea-query-statement/02-create-enum.md index f8958f739c8..f648da14798 100644 --- a/SeaORM/docs/09-generate-sea-query-statement/02-create-enum.md +++ b/SeaORM/docs/09-generate-sea-query-statement/02-create-enum.md @@ -111,7 +111,7 @@ assert_eq!( .iter() .map(|stmt| db_postgres.build(stmt)) .collect::>(), - vec![Statement::from_string( + [Statement::from_string( db_postgres, r#"CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea')"#.to_owned() ),] @@ -129,7 +129,7 @@ assert_eq!( db_postgres.build(&schema.create_table_from_entity(active_enum::Entity)), Statement::from_string( db_postgres, - vec![ + [ r#"CREATE TABLE "public"."active_enum" ("#, r#""id" serial NOT NULL PRIMARY KEY,"#, r#""tea" tea"#, @@ -154,7 +154,7 @@ assert_eq!( db_mysql.build(&schema.create_table_from_entity(active_enum::Entity)), Statement::from_string( db_mysql, - vec![ + [ "CREATE TABLE `active_enum` (", "`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,", "`tea` ENUM('EverydayTea', 'BreakfastTea')", @@ -179,7 +179,7 @@ assert_eq!( db_sqlite.build(&schema.create_enum_from_entity(active_enum::Entity)), Statement::from_string( db_sqlite, - vec![ + [ "CREATE TABLE `active_enum` (", "`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT,", "`tea` text",