Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ let result = Fruit::insert_many([apple, pear])
.exec(db)
.await?;

matches!(result, TryInsertResult::Conflicted);
result.last_insert_id.is_none();
```

### 更新
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ let result = Fruit::insert_many([apple, pear])
.exec(db)
.await?;

matches!(result, TryInsertResult::Conflicted);
result.last_insert_id.is_none();
```

### Update
Expand Down
2 changes: 1 addition & 1 deletion sea-orm-sync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ let result = Fruit::insert_many([apple, pear])
.on_conflict_do_nothing()
.exec(db)?;

matches!(result, TryInsertResult::Conflicted);
result.last_insert_id.is_none();
```

### Update
Expand Down
10 changes: 4 additions & 6 deletions sea-orm-sync/src/entity/active_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1333,14 +1333,12 @@ where
// insert new junctions
if db.support_returning() {
// use the returned value if it is supported
let res = J::insert_many(via_models_res)
let inserted = J::insert_many(via_models_res)
.on_conflict_do_nothing()
.exec_with_returning_many(db)?;
.exec_with_returning(db)?;
// run after_save hooks
if let TryInsertResult::Inserted(inserted) = res {
for model in inserted {
let _ = J::ActiveModel::after_save(model, db, true)?;
}
for model in inserted {
let _ = J::ActiveModel::after_save(model, db, true)?;
}
} else {
// fall back to individual inserts if returning is not supported
Expand Down
39 changes: 11 additions & 28 deletions sea-orm-sync/src/executor/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ where
}

/// Result of inserting many ActiveModels: the primary key of the last row
/// inserted, or `None` if the iterator was empty.
/// inserted, or `None` if the iterator was empty or the last insert id is unavailable.
#[derive(Debug)]
#[non_exhaustive]
pub struct InsertManyResult<A>
Expand All @@ -54,10 +54,6 @@ where
/// them as errors.
#[derive(Debug)]
pub enum TryInsertResult<T> {
/// There was nothing to insert, so no SQL was executed.
///
/// This typically happens when creating a [`crate::TryInsert`] from an empty iterator or None.
Empty,
/// The statement was executed, but SeaORM could not get the inserted row / insert id.
///
/// This is commonly caused by `ON CONFLICT ... DO NOTHING` (Postgres / SQLite) or the MySQL
Expand All @@ -79,15 +75,13 @@ where
{
/// Extract the last inserted id.
///
/// - [`TryInsertResult::Empty`] => `Ok(None)`
/// - [`TryInsertResult::Inserted`] => `Ok(Some(last_insert_id))`
/// - [`TryInsertResult::Inserted`] => `Ok(last_insert_id)`
/// - [`TryInsertResult::Conflicted`] => `Err(DbErr::RecordNotInserted)`
pub fn last_insert_id(
self,
) -> Result<Option<<PrimaryKey<A> as PrimaryKeyTrait>::ValueType>, DbErr> {
) -> Result<<PrimaryKey<A> as PrimaryKeyTrait>::ValueType, DbErr> {
match self {
Self::Empty => Ok(None),
Self::Inserted(v) => Ok(Some(v.last_insert_id)),
Self::Inserted(v) => Ok(v.last_insert_id),
Self::Conflicted => Err(DbErr::RecordNotInserted),
}
}
Expand All @@ -102,9 +96,6 @@ where
where
C: ConnectionTrait,
{
if self.empty {
return Ok(TryInsertResult::Empty);
}
let res = self.insert_struct.exec(db);
match res {
Ok(res) => Ok(TryInsertResult::Inserted(res)),
Expand All @@ -119,9 +110,6 @@ where
where
C: ConnectionTrait,
{
if self.empty {
return Ok(TryInsertResult::Empty);
}
let res = self.insert_struct.exec_without_returning(db);
match res {
Ok(res) => Ok(TryInsertResult::Inserted(res)),
Expand All @@ -139,9 +127,6 @@ where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait,
{
if self.empty {
return Ok(TryInsertResult::Empty);
}
let res = self.insert_struct.exec_with_returning(db);
match res {
Ok(res) => Ok(TryInsertResult::Inserted(res)),
Expand All @@ -159,10 +144,6 @@ where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait,
{
if self.empty {
return Ok(TryInsertResult::Empty);
}

let res = self.insert_struct.exec_with_returning_keys(db);
match res {
Ok(res) => Ok(TryInsertResult::Inserted(res)),
Expand All @@ -180,10 +161,6 @@ where
<A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
C: ConnectionTrait,
{
if self.empty {
return Ok(TryInsertResult::Empty);
}

let res = self.insert_struct.exec_with_returning_many(db);
match res {
Ok(res) => Ok(TryInsertResult::Inserted(res)),
Expand Down Expand Up @@ -269,7 +246,10 @@ impl<A> InsertMany<A>
where
A: ActiveModelTrait,
{
/// Execute an insert operation
/// Execute an insert operation.
///
/// The returned [`InsertManyResult::last_insert_id`] is `None` if the
/// iterator was empty or the last insert id is unavailable.
pub fn exec<C>(self, db: &C) -> Result<InsertManyResult<A>, DbErr>
where
C: ConnectionTrait,
Expand All @@ -284,6 +264,9 @@ where
Ok(r) => Ok(InsertManyResult {
last_insert_id: Some(r.last_insert_id),
}),
Err(DbErr::RecordNotInserted) => Ok(InsertManyResult {
last_insert_id: None,
}),
Err(err) => Err(err),
}
}
Expand Down
4 changes: 2 additions & 2 deletions sea-orm-sync/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@
//! ### Insert (advanced)
//! You can take advantage of database specific features to perform upsert and idempotent insert.
//! ```
//! # use sea_orm::{DbConn, TryInsertResult, DbErr, entity::*, query::*, tests_cfg::*};
//! # use sea_orm::{DbConn, DbErr, entity::*, query::*, tests_cfg::*};
//! # fn function_1(db: &DbConn) -> Result<(), DbErr> {
//! # let apple = fruit::ActiveModel {
//! # name: Set("Apple".to_owned()),
Expand Down Expand Up @@ -421,7 +421,7 @@
//! .on_conflict_do_nothing()
//! .exec(db)?;
//!
//! matches!(result, TryInsertResult::Conflicted);
//! result.last_insert_id.is_none();
//! # Ok(())
//! # }
//! ```
Expand Down
76 changes: 28 additions & 48 deletions sea-orm-sync/src/query/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,10 @@ where
pub(crate) model: PhantomData<A>,
}

