1use crate::{
2 ActiveModelTrait, ActiveValue, ColumnTrait, EntityName, EntityTrait, IntoActiveModel, Iterable,
3 PrimaryKeyTrait, QueryTrait,
4};
5use core::marker::PhantomData;
6use sea_query::{Expr, InsertStatement, OnConflict, ValueTuple};
7
8#[derive(Debug)]
10pub struct Insert<A>
11where
12 A: ActiveModelTrait,
13{
14 pub(crate) query: InsertStatement,
15 pub(crate) columns: Vec<bool>,
16 pub(crate) primary_key: Option<ValueTuple>,
17 pub(crate) model: PhantomData<A>,
18}
19
20impl<A> Default for Insert<A>
21where
22 A: ActiveModelTrait,
23{
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl<A> Insert<A>
30where
31 A: ActiveModelTrait,
32{
33 pub(crate) fn new() -> Self {
34 Self {
35 query: InsertStatement::new()
36 .into_table(A::Entity::default().table_ref())
37 .or_default_values()
38 .to_owned(),
39 columns: Vec::new(),
40 primary_key: None,
41 model: PhantomData,
42 }
43 }
44
45 pub fn one<M>(m: M) -> Self
76 where
77 M: IntoActiveModel<A>,
78 {
79 Self::new().add(m)
80 }
81
82 pub fn many<M, I>(models: I) -> Self
104 where
105 M: IntoActiveModel<A>,
106 I: IntoIterator<Item = M>,
107 {
108 Self::new().add_many(models)
109 }
110
111 #[allow(clippy::should_implement_trait)]
117 pub fn add<M>(mut self, m: M) -> Self
118 where
119 M: IntoActiveModel<A>,
120 {
121 let mut am: A = m.into_active_model();
122 self.primary_key =
123 if !<<A::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::auto_increment() {
124 am.get_primary_key_value()
125 } else {
126 None
127 };
128 let mut columns = Vec::new();
129 let mut values = Vec::new();
130 let columns_empty = self.columns.is_empty();
131 for (idx, col) in <A::Entity as EntityTrait>::Column::iter().enumerate() {
132 let av = am.take(col);
133 let av_has_val = av.is_set() || av.is_unchanged();
134 if columns_empty {
135 self.columns.push(av_has_val);
136 } else if self.columns[idx] != av_has_val {
137 continue;
138 }
139 match av {
140 ActiveValue::Set(value) | ActiveValue::Unchanged(value) => {
141 columns.push(col);
142 values.push(col.save_as(Expr::val(value)));
143 }
144 ActiveValue::NotSet => {}
145 }
146 }
147 self.query.columns(columns);
148 self.query.values_panic(values);
149 self
150 }
151
152 pub fn add_many<M, I>(mut self, models: I) -> Self
154 where
155 M: IntoActiveModel<A>,
156 I: IntoIterator<Item = M>,
157 {
158 for model in models.into_iter() {
159 self = self.add(model);
160 }
161 self
162 }
163
164 pub fn on_conflict(mut self, on_conflict: OnConflict) -> Self {
208 self.query.on_conflict(on_conflict);
209 self
210 }
211
212 pub fn do_nothing(self) -> TryInsert<A>
215 where
216 A: ActiveModelTrait,
217 {
218 TryInsert::from_insert(self)
219 }
220
221 pub fn on_empty_do_nothing(self) -> TryInsert<A>
223 where
224 A: ActiveModelTrait,
225 {
226 TryInsert::from_insert(self)
227 }
228
229 pub fn on_conflict_do_nothing(mut self) -> TryInsert<A>
262 where
263 A: ActiveModelTrait,
264 {
265 let primary_keys = <A::Entity as EntityTrait>::PrimaryKey::iter();
266 self.query.on_conflict(
267 OnConflict::columns(primary_keys.clone())
268 .do_nothing_on(primary_keys)
269 .to_owned(),
270 );
271
272 TryInsert::from_insert(self)
273 }
274}
275
276impl<A> QueryTrait for Insert<A>
277where
278 A: ActiveModelTrait,
279{
280 type QueryStatement = InsertStatement;
281
282 fn query(&mut self) -> &mut InsertStatement {
283 &mut self.query
284 }
285
286 fn as_query(&self) -> &InsertStatement {
287 &self.query
288 }
289
290 fn into_query(self) -> InsertStatement {
291 self.query
292 }
293}
294
295#[derive(Debug)]
299pub struct TryInsert<A>
300where
301 A: ActiveModelTrait,
302{
303 pub(crate) insert_struct: Insert<A>,
304}
305
306impl<A> Default for TryInsert<A>
307where
308 A: ActiveModelTrait,
309{
310 fn default() -> Self {
311 Self::new()
312 }
313}
314
315#[allow(missing_docs)]
316impl<A> TryInsert<A>
317where
318 A: ActiveModelTrait,
319{
320 pub(crate) fn new() -> Self {
321 Self {
322 insert_struct: Insert::new(),
323 }
324 }
325
326 pub fn one<M>(m: M) -> Self
327 where
328 M: IntoActiveModel<A>,
329 {
330 Self::new().add(m)
331 }
332
333 pub fn many<M, I>(models: I) -> Self
334 where
335 M: IntoActiveModel<A>,
336 I: IntoIterator<Item = M>,
337 {
338 Self::new().add_many(models)
339 }
340
341 #[allow(clippy::should_implement_trait)]
342 pub fn add<M>(mut self, m: M) -> Self
343 where
344 M: IntoActiveModel<A>,
345 {
346 self.insert_struct = self.insert_struct.add(m);
347 self
348 }
349
350 pub fn add_many<M, I>(mut self, models: I) -> Self
351 where
352 M: IntoActiveModel<A>,
353 I: IntoIterator<Item = M>,
354 {
355 for model in models.into_iter() {
356 self.insert_struct = self.insert_struct.add(model);
357 }
358 self
359 }
360
361 pub fn on_conflict(mut self, on_conflict: OnConflict) -> Self {
362 self.insert_struct.query.on_conflict(on_conflict);
363 self
364 }
365
366 pub fn from_insert(insert: Insert<A>) -> Self {
368 Self {
369 insert_struct: insert,
370 }
371 }
372}
373
374impl<A> QueryTrait for TryInsert<A>
375where
376 A: ActiveModelTrait,
377{
378 type QueryStatement = InsertStatement;
379
380 fn query(&mut self) -> &mut InsertStatement {
381 &mut self.insert_struct.query
382 }
383
384 fn as_query(&self) -> &InsertStatement {
385 &self.insert_struct.query
386 }
387
388 fn into_query(self) -> InsertStatement {
389 self.insert_struct.query
390 }
391}
392#[cfg(test)]
393mod tests {
394 use sea_query::OnConflict;
395
396 use crate::tests_cfg::cake::{self};
397 use crate::{ActiveValue, DbBackend, DbErr, EntityTrait, Insert, IntoActiveModel, QueryTrait};
398
399 #[test]
400 fn insert_1() {
401 assert_eq!(
402 Insert::<cake::ActiveModel>::new()
403 .add(cake::ActiveModel {
404 id: ActiveValue::not_set(),
405 name: ActiveValue::set("Apple Pie".to_owned()),
406 })
407 .build(DbBackend::Postgres)
408 .to_string(),
409 r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#,
410 );
411 }
412
413 #[test]
414 fn insert_2() {
415 assert_eq!(
416 Insert::<cake::ActiveModel>::new()
417 .add(cake::ActiveModel {
418 id: ActiveValue::set(1),
419 name: ActiveValue::set("Apple Pie".to_owned()),
420 })
421 .build(DbBackend::Postgres)
422 .to_string(),
423 r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#,
424 );
425 }
426
427 #[test]
428 fn insert_3() {
429 assert_eq!(
430 Insert::<cake::ActiveModel>::new()
431 .add(cake::Model {
432 id: 1,
433 name: "Apple Pie".to_owned(),
434 })
435 .build(DbBackend::Postgres)
436 .to_string(),
437 r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#,
438 );
439 }
440
441 #[test]
442 fn insert_4() {
443 assert_eq!(
444 Insert::<cake::ActiveModel>::new()
445 .add_many([
446 cake::Model {
447 id: 1,
448 name: "Apple Pie".to_owned(),
449 },
450 cake::Model {
451 id: 2,
452 name: "Orange Scone".to_owned(),
453 }
454 ])
455 .build(DbBackend::Postgres)
456 .to_string(),
457 r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie'), (2, 'Orange Scone')"#,
458 );
459 }
460
461 #[test]
462 #[should_panic(expected = "columns mismatch")]
463 fn insert_5() {
464 let apple = cake::ActiveModel {
465 name: ActiveValue::set("Apple".to_owned()),
466 ..Default::default()
467 };
468 let orange = cake::ActiveModel {
469 id: ActiveValue::set(2),
470 name: ActiveValue::set("Orange".to_owned()),
471 };
472 assert_eq!(
473 Insert::<cake::ActiveModel>::new()
474 .add_many([apple, orange])
475 .build(DbBackend::Postgres)
476 .to_string(),
477 r#"INSERT INTO "cake" ("id", "name") VALUES (NULL, 'Apple'), (2, 'Orange')"#,
478 );
479 }
480
481 #[test]
482 fn insert_6() {
483 let orange = cake::ActiveModel {
484 id: ActiveValue::set(2),
485 name: ActiveValue::set("Orange".to_owned()),
486 };
487
488 assert_eq!(
489 cake::Entity::insert(orange)
490 .on_conflict(
491 OnConflict::column(cake::Column::Name)
492 .do_nothing()
493 .to_owned()
494 )
495 .build(DbBackend::Postgres)
496 .to_string(),
497 r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING"#,
498 );
499 }
500
501 #[test]
502 fn insert_7() {
503 let orange = cake::ActiveModel {
504 id: ActiveValue::set(2),
505 name: ActiveValue::set("Orange".to_owned()),
506 };
507
508 assert_eq!(
509 cake::Entity::insert(orange)
510 .on_conflict(
511 OnConflict::column(cake::Column::Name)
512 .update_column(cake::Column::Name)
513 .to_owned()
514 )
515 .build(DbBackend::Postgres)
516 .to_string(),
517 r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name""#,
518 );
519 }
520
521 #[smol_potat::test]
522 async fn insert_8() -> Result<(), DbErr> {
523 use crate::{DbBackend, MockDatabase, Statement, Transaction};
524
525 mod post {
526 use crate as sea_orm;
527 use crate::entity::prelude::*;
528
529 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
530 #[sea_orm(table_name = "posts")]
531 pub struct Model {
532 #[sea_orm(primary_key, select_as = "INTEGER", save_as = "TEXT")]
533 pub id: i32,
534 pub title: String,
535 pub text: String,
536 }
537
538 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
539 pub enum Relation {}
540
541 impl ActiveModelBehavior for ActiveModel {}
542 }
543
544 let model = post::Model {
545 id: 1,
546 title: "News wrap up 2022".into(),
547 text: "brbrbrrrbrbrbrr...".into(),
548 };
549
550 let db = MockDatabase::new(DbBackend::Postgres)
551 .append_query_results([[model.clone()]])
552 .into_connection();
553
554 post::Entity::insert(model.into_active_model())
555 .exec(&db)
556 .await?;
557
558 assert_eq!(
559 db.into_transaction_log(),
560 [Transaction::many([Statement::from_sql_and_values(
561 DbBackend::Postgres,
562 r#"INSERT INTO "posts" ("id", "title", "text") VALUES (CAST($1 AS TEXT), $2, $3) RETURNING CAST("id" AS INTEGER)"#,
563 [
564 1.into(),
565 "News wrap up 2022".into(),
566 "brbrbrrrbrbrbrr...".into(),
567 ]
568 )])]
569 );
570
571 Ok(())
572 }
573
574 #[smol_potat::test]
575 async fn insert_9() -> Result<(), DbErr> {
576 use crate::{DbBackend, MockDatabase, MockExecResult, Statement, Transaction};
577
578 mod post {
579 use crate as sea_orm;
580 use crate::entity::prelude::*;
581
582 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
583 #[sea_orm(table_name = "posts")]
584 pub struct Model {
585 #[sea_orm(
586 primary_key,
587 auto_increment = false,
588 select_as = "INTEGER",
589 save_as = "TEXT"
590 )]
591 pub id_primary: i32,
592 #[sea_orm(
593 primary_key,
594 auto_increment = false,
595 select_as = "INTEGER",
596 save_as = "TEXT"
597 )]
598 pub id_secondary: i32,
599 pub title: String,
600 pub text: String,
601 }
602
603 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
604 pub enum Relation {}
605
606 impl ActiveModelBehavior for ActiveModel {}
607 }
608
609 let model = post::Model {
610 id_primary: 1,
611 id_secondary: 1001,
612 title: "News wrap up 2022".into(),
613 text: "brbrbrrrbrbrbrr...".into(),
614 };
615
616 let db = MockDatabase::new(DbBackend::Postgres)
617 .append_exec_results([MockExecResult {
618 last_insert_id: 1,
619 rows_affected: 1,
620 }])
621 .into_connection();
622
623 post::Entity::insert(model.into_active_model())
624 .exec(&db)
625 .await?;
626
627 assert_eq!(
628 db.into_transaction_log(),
629 [Transaction::many([Statement::from_sql_and_values(
630 DbBackend::Postgres,
631 r#"INSERT INTO "posts" ("id_primary", "id_secondary", "title", "text") VALUES (CAST($1 AS TEXT), CAST($2 AS TEXT), $3, $4) RETURNING CAST("id_primary" AS INTEGER), CAST("id_secondary" AS INTEGER)"#,
632 [
633 1.into(),
634 1001.into(),
635 "News wrap up 2022".into(),
636 "brbrbrrrbrbrbrr...".into(),
637 ]
638 )])]
639 );
640
641 Ok(())
642 }
643}