1use crate::db_type::DbType;
27use crate::dialect::Dialect;
28use crate::dialect::LockType;
29use crate::model::Model;
30use crate::typed::TypedColumn;
31use crate::value::Value;
32use std::fmt;
33use std::time::Duration;
34
35pub struct QueryBuilder<M: Model> {
37 table: Option<String>,
38 select_columns: Vec<String>,
39 where_conditions: Vec<WhereCondition>,
40 order_by: Vec<OrderClause>,
41 group_by: Vec<String>,
42 having_conditions: Vec<WhereCondition>,
43 limit_value: Option<usize>,
44 offset_value: Option<usize>,
45 joins: Vec<JoinClause>,
46 dialect: Box<dyn Dialect>,
47 soft_delete_disabled: bool,
49 tenant_id_value: Option<i64>,
51 tenant_disabled: bool,
53 keyset_cursor: Option<KeysetCursor>,
59 cache_ttl: Option<Duration>,
64 lock_type: Option<LockType>,
72 insert_or_ignore: bool,
77 #[allow(dead_code)]
78 model: std::marker::PhantomData<M>,
79}
80
81#[derive(Debug, Clone)]
89struct KeysetCursor {
90 field: String,
92 value: Value,
94 direction: KeysetDirection,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100enum KeysetDirection {
101 After,
103 Before,
105}
106
107#[derive(Debug, Clone)]
108#[allow(dead_code)]
109enum WhereCondition {
110 And(String),
115 Or(String),
117 Eq(String, Value),
119 Ne(String, Value),
121 Gt(String, Value),
123 Ge(String, Value),
125 Lt(String, Value),
127 Le(String, Value),
129 Like(String, Value),
131 OrEq(String, Value),
133 OrNe(String, Value),
135 OrGt(String, Value),
137 OrGe(String, Value),
139 OrLt(String, Value),
141 OrLe(String, Value),
143 OrLike(String, Value),
145 In(String, Vec<Value>),
146 NotIn(String, Vec<Value>),
147 Between(String, Value, Value),
148 NotBetween(String, Value, Value),
149 Null(String),
150 NotNull(String),
151 Exists(String),
152 NotExists(String),
153}
154
155#[derive(Debug, Clone)]
156struct OrderClause {
157 field: String,
158 direction: OrderDirection,
159}
160
161#[derive(Debug, Clone)]
162enum OrderDirection {
163 Asc,
164 Desc,
165}
166
167#[derive(Debug, Clone)]
168#[allow(dead_code)]
169enum JoinClause {
170 Inner(String, String, String),
171 Left(String, String, String),
172 Right(String, String, String),
173 Cross(String, String),
174}
175
176impl<M: Model> QueryBuilder<M> {
177 pub fn new(dialect: Box<dyn Dialect>) -> Self {
178 Self {
179 table: None,
180 select_columns: vec!["*".to_string()],
181 where_conditions: Vec::new(),
182 order_by: Vec::new(),
183 group_by: Vec::new(),
184 having_conditions: Vec::new(),
185 limit_value: None,
186 offset_value: None,
187 joins: Vec::new(),
188 dialect,
189 soft_delete_disabled: false,
190 tenant_id_value: None,
191 tenant_disabled: false,
192 keyset_cursor: None,
193 cache_ttl: None,
194 lock_type: None,
195 insert_or_ignore: false,
196 model: std::marker::PhantomData,
197 }
198 }
199
200 pub fn table(mut self, table: impl Into<String>) -> Self {
201 self.table = Some(table.into());
202 self
203 }
204
205 pub fn without_soft_delete(mut self) -> Self {
224 self.soft_delete_disabled = true;
225 self
226 }
227
228 pub fn is_soft_delete_disabled(&self) -> bool {
230 self.soft_delete_disabled
231 }
232
233 pub fn cache_ttl(mut self, ttl: Duration) -> Self {
253 self.cache_ttl = Some(ttl);
254 self
255 }
256
257 pub fn get_cache_ttl(&self) -> Option<Duration> {
259 self.cache_ttl
260 }
261
262 pub fn lock_for_update(mut self) -> Result<Self, crate::error::DbError> {
287 if !self.dialect.supports_lock_for_update() {
288 return Err(crate::error::DbError::QueryError(
289 "FOR UPDATE lock is not supported by this dialect".to_string(),
290 ));
291 }
292 self.lock_type = Some(LockType::ForUpdate);
293 Ok(self)
294 }
295
296 pub fn lock_shared(mut self) -> Result<Self, crate::error::DbError> {
321 if !self.dialect.supports_lock_shared() {
322 return Err(crate::error::DbError::QueryError(
323 "Shared lock is not supported by this dialect".to_string(),
324 ));
325 }
326 self.lock_type = Some(LockType::Shared);
327 Ok(self)
328 }
329
330 pub fn insert_or_ignore(mut self) -> Self {
349 self.insert_or_ignore = true;
350 self
351 }
352
353 pub fn get_lock_type(&self) -> Option<LockType> {
355 self.lock_type
356 }
357
358 pub fn is_insert_or_ignore(&self) -> bool {
360 self.insert_or_ignore
361 }
362
363 pub fn clone_for_count(&self) -> Self {
370 Self {
371 table: self.table.clone(),
372 select_columns: vec!["*".to_string()],
373 where_conditions: self.where_conditions.clone(),
374 order_by: Vec::new(),
375 group_by: self.group_by.clone(),
376 having_conditions: self.having_conditions.clone(),
377 limit_value: None,
378 offset_value: None,
379 joins: self.joins.clone(),
380 dialect: self.dialect.clone_box(),
381 soft_delete_disabled: self.soft_delete_disabled,
382 tenant_id_value: self.tenant_id_value,
383 tenant_disabled: self.tenant_disabled,
384 keyset_cursor: None,
385 cache_ttl: None, lock_type: None, insert_or_ignore: false, model: std::marker::PhantomData,
389 }
390 }
391
392 fn soft_delete_field(&self) -> Option<&'static str> {
396 if self.soft_delete_disabled {
397 return None;
398 }
399 M::soft_delete_field()
400 }
401
402 fn build_soft_delete_condition(&self) -> Option<String> {
406 self.soft_delete_field()
407 .map(|field| format!("{} IS NULL", self.dialect.quote(field)))
408 }
409
410 pub fn with_tenant_id(mut self, tenant_id: i64) -> Self {
437 self.tenant_id_value = Some(tenant_id);
438 self
439 }
440
441 pub fn without_tenant(mut self) -> Self {
445 self.tenant_disabled = true;
446 self
447 }
448
449 pub fn is_tenant_disabled(&self) -> bool {
451 self.tenant_disabled
452 }
453
454 fn tenant_field(&self) -> Option<&'static str> {
458 if self.tenant_disabled {
459 return None;
460 }
461 M::tenant_field()
462 }
463
464 fn tenant_id_value(&self) -> Option<i64> {
466 if self.tenant_disabled {
467 return None;
468 }
469 self.tenant_id_value
470 }
471
472 fn build_tenant_condition(&self) -> Option<(String, Value)> {
477 let field = self.tenant_field()?;
478 let tid = self.tenant_id_value()?;
479 Some((
480 format!("{} = ?", self.dialect.quote(field)),
481 Value::I64(tid),
482 ))
483 }
484
485 pub fn select(mut self, columns: Vec<&str>) -> Self {
493 self.select_columns = columns.into_iter().map(|s| s.to_string()).collect();
494 self
495 }
496
497 pub fn select_quoted(mut self, columns: Vec<&str>) -> Result<Self, crate::DbError> {
506 let mut quoted = Vec::with_capacity(columns.len());
507 for col in columns {
508 crate::sql_safety::validate_identifier(col, "select column")?;
509 quoted.push(self.dialect.quote(col));
510 }
511 self.select_columns = quoted;
512 Ok(self)
513 }
514
515 pub fn where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
529 self.where_conditions
530 .push(WhereCondition::Eq(field.into(), value));
531 self
532 }
533
534 pub fn where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
536 self.where_conditions
537 .push(WhereCondition::Ne(field.into(), value));
538 self
539 }
540
541 pub fn where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
543 self.where_conditions
544 .push(WhereCondition::Gt(field.into(), value));
545 self
546 }
547
548 pub fn where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
550 self.where_conditions
551 .push(WhereCondition::Ge(field.into(), value));
552 self
553 }
554
555 pub fn where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
557 self.where_conditions
558 .push(WhereCondition::Lt(field.into(), value));
559 self
560 }
561
562 pub fn where_le(mut self, field: impl Into<String>, value: Value) -> Self {
564 self.where_conditions
565 .push(WhereCondition::Le(field.into(), value));
566 self
567 }
568
569 pub fn where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
581 self.where_conditions
582 .push(WhereCondition::Like(field.into(), pattern));
583 self
584 }
585
586 pub fn or_where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
590 self.where_conditions
591 .push(WhereCondition::OrEq(field.into(), value));
592 self
593 }
594
595 pub fn or_where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
597 self.where_conditions
598 .push(WhereCondition::OrNe(field.into(), value));
599 self
600 }
601
602 pub fn or_where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
604 self.where_conditions
605 .push(WhereCondition::OrGt(field.into(), value));
606 self
607 }
608
609 pub fn or_where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
611 self.where_conditions
612 .push(WhereCondition::OrGe(field.into(), value));
613 self
614 }
615
616 pub fn or_where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
618 self.where_conditions
619 .push(WhereCondition::OrLt(field.into(), value));
620 self
621 }
622
623 pub fn or_where_le(mut self, field: impl Into<String>, value: Value) -> Self {
625 self.where_conditions
626 .push(WhereCondition::OrLe(field.into(), value));
627 self
628 }
629
630 pub fn or_where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
632 self.where_conditions
633 .push(WhereCondition::OrLike(field.into(), pattern));
634 self
635 }
636
637 pub fn where_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
638 self.where_conditions
639 .push(WhereCondition::In(field.into(), values));
640 self
641 }
642
643 pub fn where_not_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
644 self.where_conditions
645 .push(WhereCondition::NotIn(field.into(), values));
646 self
647 }
648
649 pub fn where_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
650 self.where_conditions
651 .push(WhereCondition::Between(field.into(), start, end));
652 self
653 }
654
655 pub fn where_not_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
656 self.where_conditions
657 .push(WhereCondition::NotBetween(field.into(), start, end));
658 self
659 }
660
661 pub fn where_null(mut self, field: impl Into<String>) -> Self {
662 self.where_conditions
663 .push(WhereCondition::Null(field.into()));
664 self
665 }
666
667 pub fn where_not_null(mut self, field: impl Into<String>) -> Self {
668 self.where_conditions
669 .push(WhereCondition::NotNull(field.into()));
670 self
671 }
672
673 pub fn order_by(mut self, field: impl Into<String>) -> Self {
674 self.order_by.push(OrderClause {
675 field: field.into(),
676 direction: OrderDirection::Asc,
677 });
678 self
679 }
680
681 pub fn order_desc(mut self, field: impl Into<String>) -> Self {
682 self.order_by.push(OrderClause {
683 field: field.into(),
684 direction: OrderDirection::Desc,
685 });
686 self
687 }
688
689 pub fn group_by(mut self, field: impl Into<String>) -> Self {
690 self.group_by.push(field.into());
691 self
692 }
693
694 pub fn where_eq_typed<C: TypedColumn>(mut self, value: Value) -> Self {
713 self.where_conditions
714 .push(WhereCondition::Eq(C::NAME.to_string(), value));
715 self
716 }
717
718 pub fn where_ne_typed<C: TypedColumn>(mut self, value: Value) -> Self {
720 self.where_conditions
721 .push(WhereCondition::Ne(C::NAME.to_string(), value));
722 self
723 }
724
725 pub fn where_gt_typed<C: TypedColumn>(mut self, value: Value) -> Self {
727 self.where_conditions
728 .push(WhereCondition::Gt(C::NAME.to_string(), value));
729 self
730 }
731
732 pub fn where_ge_typed<C: TypedColumn>(mut self, value: Value) -> Self {
734 self.where_conditions
735 .push(WhereCondition::Ge(C::NAME.to_string(), value));
736 self
737 }
738
739 pub fn where_lt_typed<C: TypedColumn>(mut self, value: Value) -> Self {
741 self.where_conditions
742 .push(WhereCondition::Lt(C::NAME.to_string(), value));
743 self
744 }
745
746 pub fn where_le_typed<C: TypedColumn>(mut self, value: Value) -> Self {
748 self.where_conditions
749 .push(WhereCondition::Le(C::NAME.to_string(), value));
750 self
751 }
752
753 pub fn where_null_typed<C: TypedColumn>(mut self) -> Self {
755 self.where_conditions
756 .push(WhereCondition::Null(C::NAME.to_string()));
757 self
758 }
759
760 pub fn where_not_null_typed<C: TypedColumn>(mut self) -> Self {
762 self.where_conditions
763 .push(WhereCondition::NotNull(C::NAME.to_string()));
764 self
765 }
766
767 pub fn order_by_typed<C: TypedColumn>(mut self) -> Self {
769 self.order_by.push(OrderClause {
770 field: C::NAME.to_string(),
771 direction: OrderDirection::Asc,
772 });
773 self
774 }
775
776 pub fn order_desc_typed<C: TypedColumn>(mut self) -> Self {
778 self.order_by.push(OrderClause {
779 field: C::NAME.to_string(),
780 direction: OrderDirection::Desc,
781 });
782 self
783 }
784
785 pub fn group_by_typed<C: TypedColumn>(mut self) -> Self {
787 self.group_by.push(C::NAME.to_string());
788 self
789 }
790
791 pub fn select_typed<C: TypedColumn>(mut self) -> Self {
795 self.select_columns.push(C::NAME.to_string());
796 self
797 }
798
799 pub fn select_typed_cols<C: TypedColumn, const N: usize>(mut self) -> Self {
801 self.select_columns.push(C::NAME.to_string());
804 self
805 }
806
807 pub fn having(mut self, condition: impl Into<String>) -> Self {
808 self.having_conditions
809 .push(WhereCondition::And(condition.into()));
810 self
811 }
812
813 pub fn limit(mut self, limit: usize) -> Self {
814 self.limit_value = Some(limit);
815 self
816 }
817
818 pub fn offset(mut self, offset: usize) -> Self {
819 self.offset_value = Some(offset);
820 self
821 }
822
823 pub fn page(mut self, page: usize, page_size: usize) -> Self {
824 self.limit_value = Some(page_size);
825 self.offset_value = Some((page.saturating_sub(1)) * page_size);
826 self
827 }
828
829 pub fn keyset_after(
875 mut self,
876 field: impl Into<String>,
877 cursor_value: Value,
878 page_size: usize,
879 ) -> Self {
880 let field_str = field.into();
881 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
883 existing.direction = OrderDirection::Asc;
884 } else {
885 self.order_by.push(OrderClause {
886 field: field_str.clone(),
887 direction: OrderDirection::Asc,
888 });
889 }
890 self.limit_value = Some(page_size);
891 self.offset_value = None;
893 self.keyset_cursor = Some(KeysetCursor {
894 field: field_str,
895 value: cursor_value,
896 direction: KeysetDirection::After,
897 });
898 self
899 }
900
901 pub fn keyset_before(
947 mut self,
948 field: impl Into<String>,
949 cursor_value: Value,
950 page_size: usize,
951 ) -> Self {
952 let field_str = field.into();
953 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
955 existing.direction = OrderDirection::Desc;
956 } else {
957 self.order_by.push(OrderClause {
958 field: field_str.clone(),
959 direction: OrderDirection::Desc,
960 });
961 }
962 self.limit_value = Some(page_size);
963 self.offset_value = None;
964 self.keyset_cursor = Some(KeysetCursor {
965 field: field_str,
966 value: cursor_value,
967 direction: KeysetDirection::Before,
968 });
969 self
970 }
971
972 pub fn join_inner(
973 mut self,
974 table: impl Into<String>,
975 on_left: impl Into<String>,
976 on_right: impl Into<String>,
977 ) -> Self {
978 self.joins.push(JoinClause::Inner(
979 table.into(),
980 on_left.into(),
981 on_right.into(),
982 ));
983 self
984 }
985
986 pub fn join_left(
987 mut self,
988 table: impl Into<String>,
989 on_left: impl Into<String>,
990 on_right: impl Into<String>,
991 ) -> Self {
992 self.joins.push(JoinClause::Left(
993 table.into(),
994 on_left.into(),
995 on_right.into(),
996 ));
997 self
998 }
999
1000 pub fn join_right(
1001 mut self,
1002 table: impl Into<String>,
1003 on_left: impl Into<String>,
1004 on_right: impl Into<String>,
1005 ) -> Self {
1006 self.joins.push(JoinClause::Right(
1007 table.into(),
1008 on_left.into(),
1009 on_right.into(),
1010 ));
1011 self
1012 }
1013
1014 #[tracing::instrument(skip(self), fields(op = "select"))]
1047 pub fn build_select(&self) -> String {
1048 let table = self
1049 .table
1050 .clone()
1051 .unwrap_or_else(|| M::table_name().to_string());
1052
1053 let columns = if self.select_columns.is_empty() {
1054 "*".to_string()
1055 } else {
1056 self.select_columns.join(", ")
1057 };
1058
1059 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1060
1061 for join in &self.joins {
1062 match join {
1063 JoinClause::Inner(t, l, r) => {
1064 sql.push_str(&format!(
1065 " INNER JOIN {} ON {} = {}",
1066 self.dialect.quote(t),
1067 self.dialect.quote(l),
1068 self.dialect.quote(r)
1069 ));
1070 }
1071 JoinClause::Left(t, l, r) => {
1072 sql.push_str(&format!(
1073 " LEFT JOIN {} ON {} = {}",
1074 self.dialect.quote(t),
1075 self.dialect.quote(l),
1076 self.dialect.quote(r)
1077 ));
1078 }
1079 JoinClause::Right(t, l, r) => {
1080 sql.push_str(&format!(
1081 " RIGHT JOIN {} ON {} = {}",
1082 self.dialect.quote(t),
1083 self.dialect.quote(l),
1084 self.dialect.quote(r)
1085 ));
1086 }
1087 JoinClause::Cross(t, on) => {
1088 sql.push_str(&format!(
1089 " CROSS JOIN {} ON {}",
1090 self.dialect.quote(t),
1091 self.dialect.quote(on)
1092 ));
1093 }
1094 }
1095 }
1096
1097 let where_clause = self.build_where_clause();
1099 if !where_clause.is_empty() {
1100 sql.push_str(&where_clause);
1101 }
1102
1103 if !self.group_by.is_empty() {
1104 let cols: Vec<String> = self
1105 .group_by
1106 .iter()
1107 .map(|c| self.dialect.quote(c))
1108 .collect();
1109 sql.push_str(" GROUP BY ");
1110 sql.push_str(&cols.join(", "));
1111 }
1112
1113 if !self.having_conditions.is_empty() {
1114 sql.push_str(" HAVING ");
1115 for (i, cond) in self.having_conditions.iter().enumerate() {
1116 if i > 0 {
1117 sql.push_str(" AND ");
1118 }
1119 if let WhereCondition::And(c) = cond {
1120 sql.push_str(c);
1121 }
1122 }
1123 }
1124
1125 if !self.order_by.is_empty() {
1126 let order_cols: Vec<String> = self
1127 .order_by
1128 .iter()
1129 .map(|o| {
1130 let dir = match o.direction {
1131 OrderDirection::Asc => " ASC",
1132 OrderDirection::Desc => " DESC",
1133 };
1134 format!("{}{}", self.dialect.quote(&o.field), dir)
1135 })
1136 .collect();
1137 sql.push_str(" ORDER BY ");
1138 sql.push_str(&order_cols.join(", "));
1139 }
1140
1141 if let Some(limit) = self.limit_value {
1142 sql.push_str(&format!(" LIMIT {}", limit));
1143 }
1144
1145 if let Some(offset) = self.offset_value {
1146 sql.push_str(&format!(" OFFSET {}", offset));
1147 }
1148
1149 sql
1150 }
1151
1152 fn build_where_clause(&self) -> String {
1158 self.build_where_clause_with_options(true)
1159 }
1160
1161 fn build_where_clause_with_options(&self, include_soft_delete: bool) -> String {
1169 let soft_delete_cond = if include_soft_delete {
1171 self.build_soft_delete_condition()
1172 } else {
1173 None
1174 };
1175
1176 let tenant_cond = self.build_tenant_condition().map(|(sql, value)| {
1178 sql.replacen('?', &value.to_param_with_dialect(&*self.dialect), 1)
1180 });
1181
1182 if self.where_conditions.is_empty()
1184 && soft_delete_cond.is_none()
1185 && tenant_cond.is_none()
1186 && self.keyset_cursor.is_none()
1187 {
1188 return String::new();
1189 }
1190
1191 let mut conditions: Vec<String> = self
1193 .where_conditions
1194 .iter()
1195 .map(|cond| match cond {
1196 WhereCondition::And(c) => c.clone(),
1197 WhereCondition::Or(c) => format!("OR {}", c),
1198 WhereCondition::Eq(f, v) => format!(
1200 "{} = {}",
1201 self.dialect.quote(f),
1202 v.to_param_with_dialect(&*self.dialect)
1203 ),
1204 WhereCondition::Ne(f, v) => format!(
1205 "{} != {}",
1206 self.dialect.quote(f),
1207 v.to_param_with_dialect(&*self.dialect)
1208 ),
1209 WhereCondition::Gt(f, v) => format!(
1210 "{} > {}",
1211 self.dialect.quote(f),
1212 v.to_param_with_dialect(&*self.dialect)
1213 ),
1214 WhereCondition::Ge(f, v) => format!(
1215 "{} >= {}",
1216 self.dialect.quote(f),
1217 v.to_param_with_dialect(&*self.dialect)
1218 ),
1219 WhereCondition::Lt(f, v) => format!(
1220 "{} < {}",
1221 self.dialect.quote(f),
1222 v.to_param_with_dialect(&*self.dialect)
1223 ),
1224 WhereCondition::Le(f, v) => format!(
1225 "{} <= {}",
1226 self.dialect.quote(f),
1227 v.to_param_with_dialect(&*self.dialect)
1228 ),
1229 WhereCondition::Like(f, v) => format!(
1230 "{} LIKE {}",
1231 self.dialect.quote(f),
1232 v.to_param_with_dialect(&*self.dialect)
1233 ),
1234 WhereCondition::OrEq(f, v) => format!(
1235 "OR {} = {}",
1236 self.dialect.quote(f),
1237 v.to_param_with_dialect(&*self.dialect)
1238 ),
1239 WhereCondition::OrNe(f, v) => format!(
1240 "OR {} != {}",
1241 self.dialect.quote(f),
1242 v.to_param_with_dialect(&*self.dialect)
1243 ),
1244 WhereCondition::OrGt(f, v) => format!(
1245 "OR {} > {}",
1246 self.dialect.quote(f),
1247 v.to_param_with_dialect(&*self.dialect)
1248 ),
1249 WhereCondition::OrGe(f, v) => format!(
1250 "OR {} >= {}",
1251 self.dialect.quote(f),
1252 v.to_param_with_dialect(&*self.dialect)
1253 ),
1254 WhereCondition::OrLt(f, v) => format!(
1255 "OR {} < {}",
1256 self.dialect.quote(f),
1257 v.to_param_with_dialect(&*self.dialect)
1258 ),
1259 WhereCondition::OrLe(f, v) => format!(
1260 "OR {} <= {}",
1261 self.dialect.quote(f),
1262 v.to_param_with_dialect(&*self.dialect)
1263 ),
1264 WhereCondition::OrLike(f, v) => format!(
1265 "OR {} LIKE {}",
1266 self.dialect.quote(f),
1267 v.to_param_with_dialect(&*self.dialect)
1268 ),
1269 WhereCondition::In(f, vals) => {
1270 let vals_str: Vec<String> = vals
1272 .iter()
1273 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1274 .collect();
1275 format!("{} IN ({})", self.dialect.quote(f), vals_str.join(", "))
1276 }
1277 WhereCondition::NotIn(f, vals) => {
1278 let vals_str: Vec<String> = vals
1279 .iter()
1280 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1281 .collect();
1282 format!("{} NOT IN ({})", self.dialect.quote(f), vals_str.join(", "))
1283 }
1284 WhereCondition::Between(f, start, end) => {
1285 format!(
1286 "{} BETWEEN {} AND {}",
1287 self.dialect.quote(f),
1288 start.to_param_with_dialect(&*self.dialect),
1289 end.to_param_with_dialect(&*self.dialect)
1290 )
1291 }
1292 WhereCondition::NotBetween(f, start, end) => {
1293 format!(
1294 "{} NOT BETWEEN {} AND {}",
1295 self.dialect.quote(f),
1296 start.to_param_with_dialect(&*self.dialect),
1297 end.to_param_with_dialect(&*self.dialect)
1298 )
1299 }
1300 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1301 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1302 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1303 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1304 })
1305 .collect();
1306
1307 if let Some(sd_cond) = soft_delete_cond {
1309 conditions.push(sd_cond);
1310 }
1311
1312 if let Some(t_cond) = tenant_cond {
1314 conditions.push(t_cond);
1315 }
1316
1317 if let Some(ref cursor) = self.keyset_cursor {
1319 let op = match cursor.direction {
1320 KeysetDirection::After => ">",
1321 KeysetDirection::Before => "<",
1322 };
1323 conditions.push(format!(
1324 "{} {} {}",
1325 self.dialect.quote(&cursor.field),
1326 op,
1327 cursor.value.to_param_with_dialect(&*self.dialect)
1328 ));
1329 }
1330
1331 if conditions.is_empty() {
1332 return String::new();
1333 }
1334
1335 let mut groups: Vec<Vec<String>> = Vec::new();
1338 let mut current_group: Vec<String> = Vec::new();
1339 for cond in conditions.iter() {
1340 if let Some(stripped) = cond.strip_prefix("OR ") {
1341 current_group.push(stripped.to_string());
1343 } else {
1344 if !current_group.is_empty() {
1346 groups.push(std::mem::take(&mut current_group));
1347 }
1348 current_group.push(cond.clone());
1349 }
1350 }
1351 if !current_group.is_empty() {
1352 groups.push(current_group);
1353 }
1354
1355 let group_strs: Vec<String> = groups
1356 .iter()
1357 .map(|g| {
1358 if g.len() == 1 {
1359 g[0].clone()
1360 } else {
1361 format!("({})", g.join(" OR "))
1362 }
1363 })
1364 .collect();
1365
1366 format!(" WHERE {}", group_strs.join(" AND "))
1368 }
1369
1370 #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1371 pub fn build_insert(&self, data: &std::collections::HashMap<String, Value>) -> String {
1372 let table = self
1373 .table
1374 .clone()
1375 .unwrap_or_else(|| M::table_name().to_string());
1376
1377 if data.is_empty() {
1378 return String::new();
1379 }
1380
1381 let columns: Vec<String> = data.keys().map(|k| self.dialect.quote(k)).collect();
1382 let values: Vec<String> = data
1384 .values()
1385 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1386 .collect();
1387
1388 format!(
1389 "INSERT INTO {} ({}) VALUES ({})",
1390 self.dialect.quote(&table),
1391 columns.join(", "),
1392 values.join(", ")
1393 )
1394 }
1395
1396 #[tracing::instrument(skip(self, data), fields(op = "update"))]
1397 pub fn build_update(&self, data: &std::collections::HashMap<String, Value>) -> String {
1398 let table = self
1399 .table
1400 .clone()
1401 .unwrap_or_else(|| M::table_name().to_string());
1402
1403 if data.is_empty() {
1404 return String::new();
1405 }
1406
1407 let set_clauses: Vec<String> = data
1408 .iter()
1409 .map(|(k, v)| {
1410 format!(
1411 "{} = {}",
1412 self.dialect.quote(k),
1413 v.to_param_with_dialect(&*self.dialect)
1414 )
1415 })
1416 .collect();
1417
1418 let mut sql = format!(
1419 "UPDATE {} SET {}",
1420 self.dialect.quote(&table),
1421 set_clauses.join(", ")
1422 );
1423
1424 sql.push_str(&self.build_where_clause());
1425 sql
1426 }
1427
1428 #[tracing::instrument(skip(self), fields(op = "delete"))]
1439 pub fn build_delete(&self) -> String {
1440 let table = self
1441 .table
1442 .clone()
1443 .unwrap_or_else(|| M::table_name().to_string());
1444
1445 if let Some(field) = self.soft_delete_field() {
1447 let where_clause = self.build_where_clause();
1448 return format!(
1449 "UPDATE {} SET {} = NOW(){}",
1450 self.dialect.quote(&table),
1451 self.dialect.quote(field),
1452 where_clause
1453 );
1454 }
1455
1456 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1457 sql.push_str(&self.build_where_clause());
1458 sql
1459 }
1460
1461 pub fn build_force_delete(&self) -> String {
1471 let table = self
1472 .table
1473 .clone()
1474 .unwrap_or_else(|| M::table_name().to_string());
1475
1476 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1477 sql.push_str(&self.build_where_clause_with_options(false));
1479 sql
1480 }
1481
1482 fn build_where_clause_with_params(&self) -> (String, Vec<Value>) {
1492 self.build_where_clause_with_params_options(true)
1494 }
1495
1496 fn build_where_clause_with_params_options(
1505 &self,
1506 include_soft_delete: bool,
1507 ) -> (String, Vec<Value>) {
1508 let soft_delete_cond = if include_soft_delete {
1510 self.build_soft_delete_condition()
1511 } else {
1512 None
1513 };
1514
1515 let tenant_cond = self.build_tenant_condition();
1517
1518 if self.where_conditions.is_empty()
1520 && soft_delete_cond.is_none()
1521 && tenant_cond.is_none()
1522 && self.keyset_cursor.is_none()
1523 {
1524 return (String::new(), Vec::new());
1525 }
1526
1527 let mut params = Vec::new();
1528
1529 let mut conditions: Vec<String> = self
1530 .where_conditions
1531 .iter()
1532 .map(|cond| match cond {
1533 WhereCondition::And(c) => c.clone(),
1534 WhereCondition::Or(c) => format!("OR {}", c),
1535 WhereCondition::Eq(f, v) => {
1537 params.push(v.clone());
1538 format!("{} = ?", self.dialect.quote(f))
1539 }
1540 WhereCondition::Ne(f, v) => {
1541 params.push(v.clone());
1542 format!("{} != ?", self.dialect.quote(f))
1543 }
1544 WhereCondition::Gt(f, v) => {
1545 params.push(v.clone());
1546 format!("{} > ?", self.dialect.quote(f))
1547 }
1548 WhereCondition::Ge(f, v) => {
1549 params.push(v.clone());
1550 format!("{} >= ?", self.dialect.quote(f))
1551 }
1552 WhereCondition::Lt(f, v) => {
1553 params.push(v.clone());
1554 format!("{} < ?", self.dialect.quote(f))
1555 }
1556 WhereCondition::Le(f, v) => {
1557 params.push(v.clone());
1558 format!("{} <= ?", self.dialect.quote(f))
1559 }
1560 WhereCondition::Like(f, v) => {
1561 params.push(v.clone());
1562 format!("{} LIKE ?", self.dialect.quote(f))
1563 }
1564 WhereCondition::OrEq(f, v) => {
1565 params.push(v.clone());
1566 format!("OR {} = ?", self.dialect.quote(f))
1567 }
1568 WhereCondition::OrNe(f, v) => {
1569 params.push(v.clone());
1570 format!("OR {} != ?", self.dialect.quote(f))
1571 }
1572 WhereCondition::OrGt(f, v) => {
1573 params.push(v.clone());
1574 format!("OR {} > ?", self.dialect.quote(f))
1575 }
1576 WhereCondition::OrGe(f, v) => {
1577 params.push(v.clone());
1578 format!("OR {} >= ?", self.dialect.quote(f))
1579 }
1580 WhereCondition::OrLt(f, v) => {
1581 params.push(v.clone());
1582 format!("OR {} < ?", self.dialect.quote(f))
1583 }
1584 WhereCondition::OrLe(f, v) => {
1585 params.push(v.clone());
1586 format!("OR {} <= ?", self.dialect.quote(f))
1587 }
1588 WhereCondition::OrLike(f, v) => {
1589 params.push(v.clone());
1590 format!("OR {} LIKE ?", self.dialect.quote(f))
1591 }
1592 WhereCondition::In(f, vals) => {
1593 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1594 params.extend(vals.iter().cloned());
1595 format!("{} IN ({})", self.dialect.quote(f), placeholders.join(", "))
1596 }
1597 WhereCondition::NotIn(f, vals) => {
1598 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1599 params.extend(vals.iter().cloned());
1600 format!(
1601 "{} NOT IN ({})",
1602 self.dialect.quote(f),
1603 placeholders.join(", ")
1604 )
1605 }
1606 WhereCondition::Between(f, start, end) => {
1607 params.push(start.clone());
1608 params.push(end.clone());
1609 format!("{} BETWEEN ? AND ?", self.dialect.quote(f))
1610 }
1611 WhereCondition::NotBetween(f, start, end) => {
1612 params.push(start.clone());
1613 params.push(end.clone());
1614 format!("{} NOT BETWEEN ? AND ?", self.dialect.quote(f))
1615 }
1616 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1617 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1618 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1619 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1620 })
1621 .collect();
1622
1623 if let Some(sd_cond) = soft_delete_cond {
1625 conditions.push(sd_cond);
1626 }
1627
1628 if let Some((t_sql, t_value)) = tenant_cond {
1630 conditions.push(t_sql);
1631 params.push(t_value);
1632 }
1633
1634 if let Some(ref cursor) = self.keyset_cursor {
1636 let op = match cursor.direction {
1637 KeysetDirection::After => ">",
1638 KeysetDirection::Before => "<",
1639 };
1640 conditions.push(format!("{} {} ?", self.dialect.quote(&cursor.field), op));
1641 params.push(cursor.value.clone());
1642 }
1643
1644 if conditions.is_empty() {
1645 return (String::new(), params);
1646 }
1647
1648 let mut groups: Vec<Vec<String>> = Vec::new();
1650 let mut current_group: Vec<String> = Vec::new();
1651 for cond in conditions.iter() {
1652 if let Some(stripped) = cond.strip_prefix("OR ") {
1653 current_group.push(stripped.to_string());
1654 } else {
1655 if !current_group.is_empty() {
1656 groups.push(std::mem::take(&mut current_group));
1657 }
1658 current_group.push(cond.clone());
1659 }
1660 }
1661 if !current_group.is_empty() {
1662 groups.push(current_group);
1663 }
1664
1665 let group_strs: Vec<String> = groups
1666 .iter()
1667 .map(|g| {
1668 if g.len() == 1 {
1669 g[0].clone()
1670 } else {
1671 format!("({})", g.join(" OR "))
1672 }
1673 })
1674 .collect();
1675
1676 (format!(" WHERE {}", group_strs.join(" AND ")), params)
1678 }
1679
1680 pub fn build_select_with_params(&self) -> (String, Vec<Value>) {
1685 let table = self
1686 .table
1687 .clone()
1688 .unwrap_or_else(|| M::table_name().to_string());
1689 let columns = if self.select_columns.is_empty() {
1690 "*".to_string()
1691 } else {
1692 self.select_columns.join(", ")
1693 };
1694
1695 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1696
1697 for join in &self.joins {
1698 match join {
1699 JoinClause::Inner(t, l, r) => {
1700 sql.push_str(&format!(
1701 " INNER JOIN {} ON {} = {}",
1702 self.dialect.quote(t),
1703 self.dialect.quote(l),
1704 self.dialect.quote(r)
1705 ));
1706 }
1707 JoinClause::Left(t, l, r) => {
1708 sql.push_str(&format!(
1709 " LEFT JOIN {} ON {} = {}",
1710 self.dialect.quote(t),
1711 self.dialect.quote(l),
1712 self.dialect.quote(r)
1713 ));
1714 }
1715 JoinClause::Right(t, l, r) => {
1716 sql.push_str(&format!(
1717 " RIGHT JOIN {} ON {} = {}",
1718 self.dialect.quote(t),
1719 self.dialect.quote(l),
1720 self.dialect.quote(r)
1721 ));
1722 }
1723 JoinClause::Cross(t, on) => {
1724 sql.push_str(&format!(
1725 " CROSS JOIN {} ON {}",
1726 self.dialect.quote(t),
1727 self.dialect.quote(on)
1728 ));
1729 }
1730 }
1731 }
1732
1733 let mut params = Vec::new();
1734 let (where_clause, where_params) = self.build_where_clause_with_params();
1736 if !where_clause.is_empty() {
1737 sql.push_str(&where_clause);
1738 params = where_params;
1739 }
1740
1741 if !self.group_by.is_empty() {
1742 let cols: Vec<String> = self
1743 .group_by
1744 .iter()
1745 .map(|c| self.dialect.quote(c))
1746 .collect();
1747 sql.push_str(" GROUP BY ");
1748 sql.push_str(&cols.join(", "));
1749 }
1750
1751 if !self.having_conditions.is_empty() {
1752 sql.push_str(" HAVING ");
1753 for (i, cond) in self.having_conditions.iter().enumerate() {
1754 if i > 0 {
1755 sql.push_str(" AND ");
1756 }
1757 if let WhereCondition::And(c) = cond {
1758 sql.push_str(c);
1759 }
1760 }
1761 }
1762
1763 if !self.order_by.is_empty() {
1764 let order_cols: Vec<String> = self
1765 .order_by
1766 .iter()
1767 .map(|o| {
1768 let dir = match o.direction {
1769 OrderDirection::Asc => " ASC",
1770 OrderDirection::Desc => " DESC",
1771 };
1772 format!("{}{}", self.dialect.quote(&o.field), dir)
1773 })
1774 .collect();
1775 sql.push_str(" ORDER BY ");
1776 sql.push_str(&order_cols.join(", "));
1777 }
1778
1779 if let Some(limit) = self.limit_value {
1780 sql.push_str(&format!(" LIMIT {}", limit));
1781 }
1782 if let Some(offset) = self.offset_value {
1783 sql.push_str(&format!(" OFFSET {}", offset));
1784 }
1785
1786 if let Some(lock_type) = &self.lock_type {
1788 if let Some(lock_clause) = self.dialect.build_lock_clause(*lock_type) {
1789 sql.push(' ');
1790 sql.push_str(&lock_clause);
1791 }
1792 }
1793
1794 (sql, params)
1795 }
1796
1797 pub fn build_insert_with_params(
1799 &self,
1800 data: &std::collections::HashMap<String, Value>,
1801 ) -> (String, Vec<Value>) {
1802 let table = self
1803 .table
1804 .clone()
1805 .unwrap_or_else(|| M::table_name().to_string());
1806 if data.is_empty() {
1807 return (String::new(), Vec::new());
1808 }
1809
1810 let mut columns = Vec::with_capacity(data.len());
1811 let mut params = Vec::with_capacity(data.len());
1812 let placeholders: Vec<&str> = data.iter().map(|_| "?").collect();
1813 for (k, v) in data.iter() {
1814 columns.push(self.dialect.quote(k));
1815 params.push(v.clone());
1816 }
1817
1818 let insert_clause = if self.insert_or_ignore {
1820 self.dialect.build_insert_or_ignore_prefix(&table)
1821 } else {
1822 format!("INSERT INTO {}", self.dialect.quote(&table))
1823 };
1824
1825 let sql = format!(
1826 "{} ({}) VALUES ({})",
1827 insert_clause,
1828 columns.join(", "),
1829 placeholders.join(", ")
1830 );
1831 (sql, params)
1832 }
1833
1834 pub fn build_batch_insert_with_params(
1841 &self,
1842 rows: &[std::collections::HashMap<String, Value>],
1843 ) -> (String, Vec<Value>) {
1844 let table = self
1845 .table
1846 .clone()
1847 .unwrap_or_else(|| M::table_name().to_string());
1848 if rows.is_empty() {
1849 return (String::new(), Vec::new());
1850 }
1851
1852 let first_row = &rows[0];
1854 let columns: Vec<String> = first_row.keys().cloned().collect();
1855 let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1856
1857 let mut params = Vec::with_capacity(rows.len() * columns.len());
1858 let mut value_groups: Vec<String> = Vec::with_capacity(rows.len());
1859 let is_pg = self.dialect.db_type() == DbType::PostgreSQL;
1860 let mut param_idx = 1usize;
1861 for row in rows {
1862 let placeholders: Vec<String> = columns
1863 .iter()
1864 .map(|col| match row.get(col) {
1865 Some(v) => {
1866 params.push(v.clone());
1867 if is_pg {
1868 let p = format!("${}", param_idx);
1869 param_idx += 1;
1870 p
1871 } else {
1872 "?".to_string()
1873 }
1874 }
1875 None => "NULL".to_string(),
1876 })
1877 .collect();
1878 value_groups.push(format!("({})", placeholders.join(", ")));
1879 }
1880
1881 let sql = format!(
1882 "INSERT INTO {} ({}) VALUES {}",
1883 self.dialect.quote(&table),
1884 quoted_columns.join(", "),
1885 value_groups.join(", ")
1886 );
1887 (sql, params)
1888 }
1889
1890 pub fn build_batch_upsert_with_params(
1911 &self,
1912 rows: &[std::collections::HashMap<String, Value>],
1913 conflict_columns: &[&str],
1914 update_columns: &[&str],
1915 ) -> Result<(String, Vec<Value>), crate::DbError> {
1916 if rows.is_empty() {
1917 return Err(crate::DbError::InvalidInput(
1918 "build_batch_upsert_with_params: rows cannot be empty".to_string(),
1919 ));
1920 }
1921
1922 let (insert_sql, params) = self.build_batch_insert_with_params(rows);
1924 if insert_sql.is_empty() {
1925 return Err(crate::DbError::InvalidInput(
1926 "build_batch_upsert_with_params: failed to build INSERT part".to_string(),
1927 ));
1928 }
1929
1930 let all_columns: Vec<String> = rows[0].keys().cloned().collect();
1932
1933 let conflict_clause = self
1935 .dialect
1936 .build_upsert_on_conflict(conflict_columns, update_columns, &all_columns)
1937 .ok_or_else(|| {
1938 crate::DbError::InvalidInput(format!(
1939 "build_batch_upsert_with_params: dialect {:?} does not support upsert (ON CONFLICT / ON DUPLICATE KEY UPDATE). Consider using MERGE statement or individual upserts instead.",
1940 self.dialect.db_type()
1941 ))
1942 })?;
1943
1944 let sql = format!("{} {}", insert_sql, conflict_clause);
1945 Ok((sql, params))
1946 }
1947
1948 pub fn build_update_with_params(
1951 &self,
1952 data: &std::collections::HashMap<String, Value>,
1953 ) -> (String, Vec<Value>) {
1954 let table = self
1955 .table
1956 .clone()
1957 .unwrap_or_else(|| M::table_name().to_string());
1958 if data.is_empty() {
1959 return (String::new(), Vec::new());
1960 }
1961
1962 let mut set_clauses = Vec::with_capacity(data.len());
1963 let mut params = Vec::with_capacity(data.len());
1964 for (k, v) in data.iter() {
1965 set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
1966 params.push(v.clone());
1967 }
1968
1969 let mut sql = format!(
1970 "UPDATE {} SET {}",
1971 self.dialect.quote(&table),
1972 set_clauses.join(", ")
1973 );
1974
1975 let (where_clause, where_params) = self.build_where_clause_with_params();
1977 if !where_clause.is_empty() {
1978 sql.push_str(&where_clause);
1979 params.extend(where_params);
1980 }
1981
1982 (sql, params)
1983 }
1984
1985 pub fn build_delete_with_params(&self) -> (String, Vec<Value>) {
1991 let table = self
1992 .table
1993 .clone()
1994 .unwrap_or_else(|| M::table_name().to_string());
1995
1996 if let Some(field) = self.soft_delete_field() {
1998 let (where_clause, where_params) = self.build_where_clause_with_params();
1999 let sql = format!(
2000 "UPDATE {} SET {} = NOW(){}",
2001 self.dialect.quote(&table),
2002 self.dialect.quote(field),
2003 where_clause
2004 );
2005 return (sql, where_params);
2006 }
2007
2008 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
2009 let mut params = Vec::new();
2010
2011 let (where_clause, where_params) = self.build_where_clause_with_params();
2012 if !where_clause.is_empty() {
2013 sql.push_str(&where_clause);
2014 params = where_params;
2015 }
2016
2017 (sql, params)
2018 }
2019
2020 pub fn build_force_delete_with_params(&self) -> (String, Vec<Value>) {
2024 let table = self
2025 .table
2026 .clone()
2027 .unwrap_or_else(|| M::table_name().to_string());
2028
2029 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
2030 let mut params = Vec::new();
2031
2032 let (where_clause, where_params) = self.build_where_clause_with_params_options(false);
2034 if !where_clause.is_empty() {
2035 sql.push_str(&where_clause);
2036 params = where_params;
2037 }
2038
2039 (sql, params)
2040 }
2041
2042 pub fn build_count(&self) -> String {
2043 let table = self
2044 .table
2045 .clone()
2046 .unwrap_or_else(|| M::table_name().to_string());
2047
2048 let mut sql = format!(
2049 "SELECT COUNT(*) as total FROM {}",
2050 self.dialect.quote(&table)
2051 );
2052 sql.push_str(&self.build_where_clause());
2053 sql
2054 }
2055
2056 pub fn build_exists(&self) -> String {
2057 let table = self
2058 .table
2059 .clone()
2060 .unwrap_or_else(|| M::table_name().to_string());
2061
2062 let mut sql = format!("SELECT 1 FROM {}", self.dialect.quote(&table));
2063 sql.push_str(&self.build_where_clause());
2064 sql.push_str(" LIMIT 1");
2065 format!("SELECT EXISTS({})", sql)
2066 }
2067
2068 pub fn build_max(&self, field: &str) -> String {
2069 let table = self
2070 .table
2071 .clone()
2072 .unwrap_or_else(|| M::table_name().to_string());
2073
2074 let mut sql = format!(
2075 "SELECT MAX({}) as max_val FROM {}",
2076 self.dialect.quote(field),
2077 self.dialect.quote(&table)
2078 );
2079 sql.push_str(&self.build_where_clause());
2080 sql
2081 }
2082
2083 pub fn build_min(&self, field: &str) -> String {
2084 let table = self
2085 .table
2086 .clone()
2087 .unwrap_or_else(|| M::table_name().to_string());
2088
2089 let mut sql = format!(
2090 "SELECT MIN({}) as min_val FROM {}",
2091 self.dialect.quote(field),
2092 self.dialect.quote(&table)
2093 );
2094 sql.push_str(&self.build_where_clause());
2095 sql
2096 }
2097
2098 pub fn build_sum(&self, field: &str) -> String {
2099 let table = self
2100 .table
2101 .clone()
2102 .unwrap_or_else(|| M::table_name().to_string());
2103
2104 let mut sql = format!(
2105 "SELECT SUM({}) as sum_val FROM {}",
2106 self.dialect.quote(field),
2107 self.dialect.quote(&table)
2108 );
2109 sql.push_str(&self.build_where_clause());
2110 sql
2111 }
2112
2113 pub fn build_avg(&self, field: &str) -> String {
2114 let table = self
2115 .table
2116 .clone()
2117 .unwrap_or_else(|| M::table_name().to_string());
2118
2119 let mut sql = format!(
2120 "SELECT AVG({}) as avg_val FROM {}",
2121 self.dialect.quote(field),
2122 self.dialect.quote(&table)
2123 );
2124 sql.push_str(&self.build_where_clause());
2125 sql
2126 }
2127
2128 pub fn validate(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2131 let sql = self.build_select();
2132 let mut errors = Vec::new();
2133
2134 if let Err(e) = sz_orm_sql_validator::validate_select(&sql) {
2135 errors.push(e);
2136 }
2137
2138 if !self.joins.is_empty() {
2140 for join in &self.joins {
2141 match join {
2142 JoinClause::Inner(_, left, right)
2143 | JoinClause::Left(_, left, right)
2144 | JoinClause::Right(_, left, right) => {
2145 if let Err(e) = sz_orm_sql_validator::validate_column_name(left) {
2146 errors.push(e);
2147 }
2148 if let Err(e) = sz_orm_sql_validator::validate_column_name(right) {
2149 errors.push(e);
2150 }
2151 }
2152 _ => {}
2153 }
2154 }
2155 }
2156
2157 let table = self
2159 .table
2160 .clone()
2161 .unwrap_or_else(|| M::table_name().to_string());
2162 if let Err(e) = sz_orm_sql_validator::validate_table_name(&table) {
2163 errors.push(e);
2164 }
2165
2166 if errors.is_empty() {
2167 Ok(())
2168 } else {
2169 Err(errors)
2170 }
2171 }
2172
2173 pub fn validate_insert(
2176 &self,
2177 data: &std::collections::HashMap<String, Value>,
2178 ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2179 let sql = self.build_insert(data);
2180 let mut errors = Vec::new();
2181
2182 if sql.is_empty() {
2183 errors.push(sz_orm_sql_validator::SqlValidationError::EmptyInsertData);
2184 return Err(errors);
2185 }
2186
2187 if let Err(e) = sz_orm_sql_validator::validate_insert(&sql) {
2188 errors.push(e);
2189 }
2190
2191 if errors.is_empty() {
2192 Ok(())
2193 } else {
2194 Err(errors)
2195 }
2196 }
2197
2198 pub fn validate_update(
2201 &self,
2202 data: &std::collections::HashMap<String, Value>,
2203 ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2204 let sql = self.build_update(data);
2205 let mut errors = Vec::new();
2206
2207 if sql.is_empty() {
2208 errors.push(sz_orm_sql_validator::SqlValidationError::EmptyUpdateData);
2209 return Err(errors);
2210 }
2211
2212 if let Err(e) = sz_orm_sql_validator::validate_update(&sql) {
2213 errors.push(e);
2214 }
2215
2216 if errors.is_empty() {
2217 Ok(())
2218 } else {
2219 Err(errors)
2220 }
2221 }
2222
2223 pub fn validate_delete(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2225 let sql = self.build_delete();
2226 let mut errors = Vec::new();
2227
2228 if let Err(e) = sz_orm_sql_validator::validate_delete(&sql) {
2229 errors.push(e);
2230 }
2231
2232 if errors.is_empty() {
2233 Ok(())
2234 } else {
2235 Err(errors)
2236 }
2237 }
2238}
2239
2240impl<M: Model> fmt::Debug for QueryBuilder<M> {
2241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2242 f.debug_struct("QueryBuilder")
2243 .field("table", &self.table)
2244 .field("select_columns", &self.select_columns)
2245 .field("where_conditions", &self.where_conditions.len())
2246 .field("limit", &self.limit_value)
2247 .finish()
2248 }
2249}
2250
2251#[cfg(test)]
2252#[allow(deprecated)]
2253mod tests {
2254 use super::*;
2255 use crate::db_type::DbType;
2256 use crate::dialect::get_dialect;
2257
2258 struct TestModel;
2259 impl Model for TestModel {
2260 type PrimaryKey = i64;
2261
2262 fn table_name() -> &'static str {
2263 "test_models"
2264 }
2265
2266 fn pk(&self) -> Self::PrimaryKey {
2267 1
2268 }
2269
2270 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2271 }
2272
2273 #[test]
2274 fn test_query_builder_select() -> Result<(), crate::DbError> {
2275 let dialect = get_dialect(DbType::MySQL)?;
2276 let builder = QueryBuilder::<TestModel>::new(dialect);
2277
2278 let sql = builder
2279 .table("users")
2280 .select(vec!["id", "name"])
2281 .build_select();
2282 assert!(sql.contains("SELECT id, name FROM"));
2283 assert!(sql.contains("`users`"));
2284 Ok(())
2285 }
2286
2287 #[test]
2288 fn test_query_builder_where() -> Result<(), crate::DbError> {
2289 let dialect = get_dialect(DbType::MySQL)?;
2290 let builder = QueryBuilder::<TestModel>::new(dialect);
2291
2292 let sql = builder
2293 .table("users")
2294 .where_eq("status", crate::value::Value::String("active".into()))
2295 .where_gt("age", crate::value::Value::I64(18))
2296 .build_select();
2297
2298 assert!(sql.contains("WHERE"));
2299 assert!(sql.contains("`status` = 'active'"));
2300 assert!(sql.contains("`age` > 18"));
2301 Ok(())
2302 }
2303
2304 #[test]
2305 fn test_query_builder_order_by() -> Result<(), crate::DbError> {
2306 let dialect = get_dialect(DbType::MySQL)?;
2307 let builder = QueryBuilder::<TestModel>::new(dialect);
2308
2309 let sql = builder
2310 .table("users")
2311 .order_by("created_at")
2312 .order_desc("id")
2313 .build_select();
2314
2315 assert!(sql.contains("ORDER BY"));
2316 assert!(sql.contains("`created_at` ASC"));
2317 assert!(sql.contains("`id` DESC"));
2318 Ok(())
2319 }
2320
2321 #[test]
2322 fn test_query_builder_limit_offset() -> Result<(), crate::DbError> {
2323 let dialect = get_dialect(DbType::MySQL)?;
2324 let builder = QueryBuilder::<TestModel>::new(dialect);
2325
2326 let sql = builder.table("users").limit(10).offset(20).build_select();
2327
2328 assert!(sql.contains("LIMIT 10"));
2329 assert!(sql.contains("OFFSET 20"));
2330 Ok(())
2331 }
2332
2333 #[test]
2334 fn test_query_builder_page() -> Result<(), crate::DbError> {
2335 let dialect = get_dialect(DbType::MySQL)?;
2336 let builder = QueryBuilder::<TestModel>::new(dialect);
2337
2338 let sql = builder.table("users").page(3, 20).build_select();
2339
2340 assert!(sql.contains("LIMIT 20"));
2341 assert!(sql.contains("OFFSET 40"));
2342 Ok(())
2343 }
2344
2345 #[test]
2346 fn test_query_builder_insert() -> Result<(), crate::DbError> {
2347 let dialect = get_dialect(DbType::MySQL)?;
2348 let builder = QueryBuilder::<TestModel>::new(dialect);
2349
2350 let mut data = std::collections::HashMap::new();
2351 data.insert("name".to_string(), Value::String("test".to_string()));
2352 data.insert("age".to_string(), Value::I64(25));
2353
2354 let sql = builder.table("users").build_insert(&data);
2355
2356 assert!(sql.contains("INSERT INTO"));
2357 assert!(sql.contains("`name`"));
2358 assert!(sql.contains("'test'"));
2359 Ok(())
2360 }
2361
2362 #[test]
2363 fn test_query_builder_update() -> Result<(), crate::DbError> {
2364 let dialect = get_dialect(DbType::MySQL)?;
2365 let builder = QueryBuilder::<TestModel>::new(dialect);
2366
2367 let mut data = std::collections::HashMap::new();
2368 data.insert("name".to_string(), Value::String("updated".to_string()));
2369
2370 let sql = builder
2371 .table("users")
2372 .where_eq("id", Value::I64(1))
2373 .build_update(&data);
2374
2375 assert!(sql.contains("UPDATE"));
2376 assert!(sql.contains("`name` = 'updated'"));
2377 assert!(sql.contains("WHERE"));
2378 Ok(())
2379 }
2380
2381 #[test]
2382 fn test_query_builder_delete() -> Result<(), crate::DbError> {
2383 let dialect = get_dialect(DbType::MySQL)?;
2384 let builder = QueryBuilder::<TestModel>::new(dialect);
2385
2386 let sql = builder
2387 .table("users")
2388 .where_eq("id", Value::I64(1))
2389 .build_delete();
2390
2391 assert!(sql.contains("DELETE FROM"));
2392 assert!(sql.contains("WHERE"));
2393 Ok(())
2394 }
2395
2396 #[test]
2397 fn test_query_builder_count() -> Result<(), crate::DbError> {
2398 let dialect = get_dialect(DbType::MySQL)?;
2399 let builder = QueryBuilder::<TestModel>::new(dialect);
2400
2401 let sql = builder.table("users").build_count();
2402
2403 assert!(sql.contains("SELECT COUNT(*)"));
2404 assert!(sql.contains("FROM"));
2405 Ok(())
2406 }
2407
2408 #[test]
2409 fn test_query_builder_where_in() -> Result<(), crate::DbError> {
2410 let dialect = get_dialect(DbType::MySQL)?;
2411 let builder = QueryBuilder::<TestModel>::new(dialect);
2412
2413 let sql = builder
2414 .table("users")
2415 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2416 .build_select();
2417
2418 assert!(sql.contains("IN ("));
2419 Ok(())
2420 }
2421
2422 #[test]
2423 fn test_query_builder_where_between() -> Result<(), crate::DbError> {
2424 let dialect = get_dialect(DbType::MySQL)?;
2425 let builder = QueryBuilder::<TestModel>::new(dialect);
2426
2427 let sql = builder
2428 .table("users")
2429 .where_between("age", Value::I64(18), Value::I64(30))
2430 .build_select();
2431
2432 assert!(sql.contains("BETWEEN"));
2433 Ok(())
2434 }
2435
2436 #[test]
2437 fn test_query_builder_where_null() -> Result<(), crate::DbError> {
2438 let dialect = get_dialect(DbType::MySQL)?;
2439 let builder = QueryBuilder::<TestModel>::new(dialect);
2440
2441 let sql = builder
2442 .table("users")
2443 .where_null("deleted_at")
2444 .build_select();
2445
2446 assert!(sql.contains("IS NULL"));
2447 Ok(())
2448 }
2449
2450 #[test]
2451 fn test_query_builder_join() -> Result<(), crate::DbError> {
2452 let dialect = get_dialect(DbType::MySQL)?;
2453 let builder = QueryBuilder::<TestModel>::new(dialect);
2454
2455 let sql = builder
2456 .table("users")
2457 .join_inner("posts", "users.id", "posts.user_id")
2458 .build_select();
2459
2460 assert!(sql.contains("INNER JOIN"));
2461 assert!(sql.contains("`posts`"));
2462 Ok(())
2463 }
2464
2465 #[test]
2466 fn test_query_builder_group_by() -> Result<(), crate::DbError> {
2467 let dialect = get_dialect(DbType::MySQL)?;
2468 let builder = QueryBuilder::<TestModel>::new(dialect);
2469
2470 let sql = builder.table("users").group_by("status").build_select();
2471
2472 assert!(sql.contains("GROUP BY"));
2473 assert!(sql.contains("`status`"));
2474 Ok(())
2475 }
2476
2477 #[test]
2478 fn test_query_builder_max() -> Result<(), crate::DbError> {
2479 let dialect = get_dialect(DbType::MySQL)?;
2480 let builder = QueryBuilder::<TestModel>::new(dialect);
2481
2482 let sql = builder.table("users").build_max("score");
2483
2484 assert!(sql.contains("MAX("));
2485 assert!(sql.contains("`score`"));
2486 Ok(())
2487 }
2488
2489 #[test]
2490 fn test_query_builder_min() -> Result<(), crate::DbError> {
2491 let dialect = get_dialect(DbType::MySQL)?;
2492 let builder = QueryBuilder::<TestModel>::new(dialect);
2493
2494 let sql = builder.table("users").build_min("price");
2495
2496 assert!(sql.contains("MIN("));
2497 assert!(sql.contains("`price`"));
2498 Ok(())
2499 }
2500
2501 #[test]
2502 fn test_query_builder_sum() -> Result<(), crate::DbError> {
2503 let dialect = get_dialect(DbType::MySQL)?;
2504 let builder = QueryBuilder::<TestModel>::new(dialect);
2505
2506 let sql = builder.table("orders").build_sum("amount");
2507
2508 assert!(sql.contains("SUM("));
2509 assert!(sql.contains("`amount`"));
2510 Ok(())
2511 }
2512
2513 #[test]
2514 fn test_query_builder_avg() -> Result<(), crate::DbError> {
2515 let dialect = get_dialect(DbType::MySQL)?;
2516 let builder = QueryBuilder::<TestModel>::new(dialect);
2517
2518 let sql = builder.table("scores").build_avg("value");
2519
2520 assert!(sql.contains("AVG("));
2521 assert!(sql.contains("`value`"));
2522 Ok(())
2523 }
2524
2525 #[test]
2526 fn test_validator_select() -> Result<(), crate::DbError> {
2527 let dialect = get_dialect(DbType::MySQL)?;
2528 let builder = QueryBuilder::<TestModel>::new(dialect);
2529
2530 let result = builder.table("users").select(vec!["id", "name"]).validate();
2531 assert!(result.is_ok());
2532 Ok(())
2533 }
2534
2535 #[test]
2536 fn test_validator_select_with_join() -> Result<(), crate::DbError> {
2537 let dialect = get_dialect(DbType::MySQL)?;
2538 let builder = QueryBuilder::<TestModel>::new(dialect);
2539
2540 let result = builder
2541 .table("users")
2542 .join_inner("posts", "users.id", "posts.user_id")
2543 .validate();
2544 assert!(result.is_ok());
2545 Ok(())
2546 }
2547
2548 #[test]
2549 fn test_validator_insert() -> Result<(), crate::DbError> {
2550 let dialect = get_dialect(DbType::MySQL)?;
2551 let builder = QueryBuilder::<TestModel>::new(dialect);
2552
2553 let mut data = std::collections::HashMap::new();
2554 data.insert("name".to_string(), Value::String("test".to_string()));
2555
2556 let result = builder.table("users").validate_insert(&data);
2557 assert!(result.is_ok());
2558 Ok(())
2559 }
2560
2561 #[test]
2562 fn test_validator_insert_empty_data() -> Result<(), crate::DbError> {
2563 let dialect = get_dialect(DbType::MySQL)?;
2564 let builder = QueryBuilder::<TestModel>::new(dialect);
2565
2566 let data = std::collections::HashMap::new();
2567 let result = builder.table("users").validate_insert(&data);
2568 assert!(result.is_err());
2569 Ok(())
2570 }
2571
2572 #[test]
2573 fn test_validator_update() -> Result<(), crate::DbError> {
2574 let dialect = get_dialect(DbType::MySQL)?;
2575 let builder = QueryBuilder::<TestModel>::new(dialect);
2576
2577 let mut data = std::collections::HashMap::new();
2578 data.insert("name".to_string(), Value::String("updated".to_string()));
2579
2580 let result = builder.table("users").validate_update(&data);
2581 assert!(result.is_ok());
2582 Ok(())
2583 }
2584
2585 #[test]
2586 fn test_validator_update_empty_data() -> Result<(), crate::DbError> {
2587 let dialect = get_dialect(DbType::MySQL)?;
2588 let builder = QueryBuilder::<TestModel>::new(dialect);
2589
2590 let data = std::collections::HashMap::new();
2591 let result = builder.table("users").validate_update(&data);
2592 assert!(result.is_err());
2593 Ok(())
2594 }
2595
2596 #[test]
2597 fn test_validator_delete() -> Result<(), crate::DbError> {
2598 let dialect = get_dialect(DbType::MySQL)?;
2599 let builder = QueryBuilder::<TestModel>::new(dialect);
2600
2601 let result = builder
2602 .table("users")
2603 .where_eq("id", Value::I64(1))
2604 .validate_delete();
2605 assert!(result.is_ok());
2606 Ok(())
2607 }
2608
2609 #[test]
2610 fn test_validator_delete_no_where() -> Result<(), crate::DbError> {
2611 let dialect = get_dialect(DbType::MySQL)?;
2612 let builder = QueryBuilder::<TestModel>::new(dialect);
2613
2614 let result = builder.table("users").validate_delete();
2616 assert!(result.is_ok());
2617 Ok(())
2618 }
2619
2620 #[test]
2623 fn test_m3_select_quoted_valid_columns() -> Result<(), crate::DbError> {
2624 let dialect = get_dialect(DbType::MySQL)?;
2625 let builder = QueryBuilder::<TestModel>::new(dialect);
2626 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2627 let sql = builder.build_select();
2628 assert!(sql.contains("SELECT `id`, `name` FROM"));
2630 assert!(sql.contains("`users`"));
2631 Ok(())
2632 }
2633
2634 #[test]
2635 fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), crate::DbError> {
2636 let dialect = get_dialect(DbType::MySQL)?;
2637 let builder = QueryBuilder::<TestModel>::new(dialect);
2638
2639 let result = builder
2641 .table("users")
2642 .select_quoted(vec!["id; DROP TABLE users"]);
2643 assert!(result.is_err());
2644
2645 let dialect = get_dialect(DbType::MySQL)?;
2647 let builder = QueryBuilder::<TestModel>::new(dialect);
2648 let result = builder.table("users").select_quoted(vec!["name'"]);
2649 assert!(result.is_err());
2650
2651 let dialect = get_dialect(DbType::MySQL)?;
2653 let builder = QueryBuilder::<TestModel>::new(dialect);
2654 let result = builder.table("users").select_quoted(vec!["1col"]);
2655 assert!(result.is_err());
2656
2657 let dialect = get_dialect(DbType::MySQL)?;
2659 let builder = QueryBuilder::<TestModel>::new(dialect);
2660 let result = builder.table("users").select_quoted(vec!["col name"]);
2661 assert!(result.is_err());
2662 Ok(())
2663 }
2664
2665 #[test]
2666 fn test_m3_select_quoted_postgresql_dialect() -> Result<(), crate::DbError> {
2667 let dialect = get_dialect(DbType::PostgreSQL)?;
2668 let builder = QueryBuilder::<TestModel>::new(dialect);
2669 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2670 let sql = builder.build_select();
2671 assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2673 assert!(sql.contains("\"users\""));
2674 Ok(())
2675 }
2676
2677 struct SoftDeleteModel;
2681 impl Model for SoftDeleteModel {
2682 type PrimaryKey = i64;
2683
2684 fn table_name() -> &'static str {
2685 "soft_users"
2686 }
2687
2688 fn pk(&self) -> Self::PrimaryKey {
2689 1
2690 }
2691
2692 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2693
2694 fn soft_delete_field() -> Option<&'static str> {
2695 Some("deleted_at")
2696 }
2697 }
2698
2699 #[test]
2703 fn test_p01_soft_delete_select_auto_filter() -> Result<(), crate::DbError> {
2704 let dialect = get_dialect(DbType::MySQL)?;
2705 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2706 let sql = builder.table("soft_users").build_select();
2707 assert!(
2709 sql.contains("`deleted_at` IS NULL"),
2710 "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2711 sql
2712 );
2713 Ok(())
2714 }
2715
2716 #[test]
2718 fn test_p01_soft_delete_select_with_user_where() -> Result<(), crate::DbError> {
2719 let dialect = get_dialect(DbType::MySQL)?;
2720 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2721 .table("soft_users")
2722 .where_eq("status", Value::String("active".into()))
2723 .build_select();
2724 assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2726 assert!(
2727 sql.contains("`deleted_at` IS NULL"),
2728 "软删除条件应自动追加: {}",
2729 sql
2730 );
2731 Ok(())
2732 }
2733
2734 #[test]
2738 fn test_p01_soft_delete_without_soft_delete() -> Result<(), crate::DbError> {
2739 let dialect = get_dialect(DbType::MySQL)?;
2740 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2741 .table("soft_users")
2742 .without_soft_delete()
2743 .build_select();
2744 assert!(
2746 !sql.contains("`deleted_at` IS NULL"),
2747 "without_soft_delete 应禁用过滤,实际: {}",
2748 sql
2749 );
2750 assert!(
2752 !sql.contains("WHERE"),
2753 "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2754 sql
2755 );
2756 Ok(())
2757 }
2758
2759 #[test]
2763 fn test_p01_soft_delete_delete_becomes_update() -> Result<(), crate::DbError> {
2764 let dialect = get_dialect(DbType::MySQL)?;
2765 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2766 .table("soft_users")
2767 .where_eq("id", Value::I64(42))
2768 .build_delete();
2769 assert!(
2771 sql.starts_with("UPDATE"),
2772 "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2773 sql
2774 );
2775 assert!(
2776 !sql.contains("DELETE FROM"),
2777 "不应生成 DELETE FROM: {}",
2778 sql
2779 );
2780 assert!(
2781 sql.contains("`deleted_at` = NOW()"),
2782 "应设置 deleted_at = NOW(): {}",
2783 sql
2784 );
2785 assert!(
2787 sql.contains("`deleted_at` IS NULL"),
2788 "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2789 sql
2790 );
2791 Ok(())
2792 }
2793
2794 #[test]
2798 fn test_p01_soft_delete_force_delete() -> Result<(), crate::DbError> {
2799 let dialect = get_dialect(DbType::MySQL)?;
2800 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2801 .table("soft_users")
2802 .where_eq("id", Value::I64(99))
2803 .build_force_delete();
2804 assert!(
2806 sql.starts_with("DELETE FROM"),
2807 "build_force_delete 应生成 DELETE FROM,实际: {}",
2808 sql
2809 );
2810 assert!(
2812 !sql.contains("`deleted_at` IS NULL"),
2813 "物理删除不应追加软删除过滤: {}",
2814 sql
2815 );
2816 Ok(())
2817 }
2818
2819 #[test]
2821 fn test_p01_soft_delete_select_with_params() -> Result<(), crate::DbError> {
2822 let dialect = get_dialect(DbType::MySQL)?;
2823 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2824 .table("soft_users")
2825 .where_eq("id", Value::I64(1))
2826 .build_select_with_params();
2827 assert!(
2828 sql.contains("`deleted_at` IS NULL"),
2829 "参数化版本也应自动追加软删除: {}",
2830 sql
2831 );
2832 assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
2833 assert_eq!(params[0], Value::I64(1));
2834 Ok(())
2835 }
2836
2837 #[test]
2839 fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), crate::DbError> {
2840 let dialect = get_dialect(DbType::MySQL)?;
2841 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2842 .table("soft_users")
2843 .where_eq("id", Value::I64(7))
2844 .build_delete_with_params();
2845 assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
2846 assert!(
2847 sql.contains("`deleted_at` = NOW()"),
2848 "应设置 NOW(): {}",
2849 sql
2850 );
2851 assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
2852 Ok(())
2853 }
2854
2855 #[test]
2857 fn test_p01_soft_delete_force_delete_with_params() -> Result<(), crate::DbError> {
2858 let dialect = get_dialect(DbType::MySQL)?;
2859 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2860 .table("soft_users")
2861 .where_eq("id", Value::I64(11))
2862 .build_force_delete_with_params();
2863 assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
2864 assert!(
2865 !sql.contains("`deleted_at` IS NULL"),
2866 "不应追加软删除过滤: {}",
2867 sql
2868 );
2869 assert_eq!(params.len(), 1);
2870 Ok(())
2871 }
2872
2873 #[test]
2877 fn test_p01_non_soft_delete_model_unchanged() -> Result<(), crate::DbError> {
2878 let dialect = get_dialect(DbType::MySQL)?;
2879 let sql = QueryBuilder::<TestModel>::new(dialect)
2880 .table("users")
2881 .where_eq("id", Value::I64(1))
2882 .build_select();
2883 assert!(
2884 !sql.contains("deleted_at"),
2885 "非软删除模型不应追加 deleted_at: {}",
2886 sql
2887 );
2888 let dialect = get_dialect(DbType::MySQL)?;
2890 let del_sql = QueryBuilder::<TestModel>::new(dialect)
2891 .table("users")
2892 .where_eq("id", Value::I64(1))
2893 .build_delete();
2894 assert!(
2895 del_sql.starts_with("DELETE FROM"),
2896 "非软删除模型 build_delete 应生成 DELETE: {}",
2897 del_sql
2898 );
2899 Ok(())
2900 }
2901
2902 #[test]
2904 fn test_p01_soft_delete_count_auto_filter() -> Result<(), crate::DbError> {
2905 let dialect = get_dialect(DbType::MySQL)?;
2906 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2907 .table("soft_users")
2908 .build_count();
2909 assert!(
2910 sql.contains("`deleted_at` IS NULL"),
2911 "build_count 也应追加软删除过滤: {}",
2912 sql
2913 );
2914 Ok(())
2915 }
2916
2917 #[test]
2923 fn test_p02_where_eq_uses_placeholder() -> Result<(), crate::DbError> {
2924 let dialect = get_dialect(DbType::MySQL)?;
2925 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2926 .table("users")
2927 .where_eq("name", Value::String("alice".into()))
2928 .build_select_with_params();
2929 assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
2931 assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
2932 assert_eq!(params.len(), 1);
2933 assert_eq!(params[0], Value::String("alice".into()));
2934 Ok(())
2935 }
2936
2937 #[test]
2939 fn test_p02_where_like_uses_placeholder() -> Result<(), crate::DbError> {
2940 let dialect = get_dialect(DbType::MySQL)?;
2941 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2942 .table("users")
2943 .where_like("name", Value::String("%alice%".into()))
2944 .build_select_with_params();
2945 assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
2946 assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
2947 assert_eq!(params.len(), 1);
2948 Ok(())
2949 }
2950
2951 #[test]
2955 fn test_p02_where_ne_uses_placeholder() -> Result<(), crate::DbError> {
2956 let dialect = get_dialect(DbType::MySQL)?;
2957 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2958 .table("users")
2959 .where_ne("status", Value::I64(0))
2960 .build_select_with_params();
2961 assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
2962 assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
2963 assert_eq!(params.len(), 1);
2964 assert_eq!(params[0], Value::I64(0));
2965 Ok(())
2966 }
2967
2968 #[test]
2972 fn test_p02_where_ge_uses_placeholder() -> Result<(), crate::DbError> {
2973 let dialect = get_dialect(DbType::MySQL)?;
2974 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2975 .table("users")
2976 .where_ge("age", Value::I64(18))
2977 .build_select_with_params();
2978 assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
2979 assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
2980 assert_eq!(params.len(), 1);
2981 assert_eq!(params[0], Value::I64(18));
2982 Ok(())
2983 }
2984
2985 #[test]
2989 fn test_p02_where_lt_uses_placeholder() -> Result<(), crate::DbError> {
2990 let dialect = get_dialect(DbType::MySQL)?;
2991 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2992 .table("users")
2993 .where_lt("score", Value::F64(60.0))
2994 .build_select_with_params();
2995 assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
2996 assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
2997 assert_eq!(params.len(), 1);
2998 assert_eq!(params[0], Value::F64(60.0));
2999 Ok(())
3000 }
3001
3002 #[test]
3006 fn test_p02_injection_protection_drop_table() -> Result<(), crate::DbError> {
3007 let dialect = get_dialect(DbType::MySQL)?;
3008 let evil_input = "'; DROP TABLE users; --".to_string();
3009 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3010 .table("users")
3011 .where_eq("name", Value::String(evil_input.clone()))
3012 .build_select_with_params();
3013 assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
3015 assert_eq!(params.len(), 1);
3017 assert_eq!(params[0], Value::String(evil_input));
3018 assert_eq!(sql.matches('?').count(), 1);
3020 Ok(())
3021 }
3022
3023 #[test]
3025 fn test_p02_injection_protection_or_one_equals_one() -> Result<(), crate::DbError> {
3026 let dialect = get_dialect(DbType::MySQL)?;
3027 let evil = "' OR '1'='1".to_string();
3028 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3029 .table("users")
3030 .where_eq("name", Value::String(evil.clone()))
3031 .build_select_with_params();
3032 assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
3033 assert_eq!(params.len(), 1);
3034 assert_eq!(params[0], Value::String(evil));
3035 Ok(())
3036 }
3037
3038 #[test]
3040 fn test_p02_multiple_params_order() -> Result<(), crate::DbError> {
3041 let dialect = get_dialect(DbType::MySQL)?;
3042 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3043 .table("users")
3044 .where_eq("name", Value::String("alice".into()))
3045 .where_gt("age", Value::I64(18))
3046 .where_le("score", Value::F64(99.5))
3047 .build_select_with_params();
3048 assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
3049 assert_eq!(params.len(), 3);
3050 assert_eq!(params[0], Value::String("alice".into()));
3052 assert_eq!(params[1], Value::I64(18));
3053 assert_eq!(params[2], Value::F64(99.5));
3054 Ok(())
3055 }
3056
3057 #[test]
3059 fn test_p02_where_in_uses_placeholders() -> Result<(), crate::DbError> {
3060 let dialect = get_dialect(DbType::MySQL)?;
3061 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3062 .table("users")
3063 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
3064 .build_select_with_params();
3065 assert!(
3066 sql.contains("`id` IN (?, ?, ?)"),
3067 "应使用 3 个占位符: {}",
3068 sql
3069 );
3070 assert_eq!(params.len(), 3);
3071 Ok(())
3072 }
3073
3074 #[test]
3076 fn test_p02_where_between_uses_placeholders() -> Result<(), crate::DbError> {
3077 let dialect = get_dialect(DbType::MySQL)?;
3078 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3079 .table("users")
3080 .where_between("age", Value::I64(18), Value::I64(65))
3081 .build_select_with_params();
3082 assert!(
3083 sql.contains("`age` BETWEEN ? AND ?"),
3084 "应使用 2 个占位符: {}",
3085 sql
3086 );
3087 assert_eq!(params.len(), 2);
3088 assert_eq!(params[0], Value::I64(18));
3089 assert_eq!(params[1], Value::I64(65));
3090 Ok(())
3091 }
3092
3093 #[test]
3095 fn test_p02_update_params_order_set_before_where() -> Result<(), crate::DbError> {
3096 let dialect = get_dialect(DbType::MySQL)?;
3097 let mut data = std::collections::HashMap::new();
3098 data.insert("name".to_string(), Value::String("bob".into()));
3099 data.insert("age".to_string(), Value::I64(30));
3100 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3101 .table("users")
3102 .where_eq("id", Value::I64(99))
3103 .build_update_with_params(&data);
3104 assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
3106 assert_eq!(params.len(), 3);
3107 assert_eq!(params[2], Value::I64(99));
3110 Ok(())
3111 }
3112
3113 #[test]
3117 fn test_p02_build_where_clause_inlines_value() -> Result<(), crate::DbError> {
3118 let dialect = get_dialect(DbType::MySQL)?;
3119 let sql = QueryBuilder::<TestModel>::new(dialect)
3120 .table("users")
3121 .where_eq("name", Value::String("alice".into()))
3122 .build_select();
3123 assert!(
3125 sql.contains("`name` = "),
3126 "无参数版本应含 WHERE 条件: {}",
3127 sql
3128 );
3129 assert!(
3131 !sql.contains("`name` = ?"),
3132 "无参数版本不应使用 ? 占位符: {}",
3133 sql
3134 );
3135 Ok(())
3136 }
3137
3138 #[test]
3140 fn test_p01_is_soft_delete_disabled_flag() -> Result<(), crate::DbError> {
3141 let dialect = get_dialect(DbType::MySQL)?;
3142 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
3143 assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
3144 let builder =
3145 QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
3146 assert!(
3147 builder.is_soft_delete_disabled(),
3148 "without_soft_delete 后应反映禁用状态"
3149 );
3150 Ok(())
3151 }
3152
3153 struct TenantModel;
3157 impl Model for TenantModel {
3158 type PrimaryKey = i64;
3159
3160 fn table_name() -> &'static str {
3161 "orders"
3162 }
3163
3164 fn pk(&self) -> Self::PrimaryKey {
3165 1
3166 }
3167
3168 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3169
3170 fn tenant_field() -> Option<&'static str> {
3171 Some("tenant_id")
3172 }
3173 }
3174
3175 struct SoftDeleteAndTenantModel;
3177 impl Model for SoftDeleteAndTenantModel {
3178 type PrimaryKey = i64;
3179
3180 fn table_name() -> &'static str {
3181 "documents"
3182 }
3183
3184 fn pk(&self) -> Self::PrimaryKey {
3185 1
3186 }
3187
3188 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3189
3190 fn soft_delete_field() -> Option<&'static str> {
3191 Some("deleted_at")
3192 }
3193
3194 fn tenant_field() -> Option<&'static str> {
3195 Some("tenant_id")
3196 }
3197 }
3198
3199 #[test]
3203 fn test_p03_tenant_select_auto_filter() -> Result<(), crate::DbError> {
3204 let dialect = get_dialect(DbType::MySQL)?;
3205 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3206 .table("orders")
3207 .with_tenant_id(42)
3208 .build_select_with_params();
3209 assert!(
3210 sql.contains("`tenant_id` = ?"),
3211 "多租户模型应自动追加 tenant_id = ?: {}",
3212 sql
3213 );
3214 assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
3215 assert_eq!(params[0], Value::I64(42));
3216 Ok(())
3217 }
3218
3219 #[test]
3221 fn test_p03_tenant_select_with_user_where() -> Result<(), crate::DbError> {
3222 let dialect = get_dialect(DbType::MySQL)?;
3223 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3224 .table("orders")
3225 .with_tenant_id(7)
3226 .where_eq("status", Value::String("active".into()))
3227 .build_select_with_params();
3228 assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
3229 assert!(
3230 sql.contains("`tenant_id` = ?"),
3231 "租户条件应自动追加: {}",
3232 sql
3233 );
3234 assert_eq!(params.len(), 2, "应有 2 个参数");
3235 assert_eq!(params[0], Value::String("active".into()));
3237 assert_eq!(params[1], Value::I64(7));
3238 Ok(())
3239 }
3240
3241 #[test]
3245 fn test_p03_tenant_without_tenant() -> Result<(), crate::DbError> {
3246 let dialect = get_dialect(DbType::MySQL)?;
3247 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3248 .table("orders")
3249 .with_tenant_id(42)
3250 .without_tenant()
3251 .build_select_with_params();
3252 assert!(
3253 !sql.contains("`tenant_id` = ?"),
3254 "without_tenant 应禁用过滤: {}",
3255 sql
3256 );
3257 assert_eq!(params.len(), 0, "不应有租户参数");
3258 Ok(())
3259 }
3260
3261 #[test]
3265 fn test_p03_tenant_delete_auto_filter() -> Result<(), crate::DbError> {
3266 let dialect = get_dialect(DbType::MySQL)?;
3267 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3268 .table("orders")
3269 .with_tenant_id(99)
3270 .where_eq("id", Value::I64(1))
3271 .build_delete_with_params();
3272 assert!(
3273 sql.contains("`tenant_id` = ?"),
3274 "删除应自动追加租户条件: {}",
3275 sql
3276 );
3277 assert_eq!(params.len(), 2);
3279 assert_eq!(params[0], Value::I64(1));
3280 assert_eq!(params[1], Value::I64(99));
3281 Ok(())
3282 }
3283
3284 #[test]
3286 fn test_p03_tenant_update_auto_filter() -> Result<(), crate::DbError> {
3287 let dialect = get_dialect(DbType::MySQL)?;
3288 let mut data = std::collections::HashMap::new();
3289 data.insert("status".to_string(), Value::String("shipped".into()));
3290 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3291 .table("orders")
3292 .with_tenant_id(5)
3293 .where_eq("id", Value::I64(10))
3294 .build_update_with_params(&data);
3295 assert!(
3296 sql.contains("`tenant_id` = ?"),
3297 "更新应自动追加租户条件: {}",
3298 sql
3299 );
3300 assert_eq!(params.len(), 3);
3302 assert_eq!(params[2], Value::I64(5));
3304 Ok(())
3305 }
3306
3307 #[test]
3309 fn test_p03_tenant_count_auto_filter() -> Result<(), crate::DbError> {
3310 let dialect = get_dialect(DbType::MySQL)?;
3311 let sql = QueryBuilder::<TenantModel>::new(dialect)
3312 .table("orders")
3313 .with_tenant_id(42)
3314 .build_count();
3315 assert!(
3316 sql.contains("`tenant_id` = 42"),
3317 "build_count 应追加租户条件(无参数版本内嵌值): {}",
3318 sql
3319 );
3320 Ok(())
3321 }
3322
3323 #[test]
3327 fn test_p03_non_tenant_model_unchanged() -> Result<(), crate::DbError> {
3328 let dialect = get_dialect(DbType::MySQL)?;
3329 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3331 .table("users")
3332 .with_tenant_id(42)
3333 .build_select_with_params();
3334 assert!(
3335 !sql.contains("tenant_id"),
3336 "非多租户模型不应追加 tenant_id: {}",
3337 sql
3338 );
3339 assert_eq!(params.len(), 0);
3340 Ok(())
3341 }
3342
3343 #[test]
3347 fn test_p03_tenant_no_id_no_filter() -> Result<(), crate::DbError> {
3348 let dialect = get_dialect(DbType::MySQL)?;
3349 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3350 .table("orders")
3351 .build_select_with_params();
3352 assert!(
3353 !sql.contains("tenant_id"),
3354 "未设置 tenant_id 时不应追加过滤: {}",
3355 sql
3356 );
3357 assert_eq!(params.len(), 0);
3358 Ok(())
3359 }
3360
3361 #[test]
3365 fn test_p03_soft_delete_and_tenant_combined() -> Result<(), crate::DbError> {
3366 let dialect = get_dialect(DbType::MySQL)?;
3367 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3368 .table("documents")
3369 .with_tenant_id(100)
3370 .where_eq("title", Value::String("report".into()))
3371 .build_select_with_params();
3372 assert!(
3374 sql.contains("`deleted_at` IS NULL"),
3375 "应追加软删除条件: {}",
3376 sql
3377 );
3378 assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3380 assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3382 assert_eq!(params.len(), 2);
3384 assert_eq!(params[0], Value::String("report".into()));
3385 assert_eq!(params[1], Value::I64(100));
3386 Ok(())
3387 }
3388
3389 #[test]
3391 fn test_p03_without_tenant_and_soft_delete() -> Result<(), crate::DbError> {
3392 let dialect = get_dialect(DbType::MySQL)?;
3393 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3394 .table("documents")
3395 .with_tenant_id(100)
3396 .without_tenant()
3397 .without_soft_delete()
3398 .build_select_with_params();
3399 assert!(
3400 !sql.contains("`deleted_at` IS NULL"),
3401 "应禁用软删除: {}",
3402 sql
3403 );
3404 assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
3405 assert_eq!(params.len(), 0);
3406 Ok(())
3407 }
3408
3409 #[test]
3411 fn test_p03_is_tenant_disabled_flag() -> Result<(), crate::DbError> {
3412 let dialect = get_dialect(DbType::MySQL)?;
3413 let builder = QueryBuilder::<TenantModel>::new(dialect);
3414 assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
3415 let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
3416 .with_tenant_id(1)
3417 .without_tenant();
3418 assert!(
3419 builder.is_tenant_disabled(),
3420 "without_tenant 后应反映禁用状态"
3421 );
3422 Ok(())
3423 }
3424
3425 #[test]
3429 fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), crate::DbError> {
3430 let dialect = get_dialect(DbType::MySQL)?;
3431 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3432 .table("orders")
3433 .with_tenant_id(42)
3434 .where_eq("id", Value::I64(999))
3435 .build_force_delete_with_params();
3436 assert!(
3439 sql.contains("`tenant_id` = ?"),
3440 "物理删除应保留租户条件: {}",
3441 sql
3442 );
3443 assert_eq!(params.len(), 2);
3444 assert_eq!(params[0], Value::I64(999));
3445 assert_eq!(params[1], Value::I64(42));
3446 Ok(())
3447 }
3448
3449 struct TcUsersTable;
3452 impl crate::typed::TypedTable for TcUsersTable {
3453 const NAME: &'static str = "users";
3454 }
3455 struct TcColId;
3456 impl crate::typed::TypedColumn for TcColId {
3457 const NAME: &'static str = "id";
3458 type Table = TcUsersTable;
3459 type RustType = i64;
3460 type SqlType = crate::typed_ast::Untyped;
3461 }
3462 struct TcColName;
3463 impl crate::typed::TypedColumn for TcColName {
3464 const NAME: &'static str = "name";
3465 type Table = TcUsersTable;
3466 type RustType = String;
3467 type SqlType = crate::typed_ast::Untyped;
3468 }
3469
3470 #[test]
3471 fn test_where_eq_typed() -> Result<(), crate::DbError> {
3472 let dialect = get_dialect(DbType::MySQL)?;
3473 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3474 .where_eq_typed::<TcColId>(Value::I64(42))
3475 .build_select_with_params();
3476 assert!(sql.contains("`id` = ?"));
3477 assert_eq!(params[0], Value::I64(42));
3478 Ok(())
3479 }
3480
3481 #[test]
3482 fn test_order_by_typed() -> Result<(), crate::DbError> {
3483 let dialect = get_dialect(DbType::MySQL)?;
3484 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3485 .order_by_typed::<TcColName>()
3486 .build_select_with_params();
3487 assert!(sql.contains("ORDER BY"));
3488 assert!(sql.contains("`name`"));
3489 Ok(())
3490 }
3491
3492 #[test]
3493 fn test_select_typed() -> Result<(), crate::DbError> {
3494 let dialect = get_dialect(DbType::MySQL)?;
3495 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3496 .select_typed::<TcColId>()
3497 .select_typed::<TcColName>()
3498 .build_select_with_params();
3499 assert!(sql.contains("SELECT"));
3500 assert!(sql.contains("id"));
3502 assert!(sql.contains("name"));
3503 Ok(())
3504 }
3505
3506 #[test]
3507 fn test_where_null_typed() -> Result<(), crate::DbError> {
3508 let dialect = get_dialect(DbType::MySQL)?;
3509 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3510 .where_null_typed::<TcColName>()
3511 .build_select_with_params();
3512 assert!(sql.contains("`name` IS NULL"));
3513 Ok(())
3514 }
3515
3516 #[test]
3517 fn test_where_not_null_typed() -> Result<(), crate::DbError> {
3518 let dialect = get_dialect(DbType::MySQL)?;
3519 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3520 .where_not_null_typed::<TcColName>()
3521 .build_select_with_params();
3522 assert!(sql.contains("`name` IS NOT NULL"));
3523 Ok(())
3524 }
3525
3526 #[test]
3527 fn test_group_by_typed() -> Result<(), crate::DbError> {
3528 let dialect = get_dialect(DbType::MySQL)?;
3529 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3530 .group_by_typed::<TcColName>()
3531 .build_select_with_params();
3532 assert!(sql.contains("GROUP BY"));
3533 assert!(sql.contains("`name`"));
3534 Ok(())
3535 }
3536
3537 #[test]
3538 fn test_where_gt_typed() -> Result<(), crate::DbError> {
3539 let dialect = get_dialect(DbType::MySQL)?;
3540 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3541 .where_gt_typed::<TcColId>(Value::I64(10))
3542 .build_select_with_params();
3543 assert!(sql.contains("`id` > ?"));
3544 assert_eq!(params[0], Value::I64(10));
3545 Ok(())
3546 }
3547
3548 #[test]
3551 fn test_lock_for_update_mysql() -> Result<(), crate::DbError> {
3552 let dialect = get_dialect(DbType::MySQL)?;
3553 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3554 .table("users")
3555 .where_eq("id", Value::I64(1))
3556 .lock_for_update()?
3557 .build_select_with_params();
3558 assert!(sql.contains("SELECT * FROM `users`"));
3559 assert!(sql.contains("WHERE `id` = ?"));
3560 assert!(sql.contains("FOR UPDATE"));
3561 assert_eq!(params.len(), 1);
3562 assert_eq!(params[0], Value::I64(1));
3563 Ok(())
3564 }
3565
3566 #[test]
3567 fn test_lock_shared_mysql() -> Result<(), crate::DbError> {
3568 let dialect = get_dialect(DbType::MySQL)?;
3569 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3570 .table("users")
3571 .where_eq("id", Value::I64(1))
3572 .lock_shared()?
3573 .build_select_with_params();
3574 assert!(sql.contains("SELECT * FROM `users`"));
3575 assert!(sql.contains("WHERE `id` = ?"));
3576 assert!(sql.contains("LOCK IN SHARE MODE"));
3577 assert_eq!(params.len(), 1);
3578 assert_eq!(params[0], Value::I64(1));
3579 Ok(())
3580 }
3581
3582 #[test]
3583 fn test_lock_for_update_postgresql() -> Result<(), crate::DbError> {
3584 let dialect = get_dialect(DbType::PostgreSQL)?;
3585 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3586 .table("users")
3587 .where_eq("id", Value::I64(1))
3588 .lock_for_update()?
3589 .build_select_with_params();
3590 assert!(sql.contains("SELECT * FROM \"users\""));
3591 assert!(sql.contains("WHERE \"id\" = ?"));
3592 assert!(sql.contains("FOR UPDATE"));
3593 assert_eq!(params.len(), 1);
3594 assert_eq!(params[0], Value::I64(1));
3595 Ok(())
3596 }
3597
3598 #[test]
3599 fn test_lock_shared_postgresql() -> Result<(), crate::DbError> {
3600 let dialect = get_dialect(DbType::PostgreSQL)?;
3601 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3602 .table("users")
3603 .where_eq("id", Value::I64(1))
3604 .lock_shared()?
3605 .build_select_with_params();
3606 assert!(sql.contains("SELECT * FROM \"users\""));
3607 assert!(sql.contains("WHERE \"id\" = ?"));
3608 assert!(sql.contains("FOR SHARE"));
3609 assert_eq!(params.len(), 1);
3610 assert_eq!(params[0], Value::I64(1));
3611 Ok(())
3612 }
3613
3614 #[test]
3615 fn test_lock_for_update_sqlite_should_fail() {
3616 let dialect = get_dialect(DbType::Sqlite).unwrap();
3617 let result = QueryBuilder::<TestModel>::new(dialect)
3618 .table("users")
3619 .where_eq("id", Value::I64(1))
3620 .lock_for_update();
3621 assert!(result.is_err(), "SQLite 不应支持 FOR UPDATE 锁");
3622 let err = result.err().unwrap();
3623 assert!(
3624 format!("{:?}", err).contains("FOR UPDATE lock is not supported"),
3625 "错误信息应说明不支持行锁"
3626 );
3627 }
3628
3629 #[test]
3630 fn test_lock_shared_sqlite_should_fail() {
3631 let dialect = get_dialect(DbType::Sqlite).unwrap();
3632 let result = QueryBuilder::<TestModel>::new(dialect)
3633 .table("users")
3634 .where_eq("id", Value::I64(1))
3635 .lock_shared();
3636 assert!(result.is_err(), "SQLite 不应支持共享锁");
3637 let err = result.err().unwrap();
3638 assert!(
3639 format!("{:?}", err).contains("Shared lock is not supported"),
3640 "错误信息应说明不支持共享锁"
3641 );
3642 }
3643
3644 #[test]
3645 fn test_lock_with_limit_and_offset() -> Result<(), crate::DbError> {
3646 let dialect = get_dialect(DbType::MySQL)?;
3647 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3648 .table("users")
3649 .where_eq("status", Value::String("active".into()))
3650 .limit(10)
3651 .offset(20)
3652 .lock_for_update()?
3653 .build_select_with_params();
3654 assert!(sql.contains("WHERE `status` = ?"));
3655 assert!(sql.contains("LIMIT 10"));
3656 assert!(sql.contains("OFFSET 20"));
3657 assert!(sql.contains("FOR UPDATE"));
3658 assert_eq!(params.len(), 1);
3659 Ok(())
3660 }
3661
3662 #[test]
3665 fn test_insert_or_ignore_mysql() -> Result<(), crate::DbError> {
3666 let dialect = get_dialect(DbType::MySQL)?;
3667 let mut data = std::collections::HashMap::new();
3668 data.insert("name".to_string(), Value::String("Alice".into()));
3669 data.insert("age".to_string(), Value::I64(30));
3670
3671 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3672 .table("users")
3673 .insert_or_ignore()
3674 .build_insert_with_params(&data);
3675 assert!(sql.contains("INSERT IGNORE INTO `users`"));
3676 assert!(sql.contains("`name`"), "SQL 应包含 name 列: {}", sql);
3678 assert!(sql.contains("`age`"), "SQL 应包含 age 列: {}", sql);
3679 assert!(sql.contains("VALUES (?, ?)"));
3680 assert_eq!(params.len(), 2);
3681 Ok(())
3682 }
3683
3684 #[test]
3685 fn test_insert_or_ignore_postgresql() -> Result<(), crate::DbError> {
3686 let dialect = get_dialect(DbType::PostgreSQL)?;
3687 let mut data = std::collections::HashMap::new();
3688 data.insert("name".to_string(), Value::String("Bob".into()));
3689
3690 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3691 .table("users")
3692 .insert_or_ignore()
3693 .build_insert_with_params(&data);
3694 assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3695 assert!(sql.contains("(\"name\")"));
3696 assert!(sql.contains("VALUES (?)"));
3697 assert_eq!(params.len(), 1);
3698 assert_eq!(params[0], Value::String("Bob".into()));
3699 Ok(())
3700 }
3701
3702 #[test]
3703 fn test_insert_or_ignore_sqlite() -> Result<(), crate::DbError> {
3704 let dialect = get_dialect(DbType::Sqlite)?;
3705 let mut data = std::collections::HashMap::new();
3706 data.insert("name".to_string(), Value::String("Charlie".into()));
3707
3708 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3709 .table("users")
3710 .insert_or_ignore()
3711 .build_insert_with_params(&data);
3712 assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3713 assert!(sql.contains("(\"name\")"));
3714 assert!(sql.contains("VALUES (?)"));
3715 assert_eq!(params.len(), 1);
3716 assert_eq!(params[0], Value::String("Charlie".into()));
3717 Ok(())
3718 }
3719
3720 #[test]
3721 fn test_insert_normal_without_ignore() -> Result<(), crate::DbError> {
3722 let dialect = get_dialect(DbType::MySQL)?;
3723 let mut data = std::collections::HashMap::new();
3724 data.insert("name".to_string(), Value::String("Dave".into()));
3725
3726 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3727 .table("users")
3728 .build_insert_with_params(&data);
3729 assert!(sql.contains("INSERT INTO `users`"));
3730 assert!(!sql.contains("IGNORE"), "普通插入不应包含 IGNORE");
3731 assert_eq!(params.len(), 1);
3732 assert_eq!(params[0], Value::String("Dave".into()));
3733 Ok(())
3734 }
3735
3736 #[test]
3737 fn test_insert_or_ignore_empty_data() -> Result<(), crate::DbError> {
3738 let dialect = get_dialect(DbType::MySQL)?;
3739 let data = std::collections::HashMap::new();
3740
3741 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3742 .table("users")
3743 .insert_or_ignore()
3744 .build_insert_with_params(&data);
3745 assert!(sql.is_empty(), "空数据应返回空 SQL");
3746 assert!(params.is_empty());
3747 Ok(())
3748 }
3749
3750 #[test]
3753 fn test_dialect_supports_lock_for_update() -> Result<(), crate::DbError> {
3754 let mysql = get_dialect(DbType::MySQL)?;
3755 let pg = get_dialect(DbType::PostgreSQL)?;
3756 let sqlite = get_dialect(DbType::Sqlite)?;
3757 let clickhouse = get_dialect(DbType::ClickHouse)?;
3758 let duckdb = get_dialect(DbType::DuckDB)?;
3759
3760 assert!(mysql.supports_lock_for_update(), "MySQL 应支持 FOR UPDATE");
3761 assert!(
3762 pg.supports_lock_for_update(),
3763 "PostgreSQL 应支持 FOR UPDATE"
3764 );
3765 assert!(
3766 !sqlite.supports_lock_for_update(),
3767 "SQLite 不应支持 FOR UPDATE"
3768 );
3769 assert!(
3770 !clickhouse.supports_lock_for_update(),
3771 "ClickHouse 是列式 OLAP,不应支持 FOR UPDATE"
3772 );
3773 assert!(
3774 !duckdb.supports_lock_for_update(),
3775 "DuckDB 不应支持 FOR UPDATE"
3776 );
3777 Ok(())
3778 }
3779
3780 #[test]
3781 fn test_dialect_supports_lock_shared() -> Result<(), crate::DbError> {
3782 let mysql = get_dialect(DbType::MySQL)?;
3783 let pg = get_dialect(DbType::PostgreSQL)?;
3784 let sqlite = get_dialect(DbType::Sqlite)?;
3785 let clickhouse = get_dialect(DbType::ClickHouse)?;
3786 let duckdb = get_dialect(DbType::DuckDB)?;
3787
3788 assert!(mysql.supports_lock_shared(), "MySQL 应支持共享锁");
3789 assert!(pg.supports_lock_shared(), "PostgreSQL 应支持共享锁");
3790 assert!(!sqlite.supports_lock_shared(), "SQLite 不应支持共享锁");
3791 assert!(
3792 !clickhouse.supports_lock_shared(),
3793 "ClickHouse 是列式 OLAP,不应支持共享锁"
3794 );
3795 assert!(!duckdb.supports_lock_shared(), "DuckDB 不应支持共享锁");
3796 Ok(())
3797 }
3798
3799 #[test]
3800 fn test_get_lock_type_and_is_insert_or_ignore() -> Result<(), crate::DbError> {
3801 let dialect = get_dialect(DbType::MySQL)?;
3802
3803 let builder = QueryBuilder::<TestModel>::new(dialect);
3805 assert!(builder.get_lock_type().is_none(), "默认无锁");
3806 assert!(!builder.is_insert_or_ignore(), "默认不忽略插入");
3807
3808 let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
3810 .table("users")
3811 .lock_for_update()?;
3812 assert_eq!(builder.get_lock_type(), Some(LockType::ForUpdate));
3813
3814 let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
3816 .table("users")
3817 .insert_or_ignore();
3818 assert!(builder.is_insert_or_ignore());
3819
3820 Ok(())
3821 }
3822}