/// Wrapper of [`Insert`] / [`InsertMany`], treats "no row inserted/id returned" as a normal outcome.
/// Wrapper of [`Insert`], treats "no row inserted/id returned" as a normal outcome.
///
/// Its `exec*` methods return [`crate::TryInsertResult`].
/// Mapping empty input to [`crate::TryInsertResult::Empty`] (no SQL executed) and
/// `DbErr::RecordNotInserted` to [`crate::TryInsertResult::Conflicted`].
/// Mapping `DbErr::RecordNotInserted` to [`crate::TryInsertResult::Conflicted`].
///
/// Useful for idempotent inserts such as `ON CONFLICT ... DO NOTHING` (Postgres / SQLite) or the
/// MySQL polyfill (`ON DUPLICATE KEY UPDATE pk = pk`).
Expand All @@ -48,7 +47,6 @@ where
A: ActiveModelTrait,
{
pub(crate) insert_struct: Insert<A>,
pub(crate) empty: bool,
}

impl<A> Insert<A>
Expand Down Expand Up @@ -229,7 +227,7 @@ where
TryInsert::from_one(self)
}

/// Allow insert statement to return without error if nothing's been inserted.
/// Deprecated alias to [`Insert::try_insert`].
#[deprecated(
since = "2.0.0",
note = "Please use [`TryInsert::one`] or `on_conflict_do_nothing*` methods that return [`TryInsert`], or [`Insert::try_insert`]."
Expand Down Expand Up @@ -409,58 +407,68 @@ where
/// r#"INSERT INTO `cake` (`id`, `name`) VALUES (2, 'Orange') ON DUPLICATE KEY UPDATE `id` = `id`"#,
/// );
/// ```
pub fn on_conflict_do_nothing_on<I>(mut self, columns: I) -> TryInsert<A>
pub fn on_conflict_do_nothing_on<I>(mut self, columns: I) -> Self
where
I: IntoIterator<Item = <A::Entity as EntityTrait>::Column>,
{
let primary_keys = <A::Entity as EntityTrait>::PrimaryKey::iter();
let mut on_conflict = OnConflict::columns(columns);
on_conflict.do_nothing_on(primary_keys);
self.query.on_conflict(on_conflict);
TryInsert::from_many(self)
self
}

/// Allow insert statement to return without error if nothing's been inserted.
/// Deprecated no-op.
///
/// [`InsertMany::exec`] already handles empty input and inserts that affect no rows.
#[deprecated(
since = "2.0.0",
note = "Please use [`TryInsert::many`] or `on_conflict_do_nothing*` methods that return [`TryInsert`], or [`InsertMany::try_insert`]"
note = "Deprecated no-op. Use [`InsertMany::exec`] directly."
)]
pub fn do_nothing(self) -> TryInsert<A>
pub fn do_nothing(self) -> Self
where
A: ActiveModelTrait,
{
TryInsert::from_many(self)
self
}

