sea-orm 源码解析
全卷只对着一组版本写:
| crate | 版本 | tag | commit | 日期 |
|---|---|---|---|---|
sea-orm(及 sea-orm-macros、sea-orm-migration、sea-orm-cli、sea-orm-codegen) | 2.0.4 | 2.0.4 | db40a19 | 2026-09-27 |
| sea-query | 1.0.2 | 1.0.2 | f2d5e04 | 2026-08-12 |
| sea-query-sqlx | 0.9.1 | sea-query 仓库的 sea-query-sqlx/ 目录,源码和 1.0.2 tag 一致 | ||
| sqlx | 0.9.0 | 见 sqlx 卷 |
本站跑的是 sea-orm 1.1.20(sea-query 0.32.7、sqlx 0.8.6),实体是 1.x 的展开写法。1.1.20 到 2.0.4 是大版本,讲到不同的地方会标出来,哪些改动影响本站集中在 10 在本站中。只讲 Postgres(sqlx-postgres),MySQL、SQLite 只在对比时提到。
sea-orm 给自己的描述是「An async & dynamic ORM for Rust」:执行建立在 sqlx 上,查询是运行期构造的值。它自己不写 SQL 字符串,而是把每个操作翻译成 sea-query 的语句树,sea-query 按方言把树写成 SQL 和一组参数,再经 sea-query-sqlx 交给 sqlx 执行。所以这一卷讲两个库:sea-orm 是上层的实体和执行,sea-query 是中间那层语句树;最下层 sqlx 的连接池、协议、类型编码,链到上一卷,不重讲。
全卷沿三条线展开:
- 查询主线(一次
find_by_id(..).one(&db)):EntityTrait::find_by_id造一个Select<E>,里面是 sea-query 的SelectStatement,列出实体的每一列、按主键加WHERE;.one(&db)换成Selector,先limit(1),再ConnectionTrait::query_one:到这里才按后端选PostgresQueryBuilder,写出SELECT "cake"."id", ... WHERE "cake"."id" = $1 LIMIT $2和参数;驱动里sqlx::query_with(..),每条语句单独pool.acquire()一次,fetch_one拿回的PgRow包成QueryResult,由宏生成的FromQueryResult按列名解码成Model。 - 写入主线(一次
ActiveModel::insert/update/save):ActiveModel的每个字段是ActiveValue的三态之一:Set、Unchanged、NotSet。insert只放Set的列,update只SET被Set的列、按主键WHERE,Postgres 上都用RETURNING一条语句拿回整行;save看主键有没有NotSet决定走哪条;前后各有一个ActiveModelBehavior钩子。2.0 的ActiveModelEx把一组有外键关系的对象按依赖顺序逐个写。 - 定义主线(一个实体从宏到 trait):用户只写一个
Modelstruct;DeriveEntityModel从它生成Entity(单元结构体,类型就是表)、Column枚举、PrimaryKey枚举、ActiveModel和一串 trait 实现;2.0 的#[sea_orm::model]再生成ModelEx、ActiveModelEx、EntityLoader、强类型列,并把HasOne/HasMany/BelongsTo字段变成关系。关系在运行时就是一个RelationDef值,join、find_related、Loader 都只读它。
三条线每一步对应的源码和页面,见怎么读这一卷。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> entity[02 实体] overview --> sq["03 sea-query"] entity --> select[04 查询] sq --> select entity --> write[05 写入] sq --> write select --> conn["06 连接与事务"] write --> conn conn --> mig["07 迁移与 schema"] mig --> design[08 设计取舍] design --> pit[09 常见坑] pit --> site[10 在本站中] site --> hands[11 动手]
实验环境
关于行为的说法(发出了什么 SQL、几条语句、是 join 还是第二条查询、有没有 SAVEPOINT、报什么错、rustc 说了什么)都配一个小程序和它的真实输出,平台是 M2 Max、macOS、rustc 1.99.0、Postgres 16.14(本机容器)。看实际发出的 SQL 用 sqlx 的语句日志,要看参数字节和协议消息就用 sqlx 卷那个 tokio TCP 代理,做法见一次 find 的全过程;四种看 SQL 的办法各看到什么,见怎么读这一卷。with-json 和 from_json 链到 serde 卷,tracing-spans 链到 tracing 卷。
各部分
- 00 导读:锁定的版本和本站的差距,三层结构,三条主线每一步落在哪个函数,阅读路线、四种看 SQL 的办法;各 crate 的分工、
src/的目录、feature 体系、sea-orm-sync怎么从异步源码生成。 - 01 全景:sea-orm 和 sqlx、diesel 在设计上的分界,编译期能抓到什么;用抓包把一次
find_by_id(1).one(&db)完整走一遍,LIMIT是绑定参数,每条语句各借一次连接。 - 02 实体:本卷重点。
EntityTrait一族 trait,DeriveEntityModel和 dense 格式的宏展开,列与值、TryGetable,关系与RelationDef,DeriveActiveEnum和DeriveValueType。 - 03 sea-query:语句树的结构,
QueryBuilder怎么写出 SQL、哪些值变成参数,sea-query-sqlx怎么把Value绑进 sqlx。 - 04 查询:
Select与过滤、Selector,join 与find_related,Loader 和 2.0 的 Entity Loader,部分列与自定义结果,Paginator与Cursor。 - 05 写入:
ActiveValue三态,insert/insert_many/on_conflict,update与delete,save与ActiveModelBehavior,嵌套写入ActiveModelEx。 - 06 连接与事务:
DatabaseConnection与ConnectOptions,事务与保存点,DbErr,MockDatabase与 Proxy。 - 07 迁移与 schema:
sea-orm-migration,SchemaManager与从实体建表,2.0 的 schema sync。 - 08 设计取舍:把各章的取舍汇总到一起,每条链回有证据的页面。
- 09 常见坑:用报错原文做标题,每个都先跑出最小复现。
- 10 在本站中:本站怎么用 sea-orm 1.1.20,生成的 SQL 有没有多余;1.1.20 到 2.0.4 的每条破坏性改动会不会影响本站。
- 11 动手:不依赖 sea-orm 和 sea-query,在 sqlx 0.9 上写一个迷你 ORM:
Entitytrait、Select构造器、三态ActiveValue和只写改过的列的update。