1use crate::clause::Where;
11use crate::expr::{Dialect, Expr};
12use asupersync::{Cx, Outcome};
13use sqlmodel_core::{Connection, Model, Row, Value};
14use std::marker::PhantomData;
15
16#[derive(Debug, Clone)]
20pub enum OnConflict {
21 DoNothing,
23 DoUpdate {
25 columns: Vec<String>,
27 target: Vec<String>,
29 },
30}
31
32#[derive(Debug)]
49pub struct InsertBuilder<'a, M: Model> {
50 model: &'a M,
51 returning: bool,
52 on_conflict: Option<OnConflict>,
53}
54
55impl<'a, M: Model> InsertBuilder<'a, M> {
56 pub fn new(model: &'a M) -> Self {
58 Self {
59 model,
60 returning: false,
61 on_conflict: None,
62 }
63 }
64
65 pub fn returning(mut self) -> Self {
69 self.returning = true;
70 self
71 }
72
73 pub fn on_conflict_do_nothing(mut self) -> Self {
78 self.on_conflict = Some(OnConflict::DoNothing);
79 self
80 }
81
82 pub fn on_conflict_do_update(mut self, columns: &[&str]) -> Self {
96 self.on_conflict = Some(OnConflict::DoUpdate {
97 columns: columns.iter().map(|s| s.to_string()).collect(),
98 target: Vec::new(), });
100 self
101 }
102
103 pub fn on_conflict_target_do_update(mut self, target: &[&str], columns: &[&str]) -> Self {
110 self.on_conflict = Some(OnConflict::DoUpdate {
111 columns: columns.iter().map(|s| s.to_string()).collect(),
112 target: target.iter().map(|s| s.to_string()).collect(),
113 });
114 self
115 }
116
117 pub fn build(&self) -> (String, Vec<Value>) {
119 self.build_with_dialect(Dialect::default())
120 }
121
122 pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
124 let row = self.model.to_row();
125 let fields = M::fields();
126
127 let insert_fields: Vec<_> = row
128 .iter()
129 .map(|(name, value)| {
130 let field = fields.iter().find(|f| f.name == *name);
131 if let Some(f) = field {
132 if f.auto_increment && matches!(value, Value::Null) {
133 return (*name, Value::Default);
134 }
135 }
136 (*name, value.clone())
137 })
138 .collect();
139
140 let columns: Vec<_> = insert_fields.iter().map(|(name, _)| *name).collect();
141
142 let mut placeholders = Vec::new();
143 let mut params = Vec::new();
144
145 for (_, value) in insert_fields {
146 if matches!(value, Value::Default) {
147 if dialect == Dialect::Sqlite {
150 params.push(Value::Null);
151 placeholders.push(dialect.placeholder(params.len()));
152 } else {
153 placeholders.push("DEFAULT".to_string());
154 }
155 } else {
156 params.push(value);
157 placeholders.push(dialect.placeholder(params.len()));
158 }
159 }
160
161 let mut sql = format!(
162 "INSERT INTO {} ({}) VALUES ({})",
163 M::TABLE_NAME,
164 columns.join(", "),
165 placeholders.join(", ")
166 );
167
168 if let Some(on_conflict) = &self.on_conflict {
170 match on_conflict {
171 OnConflict::DoNothing => {
172 sql.push_str(" ON CONFLICT DO NOTHING");
173 }
174 OnConflict::DoUpdate {
175 columns: update_cols,
176 target,
177 } => {
178 sql.push_str(" ON CONFLICT");
179
180 let has_target = if !target.is_empty() {
183 sql.push_str(" (");
184 sql.push_str(&target.join(", "));
185 sql.push(')');
186 true
187 } else if !M::PRIMARY_KEY.is_empty() {
188 sql.push_str(" (");
189 sql.push_str(&M::PRIMARY_KEY.join(", "));
190 sql.push(')');
191 true
192 } else {
193 tracing::warn!(
196 table = M::TABLE_NAME,
197 "ON CONFLICT DO UPDATE requires a conflict target (primary key or explicit target). \
198 Falling back to DO NOTHING since no target is available."
199 );
200 sql.push_str(" DO NOTHING");
201 false
202 };
203
204 if has_target {
206 sql.push_str(" DO UPDATE SET ");
207
208 let update_set: Vec<String> = if update_cols.is_empty() {
210 columns .iter()
212 .filter(|name| !M::PRIMARY_KEY.contains(name)) .map(|name| format!("{} = EXCLUDED.{}", name, name))
214 .collect()
215 } else {
216 update_cols
217 .iter()
218 .map(|c| format!("{} = EXCLUDED.{}", c, c))
219 .collect()
220 };
221 sql.push_str(&update_set.join(", "));
222 }
223 }
224 }
225 }
226
227 if self.returning {
229 sql.push_str(" RETURNING *");
230 }
231
232 (sql, params)
233 }
234
235 pub async fn execute<C: Connection>(
237 self,
238 cx: &Cx,
239 conn: &C,
240 ) -> Outcome<i64, sqlmodel_core::Error> {
241 let (sql, params) = self.build_with_dialect(conn.dialect());
242 conn.insert(cx, &sql, ¶ms).await
243 }
244
245 pub async fn execute_returning<C: Connection>(
249 mut self,
250 cx: &Cx,
251 conn: &C,
252 ) -> Outcome<Option<Row>, sqlmodel_core::Error> {
253 self.returning = true;
254 let (sql, params) = self.build_with_dialect(conn.dialect());
255 conn.query_one(cx, &sql, ¶ms).await
256 }
257}
258
259#[derive(Debug)]
269pub struct InsertManyBuilder<'a, M: Model> {
270 models: &'a [M],
271 returning: bool,
272 on_conflict: Option<OnConflict>,
273}
274
275impl<'a, M: Model> InsertManyBuilder<'a, M> {
276 pub fn new(models: &'a [M]) -> Self {
278 Self {
279 models,
280 returning: false,
281 on_conflict: None,
282 }
283 }
284
285 pub fn returning(mut self) -> Self {
287 self.returning = true;
288 self
289 }
290
291 pub fn on_conflict_do_nothing(mut self) -> Self {
293 self.on_conflict = Some(OnConflict::DoNothing);
294 self
295 }
296
297 pub fn on_conflict_do_update(mut self, columns: &[&str]) -> Self {
299 self.on_conflict = Some(OnConflict::DoUpdate {
300 columns: columns.iter().map(|s| s.to_string()).collect(),
301 target: Vec::new(),
302 });
303 self
304 }
305
306 pub fn build(&self) -> (String, Vec<Value>) {
308 self.build_with_dialect(Dialect::default())
309 }
310
311 pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
313 if self.models.is_empty() {
314 return (String::new(), Vec::new());
315 }
316
317 let fields = M::fields();
318 let first_row = self.models[0].to_row();
319
320 let insert_columns: Vec<_> = first_row
324 .iter()
325 .filter(|(name, value)| {
326 let field = fields.iter().find(|f| f.name == *name);
327 if let Some(f) = field {
328 if f.auto_increment {
329 return true;
330 }
331 }
332 !matches!(value, Value::Null)
333 })
334 .map(|(name, _)| *name)
335 .collect();
336
337 let mut all_values = Vec::new();
338 let mut value_groups = Vec::new();
339
340 for model in self.models {
341 let row = model.to_row();
342 let values: Vec<_> = insert_columns
343 .iter()
344 .map(|col| {
345 let val = row
346 .iter()
347 .find(|(name, _)| name == col)
348 .map_or(Value::Null, |(_, v)| v.clone());
349
350 let field = fields.iter().find(|f| f.name == *col);
352 if let Some(f) = field {
353 if f.auto_increment && matches!(val, Value::Null) {
354 return Value::Default;
355 }
356 }
357 val
358 })
359 .collect();
360
361 let mut placeholders = Vec::new();
362 for v in &values {
363 if matches!(v, Value::Default) {
364 placeholders.push("DEFAULT".to_string());
365 } else {
366 all_values.push(v.clone());
367 placeholders.push(dialect.placeholder(all_values.len()));
368 }
369 }
370
371 value_groups.push(format!("({})", placeholders.join(", ")));
372 }
373
374 let mut sql = format!(
375 "INSERT INTO {} ({}) VALUES {}",
376 M::TABLE_NAME,
377 insert_columns.join(", "),
378 value_groups.join(", ")
379 );
380 if let Some(on_conflict) = &self.on_conflict {
382 match on_conflict {
383 OnConflict::DoNothing => {
384 sql.push_str(" ON CONFLICT DO NOTHING");
385 }
386 OnConflict::DoUpdate { columns, target } => {
387 sql.push_str(" ON CONFLICT");
388
389 let has_target = if !target.is_empty() {
391 sql.push_str(" (");
392 sql.push_str(&target.join(", "));
393 sql.push(')');
394 true
395 } else if !M::PRIMARY_KEY.is_empty() {
396 sql.push_str(" (");
397 sql.push_str(&M::PRIMARY_KEY.join(", "));
398 sql.push(')');
399 true
400 } else {
401 tracing::warn!(
403 table = M::TABLE_NAME,
404 "ON CONFLICT DO UPDATE requires a conflict target (primary key or explicit target). \
405 Falling back to DO NOTHING since no target is available."
406 );
407 sql.push_str(" DO NOTHING");
408 false
409 };
410
411 if has_target {
413 sql.push_str(" DO UPDATE SET ");
414
415 let update_cols: Vec<String> = if columns.is_empty() {
416 insert_columns
417 .iter()
418 .filter(|name| !M::PRIMARY_KEY.contains(name))
419 .map(|name| format!("{} = EXCLUDED.{}", name, name))
420 .collect()
421 } else {
422 columns
423 .iter()
424 .map(|c| format!("{} = EXCLUDED.{}", c, c))
425 .collect()
426 };
427 sql.push_str(&update_cols.join(", "));
428 }
429 }
430 }
431 }
432
433 if self.returning {
434 sql.push_str(" RETURNING *");
435 }
436
437 (sql, all_values)
438 }
439
440 pub async fn execute<C: Connection>(
442 self,
443 cx: &Cx,
444 conn: &C,
445 ) -> Outcome<u64, sqlmodel_core::Error> {
446 if self.models.is_empty() {
447 return Outcome::Ok(0);
448 }
449 let (sql, params) = self.build();
450 conn.execute(cx, &sql, ¶ms).await
451 }
452
453 pub async fn execute_returning<C: Connection>(
455 mut self,
456 cx: &Cx,
457 conn: &C,
458 ) -> Outcome<Vec<Row>, sqlmodel_core::Error> {
459 if self.models.is_empty() {
460 return Outcome::Ok(Vec::new());
461 }
462 self.returning = true;
463 let (sql, params) = self.build();
464 conn.query(cx, &sql, ¶ms).await
465 }
466}
467
468#[derive(Debug, Clone)]
470pub struct SetClause {
471 column: String,
472 value: Value,
473}
474
475#[derive(Debug)]
494pub struct UpdateBuilder<'a, M: Model> {
495 model: Option<&'a M>,
496 where_clause: Option<Where>,
497 set_fields: Option<Vec<&'static str>>,
498 explicit_sets: Vec<SetClause>,
499 returning: bool,
500}
501
502impl<'a, M: Model> UpdateBuilder<'a, M> {
503 pub fn new(model: &'a M) -> Self {
505 Self {
506 model: Some(model),
507 where_clause: None,
508 set_fields: None,
509 explicit_sets: Vec::new(),
510 returning: false,
511 }
512 }
513
514 pub fn empty() -> Self {
518 Self {
519 model: None,
520 where_clause: None,
521 set_fields: None,
522 explicit_sets: Vec::new(),
523 returning: false,
524 }
525 }
526
527 pub fn set<V: Into<Value>>(mut self, column: &str, value: V) -> Self {
532 self.explicit_sets.push(SetClause {
533 column: column.to_string(),
534 value: value.into(),
535 });
536 self
537 }
538
539 pub fn set_only(mut self, fields: &[&'static str]) -> Self {
541 self.set_fields = Some(fields.to_vec());
542 self
543 }
544
545 pub fn filter(mut self, expr: Expr) -> Self {
547 self.where_clause = Some(match self.where_clause {
548 Some(existing) => existing.and(expr),
549 None => Where::new(expr),
550 });
551 self
552 }
553
554 pub fn returning(mut self) -> Self {
556 self.returning = true;
557 self
558 }
559
560 pub fn build(&self) -> (String, Vec<Value>) {
562 self.build_with_dialect(Dialect::default())
563 }
564
565 pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
567 let pk = M::PRIMARY_KEY;
568 let mut params = Vec::new();
569 let mut set_clauses = Vec::new();
570
571 for set in &self.explicit_sets {
573 set_clauses.push(format!(
574 "{} = {}",
575 set.column,
576 dialect.placeholder(params.len() + 1)
577 ));
578 params.push(set.value.clone());
579 }
580
581 if let Some(model) = &self.model {
583 let row = model.to_row();
584
585 let update_fields: Vec<_> = row
587 .iter()
588 .filter(|(name, _)| {
589 if pk.contains(name) {
591 return false;
592 }
593 if self.explicit_sets.iter().any(|s| s.column == *name) {
595 return false;
596 }
597 if let Some(fields) = &self.set_fields {
599 return fields.contains(name);
600 }
601 true
602 })
603 .collect();
604
605 for (name, value) in update_fields {
606 set_clauses.push(format!(
607 "{} = {}",
608 name,
609 dialect.placeholder(params.len() + 1)
610 ));
611 params.push(value.clone());
612 }
613 }
614
615 if set_clauses.is_empty() {
616 return (String::new(), Vec::new());
618 }
619
620 let mut sql = format!("UPDATE {} SET {}", M::TABLE_NAME, set_clauses.join(", "));
621
622 if let Some(where_clause) = &self.where_clause {
624 let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
625 sql.push_str(" WHERE ");
626 sql.push_str(&where_sql);
627 params.extend(where_params);
628 } else if let Some(model) = &self.model {
629 let pk_values = model.primary_key_value();
631 let pk_conditions: Vec<_> = pk
632 .iter()
633 .zip(pk_values.iter())
634 .enumerate()
635 .map(|(i, (col, _))| {
636 format!("{} = {}", col, dialect.placeholder(params.len() + i + 1))
637 })
638 .collect();
639
640 if !pk_conditions.is_empty() {
641 sql.push_str(" WHERE ");
642 sql.push_str(&pk_conditions.join(" AND "));
643 params.extend(pk_values);
644 }
645 }
646
647 if self.returning {
649 sql.push_str(" RETURNING *");
650 }
651
652 (sql, params)
653 }
654
655 pub async fn execute<C: Connection>(
657 self,
658 cx: &Cx,
659 conn: &C,
660 ) -> Outcome<u64, sqlmodel_core::Error> {
661 let (sql, params) = self.build();
662 if sql.is_empty() {
663 return Outcome::Ok(0);
664 }
665 conn.execute(cx, &sql, ¶ms).await
666 }
667
668 pub async fn execute_returning<C: Connection>(
670 mut self,
671 cx: &Cx,
672 conn: &C,
673 ) -> Outcome<Vec<Row>, sqlmodel_core::Error> {
674 self.returning = true;
675 let (sql, params) = self.build();
676 if sql.is_empty() {
677 return Outcome::Ok(Vec::new());
678 }
679 conn.query(cx, &sql, ¶ms).await
680 }
681}
682
683#[derive(Debug)]
704pub struct DeleteBuilder<'a, M: Model> {
705 model: Option<&'a M>,
706 where_clause: Option<Where>,
707 returning: bool,
708 _marker: PhantomData<M>,
709}
710
711impl<'a, M: Model> DeleteBuilder<'a, M> {
712 pub fn new() -> Self {
714 Self {
715 model: None,
716 where_clause: None,
717 returning: false,
718 _marker: PhantomData,
719 }
720 }
721
722 pub fn from_model(model: &'a M) -> Self {
726 Self {
727 model: Some(model),
728 where_clause: None,
729 returning: false,
730 _marker: PhantomData,
731 }
732 }
733
734 pub fn filter(mut self, expr: Expr) -> Self {
736 self.where_clause = Some(match self.where_clause {
737 Some(existing) => existing.and(expr),
738 None => Where::new(expr),
739 });
740 self
741 }
742
743 pub fn returning(mut self) -> Self {
745 self.returning = true;
746 self
747 }
748
749 pub fn build(&self) -> (String, Vec<Value>) {
751 self.build_with_dialect(Dialect::default())
752 }
753
754 pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
756 let mut sql = format!("DELETE FROM {}", M::TABLE_NAME);
757 let mut params = Vec::new();
758
759 if let Some(where_clause) = &self.where_clause {
760 let (where_sql, where_params) = where_clause.build_with_dialect(dialect, 0);
761 sql.push_str(" WHERE ");
762 sql.push_str(&where_sql);
763 params = where_params;
764 } else if let Some(model) = &self.model {
765 let pk = M::PRIMARY_KEY;
767 let pk_values = model.primary_key_value();
768 let pk_conditions: Vec<_> = pk
769 .iter()
770 .zip(pk_values.iter())
771 .enumerate()
772 .map(|(i, (col, _))| format!("{} = {}", col, dialect.placeholder(i + 1)))
773 .collect();
774
775 if !pk_conditions.is_empty() {
776 sql.push_str(" WHERE ");
777 sql.push_str(&pk_conditions.join(" AND "));
778 params.extend(pk_values);
779 }
780 }
781
782 if self.returning {
784 sql.push_str(" RETURNING *");
785 }
786
787 (sql, params)
788 }
789
790 pub async fn execute<C: Connection>(
792 self,
793 cx: &Cx,
794 conn: &C,
795 ) -> Outcome<u64, sqlmodel_core::Error> {
796 let (sql, params) = self.build();
797 conn.execute(cx, &sql, ¶ms).await
798 }
799
800 pub async fn execute_returning<C: Connection>(
802 mut self,
803 cx: &Cx,
804 conn: &C,
805 ) -> Outcome<Vec<Row>, sqlmodel_core::Error> {
806 self.returning = true;
807 let (sql, params) = self.build();
808 conn.query(cx, &sql, ¶ms).await
809 }
810}
811
812impl<M: Model> Default for DeleteBuilder<'_, M> {
813 fn default() -> Self {
814 Self::new()
815 }
816}
817
818#[derive(Debug)]
820pub struct QueryBuilder {
821 sql: String,
822 params: Vec<Value>,
823}
824
825impl QueryBuilder {
826 pub fn new(sql: impl Into<String>) -> Self {
828 Self {
829 sql: sql.into(),
830 params: Vec::new(),
831 }
832 }
833
834 pub fn bind(mut self, value: impl Into<Value>) -> Self {
836 self.params.push(value.into());
837 self
838 }
839
840 pub fn bind_all(mut self, values: impl IntoIterator<Item = Value>) -> Self {
842 self.params.extend(values);
843 self
844 }
845
846 pub fn build(self) -> (String, Vec<Value>) {
848 (self.sql, self.params)
849 }
850}
851
852#[cfg(test)]
853mod tests {
854 use super::*;
855 use crate::expr::Dialect;
856 use sqlmodel_core::field::FieldInfo;
857 use sqlmodel_core::types::SqlType;
858
859 struct TestHero {
861 id: Option<i64>,
862 name: String,
863 age: i32,
864 }
865
866 impl Model for TestHero {
867 const TABLE_NAME: &'static str = "heroes";
868 const PRIMARY_KEY: &'static [&'static str] = &["id"];
869
870 fn fields() -> &'static [FieldInfo] {
871 static FIELDS: &[FieldInfo] = &[
872 FieldInfo::new("id", "id", SqlType::BigInt)
873 .primary_key(true)
874 .auto_increment(true)
875 .nullable(true),
876 FieldInfo::new("name", "name", SqlType::Text),
877 FieldInfo::new("age", "age", SqlType::Integer),
878 ];
879 FIELDS
880 }
881
882 fn to_row(&self) -> Vec<(&'static str, Value)> {
883 vec![
884 ("id", self.id.map_or(Value::Null, Value::BigInt)),
885 ("name", Value::Text(self.name.clone())),
886 ("age", Value::Int(self.age)),
887 ]
888 }
889
890 fn from_row(_row: &Row) -> sqlmodel_core::Result<Self> {
891 unimplemented!()
892 }
893
894 fn primary_key_value(&self) -> Vec<Value> {
895 vec![self.id.map_or(Value::Null, Value::BigInt)]
896 }
897
898 fn is_new(&self) -> bool {
899 self.id.is_none()
900 }
901 }
902
903 #[test]
904 fn test_insert_basic() {
905 let hero = TestHero {
906 id: None,
907 name: "Spider-Man".to_string(),
908 age: 25,
909 };
910 let (sql, params) = InsertBuilder::new(&hero).build();
911
912 assert_eq!(
914 sql,
915 "INSERT INTO heroes (id, name, age) VALUES (DEFAULT, $1, $2)"
916 );
917 assert_eq!(params.len(), 2);
918 }
919
920 #[test]
921 fn test_insert_returning() {
922 let hero = TestHero {
923 id: None,
924 name: "Spider-Man".to_string(),
925 age: 25,
926 };
927 let (sql, _) = InsertBuilder::new(&hero).returning().build();
928
929 assert!(sql.ends_with(" RETURNING *"));
930 }
931
932 #[test]
933 fn test_insert_on_conflict_do_nothing() {
934 let hero = TestHero {
935 id: None,
936 name: "Spider-Man".to_string(),
937 age: 25,
938 };
939 let (sql, _) = InsertBuilder::new(&hero).on_conflict_do_nothing().build();
940
941 assert!(sql.contains("ON CONFLICT DO NOTHING"));
942 }
943
944 #[test]
945 fn test_insert_on_conflict_do_update() {
946 let hero = TestHero {
947 id: None,
948 name: "Spider-Man".to_string(),
949 age: 25,
950 };
951 let (sql, _) = InsertBuilder::new(&hero)
952 .on_conflict_do_update(&["name", "age"])
953 .build();
954
955 assert!(sql.contains("ON CONFLICT (id) DO UPDATE SET"));
956 assert!(sql.contains("name = EXCLUDED.name"));
957 assert!(sql.contains("age = EXCLUDED.age"));
958 }
959
960 #[test]
961 fn test_insert_many() {
962 let heroes = vec![
963 TestHero {
964 id: None,
965 name: "Spider-Man".to_string(),
966 age: 25,
967 },
968 TestHero {
969 id: None,
970 name: "Iron Man".to_string(),
971 age: 45,
972 },
973 ];
974 let (sql, params) = InsertManyBuilder::new(&heroes).build();
975
976 assert!(sql.starts_with("INSERT INTO heroes (id, name, age) VALUES"));
978 assert!(sql.contains("(DEFAULT, $1, $2), (DEFAULT, $3, $4)"));
979 assert_eq!(params.len(), 4);
980 }
981
982 #[test]
983 fn test_update_basic() {
984 let hero = TestHero {
985 id: Some(1),
986 name: "Spider-Man".to_string(),
987 age: 26,
988 };
989 let (sql, params) = UpdateBuilder::new(&hero).build();
990
991 assert!(sql.starts_with("UPDATE heroes SET"));
992 assert!(sql.contains("WHERE id = "));
993 assert!(params.len() >= 2); }
995
996 #[test]
997 fn test_update_explicit_set() {
998 let (sql, params) = UpdateBuilder::<TestHero>::empty()
999 .set("age", 30)
1000 .filter(Expr::col("id").eq(1))
1001 .build_with_dialect(Dialect::Postgres);
1002
1003 assert_eq!(sql, "UPDATE heroes SET age = $1 WHERE \"id\" = $2");
1004 assert_eq!(params.len(), 2);
1005 }
1006
1007 #[test]
1008 fn test_update_returning() {
1009 let hero = TestHero {
1010 id: Some(1),
1011 name: "Spider-Man".to_string(),
1012 age: 26,
1013 };
1014 let (sql, _) = UpdateBuilder::new(&hero).returning().build();
1015
1016 assert!(sql.ends_with(" RETURNING *"));
1017 }
1018
1019 #[test]
1020 fn test_delete_basic() {
1021 let (sql, _) = DeleteBuilder::<TestHero>::new()
1022 .filter(Expr::col("age").lt(18))
1023 .build_with_dialect(Dialect::Postgres);
1024
1025 assert_eq!(sql, "DELETE FROM heroes WHERE \"age\" < $1");
1026 }
1027
1028 #[test]
1029 fn test_delete_from_model() {
1030 let hero = TestHero {
1031 id: Some(42),
1032 name: "Spider-Man".to_string(),
1033 age: 25,
1034 };
1035 let (sql, params) = DeleteBuilder::from_model(&hero).build();
1036
1037 assert!(sql.contains("WHERE id = $1"));
1038 assert_eq!(params.len(), 1);
1039 }
1040
1041 #[test]
1042 fn test_delete_returning() {
1043 let (sql, _) = DeleteBuilder::<TestHero>::new()
1044 .filter(Expr::col("status").eq("inactive"))
1045 .returning()
1046 .build_with_dialect(Dialect::Postgres);
1047
1048 assert!(sql.ends_with(" RETURNING *"));
1049 }
1050
1051 #[test]
1052 fn test_dialect_sqlite() {
1053 let hero = TestHero {
1054 id: None,
1055 name: "Spider-Man".to_string(),
1056 age: 25,
1057 };
1058 let (sql, _) = InsertBuilder::new(&hero).build_with_dialect(Dialect::Sqlite);
1059
1060 assert!(sql.contains("?1"));
1061 assert!(sql.contains("?2"));
1062 }
1063
1064 #[test]
1065 fn test_dialect_mysql() {
1066 let hero = TestHero {
1067 id: None,
1068 name: "Spider-Man".to_string(),
1069 age: 25,
1070 };
1071 let (sql, _) = InsertBuilder::new(&hero).build_with_dialect(Dialect::Mysql);
1072
1073 assert!(sql.contains('?'));
1075 assert!(!sql.contains("$1"));
1076 }
1077}