From cf1a4d732c9d7001de84f2037f6d4a8922134a7c Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Tue, 24 Jan 2023 14:09:08 +0800 Subject: [PATCH 01/24] Update 02-writing-migration.md --- SeaORM/docs/03-migration/02-writing-migration.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SeaORM/docs/03-migration/02-writing-migration.md b/SeaORM/docs/03-migration/02-writing-migration.md index 94297cbb32f..00f6c506a24 100644 --- a/SeaORM/docs/03-migration/02-writing-migration.md +++ b/SeaORM/docs/03-migration/02-writing-migration.md @@ -7,11 +7,10 @@ Each migration contains two methods: `up` and `down`. The `up` method is used to Generate a new migration file by executing `sea-orm-cli migrate generate` command. ```shell -sea-orm-cli migrate generate NAME_OF_MIGRATION [--universal-time] +sea-orm-cli migrate generate NAME_OF_MIGRATION # E.g. to generate `migration/src/m20220101_000001_create_table.rs` shown below sea-orm-cli migrate generate create_table -sea-orm-cli migrate generate create_table -u # generates filename in Utc time (recommended) ``` Or you can create a migration file using the template below. Name the file according to the naming convention `mYYYYMMDD_HHMMSS_migration_name.rs`. From 0098ce648015f3af3f60723a802d91722d375c8f Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Sun, 29 Jan 2023 10:57:35 +0800 Subject: [PATCH 02/24] Update SeaORM/docs/03-migration/02-writing-migration.md --- SeaORM/docs/03-migration/02-writing-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeaORM/docs/03-migration/02-writing-migration.md b/SeaORM/docs/03-migration/02-writing-migration.md index 00f6c506a24..c3c58ce7dea 100644 --- a/SeaORM/docs/03-migration/02-writing-migration.md +++ b/SeaORM/docs/03-migration/02-writing-migration.md @@ -7,7 +7,7 @@ Each migration contains two methods: `up` and `down`. The `up` method is used to Generate a new migration file by executing `sea-orm-cli migrate generate` command. ```shell -sea-orm-cli migrate generate NAME_OF_MIGRATION +sea-orm-cli migrate generate NAME_OF_MIGRATION [--local-time] # E.g. to generate `migration/src/m20220101_000001_create_table.rs` shown below sea-orm-cli migrate generate create_table From 5f05e8431495aa910d0dab3d914557c86b733519 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Feb 2023 17:28:31 +0800 Subject: [PATCH 03/24] Support various UUID formats that are available in `uuid::fmt` module (SeaQL/sea-orm#1325) --- SeaORM/docs/04-generate-entity/02-entity-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeaORM/docs/04-generate-entity/02-entity-structure.md b/SeaORM/docs/04-generate-entity/02-entity-structure.md index 928a2d22a3e..798a3edfebc 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 | From 8c05ab7ae5d50bac87853cfc129cd6750c159ac3 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Feb 2023 18:47:28 +0800 Subject: [PATCH 04/24] Casting columns as a different data type on select, insert and update (SeaQL/sea-orm#1304) --- .../04-generate-entity/02-entity-structure.md | 12 ++++++++++ .../03-expanded-entity-structure.md | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/SeaORM/docs/04-generate-entity/02-entity-structure.md b/SeaORM/docs/04-generate-entity/02-entity-structure.md index 798a3edfebc..460f4cfd0bb 100644 --- a/SeaORM/docs/04-generate-entity/02-entity-structure.md +++ b/SeaORM/docs/04-generate-entity/02-entity-structure.md @@ -162,6 +162,18 @@ 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..3623a2135fa 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. From ca2a9b69e12fd7b0caa47515b73113e9246f6ca2 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Feb 2023 20:35:47 +0800 Subject: [PATCH 05/24] Methods of `ActiveModelBehavior` receive db connection as a parameter (SeaQL/sea-orm#1145, SeaQL/sea-orm#1328) --- .../03-expanded-entity-structure.md | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) 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 3623a2135fa..5eb7401a7a2 100644 --- a/SeaORM/docs/04-generate-entity/03-expanded-entity-structure.md +++ b/SeaORM/docs/04-generate-entity/03-expanded-entity-structure.md @@ -177,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 { @@ -187,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: {}", @@ -199,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) } } From f9026a1c115a53d650425fd54c7a774faa4155c0 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Feb 2023 21:27:47 +0800 Subject: [PATCH 06/24] Added `execute_unprepared` method to `DatabaseConnection` and `DatabaseTransaction` (SeaQL/sea-orm#1327) --- .../docs/03-migration/02-writing-migration.md | 36 +++++++++++++------ SeaORM/docs/05-basic-crud/08-raw-sql.md | 9 +++++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/SeaORM/docs/03-migration/02-writing-migration.md b/SeaORM/docs/03-migration/02-writing-migration.md index c3c58ce7dea..1f8ff74c373 100644 --- a/SeaORM/docs/03-migration/02-writing-migration.md +++ b/SeaORM/docs/03-migration/02-writing-migration.md @@ -215,19 +215,35 @@ pub struct Migration; #[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(()) } } ``` diff --git a/SeaORM/docs/05-basic-crud/08-raw-sql.md b/SeaORM/docs/05-basic-crud/08-raw-sql.md index 72ce686b2a7..28be5eafafe 100644 --- a/SeaORM/docs/05-basic-crud/08-raw-sql.md +++ b/SeaORM/docs/05-basic-crud/08-raw-sql.md @@ -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()` method. + +```rust +let exec_res: ExecResult = + db.execute_unprepared("CREATE EXTENSION IF NOT EXISTS citext").await?; +``` From 55db732c38eea3ddf439462745ca46da65943315 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 2 Feb 2023 22:52:06 +0800 Subject: [PATCH 07/24] Added `Select::into_tuple` to select rows as tuples (instead of defining a custom Model) (SeaQL/sea-orm#1311) --- .../docs/08-advanced-query/01-custom-select.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/SeaORM/docs/08-advanced-query/01-custom-select.md b/SeaORM/docs/08-advanced-query/01-custom-select.md index 8e6b3e6960e..daa6c8340b2 100644 --- a/SeaORM/docs/08-advanced-query/01-custom-select.md +++ b/SeaORM/docs/08-advanced-query/01-custom-select.md @@ -105,7 +105,9 @@ assert_eq!( ); ``` -You can even select a tuple value. +### Select a Tuple Value + +You can even select a tuple value with the `into_values` method but you need to supply an enum type. ```rust use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter}; @@ -127,3 +129,16 @@ let res: Vec<(String, i64)> = cake::Entity::find() assert_eq!(res, vec![("Chocolate Forest".to_owned(), 2i64)]); ``` + +Instead, the `into_tuple` method can be used to fetch the tuple value. + +```rust +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) + .group_by(cake::Column::Name) + .into_tuple() + .all(&db) + .await?; +``` From 8ae59962dbf56824d41dce92d17feaa598b4d354 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 11:33:47 +0800 Subject: [PATCH 08/24] Generate `#[serde(skip)]` for hidden columns (SeaQL/sea-orm#1171, SeaQL/sea-orm#1320) --- SeaORM/docs/04-generate-entity/01-sea-orm-cli.md | 1 + 1 file changed, 1 insertion(+) 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..9bb0e4c45c9 100644 --- a/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md +++ b/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md @@ -51,6 +51,7 @@ 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)]` - `--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`) From 0f0b9612d5621609576d33b2e8578df9e6ed525f Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 11:34:16 +0800 Subject: [PATCH 09/24] Generate entity with extra derives and attributes for model struct (SeaQL/sea-orm#1124, SeaQL/sea-orm#1321) --- SeaORM/docs/04-generate-entity/01-sea-orm-cli.md | 1 + 1 file changed, 1 insertion(+) 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 9bb0e4c45c9..03bbe4213a6 100644 --- a/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md +++ b/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md @@ -52,6 +52,7 @@ Command line options: - `--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`) From ab3f516e1e7c7c1119b6e7fb5b63bb6e71626e24 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 11:45:43 +0800 Subject: [PATCH 10/24] Generate entity with extra derives and attributes for model struct (SeaQL/sea-orm#1124, SeaQL/sea-orm#1321) --- SeaORM/docs/04-generate-entity/01-sea-orm-cli.md | 2 ++ 1 file changed, 2 insertions(+) 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 03bbe4213a6..d730a3f4fb0 100644 --- a/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md +++ b/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md @@ -55,6 +55,8 @@ Command line options: - `--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` From 77c8cdf30d7bfed78b3fdfdca99bcb5e103b7797 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 14:19:25 +0800 Subject: [PATCH 11/24] async_trait --- SeaORM/docs/03-migration/01-setting-up-migration.md | 2 +- SeaORM/docs/03-migration/02-writing-migration.md | 6 +++--- SeaORM/docs/03-migration/04-seeding-data.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) 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 1f8ff74c373..36168707dc0 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,7 +212,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> { let db = manager.get_connection(); diff --git a/SeaORM/docs/03-migration/04-seeding-data.md b/SeaORM/docs/03-migration/04-seeding-data.md index a1e14a3e27a..200206f979f 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() From e3211054642545e4138b4c95258fd78f5d093be5 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 15:02:27 +0800 Subject: [PATCH 12/24] Migrations are now performed inside a transaction for Postgres (SeaQL/sea-orm#1379) --- .../docs/03-migration/02-writing-migration.md | 6 ++++ SeaORM/docs/03-migration/04-seeding-data.md | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/SeaORM/docs/03-migration/02-writing-migration.md b/SeaORM/docs/03-migration/02-writing-migration.md index 36168707dc0..a50a0525bec 100644 --- a/SeaORM/docs/03-migration/02-writing-migration.md +++ b/SeaORM/docs/03-migration/02-writing-migration.md @@ -248,6 +248,12 @@ impl MigrationTrait for Migration { } ``` +## 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 200206f979f..fd43b4ba698 100644 --- a/SeaORM/docs/03-migration/04-seeding-data.md +++ b/SeaORM/docs/03-migration/04-seeding-data.md @@ -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(()) + } +} +``` From 707e6874d7e1ba5f2c75ffbf41a573cf0ffd5848 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 15:07:38 +0800 Subject: [PATCH 13/24] `MockDatabase::append_exec_results()`, `MockDatabase::append_query_results()`, `MockDatabase::append_exec_errors()` and `MockDatabase::append_query_errors()` take any types implemented `IntoIterator` trait (SeaQL/sea-orm#1367) --- SeaORM/docs/07-write-test/02-mock.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/SeaORM/docs/07-write-test/02-mock.md b/SeaORM/docs/07-write-test/02-mock.md index 35126723851..c1a6cb13c1f 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(), @@ -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, From 5375c34de6c820ef7c75d8fb7688e43948ccbfa6 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 15:11:59 +0800 Subject: [PATCH 14/24] Cleanup the use of `vec!` macros --- SeaORM/docs/05-basic-crud/03-insert.md | 2 +- SeaORM/docs/05-basic-crud/07-json.md | 2 +- SeaORM/docs/05-basic-crud/08-raw-sql.md | 10 +++++----- SeaORM/docs/07-write-test/02-mock.md | 18 +++++++++--------- .../docs/08-advanced-query/01-custom-select.md | 4 ++-- .../01-create-table.md | 6 +++--- .../02-create-enum.md | 8 ++++---- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/SeaORM/docs/05-basic-crud/03-insert.md b/SeaORM/docs/05-basic-crud/03-insert.md index 9ed11418dc3..f370757aeee 100644 --- a/SeaORM/docs/05-basic-crud/03-insert.md +++ b/SeaORM/docs/05-basic-crud/03-insert.md @@ -177,7 +177,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/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 28be5eafafe..c99c31f5e95 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(" ") diff --git a/SeaORM/docs/07-write-test/02-mock.md b/SeaORM/docs/07-write-test/02-mock.md index c1a6cb13c1f..4f07bcbdf60 100644 --- a/SeaORM/docs/07-write-test/02-mock.md +++ b/SeaORM/docs/07-write-test/02-mock.md @@ -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![] + [] ), ] ); @@ -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 daa6c8340b2..15ff2db8449 100644 --- a/SeaORM/docs/08-advanced-query/01-custom-select.md +++ b/SeaORM/docs/08-advanced-query/01-custom-select.md @@ -101,7 +101,7 @@ let res: Vec = cake::Entity::find() assert_eq!( res, - vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned()] + ["Chocolate Forest".to_owned(), "New York Cheese".to_owned()] ); ``` @@ -127,7 +127,7 @@ let res: Vec<(String, i64)> = cake::Entity::find() .all(&db) .await?; -assert_eq!(res, vec![("Chocolate Forest".to_owned(), 2i64)]); +assert_eq!(res, [("Chocolate Forest".to_owned(), 2i64)]); ``` Instead, the `into_tuple` method can be used to fetch the tuple value. 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", From 4370d448a89fc5ae74b592abd317ba4589677822 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 15:15:15 +0800 Subject: [PATCH 15/24] Added `DatabaseConnection::close` (SeaQL/sea-orm#1236) --- SeaORM/docs/02-install-and-config/02-connection.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/SeaORM/docs/02-install-and-config/02-connection.md b/SeaORM/docs/02-install-and-config/02-connection.md index 4a212cd9770..0b496b492bb 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 + +To close the connection explicitly, call the `close` method. + +```rust +let db = Database::connect(url).await?; + +// Closing connection here +db.close().await?; +``` From 4604cefc62a94704903fe2825eb93411e4968246 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 16:19:27 +0800 Subject: [PATCH 16/24] Added `ActiveValue::reset` to convert `Unchanged` into `Set` (SeaQL/sea-orm#1177) --- SeaORM/docs/05-basic-crud/03-insert.md | 14 ++++++++++++++ SeaORM/docs/05-basic-crud/04-update.md | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/SeaORM/docs/05-basic-crud/03-insert.md b/SeaORM/docs/05-basic-crud/03-insert.md index f370757aeee..6e2ae9d7fad 100644 --- a/SeaORM/docs/05-basic-crud/03-insert.md +++ b/SeaORM/docs/05-basic-crud/03-insert.md @@ -16,6 +16,20 @@ let _: ActiveValue = Set(10); let _: ActiveValue = NotSet; ``` +### Reset ActiveValue + +Calling `reset` method will convert `Unchanged` active value as `Set`. + +```rust +use sea_orm::ActiveValue; + +// An `Unchanged` value +let active_value: ActiveValue = ActiveValue::Unchanged(10); + +// Convert `Unchanged` active value as `Set` +assert!(active_value.reset(), ActiveValue::Set(10)); +``` + ## Model & ActiveModel An `ActiveModel` has all the attributes of `Model` wrapped in `ActiveValue`. diff --git a/SeaORM/docs/05-basic-crud/04-update.md b/SeaORM/docs/05-basic-crud/04-update.md index 5747fe766b1..797dec830a2 100644 --- a/SeaORM/docs/05-basic-crud/04-update.md +++ b/SeaORM/docs/05-basic-crud/04-update.md @@ -14,6 +14,30 @@ let mut pear: fruit::ActiveModel = pear.unwrap().into(); 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?; +``` + +### Update All Fields + +You can make `Unchanged` fields "dirty". + +```rust +let pear: Option = Fruit::find_by_id(28).one(db).await?; + +// Into ActiveModel +let mut pear: fruit::ActiveModel = pear.unwrap().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(); + +// Update corresponding row in database using primary key value +// SQL: `UPDATE "fruit" SET "name" = 'Sweet pear', "cake_id" = 10 WHERE "id" = 28` let pear: fruit::Model = pear.update(db).await?; ``` From 566454975feb67dc785ce416d3dc6e52943fbf3e Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 16:31:38 +0800 Subject: [PATCH 17/24] Added `QueryTrait::apply_if` to optionally apply a filter (SeaQL/sea-orm#1415) --- .../08-advanced-query/01-custom-select.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/SeaORM/docs/08-advanced-query/01-custom-select.md b/SeaORM/docs/08-advanced-query/01-custom-select.md index 15ff2db8449..409ea7f18dc 100644 --- a/SeaORM/docs/08-advanced-query/01-custom-select.md +++ b/SeaORM/docs/08-advanced-query/01-custom-select.md @@ -142,3 +142,23 @@ let res: Vec<(String, i64)> = cake::Entity::find() .all(&db) .await?; ``` + +## Appling an Operation if Value Exists + +Apply an operation on the [QueryTrait::QueryStatement](https://docs.rs/sea-orm/*/sea_orm/query/trait.QueryTrait.html#associatedtype.QueryStatement) if the given `Option` is `Some(_)`. + +```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"# +); +``` From 5227b620363f9e8a8e9db344962b089b16a9cd0d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 17:42:08 +0800 Subject: [PATCH 18/24] Added the `sea-orm-internal` feature flag to expose some SQLx types (SeaQL/sea-orm#1297, SeaQL/sea-orm#1434) --- .../01-database-and-async-runtime.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) 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..f4d66e7646e 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,16 @@ 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 + + `DatabaseConnection::get_*_connection_pool()` for accessing the inner SQLx connection pool + + Re-exporting SQLx errors From 528a8cc17077ba956476b5717556c06eb4788fdd Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 18:06:57 +0800 Subject: [PATCH 19/24] Add `QuerySelect::columns` method - select multiple columns (SeaQL/sea-orm#1264) --- .../08-advanced-query/01-custom-select.md | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/SeaORM/docs/08-advanced-query/01-custom-select.md b/SeaORM/docs/08-advanced-query/01-custom-select.md index 409ea7f18dc..27d88a0f119 100644 --- a/SeaORM/docs/08-advanced-query/01-custom-select.md +++ b/SeaORM/docs/08-advanced-query/01-custom-select.md @@ -18,7 +18,7 @@ assert_eq!( ## Select Some Attributes Only -Use `select_only` and `column` methods together to select only the attributes you want. +Use `select_only` and `column` methods together to select only the attribute you want. ```rust // Selecting the name column only @@ -32,6 +32,35 @@ assert_eq!( ); ``` +If you want to select multiple attributes at once. + +```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""# +); +``` + +Conditionally select all columns expect 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`. From 3b7b1cb8071f0e42e5dd4923542ab9499d56bac4 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 20:34:49 +0800 Subject: [PATCH 20/24] Update SeaORM/docs/02-install-and-config/02-connection.md Co-authored-by: Chris Tsang --- SeaORM/docs/02-install-and-config/02-connection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeaORM/docs/02-install-and-config/02-connection.md b/SeaORM/docs/02-install-and-config/02-connection.md index 0b496b492bb..e7c573bbe35 100644 --- a/SeaORM/docs/02-install-and-config/02-connection.md +++ b/SeaORM/docs/02-install-and-config/02-connection.md @@ -37,7 +37,7 @@ let db = Database::connect(opt).await?; ## Closing Connection -To close the connection explicitly, call the `close` method. +The connection will be automatically closed on drop. To close the connection explicitly, call the `close` method. ```rust let db = Database::connect(url).await?; From 71c369d88d48e0f4b58123b166167dd9448ff7a1 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 20:35:52 +0800 Subject: [PATCH 21/24] Update SeaORM/docs/05-basic-crud/03-insert.md Co-authored-by: Chris Tsang --- SeaORM/docs/05-basic-crud/03-insert.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SeaORM/docs/05-basic-crud/03-insert.md b/SeaORM/docs/05-basic-crud/03-insert.md index 6e2ae9d7fad..8a5915c398a 100644 --- a/SeaORM/docs/05-basic-crud/03-insert.md +++ b/SeaORM/docs/05-basic-crud/03-insert.md @@ -18,7 +18,7 @@ let _: ActiveValue = NotSet; ### Reset ActiveValue -Calling `reset` method will convert `Unchanged` active value as `Set`. +Calling `reset` method will convert `Unchanged` active value as `Set`, thus the attribute will be saved in the next operation. ```rust use sea_orm::ActiveValue; From eafc745fa024c7c4176b3b7a395b294f6a76cfb3 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Fri, 3 Feb 2023 20:41:28 +0800 Subject: [PATCH 22/24] fmt --- SeaORM/docs/04-generate-entity/02-entity-structure.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/SeaORM/docs/04-generate-entity/02-entity-structure.md b/SeaORM/docs/04-generate-entity/02-entity-structure.md index 460f4cfd0bb..7b1dde8edf5 100644 --- a/SeaORM/docs/04-generate-entity/02-entity-structure.md +++ b/SeaORM/docs/04-generate-entity/02-entity-structure.md @@ -167,10 +167,7 @@ pub ignore_me: String 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", -)] +#[sea_orm(select_as = "text", save_as = "citext")] pub case_insensitive_text: String ``` From c3d160af7a07e30eda186dcafe3e734f445e60c3 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Feb 2023 18:44:51 +0800 Subject: [PATCH 23/24] Edit --- SeaORM/docs/05-basic-crud/03-insert.md | 14 +-- SeaORM/docs/05-basic-crud/04-update.md | 10 +-- SeaORM/docs/05-basic-crud/08-raw-sql.md | 2 +- .../08-advanced-query/01-custom-select.md | 88 +++---------------- .../02-conditional-expression.md | 20 +++++ 5 files changed, 37 insertions(+), 97 deletions(-) diff --git a/SeaORM/docs/05-basic-crud/03-insert.md b/SeaORM/docs/05-basic-crud/03-insert.md index 8a5915c398a..3c27626060d 100644 --- a/SeaORM/docs/05-basic-crud/03-insert.md +++ b/SeaORM/docs/05-basic-crud/03-insert.md @@ -7,27 +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; -``` - -### Reset ActiveValue - -Calling `reset` method will convert `Unchanged` active value as `Set`, thus the attribute will be saved in the next operation. - -```rust -use sea_orm::ActiveValue; // An `Unchanged` value -let active_value: ActiveValue = ActiveValue::Unchanged(10); +let v: ActiveValue = Unchanged(10); // Convert `Unchanged` active value as `Set` -assert!(active_value.reset(), ActiveValue::Set(10)); +assert!(v.reset(), Set(10)); ``` ## Model & ActiveModel diff --git a/SeaORM/docs/05-basic-crud/04-update.md b/SeaORM/docs/05-basic-crud/04-update.md index 797dec830a2..31e89662edb 100644 --- a/SeaORM/docs/05-basic-crud/04-update.md +++ b/SeaORM/docs/05-basic-crud/04-update.md @@ -13,20 +13,15 @@ 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?; ``` -### Update All Fields - -You can make `Unchanged` fields "dirty". +To update all attributes, you can convert `Unchanged` into `Set`. ```rust -let pear: Option = Fruit::find_by_id(28).one(db).await?; - // Into ActiveModel -let mut pear: fruit::ActiveModel = pear.unwrap().into(); +let mut pear: fruit::ActiveModel = pear.into(); // Update name attribute pear.name = Set("Sweet pear".to_owned()); @@ -36,7 +31,6 @@ pear.reset(fruit::Column::CakeId); // Or, set all attributes as "dirty" (force update) pear.reset_all(); -// Update corresponding row in database using primary key value // 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/08-raw-sql.md b/SeaORM/docs/05-basic-crud/08-raw-sql.md index c99c31f5e95..d07fa8c0963 100644 --- a/SeaORM/docs/05-basic-crud/08-raw-sql.md +++ b/SeaORM/docs/05-basic-crud/08-raw-sql.md @@ -116,7 +116,7 @@ assert_eq!(exec_res.rows_affected(), 1); ## Execute Unprepared SQL Statement -You can execute an unprepared SQL statement with `ConnectionTrait::execute_unprepared()` method. +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 = diff --git a/SeaORM/docs/08-advanced-query/01-custom-select.md b/SeaORM/docs/08-advanced-query/01-custom-select.md index 27d88a0f119..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 attribute 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,7 +28,7 @@ assert_eq!( ); ``` -If you want to select multiple attributes at once. +If you want to select multiple attributes at once, you can supply an array. ```rust assert_eq!( @@ -45,7 +41,7 @@ assert_eq!( ); ``` -Conditionally select all columns expect a specific column. +Advanced example: conditionally select all columns except a specific column. ```rust assert_eq!( @@ -78,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. @@ -111,83 +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, - ["Chocolate Forest".to_owned(), "New York Cheese".to_owned()] -); -``` - -### Select a Tuple Value +### Unstructured Tuple -You can even select a tuple value with the `into_values` method but you need to supply an enum type. +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) - .group_by(cake::Column::Name) - .into_values::<_, QueryAs>() - .all(&db) - .await?; - -assert_eq!(res, [("Chocolate Forest".to_owned(), 2i64)]); -``` - -Instead, the `into_tuple` method can be used to fetch the tuple value. - -```rust 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_tuple() .all(&db) .await?; ``` - -## Appling an Operation if Value Exists - -Apply an operation on the [QueryTrait::QueryStatement](https://docs.rs/sea-orm/*/sea_orm/query/trait.QueryTrait.html#associatedtype.QueryStatement) if the given `Option` is `Some(_)`. - -```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/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"# +); +``` From 5a2ea6955f6c8a5a4f2f6c694b9029542c2d2b6d Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 3 Feb 2023 18:50:23 +0800 Subject: [PATCH 24/24] Edit --- .../02-install-and-config/01-database-and-async-runtime.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 f4d66e7646e..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 @@ -51,6 +51,4 @@ Basically, they are in the form of `runtime-ASYNC_RUNTIME-TLS_LIB`: + `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 - + `DatabaseConnection::get_*_connection_pool()` for accessing the inner SQLx connection pool - + Re-exporting SQLx errors ++ `sea-orm-internal` - opt-in unstable internal APIs (for accessing re-export SQLx types)