/// Convert self into a `TryInsert`. It is just a wrapper for converting `DbErr::RecordNotInserted` -> `TryInsertResult::Conflicted`.
pub fn try_insert(self) -> TryInsert<A>
/// Deprecated no-op.
///
/// [`InsertMany::exec`] already handles empty input and inserts that affect no rows.
#[deprecated(
since = "2.0.0",
note = "Deprecated no-op. Use [`InsertMany::exec`] directly."
)]
pub fn try_insert(self) -> Self
where
A: ActiveModelTrait,
{
TryInsert::from_many(self)
self
}

/// Alias to [`InsertMany::do_nothing`].
/// Deprecated no-op.
///
/// [`InsertMany::exec`] already handles empty input and inserts that affect no rows.
#[deprecated(
since = "2.0.0",
note = "Empty input is already handled by [`InsertMany::exec`] (no SQL executed). For conflict handling, use [`InsertMany::on_conflict_do_nothing`] or [`InsertMany::on_conflict_do_nothing_on`]."
note = "Deprecated no-op. Use [`InsertMany::exec`] directly."
)]
pub fn on_empty_do_nothing(self) -> TryInsert<A>
pub fn on_empty_do_nothing(self) -> Self
where
A: ActiveModelTrait,
{
TryInsert::from_many(self)
self
}

/// Set ON CONFLICT on primary key do nothing, but with MySQL specific polyfill.
/// See also [`Insert::on_conflict_do_nothing`].
pub fn on_conflict_do_nothing(mut self) -> TryInsert<A>
pub fn on_conflict_do_nothing(mut self) -> Self
where
A: ActiveModelTrait,
{
self.query.on_conflict(on_conflict_primary_key::<A>());

TryInsert::from_many(self)
self
}

/// panic when self is empty
Expand Down Expand Up @@ -527,25 +535,6 @@ where
fn from_one(insert: Insert<A>) -> Self {
Self {
insert_struct: insert,
empty: false,
}
}

fn from_many(insert: InsertMany<A>) -> Self {
let InsertMany {
query,
primary_key,
empty,
model,
} = insert;

Self {
insert_struct: Insert {
query,
primary_key,
model,
},
empty,
}
}

Expand All @@ -557,15 +546,6 @@ where
Self::from_one(Insert::one(m))
}

/// Try insert many items
pub fn many<M, I>(models: I) -> Self
where
M: IntoActiveModel<A>,
I: IntoIterator<Item = M>,
{
Self::from_many(Insert::many(models))
}

/// Set ON CONFLICT logic
pub fn on_conflict(mut self, on_conflict: OnConflict) -> Insert<A> {
self.insert_struct.query.on_conflict(on_conflict);
Expand Down
26 changes: 10 additions & 16 deletions sea-orm-sync/src/rbac/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,14 @@ impl RbacContext {
let txn = db.begin()?;

for table_name in tables {
if let Some(table_id) = resource::Entity::insert(Resource {
let table_id = resource::Entity::insert(Resource {
table: Set(table_name.to_string()),
..Default::default()
})
.on_conflict_do_nothing()
.exec(&txn)?
.last_insert_id()?
{
self.tables.insert(table_name.to_string(), table_id);
}
.last_insert_id()?;
self.tables.insert(table_name.to_string(), table_id);
}

txn.commit()
Expand All @@ -106,17 +104,15 @@ impl RbacContext {
AccessType::Update,
AccessType::Delete,
] {
if let Some(permission_id) = permission::Entity::insert(Permission {
let permission_id = permission::Entity::insert(Permission {
action: Set(action.as_str().to_owned()),
..Default::default()
})
.on_conflict_do_nothing()
.exec(&txn)?
.last_insert_id()?
{
self.permissions
.insert(action.as_str().to_owned(), permission_id);
}
.last_insert_id()?;
self.permissions
.insert(action.as_str().to_owned(), permission_id);
}

txn.commit()
Expand All @@ -131,16 +127,14 @@ impl RbacContext {
let txn = db.begin()?;

for role in roles {
if let Some(role_id) = role::Entity::insert(Role {
let role_id = role::Entity::insert(Role {
role: Set(role.to_string()),
..Default::default()
})
.on_conflict_do_nothing()
.exec(&txn)?
.last_insert_id()?
{
self.roles.insert(role.to_string(), role_id);
}
.last_insert_id()?;
self.roles.insert(role.to_string(), role_id);
}

txn.commit()
Expand Down
Loading
Loading