1use std::fmt;
27use crate::ValidationError;
28use sz_orm_model::DEFAULT_BATCH_SIZE;
29use sz_orm_model::Dialect;
30use sz_orm_model::Model;
31use sz_orm_model::Value;
32
33pub struct QueryBuilder<M: Model> {
35 table: Option<String>,
36 select_columns: Vec<String>,
37 where_conditions: Vec<WhereCondition>,
38 order_by: Vec<OrderClause>,
39 group_by: Vec<String>,
40 having_conditions: Vec<WhereCondition>,
41 limit_value: Option<usize>,
42 offset_value: Option<usize>,
43 joins: Vec<JoinClause>,
44 dialect: Box<dyn Dialect>,
45 soft_delete_disabled: bool,
47 tenant_id_value: Option<i64>,
49 tenant_disabled: bool,
51 keyset_cursor: Option<KeysetCursor>,
57 #[allow(dead_code)]
58 model: std::marker::PhantomData<M>,
59}
60
61#[derive(Debug, Clone)]
69struct KeysetCursor {
70 field: String,
72 value: Value,
74 direction: KeysetDirection,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80enum KeysetDirection {
81 After,
83 Before,
85}
86
87#[derive(Debug, Clone)]
88#[allow(dead_code)]
89enum WhereCondition {
90 And(String),
95 Or(String),
97 Eq(String, Value),
99 Ne(String, Value),
101 Gt(String, Value),
103 Ge(String, Value),
105 Lt(String, Value),
107 Le(String, Value),
109 Like(String, Value),
111 OrEq(String, Value),
113 OrNe(String, Value),
115 OrGt(String, Value),
117 OrGe(String, Value),
119 OrLt(String, Value),
121 OrLe(String, Value),
123 OrLike(String, Value),
125 In(String, Vec<Value>),
126 NotIn(String, Vec<Value>),
127 Between(String, Value, Value),
128 NotBetween(String, Value, Value),
129 Null(String),
130 NotNull(String),
131 Exists(String),
132 NotExists(String),
133}
134
135#[derive(Debug, Clone)]
136struct OrderClause {
137 field: String,
138 direction: OrderDirection,
139}
140
141#[derive(Debug, Clone)]
142enum OrderDirection {
143 Asc,
144 Desc,
145}
146
147#[derive(Debug, Clone)]
148#[allow(dead_code)]
149enum JoinClause {
150 Inner(String, String, String),
151 Left(String, String, String),
152 Right(String, String, String),
153 Cross(String, String),
154}
155
156impl<M: Model> QueryBuilder<M> {
157 pub fn new(dialect: Box<dyn Dialect>) -> Self {
158 Self {
159 table: None,
160 select_columns: vec!["*".to_string()],
161 where_conditions: Vec::new(),
162 order_by: Vec::new(),
163 group_by: Vec::new(),
164 having_conditions: Vec::new(),
165 limit_value: None,
166 offset_value: None,
167 joins: Vec::new(),
168 dialect,
169 soft_delete_disabled: false,
170 tenant_id_value: None,
171 tenant_disabled: false,
172 keyset_cursor: None,
173 model: std::marker::PhantomData,
174 }
175 }
176
177 pub fn table(mut self, table: impl Into<String>) -> Self {
178 self.table = Some(table.into());
179 self
180 }
181
182 pub fn without_soft_delete(mut self) -> Self {
201 self.soft_delete_disabled = true;
202 self
203 }
204
205 pub fn is_soft_delete_disabled(&self) -> bool {
207 self.soft_delete_disabled
208 }
209
210 fn soft_delete_field(&self) -> Option<&'static str> {
214 if self.soft_delete_disabled {
215 return None;
216 }
217 M::soft_delete_field()
218 }
219
220 fn build_soft_delete_condition(&self) -> Option<String> {
224 self.soft_delete_field()
225 .map(|field| format!("{} IS NULL", self.dialect.quote(field)))
226 }
227
228 pub fn with_tenant_id(mut self, tenant_id: i64) -> Self {
255 self.tenant_id_value = Some(tenant_id);
256 self
257 }
258
259 pub fn without_tenant(mut self) -> Self {
263 self.tenant_disabled = true;
264 self
265 }
266
267 pub fn is_tenant_disabled(&self) -> bool {
269 self.tenant_disabled
270 }
271
272 fn tenant_field(&self) -> Option<&'static str> {
276 if self.tenant_disabled {
277 return None;
278 }
279 M::tenant_field()
280 }
281
282 fn tenant_id_value(&self) -> Option<i64> {
284 if self.tenant_disabled {
285 return None;
286 }
287 self.tenant_id_value
288 }
289
290 fn build_tenant_condition(&self) -> Option<(String, Value)> {
295 let field = self.tenant_field()?;
296 let tid = self.tenant_id_value()?;
297 Some((
298 format!("{} = ?", self.dialect.quote(field)),
299 Value::I64(tid),
300 ))
301 }
302
303 pub fn select(mut self, columns: Vec<&str>) -> Self {
311 self.select_columns = columns.into_iter().map(|s| s.to_string()).collect();
312 self
313 }
314
315 pub fn select_quoted(mut self, columns: Vec<&str>) -> Result<Self, sz_orm_model::DbError> {
324 let mut quoted = Vec::with_capacity(columns.len());
325 for col in columns {
326 sz_orm_model::sql_safety::validate_identifier(col, "select column")?;
327 quoted.push(self.dialect.quote(col));
328 }
329 self.select_columns = quoted;
330 Ok(self)
331 }
332
333 #[deprecated(
354 since = "1.3.0",
355 note = "P0-2: 字符串拼接存在 SQL 注入风险,请使用 where_eq/where_ne/where_gt/where_lt/where_like 等参数化方法"
356 )]
357 pub fn where_cond(mut self, condition: impl Into<String>) -> Self {
358 self.where_conditions
359 .push(WhereCondition::And(condition.into()));
360 self
361 }
362
363 #[deprecated(
367 since = "1.3.0",
368 note = "P0-2: 字符串拼接存在 SQL 注入风险,请使用参数化方法"
369 )]
370 pub fn or_where(mut self, condition: impl Into<String>) -> Self {
371 self.where_conditions
372 .push(WhereCondition::Or(condition.into()));
373 self
374 }
375
376 pub fn where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
390 self.where_conditions
391 .push(WhereCondition::Eq(field.into(), value));
392 self
393 }
394
395 pub fn where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
397 self.where_conditions
398 .push(WhereCondition::Ne(field.into(), value));
399 self
400 }
401
402 pub fn where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
404 self.where_conditions
405 .push(WhereCondition::Gt(field.into(), value));
406 self
407 }
408
409 pub fn where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
411 self.where_conditions
412 .push(WhereCondition::Ge(field.into(), value));
413 self
414 }
415
416 pub fn where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
418 self.where_conditions
419 .push(WhereCondition::Lt(field.into(), value));
420 self
421 }
422
423 pub fn where_le(mut self, field: impl Into<String>, value: Value) -> Self {
425 self.where_conditions
426 .push(WhereCondition::Le(field.into(), value));
427 self
428 }
429
430 pub fn where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
442 self.where_conditions
443 .push(WhereCondition::Like(field.into(), pattern));
444 self
445 }
446
447 pub fn or_where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
451 self.where_conditions
452 .push(WhereCondition::OrEq(field.into(), value));
453 self
454 }
455
456 pub fn or_where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
458 self.where_conditions
459 .push(WhereCondition::OrNe(field.into(), value));
460 self
461 }
462
463 pub fn or_where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
465 self.where_conditions
466 .push(WhereCondition::OrGt(field.into(), value));
467 self
468 }
469
470 pub fn or_where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
472 self.where_conditions
473 .push(WhereCondition::OrGe(field.into(), value));
474 self
475 }
476
477 pub fn or_where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
479 self.where_conditions
480 .push(WhereCondition::OrLt(field.into(), value));
481 self
482 }
483
484 pub fn or_where_le(mut self, field: impl Into<String>, value: Value) -> Self {
486 self.where_conditions
487 .push(WhereCondition::OrLe(field.into(), value));
488 self
489 }
490
491 pub fn or_where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
493 self.where_conditions
494 .push(WhereCondition::OrLike(field.into(), pattern));
495 self
496 }
497
498 pub fn where_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
499 self.where_conditions
500 .push(WhereCondition::In(field.into(), values));
501 self
502 }
503
504 pub fn where_not_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
505 self.where_conditions
506 .push(WhereCondition::NotIn(field.into(), values));
507 self
508 }
509
510 pub fn where_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
511 self.where_conditions
512 .push(WhereCondition::Between(field.into(), start, end));
513 self
514 }
515
516 pub fn where_not_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
517 self.where_conditions
518 .push(WhereCondition::NotBetween(field.into(), start, end));
519 self
520 }
521
522 pub fn where_null(mut self, field: impl Into<String>) -> Self {
523 self.where_conditions
524 .push(WhereCondition::Null(field.into()));
525 self
526 }
527
528 pub fn where_not_null(mut self, field: impl Into<String>) -> Self {
529 self.where_conditions
530 .push(WhereCondition::NotNull(field.into()));
531 self
532 }
533
534 pub fn order_by(mut self, field: impl Into<String>) -> Self {
535 self.order_by.push(OrderClause {
536 field: field.into(),
537 direction: OrderDirection::Asc,
538 });
539 self
540 }
541
542 pub fn order_desc(mut self, field: impl Into<String>) -> Self {
543 self.order_by.push(OrderClause {
544 field: field.into(),
545 direction: OrderDirection::Desc,
546 });
547 self
548 }
549
550 pub fn group_by(mut self, field: impl Into<String>) -> Self {
551 self.group_by.push(field.into());
552 self
553 }
554
555 pub fn having(mut self, condition: impl Into<String>) -> Self {
556 self.having_conditions
557 .push(WhereCondition::And(condition.into()));
558 self
559 }
560
561 pub fn limit(mut self, limit: usize) -> Self {
562 self.limit_value = Some(limit);
563 self
564 }
565
566 pub fn offset(mut self, offset: usize) -> Self {
567 self.offset_value = Some(offset);
568 self
569 }
570
571 pub fn page(mut self, page: usize, page_size: usize) -> Self {
572 self.limit_value = Some(page_size);
573 self.offset_value = Some((page.saturating_sub(1)) * page_size);
574 self
575 }
576
577 pub fn keyset_after(
624 mut self,
625 field: impl Into<String>,
626 cursor_value: Value,
627 page_size: usize,
628 ) -> Self {
629 let field_str = field.into();
630 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
632 existing.direction = OrderDirection::Asc;
633 } else {
634 self.order_by.push(OrderClause {
635 field: field_str.clone(),
636 direction: OrderDirection::Asc,
637 });
638 }
639 self.limit_value = Some(page_size);
640 self.offset_value = None;
642 self.keyset_cursor = Some(KeysetCursor {
643 field: field_str,
644 value: cursor_value,
645 direction: KeysetDirection::After,
646 });
647 self
648 }
649
650 pub fn keyset_before(
697 mut self,
698 field: impl Into<String>,
699 cursor_value: Value,
700 page_size: usize,
701 ) -> Self {
702 let field_str = field.into();
703 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
705 existing.direction = OrderDirection::Desc;
706 } else {
707 self.order_by.push(OrderClause {
708 field: field_str.clone(),
709 direction: OrderDirection::Desc,
710 });
711 }
712 self.limit_value = Some(page_size);
713 self.offset_value = None;
714 self.keyset_cursor = Some(KeysetCursor {
715 field: field_str,
716 value: cursor_value,
717 direction: KeysetDirection::Before,
718 });
719 self
720 }
721
722 pub fn join_inner(
723 mut self,
724 table: impl Into<String>,
725 on_left: impl Into<String>,
726 on_right: impl Into<String>,
727 ) -> Self {
728 self.joins.push(JoinClause::Inner(
729 table.into(),
730 on_left.into(),
731 on_right.into(),
732 ));
733 self
734 }
735
736 pub fn join_left(
737 mut self,
738 table: impl Into<String>,
739 on_left: impl Into<String>,
740 on_right: impl Into<String>,
741 ) -> Self {
742 self.joins.push(JoinClause::Left(
743 table.into(),
744 on_left.into(),
745 on_right.into(),
746 ));
747 self
748 }
749
750 pub fn join_right(
751 mut self,
752 table: impl Into<String>,
753 on_left: impl Into<String>,
754 on_right: impl Into<String>,
755 ) -> Self {
756 self.joins.push(JoinClause::Right(
757 table.into(),
758 on_left.into(),
759 on_right.into(),
760 ));
761 self
762 }
763
764 #[tracing::instrument(skip(self), fields(op = "select"))]
797 pub fn build_select(&self) -> String {
798 let table = self
799 .table
800 .clone()
801 .unwrap_or_else(|| M::table_name().to_string());
802
803 let columns = if self.select_columns.is_empty() {
804 "*".to_string()
805 } else {
806 self.select_columns.join(", ")
807 };
808
809 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
810
811 for join in &self.joins {
812 match join {
813 JoinClause::Inner(t, l, r) => {
814 sql.push_str(&format!(
815 " INNER JOIN {} ON {} = {}",
816 self.dialect.quote(t),
817 self.dialect.quote(l),
818 self.dialect.quote(r)
819 ));
820 }
821 JoinClause::Left(t, l, r) => {
822 sql.push_str(&format!(
823 " LEFT JOIN {} ON {} = {}",
824 self.dialect.quote(t),
825 self.dialect.quote(l),
826 self.dialect.quote(r)
827 ));
828 }
829 JoinClause::Right(t, l, r) => {
830 sql.push_str(&format!(
831 " RIGHT JOIN {} ON {} = {}",
832 self.dialect.quote(t),
833 self.dialect.quote(l),
834 self.dialect.quote(r)
835 ));
836 }
837 JoinClause::Cross(t, on) => {
838 sql.push_str(&format!(
839 " CROSS JOIN {} ON {}",
840 self.dialect.quote(t),
841 self.dialect.quote(on)
842 ));
843 }
844 }
845 }
846
847 let where_clause = self.build_where_clause();
849 if !where_clause.is_empty() {
850 sql.push_str(&where_clause);
851 }
852
853 if !self.group_by.is_empty() {
854 let cols: Vec<String> = self
855 .group_by
856 .iter()
857 .map(|c| self.dialect.quote(c))
858 .collect();
859 sql.push_str(" GROUP BY ");
860 sql.push_str(&cols.join(", "));
861 }
862
863 if !self.having_conditions.is_empty() {
864 sql.push_str(" HAVING ");
865 for (i, cond) in self.having_conditions.iter().enumerate() {
866 if i > 0 {
867 sql.push_str(" AND ");
868 }
869 if let WhereCondition::And(c) = cond {
870 sql.push_str(c);
871 }
872 }
873 }
874
875 if !self.order_by.is_empty() {
876 let order_cols: Vec<String> = self
877 .order_by
878 .iter()
879 .map(|o| {
880 let dir = match o.direction {
881 OrderDirection::Asc => " ASC",
882 OrderDirection::Desc => " DESC",
883 };
884 format!("{}{}", self.dialect.quote(&o.field), dir)
885 })
886 .collect();
887 sql.push_str(" ORDER BY ");
888 sql.push_str(&order_cols.join(", "));
889 }
890
891 if let Some(limit) = self.limit_value {
892 sql.push_str(&format!(" LIMIT {}", limit));
893 }
894
895 if let Some(offset) = self.offset_value {
896 sql.push_str(&format!(" OFFSET {}", offset));
897 }
898
899 sql
900 }
901
902 fn build_where_clause(&self) -> String {
908 self.build_where_clause_with_options(true)
909 }
910
911 fn build_where_clause_with_options(&self, include_soft_delete: bool) -> String {
919 let soft_delete_cond = if include_soft_delete {
921 self.build_soft_delete_condition()
922 } else {
923 None
924 };
925
926 let tenant_cond = self.build_tenant_condition().map(|(sql, value)| {
928 sql.replacen('?', &value.to_param_with_dialect(&*self.dialect), 1)
930 });
931
932 if self.where_conditions.is_empty()
934 && soft_delete_cond.is_none()
935 && tenant_cond.is_none()
936 && self.keyset_cursor.is_none()
937 {
938 return String::new();
939 }
940
941 let mut conditions: Vec<String> = self
943 .where_conditions
944 .iter()
945 .map(|cond| match cond {
946 WhereCondition::And(c) => c.clone(),
947 WhereCondition::Or(c) => format!("OR {}", c),
948 WhereCondition::Eq(f, v) => format!(
950 "{} = {}",
951 self.dialect.quote(f),
952 v.to_param_with_dialect(&*self.dialect)
953 ),
954 WhereCondition::Ne(f, v) => format!(
955 "{} != {}",
956 self.dialect.quote(f),
957 v.to_param_with_dialect(&*self.dialect)
958 ),
959 WhereCondition::Gt(f, v) => format!(
960 "{} > {}",
961 self.dialect.quote(f),
962 v.to_param_with_dialect(&*self.dialect)
963 ),
964 WhereCondition::Ge(f, v) => format!(
965 "{} >= {}",
966 self.dialect.quote(f),
967 v.to_param_with_dialect(&*self.dialect)
968 ),
969 WhereCondition::Lt(f, v) => format!(
970 "{} < {}",
971 self.dialect.quote(f),
972 v.to_param_with_dialect(&*self.dialect)
973 ),
974 WhereCondition::Le(f, v) => format!(
975 "{} <= {}",
976 self.dialect.quote(f),
977 v.to_param_with_dialect(&*self.dialect)
978 ),
979 WhereCondition::Like(f, v) => format!(
980 "{} LIKE {}",
981 self.dialect.quote(f),
982 v.to_param_with_dialect(&*self.dialect)
983 ),
984 WhereCondition::OrEq(f, v) => format!(
985 "OR {} = {}",
986 self.dialect.quote(f),
987 v.to_param_with_dialect(&*self.dialect)
988 ),
989 WhereCondition::OrNe(f, v) => format!(
990 "OR {} != {}",
991 self.dialect.quote(f),
992 v.to_param_with_dialect(&*self.dialect)
993 ),
994 WhereCondition::OrGt(f, v) => format!(
995 "OR {} > {}",
996 self.dialect.quote(f),
997 v.to_param_with_dialect(&*self.dialect)
998 ),
999 WhereCondition::OrGe(f, v) => format!(
1000 "OR {} >= {}",
1001 self.dialect.quote(f),
1002 v.to_param_with_dialect(&*self.dialect)
1003 ),
1004 WhereCondition::OrLt(f, v) => format!(
1005 "OR {} < {}",
1006 self.dialect.quote(f),
1007 v.to_param_with_dialect(&*self.dialect)
1008 ),
1009 WhereCondition::OrLe(f, v) => format!(
1010 "OR {} <= {}",
1011 self.dialect.quote(f),
1012 v.to_param_with_dialect(&*self.dialect)
1013 ),
1014 WhereCondition::OrLike(f, v) => format!(
1015 "OR {} LIKE {}",
1016 self.dialect.quote(f),
1017 v.to_param_with_dialect(&*self.dialect)
1018 ),
1019 WhereCondition::In(f, vals) => {
1020 let vals_str: Vec<String> = vals
1022 .iter()
1023 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1024 .collect();
1025 format!("{} IN ({})", self.dialect.quote(f), vals_str.join(", "))
1026 }
1027 WhereCondition::NotIn(f, vals) => {
1028 let vals_str: Vec<String> = vals
1029 .iter()
1030 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1031 .collect();
1032 format!("{} NOT IN ({})", self.dialect.quote(f), vals_str.join(", "))
1033 }
1034 WhereCondition::Between(f, start, end) => {
1035 format!(
1036 "{} BETWEEN {} AND {}",
1037 self.dialect.quote(f),
1038 start.to_param_with_dialect(&*self.dialect),
1039 end.to_param_with_dialect(&*self.dialect)
1040 )
1041 }
1042 WhereCondition::NotBetween(f, start, end) => {
1043 format!(
1044 "{} NOT BETWEEN {} AND {}",
1045 self.dialect.quote(f),
1046 start.to_param_with_dialect(&*self.dialect),
1047 end.to_param_with_dialect(&*self.dialect)
1048 )
1049 }
1050 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1051 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1052 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1053 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1054 })
1055 .collect();
1056
1057 if let Some(sd_cond) = soft_delete_cond {
1059 conditions.push(sd_cond);
1060 }
1061
1062 if let Some(t_cond) = tenant_cond {
1064 conditions.push(t_cond);
1065 }
1066
1067 if let Some(ref cursor) = self.keyset_cursor {
1069 let op = match cursor.direction {
1070 KeysetDirection::After => ">",
1071 KeysetDirection::Before => "<",
1072 };
1073 conditions.push(format!(
1074 "{} {} {}",
1075 self.dialect.quote(&cursor.field),
1076 op,
1077 cursor.value.to_param_with_dialect(&*self.dialect)
1078 ));
1079 }
1080
1081 if conditions.is_empty() {
1082 return String::new();
1083 }
1084
1085 let mut groups: Vec<Vec<String>> = Vec::new();
1088 let mut current_group: Vec<String> = Vec::new();
1089 for cond in conditions.iter() {
1090 if let Some(stripped) = cond.strip_prefix("OR ") {
1091 current_group.push(stripped.to_string());
1093 } else {
1094 if !current_group.is_empty() {
1096 groups.push(std::mem::take(&mut current_group));
1097 }
1098 current_group.push(cond.clone());
1099 }
1100 }
1101 if !current_group.is_empty() {
1102 groups.push(current_group);
1103 }
1104
1105 let group_strs: Vec<String> = groups
1106 .iter()
1107 .map(|g| {
1108 if g.len() == 1 {
1109 g[0].clone()
1110 } else {
1111 format!("({})", g.join(" OR "))
1112 }
1113 })
1114 .collect();
1115
1116 format!(" WHERE {}", group_strs.join(" AND "))
1117 }
1118
1119 #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1120 pub fn build_insert(&self, data: &std::collections::HashMap<String, Value>) -> String {
1121 let table = self
1122 .table
1123 .clone()
1124 .unwrap_or_else(|| M::table_name().to_string());
1125
1126 if data.is_empty() {
1127 return String::new();
1128 }
1129
1130 let columns: Vec<String> = data.keys().map(|k| self.dialect.quote(k)).collect();
1131 let values: Vec<String> = data
1133 .values()
1134 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1135 .collect();
1136
1137 format!(
1138 "INSERT INTO {} ({}) VALUES ({})",
1139 self.dialect.quote(&table),
1140 columns.join(", "),
1141 values.join(", ")
1142 )
1143 }
1144
1145 #[tracing::instrument(skip(self, data), fields(op = "update"))]
1146 pub fn build_update(&self, data: &std::collections::HashMap<String, Value>) -> String {
1147 let table = self
1148 .table
1149 .clone()
1150 .unwrap_or_else(|| M::table_name().to_string());
1151
1152 if data.is_empty() {
1153 return String::new();
1154 }
1155
1156 let set_clauses: Vec<String> = data
1157 .iter()
1158 .map(|(k, v)| {
1159 format!(
1160 "{} = {}",
1161 self.dialect.quote(k),
1162 v.to_param_with_dialect(&*self.dialect)
1163 )
1164 })
1165 .collect();
1166
1167 let mut sql = format!(
1168 "UPDATE {} SET {}",
1169 self.dialect.quote(&table),
1170 set_clauses.join(", ")
1171 );
1172
1173 sql.push_str(&self.build_where_clause());
1174 sql
1175 }
1176
1177 #[tracing::instrument(skip(self), fields(op = "delete"))]
1188 pub fn build_delete(&self) -> String {
1189 let table = self
1190 .table
1191 .clone()
1192 .unwrap_or_else(|| M::table_name().to_string());
1193
1194 if let Some(field) = self.soft_delete_field() {
1196 let where_clause = self.build_where_clause();
1197 return format!(
1198 "UPDATE {} SET {} = NOW(){}",
1199 self.dialect.quote(&table),
1200 self.dialect.quote(field),
1201 where_clause
1202 );
1203 }
1204
1205 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1206 sql.push_str(&self.build_where_clause());
1207 sql
1208 }
1209
1210 pub fn build_force_delete(&self) -> String {
1220 let table = self
1221 .table
1222 .clone()
1223 .unwrap_or_else(|| M::table_name().to_string());
1224
1225 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1226 sql.push_str(&self.build_where_clause_with_options(false));
1228 sql
1229 }
1230
1231 fn build_where_clause_with_params(&self) -> (String, Vec<Value>) {
1241 self.build_where_clause_with_params_options(true)
1243 }
1244
1245 fn build_where_clause_with_params_options(
1254 &self,
1255 include_soft_delete: bool,
1256 ) -> (String, Vec<Value>) {
1257 let soft_delete_cond = if include_soft_delete {
1259 self.build_soft_delete_condition()
1260 } else {
1261 None
1262 };
1263
1264 let tenant_cond = self.build_tenant_condition();
1266
1267 if self.where_conditions.is_empty()
1269 && soft_delete_cond.is_none()
1270 && tenant_cond.is_none()
1271 && self.keyset_cursor.is_none()
1272 {
1273 return (String::new(), Vec::new());
1274 }
1275
1276 let mut params = Vec::new();
1277
1278 let mut conditions: Vec<String> = self
1279 .where_conditions
1280 .iter()
1281 .map(|cond| match cond {
1282 WhereCondition::And(c) => c.clone(),
1283 WhereCondition::Or(c) => format!("OR {}", c),
1284 WhereCondition::Eq(f, v) => {
1286 params.push(v.clone());
1287 format!("{} = ?", self.dialect.quote(f))
1288 }
1289 WhereCondition::Ne(f, v) => {
1290 params.push(v.clone());
1291 format!("{} != ?", self.dialect.quote(f))
1292 }
1293 WhereCondition::Gt(f, v) => {
1294 params.push(v.clone());
1295 format!("{} > ?", self.dialect.quote(f))
1296 }
1297 WhereCondition::Ge(f, v) => {
1298 params.push(v.clone());
1299 format!("{} >= ?", self.dialect.quote(f))
1300 }
1301 WhereCondition::Lt(f, v) => {
1302 params.push(v.clone());
1303 format!("{} < ?", self.dialect.quote(f))
1304 }
1305 WhereCondition::Le(f, v) => {
1306 params.push(v.clone());
1307 format!("{} <= ?", self.dialect.quote(f))
1308 }
1309 WhereCondition::Like(f, v) => {
1310 params.push(v.clone());
1311 format!("{} LIKE ?", self.dialect.quote(f))
1312 }
1313 WhereCondition::OrEq(f, v) => {
1314 params.push(v.clone());
1315 format!("OR {} = ?", self.dialect.quote(f))
1316 }
1317 WhereCondition::OrNe(f, v) => {
1318 params.push(v.clone());
1319 format!("OR {} != ?", self.dialect.quote(f))
1320 }
1321 WhereCondition::OrGt(f, v) => {
1322 params.push(v.clone());
1323 format!("OR {} > ?", self.dialect.quote(f))
1324 }
1325 WhereCondition::OrGe(f, v) => {
1326 params.push(v.clone());
1327 format!("OR {} >= ?", self.dialect.quote(f))
1328 }
1329 WhereCondition::OrLt(f, v) => {
1330 params.push(v.clone());
1331 format!("OR {} < ?", self.dialect.quote(f))
1332 }
1333 WhereCondition::OrLe(f, v) => {
1334 params.push(v.clone());
1335 format!("OR {} <= ?", self.dialect.quote(f))
1336 }
1337 WhereCondition::OrLike(f, v) => {
1338 params.push(v.clone());
1339 format!("OR {} LIKE ?", self.dialect.quote(f))
1340 }
1341 WhereCondition::In(f, vals) => {
1342 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1343 params.extend(vals.iter().cloned());
1344 format!("{} IN ({})", self.dialect.quote(f), placeholders.join(", "))
1345 }
1346 WhereCondition::NotIn(f, vals) => {
1347 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1348 params.extend(vals.iter().cloned());
1349 format!(
1350 "{} NOT IN ({})",
1351 self.dialect.quote(f),
1352 placeholders.join(", ")
1353 )
1354 }
1355 WhereCondition::Between(f, start, end) => {
1356 params.push(start.clone());
1357 params.push(end.clone());
1358 format!("{} BETWEEN ? AND ?", self.dialect.quote(f))
1359 }
1360 WhereCondition::NotBetween(f, start, end) => {
1361 params.push(start.clone());
1362 params.push(end.clone());
1363 format!("{} NOT BETWEEN ? AND ?", self.dialect.quote(f))
1364 }
1365 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1366 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1367 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1368 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1369 })
1370 .collect();
1371
1372 if let Some(sd_cond) = soft_delete_cond {
1374 conditions.push(sd_cond);
1375 }
1376
1377 if let Some((t_sql, t_value)) = tenant_cond {
1379 conditions.push(t_sql);
1380 params.push(t_value);
1381 }
1382
1383 if let Some(ref cursor) = self.keyset_cursor {
1385 let op = match cursor.direction {
1386 KeysetDirection::After => ">",
1387 KeysetDirection::Before => "<",
1388 };
1389 conditions.push(format!("{} {} ?", self.dialect.quote(&cursor.field), op));
1390 params.push(cursor.value.clone());
1391 }
1392
1393 if conditions.is_empty() {
1394 return (String::new(), params);
1395 }
1396
1397 let mut groups: Vec<Vec<String>> = Vec::new();
1399 let mut current_group: Vec<String> = Vec::new();
1400 for cond in conditions.iter() {
1401 if let Some(stripped) = cond.strip_prefix("OR ") {
1402 current_group.push(stripped.to_string());
1403 } else {
1404 if !current_group.is_empty() {
1405 groups.push(std::mem::take(&mut current_group));
1406 }
1407 current_group.push(cond.clone());
1408 }
1409 }
1410 if !current_group.is_empty() {
1411 groups.push(current_group);
1412 }
1413
1414 let group_strs: Vec<String> = groups
1415 .iter()
1416 .map(|g| {
1417 if g.len() == 1 {
1418 g[0].clone()
1419 } else {
1420 format!("({})", g.join(" OR "))
1421 }
1422 })
1423 .collect();
1424
1425 (format!(" WHERE {}", group_strs.join(" AND ")), params)
1426 }
1427
1428 pub fn build_select_with_params(&self) -> (String, Vec<Value>) {
1433 let table = self
1434 .table
1435 .clone()
1436 .unwrap_or_else(|| M::table_name().to_string());
1437 let columns = if self.select_columns.is_empty() {
1438 "*".to_string()
1439 } else {
1440 self.select_columns.join(", ")
1441 };
1442
1443 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1444
1445 for join in &self.joins {
1446 match join {
1447 JoinClause::Inner(t, l, r) => {
1448 sql.push_str(&format!(
1449 " INNER JOIN {} ON {} = {}",
1450 self.dialect.quote(t),
1451 self.dialect.quote(l),
1452 self.dialect.quote(r)
1453 ));
1454 }
1455 JoinClause::Left(t, l, r) => {
1456 sql.push_str(&format!(
1457 " LEFT JOIN {} ON {} = {}",
1458 self.dialect.quote(t),
1459 self.dialect.quote(l),
1460 self.dialect.quote(r)
1461 ));
1462 }
1463 JoinClause::Right(t, l, r) => {
1464 sql.push_str(&format!(
1465 " RIGHT JOIN {} ON {} = {}",
1466 self.dialect.quote(t),
1467 self.dialect.quote(l),
1468 self.dialect.quote(r)
1469 ));
1470 }
1471 JoinClause::Cross(t, on) => {
1472 sql.push_str(&format!(
1473 " CROSS JOIN {} ON {}",
1474 self.dialect.quote(t),
1475 self.dialect.quote(on)
1476 ));
1477 }
1478 }
1479 }
1480
1481 let mut params = Vec::new();
1482 let (where_clause, where_params) = self.build_where_clause_with_params();
1484 if !where_clause.is_empty() {
1485 sql.push_str(&where_clause);
1486 params = where_params;
1487 }
1488
1489 if !self.group_by.is_empty() {
1490 let cols: Vec<String> = self
1491 .group_by
1492 .iter()
1493 .map(|c| self.dialect.quote(c))
1494 .collect();
1495 sql.push_str(" GROUP BY ");
1496 sql.push_str(&cols.join(", "));
1497 }
1498
1499 if !self.having_conditions.is_empty() {
1500 sql.push_str(" HAVING ");
1501 for (i, cond) in self.having_conditions.iter().enumerate() {
1502 if i > 0 {
1503 sql.push_str(" AND ");
1504 }
1505 if let WhereCondition::And(c) = cond {
1506 sql.push_str(c);
1507 }
1508 }
1509 }
1510
1511 if !self.order_by.is_empty() {
1512 let order_cols: Vec<String> = self
1513 .order_by
1514 .iter()
1515 .map(|o| {
1516 let dir = match o.direction {
1517 OrderDirection::Asc => " ASC",
1518 OrderDirection::Desc => " DESC",
1519 };
1520 format!("{}{}", self.dialect.quote(&o.field), dir)
1521 })
1522 .collect();
1523 sql.push_str(" ORDER BY ");
1524 sql.push_str(&order_cols.join(", "));
1525 }
1526
1527 if let Some(limit) = self.limit_value {
1528 sql.push_str(&format!(" LIMIT {}", limit));
1529 }
1530 if let Some(offset) = self.offset_value {
1531 sql.push_str(&format!(" OFFSET {}", offset));
1532 }
1533
1534 (sql, params)
1535 }
1536
1537 pub fn build_insert_with_params(
1539 &self,
1540 data: &std::collections::HashMap<String, Value>,
1541 ) -> (String, Vec<Value>) {
1542 let table = self
1543 .table
1544 .clone()
1545 .unwrap_or_else(|| M::table_name().to_string());
1546 if data.is_empty() {
1547 return (String::new(), Vec::new());
1548 }
1549
1550 let mut columns = Vec::with_capacity(data.len());
1551 let mut params = Vec::with_capacity(data.len());
1552 let placeholders: Vec<&str> = data.iter().map(|_| "?").collect();
1553 for (k, v) in data.iter() {
1554 columns.push(self.dialect.quote(k));
1555 params.push(v.clone());
1556 }
1557
1558 let sql = format!(
1559 "INSERT INTO {} ({}) VALUES ({})",
1560 self.dialect.quote(&table),
1561 columns.join(", "),
1562 placeholders.join(", ")
1563 );
1564 (sql, params)
1565 }
1566
1567 pub fn build_batch_insert_with_params(
1574 &self,
1575 rows: &[std::collections::HashMap<String, Value>],
1576 ) -> (String, Vec<Value>) {
1577 let table = self
1578 .table
1579 .clone()
1580 .unwrap_or_else(|| M::table_name().to_string());
1581 if rows.is_empty() {
1582 return (String::new(), Vec::new());
1583 }
1584
1585 let first_row = &rows[0];
1587 let columns: Vec<String> = first_row.keys().cloned().collect();
1588 let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1589
1590 let mut params = Vec::with_capacity(rows.len() * columns.len());
1591 let mut value_groups: Vec<String> = Vec::with_capacity(rows.len());
1592 for row in rows {
1593 let placeholders: Vec<String> = columns
1594 .iter()
1595 .map(|col| match row.get(col) {
1596 Some(v) => {
1597 params.push(v.clone());
1598 "?".to_string()
1599 }
1600 None => "NULL".to_string(),
1601 })
1602 .collect();
1603 value_groups.push(format!("({})", placeholders.join(", ")));
1604 }
1605
1606 let sql = format!(
1607 "INSERT INTO {} ({}) VALUES {}",
1608 self.dialect.quote(&table),
1609 quoted_columns.join(", "),
1610 value_groups.join(", ")
1611 );
1612 (sql, params)
1613 }
1614
1615 pub fn build_batch_insert_chunked(
1632 &self,
1633 rows: &[std::collections::HashMap<String, Value>],
1634 ) -> Vec<(String, Vec<Value>)> {
1635 if rows.is_empty() {
1636 return Vec::new();
1637 }
1638 let table = self
1639 .table
1640 .clone()
1641 .unwrap_or_else(|| M::table_name().to_string());
1642 let first_row = &rows[0];
1643 let columns: Vec<String> = first_row.keys().cloned().collect();
1644 let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1645
1646 rows.chunks(DEFAULT_BATCH_SIZE)
1647 .map(|chunk| {
1648 let mut params = Vec::with_capacity(chunk.len() * columns.len());
1649 let mut value_groups: Vec<String> = Vec::with_capacity(chunk.len());
1650 for row in chunk {
1651 let placeholders: Vec<String> = columns
1652 .iter()
1653 .map(|col| match row.get(col) {
1654 Some(v) => {
1655 params.push(v.clone());
1656 "?".to_string()
1657 }
1658 None => "NULL".to_string(),
1659 })
1660 .collect();
1661 value_groups.push(format!("({})", placeholders.join(", ")));
1662 }
1663 let sql = format!(
1664 "INSERT INTO {} ({}) VALUES {}",
1665 self.dialect.quote(&table),
1666 quoted_columns.join(", "),
1667 value_groups.join(", ")
1668 );
1669 (sql, params)
1670 })
1671 .collect()
1672 }
1673
1674 pub fn build_batch_upsert_with_params(
1695 &self,
1696 rows: &[std::collections::HashMap<String, Value>],
1697 conflict_columns: &[&str],
1698 update_columns: &[&str],
1699 ) -> Result<(String, Vec<Value>), sz_orm_model::DbError> {
1700 if rows.is_empty() {
1701 return Err(sz_orm_model::DbError::InvalidInput(
1702 "build_batch_upsert_with_params: rows cannot be empty".to_string(),
1703 ));
1704 }
1705
1706 let (insert_sql, params) = self.build_batch_insert_with_params(rows);
1708 if insert_sql.is_empty() {
1709 return Err(sz_orm_model::DbError::InvalidInput(
1710 "build_batch_upsert_with_params: failed to build INSERT part".to_string(),
1711 ));
1712 }
1713
1714 let all_columns: Vec<String> = rows[0].keys().cloned().collect();
1716
1717 let conflict_clause = self
1719 .dialect
1720 .build_upsert_on_conflict(conflict_columns, update_columns, &all_columns)
1721 .ok_or_else(|| {
1722 sz_orm_model::DbError::InvalidInput(format!(
1723 "build_batch_upsert_with_params: dialect {:?} does not support upsert (ON CONFLICT / ON DUPLICATE KEY UPDATE). Consider using MERGE statement or individual upserts instead.",
1724 self.dialect.db_type()
1725 ))
1726 })?;
1727
1728 let sql = format!("{} {}", insert_sql, conflict_clause);
1729 Ok((sql, params))
1730 }
1731
1732 pub fn build_update_with_params(
1735 &self,
1736 data: &std::collections::HashMap<String, Value>,
1737 ) -> (String, Vec<Value>) {
1738 let table = self
1739 .table
1740 .clone()
1741 .unwrap_or_else(|| M::table_name().to_string());
1742 if data.is_empty() {
1743 return (String::new(), Vec::new());
1744 }
1745
1746 let mut set_clauses = Vec::with_capacity(data.len());
1747 let mut params = Vec::with_capacity(data.len());
1748 for (k, v) in data.iter() {
1749 set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
1750 params.push(v.clone());
1751 }
1752
1753 let mut sql = format!(
1754 "UPDATE {} SET {}",
1755 self.dialect.quote(&table),
1756 set_clauses.join(", ")
1757 );
1758
1759 let (where_clause, where_params) = self.build_where_clause_with_params();
1761 if !where_clause.is_empty() {
1762 sql.push_str(&where_clause);
1763 params.extend(where_params);
1764 }
1765
1766 (sql, params)
1767 }
1768
1769 pub fn batch_update_with_params(
1793 &self,
1794 data: &std::collections::HashMap<String, Value>,
1795 ids: &[Value],
1796 ) -> (String, Vec<Value>) {
1797 let table = self
1798 .table
1799 .clone()
1800 .unwrap_or_else(|| M::table_name().to_string());
1801 if data.is_empty() || ids.is_empty() {
1802 return (String::new(), Vec::new());
1803 }
1804
1805 let mut set_clauses = Vec::with_capacity(data.len());
1806 let mut params = Vec::with_capacity(data.len() + ids.len());
1807 for (k, v) in data.iter() {
1808 set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
1809 params.push(v.clone());
1810 }
1811
1812 let pk = M::pk_name();
1813 let placeholders: Vec<String> = ids.iter().map(|_| "?".to_string()).collect();
1814 params.extend_from_slice(ids);
1815
1816 let sql = format!(
1817 "UPDATE {} SET {} WHERE {} IN ({})",
1818 self.dialect.quote(&table),
1819 set_clauses.join(", "),
1820 self.dialect.quote(pk),
1821 placeholders.join(", ")
1822 );
1823 (sql, params)
1824 }
1825
1826 pub fn build_delete_with_params(&self) -> (String, Vec<Value>) {
1832 let table = self
1833 .table
1834 .clone()
1835 .unwrap_or_else(|| M::table_name().to_string());
1836
1837 if let Some(field) = self.soft_delete_field() {
1839 let (where_clause, where_params) = self.build_where_clause_with_params();
1840 let sql = format!(
1841 "UPDATE {} SET {} = NOW(){}",
1842 self.dialect.quote(&table),
1843 self.dialect.quote(field),
1844 where_clause
1845 );
1846 return (sql, where_params);
1847 }
1848
1849 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1850 let mut params = Vec::new();
1851
1852 let (where_clause, where_params) = self.build_where_clause_with_params();
1853 if !where_clause.is_empty() {
1854 sql.push_str(&where_clause);
1855 params = where_params;
1856 }
1857
1858 (sql, params)
1859 }
1860
1861 pub fn build_force_delete_with_params(&self) -> (String, Vec<Value>) {
1865 let table = self
1866 .table
1867 .clone()
1868 .unwrap_or_else(|| M::table_name().to_string());
1869
1870 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1871 let mut params = Vec::new();
1872
1873 let (where_clause, where_params) = self.build_where_clause_with_params_options(false);
1875 if !where_clause.is_empty() {
1876 sql.push_str(&where_clause);
1877 params = where_params;
1878 }
1879
1880 (sql, params)
1881 }
1882
1883 pub fn build_count(&self) -> String {
1884 let table = self
1885 .table
1886 .clone()
1887 .unwrap_or_else(|| M::table_name().to_string());
1888
1889 let mut sql = format!(
1890 "SELECT COUNT(*) as total FROM {}",
1891 self.dialect.quote(&table)
1892 );
1893 sql.push_str(&self.build_where_clause());
1894 sql
1895 }
1896
1897 pub fn build_exists(&self) -> String {
1898 let table = self
1899 .table
1900 .clone()
1901 .unwrap_or_else(|| M::table_name().to_string());
1902
1903 let mut sql = format!("SELECT 1 FROM {}", self.dialect.quote(&table));
1904 sql.push_str(&self.build_where_clause());
1905 sql.push_str(" LIMIT 1");
1906 format!("SELECT EXISTS({})", sql)
1907 }
1908
1909 pub fn build_max(&self, field: &str) -> String {
1910 let table = self
1911 .table
1912 .clone()
1913 .unwrap_or_else(|| M::table_name().to_string());
1914
1915 let mut sql = format!(
1916 "SELECT MAX({}) as max_val FROM {}",
1917 self.dialect.quote(field),
1918 self.dialect.quote(&table)
1919 );
1920 sql.push_str(&self.build_where_clause());
1921 sql
1922 }
1923
1924 pub fn build_min(&self, field: &str) -> String {
1925 let table = self
1926 .table
1927 .clone()
1928 .unwrap_or_else(|| M::table_name().to_string());
1929
1930 let mut sql = format!(
1931 "SELECT MIN({}) as min_val FROM {}",
1932 self.dialect.quote(field),
1933 self.dialect.quote(&table)
1934 );
1935 sql.push_str(&self.build_where_clause());
1936 sql
1937 }
1938
1939 pub fn build_sum(&self, field: &str) -> String {
1940 let table = self
1941 .table
1942 .clone()
1943 .unwrap_or_else(|| M::table_name().to_string());
1944
1945 let mut sql = format!(
1946 "SELECT SUM({}) as sum_val FROM {}",
1947 self.dialect.quote(field),
1948 self.dialect.quote(&table)
1949 );
1950 sql.push_str(&self.build_where_clause());
1951 sql
1952 }
1953
1954 pub fn build_avg(&self, field: &str) -> String {
1955 let table = self
1956 .table
1957 .clone()
1958 .unwrap_or_else(|| M::table_name().to_string());
1959
1960 let mut sql = format!(
1961 "SELECT AVG({}) as avg_val FROM {}",
1962 self.dialect.quote(field),
1963 self.dialect.quote(&table)
1964 );
1965 sql.push_str(&self.build_where_clause());
1966 sql
1967 }
1968
1969 pub fn validate(&self) -> Result<(), ValidationError> {
1972 let sql = self.build_select();
1973 let mut errors: Vec<sz_orm_sql_validator::SqlValidationError> = Vec::new();
1974
1975 if let Err(e) = sz_orm_sql_validator::validate_select(&sql) {
1976 errors.push(e);
1977 }
1978
1979 if !self.joins.is_empty() {
1981 for join in &self.joins {
1982 match join {
1983 JoinClause::Inner(_, left, right)
1984 | JoinClause::Left(_, left, right)
1985 | JoinClause::Right(_, left, right) => {
1986 if let Err(e) = sz_orm_sql_validator::validate_column_name(left) {
1987 errors.push(e);
1988 }
1989 if let Err(e) = sz_orm_sql_validator::validate_column_name(right) {
1990 errors.push(e);
1991 }
1992 }
1993 _ => {}
1994 }
1995 }
1996 }
1997
1998 let table = self
2000 .table
2001 .clone()
2002 .unwrap_or_else(|| M::table_name().to_string());
2003 if let Err(e) = sz_orm_sql_validator::validate_table_name(&table) {
2004 errors.push(e);
2005 }
2006
2007 if errors.is_empty() {
2008 Ok(())
2009 } else {
2010 Err(errors.into())
2011 }
2012 }
2013
2014 pub fn validate_insert(
2017 &self,
2018 data: &std::collections::HashMap<String, Value>,
2019 ) -> Result<(), ValidationError> {
2020 let sql = self.build_insert(data);
2021 let mut errors: Vec<sz_orm_sql_validator::SqlValidationError> = Vec::new();
2022
2023 if sql.is_empty() {
2024 errors.push(sz_orm_sql_validator::SqlValidationError::EmptyInsertData);
2025 return Err(errors.into());
2026 }
2027
2028 if let Err(e) = sz_orm_sql_validator::validate_insert(&sql) {
2029 errors.push(e);
2030 }
2031
2032 if errors.is_empty() {
2033 Ok(())
2034 } else {
2035 Err(errors.into())
2036 }
2037 }
2038
2039 pub fn validate_update(
2042 &self,
2043 data: &std::collections::HashMap<String, Value>,
2044 ) -> Result<(), ValidationError> {
2045 let sql = self.build_update(data);
2046 let mut errors: Vec<sz_orm_sql_validator::SqlValidationError> = Vec::new();
2047
2048 if sql.is_empty() {
2049 errors.push(sz_orm_sql_validator::SqlValidationError::EmptyUpdateData);
2050 return Err(errors.into());
2051 }
2052
2053 if let Err(e) = sz_orm_sql_validator::validate_update(&sql) {
2054 errors.push(e);
2055 }
2056
2057 if errors.is_empty() {
2058 Ok(())
2059 } else {
2060 Err(errors.into())
2061 }
2062 }
2063
2064 pub fn validate_delete(&self) -> Result<(), ValidationError> {
2066 let sql = self.build_delete();
2067 let mut errors: Vec<sz_orm_sql_validator::SqlValidationError> = Vec::new();
2068
2069 if let Err(e) = sz_orm_sql_validator::validate_delete(&sql) {
2070 errors.push(e);
2071 }
2072
2073 if errors.is_empty() {
2074 Ok(())
2075 } else {
2076 Err(errors.into())
2077 }
2078 }
2079}
2080
2081impl<M: Model> fmt::Debug for QueryBuilder<M> {
2082 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2083 f.debug_struct("QueryBuilder")
2084 .field("table", &self.table)
2085 .field("select_columns", &self.select_columns)
2086 .field("where_conditions", &self.where_conditions.len())
2087 .field("limit", &self.limit_value)
2088 .finish()
2089 }
2090}
2091
2092#[cfg(test)]
2093#[allow(deprecated)] mod tests {
2095 use super::*;
2096 use sz_orm_model::get_dialect;
2097 use sz_orm_model::DbType;
2098
2099 struct TestModel;
2100 impl Model for TestModel {
2101 type PrimaryKey = i64;
2102
2103 fn table_name() -> &'static str {
2104 "test_models"
2105 }
2106
2107 fn pk(&self) -> Self::PrimaryKey {
2108 1
2109 }
2110
2111 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2112 }
2113
2114 #[test]
2115 fn test_query_builder_select() -> Result<(), sz_orm_model::DbError> {
2116 let dialect = get_dialect(DbType::MySQL)?;
2117 let builder = QueryBuilder::<TestModel>::new(dialect);
2118
2119 let sql = builder
2120 .table("users")
2121 .select(vec!["id", "name"])
2122 .build_select();
2123 assert!(sql.contains("SELECT id, name FROM"));
2124 assert!(sql.contains("`users`"));
2125 Ok(())
2126 }
2127
2128 #[test]
2129 fn test_query_builder_where() -> 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_cond("status = 'active'")
2136 .where_cond("age > 18")
2137 .build_select();
2138
2139 assert!(sql.contains("WHERE"));
2140 assert!(sql.contains("status = 'active'"));
2141 assert!(sql.contains("age > 18"));
2142 Ok(())
2143 }
2144
2145 #[test]
2146 fn test_query_builder_order_by() -> Result<(), sz_orm_model::DbError> {
2147 let dialect = get_dialect(DbType::MySQL)?;
2148 let builder = QueryBuilder::<TestModel>::new(dialect);
2149
2150 let sql = builder
2151 .table("users")
2152 .order_by("created_at")
2153 .order_desc("id")
2154 .build_select();
2155
2156 assert!(sql.contains("ORDER BY"));
2157 assert!(sql.contains("`created_at` ASC"));
2158 assert!(sql.contains("`id` DESC"));
2159 Ok(())
2160 }
2161
2162 #[test]
2163 fn test_query_builder_limit_offset() -> Result<(), sz_orm_model::DbError> {
2164 let dialect = get_dialect(DbType::MySQL)?;
2165 let builder = QueryBuilder::<TestModel>::new(dialect);
2166
2167 let sql = builder.table("users").limit(10).offset(20).build_select();
2168
2169 assert!(sql.contains("LIMIT 10"));
2170 assert!(sql.contains("OFFSET 20"));
2171 Ok(())
2172 }
2173
2174 #[test]
2175 fn test_query_builder_page() -> Result<(), sz_orm_model::DbError> {
2176 let dialect = get_dialect(DbType::MySQL)?;
2177 let builder = QueryBuilder::<TestModel>::new(dialect);
2178
2179 let sql = builder.table("users").page(3, 20).build_select();
2180
2181 assert!(sql.contains("LIMIT 20"));
2182 assert!(sql.contains("OFFSET 40"));
2183 Ok(())
2184 }
2185
2186 #[test]
2187 fn test_query_builder_insert() -> Result<(), sz_orm_model::DbError> {
2188 let dialect = get_dialect(DbType::MySQL)?;
2189 let builder = QueryBuilder::<TestModel>::new(dialect);
2190
2191 let mut data = std::collections::HashMap::new();
2192 data.insert("name".to_string(), Value::String("test".to_string()));
2193 data.insert("age".to_string(), Value::I64(25));
2194
2195 let sql = builder.table("users").build_insert(&data);
2196
2197 assert!(sql.contains("INSERT INTO"));
2198 assert!(sql.contains("`name`"));
2199 assert!(sql.contains("'test'"));
2200 Ok(())
2201 }
2202
2203 #[test]
2204 fn test_query_builder_update() -> Result<(), sz_orm_model::DbError> {
2205 let dialect = get_dialect(DbType::MySQL)?;
2206 let builder = QueryBuilder::<TestModel>::new(dialect);
2207
2208 let mut data = std::collections::HashMap::new();
2209 data.insert("name".to_string(), Value::String("updated".to_string()));
2210
2211 let sql = builder
2212 .table("users")
2213 .where_cond("id = 1")
2214 .build_update(&data);
2215
2216 assert!(sql.contains("UPDATE"));
2217 assert!(sql.contains("`name` = 'updated'"));
2218 assert!(sql.contains("WHERE"));
2219 Ok(())
2220 }
2221
2222 #[test]
2223 fn test_query_builder_delete() -> Result<(), sz_orm_model::DbError> {
2224 let dialect = get_dialect(DbType::MySQL)?;
2225 let builder = QueryBuilder::<TestModel>::new(dialect);
2226
2227 let sql = builder.table("users").where_cond("id = 1").build_delete();
2228
2229 assert!(sql.contains("DELETE FROM"));
2230 assert!(sql.contains("WHERE"));
2231 Ok(())
2232 }
2233
2234 #[test]
2235 fn test_query_builder_count() -> Result<(), sz_orm_model::DbError> {
2236 let dialect = get_dialect(DbType::MySQL)?;
2237 let builder = QueryBuilder::<TestModel>::new(dialect);
2238
2239 let sql = builder.table("users").build_count();
2240
2241 assert!(sql.contains("SELECT COUNT(*)"));
2242 assert!(sql.contains("FROM"));
2243 Ok(())
2244 }
2245
2246 #[test]
2247 fn test_query_builder_where_in() -> Result<(), sz_orm_model::DbError> {
2248 let dialect = get_dialect(DbType::MySQL)?;
2249 let builder = QueryBuilder::<TestModel>::new(dialect);
2250
2251 let sql = builder
2252 .table("users")
2253 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2254 .build_select();
2255
2256 assert!(sql.contains("IN ("));
2257 Ok(())
2258 }
2259
2260 #[test]
2261 fn test_query_builder_where_between() -> Result<(), sz_orm_model::DbError> {
2262 let dialect = get_dialect(DbType::MySQL)?;
2263 let builder = QueryBuilder::<TestModel>::new(dialect);
2264
2265 let sql = builder
2266 .table("users")
2267 .where_between("age", Value::I64(18), Value::I64(30))
2268 .build_select();
2269
2270 assert!(sql.contains("BETWEEN"));
2271 Ok(())
2272 }
2273
2274 #[test]
2275 fn test_query_builder_where_null() -> Result<(), sz_orm_model::DbError> {
2276 let dialect = get_dialect(DbType::MySQL)?;
2277 let builder = QueryBuilder::<TestModel>::new(dialect);
2278
2279 let sql = builder
2280 .table("users")
2281 .where_null("deleted_at")
2282 .build_select();
2283
2284 assert!(sql.contains("IS NULL"));
2285 Ok(())
2286 }
2287
2288 #[test]
2289 fn test_query_builder_join() -> Result<(), sz_orm_model::DbError> {
2290 let dialect = get_dialect(DbType::MySQL)?;
2291 let builder = QueryBuilder::<TestModel>::new(dialect);
2292
2293 let sql = builder
2294 .table("users")
2295 .join_inner("posts", "users.id", "posts.user_id")
2296 .build_select();
2297
2298 assert!(sql.contains("INNER JOIN"));
2299 assert!(sql.contains("`posts`"));
2300 Ok(())
2301 }
2302
2303 #[test]
2304 fn test_query_builder_group_by() -> Result<(), sz_orm_model::DbError> {
2305 let dialect = get_dialect(DbType::MySQL)?;
2306 let builder = QueryBuilder::<TestModel>::new(dialect);
2307
2308 let sql = builder.table("users").group_by("status").build_select();
2309
2310 assert!(sql.contains("GROUP BY"));
2311 assert!(sql.contains("`status`"));
2312 Ok(())
2313 }
2314
2315 #[test]
2316 fn test_query_builder_max() -> Result<(), sz_orm_model::DbError> {
2317 let dialect = get_dialect(DbType::MySQL)?;
2318 let builder = QueryBuilder::<TestModel>::new(dialect);
2319
2320 let sql = builder.table("users").build_max("score");
2321
2322 assert!(sql.contains("MAX("));
2323 assert!(sql.contains("`score`"));
2324 Ok(())
2325 }
2326
2327 #[test]
2328 fn test_query_builder_min() -> Result<(), sz_orm_model::DbError> {
2329 let dialect = get_dialect(DbType::MySQL)?;
2330 let builder = QueryBuilder::<TestModel>::new(dialect);
2331
2332 let sql = builder.table("users").build_min("price");
2333
2334 assert!(sql.contains("MIN("));
2335 assert!(sql.contains("`price`"));
2336 Ok(())
2337 }
2338
2339 #[test]
2340 fn test_query_builder_sum() -> Result<(), sz_orm_model::DbError> {
2341 let dialect = get_dialect(DbType::MySQL)?;
2342 let builder = QueryBuilder::<TestModel>::new(dialect);
2343
2344 let sql = builder.table("orders").build_sum("amount");
2345
2346 assert!(sql.contains("SUM("));
2347 assert!(sql.contains("`amount`"));
2348 Ok(())
2349 }
2350
2351 #[test]
2352 fn test_query_builder_avg() -> Result<(), sz_orm_model::DbError> {
2353 let dialect = get_dialect(DbType::MySQL)?;
2354 let builder = QueryBuilder::<TestModel>::new(dialect);
2355
2356 let sql = builder.table("scores").build_avg("value");
2357
2358 assert!(sql.contains("AVG("));
2359 assert!(sql.contains("`value`"));
2360 Ok(())
2361 }
2362
2363 #[test]
2364 fn test_validator_select() -> Result<(), sz_orm_model::DbError> {
2365 let dialect = get_dialect(DbType::MySQL)?;
2366 let builder = QueryBuilder::<TestModel>::new(dialect);
2367
2368 let result = builder.table("users").select(vec!["id", "name"]).validate();
2369 assert!(result.is_ok());
2370 Ok(())
2371 }
2372
2373 #[test]
2374 fn test_validator_select_with_join() -> Result<(), sz_orm_model::DbError> {
2375 let dialect = get_dialect(DbType::MySQL)?;
2376 let builder = QueryBuilder::<TestModel>::new(dialect);
2377
2378 let result = builder
2379 .table("users")
2380 .join_inner("posts", "users.id", "posts.user_id")
2381 .validate();
2382 assert!(result.is_ok());
2383 Ok(())
2384 }
2385
2386 #[test]
2387 fn test_validator_insert() -> Result<(), sz_orm_model::DbError> {
2388 let dialect = get_dialect(DbType::MySQL)?;
2389 let builder = QueryBuilder::<TestModel>::new(dialect);
2390
2391 let mut data = std::collections::HashMap::new();
2392 data.insert("name".to_string(), Value::String("test".to_string()));
2393
2394 let result = builder.table("users").validate_insert(&data);
2395 assert!(result.is_ok());
2396 Ok(())
2397 }
2398
2399 #[test]
2400 fn test_validator_insert_empty_data() -> Result<(), sz_orm_model::DbError> {
2401 let dialect = get_dialect(DbType::MySQL)?;
2402 let builder = QueryBuilder::<TestModel>::new(dialect);
2403
2404 let data = std::collections::HashMap::new();
2405 let result = builder.table("users").validate_insert(&data);
2406 assert!(result.is_err());
2407 Ok(())
2408 }
2409
2410 #[test]
2411 fn test_validator_update() -> Result<(), sz_orm_model::DbError> {
2412 let dialect = get_dialect(DbType::MySQL)?;
2413 let builder = QueryBuilder::<TestModel>::new(dialect);
2414
2415 let mut data = std::collections::HashMap::new();
2416 data.insert("name".to_string(), Value::String("updated".to_string()));
2417
2418 let result = builder.table("users").validate_update(&data);
2419 assert!(result.is_ok());
2420 Ok(())
2421 }
2422
2423 #[test]
2424 fn test_validator_update_empty_data() -> Result<(), sz_orm_model::DbError> {
2425 let dialect = get_dialect(DbType::MySQL)?;
2426 let builder = QueryBuilder::<TestModel>::new(dialect);
2427
2428 let data = std::collections::HashMap::new();
2429 let result = builder.table("users").validate_update(&data);
2430 assert!(result.is_err());
2431 Ok(())
2432 }
2433
2434 #[test]
2435 fn test_validator_delete() -> Result<(), sz_orm_model::DbError> {
2436 let dialect = get_dialect(DbType::MySQL)?;
2437 let builder = QueryBuilder::<TestModel>::new(dialect);
2438
2439 let result = builder
2440 .table("users")
2441 .where_cond("id = 1")
2442 .validate_delete();
2443 assert!(result.is_ok());
2444 Ok(())
2445 }
2446
2447 #[test]
2448 fn test_validator_delete_no_where() -> Result<(), sz_orm_model::DbError> {
2449 let dialect = get_dialect(DbType::MySQL)?;
2450 let builder = QueryBuilder::<TestModel>::new(dialect);
2451
2452 let result = builder.table("users").validate_delete();
2454 assert!(result.is_ok());
2455 Ok(())
2456 }
2457
2458 #[test]
2461 fn test_m3_select_quoted_valid_columns() -> Result<(), sz_orm_model::DbError> {
2462 let dialect = get_dialect(DbType::MySQL)?;
2463 let builder = QueryBuilder::<TestModel>::new(dialect);
2464 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2465 let sql = builder.build_select();
2466 assert!(sql.contains("SELECT `id`, `name` FROM"));
2468 assert!(sql.contains("`users`"));
2469 Ok(())
2470 }
2471
2472 #[test]
2473 fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), sz_orm_model::DbError> {
2474 let dialect = get_dialect(DbType::MySQL)?;
2475 let builder = QueryBuilder::<TestModel>::new(dialect);
2476
2477 let result = builder
2479 .table("users")
2480 .select_quoted(vec!["id; DROP TABLE users"]);
2481 assert!(result.is_err());
2482
2483 let dialect = get_dialect(DbType::MySQL)?;
2485 let builder = QueryBuilder::<TestModel>::new(dialect);
2486 let result = builder.table("users").select_quoted(vec!["name'"]);
2487 assert!(result.is_err());
2488
2489 let dialect = get_dialect(DbType::MySQL)?;
2491 let builder = QueryBuilder::<TestModel>::new(dialect);
2492 let result = builder.table("users").select_quoted(vec!["1col"]);
2493 assert!(result.is_err());
2494
2495 let dialect = get_dialect(DbType::MySQL)?;
2497 let builder = QueryBuilder::<TestModel>::new(dialect);
2498 let result = builder.table("users").select_quoted(vec!["col name"]);
2499 assert!(result.is_err());
2500 Ok(())
2501 }
2502
2503 #[test]
2504 fn test_m3_select_quoted_postgresql_dialect() -> Result<(), sz_orm_model::DbError> {
2505 let dialect = get_dialect(DbType::PostgreSQL)?;
2506 let builder = QueryBuilder::<TestModel>::new(dialect);
2507 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2508 let sql = builder.build_select();
2509 assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2511 assert!(sql.contains("\"users\""));
2512 Ok(())
2513 }
2514
2515 struct SoftDeleteModel;
2519 impl Model for SoftDeleteModel {
2520 type PrimaryKey = i64;
2521
2522 fn table_name() -> &'static str {
2523 "soft_users"
2524 }
2525
2526 fn pk(&self) -> Self::PrimaryKey {
2527 1
2528 }
2529
2530 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2531
2532 fn soft_delete_field() -> Option<&'static str> {
2533 Some("deleted_at")
2534 }
2535 }
2536
2537 #[test]
2541 fn test_p01_soft_delete_select_auto_filter() -> Result<(), sz_orm_model::DbError> {
2542 let dialect = get_dialect(DbType::MySQL)?;
2543 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2544 let sql = builder.table("soft_users").build_select();
2545 assert!(
2547 sql.contains("`deleted_at` IS NULL"),
2548 "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2549 sql
2550 );
2551 Ok(())
2552 }
2553
2554 #[test]
2556 fn test_p01_soft_delete_select_with_user_where() -> Result<(), sz_orm_model::DbError> {
2557 let dialect = get_dialect(DbType::MySQL)?;
2558 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2559 .table("soft_users")
2560 .where_eq("status", Value::String("active".into()))
2561 .build_select();
2562 assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2564 assert!(
2565 sql.contains("`deleted_at` IS NULL"),
2566 "软删除条件应自动追加: {}",
2567 sql
2568 );
2569 Ok(())
2570 }
2571
2572 #[test]
2576 fn test_p01_soft_delete_without_soft_delete() -> Result<(), sz_orm_model::DbError> {
2577 let dialect = get_dialect(DbType::MySQL)?;
2578 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2579 .table("soft_users")
2580 .without_soft_delete()
2581 .build_select();
2582 assert!(
2584 !sql.contains("`deleted_at` IS NULL"),
2585 "without_soft_delete 应禁用过滤,实际: {}",
2586 sql
2587 );
2588 assert!(
2590 !sql.contains("WHERE"),
2591 "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2592 sql
2593 );
2594 Ok(())
2595 }
2596
2597 #[test]
2601 fn test_p01_soft_delete_delete_becomes_update() -> Result<(), sz_orm_model::DbError> {
2602 let dialect = get_dialect(DbType::MySQL)?;
2603 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2604 .table("soft_users")
2605 .where_eq("id", Value::I64(42))
2606 .build_delete();
2607 assert!(
2609 sql.starts_with("UPDATE"),
2610 "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2611 sql
2612 );
2613 assert!(
2614 !sql.contains("DELETE FROM"),
2615 "不应生成 DELETE FROM: {}",
2616 sql
2617 );
2618 assert!(
2619 sql.contains("`deleted_at` = NOW()"),
2620 "应设置 deleted_at = NOW(): {}",
2621 sql
2622 );
2623 assert!(
2625 sql.contains("`deleted_at` IS NULL"),
2626 "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2627 sql
2628 );
2629 Ok(())
2630 }
2631
2632 #[test]
2636 fn test_p01_soft_delete_force_delete() -> Result<(), sz_orm_model::DbError> {
2637 let dialect = get_dialect(DbType::MySQL)?;
2638 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2639 .table("soft_users")
2640 .where_eq("id", Value::I64(99))
2641 .build_force_delete();
2642 assert!(
2644 sql.starts_with("DELETE FROM"),
2645 "build_force_delete 应生成 DELETE FROM,实际: {}",
2646 sql
2647 );
2648 assert!(
2650 !sql.contains("`deleted_at` IS NULL"),
2651 "物理删除不应追加软删除过滤: {}",
2652 sql
2653 );
2654 Ok(())
2655 }
2656
2657 #[test]
2659 fn test_p01_soft_delete_select_with_params() -> Result<(), sz_orm_model::DbError> {
2660 let dialect = get_dialect(DbType::MySQL)?;
2661 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2662 .table("soft_users")
2663 .where_eq("id", Value::I64(1))
2664 .build_select_with_params();
2665 assert!(
2666 sql.contains("`deleted_at` IS NULL"),
2667 "参数化版本也应自动追加软删除: {}",
2668 sql
2669 );
2670 assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
2671 assert_eq!(params[0], Value::I64(1));
2672 Ok(())
2673 }
2674
2675 #[test]
2677 fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), sz_orm_model::DbError>
2678 {
2679 let dialect = get_dialect(DbType::MySQL)?;
2680 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2681 .table("soft_users")
2682 .where_eq("id", Value::I64(7))
2683 .build_delete_with_params();
2684 assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
2685 assert!(
2686 sql.contains("`deleted_at` = NOW()"),
2687 "应设置 NOW(): {}",
2688 sql
2689 );
2690 assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
2691 Ok(())
2692 }
2693
2694 #[test]
2696 fn test_p01_soft_delete_force_delete_with_params() -> Result<(), sz_orm_model::DbError> {
2697 let dialect = get_dialect(DbType::MySQL)?;
2698 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2699 .table("soft_users")
2700 .where_eq("id", Value::I64(11))
2701 .build_force_delete_with_params();
2702 assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
2703 assert!(
2704 !sql.contains("`deleted_at` IS NULL"),
2705 "不应追加软删除过滤: {}",
2706 sql
2707 );
2708 assert_eq!(params.len(), 1);
2709 Ok(())
2710 }
2711
2712 #[test]
2716 fn test_p01_non_soft_delete_model_unchanged() -> Result<(), sz_orm_model::DbError> {
2717 let dialect = get_dialect(DbType::MySQL)?;
2718 let sql = QueryBuilder::<TestModel>::new(dialect)
2719 .table("users")
2720 .where_eq("id", Value::I64(1))
2721 .build_select();
2722 assert!(
2723 !sql.contains("deleted_at"),
2724 "非软删除模型不应追加 deleted_at: {}",
2725 sql
2726 );
2727 let dialect = get_dialect(DbType::MySQL)?;
2729 let del_sql = QueryBuilder::<TestModel>::new(dialect)
2730 .table("users")
2731 .where_eq("id", Value::I64(1))
2732 .build_delete();
2733 assert!(
2734 del_sql.starts_with("DELETE FROM"),
2735 "非软删除模型 build_delete 应生成 DELETE: {}",
2736 del_sql
2737 );
2738 Ok(())
2739 }
2740
2741 #[test]
2743 fn test_p01_soft_delete_count_auto_filter() -> Result<(), sz_orm_model::DbError> {
2744 let dialect = get_dialect(DbType::MySQL)?;
2745 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2746 .table("soft_users")
2747 .build_count();
2748 assert!(
2749 sql.contains("`deleted_at` IS NULL"),
2750 "build_count 也应追加软删除过滤: {}",
2751 sql
2752 );
2753 Ok(())
2754 }
2755
2756 #[test]
2762 fn test_p02_where_eq_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2763 let dialect = get_dialect(DbType::MySQL)?;
2764 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2765 .table("users")
2766 .where_eq("name", Value::String("alice".into()))
2767 .build_select_with_params();
2768 assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
2770 assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
2771 assert_eq!(params.len(), 1);
2772 assert_eq!(params[0], Value::String("alice".into()));
2773 Ok(())
2774 }
2775
2776 #[test]
2778 fn test_p02_where_like_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2779 let dialect = get_dialect(DbType::MySQL)?;
2780 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2781 .table("users")
2782 .where_like("name", Value::String("%alice%".into()))
2783 .build_select_with_params();
2784 assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
2785 assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
2786 assert_eq!(params.len(), 1);
2787 Ok(())
2788 }
2789
2790 #[test]
2794 fn test_p02_where_ne_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2795 let dialect = get_dialect(DbType::MySQL)?;
2796 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2797 .table("users")
2798 .where_ne("status", Value::I64(0))
2799 .build_select_with_params();
2800 assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
2801 assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
2802 assert_eq!(params.len(), 1);
2803 assert_eq!(params[0], Value::I64(0));
2804 Ok(())
2805 }
2806
2807 #[test]
2811 fn test_p02_where_ge_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2812 let dialect = get_dialect(DbType::MySQL)?;
2813 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2814 .table("users")
2815 .where_ge("age", Value::I64(18))
2816 .build_select_with_params();
2817 assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
2818 assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
2819 assert_eq!(params.len(), 1);
2820 assert_eq!(params[0], Value::I64(18));
2821 Ok(())
2822 }
2823
2824 #[test]
2828 fn test_p02_where_lt_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2829 let dialect = get_dialect(DbType::MySQL)?;
2830 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2831 .table("users")
2832 .where_lt("score", Value::F64(60.0))
2833 .build_select_with_params();
2834 assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
2835 assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
2836 assert_eq!(params.len(), 1);
2837 assert_eq!(params[0], Value::F64(60.0));
2838 Ok(())
2839 }
2840
2841 #[test]
2845 fn test_p02_injection_protection_drop_table() -> Result<(), sz_orm_model::DbError> {
2846 let dialect = get_dialect(DbType::MySQL)?;
2847 let evil_input = "'; DROP TABLE users; --".to_string();
2848 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2849 .table("users")
2850 .where_eq("name", Value::String(evil_input.clone()))
2851 .build_select_with_params();
2852 assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
2854 assert_eq!(params.len(), 1);
2856 assert_eq!(params[0], Value::String(evil_input));
2857 assert_eq!(sql.matches('?').count(), 1);
2859 Ok(())
2860 }
2861
2862 #[test]
2864 fn test_p02_injection_protection_or_one_equals_one() -> Result<(), sz_orm_model::DbError> {
2865 let dialect = get_dialect(DbType::MySQL)?;
2866 let evil = "' OR '1'='1".to_string();
2867 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2868 .table("users")
2869 .where_eq("name", Value::String(evil.clone()))
2870 .build_select_with_params();
2871 assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
2872 assert_eq!(params.len(), 1);
2873 assert_eq!(params[0], Value::String(evil));
2874 Ok(())
2875 }
2876
2877 #[test]
2879 fn test_p02_multiple_params_order() -> Result<(), sz_orm_model::DbError> {
2880 let dialect = get_dialect(DbType::MySQL)?;
2881 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2882 .table("users")
2883 .where_eq("name", Value::String("alice".into()))
2884 .where_gt("age", Value::I64(18))
2885 .where_le("score", Value::F64(99.5))
2886 .build_select_with_params();
2887 assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
2888 assert_eq!(params.len(), 3);
2889 assert_eq!(params[0], Value::String("alice".into()));
2891 assert_eq!(params[1], Value::I64(18));
2892 assert_eq!(params[2], Value::F64(99.5));
2893 Ok(())
2894 }
2895
2896 #[test]
2898 fn test_p02_where_in_uses_placeholders() -> Result<(), sz_orm_model::DbError> {
2899 let dialect = get_dialect(DbType::MySQL)?;
2900 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2901 .table("users")
2902 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2903 .build_select_with_params();
2904 assert!(
2905 sql.contains("`id` IN (?, ?, ?)"),
2906 "应使用 3 个占位符: {}",
2907 sql
2908 );
2909 assert_eq!(params.len(), 3);
2910 Ok(())
2911 }
2912
2913 #[test]
2915 fn test_p02_where_between_uses_placeholders() -> Result<(), sz_orm_model::DbError> {
2916 let dialect = get_dialect(DbType::MySQL)?;
2917 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2918 .table("users")
2919 .where_between("age", Value::I64(18), Value::I64(65))
2920 .build_select_with_params();
2921 assert!(
2922 sql.contains("`age` BETWEEN ? AND ?"),
2923 "应使用 2 个占位符: {}",
2924 sql
2925 );
2926 assert_eq!(params.len(), 2);
2927 assert_eq!(params[0], Value::I64(18));
2928 assert_eq!(params[1], Value::I64(65));
2929 Ok(())
2930 }
2931
2932 #[test]
2934 fn test_p02_update_params_order_set_before_where() -> Result<(), sz_orm_model::DbError> {
2935 let dialect = get_dialect(DbType::MySQL)?;
2936 let mut data = std::collections::HashMap::new();
2937 data.insert("name".to_string(), Value::String("bob".into()));
2938 data.insert("age".to_string(), Value::I64(30));
2939 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2940 .table("users")
2941 .where_eq("id", Value::I64(99))
2942 .build_update_with_params(&data);
2943 assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
2945 assert_eq!(params.len(), 3);
2946 assert_eq!(params[2], Value::I64(99));
2949 Ok(())
2950 }
2951
2952 #[test]
2956 fn test_p02_build_where_clause_inlines_value() -> Result<(), sz_orm_model::DbError> {
2957 let dialect = get_dialect(DbType::MySQL)?;
2958 let sql = QueryBuilder::<TestModel>::new(dialect)
2959 .table("users")
2960 .where_eq("name", Value::String("alice".into()))
2961 .build_select();
2962 assert!(
2964 sql.contains("`name` = "),
2965 "无参数版本应含 WHERE 条件: {}",
2966 sql
2967 );
2968 assert!(
2970 !sql.contains("`name` = ?"),
2971 "无参数版本不应使用 ? 占位符: {}",
2972 sql
2973 );
2974 Ok(())
2975 }
2976
2977 #[test]
2979 fn test_p01_is_soft_delete_disabled_flag() -> Result<(), sz_orm_model::DbError> {
2980 let dialect = get_dialect(DbType::MySQL)?;
2981 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2982 assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
2983 let builder =
2984 QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
2985 assert!(
2986 builder.is_soft_delete_disabled(),
2987 "without_soft_delete 后应反映禁用状态"
2988 );
2989 Ok(())
2990 }
2991
2992 struct TenantModel;
2996 impl Model for TenantModel {
2997 type PrimaryKey = i64;
2998
2999 fn table_name() -> &'static str {
3000 "orders"
3001 }
3002
3003 fn pk(&self) -> Self::PrimaryKey {
3004 1
3005 }
3006
3007 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3008
3009 fn tenant_field() -> Option<&'static str> {
3010 Some("tenant_id")
3011 }
3012 }
3013
3014 struct SoftDeleteAndTenantModel;
3016 impl Model for SoftDeleteAndTenantModel {
3017 type PrimaryKey = i64;
3018
3019 fn table_name() -> &'static str {
3020 "documents"
3021 }
3022
3023 fn pk(&self) -> Self::PrimaryKey {
3024 1
3025 }
3026
3027 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3028
3029 fn soft_delete_field() -> Option<&'static str> {
3030 Some("deleted_at")
3031 }
3032
3033 fn tenant_field() -> Option<&'static str> {
3034 Some("tenant_id")
3035 }
3036 }
3037
3038 #[test]
3042 fn test_p03_tenant_select_auto_filter() -> Result<(), sz_orm_model::DbError> {
3043 let dialect = get_dialect(DbType::MySQL)?;
3044 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3045 .table("orders")
3046 .with_tenant_id(42)
3047 .build_select_with_params();
3048 assert!(
3049 sql.contains("`tenant_id` = ?"),
3050 "多租户模型应自动追加 tenant_id = ?: {}",
3051 sql
3052 );
3053 assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
3054 assert_eq!(params[0], Value::I64(42));
3055 Ok(())
3056 }
3057
3058 #[test]
3060 fn test_p03_tenant_select_with_user_where() -> Result<(), sz_orm_model::DbError> {
3061 let dialect = get_dialect(DbType::MySQL)?;
3062 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3063 .table("orders")
3064 .with_tenant_id(7)
3065 .where_eq("status", Value::String("active".into()))
3066 .build_select_with_params();
3067 assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
3068 assert!(
3069 sql.contains("`tenant_id` = ?"),
3070 "租户条件应自动追加: {}",
3071 sql
3072 );
3073 assert_eq!(params.len(), 2, "应有 2 个参数");
3074 assert_eq!(params[0], Value::String("active".into()));
3076 assert_eq!(params[1], Value::I64(7));
3077 Ok(())
3078 }
3079
3080 #[test]
3084 fn test_p03_tenant_without_tenant() -> Result<(), sz_orm_model::DbError> {
3085 let dialect = get_dialect(DbType::MySQL)?;
3086 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3087 .table("orders")
3088 .with_tenant_id(42)
3089 .without_tenant()
3090 .build_select_with_params();
3091 assert!(
3092 !sql.contains("`tenant_id` = ?"),
3093 "without_tenant 应禁用过滤: {}",
3094 sql
3095 );
3096 assert_eq!(params.len(), 0, "不应有租户参数");
3097 Ok(())
3098 }
3099
3100 #[test]
3104 fn test_p03_tenant_delete_auto_filter() -> Result<(), sz_orm_model::DbError> {
3105 let dialect = get_dialect(DbType::MySQL)?;
3106 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3107 .table("orders")
3108 .with_tenant_id(99)
3109 .where_eq("id", Value::I64(1))
3110 .build_delete_with_params();
3111 assert!(
3112 sql.contains("`tenant_id` = ?"),
3113 "删除应自动追加租户条件: {}",
3114 sql
3115 );
3116 assert_eq!(params.len(), 2);
3118 assert_eq!(params[0], Value::I64(1));
3119 assert_eq!(params[1], Value::I64(99));
3120 Ok(())
3121 }
3122
3123 #[test]
3125 fn test_p03_tenant_update_auto_filter() -> Result<(), sz_orm_model::DbError> {
3126 let dialect = get_dialect(DbType::MySQL)?;
3127 let mut data = std::collections::HashMap::new();
3128 data.insert("status".to_string(), Value::String("shipped".into()));
3129 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3130 .table("orders")
3131 .with_tenant_id(5)
3132 .where_eq("id", Value::I64(10))
3133 .build_update_with_params(&data);
3134 assert!(
3135 sql.contains("`tenant_id` = ?"),
3136 "更新应自动追加租户条件: {}",
3137 sql
3138 );
3139 assert_eq!(params.len(), 3);
3141 assert_eq!(params[2], Value::I64(5));
3143 Ok(())
3144 }
3145
3146 #[test]
3148 fn test_p03_tenant_count_auto_filter() -> Result<(), sz_orm_model::DbError> {
3149 let dialect = get_dialect(DbType::MySQL)?;
3150 let sql = QueryBuilder::<TenantModel>::new(dialect)
3151 .table("orders")
3152 .with_tenant_id(42)
3153 .build_count();
3154 assert!(
3155 sql.contains("`tenant_id` = 42"),
3156 "build_count 应追加租户条件(无参数版本内嵌值): {}",
3157 sql
3158 );
3159 Ok(())
3160 }
3161
3162 #[test]
3166 fn test_p03_non_tenant_model_unchanged() -> Result<(), sz_orm_model::DbError> {
3167 let dialect = get_dialect(DbType::MySQL)?;
3168 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3170 .table("users")
3171 .with_tenant_id(42)
3172 .build_select_with_params();
3173 assert!(
3174 !sql.contains("tenant_id"),
3175 "非多租户模型不应追加 tenant_id: {}",
3176 sql
3177 );
3178 assert_eq!(params.len(), 0);
3179 Ok(())
3180 }
3181
3182 #[test]
3186 fn test_p03_tenant_no_id_no_filter() -> Result<(), sz_orm_model::DbError> {
3187 let dialect = get_dialect(DbType::MySQL)?;
3188 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3189 .table("orders")
3190 .build_select_with_params();
3191 assert!(
3192 !sql.contains("tenant_id"),
3193 "未设置 tenant_id 时不应追加过滤: {}",
3194 sql
3195 );
3196 assert_eq!(params.len(), 0);
3197 Ok(())
3198 }
3199
3200 #[test]
3204 fn test_p03_soft_delete_and_tenant_combined() -> Result<(), sz_orm_model::DbError> {
3205 let dialect = get_dialect(DbType::MySQL)?;
3206 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3207 .table("documents")
3208 .with_tenant_id(100)
3209 .where_eq("title", Value::String("report".into()))
3210 .build_select_with_params();
3211 assert!(
3213 sql.contains("`deleted_at` IS NULL"),
3214 "应追加软删除条件: {}",
3215 sql
3216 );
3217 assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3219 assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3221 assert_eq!(params.len(), 2);
3223 assert_eq!(params[0], Value::String("report".into()));
3224 assert_eq!(params[1], Value::I64(100));
3225 Ok(())
3226 }
3227
3228 #[test]
3230 fn test_p03_without_tenant_and_soft_delete() -> Result<(), sz_orm_model::DbError> {
3231 let dialect = get_dialect(DbType::MySQL)?;
3232 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3233 .table("documents")
3234 .with_tenant_id(100)
3235 .without_tenant()
3236 .without_soft_delete()
3237 .build_select_with_params();
3238 assert!(
3239 !sql.contains("`deleted_at` IS NULL"),
3240 "应禁用软删除: {}",
3241 sql
3242 );
3243 assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
3244 assert_eq!(params.len(), 0);
3245 Ok(())
3246 }
3247
3248 #[test]
3250 fn test_p03_is_tenant_disabled_flag() -> Result<(), sz_orm_model::DbError> {
3251 let dialect = get_dialect(DbType::MySQL)?;
3252 let builder = QueryBuilder::<TenantModel>::new(dialect);
3253 assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
3254 let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
3255 .with_tenant_id(1)
3256 .without_tenant();
3257 assert!(
3258 builder.is_tenant_disabled(),
3259 "without_tenant 后应反映禁用状态"
3260 );
3261 Ok(())
3262 }
3263
3264 #[test]
3268 fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), sz_orm_model::DbError> {
3269 let dialect = get_dialect(DbType::MySQL)?;
3270 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3271 .table("orders")
3272 .with_tenant_id(42)
3273 .where_eq("id", Value::I64(999))
3274 .build_force_delete_with_params();
3275 assert!(
3278 sql.contains("`tenant_id` = ?"),
3279 "物理删除应保留租户条件: {}",
3280 sql
3281 );
3282 assert_eq!(params.len(), 2);
3283 assert_eq!(params[0], Value::I64(999));
3284 assert_eq!(params[1], Value::I64(42));
3285 Ok(())
3286 }
3287
3288 #[test]
3291 fn test_batch_insert_chunked_single_chunk() {
3292 let dialect = get_dialect(DbType::MySQL).unwrap();
3293 let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3294 let rows = vec![
3295 [("id".to_string(), Value::I64(1)), ("name".to_string(), Value::String("A".to_string()))].into_iter().collect(),
3296 [("id".to_string(), Value::I64(2)), ("name".to_string(), Value::String("B".to_string()))].into_iter().collect(),
3297 ];
3298 let chunks = builder.build_batch_insert_chunked(&rows);
3299 assert_eq!(chunks.len(), 1);
3300 let (sql, params) = &chunks[0];
3301 assert!(sql.contains("INSERT INTO"));
3302 assert_eq!(params.len(), 4);
3303 }
3304
3305 #[test]
3306 fn test_batch_insert_chunked_multi_chunk() {
3307 let dialect = get_dialect(DbType::MySQL).unwrap();
3308 let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3309 let n = DEFAULT_BATCH_SIZE + 1;
3311 let rows: Vec<std::collections::HashMap<String, Value>> = (0..n)
3312 .map(|i| [("id".to_string(), Value::I64(i as i64))].into_iter().collect())
3313 .collect();
3314 let chunks = builder.build_batch_insert_chunked(&rows);
3315 assert_eq!(chunks.len(), 2);
3316 assert!(chunks[0].0.contains("INSERT INTO"));
3318 assert_eq!(chunks[0].1.len(), DEFAULT_BATCH_SIZE);
3319 assert_eq!(chunks[1].1.len(), 1);
3321 }
3322
3323 #[test]
3324 fn test_batch_insert_chunked_empty() {
3325 let dialect = get_dialect(DbType::MySQL).unwrap();
3326 let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3327 let chunks = builder.build_batch_insert_chunked(&[]);
3328 assert!(chunks.is_empty());
3329 }
3330
3331 #[test]
3334 fn test_batch_update_with_params() {
3335 let dialect = get_dialect(DbType::MySQL).unwrap();
3336 let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3337 let mut data = std::collections::HashMap::new();
3338 data.insert("status".to_string(), Value::I64(1));
3339 let ids = vec![Value::I64(1), Value::I64(2), Value::I64(3)];
3340 let (sql, params) = builder.batch_update_with_params(&data, &ids);
3341 assert!(sql.contains("UPDATE"));
3342 assert!(sql.contains("SET"));
3343 assert!(sql.contains("WHERE"));
3344 assert!(sql.contains("IN"), "sql should contain IN clause: {}", sql);
3345 assert_eq!(params.len(), 4); assert_eq!(params[0], Value::I64(1)); assert_eq!(params[1], Value::I64(1)); assert_eq!(params[2], Value::I64(2)); assert_eq!(params[3], Value::I64(3)); }
3351
3352 #[test]
3353 fn test_batch_update_with_params_empty() {
3354 let dialect = get_dialect(DbType::MySQL).unwrap();
3355 let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3356 let mut data = std::collections::HashMap::new();
3357 data.insert("status".to_string(), Value::I64(1));
3358 assert_eq!(builder.batch_update_with_params(&data, &[]).0, "");
3359 assert_eq!(builder.batch_update_with_params(&std::collections::HashMap::new(), &[Value::I64(1)]).0, "");
3360 }
3361}