1use crate::dialect::Dialect;
27use crate::model::Model;
28use crate::value::Value;
29use std::fmt;
30
31pub struct QueryBuilder<M: Model> {
33 table: Option<String>,
34 select_columns: Vec<String>,
35 where_conditions: Vec<WhereCondition>,
36 order_by: Vec<OrderClause>,
37 group_by: Vec<String>,
38 having_conditions: Vec<WhereCondition>,
39 limit_value: Option<usize>,
40 offset_value: Option<usize>,
41 joins: Vec<JoinClause>,
42 dialect: Box<dyn Dialect>,
43 soft_delete_disabled: bool,
45 tenant_id_value: Option<i64>,
47 tenant_disabled: bool,
49 keyset_cursor: Option<KeysetCursor>,
55 #[allow(dead_code)]
56 model: std::marker::PhantomData<M>,
57}
58
59#[derive(Debug, Clone)]
67struct KeysetCursor {
68 field: String,
70 value: Value,
72 direction: KeysetDirection,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78enum KeysetDirection {
79 After,
81 Before,
83}
84
85#[derive(Debug, Clone)]
86#[allow(dead_code)]
87enum WhereCondition {
88 And(String),
93 Or(String),
95 Eq(String, Value),
97 Ne(String, Value),
99 Gt(String, Value),
101 Ge(String, Value),
103 Lt(String, Value),
105 Le(String, Value),
107 Like(String, Value),
109 OrEq(String, Value),
111 OrNe(String, Value),
113 OrGt(String, Value),
115 OrGe(String, Value),
117 OrLt(String, Value),
119 OrLe(String, Value),
121 OrLike(String, Value),
123 In(String, Vec<Value>),
124 NotIn(String, Vec<Value>),
125 Between(String, Value, Value),
126 NotBetween(String, Value, Value),
127 Null(String),
128 NotNull(String),
129 Exists(String),
130 NotExists(String),
131}
132
133#[derive(Debug, Clone)]
134struct OrderClause {
135 field: String,
136 direction: OrderDirection,
137}
138
139#[derive(Debug, Clone)]
140enum OrderDirection {
141 Asc,
142 Desc,
143}
144
145#[derive(Debug, Clone)]
146#[allow(dead_code)]
147enum JoinClause {
148 Inner(String, String, String),
149 Left(String, String, String),
150 Right(String, String, String),
151 Cross(String, String),
152}
153
154impl<M: Model> QueryBuilder<M> {
155 pub fn new(dialect: Box<dyn Dialect>) -> Self {
156 Self {
157 table: None,
158 select_columns: vec!["*".to_string()],
159 where_conditions: Vec::new(),
160 order_by: Vec::new(),
161 group_by: Vec::new(),
162 having_conditions: Vec::new(),
163 limit_value: None,
164 offset_value: None,
165 joins: Vec::new(),
166 dialect,
167 soft_delete_disabled: false,
168 tenant_id_value: None,
169 tenant_disabled: false,
170 keyset_cursor: None,
171 model: std::marker::PhantomData,
172 }
173 }
174
175 pub fn table(mut self, table: impl Into<String>) -> Self {
176 self.table = Some(table.into());
177 self
178 }
179
180 pub fn without_soft_delete(mut self) -> Self {
199 self.soft_delete_disabled = true;
200 self
201 }
202
203 pub fn is_soft_delete_disabled(&self) -> bool {
205 self.soft_delete_disabled
206 }
207
208 fn soft_delete_field(&self) -> Option<&'static str> {
212 if self.soft_delete_disabled {
213 return None;
214 }
215 M::soft_delete_field()
216 }
217
218 fn build_soft_delete_condition(&self) -> Option<String> {
222 self.soft_delete_field()
223 .map(|field| format!("{} IS NULL", self.dialect.quote(field)))
224 }
225
226 pub fn with_tenant_id(mut self, tenant_id: i64) -> Self {
253 self.tenant_id_value = Some(tenant_id);
254 self
255 }
256
257 pub fn without_tenant(mut self) -> Self {
261 self.tenant_disabled = true;
262 self
263 }
264
265 pub fn is_tenant_disabled(&self) -> bool {
267 self.tenant_disabled
268 }
269
270 fn tenant_field(&self) -> Option<&'static str> {
274 if self.tenant_disabled {
275 return None;
276 }
277 M::tenant_field()
278 }
279
280 fn tenant_id_value(&self) -> Option<i64> {
282 if self.tenant_disabled {
283 return None;
284 }
285 self.tenant_id_value
286 }
287
288 fn build_tenant_condition(&self) -> Option<(String, Value)> {
293 let field = self.tenant_field()?;
294 let tid = self.tenant_id_value()?;
295 Some((
296 format!("{} = ?", self.dialect.quote(field)),
297 Value::I64(tid),
298 ))
299 }
300
301 pub fn select(mut self, columns: Vec<&str>) -> Self {
309 self.select_columns = columns.into_iter().map(|s| s.to_string()).collect();
310 self
311 }
312
313 pub fn select_quoted(mut self, columns: Vec<&str>) -> Result<Self, crate::DbError> {
322 let mut quoted = Vec::with_capacity(columns.len());
323 for col in columns {
324 crate::sql_safety::validate_identifier(col, "select column")?;
325 quoted.push(self.dialect.quote(col));
326 }
327 self.select_columns = quoted;
328 Ok(self)
329 }
330
331 #[deprecated(
352 since = "1.3.0",
353 note = "P0-2: 字符串拼接存在 SQL 注入风险,请使用 where_eq/where_ne/where_gt/where_lt/where_like 等参数化方法"
354 )]
355 pub fn where_cond(mut self, condition: impl Into<String>) -> Self {
356 self.where_conditions
357 .push(WhereCondition::And(condition.into()));
358 self
359 }
360
361 #[deprecated(
365 since = "1.3.0",
366 note = "P0-2: 字符串拼接存在 SQL 注入风险,请使用参数化方法"
367 )]
368 pub fn or_where(mut self, condition: impl Into<String>) -> Self {
369 self.where_conditions
370 .push(WhereCondition::Or(condition.into()));
371 self
372 }
373
374 pub fn where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
388 self.where_conditions
389 .push(WhereCondition::Eq(field.into(), value));
390 self
391 }
392
393 pub fn where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
395 self.where_conditions
396 .push(WhereCondition::Ne(field.into(), value));
397 self
398 }
399
400 pub fn where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
402 self.where_conditions
403 .push(WhereCondition::Gt(field.into(), value));
404 self
405 }
406
407 pub fn where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
409 self.where_conditions
410 .push(WhereCondition::Ge(field.into(), value));
411 self
412 }
413
414 pub fn where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
416 self.where_conditions
417 .push(WhereCondition::Lt(field.into(), value));
418 self
419 }
420
421 pub fn where_le(mut self, field: impl Into<String>, value: Value) -> Self {
423 self.where_conditions
424 .push(WhereCondition::Le(field.into(), value));
425 self
426 }
427
428 pub fn where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
440 self.where_conditions
441 .push(WhereCondition::Like(field.into(), pattern));
442 self
443 }
444
445 pub fn or_where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
449 self.where_conditions
450 .push(WhereCondition::OrEq(field.into(), value));
451 self
452 }
453
454 pub fn or_where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
456 self.where_conditions
457 .push(WhereCondition::OrNe(field.into(), value));
458 self
459 }
460
461 pub fn or_where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
463 self.where_conditions
464 .push(WhereCondition::OrGt(field.into(), value));
465 self
466 }
467
468 pub fn or_where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
470 self.where_conditions
471 .push(WhereCondition::OrGe(field.into(), value));
472 self
473 }
474
475 pub fn or_where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
477 self.where_conditions
478 .push(WhereCondition::OrLt(field.into(), value));
479 self
480 }
481
482 pub fn or_where_le(mut self, field: impl Into<String>, value: Value) -> Self {
484 self.where_conditions
485 .push(WhereCondition::OrLe(field.into(), value));
486 self
487 }
488
489 pub fn or_where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
491 self.where_conditions
492 .push(WhereCondition::OrLike(field.into(), pattern));
493 self
494 }
495
496 pub fn where_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
497 self.where_conditions
498 .push(WhereCondition::In(field.into(), values));
499 self
500 }
501
502 pub fn where_not_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
503 self.where_conditions
504 .push(WhereCondition::NotIn(field.into(), values));
505 self
506 }
507
508 pub fn where_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
509 self.where_conditions
510 .push(WhereCondition::Between(field.into(), start, end));
511 self
512 }
513
514 pub fn where_not_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
515 self.where_conditions
516 .push(WhereCondition::NotBetween(field.into(), start, end));
517 self
518 }
519
520 pub fn where_null(mut self, field: impl Into<String>) -> Self {
521 self.where_conditions
522 .push(WhereCondition::Null(field.into()));
523 self
524 }
525
526 pub fn where_not_null(mut self, field: impl Into<String>) -> Self {
527 self.where_conditions
528 .push(WhereCondition::NotNull(field.into()));
529 self
530 }
531
532 pub fn order_by(mut self, field: impl Into<String>) -> Self {
533 self.order_by.push(OrderClause {
534 field: field.into(),
535 direction: OrderDirection::Asc,
536 });
537 self
538 }
539
540 pub fn order_desc(mut self, field: impl Into<String>) -> Self {
541 self.order_by.push(OrderClause {
542 field: field.into(),
543 direction: OrderDirection::Desc,
544 });
545 self
546 }
547
548 pub fn group_by(mut self, field: impl Into<String>) -> Self {
549 self.group_by.push(field.into());
550 self
551 }
552
553 pub fn having(mut self, condition: impl Into<String>) -> Self {
554 self.having_conditions
555 .push(WhereCondition::And(condition.into()));
556 self
557 }
558
559 pub fn limit(mut self, limit: usize) -> Self {
560 self.limit_value = Some(limit);
561 self
562 }
563
564 pub fn offset(mut self, offset: usize) -> Self {
565 self.offset_value = Some(offset);
566 self
567 }
568
569 pub fn page(mut self, page: usize, page_size: usize) -> Self {
570 self.limit_value = Some(page_size);
571 self.offset_value = Some((page.saturating_sub(1)) * page_size);
572 self
573 }
574
575 pub fn keyset_after(
621 mut self,
622 field: impl Into<String>,
623 cursor_value: Value,
624 page_size: usize,
625 ) -> Self {
626 let field_str = field.into();
627 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
629 existing.direction = OrderDirection::Asc;
630 } else {
631 self.order_by.push(OrderClause {
632 field: field_str.clone(),
633 direction: OrderDirection::Asc,
634 });
635 }
636 self.limit_value = Some(page_size);
637 self.offset_value = None;
639 self.keyset_cursor = Some(KeysetCursor {
640 field: field_str,
641 value: cursor_value,
642 direction: KeysetDirection::After,
643 });
644 self
645 }
646
647 pub fn keyset_before(
693 mut self,
694 field: impl Into<String>,
695 cursor_value: Value,
696 page_size: usize,
697 ) -> Self {
698 let field_str = field.into();
699 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
701 existing.direction = OrderDirection::Desc;
702 } else {
703 self.order_by.push(OrderClause {
704 field: field_str.clone(),
705 direction: OrderDirection::Desc,
706 });
707 }
708 self.limit_value = Some(page_size);
709 self.offset_value = None;
710 self.keyset_cursor = Some(KeysetCursor {
711 field: field_str,
712 value: cursor_value,
713 direction: KeysetDirection::Before,
714 });
715 self
716 }
717
718 pub fn join_inner(
719 mut self,
720 table: impl Into<String>,
721 on_left: impl Into<String>,
722 on_right: impl Into<String>,
723 ) -> Self {
724 self.joins.push(JoinClause::Inner(
725 table.into(),
726 on_left.into(),
727 on_right.into(),
728 ));
729 self
730 }
731
732 pub fn join_left(
733 mut self,
734 table: impl Into<String>,
735 on_left: impl Into<String>,
736 on_right: impl Into<String>,
737 ) -> Self {
738 self.joins.push(JoinClause::Left(
739 table.into(),
740 on_left.into(),
741 on_right.into(),
742 ));
743 self
744 }
745
746 pub fn join_right(
747 mut self,
748 table: impl Into<String>,
749 on_left: impl Into<String>,
750 on_right: impl Into<String>,
751 ) -> Self {
752 self.joins.push(JoinClause::Right(
753 table.into(),
754 on_left.into(),
755 on_right.into(),
756 ));
757 self
758 }
759
760 #[tracing::instrument(skip(self), fields(op = "select"))]
793 pub fn build_select(&self) -> String {
794 let table = self
795 .table
796 .clone()
797 .unwrap_or_else(|| M::table_name().to_string());
798
799 let columns = if self.select_columns.is_empty() {
800 "*".to_string()
801 } else {
802 self.select_columns.join(", ")
803 };
804
805 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
806
807 for join in &self.joins {
808 match join {
809 JoinClause::Inner(t, l, r) => {
810 sql.push_str(&format!(
811 " INNER JOIN {} ON {} = {}",
812 self.dialect.quote(t),
813 self.dialect.quote(l),
814 self.dialect.quote(r)
815 ));
816 }
817 JoinClause::Left(t, l, r) => {
818 sql.push_str(&format!(
819 " LEFT JOIN {} ON {} = {}",
820 self.dialect.quote(t),
821 self.dialect.quote(l),
822 self.dialect.quote(r)
823 ));
824 }
825 JoinClause::Right(t, l, r) => {
826 sql.push_str(&format!(
827 " RIGHT JOIN {} ON {} = {}",
828 self.dialect.quote(t),
829 self.dialect.quote(l),
830 self.dialect.quote(r)
831 ));
832 }
833 JoinClause::Cross(t, on) => {
834 sql.push_str(&format!(
835 " CROSS JOIN {} ON {}",
836 self.dialect.quote(t),
837 self.dialect.quote(on)
838 ));
839 }
840 }
841 }
842
843 let where_clause = self.build_where_clause();
845 if !where_clause.is_empty() {
846 sql.push_str(&where_clause);
847 }
848
849 if !self.group_by.is_empty() {
850 let cols: Vec<String> = self
851 .group_by
852 .iter()
853 .map(|c| self.dialect.quote(c))
854 .collect();
855 sql.push_str(" GROUP BY ");
856 sql.push_str(&cols.join(", "));
857 }
858
859 if !self.having_conditions.is_empty() {
860 sql.push_str(" HAVING ");
861 for (i, cond) in self.having_conditions.iter().enumerate() {
862 if i > 0 {
863 sql.push_str(" AND ");
864 }
865 if let WhereCondition::And(c) = cond {
866 sql.push_str(c);
867 }
868 }
869 }
870
871 if !self.order_by.is_empty() {
872 let order_cols: Vec<String> = self
873 .order_by
874 .iter()
875 .map(|o| {
876 let dir = match o.direction {
877 OrderDirection::Asc => " ASC",
878 OrderDirection::Desc => " DESC",
879 };
880 format!("{}{}", self.dialect.quote(&o.field), dir)
881 })
882 .collect();
883 sql.push_str(" ORDER BY ");
884 sql.push_str(&order_cols.join(", "));
885 }
886
887 if let Some(limit) = self.limit_value {
888 sql.push_str(&format!(" LIMIT {}", limit));
889 }
890
891 if let Some(offset) = self.offset_value {
892 sql.push_str(&format!(" OFFSET {}", offset));
893 }
894
895 sql
896 }
897
898 fn build_where_clause(&self) -> String {
904 self.build_where_clause_with_options(true)
905 }
906
907 fn build_where_clause_with_options(&self, include_soft_delete: bool) -> String {
915 let soft_delete_cond = if include_soft_delete {
917 self.build_soft_delete_condition()
918 } else {
919 None
920 };
921
922 let tenant_cond = self.build_tenant_condition().map(|(sql, value)| {
924 sql.replacen('?', &value.to_param_with_dialect(&*self.dialect), 1)
926 });
927
928 if self.where_conditions.is_empty()
930 && soft_delete_cond.is_none()
931 && tenant_cond.is_none()
932 && self.keyset_cursor.is_none()
933 {
934 return String::new();
935 }
936
937 let mut conditions: Vec<String> = self
939 .where_conditions
940 .iter()
941 .map(|cond| match cond {
942 WhereCondition::And(c) => c.clone(),
943 WhereCondition::Or(c) => format!("OR {}", c),
944 WhereCondition::Eq(f, v) => format!(
946 "{} = {}",
947 self.dialect.quote(f),
948 v.to_param_with_dialect(&*self.dialect)
949 ),
950 WhereCondition::Ne(f, v) => format!(
951 "{} != {}",
952 self.dialect.quote(f),
953 v.to_param_with_dialect(&*self.dialect)
954 ),
955 WhereCondition::Gt(f, v) => format!(
956 "{} > {}",
957 self.dialect.quote(f),
958 v.to_param_with_dialect(&*self.dialect)
959 ),
960 WhereCondition::Ge(f, v) => format!(
961 "{} >= {}",
962 self.dialect.quote(f),
963 v.to_param_with_dialect(&*self.dialect)
964 ),
965 WhereCondition::Lt(f, v) => format!(
966 "{} < {}",
967 self.dialect.quote(f),
968 v.to_param_with_dialect(&*self.dialect)
969 ),
970 WhereCondition::Le(f, v) => format!(
971 "{} <= {}",
972 self.dialect.quote(f),
973 v.to_param_with_dialect(&*self.dialect)
974 ),
975 WhereCondition::Like(f, v) => format!(
976 "{} LIKE {}",
977 self.dialect.quote(f),
978 v.to_param_with_dialect(&*self.dialect)
979 ),
980 WhereCondition::OrEq(f, v) => format!(
981 "OR {} = {}",
982 self.dialect.quote(f),
983 v.to_param_with_dialect(&*self.dialect)
984 ),
985 WhereCondition::OrNe(f, v) => format!(
986 "OR {} != {}",
987 self.dialect.quote(f),
988 v.to_param_with_dialect(&*self.dialect)
989 ),
990 WhereCondition::OrGt(f, v) => format!(
991 "OR {} > {}",
992 self.dialect.quote(f),
993 v.to_param_with_dialect(&*self.dialect)
994 ),
995 WhereCondition::OrGe(f, v) => format!(
996 "OR {} >= {}",
997 self.dialect.quote(f),
998 v.to_param_with_dialect(&*self.dialect)
999 ),
1000 WhereCondition::OrLt(f, v) => format!(
1001 "OR {} < {}",
1002 self.dialect.quote(f),
1003 v.to_param_with_dialect(&*self.dialect)
1004 ),
1005 WhereCondition::OrLe(f, v) => format!(
1006 "OR {} <= {}",
1007 self.dialect.quote(f),
1008 v.to_param_with_dialect(&*self.dialect)
1009 ),
1010 WhereCondition::OrLike(f, v) => format!(
1011 "OR {} LIKE {}",
1012 self.dialect.quote(f),
1013 v.to_param_with_dialect(&*self.dialect)
1014 ),
1015 WhereCondition::In(f, vals) => {
1016 let vals_str: Vec<String> = vals
1018 .iter()
1019 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1020 .collect();
1021 format!("{} IN ({})", self.dialect.quote(f), vals_str.join(", "))
1022 }
1023 WhereCondition::NotIn(f, vals) => {
1024 let vals_str: Vec<String> = vals
1025 .iter()
1026 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1027 .collect();
1028 format!("{} NOT IN ({})", self.dialect.quote(f), vals_str.join(", "))
1029 }
1030 WhereCondition::Between(f, start, end) => {
1031 format!(
1032 "{} BETWEEN {} AND {}",
1033 self.dialect.quote(f),
1034 start.to_param_with_dialect(&*self.dialect),
1035 end.to_param_with_dialect(&*self.dialect)
1036 )
1037 }
1038 WhereCondition::NotBetween(f, start, end) => {
1039 format!(
1040 "{} NOT BETWEEN {} AND {}",
1041 self.dialect.quote(f),
1042 start.to_param_with_dialect(&*self.dialect),
1043 end.to_param_with_dialect(&*self.dialect)
1044 )
1045 }
1046 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1047 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1048 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1049 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1050 })
1051 .collect();
1052
1053 if let Some(sd_cond) = soft_delete_cond {
1055 conditions.push(sd_cond);
1056 }
1057
1058 if let Some(t_cond) = tenant_cond {
1060 conditions.push(t_cond);
1061 }
1062
1063 if let Some(ref cursor) = self.keyset_cursor {
1065 let op = match cursor.direction {
1066 KeysetDirection::After => ">",
1067 KeysetDirection::Before => "<",
1068 };
1069 conditions.push(format!(
1070 "{} {} {}",
1071 self.dialect.quote(&cursor.field),
1072 op,
1073 cursor.value.to_param_with_dialect(&*self.dialect)
1074 ));
1075 }
1076
1077 if conditions.is_empty() {
1078 return String::new();
1079 }
1080
1081 let mut groups: Vec<Vec<String>> = Vec::new();
1084 let mut current_group: Vec<String> = Vec::new();
1085 for cond in conditions.iter() {
1086 if let Some(stripped) = cond.strip_prefix("OR ") {
1087 current_group.push(stripped.to_string());
1089 } else {
1090 if !current_group.is_empty() {
1092 groups.push(std::mem::take(&mut current_group));
1093 }
1094 current_group.push(cond.clone());
1095 }
1096 }
1097 if !current_group.is_empty() {
1098 groups.push(current_group);
1099 }
1100
1101 let group_strs: Vec<String> = groups
1102 .iter()
1103 .map(|g| {
1104 if g.len() == 1 {
1105 g[0].clone()
1106 } else {
1107 format!("({})", g.join(" OR "))
1108 }
1109 })
1110 .collect();
1111
1112 format!(" WHERE {}", group_strs.join(" AND "))
1113 }
1114
1115 #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1116 pub fn build_insert(&self, data: &std::collections::HashMap<String, Value>) -> String {
1117 let table = self
1118 .table
1119 .clone()
1120 .unwrap_or_else(|| M::table_name().to_string());
1121
1122 if data.is_empty() {
1123 return String::new();
1124 }
1125
1126 let columns: Vec<String> = data.keys().map(|k| self.dialect.quote(k)).collect();
1127 let values: Vec<String> = data
1129 .values()
1130 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1131 .collect();
1132
1133 format!(
1134 "INSERT INTO {} ({}) VALUES ({})",
1135 self.dialect.quote(&table),
1136 columns.join(", "),
1137 values.join(", ")
1138 )
1139 }
1140
1141 #[tracing::instrument(skip(self, data), fields(op = "update"))]
1142 pub fn build_update(&self, data: &std::collections::HashMap<String, Value>) -> String {
1143 let table = self
1144 .table
1145 .clone()
1146 .unwrap_or_else(|| M::table_name().to_string());
1147
1148 if data.is_empty() {
1149 return String::new();
1150 }
1151
1152 let set_clauses: Vec<String> = data
1153 .iter()
1154 .map(|(k, v)| {
1155 format!(
1156 "{} = {}",
1157 self.dialect.quote(k),
1158 v.to_param_with_dialect(&*self.dialect)
1159 )
1160 })
1161 .collect();
1162
1163 let mut sql = format!(
1164 "UPDATE {} SET {}",
1165 self.dialect.quote(&table),
1166 set_clauses.join(", ")
1167 );
1168
1169 sql.push_str(&self.build_where_clause());
1170 sql
1171 }
1172
1173 #[tracing::instrument(skip(self), fields(op = "delete"))]
1184 pub fn build_delete(&self) -> String {
1185 let table = self
1186 .table
1187 .clone()
1188 .unwrap_or_else(|| M::table_name().to_string());
1189
1190 if let Some(field) = self.soft_delete_field() {
1192 let where_clause = self.build_where_clause();
1193 return format!(
1194 "UPDATE {} SET {} = NOW(){}",
1195 self.dialect.quote(&table),
1196 self.dialect.quote(field),
1197 where_clause
1198 );
1199 }
1200
1201 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1202 sql.push_str(&self.build_where_clause());
1203 sql
1204 }
1205
1206 pub fn build_force_delete(&self) -> String {
1216 let table = self
1217 .table
1218 .clone()
1219 .unwrap_or_else(|| M::table_name().to_string());
1220
1221 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1222 sql.push_str(&self.build_where_clause_with_options(false));
1224 sql
1225 }
1226
1227 fn build_where_clause_with_params(&self) -> (String, Vec<Value>) {
1237 self.build_where_clause_with_params_options(true)
1239 }
1240
1241 fn build_where_clause_with_params_options(
1250 &self,
1251 include_soft_delete: bool,
1252 ) -> (String, Vec<Value>) {
1253 let soft_delete_cond = if include_soft_delete {
1255 self.build_soft_delete_condition()
1256 } else {
1257 None
1258 };
1259
1260 let tenant_cond = self.build_tenant_condition();
1262
1263 if self.where_conditions.is_empty()
1265 && soft_delete_cond.is_none()
1266 && tenant_cond.is_none()
1267 && self.keyset_cursor.is_none()
1268 {
1269 return (String::new(), Vec::new());
1270 }
1271
1272 let mut params = Vec::new();
1273
1274 let mut conditions: Vec<String> = self
1275 .where_conditions
1276 .iter()
1277 .map(|cond| match cond {
1278 WhereCondition::And(c) => c.clone(),
1279 WhereCondition::Or(c) => format!("OR {}", c),
1280 WhereCondition::Eq(f, v) => {
1282 params.push(v.clone());
1283 format!("{} = ?", self.dialect.quote(f))
1284 }
1285 WhereCondition::Ne(f, v) => {
1286 params.push(v.clone());
1287 format!("{} != ?", self.dialect.quote(f))
1288 }
1289 WhereCondition::Gt(f, v) => {
1290 params.push(v.clone());
1291 format!("{} > ?", self.dialect.quote(f))
1292 }
1293 WhereCondition::Ge(f, v) => {
1294 params.push(v.clone());
1295 format!("{} >= ?", self.dialect.quote(f))
1296 }
1297 WhereCondition::Lt(f, v) => {
1298 params.push(v.clone());
1299 format!("{} < ?", self.dialect.quote(f))
1300 }
1301 WhereCondition::Le(f, v) => {
1302 params.push(v.clone());
1303 format!("{} <= ?", self.dialect.quote(f))
1304 }
1305 WhereCondition::Like(f, v) => {
1306 params.push(v.clone());
1307 format!("{} LIKE ?", self.dialect.quote(f))
1308 }
1309 WhereCondition::OrEq(f, v) => {
1310 params.push(v.clone());
1311 format!("OR {} = ?", self.dialect.quote(f))
1312 }
1313 WhereCondition::OrNe(f, v) => {
1314 params.push(v.clone());
1315 format!("OR {} != ?", self.dialect.quote(f))
1316 }
1317 WhereCondition::OrGt(f, v) => {
1318 params.push(v.clone());
1319 format!("OR {} > ?", self.dialect.quote(f))
1320 }
1321 WhereCondition::OrGe(f, v) => {
1322 params.push(v.clone());
1323 format!("OR {} >= ?", self.dialect.quote(f))
1324 }
1325 WhereCondition::OrLt(f, v) => {
1326 params.push(v.clone());
1327 format!("OR {} < ?", self.dialect.quote(f))
1328 }
1329 WhereCondition::OrLe(f, v) => {
1330 params.push(v.clone());
1331 format!("OR {} <= ?", self.dialect.quote(f))
1332 }
1333 WhereCondition::OrLike(f, v) => {
1334 params.push(v.clone());
1335 format!("OR {} LIKE ?", self.dialect.quote(f))
1336 }
1337 WhereCondition::In(f, vals) => {
1338 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1339 params.extend(vals.iter().cloned());
1340 format!("{} IN ({})", self.dialect.quote(f), placeholders.join(", "))
1341 }
1342 WhereCondition::NotIn(f, vals) => {
1343 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1344 params.extend(vals.iter().cloned());
1345 format!(
1346 "{} NOT IN ({})",
1347 self.dialect.quote(f),
1348 placeholders.join(", ")
1349 )
1350 }
1351 WhereCondition::Between(f, start, end) => {
1352 params.push(start.clone());
1353 params.push(end.clone());
1354 format!("{} BETWEEN ? AND ?", self.dialect.quote(f))
1355 }
1356 WhereCondition::NotBetween(f, start, end) => {
1357 params.push(start.clone());
1358 params.push(end.clone());
1359 format!("{} NOT BETWEEN ? AND ?", self.dialect.quote(f))
1360 }
1361 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1362 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1363 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1364 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1365 })
1366 .collect();
1367
1368 if let Some(sd_cond) = soft_delete_cond {
1370 conditions.push(sd_cond);
1371 }
1372
1373 if let Some((t_sql, t_value)) = tenant_cond {
1375 conditions.push(t_sql);
1376 params.push(t_value);
1377 }
1378
1379 if let Some(ref cursor) = self.keyset_cursor {
1381 let op = match cursor.direction {
1382 KeysetDirection::After => ">",
1383 KeysetDirection::Before => "<",
1384 };
1385 conditions.push(format!("{} {} ?", self.dialect.quote(&cursor.field), op));
1386 params.push(cursor.value.clone());
1387 }
1388
1389 if conditions.is_empty() {
1390 return (String::new(), params);
1391 }
1392
1393 let mut groups: Vec<Vec<String>> = Vec::new();
1395 let mut current_group: Vec<String> = Vec::new();
1396 for cond in conditions.iter() {
1397 if let Some(stripped) = cond.strip_prefix("OR ") {
1398 current_group.push(stripped.to_string());
1399 } else {
1400 if !current_group.is_empty() {
1401 groups.push(std::mem::take(&mut current_group));
1402 }
1403 current_group.push(cond.clone());
1404 }
1405 }
1406 if !current_group.is_empty() {
1407 groups.push(current_group);
1408 }
1409
1410 let group_strs: Vec<String> = groups
1411 .iter()
1412 .map(|g| {
1413 if g.len() == 1 {
1414 g[0].clone()
1415 } else {
1416 format!("({})", g.join(" OR "))
1417 }
1418 })
1419 .collect();
1420
1421 (format!(" WHERE {}", group_strs.join(" AND ")), params)
1422 }
1423
1424 pub fn build_select_with_params(&self) -> (String, Vec<Value>) {
1429 let table = self
1430 .table
1431 .clone()
1432 .unwrap_or_else(|| M::table_name().to_string());
1433 let columns = if self.select_columns.is_empty() {
1434 "*".to_string()
1435 } else {
1436 self.select_columns.join(", ")
1437 };
1438
1439 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1440
1441 for join in &self.joins {
1442 match join {
1443 JoinClause::Inner(t, l, r) => {
1444 sql.push_str(&format!(
1445 " INNER JOIN {} ON {} = {}",
1446 self.dialect.quote(t),
1447 self.dialect.quote(l),
1448 self.dialect.quote(r)
1449 ));
1450 }
1451 JoinClause::Left(t, l, r) => {
1452 sql.push_str(&format!(
1453 " LEFT JOIN {} ON {} = {}",
1454 self.dialect.quote(t),
1455 self.dialect.quote(l),
1456 self.dialect.quote(r)
1457 ));
1458 }
1459 JoinClause::Right(t, l, r) => {
1460 sql.push_str(&format!(
1461 " RIGHT JOIN {} ON {} = {}",
1462 self.dialect.quote(t),
1463 self.dialect.quote(l),
1464 self.dialect.quote(r)
1465 ));
1466 }
1467 JoinClause::Cross(t, on) => {
1468 sql.push_str(&format!(
1469 " CROSS JOIN {} ON {}",
1470 self.dialect.quote(t),
1471 self.dialect.quote(on)
1472 ));
1473 }
1474 }
1475 }
1476
1477 let mut params = Vec::new();
1478 let (where_clause, where_params) = self.build_where_clause_with_params();
1480 if !where_clause.is_empty() {
1481 sql.push_str(&where_clause);
1482 params = where_params;
1483 }
1484
1485 if !self.group_by.is_empty() {
1486 let cols: Vec<String> = self
1487 .group_by
1488 .iter()
1489 .map(|c| self.dialect.quote(c))
1490 .collect();
1491 sql.push_str(" GROUP BY ");
1492 sql.push_str(&cols.join(", "));
1493 }
1494
1495 if !self.having_conditions.is_empty() {
1496 sql.push_str(" HAVING ");
1497 for (i, cond) in self.having_conditions.iter().enumerate() {
1498 if i > 0 {
1499 sql.push_str(" AND ");
1500 }
1501 if let WhereCondition::And(c) = cond {
1502 sql.push_str(c);
1503 }
1504 }
1505 }
1506
1507 if !self.order_by.is_empty() {
1508 let order_cols: Vec<String> = self
1509 .order_by
1510 .iter()
1511 .map(|o| {
1512 let dir = match o.direction {
1513 OrderDirection::Asc => " ASC",
1514 OrderDirection::Desc => " DESC",
1515 };
1516 format!("{}{}", self.dialect.quote(&o.field), dir)
1517 })
1518 .collect();
1519 sql.push_str(" ORDER BY ");
1520 sql.push_str(&order_cols.join(", "));
1521 }
1522
1523 if let Some(limit) = self.limit_value {
1524 sql.push_str(&format!(" LIMIT {}", limit));
1525 }
1526 if let Some(offset) = self.offset_value {
1527 sql.push_str(&format!(" OFFSET {}", offset));
1528 }
1529
1530 (sql, params)
1531 }
1532
1533 pub fn build_insert_with_params(
1535 &self,
1536 data: &std::collections::HashMap<String, Value>,
1537 ) -> (String, Vec<Value>) {
1538 let table = self
1539 .table
1540 .clone()
1541 .unwrap_or_else(|| M::table_name().to_string());
1542 if data.is_empty() {
1543 return (String::new(), Vec::new());
1544 }
1545
1546 let mut columns = Vec::with_capacity(data.len());
1547 let mut params = Vec::with_capacity(data.len());
1548 let placeholders: Vec<&str> = data.iter().map(|_| "?").collect();
1549 for (k, v) in data.iter() {
1550 columns.push(self.dialect.quote(k));
1551 params.push(v.clone());
1552 }
1553
1554 let sql = format!(
1555 "INSERT INTO {} ({}) VALUES ({})",
1556 self.dialect.quote(&table),
1557 columns.join(", "),
1558 placeholders.join(", ")
1559 );
1560 (sql, params)
1561 }
1562
1563 pub fn build_batch_insert_with_params(
1570 &self,
1571 rows: &[std::collections::HashMap<String, Value>],
1572 ) -> (String, Vec<Value>) {
1573 let table = self
1574 .table
1575 .clone()
1576 .unwrap_or_else(|| M::table_name().to_string());
1577 if rows.is_empty() {
1578 return (String::new(), Vec::new());
1579 }
1580
1581 let first_row = &rows[0];
1583 let columns: Vec<String> = first_row.keys().cloned().collect();
1584 let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1585
1586 let mut params = Vec::with_capacity(rows.len() * columns.len());
1587 let mut value_groups: Vec<String> = Vec::with_capacity(rows.len());
1588 for row in rows {
1589 let placeholders: Vec<String> = columns
1590 .iter()
1591 .map(|col| {
1592 match row.get(col) {
1593 Some(v) => {
1594 params.push(v.clone());
1595 "?".to_string()
1596 }
1597 None => "NULL".to_string(),
1598 }
1599 })
1600 .collect();
1601 value_groups.push(format!("({})", placeholders.join(", ")));
1602 }
1603
1604 let sql = format!(
1605 "INSERT INTO {} ({}) VALUES {}",
1606 self.dialect.quote(&table),
1607 quoted_columns.join(", "),
1608 value_groups.join(", ")
1609 );
1610 (sql, params)
1611 }
1612
1613 pub fn build_batch_upsert_with_params(
1634 &self,
1635 rows: &[std::collections::HashMap<String, Value>],
1636 conflict_columns: &[&str],
1637 update_columns: &[&str],
1638 ) -> Result<(String, Vec<Value>), crate::DbError> {
1639 if rows.is_empty() {
1640 return Err(crate::DbError::InvalidInput(
1641 "build_batch_upsert_with_params: rows cannot be empty".to_string(),
1642 ));
1643 }
1644
1645 let (insert_sql, params) = self.build_batch_insert_with_params(rows);
1647 if insert_sql.is_empty() {
1648 return Err(crate::DbError::InvalidInput(
1649 "build_batch_upsert_with_params: failed to build INSERT part".to_string(),
1650 ));
1651 }
1652
1653 let all_columns: Vec<String> = rows[0].keys().cloned().collect();
1655
1656 let conflict_clause = self
1658 .dialect
1659 .build_upsert_on_conflict(conflict_columns, update_columns, &all_columns)
1660 .ok_or_else(|| {
1661 crate::DbError::InvalidInput(format!(
1662 "build_batch_upsert_with_params: dialect {:?} does not support upsert (ON CONFLICT / ON DUPLICATE KEY UPDATE). Consider using MERGE statement or individual upserts instead.",
1663 self.dialect.db_type()
1664 ))
1665 })?;
1666
1667 let sql = format!("{} {}", insert_sql, conflict_clause);
1668 Ok((sql, params))
1669 }
1670
1671 pub fn build_update_with_params(
1674 &self,
1675 data: &std::collections::HashMap<String, Value>,
1676 ) -> (String, Vec<Value>) {
1677 let table = self
1678 .table
1679 .clone()
1680 .unwrap_or_else(|| M::table_name().to_string());
1681 if data.is_empty() {
1682 return (String::new(), Vec::new());
1683 }
1684
1685 let mut set_clauses = Vec::with_capacity(data.len());
1686 let mut params = Vec::with_capacity(data.len());
1687 for (k, v) in data.iter() {
1688 set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
1689 params.push(v.clone());
1690 }
1691
1692 let mut sql = format!(
1693 "UPDATE {} SET {}",
1694 self.dialect.quote(&table),
1695 set_clauses.join(", ")
1696 );
1697
1698 let (where_clause, where_params) = self.build_where_clause_with_params();
1700 if !where_clause.is_empty() {
1701 sql.push_str(&where_clause);
1702 params.extend(where_params);
1703 }
1704
1705 (sql, params)
1706 }
1707
1708 pub fn build_delete_with_params(&self) -> (String, Vec<Value>) {
1714 let table = self
1715 .table
1716 .clone()
1717 .unwrap_or_else(|| M::table_name().to_string());
1718
1719 if let Some(field) = self.soft_delete_field() {
1721 let (where_clause, where_params) = self.build_where_clause_with_params();
1722 let sql = format!(
1723 "UPDATE {} SET {} = NOW(){}",
1724 self.dialect.quote(&table),
1725 self.dialect.quote(field),
1726 where_clause
1727 );
1728 return (sql, where_params);
1729 }
1730
1731 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1732 let mut params = Vec::new();
1733
1734 let (where_clause, where_params) = self.build_where_clause_with_params();
1735 if !where_clause.is_empty() {
1736 sql.push_str(&where_clause);
1737 params = where_params;
1738 }
1739
1740 (sql, params)
1741 }
1742
1743 pub fn build_force_delete_with_params(&self) -> (String, Vec<Value>) {
1747 let table = self
1748 .table
1749 .clone()
1750 .unwrap_or_else(|| M::table_name().to_string());
1751
1752 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1753 let mut params = Vec::new();
1754
1755 let (where_clause, where_params) = self.build_where_clause_with_params_options(false);
1757 if !where_clause.is_empty() {
1758 sql.push_str(&where_clause);
1759 params = where_params;
1760 }
1761
1762 (sql, params)
1763 }
1764
1765 pub fn build_count(&self) -> String {
1766 let table = self
1767 .table
1768 .clone()
1769 .unwrap_or_else(|| M::table_name().to_string());
1770
1771 let mut sql = format!(
1772 "SELECT COUNT(*) as total FROM {}",
1773 self.dialect.quote(&table)
1774 );
1775 sql.push_str(&self.build_where_clause());
1776 sql
1777 }
1778
1779 pub fn build_exists(&self) -> String {
1780 let table = self
1781 .table
1782 .clone()
1783 .unwrap_or_else(|| M::table_name().to_string());
1784
1785 let mut sql = format!("SELECT 1 FROM {}", self.dialect.quote(&table));
1786 sql.push_str(&self.build_where_clause());
1787 sql.push_str(" LIMIT 1");
1788 format!("SELECT EXISTS({})", sql)
1789 }
1790
1791 pub fn build_max(&self, field: &str) -> String {
1792 let table = self
1793 .table
1794 .clone()
1795 .unwrap_or_else(|| M::table_name().to_string());
1796
1797 let mut sql = format!(
1798 "SELECT MAX({}) as max_val FROM {}",
1799 self.dialect.quote(field),
1800 self.dialect.quote(&table)
1801 );
1802 sql.push_str(&self.build_where_clause());
1803 sql
1804 }
1805
1806 pub fn build_min(&self, field: &str) -> String {
1807 let table = self
1808 .table
1809 .clone()
1810 .unwrap_or_else(|| M::table_name().to_string());
1811
1812 let mut sql = format!(
1813 "SELECT MIN({}) as min_val FROM {}",
1814 self.dialect.quote(field),
1815 self.dialect.quote(&table)
1816 );
1817 sql.push_str(&self.build_where_clause());
1818 sql
1819 }
1820
1821 pub fn build_sum(&self, field: &str) -> String {
1822 let table = self
1823 .table
1824 .clone()
1825 .unwrap_or_else(|| M::table_name().to_string());
1826
1827 let mut sql = format!(
1828 "SELECT SUM({}) as sum_val FROM {}",
1829 self.dialect.quote(field),
1830 self.dialect.quote(&table)
1831 );
1832 sql.push_str(&self.build_where_clause());
1833 sql
1834 }
1835
1836 pub fn build_avg(&self, field: &str) -> String {
1837 let table = self
1838 .table
1839 .clone()
1840 .unwrap_or_else(|| M::table_name().to_string());
1841
1842 let mut sql = format!(
1843 "SELECT AVG({}) as avg_val FROM {}",
1844 self.dialect.quote(field),
1845 self.dialect.quote(&table)
1846 );
1847 sql.push_str(&self.build_where_clause());
1848 sql
1849 }
1850
1851 pub fn validate(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
1854 let sql = self.build_select();
1855 let mut errors = Vec::new();
1856
1857 if let Err(e) = sz_orm_sql_validator::validate_select(&sql) {
1858 errors.push(e);
1859 }
1860
1861 if !self.joins.is_empty() {
1863 for join in &self.joins {
1864 match join {
1865 JoinClause::Inner(_, left, right)
1866 | JoinClause::Left(_, left, right)
1867 | JoinClause::Right(_, left, right) => {
1868 if let Err(e) = sz_orm_sql_validator::validate_column_name(left) {
1869 errors.push(e);
1870 }
1871 if let Err(e) = sz_orm_sql_validator::validate_column_name(right) {
1872 errors.push(e);
1873 }
1874 }
1875 _ => {}
1876 }
1877 }
1878 }
1879
1880 let table = self
1882 .table
1883 .clone()
1884 .unwrap_or_else(|| M::table_name().to_string());
1885 if let Err(e) = sz_orm_sql_validator::validate_table_name(&table) {
1886 errors.push(e);
1887 }
1888
1889 if errors.is_empty() {
1890 Ok(())
1891 } else {
1892 Err(errors)
1893 }
1894 }
1895
1896 pub fn validate_insert(
1899 &self,
1900 data: &std::collections::HashMap<String, Value>,
1901 ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
1902 let sql = self.build_insert(data);
1903 let mut errors = Vec::new();
1904
1905 if sql.is_empty() {
1906 errors.push(sz_orm_sql_validator::SqlValidationError::EmptyInsertData);
1907 return Err(errors);
1908 }
1909
1910 if let Err(e) = sz_orm_sql_validator::validate_insert(&sql) {
1911 errors.push(e);
1912 }
1913
1914 if errors.is_empty() {
1915 Ok(())
1916 } else {
1917 Err(errors)
1918 }
1919 }
1920
1921 pub fn validate_update(
1924 &self,
1925 data: &std::collections::HashMap<String, Value>,
1926 ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
1927 let sql = self.build_update(data);
1928 let mut errors = Vec::new();
1929
1930 if sql.is_empty() {
1931 errors.push(sz_orm_sql_validator::SqlValidationError::EmptyUpdateData);
1932 return Err(errors);
1933 }
1934
1935 if let Err(e) = sz_orm_sql_validator::validate_update(&sql) {
1936 errors.push(e);
1937 }
1938
1939 if errors.is_empty() {
1940 Ok(())
1941 } else {
1942 Err(errors)
1943 }
1944 }
1945
1946 pub fn validate_delete(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
1948 let sql = self.build_delete();
1949 let mut errors = Vec::new();
1950
1951 if let Err(e) = sz_orm_sql_validator::validate_delete(&sql) {
1952 errors.push(e);
1953 }
1954
1955 if errors.is_empty() {
1956 Ok(())
1957 } else {
1958 Err(errors)
1959 }
1960 }
1961}
1962
1963impl<M: Model> fmt::Debug for QueryBuilder<M> {
1964 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1965 f.debug_struct("QueryBuilder")
1966 .field("table", &self.table)
1967 .field("select_columns", &self.select_columns)
1968 .field("where_conditions", &self.where_conditions.len())
1969 .field("limit", &self.limit_value)
1970 .finish()
1971 }
1972}
1973
1974#[cfg(test)]
1975mod tests {
1976 use super::*;
1977 use crate::db_type::DbType;
1978 use crate::dialect::get_dialect;
1979
1980 struct TestModel;
1981 impl Model for TestModel {
1982 type PrimaryKey = i64;
1983
1984 fn table_name() -> &'static str {
1985 "test_models"
1986 }
1987
1988 fn pk(&self) -> Self::PrimaryKey {
1989 1
1990 }
1991
1992 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
1993 }
1994
1995 #[test]
1996 fn test_query_builder_select() {
1997 let dialect = get_dialect(DbType::MySQL).unwrap();
1998 let builder = QueryBuilder::<TestModel>::new(dialect);
1999
2000 let sql = builder
2001 .table("users")
2002 .select(vec!["id", "name"])
2003 .build_select();
2004 assert!(sql.contains("SELECT id, name FROM"));
2005 assert!(sql.contains("`users`"));
2006 }
2007
2008 #[test]
2009 fn test_query_builder_where() {
2010 let dialect = get_dialect(DbType::MySQL).unwrap();
2011 let builder = QueryBuilder::<TestModel>::new(dialect);
2012
2013 let sql = builder
2014 .table("users")
2015 .where_cond("status = 'active'")
2016 .where_cond("age > 18")
2017 .build_select();
2018
2019 assert!(sql.contains("WHERE"));
2020 assert!(sql.contains("status = 'active'"));
2021 assert!(sql.contains("age > 18"));
2022 }
2023
2024 #[test]
2025 fn test_query_builder_order_by() {
2026 let dialect = get_dialect(DbType::MySQL).unwrap();
2027 let builder = QueryBuilder::<TestModel>::new(dialect);
2028
2029 let sql = builder
2030 .table("users")
2031 .order_by("created_at")
2032 .order_desc("id")
2033 .build_select();
2034
2035 assert!(sql.contains("ORDER BY"));
2036 assert!(sql.contains("`created_at` ASC"));
2037 assert!(sql.contains("`id` DESC"));
2038 }
2039
2040 #[test]
2041 fn test_query_builder_limit_offset() {
2042 let dialect = get_dialect(DbType::MySQL).unwrap();
2043 let builder = QueryBuilder::<TestModel>::new(dialect);
2044
2045 let sql = builder.table("users").limit(10).offset(20).build_select();
2046
2047 assert!(sql.contains("LIMIT 10"));
2048 assert!(sql.contains("OFFSET 20"));
2049 }
2050
2051 #[test]
2052 fn test_query_builder_page() {
2053 let dialect = get_dialect(DbType::MySQL).unwrap();
2054 let builder = QueryBuilder::<TestModel>::new(dialect);
2055
2056 let sql = builder.table("users").page(3, 20).build_select();
2057
2058 assert!(sql.contains("LIMIT 20"));
2059 assert!(sql.contains("OFFSET 40"));
2060 }
2061
2062 #[test]
2063 fn test_query_builder_insert() {
2064 let dialect = get_dialect(DbType::MySQL).unwrap();
2065 let builder = QueryBuilder::<TestModel>::new(dialect);
2066
2067 let mut data = std::collections::HashMap::new();
2068 data.insert("name".to_string(), Value::String("test".to_string()));
2069 data.insert("age".to_string(), Value::I64(25));
2070
2071 let sql = builder.table("users").build_insert(&data);
2072
2073 assert!(sql.contains("INSERT INTO"));
2074 assert!(sql.contains("`name`"));
2075 assert!(sql.contains("'test'"));
2076 }
2077
2078 #[test]
2079 fn test_query_builder_update() {
2080 let dialect = get_dialect(DbType::MySQL).unwrap();
2081 let builder = QueryBuilder::<TestModel>::new(dialect);
2082
2083 let mut data = std::collections::HashMap::new();
2084 data.insert("name".to_string(), Value::String("updated".to_string()));
2085
2086 let sql = builder
2087 .table("users")
2088 .where_cond("id = 1")
2089 .build_update(&data);
2090
2091 assert!(sql.contains("UPDATE"));
2092 assert!(sql.contains("`name` = 'updated'"));
2093 assert!(sql.contains("WHERE"));
2094 }
2095
2096 #[test]
2097 fn test_query_builder_delete() {
2098 let dialect = get_dialect(DbType::MySQL).unwrap();
2099 let builder = QueryBuilder::<TestModel>::new(dialect);
2100
2101 let sql = builder.table("users").where_cond("id = 1").build_delete();
2102
2103 assert!(sql.contains("DELETE FROM"));
2104 assert!(sql.contains("WHERE"));
2105 }
2106
2107 #[test]
2108 fn test_query_builder_count() {
2109 let dialect = get_dialect(DbType::MySQL).unwrap();
2110 let builder = QueryBuilder::<TestModel>::new(dialect);
2111
2112 let sql = builder.table("users").build_count();
2113
2114 assert!(sql.contains("SELECT COUNT(*)"));
2115 assert!(sql.contains("FROM"));
2116 }
2117
2118 #[test]
2119 fn test_query_builder_where_in() {
2120 let dialect = get_dialect(DbType::MySQL).unwrap();
2121 let builder = QueryBuilder::<TestModel>::new(dialect);
2122
2123 let sql = builder
2124 .table("users")
2125 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2126 .build_select();
2127
2128 assert!(sql.contains("IN ("));
2129 }
2130
2131 #[test]
2132 fn test_query_builder_where_between() {
2133 let dialect = get_dialect(DbType::MySQL).unwrap();
2134 let builder = QueryBuilder::<TestModel>::new(dialect);
2135
2136 let sql = builder
2137 .table("users")
2138 .where_between("age", Value::I64(18), Value::I64(30))
2139 .build_select();
2140
2141 assert!(sql.contains("BETWEEN"));
2142 }
2143
2144 #[test]
2145 fn test_query_builder_where_null() {
2146 let dialect = get_dialect(DbType::MySQL).unwrap();
2147 let builder = QueryBuilder::<TestModel>::new(dialect);
2148
2149 let sql = builder
2150 .table("users")
2151 .where_null("deleted_at")
2152 .build_select();
2153
2154 assert!(sql.contains("IS NULL"));
2155 }
2156
2157 #[test]
2158 fn test_query_builder_join() {
2159 let dialect = get_dialect(DbType::MySQL).unwrap();
2160 let builder = QueryBuilder::<TestModel>::new(dialect);
2161
2162 let sql = builder
2163 .table("users")
2164 .join_inner("posts", "users.id", "posts.user_id")
2165 .build_select();
2166
2167 assert!(sql.contains("INNER JOIN"));
2168 assert!(sql.contains("`posts`"));
2169 }
2170
2171 #[test]
2172 fn test_query_builder_group_by() {
2173 let dialect = get_dialect(DbType::MySQL).unwrap();
2174 let builder = QueryBuilder::<TestModel>::new(dialect);
2175
2176 let sql = builder.table("users").group_by("status").build_select();
2177
2178 assert!(sql.contains("GROUP BY"));
2179 assert!(sql.contains("`status`"));
2180 }
2181
2182 #[test]
2183 fn test_query_builder_max() {
2184 let dialect = get_dialect(DbType::MySQL).unwrap();
2185 let builder = QueryBuilder::<TestModel>::new(dialect);
2186
2187 let sql = builder.table("users").build_max("score");
2188
2189 assert!(sql.contains("MAX("));
2190 assert!(sql.contains("`score`"));
2191 }
2192
2193 #[test]
2194 fn test_query_builder_min() {
2195 let dialect = get_dialect(DbType::MySQL).unwrap();
2196 let builder = QueryBuilder::<TestModel>::new(dialect);
2197
2198 let sql = builder.table("users").build_min("price");
2199
2200 assert!(sql.contains("MIN("));
2201 assert!(sql.contains("`price`"));
2202 }
2203
2204 #[test]
2205 fn test_query_builder_sum() {
2206 let dialect = get_dialect(DbType::MySQL).unwrap();
2207 let builder = QueryBuilder::<TestModel>::new(dialect);
2208
2209 let sql = builder.table("orders").build_sum("amount");
2210
2211 assert!(sql.contains("SUM("));
2212 assert!(sql.contains("`amount`"));
2213 }
2214
2215 #[test]
2216 fn test_query_builder_avg() {
2217 let dialect = get_dialect(DbType::MySQL).unwrap();
2218 let builder = QueryBuilder::<TestModel>::new(dialect);
2219
2220 let sql = builder.table("scores").build_avg("value");
2221
2222 assert!(sql.contains("AVG("));
2223 assert!(sql.contains("`value`"));
2224 }
2225
2226 #[test]
2227 fn test_validator_select() {
2228 let dialect = get_dialect(DbType::MySQL).unwrap();
2229 let builder = QueryBuilder::<TestModel>::new(dialect);
2230
2231 let result = builder.table("users").select(vec!["id", "name"]).validate();
2232 assert!(result.is_ok());
2233 }
2234
2235 #[test]
2236 fn test_validator_select_with_join() {
2237 let dialect = get_dialect(DbType::MySQL).unwrap();
2238 let builder = QueryBuilder::<TestModel>::new(dialect);
2239
2240 let result = builder
2241 .table("users")
2242 .join_inner("posts", "users.id", "posts.user_id")
2243 .validate();
2244 assert!(result.is_ok());
2245 }
2246
2247 #[test]
2248 fn test_validator_insert() {
2249 let dialect = get_dialect(DbType::MySQL).unwrap();
2250 let builder = QueryBuilder::<TestModel>::new(dialect);
2251
2252 let mut data = std::collections::HashMap::new();
2253 data.insert("name".to_string(), Value::String("test".to_string()));
2254
2255 let result = builder.table("users").validate_insert(&data);
2256 assert!(result.is_ok());
2257 }
2258
2259 #[test]
2260 fn test_validator_insert_empty_data() {
2261 let dialect = get_dialect(DbType::MySQL).unwrap();
2262 let builder = QueryBuilder::<TestModel>::new(dialect);
2263
2264 let data = std::collections::HashMap::new();
2265 let result = builder.table("users").validate_insert(&data);
2266 assert!(result.is_err());
2267 }
2268
2269 #[test]
2270 fn test_validator_update() {
2271 let dialect = get_dialect(DbType::MySQL).unwrap();
2272 let builder = QueryBuilder::<TestModel>::new(dialect);
2273
2274 let mut data = std::collections::HashMap::new();
2275 data.insert("name".to_string(), Value::String("updated".to_string()));
2276
2277 let result = builder.table("users").validate_update(&data);
2278 assert!(result.is_ok());
2279 }
2280
2281 #[test]
2282 fn test_validator_update_empty_data() {
2283 let dialect = get_dialect(DbType::MySQL).unwrap();
2284 let builder = QueryBuilder::<TestModel>::new(dialect);
2285
2286 let data = std::collections::HashMap::new();
2287 let result = builder.table("users").validate_update(&data);
2288 assert!(result.is_err());
2289 }
2290
2291 #[test]
2292 fn test_validator_delete() {
2293 let dialect = get_dialect(DbType::MySQL).unwrap();
2294 let builder = QueryBuilder::<TestModel>::new(dialect);
2295
2296 let result = builder
2297 .table("users")
2298 .where_cond("id = 1")
2299 .validate_delete();
2300 assert!(result.is_ok());
2301 }
2302
2303 #[test]
2304 fn test_validator_delete_no_where() {
2305 let dialect = get_dialect(DbType::MySQL).unwrap();
2306 let builder = QueryBuilder::<TestModel>::new(dialect);
2307
2308 let result = builder.table("users").validate_delete();
2310 assert!(result.is_ok());
2311 }
2312
2313 #[test]
2316 fn test_m3_select_quoted_valid_columns() {
2317 let dialect = get_dialect(DbType::MySQL).unwrap();
2318 let builder = QueryBuilder::<TestModel>::new(dialect);
2319 let builder = builder
2320 .table("users")
2321 .select_quoted(vec!["id", "name"])
2322 .expect("valid columns should succeed");
2323 let sql = builder.build_select();
2324 assert!(sql.contains("SELECT `id`, `name` FROM"));
2326 assert!(sql.contains("`users`"));
2327 }
2328
2329 #[test]
2330 fn test_m3_select_quoted_rejects_sql_injection() {
2331 let dialect = get_dialect(DbType::MySQL).unwrap();
2332 let builder = QueryBuilder::<TestModel>::new(dialect);
2333
2334 let result = builder
2336 .table("users")
2337 .select_quoted(vec!["id; DROP TABLE users"]);
2338 assert!(result.is_err());
2339
2340 let dialect = get_dialect(DbType::MySQL).unwrap();
2342 let builder = QueryBuilder::<TestModel>::new(dialect);
2343 let result = builder.table("users").select_quoted(vec!["name'"]);
2344 assert!(result.is_err());
2345
2346 let dialect = get_dialect(DbType::MySQL).unwrap();
2348 let builder = QueryBuilder::<TestModel>::new(dialect);
2349 let result = builder.table("users").select_quoted(vec!["1col"]);
2350 assert!(result.is_err());
2351
2352 let dialect = get_dialect(DbType::MySQL).unwrap();
2354 let builder = QueryBuilder::<TestModel>::new(dialect);
2355 let result = builder.table("users").select_quoted(vec!["col name"]);
2356 assert!(result.is_err());
2357 }
2358
2359 #[test]
2360 fn test_m3_select_quoted_postgresql_dialect() {
2361 let dialect = get_dialect(DbType::PostgreSQL).unwrap();
2362 let builder = QueryBuilder::<TestModel>::new(dialect);
2363 let builder = builder
2364 .table("users")
2365 .select_quoted(vec!["id", "name"])
2366 .expect("valid columns should succeed");
2367 let sql = builder.build_select();
2368 assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2370 assert!(sql.contains("\"users\""));
2371 }
2372
2373 struct SoftDeleteModel;
2377 impl Model for SoftDeleteModel {
2378 type PrimaryKey = i64;
2379
2380 fn table_name() -> &'static str {
2381 "soft_users"
2382 }
2383
2384 fn pk(&self) -> Self::PrimaryKey {
2385 1
2386 }
2387
2388 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2389
2390 fn soft_delete_field() -> Option<&'static str> {
2391 Some("deleted_at")
2392 }
2393 }
2394
2395 #[test]
2399 fn test_p01_soft_delete_select_auto_filter() {
2400 let dialect = get_dialect(DbType::MySQL).unwrap();
2401 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2402 let sql = builder.table("soft_users").build_select();
2403 assert!(
2405 sql.contains("`deleted_at` IS NULL"),
2406 "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2407 sql
2408 );
2409 }
2410
2411 #[test]
2413 fn test_p01_soft_delete_select_with_user_where() {
2414 let dialect = get_dialect(DbType::MySQL).unwrap();
2415 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2416 .table("soft_users")
2417 .where_eq("status", Value::String("active".into()))
2418 .build_select();
2419 assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2421 assert!(
2422 sql.contains("`deleted_at` IS NULL"),
2423 "软删除条件应自动追加: {}",
2424 sql
2425 );
2426 }
2427
2428 #[test]
2432 fn test_p01_soft_delete_without_soft_delete() {
2433 let dialect = get_dialect(DbType::MySQL).unwrap();
2434 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2435 .table("soft_users")
2436 .without_soft_delete()
2437 .build_select();
2438 assert!(
2440 !sql.contains("`deleted_at` IS NULL"),
2441 "without_soft_delete 应禁用过滤,实际: {}",
2442 sql
2443 );
2444 assert!(
2446 !sql.contains("WHERE"),
2447 "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2448 sql
2449 );
2450 }
2451
2452 #[test]
2456 fn test_p01_soft_delete_delete_becomes_update() {
2457 let dialect = get_dialect(DbType::MySQL).unwrap();
2458 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2459 .table("soft_users")
2460 .where_eq("id", Value::I64(42))
2461 .build_delete();
2462 assert!(
2464 sql.starts_with("UPDATE"),
2465 "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2466 sql
2467 );
2468 assert!(
2469 !sql.contains("DELETE FROM"),
2470 "不应生成 DELETE FROM: {}",
2471 sql
2472 );
2473 assert!(
2474 sql.contains("`deleted_at` = NOW()"),
2475 "应设置 deleted_at = NOW(): {}",
2476 sql
2477 );
2478 assert!(
2480 sql.contains("`deleted_at` IS NULL"),
2481 "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2482 sql
2483 );
2484 }
2485
2486 #[test]
2490 fn test_p01_soft_delete_force_delete() {
2491 let dialect = get_dialect(DbType::MySQL).unwrap();
2492 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2493 .table("soft_users")
2494 .where_eq("id", Value::I64(99))
2495 .build_force_delete();
2496 assert!(
2498 sql.starts_with("DELETE FROM"),
2499 "build_force_delete 应生成 DELETE FROM,实际: {}",
2500 sql
2501 );
2502 assert!(
2504 !sql.contains("`deleted_at` IS NULL"),
2505 "物理删除不应追加软删除过滤: {}",
2506 sql
2507 );
2508 }
2509
2510 #[test]
2512 fn test_p01_soft_delete_select_with_params() {
2513 let dialect = get_dialect(DbType::MySQL).unwrap();
2514 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2515 .table("soft_users")
2516 .where_eq("id", Value::I64(1))
2517 .build_select_with_params();
2518 assert!(
2519 sql.contains("`deleted_at` IS NULL"),
2520 "参数化版本也应自动追加软删除: {}",
2521 sql
2522 );
2523 assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
2524 assert_eq!(params[0], Value::I64(1));
2525 }
2526
2527 #[test]
2529 fn test_p01_soft_delete_delete_with_params_becomes_update() {
2530 let dialect = get_dialect(DbType::MySQL).unwrap();
2531 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2532 .table("soft_users")
2533 .where_eq("id", Value::I64(7))
2534 .build_delete_with_params();
2535 assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
2536 assert!(sql.contains("`deleted_at` = NOW()"), "应设置 NOW(): {}", sql);
2537 assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
2538 }
2539
2540 #[test]
2542 fn test_p01_soft_delete_force_delete_with_params() {
2543 let dialect = get_dialect(DbType::MySQL).unwrap();
2544 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2545 .table("soft_users")
2546 .where_eq("id", Value::I64(11))
2547 .build_force_delete_with_params();
2548 assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
2549 assert!(
2550 !sql.contains("`deleted_at` IS NULL"),
2551 "不应追加软删除过滤: {}",
2552 sql
2553 );
2554 assert_eq!(params.len(), 1);
2555 }
2556
2557 #[test]
2561 fn test_p01_non_soft_delete_model_unchanged() {
2562 let dialect = get_dialect(DbType::MySQL).unwrap();
2563 let sql = QueryBuilder::<TestModel>::new(dialect)
2564 .table("users")
2565 .where_eq("id", Value::I64(1))
2566 .build_select();
2567 assert!(
2568 !sql.contains("deleted_at"),
2569 "非软删除模型不应追加 deleted_at: {}",
2570 sql
2571 );
2572 let dialect = get_dialect(DbType::MySQL).unwrap();
2574 let del_sql = QueryBuilder::<TestModel>::new(dialect)
2575 .table("users")
2576 .where_eq("id", Value::I64(1))
2577 .build_delete();
2578 assert!(
2579 del_sql.starts_with("DELETE FROM"),
2580 "非软删除模型 build_delete 应生成 DELETE: {}",
2581 del_sql
2582 );
2583 }
2584
2585 #[test]
2587 fn test_p01_soft_delete_count_auto_filter() {
2588 let dialect = get_dialect(DbType::MySQL).unwrap();
2589 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2590 .table("soft_users")
2591 .build_count();
2592 assert!(
2593 sql.contains("`deleted_at` IS NULL"),
2594 "build_count 也应追加软删除过滤: {}",
2595 sql
2596 );
2597 }
2598
2599 #[test]
2605 fn test_p02_where_eq_uses_placeholder() {
2606 let dialect = get_dialect(DbType::MySQL).unwrap();
2607 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2608 .table("users")
2609 .where_eq("name", Value::String("alice".into()))
2610 .build_select_with_params();
2611 assert!(
2613 sql.contains("`name` = ?"),
2614 "应使用 ? 占位符: {}",
2615 sql
2616 );
2617 assert!(
2618 !sql.contains("'alice'"),
2619 "不应内嵌值到 SQL: {}",
2620 sql
2621 );
2622 assert_eq!(params.len(), 1);
2623 assert_eq!(params[0], Value::String("alice".into()));
2624 }
2625
2626 #[test]
2628 fn test_p02_where_like_uses_placeholder() {
2629 let dialect = get_dialect(DbType::MySQL).unwrap();
2630 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2631 .table("users")
2632 .where_like("name", Value::String("%alice%".into()))
2633 .build_select_with_params();
2634 assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
2635 assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
2636 assert_eq!(params.len(), 1);
2637 }
2638
2639 #[test]
2643 fn test_p02_injection_protection_drop_table() {
2644 let dialect = get_dialect(DbType::MySQL).unwrap();
2645 let evil_input = "'; DROP TABLE users; --".to_string();
2646 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2647 .table("users")
2648 .where_eq("name", Value::String(evil_input.clone()))
2649 .build_select_with_params();
2650 assert!(
2652 !sql.contains("DROP TABLE"),
2653 "SQL 注入未防护: {}",
2654 sql
2655 );
2656 assert_eq!(params.len(), 1);
2658 assert_eq!(params[0], Value::String(evil_input));
2659 assert_eq!(sql.matches('?').count(), 1);
2661 }
2662
2663 #[test]
2665 fn test_p02_injection_protection_or_one_equals_one() {
2666 let dialect = get_dialect(DbType::MySQL).unwrap();
2667 let evil = "' OR '1'='1".to_string();
2668 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2669 .table("users")
2670 .where_eq("name", Value::String(evil.clone()))
2671 .build_select_with_params();
2672 assert!(
2673 !sql.contains("OR '1'='1'"),
2674 "OR 1=1 注入未防护: {}",
2675 sql
2676 );
2677 assert_eq!(params.len(), 1);
2678 assert_eq!(params[0], Value::String(evil));
2679 }
2680
2681 #[test]
2683 fn test_p02_multiple_params_order() {
2684 let dialect = get_dialect(DbType::MySQL).unwrap();
2685 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2686 .table("users")
2687 .where_eq("name", Value::String("alice".into()))
2688 .where_gt("age", Value::I64(18))
2689 .where_le("score", Value::F64(99.5))
2690 .build_select_with_params();
2691 assert_eq!(
2692 sql.matches('?').count(),
2693 3,
2694 "应有 3 个占位符: {}",
2695 sql
2696 );
2697 assert_eq!(params.len(), 3);
2698 assert_eq!(params[0], Value::String("alice".into()));
2700 assert_eq!(params[1], Value::I64(18));
2701 assert_eq!(params[2], Value::F64(99.5));
2702 }
2703
2704 #[test]
2706 fn test_p02_where_in_uses_placeholders() {
2707 let dialect = get_dialect(DbType::MySQL).unwrap();
2708 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2709 .table("users")
2710 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2711 .build_select_with_params();
2712 assert!(
2713 sql.contains("`id` IN (?, ?, ?)"),
2714 "应使用 3 个占位符: {}",
2715 sql
2716 );
2717 assert_eq!(params.len(), 3);
2718 }
2719
2720 #[test]
2722 fn test_p02_where_between_uses_placeholders() {
2723 let dialect = get_dialect(DbType::MySQL).unwrap();
2724 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2725 .table("users")
2726 .where_between("age", Value::I64(18), Value::I64(65))
2727 .build_select_with_params();
2728 assert!(
2729 sql.contains("`age` BETWEEN ? AND ?"),
2730 "应使用 2 个占位符: {}",
2731 sql
2732 );
2733 assert_eq!(params.len(), 2);
2734 assert_eq!(params[0], Value::I64(18));
2735 assert_eq!(params[1], Value::I64(65));
2736 }
2737
2738 #[test]
2740 fn test_p02_update_params_order_set_before_where() {
2741 let dialect = get_dialect(DbType::MySQL).unwrap();
2742 let mut data = std::collections::HashMap::new();
2743 data.insert("name".to_string(), Value::String("bob".into()));
2744 data.insert("age".to_string(), Value::I64(30));
2745 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2746 .table("users")
2747 .where_eq("id", Value::I64(99))
2748 .build_update_with_params(&data);
2749 assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
2751 assert_eq!(params.len(), 3);
2752 assert_eq!(params[2], Value::I64(99));
2755 }
2756
2757 #[test]
2761 fn test_p02_build_where_clause_inlines_value() {
2762 let dialect = get_dialect(DbType::MySQL).unwrap();
2763 let sql = QueryBuilder::<TestModel>::new(dialect)
2764 .table("users")
2765 .where_eq("name", Value::String("alice".into()))
2766 .build_select();
2767 assert!(
2769 sql.contains("`name` = "),
2770 "无参数版本应含 WHERE 条件: {}",
2771 sql
2772 );
2773 assert!(
2775 !sql.contains("`name` = ?"),
2776 "无参数版本不应使用 ? 占位符: {}",
2777 sql
2778 );
2779 }
2780
2781 #[test]
2783 fn test_p01_is_soft_delete_disabled_flag() {
2784 let dialect = get_dialect(DbType::MySQL).unwrap();
2785 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2786 assert!(
2787 !builder.is_soft_delete_disabled(),
2788 "默认应启用软删除过滤"
2789 );
2790 let builder = QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL).unwrap())
2791 .without_soft_delete();
2792 assert!(
2793 builder.is_soft_delete_disabled(),
2794 "without_soft_delete 后应反映禁用状态"
2795 );
2796 }
2797
2798 struct TenantModel;
2802 impl Model for TenantModel {
2803 type PrimaryKey = i64;
2804
2805 fn table_name() -> &'static str {
2806 "orders"
2807 }
2808
2809 fn pk(&self) -> Self::PrimaryKey {
2810 1
2811 }
2812
2813 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2814
2815 fn tenant_field() -> Option<&'static str> {
2816 Some("tenant_id")
2817 }
2818 }
2819
2820 struct SoftDeleteAndTenantModel;
2822 impl Model for SoftDeleteAndTenantModel {
2823 type PrimaryKey = i64;
2824
2825 fn table_name() -> &'static str {
2826 "documents"
2827 }
2828
2829 fn pk(&self) -> Self::PrimaryKey {
2830 1
2831 }
2832
2833 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2834
2835 fn soft_delete_field() -> Option<&'static str> {
2836 Some("deleted_at")
2837 }
2838
2839 fn tenant_field() -> Option<&'static str> {
2840 Some("tenant_id")
2841 }
2842 }
2843
2844 #[test]
2848 fn test_p03_tenant_select_auto_filter() {
2849 let dialect = get_dialect(DbType::MySQL).unwrap();
2850 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2851 .table("orders")
2852 .with_tenant_id(42)
2853 .build_select_with_params();
2854 assert!(
2855 sql.contains("`tenant_id` = ?"),
2856 "多租户模型应自动追加 tenant_id = ?: {}",
2857 sql
2858 );
2859 assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
2860 assert_eq!(params[0], Value::I64(42));
2861 }
2862
2863 #[test]
2865 fn test_p03_tenant_select_with_user_where() {
2866 let dialect = get_dialect(DbType::MySQL).unwrap();
2867 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2868 .table("orders")
2869 .with_tenant_id(7)
2870 .where_eq("status", Value::String("active".into()))
2871 .build_select_with_params();
2872 assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
2873 assert!(
2874 sql.contains("`tenant_id` = ?"),
2875 "租户条件应自动追加: {}",
2876 sql
2877 );
2878 assert_eq!(params.len(), 2, "应有 2 个参数");
2879 assert_eq!(params[0], Value::String("active".into()));
2881 assert_eq!(params[1], Value::I64(7));
2882 }
2883
2884 #[test]
2888 fn test_p03_tenant_without_tenant() {
2889 let dialect = get_dialect(DbType::MySQL).unwrap();
2890 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2891 .table("orders")
2892 .with_tenant_id(42)
2893 .without_tenant()
2894 .build_select_with_params();
2895 assert!(
2896 !sql.contains("`tenant_id` = ?"),
2897 "without_tenant 应禁用过滤: {}",
2898 sql
2899 );
2900 assert_eq!(params.len(), 0, "不应有租户参数");
2901 }
2902
2903 #[test]
2907 fn test_p03_tenant_delete_auto_filter() {
2908 let dialect = get_dialect(DbType::MySQL).unwrap();
2909 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2910 .table("orders")
2911 .with_tenant_id(99)
2912 .where_eq("id", Value::I64(1))
2913 .build_delete_with_params();
2914 assert!(
2915 sql.contains("`tenant_id` = ?"),
2916 "删除应自动追加租户条件: {}",
2917 sql
2918 );
2919 assert_eq!(params.len(), 2);
2921 assert_eq!(params[0], Value::I64(1));
2922 assert_eq!(params[1], Value::I64(99));
2923 }
2924
2925 #[test]
2927 fn test_p03_tenant_update_auto_filter() {
2928 let dialect = get_dialect(DbType::MySQL).unwrap();
2929 let mut data = std::collections::HashMap::new();
2930 data.insert("status".to_string(), Value::String("shipped".into()));
2931 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2932 .table("orders")
2933 .with_tenant_id(5)
2934 .where_eq("id", Value::I64(10))
2935 .build_update_with_params(&data);
2936 assert!(
2937 sql.contains("`tenant_id` = ?"),
2938 "更新应自动追加租户条件: {}",
2939 sql
2940 );
2941 assert_eq!(params.len(), 3);
2943 assert_eq!(params[2], Value::I64(5));
2945 }
2946
2947 #[test]
2949 fn test_p03_tenant_count_auto_filter() {
2950 let dialect = get_dialect(DbType::MySQL).unwrap();
2951 let sql = QueryBuilder::<TenantModel>::new(dialect)
2952 .table("orders")
2953 .with_tenant_id(42)
2954 .build_count();
2955 assert!(
2956 sql.contains("`tenant_id` = 42"),
2957 "build_count 应追加租户条件(无参数版本内嵌值): {}",
2958 sql
2959 );
2960 }
2961
2962 #[test]
2966 fn test_p03_non_tenant_model_unchanged() {
2967 let dialect = get_dialect(DbType::MySQL).unwrap();
2968 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2970 .table("users")
2971 .with_tenant_id(42)
2972 .build_select_with_params();
2973 assert!(
2974 !sql.contains("tenant_id"),
2975 "非多租户模型不应追加 tenant_id: {}",
2976 sql
2977 );
2978 assert_eq!(params.len(), 0);
2979 }
2980
2981 #[test]
2985 fn test_p03_tenant_no_id_no_filter() {
2986 let dialect = get_dialect(DbType::MySQL).unwrap();
2987 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2988 .table("orders")
2989 .build_select_with_params();
2990 assert!(
2991 !sql.contains("tenant_id"),
2992 "未设置 tenant_id 时不应追加过滤: {}",
2993 sql
2994 );
2995 assert_eq!(params.len(), 0);
2996 }
2997
2998 #[test]
3002 fn test_p03_soft_delete_and_tenant_combined() {
3003 let dialect = get_dialect(DbType::MySQL).unwrap();
3004 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3005 .table("documents")
3006 .with_tenant_id(100)
3007 .where_eq("title", Value::String("report".into()))
3008 .build_select_with_params();
3009 assert!(
3011 sql.contains("`deleted_at` IS NULL"),
3012 "应追加软删除条件: {}",
3013 sql
3014 );
3015 assert!(
3017 sql.contains("`tenant_id` = ?"),
3018 "应追加租户条件: {}",
3019 sql
3020 );
3021 assert!(
3023 sql.contains("`title` = ?"),
3024 "用户条件应保留: {}",
3025 sql
3026 );
3027 assert_eq!(params.len(), 2);
3029 assert_eq!(params[0], Value::String("report".into()));
3030 assert_eq!(params[1], Value::I64(100));
3031 }
3032
3033 #[test]
3035 fn test_p03_without_tenant_and_soft_delete() {
3036 let dialect = get_dialect(DbType::MySQL).unwrap();
3037 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3038 .table("documents")
3039 .with_tenant_id(100)
3040 .without_tenant()
3041 .without_soft_delete()
3042 .build_select_with_params();
3043 assert!(
3044 !sql.contains("`deleted_at` IS NULL"),
3045 "应禁用软删除: {}",
3046 sql
3047 );
3048 assert!(
3049 !sql.contains("`tenant_id` = ?"),
3050 "应禁用租户: {}",
3051 sql
3052 );
3053 assert_eq!(params.len(), 0);
3054 }
3055
3056 #[test]
3058 fn test_p03_is_tenant_disabled_flag() {
3059 let dialect = get_dialect(DbType::MySQL).unwrap();
3060 let builder = QueryBuilder::<TenantModel>::new(dialect);
3061 assert!(
3062 !builder.is_tenant_disabled(),
3063 "默认应启用租户过滤"
3064 );
3065 let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL).unwrap())
3066 .with_tenant_id(1)
3067 .without_tenant();
3068 assert!(
3069 builder.is_tenant_disabled(),
3070 "without_tenant 后应反映禁用状态"
3071 );
3072 }
3073
3074 #[test]
3078 fn test_p03_tenant_force_delete_keeps_tenant_filter() {
3079 let dialect = get_dialect(DbType::MySQL).unwrap();
3080 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3081 .table("orders")
3082 .with_tenant_id(42)
3083 .where_eq("id", Value::I64(999))
3084 .build_force_delete_with_params();
3085 assert!(
3088 sql.contains("`tenant_id` = ?"),
3089 "物理删除应保留租户条件: {}",
3090 sql
3091 );
3092 assert_eq!(params.len(), 2);
3093 assert_eq!(params[0], Value::I64(999));
3094 assert_eq!(params[1], Value::I64(42));
3095 }
3096}