insert
主线位置:写入主线的 insert 分支。
am.insert(&db)是ActiveModelTrait::insert:before_save(self, db, true)→Entity::insert(am)造一个Insert<A>(里面是 sea-query 的InsertStatement)→exec_with_returning(db)→after_save。这一页讲中间两步:Insert怎么从ActiveModel挑列,几个exec各加什么RETURNING、返回什么;insert_many怎么对齐各行的列;on_conflict和冲突时的结果。钩子在 save 与 ActiveModelBehavior。
flowchart TB am[ActiveModel] -->|"Insert::one"| ins["Insert A"] ams["多个 ActiveModel"] -->|"InsertMany::many"| many["InsertMany A"] many -->|空列表| empty["不发 SQL"] many --> ins ins -->|exec| pk["RETURNING 主键"] ins -->|exec_with_returning| all["RETURNING 全部列"] ins -->|exec_without_returning| cnt["rows_affected"] ins -->|try_insert 等| tri["TryInsert A"] tri --> res["Inserted / Conflicted / Empty"]
实验沿用上一页的 item 表(id serial primary key、sku text not null unique、name text not null、qty int not null default 0、note text),每个程序开头重建表并插入三行 a1、b2、c3。SQL 取自 sqlx 的语句日志(sqlx=info,带 $N、不带值),要看值时另外 build(DbBackend::Postgres) 打印。平台 M2 Max、macOS、rustc 1.99.0、Postgres 16.14,sea-orm 2.0.4;对比用的 1.1.20 在单独的 crate 里。
Insert::one:有值的列都写
Insert::one 逐列 take,Set 和 Unchanged 都进列表:
pub fn one<M>(m: M) -> Self
where
M: IntoActiveModel<A>,
{
let mut query = InsertStatement::new();
query
.into_table(A::Entity::default().table_ref())
.or_default_values();
let mut am: A = m.into_active_model();
let primary_key =
if !<<A::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::auto_increment() {
am.get_primary_key_value()
} else {
None
};
let mut columns = Vec::new();
let mut values = Vec::new();
for col in <A::Entity as EntityTrait>::Column::iter() {
let av = am.take(col);
match av {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => {
columns.push(col);
values.push(col.save_as(Expr::val(value)));
}
ActiveValue::NotSet => {}
}
}
query.columns(columns);
query.values_panic(values);
// ...
}
三处值得看。NotSet 的列不出现在 SQL 里,Postgres 用列的默认值(serial 的 nextval、default 0)。primary_key 只在主键不是自增时记下,给不支持 RETURNING 的 MySQL 用来充当 last_insert_id;Postgres 上它不起作用。or_default_values() 是给「一列都没有」准备的:sea-query 的 insert_default_keyword 在 Postgres 上写 VALUES (DEFAULT),下面的实验里会看到。
参数 M: IntoActiveModel<A>,所以 Insert::one 也收 Model:一个 Model 转成全 Unchanged,所有列(包括主键)都会写进去。
四个 exec,三种 RETURNING
Insert<A> 的执行方法在 executor/insert.rs,区别在加不加 RETURNING、加哪几列:
| 方法 | RETURNING | 返回 |
|---|---|---|
exec | 主键列 | InsertResult { last_insert_id } |
exec_with_returning | 全部列 | Model |
exec_without_returning | 无 | u64(rows_affected) |
exec_with_returning_keys / exec_with_returning_many | 主键 / 全部列 | Vec |
exec 在 exec_insert 里按后端分三路:
let last_insert_id = match (primary_key, db.support_returning()) {
(_, true) => {
let mut rows = db.query_all(&statement).await?;
let row = match rows.pop() {
Some(row) => row,
None => return Err(DbErr::RecordNotInserted),
};
let cols = PrimaryKey::<A>::iter()
.map(|col| col.to_string())
.collect::<Vec<_>>();
row.try_get_many("", cols.as_ref())
.map_err(|_| DbErr::UnpackInsertId)?
}
(Some(value_tuple), false) => {
let res = db.execute(&statement).await?;
if res.rows_affected() == 0 {
return Err(DbErr::RecordNotInserted);
}
FromValueTuple::from_value_tuple(value_tuple)
}
(None, false) => {
let res = db.execute(&statement).await?;
// ...
let last_insert_id = res.last_insert_id();
// ...
}
};
Postgres 走第一路:last_insert_id 就是 RETURNING 回来的最后一行的主键;没有返回行就是 RecordNotInserted(None of the records are inserted),这是后面 DO NOTHING 的伏笔。exec_insert_with_returning 则把每一列的 select_as(into_returning_expr(..)) 放进 RETURNING,用 ReturningSelector::one 读回一个 Model;不支持 RETURNING 的后端先 exec_insert 再按 last_insert_id find_by_id,那是两条语句。
实验:每个 exec 发出什么
src/bin/insert_one.rs,pear() 是一个只设了 sku 和 name 的 ActiveModel:
let pear = || item::ActiveModel { sku: Set("p1".into()), name: Set("pear".into()), ..Default::default() };
let stmt = item::Entity::insert(pear()).build(DbBackend::Postgres);
println!("build: {} {:?}", stmt.sql, stmt.values.map(|v| v.0).unwrap_or_default());
println!("--- ActiveModel::insert");
let m = pear().insert(&db).await?;
println!("=> {m:?}");
println!("--- Entity::insert(..).exec");
let mut am = pear();
am.sku = Set("p2".into());
let r = item::Entity::insert(am).exec(&db).await?;
println!("=> last_insert_id {}", r.last_insert_id);
println!("--- exec_without_returning");
// ... sku 换成 p3,调 exec_without_returning
println!("--- nothing Set");
let r = item::Entity::insert(<item::ActiveModel as Default>::default()).exec(&db).await;
println!("=> {r:?}");
println!("--- insert a Model read back (all Unchanged)");
let a1 = item::Entity::find_by_id(1).one(&db).await?.unwrap();
let r = a1.into_active_model().insert(&db).await;
// ... 打印 Ok 或 Err 的 Display
build: INSERT INTO "item" ("sku", "name") VALUES ($1, $2) [String(Some("p1")), String(Some("pear"))]
--- ActiveModel::insert
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=1 rows_returned=1 elapsed=2.623625ms elapsed_secs=0.002623625
=> Model { id: 4, sku: "p1", name: "pear", qty: 0, note: None }
--- Entity::insert(..).exec
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) RETURNING \"id\"\n" rows_affected=1 rows_returned=1 elapsed=927.667µs elapsed_secs=0.000927667
=> last_insert_id 5
--- exec_without_returning
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2)\n" rows_affected=1 rows_returned=0 elapsed=819.166µs elapsed_secs=0.000819166
=> 1
--- nothing Set
INFO sqlx::query: summary="INSERT INTO \"item\" VALUES …" db.statement="\n\nINSERT INTO \"item\" VALUES (DEFAULT) RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=611.417µs elapsed_secs=0.000611417
=> Err(Query(SqlxError(Database(PgDatabaseError { severity: Error, code: "23502", message: "null value in column \"sku\" of relation \"item\" violates not-null constraint", detail: Some("Failing row contains (7, null, null, 0, null)."), hint: None, position: None, where: None, schema: Some("so_write1"), table: Some("item"), column: Some("sku"), data_type: None, constraint: None, file: Some("execMain.c"), line: Some(2057), routine: Some("ExecConstraints") }))))
--- insert a Model read back (all Unchanged)
INFO sqlx::query: summary="SELECT \"item\".\"id\", \"item\".\"sku\", \"item\".\"name\", …" db.statement="\n\nSELECT \"item\".\"id\", \"item\".\"sku\", \"item\".\"name\", \"item\".\"qty\", \"item\".\"note\" FROM \"item\" WHERE \"item\".\"id\" = $1 LIMIT $2\n" rows_affected=1 rows_returned=1 elapsed=906.125µs elapsed_secs=0.000906125
INFO sqlx::query: summary="INSERT INTO \"item\" (\"id\", …" db.statement="\n\nINSERT INTO \"item\" (\"id\", \"sku\", \"name\", \"qty\", \"note\") VALUES ($1, $2, $3, $4, $5) RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=765.084µs elapsed_secs=0.000765084
=> Err Query Error: error returned from database: duplicate key value violates unique constraint "item_pkey" at line 666
每次写入都是一条语句。build 的结果里没有 RETURNING,它是执行方法加上去的;ActiveModel::insert 用的是 exec_with_returning,所以 qty 的默认值 0 和数据库分配的 id 都随这一条语句回来。一列都没 Set 时发出的是 VALUES (DEFAULT),这张表的 sku 没有默认值,报 23502。最后一段是常见的误用:读出来的 Model 全是 Unchanged,insert 把 id 也写进去,撞上主键(错误末尾的 at line 666 是 sqlx 0.9 的 Display 加的服务端源码行号,见 sqlx 卷的错误)。想「复制一行」得先把主键置成 NotSet。
insert_many:列取并集,缺的补 NULL
InsertMany::many 要把每行可能不同的列集合拼进一条 INSERT ... VALUES (..), (..),而 SQL 要求每行的列一样:
for model in models.into_iter() {
let mut am: A = model.into_active_model();
// ...
let mut values = Vec::with_capacity(columns.len());
for (idx, col) in <A::Entity as EntityTrait>::Column::iter().enumerate() {
let av = am.take(col);
match av {
ActiveValue::Set(value) | ActiveValue::Unchanged(value) => {
columns[idx] = Some(col); // mark the column as used
null_value[idx] = Some(value.as_null()); // store the null value with the correct type
values.push(col.save_as(Expr::val(value))); // same as add() above
}
ActiveValue::NotSet => {
values.push(SimpleExpr::Keyword(Keyword::Null)); // indicate a missing value
}
}
}
all_values.push(values);
}
let empty = all_values.is_empty();
// ...
for values in all_values {
// since we've aligned the column set, this never panics
query.values_panic(values.into_iter().enumerate().filter_map(|(i, v)| {
if columns[i].is_some() {
// only if the column is used
if !matches!(v, SimpleExpr::Keyword(Keyword::Null)) {
// use the value expression
Some(v)
} else {
// use null as standin, which must be Some
null_value[i].clone().map(SimpleExpr::Value)
}
} else {
None
}
}));
}
任何一行设了的列都进列表;某一行没设的列,填进去的是 value.as_null(),一个带类型的 NULL 参数(Int(None) 这样的),不是 SQL 的 DEFAULT。所以只要有一行设了某列,其他行这一列就从「交给默认值」变成了「写 NULL」。src/bin/insert_many.rs 让两行里只有第二行设 qty:
let stmt = item::Entity::insert_many([am("m5", None), am("m6", Some(9))]).build(DbBackend::Postgres);
println!("values {:?}", stmt.values.map(|v| v.0).unwrap_or_default());
let r = item::Entity::insert_many([am("m5", None), am("m6", Some(9))]).exec(&db).await;
--- mixed: only the second sets qty
values [String(Some("m5")), String(Some("m5")), Int(None), String(Some("m6")), String(Some("m6")), Int(Some(9))]
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\", \"qty\") VALUES ($1, $2, $3), ($4, $5, $6) RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=644.5µs elapsed_secs=0.0006445
=> Err Query Error: error returned from database: null value in column "qty" of relation "item" violates not-null constraint at line 2057
第一行的 qty 绑的是 Int(None),qty 是 not null default 0,整条语句失败。单独插第一行(Insert::one)不会出这个错,因为 qty 根本不在列表里。每行设同样的列,或者在 Rust 里补上默认值,才能避开。
列一样时,exec 和 exec_with_returning 的结果:
--- same columns, exec
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2), ($3, $4) RETURNING \"id\"\n" rows_affected=2 rows_returned=2 elapsed=1.949666ms elapsed_secs=0.001949666
=> last_insert_id Some(5)
--- same columns, exec_with_returning
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2), ($3, $4) RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=2 rows_returned=2 elapsed=1.217791ms elapsed_secs=0.001217791
=> [Model { id: 6, sku: "m3", name: "m3", qty: 0, note: None }, Model { id: 7, sku: "m4", name: "m4", qty: 0, note: None }]
InsertMany::exec 返回 InsertManyResult { last_insert_id: Option<..> },Some(5) 是最后一行的主键(exec_insert 里的 rows.pop()),要全部主键用 exec_with_returning_keys。InsertMany::exec_with_returning 返回 Vec<Model>,它在内部调的是 exec_with_returning_many。
空列表
InsertMany 比 Insert 多一个 empty 字段,每个 exec 开头先看它:
pub async fn exec<C>(self, db: &C) -> Result<InsertManyResult<A>, DbErr>
where
C: ConnectionTrait,
{
if self.empty {
return Ok(InsertManyResult {
last_insert_id: None,
});
}
let res = self.into_one().exec(db).await;
// ...
}
exec_with_returning 返回 vec![],exec_without_returning 返回 0,都不碰数据库:
--- empty
exec => last_insert_id None
exec_with_returning => []
exec_without_returning => 0
--- end
--- empty 和 --- end 之间没有一行 sqlx 日志。
insert_many([]) 会插入一行默认值1.1.20 里 insert_many 和 insert 共用一个 Insert 结构,Insert::many 从 Insert::new() 开始,而 new() 里有 or_default_values()。列表为空时一列也没有,生成的就是 INSERT INTO "item" VALUES (DEFAULT) RETURNING "id"(exp-seaorm-write1-old 的 write_old,1.1.20):
--- insert_many([]).exec
INFO sqlx::query: summary="INSERT INTO \"item\" VALUES …" db.statement="\n\nINSERT INTO \"item\" VALUES (DEFAULT) RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=1.019667ms elapsed_secs=0.001019667
=> Err(Query(SqlxError(Database(PgDatabaseError { severity: Error, code: "23502", message: "null value in column \"sku\" of relation \"item\" violates not-null constraint", detail: Some("Failing row contains (4, null, null, 0, null)."), hint: None, position: None, where: None, schema: Some("so_write1"), table: Some("item"), column: Some("sku"), data_type: None, constraint: None, file: Some("execMain.c"), line: Some(2057), routine: Some("ExecConstraints") }))))
...
--- insert_many([]).on_empty_do_nothing().exec
=> Ok(Empty)
item 有不带默认值的 not null 列,所以只是报错。换一张每列都有默认值的表(counter (id serial primary key, n int not null default 0),empty_old),同样的调用在 1.1.20 下真的插进一行:
insert_many([]).exec => Ok(InsertResult { last_insert_id: 1 })
rows now: [Model { id: 1, n: 0 }]
2.0.4 下(empty_new)什么都不发,表还是空的:
insert_many([]).exec => Ok(InsertManyResult { last_insert_id: None })
rows now: []
这个问题最早是 issue #873:「Sometimes, as a result of the mapping/filtering, the resulting iterator may be empty. In this case, the insert_many operation fails; I'd expect this to succeed and the database not being hit at all as there is nothing to do.」,日志里正是 VALUES (DEFAULT) RETURNING "id"。PR #1708(0.12.0-rc.3)加了 TryInsert 和要手动调用的 on_empty_do_nothing(),不调用时行为不变。2.0 的 PR #2628(「Insert many」)把 insert_many 拆成单独的 InsertMany,CHANGELOG 写着「removed APIs that can panic」「on empty iterator, None or vec![] is returned on exec operations」,last_insert_id 也因此变成 Option。仓库里最早包含这个提交的 tag 是 2.0.0-rc.28(更早的候选版本没有 tag)。本站还在 1.1.20 上:一个可能为空的列表交给 insert_many 之前要自己判断,或者加 on_empty_do_nothing()。
on_conflict:upsert 与 DO NOTHING
Insert::on_conflict(OnConflict) 把 sea-query 的 OnConflict 原样放进语句,OnConflict::column(..) 指定冲突目标,update_column(s) 生成 DO UPDATE SET col = "excluded".col,do_nothing() 生成 DO NOTHING。upsert 配 exec_with_returning,冲突时拿回的是更新后的那一行(src/bin/on_conflict.rs):
let upsert = OnConflict::column(item::Column::Sku).update_column(item::Column::Name).to_owned();
let m = item::Entity::insert(am("a1", "apple v2")).on_conflict(upsert.clone()).exec_with_returning(&db).await?;
--- upsert, exec_with_returning
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO UPDATE SET \"name\" = \"excluded\".\"name\" RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=1 rows_returned=1 elapsed=2.035542ms elapsed_secs=0.002035542
=> Model { id: 1, sku: "a1", name: "apple v2", qty: 3, note: None }
DO NOTHING 的麻烦在于冲突时 Postgres 不返回行,RETURNING 也就是空的。Insert::exec 把「没有返回行」当成 RecordNotInserted,于是一次「按设计什么也不做」的插入变成了一个错误。为此有 TryInsert:它包着同一个 Insert,执行方法把这个错误翻译成 TryInsertResult::Conflicted:
pub async fn exec<C>(self, db: &C) -> Result<TryInsertResult<InsertResult<A>>, DbErr>
where
C: ConnectionTrait,
{
if self.empty {
return Ok(TryInsertResult::Empty);
}
let res = self.insert_struct.exec(db).await;
match res {
Ok(res) => Ok(TryInsertResult::Inserted(res)),
Err(DbErr::RecordNotInserted) => Ok(TryInsertResult::Conflicted),
Err(err) => Err(err),
}
}
拿到 TryInsert 的办法有几个:Insert::try_insert()、on_conflict_do_nothing_on(cols)(冲突目标是 cols)、on_conflict_do_nothing()(冲突目标是主键),InsertMany 上也有同名的方法。2.0 把 Insert::do_nothing()、on_empty_do_nothing() 标成了 deprecated。对已有的 a1 再插一次,各种组合的结果:
--- do_nothing via on_conflict(..), exec
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=563.958µs elapsed_secs=0.000563958
=> Err(RecordNotInserted)
Display: None of the records are inserted
--- on_conflict_do_nothing_on([Sku]), exec
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=181.5µs elapsed_secs=0.0001815
=> Ok(Conflicted)
--- same, exec_without_returning
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING\n" rows_affected=0 rows_returned=0 elapsed=330.541µs elapsed_secs=0.000330541
=> Ok(Inserted(0))
--- insert_many, one conflicts, on_conflict_do_nothing_on, exec
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2), ($3, $4) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\"\n" rows_affected=1 rows_returned=1 elapsed=1.134375ms elapsed_secs=0.001134375
=> Ok(Inserted(InsertResult { last_insert_id: 9 }))
--- insert_many, all conflict, exec
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2), ($3, $4) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=432.292µs elapsed_secs=0.000432292
=> Ok(Conflicted)
--- insert_many, all conflict, exec_with_returning_many
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2), ($3, $4) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=743.75µs elapsed_secs=0.00074375
=> Ok(Inserted([]))
--- on_conflict_do_nothing() (primary key), sku clash
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"id\") DO NOTHING RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=737.375µs elapsed_secs=0.000737375
=> Err Query Error: error returned from database: duplicate key value violates unique constraint "item_sku_key" at line 666
SQL 完全一样的两次,差别只在调用的是 Insert::exec 还是 TryInsert::exec。Conflicted 只有在「一行都没插进去」时出现:两行里一行冲突,结果是 Inserted,last_insert_id 是插进去的那一行。exec_without_returning 不看返回行,冲突时是 Inserted(0);exec_with_returning_many 冲突时是 Inserted([]),都要自己看数量。on_conflict_do_nothing() 的冲突目标是主键,sku 撞了照样报 23505:这张表的主键是 serial,插入时不带主键,它几乎永远用不上,要写 on_conflict_do_nothing_on([Column::Sku])。冲突目标后面跟着的 do_nothing_on(主键) 只对 MySQL 有意义(生成 ON DUPLICATE KEY UPDATE id = id),Postgres 上就是 DO NOTHING。
这些行为在 1.1.20 里也一样(write_old):on_conflict(do_nothing).exec 是 Err(RecordNotInserted),加 .do_nothing() 变成 TryInsert 后是 Ok(Conflicted)。2.0 改的是拿到 TryInsert 的方法名,冲突时的结果没变。
TryInsert 的 exec_with_returning 遇到冲突,返回哪一个?let r = item::Entity::insert(am("a1", "x"))
.on_conflict_do_nothing_on([item::Column::Sku])
.exec_with_returning(&db)
.await;
println!("=> {r:?}");
a1 已经存在。选项:Ok(Conflicted)、Ok(Inserted(..))、Err(..)。平台 M2 Max、macOS、rustc 1.99.0、Postgres 16.14。
动脑筋答案
--- same, exec_with_returning
INFO sqlx::query: summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=381.042µs elapsed_secs=0.000381042
=> Err(RecordNotFound("Failed to find inserted item"))
是 Err,而且不是 RecordNotInserted。TryInsert::exec_with_returning 只把 RecordNotInserted 翻译成 Conflicted,可它调用的 exec_insert_with_returning 在没有返回行时报的是另一个错误:
match found {
Some(model) => Ok(model),
None => Err(DbErr::RecordNotFound(
"Failed to find inserted item".to_owned(),
)),
}
于是 Conflicted 这个分支在这个方法上永远走不到,冲突以 RecordNotFound 的形式冒出来。1.1.20 的 do_nothing().exec_with_returning 也是这个结果。要「插入并拿回整行,冲突时什么也不做」,可以用 exec_with_returning_many(冲突时 Inserted([])),或者用 exec 拿 Conflicted 再查一次。
小结
Insert::one写Set和Unchanged的列,NotSet交给默认值;一列都没有时是VALUES (DEFAULT);读来的Model直接插入会带上主键。exec只RETURNING主键,last_insert_id来自返回的最后一行;exec_with_returning一条语句拿回整行;exec_without_returning只给rows_affected。insert_many取各行列的并集,某行没设的列补成带类型的 NULL,不是DEFAULT,not null default的列会因此失败。- 2.0 的
InsertMany对空列表不发 SQL;1.1.20 发VALUES (DEFAULT),可能报错,也可能插进一行默认值。 DO NOTHING冲突时:Insert::exec报RecordNotInserted,TryInsert::exec给Conflicted,exec_without_returning给Inserted(0),exec_with_returning报RecordNotFound("Failed to find inserted item");1.1.20 相同。on_conflict_do_nothing()只针对主键,按唯一列去重要用on_conflict_do_nothing_on。