Skip to content

Commit 5db9260

Browse files
committed
[SeaORM] Docs
1 parent 929eeb9 commit 5db9260

File tree

2 files changed

+230
-13
lines changed

2 files changed

+230
-13
lines changed
Lines changed: 229 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,238 @@
11
# Entity First Workfllow
22

3-
SeaORM also supports an Entity first approach: your entities are the source of truth, and you run run DDL on the database to match your entity definition.
4-
53
:::tip Since `2.0.0`
6-
The following requires the `schema-sync` feature flag.
74
:::
85

6+
## What's Entity first?
7+
8+
SeaORM used to adopt a schema‑first approach: meaning you design database tables and write migration scripts first, then generate entities from that schema.
9+
10+
Entity‑first flips the flow: you hand-write the entity files, and let SeaORM generates the tables and foreign keys for you.
11+
12+
All you have to do is to add the following to your [`main.rs`](https://github.com/SeaQL/sea-orm/blob/master/examples/quickstart/src/main.rs) right after creating the database connection:
13+
14+
```rust
15+
let db = &Database::connect(db_url).await?;
16+
// synchronizes database schema with entity definitions
17+
db.get_schema_registry("my_crate::entity::*").sync(db).await?;
18+
```
19+
20+
This requires two feature flags `schema-sync` and `entity-registry`, and we're going to explain what they do.
21+
22+
## Entity Registry
23+
24+
The above function `get_schema_registry` unfolds into the following:
25+
926
```rust
10-
// it doesn't matter which order you register entities.
11-
// SeaORM figures out the foreign key dependencies and
12-
// creates the tables in the right order along with foreign keys
1327
db.get_schema_builder()
14-
.register(cake::Entity)
15-
.register(cake_filling::Entity)
16-
.register(filling::Entity)
17-
.sync(db) // synchronize the schema with database,
18-
// will create missing tables, columns, indexes, foreign keys.
19-
// this operation is addition only, will not drop anything.
28+
.register(comment::Entity)
29+
.register(post::Entity)
30+
.register(profile::Entity)
31+
.register(user::Entity)
32+
.sync(db)
2033
.await?;
2134
```
35+
36+
You might be wondering: how can SeaORM recognize my entities when, at compile time, the SeaORM crate itself has no knowledge of them?
37+
38+
Rest assured, there's no source‑file scanning or other hacks involved - this is powered by the brilliant [`inventory`](https://docs.rs/inventory/latest/inventory/) crate. The `inventory` crate works by registering items (called plugins) into linker-collected sections.
39+
40+
At compile-time, each `Entity` module registers itself to the global `inventory` along with their module paths and some metadata. On runtime, SeaORM then filters the Entities you requested and construct a [`SchemaBuilder`](https://docs.rs/sea-orm/2.0.0-rc.15/sea_orm/schema/struct.SchemaBuilder.html).
41+
42+
The `EntityRegistry` is completely optional and just adds extra convenience, it's perfectly fine for you to `register` Entities manually like above.
43+
44+
## Resolving Entity Relations
45+
46+
If you remember from the previous post, you'll notice that `comment` has a foreign key referencing `post`. Since SQLite doesn't allow adding foreign keys after the fact, the `post` table must be created before the `comment` table.
47+
48+
This is where SeaORM shines: it automatically builds a dependency graph from your entities and determines the correct topological order to create the tables, so you don't have to keep track of them in your head.
49+
50+
## Schema Sync in Action
51+
52+
The second feature, `schema-sync`, compares the in‑memory entity definitions with the live database schema, detects missing tables, columns, and keys, and creates them idempotently - no matter how many times you run `sync`, the schema converges to the same state.
53+
54+
Let's walk through the different scenarios:
55+
56+
### Adding Table
57+
58+
Let's say you added a new Entity under `mod.rs`
59+
60+
```rust title="entity/mod.rs"
61+
//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.14
62+
63+
pub mod prelude;
64+
65+
pub mod post;
66+
pub mod upvote; // ⬅ new entity module
67+
..
68+
```
69+
70+
The next time you `cargo run`, you'll see the following:
71+
72+
```sh
73+
CREATE TABLE "upvote" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, .. )
74+
```
75+
76+
This will create the table along with any foreign keys.
77+
78+
### Adding Columns
79+
80+
```rust title="entity/profile.rs"
81+
use sea_orm::entity::prelude::*;
82+
83+
#[sea_orm::model]
84+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
85+
#[sea_orm(table_name = "profile")]
86+
pub struct Model {
87+
#[sea_orm(primary_key)]
88+
pub id: i32,
89+
pub picture: String,
90+
pub date_of_birth: Option<DateTimeUtc>, // ⬅ new column
91+
..
92+
}
93+
94+
impl ActiveModelBehavior for ActiveModel {}
95+
```
96+
97+
The next time you `cargo run`, you'll see the following:
98+
99+
```sh
100+
ALTER TABLE "profile" ADD COLUMN "date_of_birth" timestamp with time zone
101+
```
102+
103+
How about adding a non-nullable column? You can set a `default_value` or `default_expr`:
104+
105+
```rust
106+
#[sea_orm(default_value = 0)]
107+
pub post_count: i32,
108+
109+
// this doesn't work in SQLite
110+
#[sea_orm(default_expr = "Expr::current_timestamp()")]
111+
pub updated_at: DateTimeUtc,
112+
```
113+
114+
### Rename Column
115+
116+
If you only want to rename the field name in code, you can simply remap the column name:
117+
118+
```rust
119+
pub struct Model {
120+
..
121+
#[sea_orm(column_name = "date_of_birth")]
122+
pub dob: Option<DateTimeUtc>, // ⬅ renamed for brevity
123+
}
124+
```
125+
126+
This doesn't involve any schema change.
127+
128+
If you want to actually rename the column, then you have to add a special attribute. Note that you can't simply change the field name, as this will be recognized as adding a new column.
129+
130+
```rust
131+
pub struct Model {
132+
..
133+
#[sea_orm(renamed_from = "date_of_birth")] // ⬅ special annotation
134+
pub dob: Option<DateTimeUtc>,
135+
}
136+
```
137+
138+
The next time you `cargo run`, you'll see the following:
139+
140+
```sh
141+
ALTER TABLE "profile" RENAME COLUMN "date_of_birth" TO "dob"
142+
```
143+
144+
Nice, isn't it?
145+
146+
### Add Foreign Key
147+
148+
Let's create a new table with a foreign key:
149+
150+
```rust title="entity/upvote.rs"
151+
use sea_orm::entity::prelude::*;
152+
153+
#[sea_orm::model]
154+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
155+
#[sea_orm(table_name = "upvote")]
156+
pub struct Model {
157+
#[sea_orm(primary_key, auto_increment = false)]
158+
pub post_id: i32,
159+
#[sea_orm(belongs_to, from = "post_id", to = "id")]
160+
pub post: HasOne<super::post::Entity>,
161+
..
162+
}
163+
164+
impl ActiveModelBehavior for ActiveModel {}
165+
```
166+
167+
The next time you `cargo run`, you'll see the following:
168+
169+
```sh
170+
CREATE TABLE "upvote" (
171+
"post_id" integer NOT NULL PRIMARY KEY,
172+
..
173+
FOREIGN KEY ("post_id") REFERENCES "post" ("id")
174+
)
175+
```
176+
177+
If however, the `post` relation is added after the table has been created, then the foreign key couldn't be created for SQLite. Relational queries would still work, but functions completely client-side.
178+
179+
### Add Unique Key
180+
181+
Now, let's say we've forgotten to add a unique constraint on user name:
182+
183+
```rust title="entity/user.rs"
184+
use sea_orm::entity::prelude::*;
185+
186+
#[sea_orm::model]
187+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
188+
#[sea_orm(table_name = "user")]
189+
pub struct Model {
190+
#[sea_orm(primary_key)]
191+
pub id: i32,
192+
#[sea_orm(unique)] // ⬅ add unique key
193+
pub name: String,
194+
#[sea_orm(unique)]
195+
pub email: String,
196+
..
197+
}
198+
```
199+
200+
The next time you `cargo run`, you'll see the following:
201+
202+
```sh
203+
CREATE UNIQUE INDEX "idx-user-name" ON "user" ("name")
204+
```
205+
206+
As mentioned in the previous blog post, you'll also get a shorthand method generated on the Entity:
207+
208+
```rust
209+
user::Entity::find_by_name("Bob")..
210+
```
211+
212+
### Remove Unique Key
213+
214+
Well, you've changed your mind and want to remove the unique constraint on user name:
215+
216+
```rust
217+
pub struct Model {
218+
#[sea_orm(primary_key)]
219+
pub id: i32,
220+
// no annotation
221+
pub name: String,
222+
#[sea_orm(unique)]
223+
pub email: String,
224+
..
225+
}
226+
```
227+
228+
The next time you `cargo run`, you'll see the following:
229+
230+
```sh
231+
DROP INDEX "idx-user-name"
232+
```
233+
234+
## Footnotes
235+
236+
Note that in general schema sync would not attempt to do any destructive actions, so meaning no `DROP` on tables, columns and foreign keys. Dropping index is an exception here.
237+
238+
Every time the application starts, a full schema discovery is performed. This may not be desirable in production, so `sync` is gated behind a feature flag `schema-sync` that can be turned off based on build profile.

SeaORM/docs/09-schema-statement/03-create-index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub struct Model {
1010
pub id: i32,
1111
#[sea_orm(indexed)]
1212
pub index1_attr: i32,
13-
#[sea_orm(unique, indexed)]
13+
#[sea_orm(unique)]
1414
pub index2_attr: i32,
1515
#[sea_orm(unique_key = "my_unique")]
1616
pub unique_key_a: String,

0 commit comments

Comments
 (0)