1use crate::{
2 ActiveModelTrait, ActiveValue, ColumnTrait, EntityName, EntityTrait, IntoActiveModel, Iterable,
3 PrimaryKeyTrait, QueryTrait,
4};
5use core::marker::PhantomData;
6use sea_query::{Expr, InsertStatement, Keyword, OnConflict, SimpleExpr, Value, 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 panic!("columns mismatch");
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 #[deprecated(
158 since = "1.1.3",
159 note = "Please use [`Insert::add_many`] which does not panic"
160 )]
161 pub fn add_multi<M, I>(mut self, models: I) -> Self
162 where
163 M: IntoActiveModel<A>,
164 I: IntoIterator<Item = M>,
165 {
166 for model in models.into_iter() {
167 self = self.add(model);
168 }
169 self
170 }
171
172 pub fn add_many<M, I>(mut self, models: I) -> Self
174 where
175 M: IntoActiveModel<A>,
176 I: IntoIterator<Item = M>,
177 {
178 let mut columns: Vec<_> = <A::Entity as EntityTrait>::Column::iter()
179 .map(|_| None)
180 .collect();
181 let mut null_value: Vec<Option<Value>> =
182 std::iter::repeat(None).take(columns.len()).collect();
183 let mut all_values: Vec<Vec<SimpleExpr>> = Vec::new();
184
185 for model in models.into_iter() {
186 let mut am: A = model.into_active_model();
187 self.primary_key =
188 if !<<A::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::auto_increment() {
189 am.get_primary_key_value()
190 } else {
191 None
192 };
193 let mut values = Vec::with_capacity(columns.len());
194 for (idx, col) in <A::Entity as EntityTrait>::Column::iter().enumerate() {
195 let av = am.take(col);
196 match av {
197 ActiveValue::Set(value) | ActiveValue::Unchanged(value) => {
198 columns[idx] = Some(col); null_value[idx] = Some(value.as_null()); values.push(col.save_as(Expr::val(value))); }
202 ActiveValue::NotSet => {
203 values.push(SimpleExpr::Keyword(Keyword::Null)); }
205 }
206 }
207 all_values.push(values);
208 }
209
210 if !all_values.is_empty() {
211 self.query.columns(columns.iter().cloned().flatten());
213
214 self.columns = columns.iter().map(Option::is_some).collect();
216 }
217
218 for values in all_values {
219 self.query
221 .values_panic(values.into_iter().enumerate().filter_map(|(i, v)| {
222 if columns[i].is_some() {
223 if !matches!(v, SimpleExpr::Keyword(Keyword::Null)) {
225 Some(v)
227 } else {
228 null_value[i].clone().map(SimpleExpr::Value)
230 }
231 } else {
232 None
233 }
234 }));
235 }
236
237 self
238 }
239
240 pub fn on_conflict(mut self, on_conflict: OnConflict) -> Self {
284 self.query.on_conflict(on_conflict);
285 self
286 }
287
288 pub fn do_nothing(self) -> TryInsert<A>
290 where
291 A: ActiveModelTrait,
292 {
293 TryInsert::from_insert(self)
294 }
295
296 pub fn on_empty_do_nothing(self) -> TryInsert<A>
298 where
299 A: ActiveModelTrait,
300 {
301 TryInsert::from_insert(self)
302 }
303
304 pub fn on_conflict_do_nothing(mut self) -> TryInsert<A>
337 where
338 A: ActiveModelTrait,
339 {
340 let primary_keys = <A::Entity as EntityTrait>::PrimaryKey::iter();
341 self.query.on_conflict(
342 OnConflict::columns(primary_keys.clone())
343 .do_nothing_on(primary_keys)
344 .to_owned(),
345 );
346
347 TryInsert::from_insert(self)
348 }
349}
350
351impl<A> QueryTrait for Insert<A>
352where
353 A: ActiveModelTrait,
354{
355 type QueryStatement = InsertStatement;
356
357 fn query(&mut self) -> &mut InsertStatement {
358 &mut self.query
359 }
360
361 fn as_query(&self) -> &InsertStatement {
362 &self.query
363 }
364
365 fn into_query(self) -> InsertStatement {
366 self.query
367 }
368}
369
370#[derive(Debug)]
374pub struct TryInsert<A>
375where
376 A: ActiveModelTrait,
377{
378 pub(crate) insert_struct: Insert<A>,
379}
380
381impl<A> Default for TryInsert<A>
382where
383 A: ActiveModelTrait,
384{
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390#[allow(missing_docs)]
391impl<A> TryInsert<A>
392where
393 A: ActiveModelTrait,
394{
395 pub(crate) fn new() -> Self {
396 Self {
397 insert_struct: Insert::new(),
398 }
399 }
400
401 pub fn one<M>(m: M) -> Self
402 where
403 M: IntoActiveModel<A>,
404 {
405 Self::new().add(m)
406 }
407
408 pub fn many<M, I>(models: I) -> Self
409 where
410 M: IntoActiveModel<A>,
411 I: IntoIterator<Item = M>,
412 {
413 Self::new().add_many(models)
414 }
415
416 #[allow(clippy::should_implement_trait)]
417 pub fn add<M>(mut self, m: M) -> Self
418 where
419 M: IntoActiveModel<A>,
420 {
421 self.insert_struct = self.insert_struct.add(m);
422 self
423 }
424
425 pub fn add_many<M, I>(mut self, models: I) -> Self
426 where
427 M: IntoActiveModel<A>,
428 I: IntoIterator<Item = M>,
429 {
430 self.insert_struct = self.insert_struct.add_many(models);
431 self
432 }
433
434 pub fn on_conflict(mut self, on_conflict: OnConflict) -> Self {
435 self.insert_struct.query.on_conflict(on_conflict);
436 self
437 }
438
439 pub fn from_insert(insert: Insert<A>) -> Self {
441 Self {
442 insert_struct: insert,
443 }
444 }
445}
446
447impl<A> QueryTrait for TryInsert<A>
448where
449 A: ActiveModelTrait,
450{
451 type QueryStatement = InsertStatement;
452
453 fn query(&mut self) -> &mut InsertStatement {
454 &mut self.insert_struct.query
455 }
456
457 fn as_query(&self) -> &InsertStatement {
458 &self.insert_struct.query
459 }
460
461 fn into_query(self) -> InsertStatement {
462 self.insert_struct.query
463 }
464}
465#[cfg(test)]
466mod tests {
467 use sea_query::OnConflict;
468
469 use crate::tests_cfg::{cake, cake_filling};
470 use crate::{
471 ActiveValue, DbBackend, DbErr, EntityTrait, Insert, IntoActiveModel, NotSet, QueryTrait,
472 Set,
473 };
474
475 #[test]
476 fn insert_1() {
477 assert_eq!(
478 Insert::<cake::ActiveModel>::new()
479 .add(cake::ActiveModel {
480 id: ActiveValue::not_set(),
481 name: ActiveValue::set("Apple Pie".to_owned()),
482 })
483 .build(DbBackend::Postgres)
484 .to_string(),
485 r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie')"#,
486 );
487 }
488
489 #[test]
490 fn insert_2() {
491 assert_eq!(
492 Insert::<cake::ActiveModel>::new()
493 .add(cake::ActiveModel {
494 id: ActiveValue::set(1),
495 name: ActiveValue::set("Apple Pie".to_owned()),
496 })
497 .build(DbBackend::Postgres)
498 .to_string(),
499 r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#,
500 );
501 }
502
503 #[test]
504 fn insert_3() {
505 assert_eq!(
506 Insert::<cake::ActiveModel>::new()
507 .add(cake::Model {
508 id: 1,
509 name: "Apple Pie".to_owned(),
510 })
511 .build(DbBackend::Postgres)
512 .to_string(),
513 r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie')"#,
514 );
515 }
516
517 #[test]
518 fn insert_many_1() {
519 assert_eq!(
520 Insert::<cake::ActiveModel>::new()
521 .add_many([
522 cake::Model {
523 id: 1,
524 name: "Apple Pie".to_owned(),
525 },
526 cake::Model {
527 id: 2,
528 name: "Orange Scone".to_owned(),
529 }
530 ])
531 .build(DbBackend::Postgres)
532 .to_string(),
533 r#"INSERT INTO "cake" ("id", "name") VALUES (1, 'Apple Pie'), (2, 'Orange Scone')"#,
534 );
535 }
536
537 #[test]
538 fn insert_many_2() {
539 assert_eq!(
540 Insert::<cake::ActiveModel>::new()
541 .add_many([
542 cake::ActiveModel {
543 id: NotSet,
544 name: Set("Apple Pie".to_owned()),
545 },
546 cake::ActiveModel {
547 id: NotSet,
548 name: Set("Orange Scone".to_owned()),
549 }
550 ])
551 .build(DbBackend::Postgres)
552 .to_string(),
553 r#"INSERT INTO "cake" ("name") VALUES ('Apple Pie'), ('Orange Scone')"#,
554 );
555 }
556
557 #[test]
558 fn insert_many_3() {
559 let apple = cake_filling::ActiveModel {
560 cake_id: ActiveValue::set(2),
561 filling_id: ActiveValue::NotSet,
562 };
563 let orange = cake_filling::ActiveModel {
564 cake_id: ActiveValue::NotSet,
565 filling_id: ActiveValue::set(3),
566 };
567 assert_eq!(
568 Insert::<cake_filling::ActiveModel>::new()
569 .add_many([apple, orange])
570 .build(DbBackend::Postgres)
571 .to_string(),
572 r#"INSERT INTO "cake_filling" ("cake_id", "filling_id") VALUES (2, NULL), (NULL, 3)"#,
573 );
574 }
575
576 #[test]
577 fn insert_6() {
578 let orange = cake::ActiveModel {
579 id: ActiveValue::set(2),
580 name: ActiveValue::set("Orange".to_owned()),
581 };
582
583 assert_eq!(
584 cake::Entity::insert(orange)
585 .on_conflict(
586 OnConflict::column(cake::Column::Name)
587 .do_nothing()
588 .to_owned()
589 )
590 .build(DbBackend::Postgres)
591 .to_string(),
592 r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING"#,
593 );
594 }
595
596 #[test]
597 fn insert_7() {
598 let orange = cake::ActiveModel {
599 id: ActiveValue::set(2),
600 name: ActiveValue::set("Orange".to_owned()),
601 };
602
603 assert_eq!(
604 cake::Entity::insert(orange)
605 .on_conflict(
606 OnConflict::column(cake::Column::Name)
607 .update_column(cake::Column::Name)
608 .to_owned()
609 )
610 .build(DbBackend::Postgres)
611 .to_string(),
612 r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name""#,
613 );
614 }
615
616 #[smol_potat::test]
617 async fn insert_8() -> Result<(), DbErr> {
618 use crate::{DbBackend, MockDatabase, Statement, Transaction};
619
620 mod post {
621 use crate as sea_orm;
622 use crate::entity::prelude::*;
623
624 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
625 #[sea_orm(table_name = "posts")]
626 pub struct Model {
627 #[sea_orm(primary_key, select_as = "INTEGER", save_as = "TEXT")]
628 pub id: i32,
629 pub title: String,
630 pub text: String,
631 }
632
633 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
634 pub enum Relation {}
635
636 impl ActiveModelBehavior for ActiveModel {}
637 }
638
639 let model = post::Model {
640 id: 1,
641 title: "News wrap up 2022".into(),
642 text: "brbrbrrrbrbrbrr...".into(),
643 };
644
645 let db = MockDatabase::new(DbBackend::Postgres)
646 .append_query_results([[model.clone()]])
647 .into_connection();
648
649 post::Entity::insert(model.into_active_model())
650 .exec(&db)
651 .await?;
652
653 assert_eq!(
654 db.into_transaction_log(),
655 [Transaction::many([Statement::from_sql_and_values(
656 DbBackend::Postgres,
657 r#"INSERT INTO "posts" ("id", "title", "text") VALUES (CAST($1 AS TEXT), $2, $3) RETURNING CAST("id" AS INTEGER)"#,
658 [
659 1.into(),
660 "News wrap up 2022".into(),
661 "brbrbrrrbrbrbrr...".into(),
662 ]
663 )])]
664 );
665
666 Ok(())
667 }
668
669 #[smol_potat::test]
670 async fn insert_9() -> Result<(), DbErr> {
671 use crate::{DbBackend, MockDatabase, MockExecResult, Statement, Transaction};
672
673 mod post {
674 use crate as sea_orm;
675 use crate::entity::prelude::*;
676
677 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
678 #[sea_orm(table_name = "posts")]
679 pub struct Model {
680 #[sea_orm(
681 primary_key,
682 auto_increment = false,
683 select_as = "INTEGER",
684 save_as = "TEXT"
685 )]
686 pub id_primary: i32,
687 #[sea_orm(
688 primary_key,
689 auto_increment = false,
690 select_as = "INTEGER",
691 save_as = "TEXT"
692 )]
693 pub id_secondary: i32,
694 pub title: String,
695 pub text: String,
696 }
697
698 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
699 pub enum Relation {}
700
701 impl ActiveModelBehavior for ActiveModel {}
702 }
703
704 let model = post::Model {
705 id_primary: 1,
706 id_secondary: 1001,
707 title: "News wrap up 2022".into(),
708 text: "brbrbrrrbrbrbrr...".into(),
709 };
710
711 let db = MockDatabase::new(DbBackend::Postgres)
712 .append_exec_results([MockExecResult {
713 last_insert_id: 1,
714 rows_affected: 1,
715 }])
716 .into_connection();
717
718 post::Entity::insert(model.into_active_model())
719 .exec(&db)
720 .await?;
721
722 assert_eq!(
723 db.into_transaction_log(),
724 [Transaction::many([Statement::from_sql_and_values(
725 DbBackend::Postgres,
726 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)"#,
727 [
728 1.into(),
729 1001.into(),
730 "News wrap up 2022".into(),
731 "brbrbrrrbrbrbrr...".into(),
732 ]
733 )])]
734 );
735
736 Ok(())
737 }
738}