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 2aea8c64b0..d1278fdf48 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 @@ -52,5 +52,5 @@ Basically, they are in the form of `runtime-ASYNC_RUNTIME-TLS_LIB`: + `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 ++ `postgres-array` - support array types in Postgres (automatically enabled when `sqlx-postgres` feature is turned on) + `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 e7c573bbe3..a69765b22a 100644 --- a/SeaORM/docs/02-install-and-config/02-connection.md +++ b/SeaORM/docs/02-install-and-config/02-connection.md @@ -21,7 +21,7 @@ Multiple queries will execute in parallel as you `await` on them. To configure the connection, use the [`ConnectOptions`](https://docs.rs/sea-orm/*/sea_orm/struct.ConnectOptions.html) interface: ```rust -let mut opt = ConnectOptions::new("protocol://username:password@host/database".to_owned()); +let mut opt = ConnectOptions::new("protocol://username:password@host/database"); opt.max_connections(100) .min_connections(5) .connect_timeout(Duration::from_secs(8)) @@ -30,11 +30,23 @@ opt.max_connections(100) .max_lifetime(Duration::from_secs(8)) .sqlx_logging(true) .sqlx_logging_level(log::LevelFilter::Info) - .set_schema_search_path("my_schema".into()); // Setting default PostgreSQL schema + .set_schema_search_path("my_schema"); // Setting default PostgreSQL schema let db = Database::connect(opt).await?; ``` +## Checking Connection is Valid + +Checks if a connection to the database is still valid. + +```rust +|db: DatabaseConnection| { + assert!(db.ping().await.is_ok()); + db.clone().close().await; + assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire))); +} +``` + ## Closing Connection The connection will be automatically closed on drop. To close the connection explicitly, call the `close` method. diff --git a/SeaORM/docs/03-migration/01-setting-up-migration.md b/SeaORM/docs/03-migration/01-setting-up-migration.md index 42f76c8c2f..324a76bd83 100644 --- a/SeaORM/docs/03-migration/01-setting-up-migration.md +++ b/SeaORM/docs/03-migration/01-setting-up-migration.md @@ -6,7 +6,26 @@ If you are starting from a fresh database, it's better to version control your d ## Migration Table -A table named `seaql_migrations` will be created in your database to keep track of the applied migrations. It will be created automatically when you run the migration. +A table will be created in your database to keep track of the applied migrations. It will be created automatically when you run the migration. + +By default, it will be named `seaql_migrations`. You can also use a custom name for your migration table. + +```rust +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20220118_000001_create_cake_table::Migration), + Box::new(m20220118_000002_create_fruit_table::Migration), + ] + } + + // Override the name of migration table + fn migration_table_name() -> sea_orm::DynIden { + Alias::new("override_migration_table_name").into_iden() + } +} +``` ## Creating Migration Directory @@ -45,6 +64,8 @@ migration └── main.rs # Migrator CLI, for running manually ``` +Note that if you setup the migration directory directly within a Git repository, a `.gitignore` file will also be created. + ## Workspace Structure It is recommended to structure your cargo workspace as follows to share SeaORM entities between the app crate and the migration crate. Checkout the [integration examples](https://github.com/SeaQL/sea-orm/tree/master/examples) for demonstration. diff --git a/SeaORM/docs/03-migration/02-writing-migration.md b/SeaORM/docs/03-migration/02-writing-migration.md index 79537e5ce6..717bfb863d 100644 --- a/SeaORM/docs/03-migration/02-writing-migration.md +++ b/SeaORM/docs/03-migration/02-writing-migration.md @@ -6,11 +6,16 @@ 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. +If you name the file with spaces, it will be converted according to the convention automatically. + ```shell 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 + +# This create the same migration file as above command +sea-orm-cli migrate generate "create table" ``` Or you can create a migration file using the template below. Name the file according to the naming convention `mYYYYMMDD_HHMMSS_migration_name.rs`. diff --git a/SeaORM/docs/03-migration/03-running-migration.md b/SeaORM/docs/03-migration/03-running-migration.md index 7c621db32a..ee6822088d 100644 --- a/SeaORM/docs/03-migration/03-running-migration.md +++ b/SeaORM/docs/03-migration/03-running-migration.md @@ -84,11 +84,24 @@ For CLI, you can specify the target schema with `-s` / `--database_schema` optio You can also run the migration on the target schema programmatically: ```rust -let connect_options = ConnectOptions::new("postgres://root:root@localhost/database".into()) - .set_schema_search_path("my_schema".into()) // Override the default schema +let connect_options = ConnectOptions::new("postgres://root:root@localhost/database") + .set_schema_search_path("my_schema") // Override the default schema .to_owned(); let db = Database::connect(connect_options).await? migration::Migrator::up(&db, None).await?; ``` + +## Checking Migration Status + +You can use `Migration::name()` and `Migration::status()` to get the name and status of a `sea_orm_migration::Migration` + +```rust +let migrations = Migrator::get_pending_migrations(db).await?; +assert_eq!(migrations.len(), 5); + +let migration = migrations.get(0).unwrap(); +assert_eq!(migration.name(), "m20220118_000002_create_fruit_table"); +assert_eq!(migration.status(), MigrationStatus::Pending); +``` \ No newline at end of file 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 d730a3f4fb..6948988a66 100644 --- a/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md +++ b/SeaORM/docs/04-generate-entity/01-sea-orm-cli.md @@ -57,6 +57,7 @@ Command line options: - `--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 +- `--seaography`: an additional RelatedEntity enum for seaography integration will be generated. ```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 cf3d8ad8d8..4ec6b060fd 100644 --- a/SeaORM/docs/04-generate-entity/02-entity-structure.md +++ b/SeaORM/docs/04-generate-entity/02-entity-structure.md @@ -199,6 +199,8 @@ pub id: i32 This is usually the case in junction tables, where a two-column tuple is used as the primary key. Simply annotate multiple columns to define a composite primary key. By default, `auto_increment` is `false` for composite key. +You can define up to 12 primary key in a model. + ```rust pub struct Model { #[sea_orm(primary_key)] diff --git a/SeaORM/docs/05-basic-crud/03-insert.md b/SeaORM/docs/05-basic-crud/03-insert.md index ea3e425cd4..bf51fa7e15 100644 --- a/SeaORM/docs/05-basic-crud/03-insert.md +++ b/SeaORM/docs/05-basic-crud/03-insert.md @@ -187,6 +187,18 @@ let res: InsertResult = Fruit::insert_many([apple, orange]).exec(db).await?; assert_eq!(res.last_insert_id, 30) ``` +Supplying an empty iterator to `insert_many` method will yield an error. However, you can change the behaviour with `on_empty_do_nothing` method to handle inserting an empty iterator properly. + +```rust +// now, you can do: +let res = Bakery::insert_many(std::iter::empty()) + .on_empty_do_nothing() + .exec(db) + .await; + +assert!(matches!(res, Ok(TryInsertResult::Empty))); +``` + ## On Conflict Insert active model with on conflict behaviour. @@ -269,3 +281,17 @@ let res = Entity::insert_many([ assert_eq!(res.err(), Some(DbErr::RecordNotInserted)); ``` + +Or you can use `.do_nothing()` to handle insert with conflict properly. + +```rust +let on = OnConflict::column(Column::Id).do_nothing().to_owned(); + +// Existing behaviour +let res = Entity::insert_many([..]).on_conflict(on).exec(db).await; +assert!(matches!(res, Err(DbErr::RecordNotInserted))); + +// you can also: +let res = Entity::insert_many([..]).on_conflict(on).do_nothing().exec(db).await; +assert!(matches!(res, Ok(TryInsertResult::Conflicted))); +``` \ No newline at end of file diff --git a/SeaORM/docs/05-basic-crud/08-raw-sql.md b/SeaORM/docs/05-basic-crud/08-raw-sql.md index d07fa8c096..4663267d3d 100644 --- a/SeaORM/docs/05-basic-crud/08-raw-sql.md +++ b/SeaORM/docs/05-basic-crud/08-raw-sql.md @@ -88,7 +88,7 @@ You can build SQL statements using `sea-query` and query / execute it directly o let query_res: Option = db .query_one(Statement::from_string( DatabaseBackend::MySql, - "SELECT * FROM `cake`;".to_owned(), + "SELECT * FROM `cake`;", )) .await?; let query_res = query_res.unwrap(); @@ -97,7 +97,7 @@ let id: i32 = query_res.try_get("", "id")?; let query_res_vec: Vec = db .query_all(Statement::from_string( DatabaseBackend::MySql, - "SELECT * FROM `cake`;".to_owned(), + "SELECT * FROM `cake`;", )) .await?; ``` @@ -108,7 +108,7 @@ let query_res_vec: Vec = db let exec_res: ExecResult = db .execute(Statement::from_string( DatabaseBackend::MySql, - "DROP DATABASE IF EXISTS `sea`;".to_owned(), + "DROP DATABASE IF EXISTS `sea`;", )) .await?; assert_eq!(exec_res.rows_affected(), 1); diff --git a/SeaORM/docs/06-relation/03-many-to-many.md b/SeaORM/docs/06-relation/03-many-to-many.md index df95ae640e..ec3fbbcdd5 100644 --- a/SeaORM/docs/06-relation/03-many-to-many.md +++ b/SeaORM/docs/06-relation/03-many-to-many.md @@ -89,3 +89,44 @@ pub enum Relation { Filling, } ``` + +Note that the implementation of `Related` with `via` and `to` methods will not be generated if there exists multiple paths via an intermediate table. + +For example, in the schema defined below, there are two paths: +- Path 1. `users <-> users_votes <-> bills` +- Path 2. `users <-> users_saved_bills <-> bills` + +Therefore, the implementation of `Related` will not be generated + +```sql +CREATE TABLE users +( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + email TEXT UNIQUE NOT NULL, + ... +); +``` +```sql +CREATE TABLE bills +( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + ... +); +``` +```sql +CREATE TABLE users_votes +( + user_id uuid REFERENCES users (id) ON UPDATE CASCADE ON DELETE CASCADE, + bill_id uuid REFERENCES bills (id) ON UPDATE CASCADE ON DELETE CASCADE, + vote boolean NOT NULL, + CONSTRAINT users_bills_pkey PRIMARY KEY (user_id, bill_id) +); +``` +```sql +CREATE TABLE users_saved_bills +( + user_id uuid REFERENCES users (id) ON UPDATE CASCADE ON DELETE CASCADE, + bill_id uuid REFERENCES bills (id) ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT users_saved_bills_pkey PRIMARY KEY (user_id, bill_id) +); +``` \ No newline at end of file diff --git a/SeaORM/docs/06-relation/06-custom-join-condition.md b/SeaORM/docs/06-relation/06-custom-join-condition.md index e6f52a32d9..724231969c 100644 --- a/SeaORM/docs/06-relation/06-custom-join-condition.md +++ b/SeaORM/docs/06-relation/06-custom-join-condition.md @@ -18,6 +18,8 @@ It can be done via one of the following ways. Add your additional join conditions directly to the relation enum. The easiest way is to provide a `sea_query::SimpleExpr` via `on_condition` procedural macros attribute. +If you want to have a `OR` condition relation, you can use `condition_type = "any"` to alter the relation. + ```rust #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { @@ -29,6 +31,13 @@ pub enum Relation { on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# )] TropicalFruit, + // specify `condition_type = "any"` to override it + #[sea_orm( + has_many = "super::fruit::Entity", + on_condition = r#"super::fruit::Column::Name.like("%tropical%")"# + condition_type = "any", + )] + OrTropicalFruit, } ``` @@ -126,7 +135,7 @@ assert_eq!( ## Custom Join -Last but not least, custom join conditions can be defined at the point you construct the join expression. +Last but not least, custom join conditions can be defined at the point you construct the join expression. Overriding condition relation can also be done in custom join on the fly. ```rust assert_eq!( @@ -153,6 +162,19 @@ assert_eq!( .into_condition() }) ) + .join_as_rev( + JoinType::LeftJoin, + cake_filling::Relation::Cake + .def() + // chained AND / OR join on condition + .condition_type(ConditionType::Any) + .on_condition(|left, _right| { + Expr::col((left, cake_filling::Column::CakeId)) + .gt(10) + .into_condition() + }), + Alias::new("cake_filling_alias") + ) .join(JoinType::LeftJoin, filling::Relation::Vendor.def()) .build(DbBackend::MySql) .to_string(), @@ -161,6 +183,7 @@ assert_eq!( "LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` AND `fruit`.`name` LIKE '%tropical%'", "LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id` AND `cake_filling`.`cake_id` > 10", "LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id` AND `filling`.`name` LIKE '%lemon%'", + "LEFT JOIN `cake_filling` AS `cake_filling_alias` ON `cake_filling_alias`.`cake_id` = `cake`.`id` OR `cake_filling_alias`.`cake_id` > 10", "LEFT JOIN `vendor` ON `filling`.`vendor_id` = `vendor`.`id`", ] .join(" ") diff --git a/SeaORM/docs/08-advanced-query/01-custom-select.md b/SeaORM/docs/08-advanced-query/01-custom-select.md index e320f4a0c6..42b8f4873b 100644 --- a/SeaORM/docs/08-advanced-query/01-custom-select.md +++ b/SeaORM/docs/08-advanced-query/01-custom-select.md @@ -57,6 +57,33 @@ assert_eq!( ); ``` +### Optional field + +Since 0.12, SeaORM supports for partial select of `Option` model field. A `None` value will be filled when the select result does not contain the `Option` field without throwing an error. + +```rust +customer::ActiveModel { + name: Set("Alice".to_owned()), + notes: Set(Some("Want to communicate with Bob".to_owned())), + ..Default::default() +} +.save(db) +.await?; + +// The `notes` field was intentionally leaved out +let customer = Customer::find() + .select_only() + .column(customer::Column::Id) + .column(customer::Column::Name) + .one(db) + .await + .unwrap(); + +// The select result does not contain `notes` field. +// Since it's of type `Option`, it'll be `None` and no error will be thrown. +assert_eq!(customers.notes, None); +``` + ## 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`. @@ -74,6 +101,45 @@ assert_eq!( ); ``` +Alternatively, you can simply select with `expr`, `exprs` and `expr_as` methods. + +```rust +use sea_orm::sea_query::Expr; +use sea_orm::{entity::*, tests_cfg::cake, DbBackend, QuerySelect, QueryTrait}; + +assert_eq!( + cake::Entity::find() + .select_only() + .expr(Expr::col((cake::Entity, cake::Column::Id))) + .build(DbBackend::MySql) + .to_string(), + "SELECT `cake`.`id` FROM `cake`" +); + +assert_eq!( + cake::Entity::find() + .select_only() + .exprs([ + Expr::col((cake::Entity, cake::Column::Id)), + Expr::col((cake::Entity, cake::Column::Name)), + ]) + .build(DbBackend::MySql) + .to_string(), + "SELECT `cake`.`id`, `cake`.`name` FROM `cake`" +); + +assert_eq!( + cake::Entity::find() + .expr_as( + Func::upper(Expr::col((cake::Entity, cake::Column::Name))), + "name_upper" + ) + .build(DbBackend::MySql) + .to_string(), + "SELECT `cake`.`id`, `cake`.`name`, UPPER(`cake`.`name`) AS `name_upper` FROM `cake`" +); +``` + ## Handling Select Results ### Custom Struct diff --git a/SeaORM/docs/08-advanced-query/09-error-handling.md b/SeaORM/docs/08-advanced-query/09-error-handling.md index 57fe54c877..52fb72dad2 100644 --- a/SeaORM/docs/08-advanced-query/09-error-handling.md +++ b/SeaORM/docs/08-advanced-query/09-error-handling.md @@ -39,3 +39,28 @@ match error { _ => panic!("Unexpected Error kind"), } ``` + +## SQL related errors + +You can use `DbErr::sql_err()` method to convert SQL related error into common database errors `SqlErr`, such as unique constraint or foreign key violation errors. + +```rust +assert!(matches!( + cake + .into_active_model() + .insert(db) + .await + .expect_err("Insert a row with duplicated primary key") + .sql_err(), + Some(SqlErr::UniqueConstraintViolation(_)) +)); + +assert!(matches!( + fk_cake + .insert(db) + .await + .expect_err("Insert a row with invalid foreign key") + .sql_err(), + Some(SqlErr::ForeignKeyConstraintViolation(_)) +)); +``` \ No newline at end of file diff --git a/SeaORM/docs/09-schema-statement/02-create-enum.md b/SeaORM/docs/09-schema-statement/02-create-enum.md index a97ce8b94d..e87c2c7d32 100644 --- a/SeaORM/docs/09-schema-statement/02-create-enum.md +++ b/SeaORM/docs/09-schema-statement/02-create-enum.md @@ -64,6 +64,40 @@ assert_eq!( ); ``` +Note that non-UAX#31 compliant characters would be converted. See the following code snippet to understand how would it be converted. + +```rust +#[derive(Clone, Debug, PartialEq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(None)")] +pub enum StringValue { + #[sea_orm(string_value = "")] + Member1, + #[sea_orm(string_value = "$")] + Member2, + #[sea_orm(string_value = "$$")] + Member3, + #[sea_orm(string_value = "AB")] + Member4, + #[sea_orm(string_value = "A_B")] + Member5, + #[sea_orm(string_value = "A$B")] + Member6, + #[sea_orm(string_value = "0 123")] + Member7, +} + +// The following will be generated +pub enum StringValueVariant { + __Empty, + _0x24, + _0x240x24, + Ab, + A0x5Fb, + A0x24B, + _0x300x20123, +} +``` + ## Native Database Enum Enum support is different across databases. Let's go through them one-by-one. @@ -113,7 +147,7 @@ assert_eq!( .collect::>(), [Statement::from_string( db_postgres, - r#"CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea')"#.to_owned() + r#"CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea')"# ),] ); @@ -121,7 +155,7 @@ assert_eq!( db_postgres.build(&schema.create_enum_from_active_enum::()), Statement::from_string( db_postgres, - r#"CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea')"#.to_owned() + r#"CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea')"# ) ); diff --git a/SeaORM/docs/10-internal-design/02-derive-macro.md b/SeaORM/docs/10-internal-design/02-derive-macro.md index afda3a9b4e..ca96d14cec 100644 --- a/SeaORM/docs/10-internal-design/02-derive-macro.md +++ b/SeaORM/docs/10-internal-design/02-derive-macro.md @@ -8,6 +8,10 @@ The [`EntityModel`](#) derive macro is the 'almighty' macro which automatically The [`DeriveEntity`](#) derive macro will implement [`EntityTrait`](#) for `Entity` and it assumes `Model`, `Column`, `PrimaryKey` and `Relation` exist in the current scope. It also provides implementation of [`Iden`](#) and [`IdenStatic`](#) for `Entity`. +## RelatedEntity + +The [`DeriveRelatedEntity`](#) derive macro will implement [`seaography::RelationBuilder`](#) for `RelatedEntity` enumeration when the `seaography` feature is enabled + ## Column The [`DeriveColumn`](#) derive macro will implement [`ColumnTrait`](#) for `Columns`. It defines the identifier of each column by implementing [`Iden`](#) and [`IdenStatic`](#). The [`EnumIter`](#) is also derived, allowing iteration over all enum variants. @@ -24,6 +28,10 @@ The [`DeriveModel`](#) derive macro will implement [`ModelTrait`](#) for `Model` The [`DeriveActiveModel`](#) derive macro will implement [`ActiveModelTrait`](#) for `ActiveModel` which provides setters and getters for all active values in the active model. +## Partial Model + +The [`DerivePartialModel`](#) derive macro will implement [`PartialModelTrait`](#) for `Model`. + ## Active Enum The [`DeriveActiveEnum`](#) derive macro will implement [`ActiveEnum`](#) for any enums.