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#[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#[derive(Debug)]
22pub struct InsertResult<A>
23where
24 A: ActiveModelTrait,
25{
26 pub last_insert_id: <<<A as ActiveModelTrait>::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType,
28}
29
30#[derive(Debug)]
32pub enum TryInsertResult<T> {
33 Empty,
35 Conflicted,
37 Inserted(T),
39}
40
41impl<A> TryInsert<A>
42where
43 A: ActiveModelTrait,
44{
45 #[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 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 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 #[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 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 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 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 pub fn new(primary_key: Option<ValueTuple>, query: InsertStatement) -> Self {
166 Self {
167 primary_key,
168 query,
169 model: PhantomData,
170 }
171 }
172
173 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 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 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 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}