1use std::fmt;
27use sz_orm_model::Dialect;
28use sz_orm_model::Model;
29use sz_orm_model::Value;
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, sz_orm_model::DbError> {
322 let mut quoted = Vec::with_capacity(columns.len());
323 for col in columns {
324 sz_orm_model::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(
622 mut self,
623 field: impl Into<String>,
624 cursor_value: Value,
625 page_size: usize,
626 ) -> Self {
627 let field_str = field.into();
628 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
630 existing.direction = OrderDirection::Asc;
631 } else {
632 self.order_by.push(OrderClause {
633 field: field_str.clone(),
634 direction: OrderDirection::Asc,
635 });
636 }
637 self.limit_value = Some(page_size);
638 self.offset_value = None;
640 self.keyset_cursor = Some(KeysetCursor {
641 field: field_str,
642 value: cursor_value,
643 direction: KeysetDirection::After,
644 });
645 self
646 }
647
648 pub fn keyset_before(
695 mut self,
696 field: impl Into<String>,
697 cursor_value: Value,
698 page_size: usize,
699 ) -> Self {
700 let field_str = field.into();
701 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
703 existing.direction = OrderDirection::Desc;
704 } else {
705 self.order_by.push(OrderClause {
706 field: field_str.clone(),
707 direction: OrderDirection::Desc,
708 });
709 }
710 self.limit_value = Some(page_size);
711 self.offset_value = None;
712 self.keyset_cursor = Some(KeysetCursor {
713 field: field_str,
714 value: cursor_value,
715 direction: KeysetDirection::Before,
716 });
717 self
718 }
719
720 pub fn join_inner(
721 mut self,
722 table: impl Into<String>,
723 on_left: impl Into<String>,
724 on_right: impl Into<String>,
725 ) -> Self {
726 self.joins.push(JoinClause::Inner(
727 table.into(),
728 on_left.into(),
729 on_right.into(),
730 ));
731 self
732 }
733
734 pub fn join_left(
735 mut self,
736 table: impl Into<String>,
737 on_left: impl Into<String>,
738 on_right: impl Into<String>,
739 ) -> Self {
740 self.joins.push(JoinClause::Left(
741 table.into(),
742 on_left.into(),
743 on_right.into(),
744 ));
745 self
746 }
747
748 pub fn join_right(
749 mut self,
750 table: impl Into<String>,
751 on_left: impl Into<String>,
752 on_right: impl Into<String>,
753 ) -> Self {
754 self.joins.push(JoinClause::Right(
755 table.into(),
756 on_left.into(),
757 on_right.into(),
758 ));
759 self
760 }
761
762 #[tracing::instrument(skip(self), fields(op = "select"))]
795 pub fn build_select(&self) -> String {
796 let table = self
797 .table
798 .clone()
799 .unwrap_or_else(|| M::table_name().to_string());
800
801 let columns = if self.select_columns.is_empty() {
802 "*".to_string()
803 } else {
804 self.select_columns.join(", ")
805 };
806
807 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
808
809 for join in &self.joins {
810 match join {
811 JoinClause::Inner(t, l, r) => {
812 sql.push_str(&format!(
813 " INNER JOIN {} ON {} = {}",
814 self.dialect.quote(t),
815 self.dialect.quote(l),
816 self.dialect.quote(r)
817 ));
818 }
819 JoinClause::Left(t, l, r) => {
820 sql.push_str(&format!(
821 " LEFT JOIN {} ON {} = {}",
822 self.dialect.quote(t),
823 self.dialect.quote(l),
824 self.dialect.quote(r)
825 ));
826 }
827 JoinClause::Right(t, l, r) => {
828 sql.push_str(&format!(
829 " RIGHT JOIN {} ON {} = {}",
830 self.dialect.quote(t),
831 self.dialect.quote(l),
832 self.dialect.quote(r)
833 ));
834 }
835 JoinClause::Cross(t, on) => {
836 sql.push_str(&format!(
837 " CROSS JOIN {} ON {}",
838 self.dialect.quote(t),
839 self.dialect.quote(on)
840 ));
841 }
842 }
843 }
844
845 let where_clause = self.build_where_clause();
847 if !where_clause.is_empty() {
848 sql.push_str(&where_clause);
849 }
850
851 if !self.group_by.is_empty() {
852 let cols: Vec<String> = self
853 .group_by
854 .iter()
855 .map(|c| self.dialect.quote(c))
856 .collect();
857 sql.push_str(" GROUP BY ");
858 sql.push_str(&cols.join(", "));
859 }
860
861 if !self.having_conditions.is_empty() {
862 sql.push_str(" HAVING ");
863 for (i, cond) in self.having_conditions.iter().enumerate() {
864 if i > 0 {
865 sql.push_str(" AND ");
866 }
867 if let WhereCondition::And(c) = cond {
868 sql.push_str(c);
869 }
870 }
871 }
872
873 if !self.order_by.is_empty() {
874 let order_cols: Vec<String> = self
875 .order_by
876 .iter()
877 .map(|o| {
878 let dir = match o.direction {
879 OrderDirection::Asc => " ASC",
880 OrderDirection::Desc => " DESC",
881 };
882 format!("{}{}", self.dialect.quote(&o.field), dir)
883 })
884 .collect();
885 sql.push_str(" ORDER BY ");
886 sql.push_str(&order_cols.join(", "));
887 }
888
889 if let Some(limit) = self.limit_value {
890 sql.push_str(&format!(" LIMIT {}", limit));
891 }
892
893 if let Some(offset) = self.offset_value {
894 sql.push_str(&format!(" OFFSET {}", offset));
895 }
896
897 sql
898 }
899
900 fn build_where_clause(&self) -> String {
906 self.build_where_clause_with_options(true)
907 }
908
909 fn build_where_clause_with_options(&self, include_soft_delete: bool) -> String {
917 let soft_delete_cond = if include_soft_delete {
919 self.build_soft_delete_condition()
920 } else {
921 None
922 };
923
924 let tenant_cond = self.build_tenant_condition().map(|(sql, value)| {
926 sql.replacen('?', &value.to_param_with_dialect(&*self.dialect), 1)
928 });
929
930 if self.where_conditions.is_empty()
932 && soft_delete_cond.is_none()
933 && tenant_cond.is_none()
934 && self.keyset_cursor.is_none()
935 {
936 return String::new();
937 }
938
939 let mut conditions: Vec<String> = self
941 .where_conditions
942 .iter()
943 .map(|cond| match cond {
944 WhereCondition::And(c) => c.clone(),
945 WhereCondition::Or(c) => format!("OR {}", c),
946 WhereCondition::Eq(f, v) => format!(
948 "{} = {}",
949 self.dialect.quote(f),
950 v.to_param_with_dialect(&*self.dialect)
951 ),
952 WhereCondition::Ne(f, v) => format!(
953 "{} != {}",
954 self.dialect.quote(f),
955 v.to_param_with_dialect(&*self.dialect)
956 ),
957 WhereCondition::Gt(f, v) => format!(
958 "{} > {}",
959 self.dialect.quote(f),
960 v.to_param_with_dialect(&*self.dialect)
961 ),
962 WhereCondition::Ge(f, v) => format!(
963 "{} >= {}",
964 self.dialect.quote(f),
965 v.to_param_with_dialect(&*self.dialect)
966 ),
967 WhereCondition::Lt(f, v) => format!(
968 "{} < {}",
969 self.dialect.quote(f),
970 v.to_param_with_dialect(&*self.dialect)
971 ),
972 WhereCondition::Le(f, v) => format!(
973 "{} <= {}",
974 self.dialect.quote(f),
975 v.to_param_with_dialect(&*self.dialect)
976 ),
977 WhereCondition::Like(f, v) => format!(
978 "{} LIKE {}",
979 self.dialect.quote(f),
980 v.to_param_with_dialect(&*self.dialect)
981 ),
982 WhereCondition::OrEq(f, v) => format!(
983 "OR {} = {}",
984 self.dialect.quote(f),
985 v.to_param_with_dialect(&*self.dialect)
986 ),
987 WhereCondition::OrNe(f, v) => format!(
988 "OR {} != {}",
989 self.dialect.quote(f),
990 v.to_param_with_dialect(&*self.dialect)
991 ),
992 WhereCondition::OrGt(f, v) => format!(
993 "OR {} > {}",
994 self.dialect.quote(f),
995 v.to_param_with_dialect(&*self.dialect)
996 ),
997 WhereCondition::OrGe(f, v) => format!(
998 "OR {} >= {}",
999 self.dialect.quote(f),
1000 v.to_param_with_dialect(&*self.dialect)
1001 ),
1002 WhereCondition::OrLt(f, v) => format!(
1003 "OR {} < {}",
1004 self.dialect.quote(f),
1005 v.to_param_with_dialect(&*self.dialect)
1006 ),
1007 WhereCondition::OrLe(f, v) => format!(
1008 "OR {} <= {}",
1009 self.dialect.quote(f),
1010 v.to_param_with_dialect(&*self.dialect)
1011 ),
1012 WhereCondition::OrLike(f, v) => format!(
1013 "OR {} LIKE {}",
1014 self.dialect.quote(f),
1015 v.to_param_with_dialect(&*self.dialect)
1016 ),
1017 WhereCondition::In(f, vals) => {
1018 let vals_str: Vec<String> = vals
1020 .iter()
1021 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1022 .collect();
1023 format!("{} IN ({})", self.dialect.quote(f), vals_str.join(", "))
1024 }
1025 WhereCondition::NotIn(f, vals) => {
1026 let vals_str: Vec<String> = vals
1027 .iter()
1028 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1029 .collect();
1030 format!("{} NOT IN ({})", self.dialect.quote(f), vals_str.join(", "))
1031 }
1032 WhereCondition::Between(f, start, end) => {
1033 format!(
1034 "{} BETWEEN {} AND {}",
1035 self.dialect.quote(f),
1036 start.to_param_with_dialect(&*self.dialect),
1037 end.to_param_with_dialect(&*self.dialect)
1038 )
1039 }
1040 WhereCondition::NotBetween(f, start, end) => {
1041 format!(
1042 "{} NOT BETWEEN {} AND {}",
1043 self.dialect.quote(f),
1044 start.to_param_with_dialect(&*self.dialect),
1045 end.to_param_with_dialect(&*self.dialect)
1046 )
1047 }
1048 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1049 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1050 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1051 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1052 })
1053 .collect();
1054
1055 if let Some(sd_cond) = soft_delete_cond {
1057 conditions.push(sd_cond);
1058 }
1059
1060 if let Some(t_cond) = tenant_cond {
1062 conditions.push(t_cond);
1063 }
1064
1065 if let Some(ref cursor) = self.keyset_cursor {
1067 let op = match cursor.direction {
1068 KeysetDirection::After => ">",
1069 KeysetDirection::Before => "<",
1070 };
1071 conditions.push(format!(
1072 "{} {} {}",
1073 self.dialect.quote(&cursor.field),
1074 op,
1075 cursor.value.to_param_with_dialect(&*self.dialect)
1076 ));
1077 }
1078
1079 if conditions.is_empty() {
1080 return String::new();
1081 }
1082
1083 let mut groups: Vec<Vec<String>> = Vec::new();
1086 let mut current_group: Vec<String> = Vec::new();
1087 for cond in conditions.iter() {
1088 if let Some(stripped) = cond.strip_prefix("OR ") {
1089 current_group.push(stripped.to_string());
1091 } else {
1092 if !current_group.is_empty() {
1094 groups.push(std::mem::take(&mut current_group));
1095 }
1096 current_group.push(cond.clone());
1097 }
1098 }
1099 if !current_group.is_empty() {
1100 groups.push(current_group);
1101 }
1102
1103 let group_strs: Vec<String> = groups
1104 .iter()
1105 .map(|g| {
1106 if g.len() == 1 {
1107 g[0].clone()
1108 } else {
1109 format!("({})", g.join(" OR "))
1110 }
1111 })
1112 .collect();
1113
1114 format!(" WHERE {}", group_strs.join(" AND "))
1115 }
1116
1117 #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1118 pub fn build_insert(&self, data: &std::collections::HashMap<String, Value>) -> String {
1119 let table = self
1120 .table
1121 .clone()
1122 .unwrap_or_else(|| M::table_name().to_string());
1123
1124 if data.is_empty() {
1125 return String::new();
1126 }
1127
1128 let columns: Vec<String> = data.keys().map(|k| self.dialect.quote(k)).collect();
1129 let values: Vec<String> = data
1131 .values()
1132 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1133 .collect();
1134
1135 format!(
1136 "INSERT INTO {} ({}) VALUES ({})",
1137 self.dialect.quote(&table),
1138 columns.join(", "),
1139 values.join(", ")
1140 )
1141 }
1142
1143 #[tracing::instrument(skip(self, data), fields(op = "update"))]
1144 pub fn build_update(&self, data: &std::collections::HashMap<String, Value>) -> String {
1145 let table = self
1146 .table
1147 .clone()
1148 .unwrap_or_else(|| M::table_name().to_string());
1149
1150 if data.is_empty() {
1151 return String::new();
1152 }
1153
1154 let set_clauses: Vec<String> = data
1155 .iter()
1156 .map(|(k, v)| {
1157 format!(
1158 "{} = {}",
1159 self.dialect.quote(k),
1160 v.to_param_with_dialect(&*self.dialect)
1161 )
1162 })
1163 .collect();
1164
1165 let mut sql = format!(
1166 "UPDATE {} SET {}",
1167 self.dialect.quote(&table),
1168 set_clauses.join(", ")
1169 );
1170
1171 sql.push_str(&self.build_where_clause());
1172 sql
1173 }
1174
1175 #[tracing::instrument(skip(self), fields(op = "delete"))]
1186 pub fn build_delete(&self) -> String {
1187 let table = self
1188 .table
1189 .clone()
1190 .unwrap_or_else(|| M::table_name().to_string());
1191
1192 if let Some(field) = self.soft_delete_field() {
1194 let where_clause = self.build_where_clause();
1195 return format!(
1196 "UPDATE {} SET {} = NOW(){}",
1197 self.dialect.quote(&table),
1198 self.dialect.quote(field),
1199 where_clause
1200 );
1201 }
1202
1203 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1204 sql.push_str(&self.build_where_clause());
1205 sql
1206 }
1207
1208 pub fn build_force_delete(&self) -> String {
1218 let table = self
1219 .table
1220 .clone()
1221 .unwrap_or_else(|| M::table_name().to_string());
1222
1223 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1224 sql.push_str(&self.build_where_clause_with_options(false));
1226 sql
1227 }
1228
1229 fn build_where_clause_with_params(&self) -> (String, Vec<Value>) {
1239 self.build_where_clause_with_params_options(true)
1241 }
1242
1243 fn build_where_clause_with_params_options(
1252 &self,
1253 include_soft_delete: bool,
1254 ) -> (String, Vec<Value>) {
1255 let soft_delete_cond = if include_soft_delete {
1257 self.build_soft_delete_condition()
1258 } else {
1259 None
1260 };
1261
1262 let tenant_cond = self.build_tenant_condition();
1264
1265 if self.where_conditions.is_empty()
1267 && soft_delete_cond.is_none()
1268 && tenant_cond.is_none()
1269 && self.keyset_cursor.is_none()
1270 {
1271 return (String::new(), Vec::new());
1272 }
1273
1274 let mut params = Vec::new();
1275
1276 let mut conditions: Vec<String> = self
1277 .where_conditions
1278 .iter()
1279 .map(|cond| match cond {
1280 WhereCondition::And(c) => c.clone(),
1281 WhereCondition::Or(c) => format!("OR {}", c),
1282 WhereCondition::Eq(f, v) => {
1284 params.push(v.clone());
1285 format!("{} = ?", self.dialect.quote(f))
1286 }
1287 WhereCondition::Ne(f, v) => {
1288 params.push(v.clone());
1289 format!("{} != ?", self.dialect.quote(f))
1290 }
1291 WhereCondition::Gt(f, v) => {
1292 params.push(v.clone());
1293 format!("{} > ?", self.dialect.quote(f))
1294 }
1295 WhereCondition::Ge(f, v) => {
1296 params.push(v.clone());
1297 format!("{} >= ?", self.dialect.quote(f))
1298 }
1299 WhereCondition::Lt(f, v) => {
1300 params.push(v.clone());
1301 format!("{} < ?", self.dialect.quote(f))
1302 }
1303 WhereCondition::Le(f, v) => {
1304 params.push(v.clone());
1305 format!("{} <= ?", self.dialect.quote(f))
1306 }
1307 WhereCondition::Like(f, v) => {
1308 params.push(v.clone());
1309 format!("{} LIKE ?", self.dialect.quote(f))
1310 }
1311 WhereCondition::OrEq(f, v) => {
1312 params.push(v.clone());
1313 format!("OR {} = ?", self.dialect.quote(f))
1314 }
1315 WhereCondition::OrNe(f, v) => {
1316 params.push(v.clone());
1317 format!("OR {} != ?", self.dialect.quote(f))
1318 }
1319 WhereCondition::OrGt(f, v) => {
1320 params.push(v.clone());
1321 format!("OR {} > ?", self.dialect.quote(f))
1322 }
1323 WhereCondition::OrGe(f, v) => {
1324 params.push(v.clone());
1325 format!("OR {} >= ?", self.dialect.quote(f))
1326 }
1327 WhereCondition::OrLt(f, v) => {
1328 params.push(v.clone());
1329 format!("OR {} < ?", self.dialect.quote(f))
1330 }
1331 WhereCondition::OrLe(f, v) => {
1332 params.push(v.clone());
1333 format!("OR {} <= ?", self.dialect.quote(f))
1334 }
1335 WhereCondition::OrLike(f, v) => {
1336 params.push(v.clone());
1337 format!("OR {} LIKE ?", self.dialect.quote(f))
1338 }
1339 WhereCondition::In(f, vals) => {
1340 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1341 params.extend(vals.iter().cloned());
1342 format!("{} IN ({})", self.dialect.quote(f), placeholders.join(", "))
1343 }
1344 WhereCondition::NotIn(f, vals) => {
1345 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1346 params.extend(vals.iter().cloned());
1347 format!(
1348 "{} NOT IN ({})",
1349 self.dialect.quote(f),
1350 placeholders.join(", ")
1351 )
1352 }
1353 WhereCondition::Between(f, start, end) => {
1354 params.push(start.clone());
1355 params.push(end.clone());
1356 format!("{} BETWEEN ? AND ?", self.dialect.quote(f))
1357 }
1358 WhereCondition::NotBetween(f, start, end) => {
1359 params.push(start.clone());
1360 params.push(end.clone());
1361 format!("{} NOT BETWEEN ? AND ?", self.dialect.quote(f))
1362 }
1363 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1364 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1365 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1366 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1367 })
1368 .collect();
1369
1370 if let Some(sd_cond) = soft_delete_cond {
1372 conditions.push(sd_cond);
1373 }
1374
1375 if let Some((t_sql, t_value)) = tenant_cond {
1377 conditions.push(t_sql);
1378 params.push(t_value);
1379 }
1380
1381 if let Some(ref cursor) = self.keyset_cursor {
1383 let op = match cursor.direction {
1384 KeysetDirection::After => ">",
1385 KeysetDirection::Before => "<",
1386 };
1387 conditions.push(format!("{} {} ?", self.dialect.quote(&cursor.field), op));
1388 params.push(cursor.value.clone());
1389 }
1390
1391 if conditions.is_empty() {
1392 return (String::new(), params);
1393 }
1394
1395 let mut groups: Vec<Vec<String>> = Vec::new();
1397 let mut current_group: Vec<String> = Vec::new();
1398 for cond in conditions.iter() {
1399 if let Some(stripped) = cond.strip_prefix("OR ") {
1400 current_group.push(stripped.to_string());
1401 } else {
1402 if !current_group.is_empty() {
1403 groups.push(std::mem::take(&mut current_group));
1404 }
1405 current_group.push(cond.clone());
1406 }
1407 }
1408 if !current_group.is_empty() {
1409 groups.push(current_group);
1410 }
1411
1412 let group_strs: Vec<String> = groups
1413 .iter()
1414 .map(|g| {
1415 if g.len() == 1 {
1416 g[0].clone()
1417 } else {
1418 format!("({})", g.join(" OR "))
1419 }
1420 })
1421 .collect();
1422
1423 (format!(" WHERE {}", group_strs.join(" AND ")), params)
1424 }
1425
1426 pub fn build_select_with_params(&self) -> (String, Vec<Value>) {
1431 let table = self
1432 .table
1433 .clone()
1434 .unwrap_or_else(|| M::table_name().to_string());
1435 let columns = if self.select_columns.is_empty() {
1436 "*".to_string()
1437 } else {
1438 self.select_columns.join(", ")
1439 };
1440
1441 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1442
1443 for join in &self.joins {
1444 match join {
1445 JoinClause::Inner(t, l, r) => {
1446 sql.push_str(&format!(
1447 " INNER JOIN {} ON {} = {}",
1448 self.dialect.quote(t),
1449 self.dialect.quote(l),
1450 self.dialect.quote(r)
1451 ));
1452 }
1453 JoinClause::Left(t, l, r) => {
1454 sql.push_str(&format!(
1455 " LEFT JOIN {} ON {} = {}",
1456 self.dialect.quote(t),
1457 self.dialect.quote(l),
1458 self.dialect.quote(r)
1459 ));
1460 }
1461 JoinClause::Right(t, l, r) => {
1462 sql.push_str(&format!(
1463 " RIGHT JOIN {} ON {} = {}",
1464 self.dialect.quote(t),
1465 self.dialect.quote(l),
1466 self.dialect.quote(r)
1467 ));
1468 }
1469 JoinClause::Cross(t, on) => {
1470 sql.push_str(&format!(
1471 " CROSS JOIN {} ON {}",
1472 self.dialect.quote(t),
1473 self.dialect.quote(on)
1474 ));
1475 }
1476 }
1477 }
1478
1479 let mut params = Vec::new();
1480 let (where_clause, where_params) = self.build_where_clause_with_params();
1482 if !where_clause.is_empty() {
1483 sql.push_str(&where_clause);
1484 params = where_params;
1485 }
1486
1487 if !self.group_by.is_empty() {
1488 let cols: Vec<String> = self
1489 .group_by
1490 .iter()
1491 .map(|c| self.dialect.quote(c))
1492 .collect();
1493 sql.push_str(" GROUP BY ");
1494 sql.push_str(&cols.join(", "));
1495 }
1496
1497 if !self.having_conditions.is_empty() {
1498 sql.push_str(" HAVING ");
1499 for (i, cond) in self.having_conditions.iter().enumerate() {
1500 if i > 0 {
1501 sql.push_str(" AND ");
1502 }
1503 if let WhereCondition::And(c) = cond {
1504 sql.push_str(c);
1505 }
1506 }
1507 }
1508
1509 if !self.order_by.is_empty() {
1510 let order_cols: Vec<String> = self
1511 .order_by
1512 .iter()
1513 .map(|o| {
1514 let dir = match o.direction {
1515 OrderDirection::Asc => " ASC",
1516 OrderDirection::Desc => " DESC",
1517 };
1518 format!("{}{}", self.dialect.quote(&o.field), dir)
1519 })
1520 .collect();
1521 sql.push_str(" ORDER BY ");
1522 sql.push_str(&order_cols.join(", "));
1523 }
1524
1525 if let Some(limit) = self.limit_value {
1526 sql.push_str(&format!(" LIMIT {}", limit));
1527 }
1528 if let Some(offset) = self.offset_value {
1529 sql.push_str(&format!(" OFFSET {}", offset));
1530 }
1531
1532 (sql, params)
1533 }
1534
1535 pub fn build_insert_with_params(
1537 &self,
1538 data: &std::collections::HashMap<String, Value>,
1539 ) -> (String, Vec<Value>) {
1540 let table = self
1541 .table
1542 .clone()
1543 .unwrap_or_else(|| M::table_name().to_string());
1544 if data.is_empty() {
1545 return (String::new(), Vec::new());
1546 }
1547
1548 let mut columns = Vec::with_capacity(data.len());
1549 let mut params = Vec::with_capacity(data.len());
1550 let placeholders: Vec<&str> = data.iter().map(|_| "?").collect();
1551 for (k, v) in data.iter() {
1552 columns.push(self.dialect.quote(k));
1553 params.push(v.clone());
1554 }
1555
1556 let sql = format!(
1557 "INSERT INTO {} ({}) VALUES ({})",
1558 self.dialect.quote(&table),
1559 columns.join(", "),
1560 placeholders.join(", ")
1561 );
1562 (sql, params)
1563 }
1564
1565 pub fn build_batch_insert_with_params(
1572 &self,
1573 rows: &[std::collections::HashMap<String, Value>],
1574 ) -> (String, Vec<Value>) {
1575 let table = self
1576 .table
1577 .clone()
1578 .unwrap_or_else(|| M::table_name().to_string());
1579 if rows.is_empty() {
1580 return (String::new(), Vec::new());
1581 }
1582
1583 let first_row = &rows[0];
1585 let columns: Vec<String> = first_row.keys().cloned().collect();
1586 let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1587
1588 let mut params = Vec::with_capacity(rows.len() * columns.len());
1589 let mut value_groups: Vec<String> = Vec::with_capacity(rows.len());
1590 for row in rows {
1591 let placeholders: Vec<String> = columns
1592 .iter()
1593 .map(|col| match row.get(col) {
1594 Some(v) => {
1595 params.push(v.clone());
1596 "?".to_string()
1597 }
1598 None => "NULL".to_string(),
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>), sz_orm_model::DbError> {
1639 if rows.is_empty() {
1640 return Err(sz_orm_model::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(sz_orm_model::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 sz_orm_model::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)]
1975#[allow(deprecated)] mod tests {
1977 use super::*;
1978 use sz_orm_model::get_dialect;
1979 use sz_orm_model::DbType;
1980
1981 struct TestModel;
1982 impl Model for TestModel {
1983 type PrimaryKey = i64;
1984
1985 fn table_name() -> &'static str {
1986 "test_models"
1987 }
1988
1989 fn pk(&self) -> Self::PrimaryKey {
1990 1
1991 }
1992
1993 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
1994 }
1995
1996 #[test]
1997 fn test_query_builder_select() -> Result<(), sz_orm_model::DbError> {
1998 let dialect = get_dialect(DbType::MySQL)?;
1999 let builder = QueryBuilder::<TestModel>::new(dialect);
2000
2001 let sql = builder
2002 .table("users")
2003 .select(vec!["id", "name"])
2004 .build_select();
2005 assert!(sql.contains("SELECT id, name FROM"));
2006 assert!(sql.contains("`users`"));
2007 Ok(())
2008 }
2009
2010 #[test]
2011 fn test_query_builder_where() -> Result<(), sz_orm_model::DbError> {
2012 let dialect = get_dialect(DbType::MySQL)?;
2013 let builder = QueryBuilder::<TestModel>::new(dialect);
2014
2015 let sql = builder
2016 .table("users")
2017 .where_cond("status = 'active'")
2018 .where_cond("age > 18")
2019 .build_select();
2020
2021 assert!(sql.contains("WHERE"));
2022 assert!(sql.contains("status = 'active'"));
2023 assert!(sql.contains("age > 18"));
2024 Ok(())
2025 }
2026
2027 #[test]
2028 fn test_query_builder_order_by() -> Result<(), sz_orm_model::DbError> {
2029 let dialect = get_dialect(DbType::MySQL)?;
2030 let builder = QueryBuilder::<TestModel>::new(dialect);
2031
2032 let sql = builder
2033 .table("users")
2034 .order_by("created_at")
2035 .order_desc("id")
2036 .build_select();
2037
2038 assert!(sql.contains("ORDER BY"));
2039 assert!(sql.contains("`created_at` ASC"));
2040 assert!(sql.contains("`id` DESC"));
2041 Ok(())
2042 }
2043
2044 #[test]
2045 fn test_query_builder_limit_offset() -> Result<(), sz_orm_model::DbError> {
2046 let dialect = get_dialect(DbType::MySQL)?;
2047 let builder = QueryBuilder::<TestModel>::new(dialect);
2048
2049 let sql = builder.table("users").limit(10).offset(20).build_select();
2050
2051 assert!(sql.contains("LIMIT 10"));
2052 assert!(sql.contains("OFFSET 20"));
2053 Ok(())
2054 }
2055
2056 #[test]
2057 fn test_query_builder_page() -> Result<(), sz_orm_model::DbError> {
2058 let dialect = get_dialect(DbType::MySQL)?;
2059 let builder = QueryBuilder::<TestModel>::new(dialect);
2060
2061 let sql = builder.table("users").page(3, 20).build_select();
2062
2063 assert!(sql.contains("LIMIT 20"));
2064 assert!(sql.contains("OFFSET 40"));
2065 Ok(())
2066 }
2067
2068 #[test]
2069 fn test_query_builder_insert() -> Result<(), sz_orm_model::DbError> {
2070 let dialect = get_dialect(DbType::MySQL)?;
2071 let builder = QueryBuilder::<TestModel>::new(dialect);
2072
2073 let mut data = std::collections::HashMap::new();
2074 data.insert("name".to_string(), Value::String("test".to_string()));
2075 data.insert("age".to_string(), Value::I64(25));
2076
2077 let sql = builder.table("users").build_insert(&data);
2078
2079 assert!(sql.contains("INSERT INTO"));
2080 assert!(sql.contains("`name`"));
2081 assert!(sql.contains("'test'"));
2082 Ok(())
2083 }
2084
2085 #[test]
2086 fn test_query_builder_update() -> Result<(), sz_orm_model::DbError> {
2087 let dialect = get_dialect(DbType::MySQL)?;
2088 let builder = QueryBuilder::<TestModel>::new(dialect);
2089
2090 let mut data = std::collections::HashMap::new();
2091 data.insert("name".to_string(), Value::String("updated".to_string()));
2092
2093 let sql = builder
2094 .table("users")
2095 .where_cond("id = 1")
2096 .build_update(&data);
2097
2098 assert!(sql.contains("UPDATE"));
2099 assert!(sql.contains("`name` = 'updated'"));
2100 assert!(sql.contains("WHERE"));
2101 Ok(())
2102 }
2103
2104 #[test]
2105 fn test_query_builder_delete() -> Result<(), sz_orm_model::DbError> {
2106 let dialect = get_dialect(DbType::MySQL)?;
2107 let builder = QueryBuilder::<TestModel>::new(dialect);
2108
2109 let sql = builder.table("users").where_cond("id = 1").build_delete();
2110
2111 assert!(sql.contains("DELETE FROM"));
2112 assert!(sql.contains("WHERE"));
2113 Ok(())
2114 }
2115
2116 #[test]
2117 fn test_query_builder_count() -> Result<(), sz_orm_model::DbError> {
2118 let dialect = get_dialect(DbType::MySQL)?;
2119 let builder = QueryBuilder::<TestModel>::new(dialect);
2120
2121 let sql = builder.table("users").build_count();
2122
2123 assert!(sql.contains("SELECT COUNT(*)"));
2124 assert!(sql.contains("FROM"));
2125 Ok(())
2126 }
2127
2128 #[test]
2129 fn test_query_builder_where_in() -> Result<(), sz_orm_model::DbError> {
2130 let dialect = get_dialect(DbType::MySQL)?;
2131 let builder = QueryBuilder::<TestModel>::new(dialect);
2132
2133 let sql = builder
2134 .table("users")
2135 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2136 .build_select();
2137
2138 assert!(sql.contains("IN ("));
2139 Ok(())
2140 }
2141
2142 #[test]
2143 fn test_query_builder_where_between() -> Result<(), sz_orm_model::DbError> {
2144 let dialect = get_dialect(DbType::MySQL)?;
2145 let builder = QueryBuilder::<TestModel>::new(dialect);
2146
2147 let sql = builder
2148 .table("users")
2149 .where_between("age", Value::I64(18), Value::I64(30))
2150 .build_select();
2151
2152 assert!(sql.contains("BETWEEN"));
2153 Ok(())
2154 }
2155
2156 #[test]
2157 fn test_query_builder_where_null() -> Result<(), sz_orm_model::DbError> {
2158 let dialect = get_dialect(DbType::MySQL)?;
2159 let builder = QueryBuilder::<TestModel>::new(dialect);
2160
2161 let sql = builder
2162 .table("users")
2163 .where_null("deleted_at")
2164 .build_select();
2165
2166 assert!(sql.contains("IS NULL"));
2167 Ok(())
2168 }
2169
2170 #[test]
2171 fn test_query_builder_join() -> Result<(), sz_orm_model::DbError> {
2172 let dialect = get_dialect(DbType::MySQL)?;
2173 let builder = QueryBuilder::<TestModel>::new(dialect);
2174
2175 let sql = builder
2176 .table("users")
2177 .join_inner("posts", "users.id", "posts.user_id")
2178 .build_select();
2179
2180 assert!(sql.contains("INNER JOIN"));
2181 assert!(sql.contains("`posts`"));
2182 Ok(())
2183 }
2184
2185 #[test]
2186 fn test_query_builder_group_by() -> Result<(), sz_orm_model::DbError> {
2187 let dialect = get_dialect(DbType::MySQL)?;
2188 let builder = QueryBuilder::<TestModel>::new(dialect);
2189
2190 let sql = builder.table("users").group_by("status").build_select();
2191
2192 assert!(sql.contains("GROUP BY"));
2193 assert!(sql.contains("`status`"));
2194 Ok(())
2195 }
2196
2197 #[test]
2198 fn test_query_builder_max() -> Result<(), sz_orm_model::DbError> {
2199 let dialect = get_dialect(DbType::MySQL)?;
2200 let builder = QueryBuilder::<TestModel>::new(dialect);
2201
2202 let sql = builder.table("users").build_max("score");
2203
2204 assert!(sql.contains("MAX("));
2205 assert!(sql.contains("`score`"));
2206 Ok(())
2207 }
2208
2209 #[test]
2210 fn test_query_builder_min() -> Result<(), sz_orm_model::DbError> {
2211 let dialect = get_dialect(DbType::MySQL)?;
2212 let builder = QueryBuilder::<TestModel>::new(dialect);
2213
2214 let sql = builder.table("users").build_min("price");
2215
2216 assert!(sql.contains("MIN("));
2217 assert!(sql.contains("`price`"));
2218 Ok(())
2219 }
2220
2221 #[test]
2222 fn test_query_builder_sum() -> Result<(), sz_orm_model::DbError> {
2223 let dialect = get_dialect(DbType::MySQL)?;
2224 let builder = QueryBuilder::<TestModel>::new(dialect);
2225
2226 let sql = builder.table("orders").build_sum("amount");
2227
2228 assert!(sql.contains("SUM("));
2229 assert!(sql.contains("`amount`"));
2230 Ok(())
2231 }
2232
2233 #[test]
2234 fn test_query_builder_avg() -> Result<(), sz_orm_model::DbError> {
2235 let dialect = get_dialect(DbType::MySQL)?;
2236 let builder = QueryBuilder::<TestModel>::new(dialect);
2237
2238 let sql = builder.table("scores").build_avg("value");
2239
2240 assert!(sql.contains("AVG("));
2241 assert!(sql.contains("`value`"));
2242 Ok(())
2243 }
2244
2245 #[test]
2246 fn test_validator_select() -> Result<(), sz_orm_model::DbError> {
2247 let dialect = get_dialect(DbType::MySQL)?;
2248 let builder = QueryBuilder::<TestModel>::new(dialect);
2249
2250 let result = builder.table("users").select(vec!["id", "name"]).validate();
2251 assert!(result.is_ok());
2252 Ok(())
2253 }
2254
2255 #[test]
2256 fn test_validator_select_with_join() -> Result<(), sz_orm_model::DbError> {
2257 let dialect = get_dialect(DbType::MySQL)?;
2258 let builder = QueryBuilder::<TestModel>::new(dialect);
2259
2260 let result = builder
2261 .table("users")
2262 .join_inner("posts", "users.id", "posts.user_id")
2263 .validate();
2264 assert!(result.is_ok());
2265 Ok(())
2266 }
2267
2268 #[test]
2269 fn test_validator_insert() -> Result<(), sz_orm_model::DbError> {
2270 let dialect = get_dialect(DbType::MySQL)?;
2271 let builder = QueryBuilder::<TestModel>::new(dialect);
2272
2273 let mut data = std::collections::HashMap::new();
2274 data.insert("name".to_string(), Value::String("test".to_string()));
2275
2276 let result = builder.table("users").validate_insert(&data);
2277 assert!(result.is_ok());
2278 Ok(())
2279 }
2280
2281 #[test]
2282 fn test_validator_insert_empty_data() -> Result<(), sz_orm_model::DbError> {
2283 let dialect = get_dialect(DbType::MySQL)?;
2284 let builder = QueryBuilder::<TestModel>::new(dialect);
2285
2286 let data = std::collections::HashMap::new();
2287 let result = builder.table("users").validate_insert(&data);
2288 assert!(result.is_err());
2289 Ok(())
2290 }
2291
2292 #[test]
2293 fn test_validator_update() -> Result<(), sz_orm_model::DbError> {
2294 let dialect = get_dialect(DbType::MySQL)?;
2295 let builder = QueryBuilder::<TestModel>::new(dialect);
2296
2297 let mut data = std::collections::HashMap::new();
2298 data.insert("name".to_string(), Value::String("updated".to_string()));
2299
2300 let result = builder.table("users").validate_update(&data);
2301 assert!(result.is_ok());
2302 Ok(())
2303 }
2304
2305 #[test]
2306 fn test_validator_update_empty_data() -> Result<(), sz_orm_model::DbError> {
2307 let dialect = get_dialect(DbType::MySQL)?;
2308 let builder = QueryBuilder::<TestModel>::new(dialect);
2309
2310 let data = std::collections::HashMap::new();
2311 let result = builder.table("users").validate_update(&data);
2312 assert!(result.is_err());
2313 Ok(())
2314 }
2315
2316 #[test]
2317 fn test_validator_delete() -> Result<(), sz_orm_model::DbError> {
2318 let dialect = get_dialect(DbType::MySQL)?;
2319 let builder = QueryBuilder::<TestModel>::new(dialect);
2320
2321 let result = builder
2322 .table("users")
2323 .where_cond("id = 1")
2324 .validate_delete();
2325 assert!(result.is_ok());
2326 Ok(())
2327 }
2328
2329 #[test]
2330 fn test_validator_delete_no_where() -> Result<(), sz_orm_model::DbError> {
2331 let dialect = get_dialect(DbType::MySQL)?;
2332 let builder = QueryBuilder::<TestModel>::new(dialect);
2333
2334 let result = builder.table("users").validate_delete();
2336 assert!(result.is_ok());
2337 Ok(())
2338 }
2339
2340 #[test]
2343 fn test_m3_select_quoted_valid_columns() -> Result<(), sz_orm_model::DbError> {
2344 let dialect = get_dialect(DbType::MySQL)?;
2345 let builder = QueryBuilder::<TestModel>::new(dialect);
2346 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2347 let sql = builder.build_select();
2348 assert!(sql.contains("SELECT `id`, `name` FROM"));
2350 assert!(sql.contains("`users`"));
2351 Ok(())
2352 }
2353
2354 #[test]
2355 fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), sz_orm_model::DbError> {
2356 let dialect = get_dialect(DbType::MySQL)?;
2357 let builder = QueryBuilder::<TestModel>::new(dialect);
2358
2359 let result = builder
2361 .table("users")
2362 .select_quoted(vec!["id; DROP TABLE users"]);
2363 assert!(result.is_err());
2364
2365 let dialect = get_dialect(DbType::MySQL)?;
2367 let builder = QueryBuilder::<TestModel>::new(dialect);
2368 let result = builder.table("users").select_quoted(vec!["name'"]);
2369 assert!(result.is_err());
2370
2371 let dialect = get_dialect(DbType::MySQL)?;
2373 let builder = QueryBuilder::<TestModel>::new(dialect);
2374 let result = builder.table("users").select_quoted(vec!["1col"]);
2375 assert!(result.is_err());
2376
2377 let dialect = get_dialect(DbType::MySQL)?;
2379 let builder = QueryBuilder::<TestModel>::new(dialect);
2380 let result = builder.table("users").select_quoted(vec!["col name"]);
2381 assert!(result.is_err());
2382 Ok(())
2383 }
2384
2385 #[test]
2386 fn test_m3_select_quoted_postgresql_dialect() -> Result<(), sz_orm_model::DbError> {
2387 let dialect = get_dialect(DbType::PostgreSQL)?;
2388 let builder = QueryBuilder::<TestModel>::new(dialect);
2389 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2390 let sql = builder.build_select();
2391 assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2393 assert!(sql.contains("\"users\""));
2394 Ok(())
2395 }
2396
2397 struct SoftDeleteModel;
2401 impl Model for SoftDeleteModel {
2402 type PrimaryKey = i64;
2403
2404 fn table_name() -> &'static str {
2405 "soft_users"
2406 }
2407
2408 fn pk(&self) -> Self::PrimaryKey {
2409 1
2410 }
2411
2412 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2413
2414 fn soft_delete_field() -> Option<&'static str> {
2415 Some("deleted_at")
2416 }
2417 }
2418
2419 #[test]
2423 fn test_p01_soft_delete_select_auto_filter() -> Result<(), sz_orm_model::DbError> {
2424 let dialect = get_dialect(DbType::MySQL)?;
2425 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2426 let sql = builder.table("soft_users").build_select();
2427 assert!(
2429 sql.contains("`deleted_at` IS NULL"),
2430 "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2431 sql
2432 );
2433 Ok(())
2434 }
2435
2436 #[test]
2438 fn test_p01_soft_delete_select_with_user_where() -> Result<(), sz_orm_model::DbError> {
2439 let dialect = get_dialect(DbType::MySQL)?;
2440 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2441 .table("soft_users")
2442 .where_eq("status", Value::String("active".into()))
2443 .build_select();
2444 assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2446 assert!(
2447 sql.contains("`deleted_at` IS NULL"),
2448 "软删除条件应自动追加: {}",
2449 sql
2450 );
2451 Ok(())
2452 }
2453
2454 #[test]
2458 fn test_p01_soft_delete_without_soft_delete() -> Result<(), sz_orm_model::DbError> {
2459 let dialect = get_dialect(DbType::MySQL)?;
2460 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2461 .table("soft_users")
2462 .without_soft_delete()
2463 .build_select();
2464 assert!(
2466 !sql.contains("`deleted_at` IS NULL"),
2467 "without_soft_delete 应禁用过滤,实际: {}",
2468 sql
2469 );
2470 assert!(
2472 !sql.contains("WHERE"),
2473 "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2474 sql
2475 );
2476 Ok(())
2477 }
2478
2479 #[test]
2483 fn test_p01_soft_delete_delete_becomes_update() -> Result<(), sz_orm_model::DbError> {
2484 let dialect = get_dialect(DbType::MySQL)?;
2485 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2486 .table("soft_users")
2487 .where_eq("id", Value::I64(42))
2488 .build_delete();
2489 assert!(
2491 sql.starts_with("UPDATE"),
2492 "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2493 sql
2494 );
2495 assert!(
2496 !sql.contains("DELETE FROM"),
2497 "不应生成 DELETE FROM: {}",
2498 sql
2499 );
2500 assert!(
2501 sql.contains("`deleted_at` = NOW()"),
2502 "应设置 deleted_at = NOW(): {}",
2503 sql
2504 );
2505 assert!(
2507 sql.contains("`deleted_at` IS NULL"),
2508 "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2509 sql
2510 );
2511 Ok(())
2512 }
2513
2514 #[test]
2518 fn test_p01_soft_delete_force_delete() -> Result<(), sz_orm_model::DbError> {
2519 let dialect = get_dialect(DbType::MySQL)?;
2520 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2521 .table("soft_users")
2522 .where_eq("id", Value::I64(99))
2523 .build_force_delete();
2524 assert!(
2526 sql.starts_with("DELETE FROM"),
2527 "build_force_delete 应生成 DELETE FROM,实际: {}",
2528 sql
2529 );
2530 assert!(
2532 !sql.contains("`deleted_at` IS NULL"),
2533 "物理删除不应追加软删除过滤: {}",
2534 sql
2535 );
2536 Ok(())
2537 }
2538
2539 #[test]
2541 fn test_p01_soft_delete_select_with_params() -> Result<(), sz_orm_model::DbError> {
2542 let dialect = get_dialect(DbType::MySQL)?;
2543 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2544 .table("soft_users")
2545 .where_eq("id", Value::I64(1))
2546 .build_select_with_params();
2547 assert!(
2548 sql.contains("`deleted_at` IS NULL"),
2549 "参数化版本也应自动追加软删除: {}",
2550 sql
2551 );
2552 assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
2553 assert_eq!(params[0], Value::I64(1));
2554 Ok(())
2555 }
2556
2557 #[test]
2559 fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), sz_orm_model::DbError>
2560 {
2561 let dialect = get_dialect(DbType::MySQL)?;
2562 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2563 .table("soft_users")
2564 .where_eq("id", Value::I64(7))
2565 .build_delete_with_params();
2566 assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
2567 assert!(
2568 sql.contains("`deleted_at` = NOW()"),
2569 "应设置 NOW(): {}",
2570 sql
2571 );
2572 assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
2573 Ok(())
2574 }
2575
2576 #[test]
2578 fn test_p01_soft_delete_force_delete_with_params() -> Result<(), sz_orm_model::DbError> {
2579 let dialect = get_dialect(DbType::MySQL)?;
2580 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2581 .table("soft_users")
2582 .where_eq("id", Value::I64(11))
2583 .build_force_delete_with_params();
2584 assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
2585 assert!(
2586 !sql.contains("`deleted_at` IS NULL"),
2587 "不应追加软删除过滤: {}",
2588 sql
2589 );
2590 assert_eq!(params.len(), 1);
2591 Ok(())
2592 }
2593
2594 #[test]
2598 fn test_p01_non_soft_delete_model_unchanged() -> Result<(), sz_orm_model::DbError> {
2599 let dialect = get_dialect(DbType::MySQL)?;
2600 let sql = QueryBuilder::<TestModel>::new(dialect)
2601 .table("users")
2602 .where_eq("id", Value::I64(1))
2603 .build_select();
2604 assert!(
2605 !sql.contains("deleted_at"),
2606 "非软删除模型不应追加 deleted_at: {}",
2607 sql
2608 );
2609 let dialect = get_dialect(DbType::MySQL)?;
2611 let del_sql = QueryBuilder::<TestModel>::new(dialect)
2612 .table("users")
2613 .where_eq("id", Value::I64(1))
2614 .build_delete();
2615 assert!(
2616 del_sql.starts_with("DELETE FROM"),
2617 "非软删除模型 build_delete 应生成 DELETE: {}",
2618 del_sql
2619 );
2620 Ok(())
2621 }
2622
2623 #[test]
2625 fn test_p01_soft_delete_count_auto_filter() -> Result<(), sz_orm_model::DbError> {
2626 let dialect = get_dialect(DbType::MySQL)?;
2627 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2628 .table("soft_users")
2629 .build_count();
2630 assert!(
2631 sql.contains("`deleted_at` IS NULL"),
2632 "build_count 也应追加软删除过滤: {}",
2633 sql
2634 );
2635 Ok(())
2636 }
2637
2638 #[test]
2644 fn test_p02_where_eq_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2645 let dialect = get_dialect(DbType::MySQL)?;
2646 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2647 .table("users")
2648 .where_eq("name", Value::String("alice".into()))
2649 .build_select_with_params();
2650 assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
2652 assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
2653 assert_eq!(params.len(), 1);
2654 assert_eq!(params[0], Value::String("alice".into()));
2655 Ok(())
2656 }
2657
2658 #[test]
2660 fn test_p02_where_like_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2661 let dialect = get_dialect(DbType::MySQL)?;
2662 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2663 .table("users")
2664 .where_like("name", Value::String("%alice%".into()))
2665 .build_select_with_params();
2666 assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
2667 assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
2668 assert_eq!(params.len(), 1);
2669 Ok(())
2670 }
2671
2672 #[test]
2676 fn test_p02_where_ne_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2677 let dialect = get_dialect(DbType::MySQL)?;
2678 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2679 .table("users")
2680 .where_ne("status", Value::I64(0))
2681 .build_select_with_params();
2682 assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
2683 assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
2684 assert_eq!(params.len(), 1);
2685 assert_eq!(params[0], Value::I64(0));
2686 Ok(())
2687 }
2688
2689 #[test]
2693 fn test_p02_where_ge_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2694 let dialect = get_dialect(DbType::MySQL)?;
2695 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2696 .table("users")
2697 .where_ge("age", Value::I64(18))
2698 .build_select_with_params();
2699 assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
2700 assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
2701 assert_eq!(params.len(), 1);
2702 assert_eq!(params[0], Value::I64(18));
2703 Ok(())
2704 }
2705
2706 #[test]
2710 fn test_p02_where_lt_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2711 let dialect = get_dialect(DbType::MySQL)?;
2712 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2713 .table("users")
2714 .where_lt("score", Value::F64(60.0))
2715 .build_select_with_params();
2716 assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
2717 assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
2718 assert_eq!(params.len(), 1);
2719 assert_eq!(params[0], Value::F64(60.0));
2720 Ok(())
2721 }
2722
2723 #[test]
2727 fn test_p02_injection_protection_drop_table() -> Result<(), sz_orm_model::DbError> {
2728 let dialect = get_dialect(DbType::MySQL)?;
2729 let evil_input = "'; DROP TABLE users; --".to_string();
2730 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2731 .table("users")
2732 .where_eq("name", Value::String(evil_input.clone()))
2733 .build_select_with_params();
2734 assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
2736 assert_eq!(params.len(), 1);
2738 assert_eq!(params[0], Value::String(evil_input));
2739 assert_eq!(sql.matches('?').count(), 1);
2741 Ok(())
2742 }
2743
2744 #[test]
2746 fn test_p02_injection_protection_or_one_equals_one() -> Result<(), sz_orm_model::DbError> {
2747 let dialect = get_dialect(DbType::MySQL)?;
2748 let evil = "' OR '1'='1".to_string();
2749 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2750 .table("users")
2751 .where_eq("name", Value::String(evil.clone()))
2752 .build_select_with_params();
2753 assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
2754 assert_eq!(params.len(), 1);
2755 assert_eq!(params[0], Value::String(evil));
2756 Ok(())
2757 }
2758
2759 #[test]
2761 fn test_p02_multiple_params_order() -> Result<(), sz_orm_model::DbError> {
2762 let dialect = get_dialect(DbType::MySQL)?;
2763 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2764 .table("users")
2765 .where_eq("name", Value::String("alice".into()))
2766 .where_gt("age", Value::I64(18))
2767 .where_le("score", Value::F64(99.5))
2768 .build_select_with_params();
2769 assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
2770 assert_eq!(params.len(), 3);
2771 assert_eq!(params[0], Value::String("alice".into()));
2773 assert_eq!(params[1], Value::I64(18));
2774 assert_eq!(params[2], Value::F64(99.5));
2775 Ok(())
2776 }
2777
2778 #[test]
2780 fn test_p02_where_in_uses_placeholders() -> Result<(), sz_orm_model::DbError> {
2781 let dialect = get_dialect(DbType::MySQL)?;
2782 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2783 .table("users")
2784 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2785 .build_select_with_params();
2786 assert!(
2787 sql.contains("`id` IN (?, ?, ?)"),
2788 "应使用 3 个占位符: {}",
2789 sql
2790 );
2791 assert_eq!(params.len(), 3);
2792 Ok(())
2793 }
2794
2795 #[test]
2797 fn test_p02_where_between_uses_placeholders() -> Result<(), sz_orm_model::DbError> {
2798 let dialect = get_dialect(DbType::MySQL)?;
2799 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2800 .table("users")
2801 .where_between("age", Value::I64(18), Value::I64(65))
2802 .build_select_with_params();
2803 assert!(
2804 sql.contains("`age` BETWEEN ? AND ?"),
2805 "应使用 2 个占位符: {}",
2806 sql
2807 );
2808 assert_eq!(params.len(), 2);
2809 assert_eq!(params[0], Value::I64(18));
2810 assert_eq!(params[1], Value::I64(65));
2811 Ok(())
2812 }
2813
2814 #[test]
2816 fn test_p02_update_params_order_set_before_where() -> Result<(), sz_orm_model::DbError> {
2817 let dialect = get_dialect(DbType::MySQL)?;
2818 let mut data = std::collections::HashMap::new();
2819 data.insert("name".to_string(), Value::String("bob".into()));
2820 data.insert("age".to_string(), Value::I64(30));
2821 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2822 .table("users")
2823 .where_eq("id", Value::I64(99))
2824 .build_update_with_params(&data);
2825 assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
2827 assert_eq!(params.len(), 3);
2828 assert_eq!(params[2], Value::I64(99));
2831 Ok(())
2832 }
2833
2834 #[test]
2838 fn test_p02_build_where_clause_inlines_value() -> Result<(), sz_orm_model::DbError> {
2839 let dialect = get_dialect(DbType::MySQL)?;
2840 let sql = QueryBuilder::<TestModel>::new(dialect)
2841 .table("users")
2842 .where_eq("name", Value::String("alice".into()))
2843 .build_select();
2844 assert!(
2846 sql.contains("`name` = "),
2847 "无参数版本应含 WHERE 条件: {}",
2848 sql
2849 );
2850 assert!(
2852 !sql.contains("`name` = ?"),
2853 "无参数版本不应使用 ? 占位符: {}",
2854 sql
2855 );
2856 Ok(())
2857 }
2858
2859 #[test]
2861 fn test_p01_is_soft_delete_disabled_flag() -> Result<(), sz_orm_model::DbError> {
2862 let dialect = get_dialect(DbType::MySQL)?;
2863 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2864 assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
2865 let builder =
2866 QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
2867 assert!(
2868 builder.is_soft_delete_disabled(),
2869 "without_soft_delete 后应反映禁用状态"
2870 );
2871 Ok(())
2872 }
2873
2874 struct TenantModel;
2878 impl Model for TenantModel {
2879 type PrimaryKey = i64;
2880
2881 fn table_name() -> &'static str {
2882 "orders"
2883 }
2884
2885 fn pk(&self) -> Self::PrimaryKey {
2886 1
2887 }
2888
2889 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2890
2891 fn tenant_field() -> Option<&'static str> {
2892 Some("tenant_id")
2893 }
2894 }
2895
2896 struct SoftDeleteAndTenantModel;
2898 impl Model for SoftDeleteAndTenantModel {
2899 type PrimaryKey = i64;
2900
2901 fn table_name() -> &'static str {
2902 "documents"
2903 }
2904
2905 fn pk(&self) -> Self::PrimaryKey {
2906 1
2907 }
2908
2909 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2910
2911 fn soft_delete_field() -> Option<&'static str> {
2912 Some("deleted_at")
2913 }
2914
2915 fn tenant_field() -> Option<&'static str> {
2916 Some("tenant_id")
2917 }
2918 }
2919
2920 #[test]
2924 fn test_p03_tenant_select_auto_filter() -> Result<(), sz_orm_model::DbError> {
2925 let dialect = get_dialect(DbType::MySQL)?;
2926 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2927 .table("orders")
2928 .with_tenant_id(42)
2929 .build_select_with_params();
2930 assert!(
2931 sql.contains("`tenant_id` = ?"),
2932 "多租户模型应自动追加 tenant_id = ?: {}",
2933 sql
2934 );
2935 assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
2936 assert_eq!(params[0], Value::I64(42));
2937 Ok(())
2938 }
2939
2940 #[test]
2942 fn test_p03_tenant_select_with_user_where() -> Result<(), sz_orm_model::DbError> {
2943 let dialect = get_dialect(DbType::MySQL)?;
2944 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2945 .table("orders")
2946 .with_tenant_id(7)
2947 .where_eq("status", Value::String("active".into()))
2948 .build_select_with_params();
2949 assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
2950 assert!(
2951 sql.contains("`tenant_id` = ?"),
2952 "租户条件应自动追加: {}",
2953 sql
2954 );
2955 assert_eq!(params.len(), 2, "应有 2 个参数");
2956 assert_eq!(params[0], Value::String("active".into()));
2958 assert_eq!(params[1], Value::I64(7));
2959 Ok(())
2960 }
2961
2962 #[test]
2966 fn test_p03_tenant_without_tenant() -> Result<(), sz_orm_model::DbError> {
2967 let dialect = get_dialect(DbType::MySQL)?;
2968 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2969 .table("orders")
2970 .with_tenant_id(42)
2971 .without_tenant()
2972 .build_select_with_params();
2973 assert!(
2974 !sql.contains("`tenant_id` = ?"),
2975 "without_tenant 应禁用过滤: {}",
2976 sql
2977 );
2978 assert_eq!(params.len(), 0, "不应有租户参数");
2979 Ok(())
2980 }
2981
2982 #[test]
2986 fn test_p03_tenant_delete_auto_filter() -> Result<(), sz_orm_model::DbError> {
2987 let dialect = get_dialect(DbType::MySQL)?;
2988 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
2989 .table("orders")
2990 .with_tenant_id(99)
2991 .where_eq("id", Value::I64(1))
2992 .build_delete_with_params();
2993 assert!(
2994 sql.contains("`tenant_id` = ?"),
2995 "删除应自动追加租户条件: {}",
2996 sql
2997 );
2998 assert_eq!(params.len(), 2);
3000 assert_eq!(params[0], Value::I64(1));
3001 assert_eq!(params[1], Value::I64(99));
3002 Ok(())
3003 }
3004
3005 #[test]
3007 fn test_p03_tenant_update_auto_filter() -> Result<(), sz_orm_model::DbError> {
3008 let dialect = get_dialect(DbType::MySQL)?;
3009 let mut data = std::collections::HashMap::new();
3010 data.insert("status".to_string(), Value::String("shipped".into()));
3011 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3012 .table("orders")
3013 .with_tenant_id(5)
3014 .where_eq("id", Value::I64(10))
3015 .build_update_with_params(&data);
3016 assert!(
3017 sql.contains("`tenant_id` = ?"),
3018 "更新应自动追加租户条件: {}",
3019 sql
3020 );
3021 assert_eq!(params.len(), 3);
3023 assert_eq!(params[2], Value::I64(5));
3025 Ok(())
3026 }
3027
3028 #[test]
3030 fn test_p03_tenant_count_auto_filter() -> Result<(), sz_orm_model::DbError> {
3031 let dialect = get_dialect(DbType::MySQL)?;
3032 let sql = QueryBuilder::<TenantModel>::new(dialect)
3033 .table("orders")
3034 .with_tenant_id(42)
3035 .build_count();
3036 assert!(
3037 sql.contains("`tenant_id` = 42"),
3038 "build_count 应追加租户条件(无参数版本内嵌值): {}",
3039 sql
3040 );
3041 Ok(())
3042 }
3043
3044 #[test]
3048 fn test_p03_non_tenant_model_unchanged() -> Result<(), sz_orm_model::DbError> {
3049 let dialect = get_dialect(DbType::MySQL)?;
3050 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3052 .table("users")
3053 .with_tenant_id(42)
3054 .build_select_with_params();
3055 assert!(
3056 !sql.contains("tenant_id"),
3057 "非多租户模型不应追加 tenant_id: {}",
3058 sql
3059 );
3060 assert_eq!(params.len(), 0);
3061 Ok(())
3062 }
3063
3064 #[test]
3068 fn test_p03_tenant_no_id_no_filter() -> Result<(), sz_orm_model::DbError> {
3069 let dialect = get_dialect(DbType::MySQL)?;
3070 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3071 .table("orders")
3072 .build_select_with_params();
3073 assert!(
3074 !sql.contains("tenant_id"),
3075 "未设置 tenant_id 时不应追加过滤: {}",
3076 sql
3077 );
3078 assert_eq!(params.len(), 0);
3079 Ok(())
3080 }
3081
3082 #[test]
3086 fn test_p03_soft_delete_and_tenant_combined() -> Result<(), sz_orm_model::DbError> {
3087 let dialect = get_dialect(DbType::MySQL)?;
3088 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3089 .table("documents")
3090 .with_tenant_id(100)
3091 .where_eq("title", Value::String("report".into()))
3092 .build_select_with_params();
3093 assert!(
3095 sql.contains("`deleted_at` IS NULL"),
3096 "应追加软删除条件: {}",
3097 sql
3098 );
3099 assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3101 assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3103 assert_eq!(params.len(), 2);
3105 assert_eq!(params[0], Value::String("report".into()));
3106 assert_eq!(params[1], Value::I64(100));
3107 Ok(())
3108 }
3109
3110 #[test]
3112 fn test_p03_without_tenant_and_soft_delete() -> Result<(), sz_orm_model::DbError> {
3113 let dialect = get_dialect(DbType::MySQL)?;
3114 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3115 .table("documents")
3116 .with_tenant_id(100)
3117 .without_tenant()
3118 .without_soft_delete()
3119 .build_select_with_params();
3120 assert!(
3121 !sql.contains("`deleted_at` IS NULL"),
3122 "应禁用软删除: {}",
3123 sql
3124 );
3125 assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
3126 assert_eq!(params.len(), 0);
3127 Ok(())
3128 }
3129
3130 #[test]
3132 fn test_p03_is_tenant_disabled_flag() -> Result<(), sz_orm_model::DbError> {
3133 let dialect = get_dialect(DbType::MySQL)?;
3134 let builder = QueryBuilder::<TenantModel>::new(dialect);
3135 assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
3136 let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
3137 .with_tenant_id(1)
3138 .without_tenant();
3139 assert!(
3140 builder.is_tenant_disabled(),
3141 "without_tenant 后应反映禁用状态"
3142 );
3143 Ok(())
3144 }
3145
3146 #[test]
3150 fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), sz_orm_model::DbError> {
3151 let dialect = get_dialect(DbType::MySQL)?;
3152 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3153 .table("orders")
3154 .with_tenant_id(42)
3155 .where_eq("id", Value::I64(999))
3156 .build_force_delete_with_params();
3157 assert!(
3160 sql.contains("`tenant_id` = ?"),
3161 "物理删除应保留租户条件: {}",
3162 sql
3163 );
3164 assert_eq!(params.len(), 2);
3165 assert_eq!(params[0], Value::I64(999));
3166 assert_eq!(params[1], Value::I64(42));
3167 Ok(())
3168 }
3169}