sea_orm/executor/
insert.rs

1use crate::{
2    error::*, ActiveModelTrait, ColumnTrait, ConnectionTrait, DbBackend, EntityTrait, Insert,
3    IntoActiveModel, Iterable, PrimaryKeyToColumn, PrimaryKeyTrait, SelectModel, SelectorRaw,
4    TryFromU64, TryInsert,
5};
6use sea_query::{FromValueTuple, Iden, InsertStatement, Query, ValueTuple};
7use std::{future::Future, marker::PhantomData};
8
9/// Defines a structure to perform INSERT operations in an ActiveModel
10#[derive(Debug)]
11pub struct Inserter<A>
12where
13    A: ActiveModelTrait,
14{
15    primary_key: Option<ValueTuple>,
16    query: InsertStatement,
17    model: PhantomData<A>,
18}
19
20/// The result of an INSERT operation on an ActiveModel
21#[derive(Debug)]
22pub struct InsertResult<A>
23where
24    A: ActiveModelTrait,
25{
26    /// The id performed when AUTOINCREMENT was performed on the PrimaryKey
27    pub last_insert_id: <<<A as ActiveModelTrait>::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType,
28}
29
30/// The types of results for an INSERT operation
31#[derive(Debug)]
32pub enum TryInsertResult<T> {
33    /// The INSERT statement did not have any value to insert
34    Empty,
35    /// The INSERT operation did not insert any valid value
36    Conflicted,
37    /// Successfully inserted
38    Inserted(T),
39}
40
41impl<A> TryInsert<A>
42where
43    A: ActiveModelTrait,
44{
45    /// Execute an insert operation
46    #[allow(unused_mut)]
47    pub async fn exec<'a, C>(self, db: &'a C) -> Result<TryInsertResult<InsertResult<A>>, DbErr>
48    where
49        C: ConnectionTrait,
50        A: 'a,
51    {
52        if self.insert_struct.columns.is_empty() {
53            return Ok(TryInsertResult::Empty);
54        }
55        let res = self.insert_struct.exec(db).await;
56        match res {
57            Ok(res) => Ok(TryInsertResult::Inserted(res)),
58            Err(DbErr::RecordNotInserted) => Ok(TryInsertResult::Conflicted),
59            Err(err) => Err(err),
60        }
61    }
62
63    /// Execute an insert operation without returning (don't use `RETURNING` syntax)
64    /// Number of rows affected is returned
65    pub async fn exec_without_returning<'a, C>(
66        self,
67        db: &'a C,
68    ) -> Result<TryInsertResult<u64>, DbErr>
69    where
70        <A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
71        C: ConnectionTrait,
72        A: 'a,
73    {
74        if self.insert_struct.columns.is_empty() {
75            return Ok(TryInsertResult::Empty);
76        }
77        let res = self.insert_struct.exec_without_returning(db).await;
78        match res {
79            Ok(res) => Ok(TryInsertResult::Inserted(res)),
80            Err(DbErr::RecordNotInserted) => Ok(TryInsertResult::Conflicted),
81            Err(err) => Err(err),
82        }
83    }
84
85    /// Execute an insert operation and return the inserted model (use `RETURNING` syntax if supported)
86    pub async fn exec_with_returning<'a, C>(
87        self,
88        db: &'a C,
89    ) -> Result<TryInsertResult<<A::Entity as EntityTrait>::Model>, DbErr>
90    where
91        <A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
92        C: ConnectionTrait,
93        A: 'a,
94    {
95        if self.insert_struct.columns.is_empty() {
96            return Ok(TryInsertResult::Empty);
97        }
98        let res = self.insert_struct.exec_with_returning(db).await;
99        match res {
100            Ok(res) => Ok(TryInsertResult::Inserted(res)),
101            Err(DbErr::RecordNotInserted) => Ok(TryInsertResult::Conflicted),
102            Err(err) => Err(err),
103        }
104    }
105}
106
107impl<A> Insert<A>
108where
109    A: ActiveModelTrait,
110{
111    /// Execute an insert operation
112    #[allow(unused_mut)]
113    pub fn exec<'a, C>(self, db: &'a C) -> impl Future<Output = Result<InsertResult<A>, DbErr>> + '_
114    where
115        C: ConnectionTrait,
116        A: 'a,
117    {
118        // so that self is dropped before entering await
119        let mut query = self.query;
120        if db.support_returning() {
121            let db_backend = db.get_database_backend();
122            let returning =
123                Query::returning().exprs(<A::Entity as EntityTrait>::PrimaryKey::iter().map(|c| {
124                    c.into_column()
125                        .select_as(c.into_column().into_returning_expr(db_backend))
126                }));
127            query.returning(returning);
128        }
129        Inserter::<A>::new(self.primary_key, query).exec(db)
130    }
131
132    /// Execute an insert operation without returning (don't use `RETURNING` syntax)
133    /// Number of rows affected is returned
134    pub fn exec_without_returning<'a, C>(
135        self,
136        db: &'a C,
137    ) -> impl Future<Output = Result<u64, DbErr>> + '_
138    where
139        <A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
140        C: ConnectionTrait,
141        A: 'a,
142    {
143        Inserter::<A>::new(self.primary_key, self.query).exec_without_returning(db)
144    }
145
146    /// Execute an insert operation and return the inserted model (use `RETURNING` syntax if supported)
147    pub fn exec_with_returning<'a, C>(
148        self,
149        db: &'a C,
150    ) -> impl Future<Output = Result<<A::Entity as EntityTrait>::Model, DbErr>> + '_
151    where
152        <A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
153        C: ConnectionTrait,
154        A: 'a,
155    {
156        Inserter::<A>::new(self.primary_key, self.query).exec_with_returning(db)
157    }
158}
159
160impl<A> Inserter<A>
161where
162    A: ActiveModelTrait,
163{
164    /// Instantiate a new insert operation
165    pub fn new(primary_key: Option<ValueTuple>, query: InsertStatement) -> Self {
166        Self {
167            primary_key,
168            query,
169            model: PhantomData,
170        }
171    }
172
173    /// Execute an insert operation, returning the last inserted id
174    pub fn exec<'a, C>(self, db: &'a C) -> impl Future<Output = Result<InsertResult<A>, DbErr>> + '_
175    where
176        C: ConnectionTrait,
177        A: 'a,
178    {
179        exec_insert(self.primary_key, self.query, db)
180    }
181
182    /// Execute an insert operation
183    pub fn exec_without_returning<'a, C>(
184        self,
185        db: &'a C,
186    ) -> impl Future<Output = Result<u64, DbErr>> + '_
187    where
188        C: ConnectionTrait,
189        A: 'a,
190    {
191        exec_insert_without_returning(self.query, db)
192    }
193
194    /// Execute an insert operation and return the inserted model (use `RETURNING` syntax if supported)
195    pub fn exec_with_returning<'a, C>(
196        self,
197        db: &'a C,
198    ) -> impl Future<Output = Result<<A::Entity as EntityTrait>::Model, DbErr>> + '_
199    where
200        <A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
201        C: ConnectionTrait,
202        A: 'a,
203    {
204        exec_insert_with_returning::<A, _>(self.primary_key, self.query, db)
205    }
206}
207
208async fn exec_insert<A, C>(
209    primary_key: Option<ValueTuple>,
210    statement: InsertStatement,
211    db: &C,
212) -> Result<InsertResult<A>, DbErr>
213where
214    C: ConnectionTrait,
215    A: ActiveModelTrait,
216{
217    let db_backend = db.get_database_backend();
218    let statement = db_backend.build(&statement);
219
220    type PrimaryKey<A> = <<A as ActiveModelTrait>::Entity as EntityTrait>::PrimaryKey;
221    type ValueTypeOf<A> = <PrimaryKey<A> as PrimaryKeyTrait>::ValueType;
222
223    let last_insert_id = match (primary_key, db.support_returning()) {
224        (Some(value_tuple), _) => {
225            let res = db.execute(statement).await?;
226            if res.rows_affected() == 0 {
227                return Err(DbErr::RecordNotInserted);
228            }
229            FromValueTuple::from_value_tuple(value_tuple)
230        }
231        (None, true) => {
232            let mut rows = db.query_all(statement).await?;
233            let row = match rows.pop() {
234                Some(row) => row,
235                None => return Err(DbErr::RecordNotInserted),
236            };
237            let cols = PrimaryKey::<A>::iter()
238                .map(|col| col.to_string())
239                .collect::<Vec<_>>();
240            row.try_get_many("", cols.as_ref())
241                .map_err(|_| DbErr::UnpackInsertId)?
242        }
243        (None, false) => {
244            let res = db.execute(statement).await?;
245            if res.rows_affected() == 0 {
246                return Err(DbErr::RecordNotInserted);
247            }
248            let last_insert_id = res.last_insert_id();
249            // For MySQL, the affected-rows number:
250            //   - The affected-rows value per row is `1` if the row is inserted as a new row,
251            //   - `2` if an existing row is updated,
252            //   - and `0` if an existing row is set to its current values.
253            // Reference: https://dev.mysql.com/doc/refman/8.4/en/insert-on-duplicate.html
254            if db_backend == DbBackend::MySql && last_insert_id == 0 {
255                return Err(DbErr::RecordNotInserted);
256            }
257            ValueTypeOf::<A>::try_from_u64(last_insert_id).map_err(|_| DbErr::UnpackInsertId)?
258        }
259    };
260
261    Ok(InsertResult { last_insert_id })
262}
263
264async fn exec_insert_without_returning<C>(
265    insert_statement: InsertStatement,
266    db: &C,
267) -> Result<u64, DbErr>
268where
269    C: ConnectionTrait,
270{
271    let db_backend = db.get_database_backend();
272    let insert_statement = db_backend.build(&insert_statement);
273    let exec_result = db.execute(insert_statement).await?;
274    Ok(exec_result.rows_affected())
275}
276
277async fn exec_insert_with_returning<A, C>(
278    primary_key: Option<ValueTuple>,
279    mut insert_statement: InsertStatement,
280    db: &C,
281) -> Result<<A::Entity as EntityTrait>::Model, DbErr>
282where
283    <A::Entity as EntityTrait>::Model: IntoActiveModel<A>,
284    C: ConnectionTrait,
285    A: ActiveModelTrait,
286{
287    let db_backend = db.get_database_backend();
288    let found = match db.support_returning() {
289        true => {
290            let returning = Query::returning().exprs(
291                <A::Entity as EntityTrait>::Column::iter()
292                    .map(|c| c.select_as(c.into_returning_expr(db_backend))),
293            );
294            insert_statement.returning(returning);
295            let insert_statement = db_backend.build(&insert_statement);
296            SelectorRaw::<SelectModel<<A::Entity as EntityTrait>::Model>>::from_statement(
297                insert_statement,
298            )
299            .one(db)
300            .await?
301        }
302        false => {
303            let insert_res = exec_insert::<A, _>(primary_key, insert_statement, db).await?;
304            <A::Entity as EntityTrait>::find_by_id(insert_res.last_insert_id)
305                .one(db)
306                .await?
307        }
308    };
309    match found {
310        Some(model) => Ok(model),
311        None => Err(DbErr::RecordNotFound(
312            "Failed to find inserted item".to_owned(),
313        )),
314    }
315}