Skip to content

Commit

Permalink
SeaORM 0.11.x Docs - 1 (#87)
Browse files Browse the repository at this point in the history
* Update 02-writing-migration.md

* Update SeaORM/docs/03-migration/02-writing-migration.md

* Support various UUID formats that are available in `uuid::fmt` module (SeaQL/sea-orm#1325)

* Casting columns as a different data type on select, insert and update (SeaQL/sea-orm#1304)

* Methods of `ActiveModelBehavior` receive db connection as a parameter (SeaQL/sea-orm#1145, SeaQL/sea-orm#1328)

* Added `execute_unprepared` method to `DatabaseConnection` and `DatabaseTransaction` (SeaQL/sea-orm#1327)

* Added `Select::into_tuple` to select rows as tuples (instead of defining a custom Model) (SeaQL/sea-orm#1311)

* Generate `#[serde(skip)]` for hidden columns (SeaQL/sea-orm#1171, SeaQL/sea-orm#1320)

* Generate entity with extra derives and attributes for model struct (SeaQL/sea-orm#1124, SeaQL/sea-orm#1321)

* Generate entity with extra derives and attributes for model struct (SeaQL/sea-orm#1124, SeaQL/sea-orm#1321)

* async_trait

* Migrations are now performed inside a transaction for Postgres (SeaQL/sea-orm#1379)

* `MockDatabase::append_exec_results()`, `MockDatabase::append_query_results()`, `MockDatabase::append_exec_errors()` and `MockDatabase::append_query_errors()` take any types implemented `IntoIterator` trait (SeaQL/sea-orm#1367)

* Cleanup the use of `vec!` macros

* Added `DatabaseConnection::close` (SeaQL/sea-orm#1236)

* Added `ActiveValue::reset` to convert `Unchanged` into `Set` (SeaQL/sea-orm#1177)

* Added `QueryTrait::apply_if` to optionally apply a filter (SeaQL/sea-orm#1415)

* Added the `sea-orm-internal` feature flag to expose some SQLx types (SeaQL/sea-orm#1297, SeaQL/sea-orm#1434)

* Add `QuerySelect::columns` method - select multiple columns (SeaQL/sea-orm#1264)

* Edit

* Update SeaORM/docs/02-install-and-config/02-connection.md

Co-authored-by: Chris Tsang <[email protected]>

* Update SeaORM/docs/05-basic-crud/03-insert.md

Co-authored-by: Chris Tsang <[email protected]>

* fmt

* Edit

---------

Co-authored-by: Chris Tsang <[email protected]>
  • Loading branch information
billy1624 and tyt2y3 authored Feb 3, 2023
1 parent d62dc89 commit 877caf8
Show file tree
Hide file tree
Showing 17 changed files with 271 additions and 97 deletions.
14 changes: 11 additions & 3 deletions SeaORM/docs/02-install-and-config/01-database-and-async-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ Basically, they are in the form of `runtime-ASYNC_RUNTIME-TLS_LIB`:

## Extra features

`debug-print` - print every SQL statement to logger

`mock` - mock interface for unit testing
+ `debug-print` - print every SQL statement to logger
+ `mock` - mock interface for unit testing
+ `macros` - procedural macros for your convenient
+ `with-chrono` - support [`chrono`](https://crates.io/crates/chrono) types
+ `with-time` - support [`time`](https://crates.io/crates/time) types
+ `with-json` - support [`serde-json`](https://crates.io/crates/serde-json) types
+ `with-rust_decimal` - support [`rust_decimal`](https://crates.io/crates/rust_decimal) types
+ `with-bigdecimal` - support [`bigdecimal`](https://crates.io/crates/bigdecimal) types
+ `with-uuid` - support [`uuid`](https://crates.io/crates/uuid) types
+ `postgres-array` - support array types in Postgres
+ `sea-orm-internal` - opt-in unstable internal APIs (for accessing re-export SQLx types)
11 changes: 11 additions & 0 deletions SeaORM/docs/02-install-and-config/02-connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,14 @@ opt.max_connections(100)

let db = Database::connect(opt).await?;
```

## Closing Connection

The connection will be automatically closed on drop. To close the connection explicitly, call the `close` method.

```rust
let db = Database::connect(url).await?;

// Closing connection here
db.close().await?;
```
2 changes: 1 addition & 1 deletion SeaORM/docs/03-migration/01-setting-up-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Replace the sample below with your own migration scripts
Expand Down
48 changes: 35 additions & 13 deletions SeaORM/docs/03-migration/02-writing-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
Expand All @@ -46,7 +46,7 @@ mod m20220101_000001_create_table;

pub struct Migrator;

#[async_trait::async_trait]
#[async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Expand Down Expand Up @@ -212,26 +212,48 @@ use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let sql = r#"
CREATE TABLE `cake` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(255) NOT NULL
)"#;
let stmt = Statement::from_string(manager.get_database_backend(), sql.to_owned());
manager.get_connection().execute(stmt).await.map(|_| ())
let db = manager.get_connection();

// Use `execute_unprepared` if the SQL statement doesn't have value bindings
db.execute_unprepared(
"CREATE TABLE `cake` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(255) NOT NULL
)"
)
.await?;

// Construct a `Statement` if the SQL contains value bindings
let stmt = Statement::from_sql_and_values(
manager.get_database_backend(),
r#"INSERT INTO `cake` (`name`) VALUES (?)"#,
["Cheese Cake".into()]
);
db.execute(stmt).await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let sql = "DROP TABLE `cake`";
let stmt = Statement::from_string(manager.get_database_backend(), sql.to_owned());
manager.get_connection().execute(stmt).await.map(|_| ())
manager
.get_connection()
.execute_unprepared("DROP TABLE `cake`")
.await?;

Ok(())
}
}
```

## Atomic Migration

Migration will be executed in Postgres atomically that means migration scripts will be executed inside a transaction. Changes done to the database will be rolled back if the migration failed. However, atomic migration is not supported in MySQL and SQLite.

You can start a transaction inside each migration to perform operations like [seeding sample data](03-migration/04-seeding-data.md#seeding-data-transactionally) for a newly created table.

## Schema first or Entity first?

In the grand scheme of things, we recommend a schema first approach: you write migrations first and then generate entities from a live database.
Expand Down
36 changes: 34 additions & 2 deletions SeaORM/docs/03-migration/04-seeding-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use sea_orm_migration::sea_orm::{entity::*, query::*};

// ...

#[async_trait::async_trait]
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
Expand All @@ -31,7 +31,7 @@ use sea_orm_migration::sea_orm::{entity::*, query::*};

// ...

#[async_trait::async_trait]
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let insert = Query::insert()
Expand All @@ -54,3 +54,35 @@ pub enum Cake {
Name,
}
```

## Seeding Data Transactionally

Starts a transaction and execute SQL inside migration up and down.

```rust
use sea_orm_migration::sea_orm::{entity::*, query::*};

// ...

#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Get the connection and start a transaction
let db = manager.get_connection();
let transaction = db.begin().await?;

// Insert with the transaction connection
cake::ActiveModel {
name: Set("Cheesecake".to_owned()),
..Default::default()
}
.insert(&transaction)
.await?;

// Commit it
transaction.commit().await?;

Ok(())
}
}
```
4 changes: 4 additions & 0 deletions SeaORM/docs/04-generate-entity/01-sea-orm-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@ Command line options:
- `--compact-format`: generate entity file of [compact format](04-generate-entity/02-entity-structure.md) (default: true)
- `--expanded-format`: generate entity file of [expanded format](04-generate-entity/03-expanded-entity-structure.md)
- `--with-serde`: automatically derive serde Serialize / Deserialize traits for the entity (`none`, `serialize`, `deserialize`, `both`) (default: `none`)
- `--serde-skip-deserializing-primary-key`: generate entity model with primary key field labeled as `#[serde(skip_deserializing)]`
- `--serde-skip-hidden-column`: generate entity model with hidden column (column name starts with `_`) field labeled as `#[serde(skip)]`
- `--date-time-crate`: the datetime crate to use for generating entities (`chrono`, `time`) (default: `chrono`)
- `--max-connections`: maximum number of database connections to be initialized in the connection pool (default: `1`)
- `--model-extra-derives`: append extra derive macros to the generated model struct
- `--model-extra-attributes`: append extra attributes to generated model struct

```shell
# Generate entity files of database `bakery` to `entity/src`
Expand Down
11 changes: 10 additions & 1 deletion SeaORM/docs/04-generate-entity/02-entity-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ For the mappings of Rust non-primitive data types. You can check [`entity/prelud
| `DateTime`: chrono::NaiveDateTime <br/>`TimeDateTime`: time::PrimitiveDateTime | DateTime | text | datetime | timestamp |
| `DateTimeLocal`: chrono::DateTime&lt;Local&gt; <br/>`DateTimeUtc`: chrono::DateTime&lt;Utc&gt; | Timestamp | text | timestamp | N/A |
| `DateTimeWithTimeZone`: chrono::DateTime&lt;FixedOffset&gt; <br/>`TimeDateTimeWithTimeZone`: time::OffsetDateTime | TimestampWithTimeZone | text | timestamp | timestamp with time zone |
| `Uuid`: uuid::Uuid | Uuid | text | binary(16) | uuid |
| `Uuid`: uuid::Uuid, uuid::fmt::Braced, uuid::fmt::Hyphenated, uuid::fmt::Simple, uuid::fmt::Urn | Uuid | text | binary(16) | uuid |
| `Json`: serde_json::Value | Json | text | json | json |
| `Decimal`: rust_decimal::Decimal | Decimal | real | decimal | decimal |

Expand Down Expand Up @@ -162,6 +162,15 @@ If you want to ignore a particular model attribute such that it maps to no datab
pub ignore_me: String
```

### Cast Column Type on Select and Save

If you need to select a column as one type but save it into the database as another, you can specify the `select_as` and the `save_as` attributes to perform the casting. A typical use case is selecting a column of type `citext` (case-insensitive text) as `String` in Rust and saving it into the database as `citext`. One should define the model field as below:

```rust
#[sea_orm(select_as = "text", save_as = "citext")]
pub case_insensitive_text: String
```

## Primary Key

Use the `primary_key` attribute to mark a column as the primary key.
Expand Down
45 changes: 41 additions & 4 deletions SeaORM/docs/04-generate-entity/03-expanded-entity-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,30 @@ To specify the datatype of each column, the [`ColumnType`](https://docs.rs/sea-o
ColumnType::String(None).def().default_value("Sam").unique().indexed().nullable()
```

### Cast Column Type on Select and Save

If you need to select a column as one type but save it into the database as another, you can override the `select_as` and the `save_as` methods to perform the casting. A typical use case is selecting a column of type `citext` (case-insensitive text) as `String` in Rust and saving it into the database as `citext`. One should override the `ColumnTrait`'s methods as below:

```rust
use sea_orm::sea_query::{Expr, SimpleExpr, Alias}

impl ColumnTrait for Column {
// Snipped...

/// Cast column expression used in select statement.
fn select_as(&self, expr: Expr) -> SimpleExpr {
Column::CaseInsensitiveText => expr.cast_as(Alias::new("text")),
_ => self.select_enum_as(expr),
}

/// Cast value of a column into the correct type for database storage.
fn save_as(&self, val: Expr) -> SimpleExpr {
Column::CaseInsensitiveText => val.cast_as(Alias::new("citext")),
_ => self.save_enum_as(val),
}
}
```

## Primary Key

An enum representing the primary key of this table. A composite key is represented by an enum with multiple variants.
Expand Down Expand Up @@ -153,6 +177,7 @@ pub struct ActiveModel {
Handlers for different triggered actions on an `ActiveModel`. For example, you can perform custom validation logic, preventing a model from saving into database. You can abort an action even after it is done, if you are inside a transaction.

```rust
#[async_trait]
impl ActiveModelBehavior for ActiveModel {
/// Create a new ActiveModel with default values. Also used by `Default::default()`.
fn new() -> Self {
Expand All @@ -163,7 +188,10 @@ impl ActiveModelBehavior for ActiveModel {
}

/// Will be triggered before insert / update
fn before_save(self, insert: bool) -> Result<Self, DbErr> {
async fn before_save<C>(self, db: &C, insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
if self.price.as_ref() <= &0.0 {
Err(DbErr::Custom(format!(
"[before_save] Invalid Price, insert: {}",
Expand All @@ -175,17 +203,26 @@ impl ActiveModelBehavior for ActiveModel {
}

/// Will be triggered after insert / update
fn after_save(model: Model, insert: bool) -> Result<Model, DbErr> {
async fn after_save<C>(model: Model, db: &C, insert: bool) -> Result<Model, DbErr>
where
C: ConnectionTrait,
{
Ok(model)
}

/// Will be triggered before delete
fn before_delete(self) -> Result<Self, DbErr> {
async fn before_delete<C>(self, db: &C) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
Ok(self)
}

/// Will be triggered after delete
fn after_delete(self) -> Result<Self, DbErr> {
async fn after_delete<C>(self, db: &C) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
Ok(self)
}
}
Expand Down
10 changes: 8 additions & 2 deletions SeaORM/docs/05-basic-crud/03-insert.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ Before diving into SeaORM insert we have to introduce `ActiveValue` and `ActiveM
A wrapper struct to capture the changes made to `ActiveModel` attributes.

```rust
use sea_orm::ActiveValue::NotSet;
use sea_orm::ActiveValue::{Set, NotSet, Unchanged};

// Set value
let _: ActiveValue<i32> = Set(10);

// NotSet value
let _: ActiveValue<i32> = NotSet;

// An `Unchanged` value
let v: ActiveValue<i32> = Unchanged(10);

// Convert `Unchanged` active value as `Set`
assert!(v.reset(), Set(10));
```

## Model & ActiveModel
Expand Down Expand Up @@ -177,7 +183,7 @@ let orange = fruit::ActiveModel {
..Default::default()
};

let res: InsertResult = Fruit::insert_many(vec![apple, orange]).exec(db).await?;
let res: InsertResult = Fruit::insert_many([apple, orange]).exec(db).await?;
assert_eq!(res.last_insert_id, 30)
```

Expand Down
20 changes: 19 additions & 1 deletion SeaORM/docs/05-basic-crud/04-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,25 @@ let mut pear: fruit::ActiveModel = pear.unwrap().into();
// Update name attribute
pear.name = Set("Sweet pear".to_owned());

// Update corresponding row in database using primary key value
// SQL: `UPDATE "fruit" SET "name" = 'Sweet pear' WHERE "id" = 28`
let pear: fruit::Model = pear.update(db).await?;
```

To update all attributes, you can convert `Unchanged` into `Set`.

```rust
// Into ActiveModel
let mut pear: fruit::ActiveModel = pear.into();

// Update name attribute
pear.name = Set("Sweet pear".to_owned());

// Set a specific attribute as "dirty" (force update)
pear.reset(fruit::Column::CakeId);
// Or, set all attributes as "dirty" (force update)
pear.reset_all();

// SQL: `UPDATE "fruit" SET "name" = 'Sweet pear', "cake_id" = 10 WHERE "id" = 28`
let pear: fruit::Model = pear.update(db).await?;
```

Expand Down
2 changes: 1 addition & 1 deletion SeaORM/docs/05-basic-crud/07-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ let cakes: Vec<serde_json::Value> = Cake::find()

assert_eq!(
cakes,
vec![
[
serde_json::json!({
"id": 2,
"name": "Chocolate Forest"
Expand Down
Loading

0 comments on commit 877caf8

Please sign in to comment.