diff --git a/SeaORM/versioned_docs/version-1.1.x/01-index.md b/SeaORM/versioned_docs/version-1.1.x/01-index.md new file mode 100644 index 00000000000..2e9ae2d3400 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/01-index.md @@ -0,0 +1,141 @@ +# Index + +## Introduction + +1. Introduction + + 1.1. [What is an ORM](01-introduction/01-orm.md) + + 1.2. [Async Programming in Rust](01-introduction/02-async.md) + + 1.3. [SeaORM Concepts](01-introduction/03-sea-orm.md) + + 1.4. [Tutorial & Examples](01-introduction/04-tutorial.md) + +## Basics + +2. Installation & Configuration + + 2.1 [Choosing a Database & Async Runtime](02-install-and-config/01-database-and-async-runtime.md) + + 2.2 [Connection Pool](02-install-and-config/02-connection.md) + + 2.3 [Debug Log](02-install-and-config/03-debug-log.md) + +3. Migration + + 3.1 [Setting Up Migration](03-migration/01-setting-up-migration.md) + + 3.2 [Writing Migration](03-migration/02-writing-migration.md) + + 3.3 [Running Migration](03-migration/03-running-migration.md) + + 3.4 [Seeding Data](03-migration/04-seeding-data.md) + +4. Generating Entities + + 4.1 [Using `sea-orm-cli`](04-generate-entity/01-sea-orm-cli.md) + + 4.2 [Entity Structure](04-generate-entity/02-entity-structure.md) + + 4.3 [Expanded Entity Structure](04-generate-entity/03-expanded-entity-structure.md) + + 4.4 [Enumeration](04-generate-entity/04-enumeration.md) + + 4.5 [New Type](04-generate-entity/05-newtype.md) + +5. Basic CRUD + + 5.1 [Basic Schema](05-basic-crud/01-basic-schema.md) + + 5.2 [SELECT: find, filter, sort, paging](05-basic-crud/02-select.md) + + 5.3 [INSERT: Model & ActiveModel, insert many](05-basic-crud/03-insert.md) + + 5.4 [UPDATE: find & save, update many](05-basic-crud/04-update.md) + + 5.5 [SAVE: insert or update](05-basic-crud/05-save.md) + + 5.6 [DELETE: delete one & delete many](05-basic-crud/06-delete.md) + + 5.7 [JSON](05-basic-crud/07-json.md) + + 5.8 [Raw SQL query](05-basic-crud/08-raw-sql.md) + +## Advanced Topics + +6. Relations + + 6.1 [One to One](06-relation/01-one-to-one.md) + + 6.2 [One to Many](06-relation/02-one-to-many.md) + + 6.3 [Many to Many](06-relation/03-many-to-many.md) + + 6.4 [Chained Relations](06-relation/04-chained-relations.md) + + 6.5 [Self Referencing](06-relation/05-self-referencing.md) + + 6.6 [Custom Join Condition](06-relation/06-custom-join-condition.md) + + 6.7 [Data Loader](06-relation/07-data-loader.md) + + 6.8 [Bakery Schema](06-relation/08-bakery-schema.md) + +7. Writing Tests + + 7.1 [Robust & Correct](07-write-test/01-testing.md) + + 7.2 [Mock Interface](07-write-test/02-mock.md) + + 7.3 [Using SQLite](07-write-test/03-sqlite.md) + +8. Advanced Queries + + 8.1 [Custom select](08-advanced-query/01-custom-select.md) + + 8.2 [Conditional expressions](08-advanced-query/02-conditional-expression.md) + + 8.3 [Aggregate functions](08-advanced-query/03-aggregate-function.md) + + 8.4 [Custom Joins](08-advanced-query/04-custom-joins.md) + + 8.5 [Sub Query](08-advanced-query/05-subquery.md) + + 8.6 [Transaction](08-advanced-query/06-transaction.md) + + 8.7 [Streaming](08-advanced-query/07-streaming.md) + + 8.8 [Custom Active Model](08-advanced-query/08-custom-active-model.md) + + 8.9 [Error Handling](08-advanced-query/09-error-handling.md) + +9. Schema Statement + + 9.1 [Create Table](09-schema-statement/01-create-table.md) + + 9.2 [Create Enum](09-schema-statement/02-create-enum.md) + + 9.3 [Create Index](09-schema-statement/03-create-index.md) + +10. Seaography + + 10.1 [🧭 Seaography Intro](10-seaography/01-seaography-intro.md) + + 10.2 [Getting Started](10-seaography/02-getting-started.md) + + 10.3 [Looking Forward](10-seaography/03-looking-forward.md) + +11. Internal Design + + 11.1 [Traits and Types](11-internal-design/01-trait-and-type.md) + + 11.2 [Derive Macros](11-internal-design/02-derive-macro.md) + + 11.3 [Compare with Diesel](11-internal-design/03-diesel.md) + + 11.4 [Architecture](11-internal-design/04-architecture.md) + +12. What's Next + + 12.1 [What's Next](12-whats-next/01-whats-next.md) \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/01-introduction/01-orm.md b/SeaORM/versioned_docs/version-1.1.x/01-introduction/01-orm.md new file mode 100644 index 00000000000..de887d2a9e3 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/01-introduction/01-orm.md @@ -0,0 +1,13 @@ +# What is an ORM + +An Object Relational Mapper (ORM) is a programming library to help you interact with a relational database from an Object-Oriented Programming (OOP) language. + +Tables and columns in a database are mapped to objects and attributes, while additional methods allow you to load and store data from and to the database. + +Services built in Rust are lightweight (small binary size, low memory usage), safe (with compile-time guarantee), correct (if unit tests are well-designed), and fast (compile-time optimizations minimize runtime overhead). + +Due to Rust being a static, strongly typed, compiled, thread-safe, non-garbage-collected, and unconventional object-oriented language, working with an ORM in Rust is a bit different from other scripting languages you are already familiar with. + +SeaORM tries to help you in reaping the above benefits while avoiding the hiccups when programming in Rust. + +Let's get started. \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/01-introduction/02-async.md b/SeaORM/versioned_docs/version-1.1.x/01-introduction/02-async.md new file mode 100644 index 00000000000..4f8822d77b0 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/01-introduction/02-async.md @@ -0,0 +1,11 @@ +# Async Programming + +Async programming in Rust is a recent development, only having been stabilized in Rust [`1.39`](https://github.com/rust-lang/rust/releases/tag/1.39.0). The async ecosystem is rapidly evolving, and SeaORM is one of the first crates built from the ground up with async support in mind. + +The first thing to learn is the [`Future`](https://rust-lang.github.io/async-book/02_execution/02_future.html) trait. It's a placeholder for a function that will compute and return some value in the future. It's lazy, meaning `.await` must be called for any actual work to be done. `Future` allows us to achieve concurrency with little programming effort, e.g. [`future::join_all`](https://docs.rs/futures/latest/futures/future/fn.join_all.html) to execute multiple queries in parallel. + +Second, `async` in Rust is [multi-threaded programming](https://rust-lang.github.io/async-book/03_async_await/01_chapter.html) with syntactic sugar. A `Future` may move between threads, so any variables used in async bodies must be able to travel between threads, i.e. [`Send`](https://doc.rust-lang.org/nomicon/send-and-sync.html). + +Third, there are multiple async runtimes in Rust. [`async-std`](https://crates.io/crates/async-std) and [`tokio`](https://crates.io/crates/tokio) are the two most widely used. SeaORM's underlying driver, [`SQLx`](https://crates.io/crates/sqlx), supports both. + +Knowing these concepts is essential to get you up and running in async Rust. \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/01-introduction/03-sea-orm.md b/SeaORM/versioned_docs/version-1.1.x/01-introduction/03-sea-orm.md new file mode 100644 index 00000000000..c18cae3f9bd --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/01-introduction/03-sea-orm.md @@ -0,0 +1,15 @@ +# SeaORM Concepts + +In SeaORM, a database with a collection of tables is called a `Schema`. + +Each table corresponds to an [`Entity`](04-generate-entity/02-entity-structure.md#entity) in SeaORM, which helps you perform `CRUD` (Create, Read, Update, and Delete) operations on relevant tables. + +The `Entity` trait provides an API for you to inspect its properties ([`Column`](04-generate-entity/02-entity-structure.md#column), [`Relation`](04-generate-entity/02-entity-structure.md#relation) and [`PrimaryKey`](04-generate-entity/02-entity-structure.md#primary-key)) at runtime. + +Each table has multiple columns, which are referred to as `attribute`. + +These attributes and their values are grouped in a Rust struct (a [`Model`](04-generate-entity/03-expanded-entity-structure.md#model)) so that you can manipulate them. + +However, `Model` is for read operations only. To perform insert, update, or delete, you need to use [`ActiveModel`](04-generate-entity/03-expanded-entity-structure.md#active-model) which attaches meta-data on each attribute. + +Finally, there is no singleton (global context) in SeaORM. Application code is responsible for managing the ownership of the `DatabaseConnection`. We do provide integration examples for web frameworks including [Rocket](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example), [Actix](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example), [axum](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) and [poem](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) to help you get started quickly. \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/01-introduction/04-tutorial.md b/SeaORM/versioned_docs/version-1.1.x/01-introduction/04-tutorial.md new file mode 100644 index 00000000000..7252f11d59b --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/01-introduction/04-tutorial.md @@ -0,0 +1,19 @@ +# Tutorial & Examples + +If you prefer a step-by-step tutorial on how to use SeaORM, you can check out our official [SeaORM Tutorial](https://www.sea-ql.org/sea-orm-tutorial/). There are also some good tutorials written by [the community](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#learning-resources)! + +You can also check out [SeaORM Cookbook](https://www.sea-ql.org/sea-orm-cookbook/) for some frequently asked questions and recommended practices of using SeaORM. + +If you are so eager and want something grab-and-go, SeaQL maintains a set of official examples contributed by the community (we welcome more!): + ++ [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) ++ [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example) ++ [GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example) ++ [jsonrpsee Example](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example) ++ [Loco Example](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_example) ++ [Loco REST Starter Example](https://github.com/SeaQL/sea-orm/tree/master/examples/loco_starter) ++ [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example) ++ [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example) ++ [Salvo Example](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example) ++ [Tonic Example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example) ++ [Seaography Example](https://github.com/SeaQL/sea-orm/tree/master/examples/seaography_example) diff --git a/SeaORM/versioned_docs/version-1.1.x/01-introduction/_category_.json b/SeaORM/versioned_docs/version-1.1.x/01-introduction/_category_.json new file mode 100644 index 00000000000..16a97d6e429 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/01-introduction/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Introduction", + "collapsed": false +} diff --git a/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/01-database-and-async-runtime.md b/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/01-database-and-async-runtime.md new file mode 100644 index 00000000000..040c8918f70 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/01-database-and-async-runtime.md @@ -0,0 +1,63 @@ +# Database & Async Runtime + +:::caution We need your support! ⭐ +Thank you for using SeaORM. Please star our [GitHub repo](https://github.com/SeaQL/sea-orm)! Your support is vital to the continued development and maintenance of SeaORM. +::: + +First, add `sea-orm` to the `[dependencies]` section of `Cargo.toml`. + +```toml title="Cargo.toml" +sea-orm = { version = "1.0.0-rc.5", features = [ , , "macros" ] } +``` + +You must choose a `DATABASE_DRIVER` and an `ASYNC_RUNTIME`. `macros` is needed if you use SeaORM's generated entities (most likely). + +## DATABASE_DRIVER + +You can choose one or more from: + ++ `sqlx-mysql` - SQLx MySQL and MariaDB ++ `sqlx-postgres` - SQLx PostgreSQL ++ `sqlx-sqlite` - SQLx SQLite + +See also: [SQLx docs](https://docs.rs/crate/sqlx/latest/features). + +:::tip SQL Server (MSSQL) backend + +The installation and configuration of MSSQL driver can be found [here](https://www.sea-ql.org/SeaORM-X/docs/install-and-config/database-and-async-runtime/). + +::: + +## ASYNC_RUNTIME + +You have to choose one from: + +`runtime-async-std-native-tls`, `runtime-tokio-native-tls`, `runtime-async-std-rustls`, `runtime-tokio-rustls` + +Basically, they are in the form of `runtime-ASYNC_RUNTIME-TLS_LIB`: + ++ `ASYNC_RUNTIME` can be [`async-std`](https://crates.io/crates/async-std) or [`tokio`](https://crates.io/crates/tokio) ++ `TLS_LIB` can either be [`native-tls`](https://crates.io/crates/native-tls) or [`rustls`](https://crates.io/crates/rustls) + +1. Choose the ASYNC_RUNTIME corresponding to your Rust web framework: + +| ASYNC_RUNTIME | Web Framework | +| :-----------: | :------------: | +| `async-std` | [`Tide`](https://docs.rs/tide) | +| `tokio` | [`Axum`](https://docs.rs/axum), [`Actix`](https://actix.rs/), [`Poem`](https://docs.rs/poem), [`Rocket`](https://rocket.rs/) | + +2. `native-tls` uses the platform's native security facilities, while `rustls` is an (almost) pure Rust implementation. + +## Extra features + ++ `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 (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/versioned_docs/version-1.1.x/02-install-and-config/02-connection.md b/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/02-connection.md new file mode 100644 index 00000000000..3a7b073d926 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/02-connection.md @@ -0,0 +1,101 @@ +# Database Connection + +To obtain a database connection, use the [`Database`](https://docs.rs/sea-orm/*/sea_orm/struct.Database.html) interface: + +```rust +let db: DatabaseConnection = Database::connect("protocol://username:password@host/database").await?; +``` + +`protocol` can be `mysql:`, `postgres:` or `sqlite:`. + +`host` is usually `localhost`, a domain name or an IP address. + +:::tip + +If you can't get `localhost` to work, try putting in an IP address and port number, e.g. `127.0.0.1:3306` or even `192.168.x.x`. + +::: + +Under the hood, a [`sqlx::Pool`](https://docs.rs/sqlx/0.5/sqlx/struct.Pool.html) is created and owned by [`DatabaseConnection`](https://docs.rs/sea-orm/*/sea_orm/enum.DatabaseConnection.html). + +Each time you call `execute` or `query_one/all` on it, a connection will be acquired and released from the pool. + +Multiple queries will execute in parallel as you `await` on them. + +## Connection String + +Here are some tips for database specific options: + +### MySQL + +Can't think of any + +### Postgres + +#### Specify a schema + +``` +postgres://username:password@host/database?currentSchema=my_schema +``` + +### SQLite + +#### In memory + +``` +sqlite::memory: +``` + +#### Create file if not exists + +``` +sqlite://path/to/db.sqlite?mode=rwc +``` + +#### Read only + +``` +sqlite://path/to/db.sqlite?mode=ro +``` + +## Connect Options + +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"); +opt.max_connections(100) + .min_connections(5) + .connect_timeout(Duration::from_secs(8)) + .acquire_timeout(Duration::from_secs(8)) + .idle_timeout(Duration::from_secs(8)) + .max_lifetime(Duration::from_secs(8)) + .sqlx_logging(true) + .sqlx_logging_level(log::LevelFilter::Info) + .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. + +```rust +let db = Database::connect(url).await?; + +// Closing connection here +db.close().await?; +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/03-debug-log.md b/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/03-debug-log.md new file mode 100644 index 00000000000..4a68b6ba862 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/03-debug-log.md @@ -0,0 +1,39 @@ +# Debug Log + +SeaORM logs debug messages via the [`tracing`](https://crates.io/crates/tracing) crate. + +You can enable SeaORM's logging with the `debug-print` feature flag: + +```toml +[dependencies.sea-orm] +version = "1.0.0-rc.5" +features = ["debug-print"] +``` + +You need to setup [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) to capture and view the debug log. See the snippet below and a complete example [here](https://github.com/SeaQL/sea-orm/blob/master/examples/actix_example/src/main.rs). + +```rust +pub async fn main() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .with_test_writer() + .init(); + + // ... +} +``` + +SeaORM's debug print injects parameters into the SQL string, which makes it easier to read. Instead of seeing `SELECT "chef"."name" FROM "chef" WHERE "chef"."id" = $1`, you will see `SELECT "chef"."name" FROM "chef" WHERE "chef"."id" = 100`. + +## SQLx Logging + +SQLx also logs by default. If you turned on SeaORM's `debug-print`, you can disable SQLx's log by passing [`ConnectOptions`](https://docs.rs/sea-orm/*/sea_orm/struct.ConnectOptions.html) to `connect()`. + +```rust +let mut opt = ConnectOptions::new("protocol://username:password@host/database".to_owned()); +opt + .sqlx_logging(false) // Disable SQLx log + .sqlx_logging_level(log::LevelFilter::Info); // Or set SQLx log level + +let db = Database::connect(opt).await?; +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/_category_.json b/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/_category_.json new file mode 100644 index 00000000000..45d86975adf --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/02-install-and-config/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Installation & Configuration", + "collapsed": false +} diff --git a/SeaORM/versioned_docs/version-1.1.x/03-migration/01-setting-up-migration.md b/SeaORM/versioned_docs/version-1.1.x/03-migration/01-setting-up-migration.md new file mode 100644 index 00000000000..b5d79d81efe --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/03-migration/01-setting-up-migration.md @@ -0,0 +1,179 @@ +# Setting Up Migration + +:::tip We need your help! πŸ“ +Thank you for reading this documentation. While we have you, would you spare a few minutes into completing our [SeaQL Community Survey](https://www.sea-ql.org/community-survey)? This is essential for the continued development and maintenance of SeaORM. +::: + +If you are starting from a fresh database, it's better to version control your database schema. SeaORM ships with a migration tool, allowing you to write migrations in SeaQuery or SQL. + +If you already have a database with tables and data, you can skip this chapter and move on to [generating SeaORM entities](04-generate-entity/01-sea-orm-cli.md). + +## Migration Table + +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 { + // Override the name of migration table + fn migration_table_name() -> sea_orm::DynIden { + Alias::new("override_migration_table_name").into_iden() + } + .. +} +``` +
+ +## Creating Migration Directory + +First, install `sea-orm-cli` with `cargo`. + +```shell +cargo install sea-orm-cli@1.0.0-rc.5 +``` + +:::tip SQL Server (MSSQL) backend + +The installation of `sea-orm-cli` with MSSQL support can be found [here](https://www.sea-ql.org/SeaORM-X/docs/migration/setting-up-migration/). + +::: + +Then, setup the migration directory by executing `sea-orm-cli migrate init`. + +```shell +# Setup the migration directory in `./migration` +$ sea-orm-cli migrate init +Initializing migration directory... +Creating file `./migration/src/lib.rs` +Creating file `./migration/src/m20220101_000001_create_table.rs` +Creating file `./migration/src/main.rs` +Creating file `./migration/Cargo.toml` +Creating file `./migration/README.md` +Done! + +# If you want to setup the migration directory in else where +$ sea-orm-cli migrate init -d ./other/migration/dir +``` + +You should have a migration directory with a structure like below. + +``` +migration +β”œβ”€β”€ Cargo.toml +β”œβ”€β”€ README.md +└── src + β”œβ”€β”€ lib.rs # Migrator API, for integration + β”œβ”€β”€ m20220101_000001_create_table.rs # A sample migration file + └── 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. + +### Migration Crate + +Import the [`sea-orm-migration`](https://crates.io/crates/sea-orm-migration) and [`async-std`](https://crates.io/crates/async-std) crate. + +```toml title="migration/Cargo.toml" +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } + +[dependencies.sea-orm-migration] +version = "1.0.0-rc.5" +features = [ + # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. + # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. + # e.g. + # "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature + # "sqlx-postgres", # `DATABASE_DRIVER` feature +] +``` + +Let's write a migration. Detailed instructions in the next section. + +```rust title="migration/src/m20220120_000001_create_post_table.rs" +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + } +} +``` + +### Entity Crate + +Create an entity crate in your root workspace. + +
+ You don't have SeaORM entities defined? + +You can create an entity crate without any entity files. Then, write the migration and run it to create tables in the database. Finally, [generate SeaORM entities](04-generate-entity/01-sea-orm-cli.md) with `sea-orm-cli` and output the entity files to `entity/src/entities` folder. + +After generating the entity files, you can re-export the generated entities by adding following lines in `entity/src/lib.rs`: + +```rust +mod entities; +pub use entities::*; +``` +
+ +``` +entity +β”œβ”€β”€ Cargo.toml # Include SeaORM dependency +└── src + β”œβ”€β”€ lib.rs # Re-export SeaORM and entities + └── post.rs # Define the `post` entity +``` + +Specify SeaORM dependency. + +```toml title="entity/Cargo.toml" +[dependencies] +sea-orm = { version = "1.0.0-rc.5" } +``` + +### App Crate + +This is where the application logic goes. + +Create a workspace that contains app, entity and migration crates and import the entity crate into the app crate. + +If we want to bundle the migration utility as part of your app, you'd also want to import the migration crate. + +```toml title="./Cargo.toml" +[workspace] +members = [".", "entity", "migration"] + +[dependencies] +entity = { path = "entity" } +migration = { path = "migration" } # depends on your needs + +[dependencies] +sea-orm = { version = "1.0.0-rc.5", features = [..] } +``` + +In your app, you can then run the migrator on startup. + +```rust title="src/main.rs" +use migration::{Migrator, MigratorTrait}; + +let connection = sea_orm::Database::connect(&database_url).await?; +Migrator::up(&connection, None).await?; +``` \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/03-migration/02-writing-migration.md b/SeaORM/versioned_docs/version-1.1.x/03-migration/02-writing-migration.md new file mode 100644 index 00000000000..0f3409453b9 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/03-migration/02-writing-migration.md @@ -0,0 +1,383 @@ +# Writing Migration + +Each migration contains two methods: `up` and `down`. The `up` method is used to alter the database schema, such as adding new tables, columns or indexes, while the `down` method revert the actions performed in the `up` method. + +The SeaORM migration system has the following advantages: + +1. Write DDL statements with SeaQuery or SQL +2. Execute multiple DDL (with conditions) +3. Seed data using the SeaORM API + +## Creating Migrations + +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`. + +```rust title="migration/src/m20220101_000001_create_table.rs" +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( ... ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table( ... ) + .await + } +} +``` + +Additionally, you have to include the new migration in the [`MigratorTrait::migrations`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/migrator/trait.MigratorTrait.html#tymethod.migrations) method. Note that the migrations should be sorted in chronological order. + +```rust title="migration/src/lib.rs" +pub use sea_orm_migration::*; + +mod m20220101_000001_create_table; + +pub struct Migrator; + +#[async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20220101_000001_create_table::Migration), + ] + } +} +``` + +## Defining Migration + +See [`SchemaManager`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/manager/struct.SchemaManager.html) for API reference. + +### Using SeaQuery + +Click [here](https://github.com/SeaQL/sea-query#table-create) to take a quick tour of SeaQuery's DDL statements. + +You can use the [`DeriveIden`](https://docs.rs/sea-orm/*/sea_orm/derive.DeriveIden.html) macro to define identifiers that will be used in your migration. + +```rust +#[derive(DeriveIden)] +enum Post { + Table, // this is a special case; will be mapped to `post` + Id, + Title, + #[sea_orm(iden = "full_text")] // Renaming the identifier + Text, +} + +assert_eq!(Post::Table.to_string(), "post"); +assert_eq!(Post::Id.to_string(), "id"); +assert_eq!(Post::Title.to_string(), "title"); +assert_eq!(Post::Text.to_string(), "full_text"); +``` + +Here are some common DDL snippets you may find useful. + +#### Schema Creation Methods +- Create Table + ```rust + use sea_orm::{EnumIter, Iterable}; + + #[derive(DeriveIden)] + enum Post { + Table, + Id, + Title, + #[sea_orm(iden = "text")] // Renaming the identifier + Text, + Category, + } + + #[derive(Iden, EnumIter)] + pub enum Category { + #[iden = "Feed"] + Feed, + #[iden = "Story"] + Story, + } + + // Remember to import `sea_orm_migration::schema::*` schema helpers into scope + use sea_orm_migration::{prelude::*, schema::*}; + + // Defining the schema with helpers + manager + .create_table( + Table::create() + .table(Post::Table) + .if_not_exists() + .col(pk_auto(Post::Id)) + .col(string(Post::Title)) + .col(string(Post::Text)) + .col(enumeration_null(Post::Category, Alias::new("category"), Category::iter())) + ) + .await + + // Or, you can define the schema without the helpers + manager + .create_table( + Table::create() + .table(Post::Table) + .if_not_exists() + .col( + ColumnDef::new(Post::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(Post::Title).string().not_null()) + .col(ColumnDef::new(Post::Text).string().not_null()) + .col( + ColumnDef::new(Post::Category) + .enumeration(Alias::new("category"), Category::iter()), + ) + ) + .await + ``` +- Create Index + ```rust + manager.create_index(sea_query::Index::create()..) + ``` +- Create Foreign Key + ```rust + manager.create_foreign_key(sea_query::ForeignKey::create()..) + ``` +- Create Data Type (PostgreSQL only) + ```rust + use sea_orm::{EnumIter, Iterable}; + + #[derive(DeriveIden)] + struct CategoryEnum; + + #[derive(DeriveIden, EnumIter)] + enum CategoryVariants { + Feed, + #[sea_orm(iden = "story")] + Story, + } + + manager + .create_type( + Type::create() + .as_enum(CategoryEnum) + .values(CategoryVariants::iter()) + ) + .await?; + ``` + +#### Schema Mutation Methods +- Drop Table + ```rust + use entity::post; + + manager.drop_table(sea_query::Table::drop()..) + ``` +- Alter Table + ```rust + manager.alter_table(sea_query::Table::alter()..) + ``` +- Rename Table + ```rust + manager.rename_table(sea_query::Table::rename()..) + ``` +- Truncate Table + ```rust + manager.truncate_table(sea_query::Table::truncate()..) + ``` +- Drop Index + ```rust + manager.drop_index(sea_query::Index::drop()..) + ``` +- Drop Foreign Key + ```rust + manager.drop_foreign_key(sea_query::ForeignKey::drop()..) + ``` +- Alter Data Type (PostgreSQL only) + ```rust + manager.alter_type(sea_query::Type::alter()..) + ``` +- Drop Data Type (PostgreSQL only) + ```rust + manager.drop_type(sea_query::extension::postgres::Type()..) + ``` + +#### Schema Inspection Methods + +- Has Table + ```rust + manager.has_table("table_name") + ``` +- Has Column + ```rust + manager.has_column("table_name", "column_name") + ``` +- Has Index + ```rust + manager.has_index("table_name", "index_name") + ``` + +### Using raw SQL + +You can write migration files in raw SQL, but then you lost the multi-backend compatibility SeaQuery offers. + +```rust title="migration/src/m20220101_000001_create_table.rs" +use sea_orm::Statement; +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + 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> { + manager + .get_connection() + .execute_unprepared("DROP TABLE `cake`") + .await?; + + Ok(()) + } +} +``` + +## Tip 1: combining multiple schema changes in one migration + +You can combine multiple changes within both up and down migration functions. Here is a complete example: + +```rust +// Remember to import `sea_orm_migration::schema::*` schema helpers into scope +use sea_orm_migration::{prelude::*, schema::*}; + +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + + manager + .create_table( + sea_query::Table::create() + .table(Post::Table) + .if_not_exists() + .col(pk_auto(Post::Id)) + .col(string(Post::Title)) + .col(string(Post::Text)) + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx-post_title") + .table(Post::Table) + .col(Post::Title) + ) + .await?; + + Ok(()) // All good! +} +``` + +and here we have the matching down function: + +```rust +async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + + manager.drop_index(Index::drop().name("idx-post-title")) + .await?; + + manager.drop_table(Table::drop().table(Post::Table)) + .await?; + + Ok(()) // All good! +} +``` + +## Tip 2: `ADD COLUMN IF NOT EXISTS` + +Since this syntax is not available on MySQL, you can: + +```rust +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + if manager.has_column("my_table", "col_to_add").await? { + // ALTER TABLE `my_table` ADD COLUMN `col_to_add` .. + } + + Ok(()) +} +``` + +## Tip 3: Seed data with Entity + +```rust +async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + cake::ActiveModel { + name: Set("Cheesecake".to_owned()), + ..Default::default() + } + .insert(db) + .await?; + + Ok(()) +} +``` + +[Full example](https://github.com/SeaQL/sea-orm/blob/master/examples/seaography_example/migration/src/m20230102_000001_seed_bakery_data.rs). + +## 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. + +At times, you might want to use the [`create_*_from_entity`](09-schema-statement/01-create-table.md) methods to bootstrap your database with several hand written entity files. + +That's perfectly fine if you intend to never change the entity schema. Or, you can keep the original entity and embed a copy in the migration file. diff --git a/SeaORM/versioned_docs/version-1.1.x/03-migration/03-running-migration.md b/SeaORM/versioned_docs/version-1.1.x/03-migration/03-running-migration.md new file mode 100644 index 00000000000..bf6c958e209 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/03-migration/03-running-migration.md @@ -0,0 +1,113 @@ +# Running Migration + +After you have defined the migrations, you can apply or revert migrations in the terminal or on application startup. + +## Command Line Interface (CLI) + +Migrations can be run manually in the terminal. `DATABASE_URL` must be set in your environment, follow the instructions [here](04-generate-entity/01-sea-orm-cli.md#configure-environment) to configure it. + +Supported commands: +- `init`: Initialize migration directory +- `generate`: Generate a new migration file +- `up`: Apply all pending migrations +- `up -n 10`: Apply 10 pending migrations +- `down`: Rollback last applied migration +- `down -n 10`: Rollback last 10 applied migrations +- `status`: Check the status of all migrations +- `fresh`: Drop all tables from the database, then reapply all migrations +- `refresh`: Rollback all applied migrations, then reapply all migrations +- `reset`: Rollback all applied migrations + +### Via `sea-orm-cli` + +The `sea-orm-cli` will execute `cargo run --manifest-path ./migration/Cargo.toml -- COMMAND` under the hood. + +```shell +$ sea-orm-cli migrate COMMAND +``` + +You can customize the manifest path. + +```shell +$ sea-orm-cli migrate COMMAND -d ./other/migration/dir +``` + +### Via SeaSchema Migrator CLI + +Run the migrator CLI defined in `migration/main.rs`. + +```shell +cd migration +cargo run -- COMMAND +``` + +## Migrating Programmatically + +You can perform migration on application startup with `Migrator`, which implements the [`MigratorTrait`](https://docs.rs/sea-orm-migration/*/sea_orm_migration/migrator/trait.MigratorTrait.html). + +```rust title="src/main.rs" +use migration::{Migrator, MigratorTrait}; + +/// Apply all pending migrations +Migrator::up(db, None).await?; + +/// Apply 10 pending migrations +Migrator::up(db, Some(10)).await?; + +/// Rollback all applied migrations +Migrator::down(db, None).await?; + +/// Rollback last 10 applied migrations +Migrator::down(db, Some(10)).await?; + +/// Check the status of all migrations +Migrator::status(db).await?; + +/// Drop all tables from the database, then reapply all migrations +Migrator::fresh(db).await?; + +/// Rollback all applied migrations, then reapply all migrations +Migrator::refresh(db).await?; + +/// Rollback all applied migrations +Migrator::reset(db).await?; +``` + +## Running Migration on Any PostgreSQL Schema + +By default migration will be run on the `public` schema, you can now override it when running migration on the CLI or programmatically. + +For CLI, you can specify the target schema with `-s` / `--database_schema` option: +* via sea-orm-cli: `sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema` +* via SeaORM migrator: `cargo run -- -u postgres://root:root@localhost/database -s my_schema` + +You can also run the migration on the target schema programmatically: + +```rust +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?; +``` + +:::tip SQL Server (MSSQL) backend + +The configuration of running migration on any MSSQL schema can be found [here](https://www.sea-ql.org/SeaORM-X/docs/migration/running-migration/). + +::: + +## Checking Migration Status + +You can use `MigratorTrait::get_pending_migrations()` and `MigratorTrait::get_applied_migrations()` to retrieve the list of migrations. + +```rust +let migrations = Migrator::get_pending_migrations(db).await?; +assert_eq!(migrations.len(), 5); + +let migration = migrations[0]; +assert_eq!(migration.name(), "m20220118_000002_create_fruit_table"); +assert_eq!(migration.status(), MigrationStatus::Pending); +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/03-migration/04-seeding-data.md b/SeaORM/versioned_docs/version-1.1.x/03-migration/04-seeding-data.md new file mode 100644 index 00000000000..383e9e530d3 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/03-migration/04-seeding-data.md @@ -0,0 +1,87 @@ +# Seeding Data + +You can retrieve a `DbConn` from `SchemaManager` and perform data operations as needed, for example, to seed data. + +```rust +use sea_orm_migration::sea_orm::{entity::*, query::*}; + +// ... + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + cake::ActiveModel { + name: Set("Cheesecake".to_owned()), + ..Default::default() + } + .insert(db) + .await?; + + Ok(()) + } +} +``` + +You could also write SeaQuery statement to seed the table. + +```rust +use sea_orm_migration::sea_orm::{entity::*, query::*}; + +// ... + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let insert = Query::insert() + .into_table(Cake::Table) + .columns([Cake::Name]) + .values_panic(["Tiramisu".into()]) + .to_owned(); + + manager.exec_stmt(insert).await?; + + Ok(()) + } +} + +#[derive(DeriveIden)] +pub enum Cake { + Table, + Id, + 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/versioned_docs/version-1.1.x/03-migration/_category_.json b/SeaORM/versioned_docs/version-1.1.x/03-migration/_category_.json new file mode 100644 index 00000000000..dd4e8384894 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/03-migration/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migration", + "collapsed": false + } + \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/01-sea-orm-cli.md b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/01-sea-orm-cli.md new file mode 100644 index 00000000000..60900e43d31 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/01-sea-orm-cli.md @@ -0,0 +1,73 @@ +# Using `sea-orm-cli` + +First, install `sea-orm-cli` with `cargo`. + +```shell +cargo install sea-orm-cli@1.0.0-rc.5 +``` + +:::tip SQL Server (MSSQL) backend + +The installation and the usage of `sea-orm-cli` with MSSQL support can be found [here](https://www.sea-ql.org/SeaORM-X/docs/generate-entity/sea-orm-cli/). + +::: + +## Configure Environment + +Set `DATABASE_URL` in your environment, or create a `.env` file in your project root. Specify your database connection. + +```env title=".env" +DATABASE_URL=protocol://username:password@localhost/database +``` + +## Getting Help + +Use `-h` flag on any CLI command or subcommand for help. + +```shell +# List all available commands +sea-orm-cli -h + +# List all subcommands available in `generate` command +sea-orm-cli generate -h + +# Show how to use `generate entity` subcommand +sea-orm-cli generate entity -h +``` + +## Generating Entity Files + +Discover all tables in a database and generate a corresponding SeaORM entity file for each table. + +Supported databases: +- MySQL +- PostgreSQL +- SQLite + +Command line options: +- `-u` / `--database-url`: database URL (default: DATABASE_URL specified in ENV) +- `-s` / `--database-schema`: database schema (default: DATABASE_SCHEMA specified in ENV) + - for MySQL & SQLite, this argument is ignored + - for PostgreSQL, this argument is optional with default value 'public' +- `-o` / `--output-dir`: entity file output directory (default: current directory) +- `-v` / `--verbose`: print debug messages +- `-l` / `--lib`: generate index file as `lib.rs` instead of `mod.rs` +- `--include-hidden-tables`: generate entity files from hidden tables (tables with names starting with an underscore are hidden and ignored by default) +- `--ignore-tables`: skip generating entity file for specified tables (default: `seaql_migrations`) +- `--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 +- `--enum-extra-derives`: append extra derive macros to generated enums +- `--enum-extra-attributes`: append extra attributes to generated enums +- `--seaography`: generate addition structs in entities for seaography integration + +```shell +# Generate entity files of database `bakery` to `entity/src` +sea-orm-cli generate entity -u protocol://username:password@localhost/bakery -o entity/src +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/02-entity-structure.md b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/02-entity-structure.md new file mode 100644 index 00000000000..cefbf8898f5 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/02-entity-structure.md @@ -0,0 +1,325 @@ +# Entity Structure + +Let's look at a simple [Cake](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/cake.rs) entity. + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "cake")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} +``` + +:::info + +Do not delete the `Relation` enum or `ActiveModelBehavior` impl block even if they are empty. +::: + +## Entity + +The `DeriveEntityModel` macro does all the heavy lifting of defining an `Entity` with associating `Model`, `Column` and `PrimaryKey`. + +### Table Name + +The `table_name` attribute specifies the corresponding table in the database. +Optionally, you can also specify the database schema or database name by `schema_name`. + +### Column Names + +By default, all column names are assumed to be in snake_case. You can override this behaviour for all columns in a model by specifying the `rename_all` attribute. + +```rust +#[sea_orm(rename_all = "camelCase")] +pub struct Model { ... } +``` + +
+ You can find a list of valid values for the `rename_all` attribute here + +- camelCase +- kebab-case +- mixed_case +- SCREAMING_SNAKE_CASE +- snake_case +- title_case +- UPPERCASE +- lowercase +- SCREAMING-KEBAB-CASE +- PascalCase + +
+ +## Column + +### Column Name + +You can override the column name by specifying the `column_name` attribute. + +```rust +#[derive(DeriveEntityModel)] +#[sea_orm(table_name = "user", rename_all = "camelCase")] +pub struct Model { + #[sea_orm(primary_key)] + id: i32, + first_name: String, // firstName + #[sea_orm(column_name = "lAsTnAmE")] + last_name: String, // lAsTnAmE +} +``` + +### Column Type + +The column type will be derived automatically with the following mapping: + +:::tip SQL Server (MSSQL) backend + +The column type mapping of MSSQL can be found [here](https://www.sea-ql.org/SeaORM-X/docs/generate-entity/entity-structure/). + +::: + +For the mappings of Rust primitive data types. + +| Rust type | Database Type
([`ColumnType`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ColumnType.html)) | SQLite
datatype | MySQL
datatype | PostgreSQL
datatype | +| --------- | --------- | --------- | --------- | --------- | +| `String` | Char | char | char | char | +| `String` | String | varchar | varchar | varchar | +| `i8` | TinyInteger | tinyint | tinyint | char | +| `u8` | TinyUnsigned | tinyint | tinyint unsigned | N/A | +| `i16` | SmallInteger | smallint | smallint | smallint | +| `u16` | SmallUnsigned | smallint | smallint unsigned | N/A | +| `i32` | Integer | integer | int | integer | +| `u32` | Unsigned | integer | int unsigned | N/A | +| `i64` | BigInteger | bigint | bigint | bigint | +| `u64` | BigUnsigned | bigint | bigint unsigned | N/A | +| `f32` | Float | float | float | real | +| `f64` | Double | double | double | double precision | +| `bool` | Boolean | boolean | bool | bool | +| `Vec` | Binary | blob | blob | bytea | + +For the mappings of Rust non-primitive data types. You can check [`entity/prelude.rs`](https://github.com/SeaQL/sea-orm/blob/master/src/entity/prelude.rs) for all of the reexported types. + +| Rust type | Database Type
([`ColumnType`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ColumnType.html)) | SQLite
datatype | MySQL
datatype | PostgreSQL
datatype | +| --------- | --------- | --------- | --------- | --------- | +| `Date`: chrono::NaiveDate
`TimeDate`: time::Date | Date | date_text | date | date | +| `Time`: chrono::NaiveTime
`TimeTime`: time::Time | Time | time_text | time | time | +| `DateTime`: chrono::NaiveDateTime
`TimeDateTime`: time::PrimitiveDateTime | DateTime | datetime_text | datetime | timestamp | +| `DateTimeLocal`: chrono::DateTime<Local>
`DateTimeUtc`: chrono::DateTime<Utc> | Timestamp | timestamp_text | timestamp | N/A | +| `DateTimeWithTimeZone`: chrono::DateTime<FixedOffset>
`TimeDateTimeWithTimeZone`: time::OffsetDateTime | TimestampWithTimeZone | timestamp_with_timezone_text | timestamp | timestamp with time zone | +| `Uuid`: uuid::Uuid, uuid::fmt::Braced, uuid::fmt::Hyphenated, uuid::fmt::Simple, uuid::fmt::Urn | Uuid | uuid_text | binary(16) | uuid | +| `Json`: serde_json::Value | Json | json_text | json | json | +| `Decimal`: rust_decimal::Decimal | Decimal | real | decimal | decimal | + +You can override the default mappings between a Rust type and `ColumnType` by the `column_type` attribute. + +```rust +#[sea_orm(column_type = "Text")] +pub name: String +``` + +If you need your JSON field to be deserialized into a struct. You would need to derive `FromJsonQueryResult` for it. + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "json_struct")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + // JSON column defined in `serde_json::Value` + pub json: Json, + // JSON column defined in custom struct + pub json_value: KeyValue, + pub json_value_opt: Option, +} + +// The custom struct must derive `FromJsonQueryResult`, `Serialize` and `Deserialize` +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] +pub struct KeyValue { + pub id: i32, + pub name: String, + pub price: f32, + pub notes: Option, +} +``` + +:::info +Array datatype is a Postgres-only feature. You can define a vector of types that are already supported by SeaORM. +::: + +```rust +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "collection")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub integers: Vec, + pub integers_opt: Option>, + pub floats: Vec, + pub doubles: Vec, + pub strings: Vec, +} +``` + +### Additional Properties + +You can add additional properties `default_value`, `unique`, `indexed` and `nullable` to a column. + +If you specified a custom `column_type` for an optional attribute, you must also specify `nullable`. + +```rust +#[sea_orm(column_type = "Text", default_value = "Sam", unique, indexed, nullable)] +pub name: Option +``` + +### 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 +``` + +### Ignore Attribute + +If you want to ignore a particular model attribute such that it maps to no database column, you can use the `ignore` annotation. + +```rust +#[sea_orm(ignore)] +pub ignore_me: String +``` + +## Primary Key + +Use the `primary_key` attribute to mark a column as the primary key. + +```rust +#[sea_orm(primary_key)] +pub id: i32 +``` + +### Auto Increment + +By default, `auto_increment` is implied for `primary_key` column. Override it by specifying `false`. + +```rust +#[sea_orm(primary_key, auto_increment = false)] +pub id: i32 +``` + +### Composite Key + +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. `auto_increment` is `false` for composite key. + +The max arity of a primary key is 12. + +```rust +pub struct Model { + #[sea_orm(primary_key)] + pub cake_id: i32, + #[sea_orm(primary_key)] + pub fruit_id: i32, +} +``` + +## Relation + +`DeriveRelation` is a macro to help you implement the [`RelationTrait`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.RelationTrait.html). + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, +} +``` + +If there are no relations, simply write: + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} +``` + +The [Related](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Related.html) trait connects entities together, such that you can build queries selecting both entities. + +Learn more about relations in the [Relation](06-relation/01-one-to-one.md) chapter. + +## Active Model Behavior + +Hooks for different actions on an `ActiveModel`. For example, you can perform custom validation logic or trigger side effects. Inside a transaction, you can even abort an action after it is done, preventing it from saving into the database. + +```rust +#[async_trait] +impl ActiveModelBehavior for ActiveModel { + /// Create a new ActiveModel with default values. Also used by `Default::default()`. + fn new() -> Self { + Self { + uuid: Set(Uuid::new_v4()), + ..ActiveModelTrait::default() + } + } + + /// Will be triggered before insert / update + 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: {}", + insert + ))) + } else { + Ok(self) + } + } + + /// Will be triggered after insert / update + async fn after_save(model: Model, db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { + Ok(model) + } + + /// Will be triggered before delete + async fn before_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { + Ok(self) + } + + /// Will be triggered after delete + async fn after_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { + Ok(self) + } +} +``` + +If no customization is needed, simply write: + +```rust +impl ActiveModelBehavior for ActiveModel {} +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/03-expanded-entity-structure.md b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/03-expanded-entity-structure.md new file mode 100644 index 00000000000..18207857677 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/03-expanded-entity-structure.md @@ -0,0 +1,218 @@ +# Expanded Entity Structure + +SeaORM is dynamic, which means you have the flexibility to configure things on runtime. If you are curious what `DeriveEntityModel` expands into, read along. Otherwise, you can skip this for now. + +The expanded entity format can be generated by `sea-orm-cli` with the `--expanded-format` option. + +Let's go through the sections of the expanded [Cake](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/cake_expanded.rs) entity. + +## Entity + +By implementing the [`EntityTrait`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html), you can perform CRUD operations on the given table. + +```rust +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn schema_name(&self) -> Option<&str> { + None + } + + fn table_name(&self) -> &str { + "cake" + } +} +``` + +## Column + +An enum representing all columns in this table. + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Name => ColumnType::String(None).def(), + } + } +} +``` + +All column names are assumed to be in snake-case. You can override the column name by specifying the `column_name` attribute. + +```rust +pub enum Column { + Id, // maps to "id" in SQL + Name, // maps to "name" in SQL + #[sea_orm(column_name = "create_at")] + CreateAt // maps to "create_at" in SQL +} +``` + +To specify the datatype of each column, the [`ColumnType`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ColumnType.html) enum can be used. + +### Additional properties + +- Default Value +- Unique +- Indexed +- Nullable + +```rust +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 { + match self { + 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 { + match self { + 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. + +`ValueType` defines the type of last_insert_id in [`InsertResult`](https://docs.rs/sea-orm/*/sea_orm/struct.InsertResult.html). + +`auto_increment` defines whether the primary key has an auto-generated value. + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + #[sea_orm(column_name = "id")] // Override the default column name + Id, // maps to "id" in SQL +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + + fn auto_increment() -> bool { + true + } +} +``` + +Example composite key + +```rust +pub enum PrimaryKey { + CakeId, + FruitId, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = (i32, i32); + + fn auto_increment() -> bool { + false + } +} +``` + +## Model + +The Rust struct for storing query results. + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveModel, DeriveActiveModel)] +pub struct Model { + pub id: i32, + pub name: String, +} +``` + +### Nullable Attribute + +If the table column is nullable, wrap it with an `Option`. + +```rust {3} +pub struct Model { + pub id: i32, + pub name: Option, +} +``` + +## Active Model + +An `ActiveModel` has all the attributes of its corresponding `Model` but all attributes are wrapped in an [`ActiveValue`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ActiveValue.html). + +```rust +#[derive(Clone, Debug, PartialEq)] +pub struct ActiveModel { + pub id: ActiveValue, + pub name: ActiveValue>, +} +``` + +## Relation + +Specifying the relations with other entities. + +```rust +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), + } + } +} +``` + +## Related + +Defining trait bounds to help you query related entities together, especially helpful in many-to-many relations. + +```rust +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + + fn via() -> Option { + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/04-enumeration.md b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/04-enumeration.md new file mode 100644 index 00000000000..2b4ac12094a --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/04-enumeration.md @@ -0,0 +1,298 @@ +# Enumeration + +You can use Rust enums in model where the values are mapped to a database string, integer or native enum. + +### String + +For string enums, in addition to being able to specify the string value for each variant, you can also specify the `rename_all` attribute on the Enum if all the values should have string values based on case-transformations. + +#### Manual string values + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(1))")] +pub enum Category { + #[sea_orm(string_value = "B")] + Big, + #[sea_orm(string_value = "S")] + Small, +} +``` + +#### Derived string values from variants + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")] +pub enum Category { + #[sea_orm(string_value = "bigTask")] + BigTask, + #[sea_orm(string_value = "smallBreak")] + SmallWork, +} +``` + +The above is equivalent to: + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "camelCase")] +pub enum Category { + BigTask, + SmallWork, +} +``` + +
+ You can find a list of valid values for the `rename_all` attribute here + +- camelCase +- kebab-case +- mixed_case +- SCREAMING_SNAKE_CASE +- snake_case +- title_case +- UPPERCASE +- lowercase +- SCREAMING-KEBAB-CASE +- PascalCase + +
+ +### Integers + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "i32", db_type = "Integer")] +pub enum Color { + #[sea_orm(num_value = 0)] + Black, + #[sea_orm(num_value = 1)] + White, +} +``` +Alternatively, you could write: +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "i32", db_type = "Integer")] +pub enum Color { + Black = 0, + White = 1, +} +``` + +## Native Database Enum + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")] +pub enum Tea { + #[sea_orm(string_value = "EverydayTea")] + EverydayTea, + #[sea_orm(string_value = "BreakfastTea")] + BreakfastTea, +} +``` + +### MySQL + +MySQL enum is just part of the column definition, and cannot be reused for different tables. + +```rust +Table::create() + .table(Posts::TableName) + .col( + ColumnDef::new(Posts::ColumnName) + .enumeration(Alias::new("tea"), [Alias::new("EverydayTea"), Alias::new("BreakfastTea")]), + ) + +"CREATE TABLE `table_name` (`column_name` ENUM('EverydayTea', 'BreakfastTea'))", +``` + +### Postgres + +If you are using Postgres, the enum has to be created in a separate `Type` statement in a migration, you can create it with: + +#### 1. `TYPE` statement + +[Full example](https://github.com/SeaQL/sea-orm/blob/master/sea-orm-migration/tests/common/migration/m20220118_000004_create_tea_enum.rs). + +```rust +// run this in migration: + +manager + .create_type( + // CREATE TYPE "tea" AS ENUM ('EverydayTea', 'BreakfastTea') + Type::create() + .as_enum(Alias::new("tea")) + .values([Alias::new("EverydayTea"), Alias::new("BreakfastTea")]) + .to_owned(), + ) + .await?; +``` + +#### 2. `create_enum_from_active_enum` +This method will provide an interface for adding the type to the database, using the type for table columns, and adding values of this type to rows when seeding data. + +1. Define an `ActiveEnum` + +```rust +#[derive(EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea_type")] +pub enum TeaType { + #[sea_orm(string_value = "EverydayTea")] + EverydayTea, + #[sea_orm(string_value = "BreakfastTea")] + BreakfastTea, +} +``` + +2. Create the type in the database + +```rust +use sea_orm::{Schema, DbBackend}; + +// in a migration: +let schema = Schema::new(DbBackend::Postgres); + +manager + .create_type( + // CREATE TYPE "tea_type" AS ENUM ('EverydayTea', 'BreakfastTea') + schema.create_enum_from_active_enum::(), + ) + .await?; +``` + +3. Use the type as a table column type when creating a table + +```rust diff +// in a migration: + +manager::create() + .table(Tea::Table) + .if_not_exists() + .col(Column::new(Tea::Type).custom(TeaType::name())) // use the type for a table column + // ... more columns +``` +> see also [Schema Creation Methods - Create Table](https://www.sea-ql.org/SeaORM/docs/migration/writing-migration/#schema-creation-methods) + +4. Use the type when populating the database + +```rust +// in a migration + +let insert = Query::insert() + .into_table(Tea::Table) + .columns([Tea::TeaType]) + .values_panic([TeaType::EverydayTea.as_enum()]) // call `as_enum` to convert the variant into a SimpleExpr + .to_owned(); + +manager.exec_stmt(insert).await?; +// ... +``` +> see also [Seeding Data - with sea_query statement](https://www.sea-ql.org/SeaORM/docs/migration/seeding-data/#:~:text=write%20SeaQuery%20statement%20to%20seed%20the%20table) + +## Implementations + +You can implement [`ActiveEnum`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.ActiveEnum.html) by using the [`DeriveActiveEnum`](https://docs.rs/sea-orm/*/sea_orm/derive.DeriveActiveEnum.html) macro. + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm( + rs_type = "String", + db_type = "String(StringLen::N(1))", + enum_name = "category" +)] +pub enum Category { + #[sea_orm(string_value = "B")] + Big, + #[sea_orm(string_value = "S")] + Small, +} +``` + +
+ For illustration purpose, this is roughly what the macro implements: +
+ +```rust +use sea_orm::entity::prelude::*; + +#[derive(Debug, PartialEq, Eq, EnumIter)] +pub enum Category { + Big, + Small, +} + +// Implementing manually +impl ActiveEnum for Category { + // The macro attribute `rs_type` is being pasted here + type Value = String; + + // By default, the name of Rust enum in camel case if `enum_name` was not provided explicitly + fn name() -> String { + "category".to_owned() + } + + // Map Rust enum variants to corresponding `num_value` or `string_value` + fn to_value(&self) -> Self::Value { + match self { + Self::Big => "B", + Self::Small => "S", + } + .to_owned() + } + + // Map `num_value` or `string_value` to corresponding Rust enum variants + fn try_from_value(v: &Self::Value) -> Result { + match v.as_ref() { + "B" => Ok(Self::Big), + "S" => Ok(Self::Small), + _ => Err(DbErr::Type(format!( + "unexpected value for Category enum: {}", + v + ))), + } + } + + // The macro attribute `db_type` is being pasted here + fn db_type() -> ColumnDef { + ColumnType::String(Some(1)).def() + } +} +``` +
+
+ +## Using ActiveEnum on Model + +```rust +use sea_orm::entity::prelude::*; + +// Define the `Category` active enum +#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(1))")] +pub enum Category { + #[sea_orm(string_value = "B")] + Big, + #[sea_orm(string_value = "S")] + Small, +} + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "active_enum")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + // Represents a db column using `Category` active enum + pub category: Category, + pub category_opt: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/05-newtype.md b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/05-newtype.md new file mode 100644 index 00000000000..81d7cf81c88 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/05-newtype.md @@ -0,0 +1,135 @@ +# New Type + +You can define a New Type (`T`) and use it as model field. The following traits have to be implemented. + +1. Implement `From` for [`sea_query::Value`](https://docs.rs/sea-query/*/sea_query/value/enum.Value.html) +2. Implement [`sea_orm::TryGetable`](https://docs.rs/sea-orm/*/sea_orm/trait.TryGetable.html) for `T` +3. Implement [`sea_query::ValueType`](https://docs.rs/sea-query/*/sea_query/value/trait.ValueType.html) for `T` +4. If the field is `Option`, implement [`sea_query::Nullable`](https://docs.rs/sea-query/*/sea_query/value/trait.Nullable.html) for `T` + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "custom_value_type")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub number: Integer, + // Postgres only + pub str_vec: StringVec, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] +pub struct Integer(i32); + +#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] +pub struct StringVec(pub Vec); +``` + +
+ Which `StringVec` expands to: + +```rust +impl std::convert::From for Value { + fn from(source: StringVec) -> Self { + source.0.into() + } +} + +impl sea_orm::TryGetable for StringVec { + fn try_get_by(res: &QueryResult, idx: I) -> Result { + as sea_orm::TryGetable>::try_get_by(res, idx).map(|v| StringVec(v)) + } +} + +impl sea_orm::sea_query::ValueType for StringVec { + fn try_from(v: Value) -> Result { + as sea_orm::sea_query::ValueType>::try_from(v).map(|v| StringVec(v)) + } + + fn type_name() -> String { + stringify!(StringVec).to_owned() + } + + fn array_type() -> sea_orm::sea_query::ArrayType { + sea_orm::sea_query::ArrayType::String + } + + fn column_type() -> sea_orm::sea_query::ColumnType { + sea_orm::sea_query::ColumnType::String(None) + } +} +``` +
+ +You can also define a backend-generic `Vec` field by serialize / deserialize the object to / from JSON: + +```rust +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "json_vec")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub str_vec: ObjectVec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] +pub struct ObjectVec(pub Vec); + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct MyObject { + .. +} +``` + +
+ Which `ObjectVec` expands to: + +```rust +impl sea_orm::TryGetableFromJson for ObjectVec {} + +impl std::convert::From for sea_orm::Value { + fn from(source: ObjectVec) -> Self { + sea_orm::Value::Json(serde_json::to_value(&source).ok().map(|s| std::boxed::Box::new(s))) + } +} + +impl sea_orm::sea_query::ValueType for ObjectVec { + fn try_from(v: sea_orm::Value) -> Result { + match v { + sea_orm::Value::Json(Some(json)) => Ok( + serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?, + ), + _ => Err(sea_orm::sea_query::ValueTypeErr), + } + } + + fn type_name() -> String { + stringify!(ObjectVec).to_owned() + } + + fn array_type() -> sea_orm::sea_query::ArrayType { + sea_orm::sea_query::ArrayType::Json + } + + fn column_type() -> sea_orm::sea_query::ColumnType { + sea_orm::sea_query::ColumnType::Json + } +} + +impl sea_orm::sea_query::Nullable for ObjectVec { + fn null() -> sea_orm::Value { + sea_orm::Value::Json(None) + } +} +``` +
\ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/_category_.json b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/_category_.json new file mode 100644 index 00000000000..300b9eddb52 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/04-generate-entity/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Generating Entities", + "collapsed": false +} diff --git a/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/01-basic-schema.md b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/01-basic-schema.md new file mode 100644 index 00000000000..607b8472a8c --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/01-basic-schema.md @@ -0,0 +1,9 @@ +# Basic Schema + +We will be using this [basic schema](https://github.com/SeaQL/sea-orm/tree/master/src/tests_cfg) for demonstration: + ++ `cake` one-to-many `fruit` ++ `cake` many-to-many `filling` ++ `cake_filling` is the junction table between `cake` and `filling` + +![Basic Schema](https://raw.githubusercontent.com/SeaQL/sea-orm/master/src/tests_cfg/basic_schema.svg) \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/02-select.md b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/02-select.md new file mode 100644 index 00000000000..dacca2268d6 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/02-select.md @@ -0,0 +1,164 @@ +# Select + +Once you have defined the entity, you are ready to retrieve data from the database. Each row of data in the database corresponds to a `Model`. + +By default, SeaORM will select all columns defined in the `Column` enum. + +## Find by Primary Key + +Find a model by its primary key, it can be a single key or composite key. We start by calling [`find_by_id`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html#method.find_by_id) on [`Entity`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html) which helps you construct the select query and condition automatically. Then, fetch a single model from the database with the `one` method. + +```rust +use super::cake::Entity as Cake; +use super::cake_filling::Entity as CakeFilling; + +// Find by primary key +let cheese: Option = Cake::find_by_id(1).one(db).await?; + +// Find by composite primary keys +let vanilla: Option = CakeFilling::find_by_id((6, 8)).one(db).await?; +``` + +## Find with Conditions and Orders + +In addition to retrieving a model by primary key, you can also retrieve one or more models matching specific conditions in a certain order. The [`find`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.EntityTrait.html#method.find) method gives you access to the query builder in SeaORM. It supports the construction of all common select expressions like `where` and `order by`. They can be constructed using [`filter`](https://docs.rs/sea-orm/*/sea_orm/query/trait.QueryFilter.html#method.filter) and [`order_by_*`](https://docs.rs/sea-orm/*/sea_orm/query/trait.QueryOrder.html#method.order_by) methods respectively. + +> Read more about [conditional expression](08-advanced-query/02-conditional-expression.md). + +```rust +let chocolate: Vec = Cake::find() + .filter(cake::Column::Name.contains("chocolate")) + .order_by_asc(cake::Column::Name) + .all(db) + .await?; +``` + +## Find Related Models + +> Read more on the [Relation](06-relation/01-one-to-one.md) chapter. + +### Lazy Loading + +Use the [`find_related`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.ModelTrait.html#method.find_related) method. + +Related models are loaded on demand when you ask for them, preferable if you want to load related models based on some application logic. Note that lazy loading will increase database round trips compared to eager loading. + +```rust +// Find a cake model first +let cheese: Option = Cake::find_by_id(1).one(db).await?; +let cheese: cake::Model = cheese.unwrap(); + +// Then, find all related fruits of this cake +let fruits: Vec = cheese.find_related(Fruit).all(db).await?; +``` + +### Eager Loading + +All related models are loaded at once. This provides minimum database round trips compared to lazy loading. + +#### One to One + +Use the [`find_also_related`](https://docs.rs/sea-orm/*/sea_orm/query/struct.Select.html#method.find_also_related) method. + +```rust +let fruits_and_cakes: Vec<(fruit::Model, Option)> = Fruit::find().find_also_related(Cake).all(db).await?; +``` + +#### One to Many / Many to Many + +Using the [`find_with_related`](https://docs.rs/sea-orm/*/sea_orm/query/struct.Select.html#method.find_with_related) method, the related models will be grouped by the parent models. This method handles both 1-N and M-N cases, and will perform 2 joins when there is a junction table involved. + +```rust +let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find() + .find_with_related(Fruit) + .all(db) + .await?; +``` + +### Batch Loading + +Since 0.11, we introduced a [LoaderTrait](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html) to load related entities in batches. + +Compared to eager loading, it saves bandwidth (consider the one to many case, the one side rows may duplicate) at the cost of one (or two, in the case of many to many) more database roundtrip. + +#### One to One + +Use the [load_one](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_one) method. + +```rust +let fruits: Vec = Fruit::find().all(db).await?; +let cakes: Vec> = fruits.load_one(Cake, db).await?; +``` + +#### One to Many + +Use the [load_many](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_many) method. + +```rust +let cakes: Vec = Cake::find().all(db).await?; +let fruits: Vec> = cakes.load_many(Fruit, db).await?; +``` + +#### Many to Many + +Use the [load_many_to_many](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_many_to_many) method. You have to provide the junction Entity. + +```rust +let cakes: Vec = Cake::find().all(db).await?; +let fillings: Vec> = cakes.load_many_to_many(Filling, CakeFilling, db).await?; +``` + +## Paginate Result + +Convert any SeaORM select into a [paginator](https://docs.rs/sea-orm/*/sea_orm/struct.Paginator.html) with custom page size. + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake}; +let mut cake_pages = cake::Entity::find() + .order_by_asc(cake::Column::Id) + .paginate(db, 50); + +while let Some(cakes) = cake_pages.fetch_and_next().await? { + // Do something on cakes: Vec +} +``` + +## Cursor Pagination + +Use cursor pagination If you want to paginate rows based on column(s) such as the primary key. + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake}; +// Create a cursor that order by `cake`.`id` +let mut cursor = cake::Entity::find().cursor_by(cake::Column::Id); + +// Filter paginated result by `cake`.`id` > 1 AND `cake`.`id` < 100 +cursor.after(1).before(100); + +// Get first 10 rows (order by `cake`.`id` ASC) +for cake in cursor.first(10).all(db).await? { + // Do something on cake: cake::Model +} + +// Get last 10 rows (order by `cake`.`id` DESC but rows are returned in ascending order) +for cake in cursor.last(10).all(db).await? { + // Do something on cake: cake::Model +} +``` + +Paginate rows based on a composite primary key is also available. + +```rust +use sea_orm::{entity::*, query::*, tests_cfg::cake_filling}; +let rows = cake_filling::Entity::find() + .cursor_by((cake_filling::Column::CakeId, cake_filling::Column::FillingId)) + .after((0, 1)) + .before((10, 11)) + .first(3) + .all(&db) + .await?, +``` + +## Select custom + +If you want to select custom columns and expressions, read the [custom select](08-advanced-query/01-custom-select.md) section. diff --git a/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/03-insert.md b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/03-insert.md new file mode 100644 index 00000000000..41b1c533e9f --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/03-insert.md @@ -0,0 +1,300 @@ +# Insert + +Before diving into SeaORM insert we have to introduce `ActiveValue` and `ActiveModel`. + +## ActiveValue + +A wrapper struct to capture the changes made to `ActiveModel` attributes. + +```rust +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 + +An `ActiveModel` has all the attributes of `Model` wrapped in `ActiveValue`. + +You can use `ActiveModel` to insert a row with a subset of columns set. + +```rust +let cheese: Option = Cake::find_by_id(1).one(db).await?; + +// Get Model +let model: cake::Model = cheese.unwrap(); +assert_eq!(model.name, "Cheese Cake".to_owned()); + +// Into ActiveModel +let active_model: cake::ActiveModel = model.into(); +assert_eq!(active_model.name, ActiveValue::unchanged("Cheese Cake".to_owned())); +``` + +### Set ActiveModel from JSON Value + +If you want to save user input into the database you can easily convert JSON value into `ActiveModel`. Note that you might want to [skip deserializing](https://serde.rs/attr-skip-serializing.html) JSON's primary key attribute, you can config that as shown below. + +```rust +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "fruit")] +pub struct Model { + #[sea_orm(primary_key)] + #[serde(skip_deserializing)] // Skip deserializing + pub id: i32, + pub name: String, + pub cake_id: Option, +} +``` + +Set the attributes in `ActiveModel` with `set_from_json` method. + +```rust +// A ActiveModel with primary key set +let mut fruit = fruit::ActiveModel { + id: ActiveValue::Set(1), + name: ActiveValue::NotSet, + cake_id: ActiveValue::NotSet, +}; + +// Note that this method will not alter the primary key values in ActiveModel +fruit.set_from_json(json!({ + "id": 8, + "name": "Apple", + "cake_id": 1, +}))?; + +assert_eq!( + fruit, + fruit::ActiveModel { + id: ActiveValue::Set(1), + name: ActiveValue::Set("Apple".to_owned()), + cake_id: ActiveValue::Set(Some(1)), + } +); +``` + +Create a new `ActiveModel` from JSON value with the `from_json` method. + +```rust +let fruit = fruit::ActiveModel::from_json(json!({ + "name": "Apple", +}))?; + +assert_eq!( + fruit, + fruit::ActiveModel { + id: ActiveValue::NotSet, + name: ActiveValue::Set("Apple".to_owned()), + cake_id: ActiveValue::NotSet, + } +); +``` + +### Checking if an ActiveModel is changed + +You can check whether any field in an `ActiveModel` is `Set` with the [`is_changed`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ActiveModelTrait.html#method.is_changed) method. + +```rust +let mut fruit: fruit::ActiveModel = Default::default(); +assert!(!fruit.is_changed()); + +fruit.set(fruit::Column::Name, "apple".into()); +assert!(fruit.is_changed()); +``` + +### Convert ActiveModel back to Model + +Using [try_into_model](https://docs.rs/sea-orm/*/sea_orm/entity/trait.TryIntoModel.html#tymethod.try_into_model) method you can convert ActiveModel back to Model. + +```rust +assert_eq!( + ActiveModel { + id: Set(2), + name: Set("Apple".to_owned()), + cake_id: Set(Some(1)), + } + .try_into_model() + .unwrap(), + Model { + id: 2, + name: "Apple".to_owned(), + cake_id: Some(1), + } +); + +assert_eq!( + ActiveModel { + id: Set(1), + name: NotSet, + cake_id: Set(None), + } + .try_into_model(), + Err(DbErr::AttrNotSet(String::from("name"))) +); +``` + +## Insert One + +Insert an active model and get back a fresh `Model`. Its value is retrieved from database, so any auto-generated fields will be populated. + +```rust +let pear = fruit::ActiveModel { + name: Set("Pear".to_owned()), + ..Default::default() // all other attributes are `NotSet` +}; + +let pear: fruit::Model = pear.insert(db).await?; +``` + +Insert an active model and get back the last insert id. Its type matches the model's primary key type, so it could be a tuple if the model has a composite primary key. + +```rust +let pear = fruit::ActiveModel { + name: Set("Pear".to_owned()), + ..Default::default() // all other attributes are `NotSet` +}; + +let res: InsertResult = fruit::Entity::insert(pear).exec(db).await?; +assert_eq!(res.last_insert_id, 28) +``` + +:::tip SQL Server (MSSQL) backend + +The `IDENTITY INSERT` of MSSQL is documented [here](https://www.sea-ql.org/SeaORM-X/docs/basic-crud/insert/). + +::: + +## Insert Many + +Insert many active models and get back the last insert id. + +```rust +let apple = fruit::ActiveModel { + name: Set("Apple".to_owned()), + ..Default::default() +}; + +let orange = fruit::ActiveModel { + name: Set("Orange".to_owned()), + ..Default::default() +}; + +let res: InsertResult = Fruit::insert_many([apple, orange]).exec(db).await?; +assert_eq!(res.last_insert_id, 30) +``` + +Supplying an empty set to `insert_many` method will result in an error. However, you can change the behaviour with `on_empty_do_nothing` which wraps the `InsertResult` with a `TryInsertResult`. + +```rust +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. + +```rust +let orange = cake::ActiveModel { + id: ActiveValue::set(2), + name: ActiveValue::set("Orange".to_owned()), +}; + +assert_eq!( + cake::Entity::insert(orange.clone()) + .on_conflict( + // on conflict do nothing + sea_query::OnConflict::column(cake::Column::Name) + .do_nothing() + .to_owned() + ) + .build(DbBackend::Postgres) + .to_string(), + r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING"#, +); + +assert_eq!( + cake::Entity::insert(orange) + .on_conflict( + // on conflict do update + sea_query::OnConflict::column(cake::Column::Name) + .update_column(cake::Column::Name) + .to_owned() + ) + .build(DbBackend::Postgres) + .to_string(), + r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name""#, +); +``` + +Performing an upsert statement without inserting or updating any of the row will result in a `DbErr::RecordNotInserted` error. + +```rust +// When `id` column have conflicting value, do nothing +let on_conflict = OnConflict::column(Column::Id).do_nothing().to_owned(); + +// Insert `1`, `2`, `3` into the table +let res = Entity::insert_many([ + ActiveModel { id: Set(1) }, + ActiveModel { id: Set(2) }, + ActiveModel { id: Set(3) }, +]) +.on_conflict(on_conflict.clone()) +.exec(db) +.await; + +assert_eq!(res?.last_insert_id, 3); + +// Insert `4` into the table together with the previous 3 rows +let res = Entity::insert_many([ + ActiveModel { id: Set(1) }, + ActiveModel { id: Set(2) }, + ActiveModel { id: Set(3) }, + ActiveModel { id: Set(4) }, +]) +.on_conflict(on_conflict.clone()) +.exec(db) +.await; + +assert_eq!(res?.last_insert_id, 4); + +// Repeat last insert. Since all 4 rows already exist, this essentially did nothing. +// A `DbErr::RecordNotInserted` error will be thrown. +let res = Entity::insert_many([ + ActiveModel { id: Set(1) }, + ActiveModel { id: Set(2) }, + ActiveModel { id: Set(3) }, + ActiveModel { id: Set(4) }, +]) +.on_conflict(on_conflict) +.exec(db) +.await; + +assert_eq!(res.err(), Some(DbErr::RecordNotInserted)); +``` + +If you want `RecordNotInserted` to be an `Ok` instead of an error, call `.do_nothing()`: + +```rust +let res = Entity::insert_many([..]) + .on_conflict(on_conflict) + .do_nothing() + .exec(db) + .await; + +assert!(matches!(res, Ok(TryInsertResult::Conflicted))); +``` \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/04-update.md b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/04-update.md new file mode 100644 index 00000000000..433d3e27993 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/04-update.md @@ -0,0 +1,68 @@ +# Update + +## Update One + +You will get a `Model` from find result. If you want to save the model back into the database, you need to convert it into an `ActiveModel` *first*. The generated query will only include the `Set` attributes. + +```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()); + +// 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?; +``` + +## Update Many + +You can also update multiple rows in the database without finding each `Model` with SeaORM select. + +```rust +// Bulk set attributes using ActiveModel +let update_result: UpdateResult = Fruit::update_many() + .set(pear) + .filter(fruit::Column::Id.eq(1)) + .exec(db) + .await?; + +// UPDATE `fruit` SET `cake_id` = 1 WHERE `fruit`.`name` LIKE '%Apple%' +Fruit::update_many() + .col_expr(fruit::Column::CakeId, Expr::value(1)) + .filter(fruit::Column::Name.contains("Apple")) + .exec(db) + .await?; +``` + +### Update with returning (Postgres only) + +Use `exec_with_returning` to return models that were modified: + +```rust +let fruits: Vec = Fruit::update_many() + .col_expr(fruit::Column::CakeId, Expr::value(1)) + .filter(fruit::Column::Name.contains("Apple")) + .exec_with_returning(db) + .await?; +``` \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/05-save.md b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/05-save.md new file mode 100644 index 00000000000..480fce9ecd6 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/05-save.md @@ -0,0 +1,32 @@ +# Save + +This is a helper method to save (insert / update) `ActiveModel` into the database. + +## Save Behaviour + +When saving an `ActiveModel`, it will perform either insert or update depending on the primary key attribute: + +- Insert if primary key is `NotSet` +- Update if primary key is `Set` or `Unchanged` + +## Usage + +Calling `save` to insert or update an `ActiveModel`. + +```rust +use sea_orm::ActiveValue::NotSet; + +let banana = fruit::ActiveModel { + id: NotSet, // primary key is NotSet + name: Set("Banana".to_owned()), + ..Default::default() // all other attributes are `NotSet` +}; + +// Insert, because primary key `id` is `NotSet` +let banana: fruit::ActiveModel = banana.save(db).await?; + +banana.name = Set("Banana Mongo".to_owned()); + +// Update, because primary key `id` is `Unchanged` +let banana: fruit::ActiveModel = banana.save(db).await?; +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/06-delete.md b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/06-delete.md new file mode 100644 index 00000000000..e49f2d33ac9 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/06-delete.md @@ -0,0 +1,38 @@ +# Delete + +## Delete One + +Find a `Model` from the database, then delete the corresponding row from database. + +```rust +use sea_orm::entity::ModelTrait; + +let orange: Option = Fruit::find_by_id(30).one(db).await?; +let orange: fruit::Model = orange.unwrap(); + +let res: DeleteResult = orange.delete(db).await?; +assert_eq!(res.rows_affected, 1); +``` + +## Delete by Primary Key + +Instead of selecting `Model` from the database then deleting it. You could also delete a row from database directly by its primary key. + +```rust +let res: DeleteResult = Fruit::delete_by_id(38).exec(db).await?; +assert_eq!(res.rows_affected, 1); +``` + +## Delete Many + +You can also delete multiple rows from the database without finding each `Model` with SeaORM select. + +```rust +// DELETE FROM `fruit` WHERE `fruit`.`name` LIKE '%Orange%' +let res: DeleteResult = fruit::Entity::delete_many() + .filter(fruit::Column::Name.contains("Orange")) + .exec(db) + .await?; + +assert_eq!(res.rows_affected, 2); +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/07-json.md b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/07-json.md new file mode 100644 index 00000000000..5778e1e43c5 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/07-json.md @@ -0,0 +1,54 @@ +# JSON + +## Select JSON Result + +All SeaORM selects are capable of returning `serde_json::Value`. + +```rust +// Find by id +let cake: Option = Cake::find_by_id(1) + .into_json() + .one(db) + .await?; + +assert_eq!( + cake, + Some(serde_json::json!({ + "id": 1, + "name": "Cheese Cake" + })) +); + +// Find with filter +let cakes: Vec = Cake::find() + .filter(cake::Column::Name.contains("chocolate")) + .order_by_asc(cake::Column::Name) + .into_json() + .all(db) + .await?; + +assert_eq!( + cakes, + [ + serde_json::json!({ + "id": 2, + "name": "Chocolate Forest" + }), + serde_json::json!({ + "id": 8, + "name": "Chocolate Cupcake" + }), + ] +); + +// Paginate json result +let cake_pages: Paginator<_> = Cake::find() + .filter(cake::Column::Name.contains("chocolate")) + .order_by_asc(cake::Column::Name) + .into_json() + .paginate(db, 50); + +while let Some(cakes) = cake_pages.fetch_and_next().await? { + // Do something on cakes: Vec +} +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/08-raw-sql.md b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/08-raw-sql.md new file mode 100644 index 00000000000..4663267d3d5 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/08-raw-sql.md @@ -0,0 +1,124 @@ +# Raw SQL + +## Query by raw SQL + +You can select `Model` from raw query, with appropriate syntax for binding parameters, i.e. `?` for MySQL and SQLite, and `$N` for PostgreSQL. + +```rust +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"#, + [1.into()], + )) + .one(&db) + .await?; +``` + +You can also select a custom model. Here, we select all unique names from cake. + +```rust +#[derive(Debug, FromQueryResult)] +pub struct UniqueCake { + name: String, +} + +let unique: Vec = UniqueCake::find_by_statement(Statement::from_sql_and_values( + DbBackend::Postgres, + r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name"#, + [], + )) + .all(&db) + .await?; +``` + +If you do not know what your model looks like beforehand, you can use `JsonValue`. + +```rust +let unique: Vec = JsonValue::find_by_statement(Statement::from_sql_and_values( + DbBackend::Postgres, + r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name"#, + [], + )) + .all(&db) + .await?; + ``` + +You can paginate [`SelectorRaw`](https://docs.rs/sea-orm/*/sea_orm/struct.SelectorRaw.html) and fetch `Model` in batch. + +```rust +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"#, + [1.into()], + )) + .paginate(db, 50); + +while let Some(cakes) = cake_pages.fetch_and_next().await? { + // Do something on cakes: Vec +} +``` + +## Get raw SQL query + +Use `build` and `to_string` methods on any CRUD operations to get the database-specific raw SQL for debugging purposes. + +```rust +use sea_orm::DatabaseBackend; + +assert_eq!( + cake_filling::Entity::find_by_id((6, 8)) + .build(DatabaseBackend::MySql) + .to_string(), + [ + "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(" ") +); +``` + +## Use Raw Query & Execute Interface + +You can build SQL statements using `sea-query` and query / execute it directly on the `DatabaseConnection` interface inside SeaORM. + +### Get Custom Result using `query_one` and `query_all` methods + +```rust +let query_res: Option = db + .query_one(Statement::from_string( + DatabaseBackend::MySql, + "SELECT * FROM `cake`;", + )) + .await?; +let query_res = query_res.unwrap(); +let id: i32 = query_res.try_get("", "id")?; + +let query_res_vec: Vec = db + .query_all(Statement::from_string( + DatabaseBackend::MySql, + "SELECT * FROM `cake`;", + )) + .await?; +``` + +### Execute Query using `execute` method + +```rust +let exec_res: ExecResult = db + .execute(Statement::from_string( + DatabaseBackend::MySql, + "DROP DATABASE IF EXISTS `sea`;", + )) + .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/versioned_docs/version-1.1.x/05-basic-crud/_category_.json b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/_category_.json new file mode 100644 index 00000000000..14ef5126e1c --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/05-basic-crud/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Basic CRUD", + "collapsed": false +} diff --git a/SeaORM/versioned_docs/version-1.1.x/06-relation/01-one-to-one.md b/SeaORM/versioned_docs/version-1.1.x/06-relation/01-one-to-one.md new file mode 100644 index 00000000000..66f5ee1a7d9 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/06-relation/01-one-to-one.md @@ -0,0 +1,108 @@ +# One to One + +:::tip We need your help! πŸ“ +Thank you for reading this documentation. While we have you, would you spare a few minutes into completing our [SeaQL Community Survey](https://www.sea-ql.org/community-survey)? This is essential for the continued development and maintenance of SeaORM. +::: + +A one-to-one relation is the most basic type of database relation. Let say a `Cake` entity has at most one `Fruit` topping. + +## Defining the Relation + +On the `Cake` entity, to define the relation: +1. Add a new variant `Fruit` to the `Relation` enum. +1. Define it with `Entity::has_one()`. +1. Implement the `Related` trait. + +```rust {3,9,14} title="entity/cake.rs" +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_one(super::fruit::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} +``` + +Alternatively, the definition can be shortened by the `DeriveRelation` macro, +where the following eliminates the need for the `RelationTrait` implementation above: + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_one = "super::fruit::Entity")] + Fruit, +} + +// `Related` trait has to be implemented by hand +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} +``` + +## Defining the Inverse Relation + +On the `Fruit` entity, its `cake_id` attribute is referencing the primary key of `Cake` entity. + +To define the inverse relation: +1. Add a new enum variant `Relation::Cake` to the `Fruit` entity. +1. Write the definition of it with the `Entity::belongs_to()` method, we always define the inverse relation using this method. +1. Implement the `Related` trait. + +```rust title="entity/fruit.rs" +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} +``` + +Alternatively, the definition can be shortened by the `DeriveRelation` macro, +where the following eliminates the need for the `RelationTrait` implementation above: + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::cake::Entity", + from = "Column::CakeId", + to = "super::cake::Column::Id" + )] + Cake, +} + +// `Related` trait has to be implemented by hand +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/06-relation/02-one-to-many.md b/SeaORM/versioned_docs/version-1.1.x/06-relation/02-one-to-many.md new file mode 100644 index 00000000000..979bb000a27 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/06-relation/02-one-to-many.md @@ -0,0 +1,96 @@ +# One to Many + +A one-to-many relation is similar to a one-to-one relation. In the previous section, we gave the example of "a `Cake` entity has at most one `Fruit` topping". To make it a one-to-many relation, we remove the "at most one" constraint. So, we have a `Cake` entity that might have many `Fruit` toppings. + +## Defining the Relation + +This is almost identical to defining a one-to-one relation; the only difference is that we use `Entity::has_many()` method here. + +```rust {3,9,14} title="entity/cake.rs" +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Fruit, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Fruit => Entity::has_many(super::fruit::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} +``` + +Alternatively, the definition can be shortened by the `DeriveRelation` macro, +where the following eliminates the need for the `RelationTrait` implementation above: + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::fruit::Entity")] + Fruit, +} + +// `Related` trait has to be implemented by hand +impl Related for Entity { + fn to() -> RelationDef { + Relation::Fruit.def() + } +} +``` + +## Defining the Inverse Relation + +It is the same as defining the one-to-one inverse relation. + +```rust title="entity/fruit.rs" +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} +``` + +Alternatively, the definition can be shortened by the `DeriveRelation` macro, +where the following eliminates the need for the `RelationTrait` implementation above: + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::cake::Entity", + from = "Column::CakeId", + to = "super::cake::Column::Id" + )] + Cake, +} + +// `Related` trait has to be implemented by hand +impl Related for Entity { + fn to() -> RelationDef { + Relation::Cake.def() + } +} +``` diff --git a/SeaORM/versioned_docs/version-1.1.x/06-relation/03-many-to-many.md b/SeaORM/versioned_docs/version-1.1.x/06-relation/03-many-to-many.md new file mode 100644 index 00000000000..2f3a7384704 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/06-relation/03-many-to-many.md @@ -0,0 +1,134 @@ +# Many to Many + +A many-to-many relation is formed by three tables, where two tables are related via a junction table. As an example, a `Cake` has many `Filling` and `Filling` are shared by many `Cake` via an intermediate entity `CakeFilling`. + +## Defining the Relation + +On the `Cake` entity, implement the `Related` trait. + +`Relation` in SeaORM is an arrow: it has `from` and `to`. `cake_filling::Relation::Cake` defines `CakeFilling -> Cake`. Calling [`rev`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.RelationDef.html#method.rev) reverses it into `Cake -> CakeFilling`. + +Chaining this with `cake_filling::Relation::Filling` which defines `CakeFilling -> Filling` resulting in `Cake -> CakeFilling -> Filling`. + +```rust {4,10} title="entity/cake.rs" +impl Related for Entity { + // The final relation is Cake -> CakeFilling -> Filling + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + + fn via() -> Option { + // The original relation is CakeFilling -> Cake, + // after `rev` it becomes Cake -> CakeFilling + Some(super::cake_filling::Relation::Cake.def().rev()) + } +} +``` + +Similarly, on the `Filling` entity, implement the `Related` trait. First, join with intermediate table `via` the inverse of `cake_filling::Relation::Filling` relation, then join `to` `Cake` entity with `cake_filling::Relation::Cake` relation. + +```rust {3,7} title="entity/filling.rs" +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Cake.def() + } + + fn via() -> Option { + Some(super::cake_filling::Relation::Filling.def().rev()) + } +} +``` + +## Defining the Inverse Relation + +On the `CakeFilling` entity, its `cake_id` attribute is referencing the primary key of `Cake` entity, and its `filling_id` attribute is referencing the primary key of `Filling` entity. + +To define the inverse relation: +1. Add two new variants `Cake` and `Filling` to the `Relation` enum. +1. Define both relations with `Entity::belongs_to()`. + +```rust title="entity/cake_filling.rs" +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Filling, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::belongs_to(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Filling => Entity::belongs_to(super::filling::Entity) + .from(Column::FillingId) + .to(super::filling::Column::Id) + .into(), + } + } +} +``` + +Alternatively, the definition can be shortened by the `DeriveRelation` macro, where the following eliminates the need for the `RelationTrait` implementation above: + +```rust +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::cake::Entity", + from = "Column::CakeId", + to = "super::cake::Column::Id" + )] + Cake, + #[sea_orm( + belongs_to = "super::filling::Entity", + from = "Column::FillingId", + to = "super::filling::Column::Id" + )] + 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/versioned_docs/version-1.1.x/06-relation/04-chained-relations.md b/SeaORM/versioned_docs/version-1.1.x/06-relation/04-chained-relations.md new file mode 100644 index 00000000000..cb21665a11d --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/06-relation/04-chained-relations.md @@ -0,0 +1,94 @@ +# Chained Relations + +The `Related` trait is a representation of the arrows (1-1, 1-N, M-N) we draw on Entity Relationship Diagrams. A [`Linked`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Linked.html) is composed of a chain of relations, and is useful when: + +1. there exist multiple join paths between a pair of entities +1. joining across multiple entities in a relational query + +Take [this](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/cake.rs) as a simple example, where we join cake and filling via an intermediate `cake_filling` table. + +```rust title="entity/links.rs" +pub struct CakeToFilling; + +impl Linked for CakeToFilling { + type FromEntity = cake::Entity; + + type ToEntity = filling::Entity; + + fn link(&self) -> Vec { + vec![ + cake_filling::Relation::Cake.def().rev(), + cake_filling::Relation::Filling.def(), + ] + } +} +``` + +Alternatively, the `RelationDef` can be defined on the fly, where the following is equivalent to the above: + +```rust +pub struct CakeToFilling; + +impl Linked for CakeToFilling { + type FromEntity = cake::Entity; + + type ToEntity = filling::Entity; + + fn link(&self) -> Vec { + vec![ + cake_filling::Relation::Cake.def().rev(), + cake_filling::Entity::belongs_to(filling::Entity) + .from(cake_filling::Column::FillingId) + .to(filling::Column::Id) + .into(), + ] + } +} +``` + +### Lazy Loading + +Find fillings that can be filled into a cake with the [`find_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ModelTrait.html#method.find_linked) method. + +```rust +let cake_model = cake::Model { + id: 12, + name: "".to_owned(), +}; + +assert_eq!( + cake_model + .find_linked(cake::CakeToFilling) + .build(DbBackend::MySql) + .to_string(), + [ + "SELECT `filling`.`id`, `filling`.`name`, `filling`.`vendor_id`", + "FROM `filling`", + "INNER JOIN `cake_filling` AS `r0` ON `r0`.`filling_id` = `filling`.`id`", + "INNER JOIN `cake` AS `r1` ON `r1`.`id` = `r0`.`cake_id`", + "WHERE `r1`.`id` = 12", + ] + .join(" ") +); +``` + +### Eager Loading + +[`find_also_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_also_linked) is a dual of `find_also_related`; [`find_with_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_with_linked) is a dual of `find_with_related`; : + +```rust +assert_eq!( + cake::Entity::find() + .find_also_linked(links::CakeToFilling) + .build(DbBackend::MySql) + .to_string(), + [ + r#"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,"#, + r#"`r1`.`id` AS `B_id`, `r1`.`name` AS `B_name`, `r1`.`vendor_id` AS `B_vendor_id`"#, + r#"FROM `cake`"#, + r#"LEFT JOIN `cake_filling` AS `r0` ON `cake`.`id` = `r0`.`cake_id`"#, + r#"LEFT JOIN `filling` AS `r1` ON `r0`.`filling_id` = `r1`.`id`"#, + ] + .join(" ") +); +``` \ No newline at end of file diff --git a/SeaORM/versioned_docs/version-1.1.x/06-relation/05-self-referencing.md b/SeaORM/versioned_docs/version-1.1.x/06-relation/05-self-referencing.md new file mode 100644 index 00000000000..6d7ffd78c09 --- /dev/null +++ b/SeaORM/versioned_docs/version-1.1.x/06-relation/05-self-referencing.md @@ -0,0 +1,36 @@ +# Self Referencing + +In previous section, we introduced the [`Linked`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Linked.html) trait. It can also help you define self referencing relations. + +The following example defines an Entity that references itself. + +```rust +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "self_join")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub uuid: Uuid, + pub uuid_ref: Option, + pub time: Option