1use crate::db_type::DbType;
27use crate::dialect::Dialect;
28use crate::dialect::LockType;
29use crate::model::Model;
30use crate::typed::TypedColumn;
31use crate::value::Value;
32use std::fmt;
33use std::time::Duration;
34
35pub struct QueryBuilder<M: Model> {
37 table: Option<String>,
38 select_columns: Vec<String>,
39 select_mode: crate::partial_model::SelectMode,
40 where_conditions: Vec<WhereCondition>,
41 order_by: Vec<OrderClause>,
42 group_by: Vec<String>,
43 having_conditions: Vec<WhereCondition>,
44 limit_value: Option<usize>,
45 offset_value: Option<usize>,
46 joins: Vec<JoinClause>,
47 dialect: Box<dyn Dialect>,
48 soft_delete_disabled: bool,
50 tenant_id_value: Option<i64>,
52 tenant_disabled: bool,
54 keyset_cursor: Option<KeysetCursor>,
60 cache_ttl: Option<Duration>,
65 lock_type: Option<LockType>,
73 insert_or_ignore: bool,
78 #[allow(dead_code)]
79 model: std::marker::PhantomData<M>,
80}
81
82#[derive(Debug, Clone)]
90struct KeysetCursor {
91 field: String,
93 value: Value,
95 direction: KeysetDirection,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101enum KeysetDirection {
102 After,
104 Before,
106}
107
108#[derive(Debug, Clone)]
109#[allow(dead_code)]
110enum WhereCondition {
111 And(String),
116 Or(String),
118 Eq(String, Value),
120 Ne(String, Value),
122 Gt(String, Value),
124 Ge(String, Value),
126 Lt(String, Value),
128 Le(String, Value),
130 Like(String, Value),
132 OrEq(String, Value),
134 OrNe(String, Value),
136 OrGt(String, Value),
138 OrGe(String, Value),
140 OrLt(String, Value),
142 OrLe(String, Value),
144 OrLike(String, Value),
146 In(String, Vec<Value>),
147 NotIn(String, Vec<Value>),
148 Between(String, Value, Value),
149 NotBetween(String, Value, Value),
150 Null(String),
151 NotNull(String),
152 Exists(String),
153 NotExists(String),
154}
155
156#[derive(Debug, Clone)]
157struct OrderClause {
158 field: String,
159 direction: OrderDirection,
160}
161
162#[derive(Debug, Clone)]
163enum OrderDirection {
164 Asc,
165 Desc,
166}
167
168#[derive(Debug, Clone)]
169#[allow(dead_code)]
170enum JoinClause {
171 Inner(String, String, String),
172 Left(String, String, String),
173 Right(String, String, String),
174 Cross(String, String),
175 Relation(
178 crate::relation_trait::JoinKind,
179 String,
180 String,
181 String,
182 String,
183 ),
184}
185
186impl<M: Model> QueryBuilder<M> {
187 pub fn new(dialect: Box<dyn Dialect>) -> Self {
188 Self {
189 table: None,
190 select_columns: vec!["*".to_string()],
191 select_mode: crate::partial_model::SelectMode::All,
192 where_conditions: Vec::new(),
193 order_by: Vec::new(),
194 group_by: Vec::new(),
195 having_conditions: Vec::new(),
196 limit_value: None,
197 offset_value: None,
198 joins: Vec::new(),
199 dialect,
200 soft_delete_disabled: false,
201 tenant_id_value: None,
202 tenant_disabled: false,
203 keyset_cursor: None,
204 cache_ttl: None,
205 lock_type: None,
206 insert_or_ignore: false,
207 model: std::marker::PhantomData,
208 }
209 }
210
211 pub fn table(mut self, table: impl Into<String>) -> Self {
212 self.table = Some(table.into());
213 self
214 }
215
216 pub fn without_soft_delete(mut self) -> Self {
235 self.soft_delete_disabled = true;
236 self
237 }
238
239 pub fn is_soft_delete_disabled(&self) -> bool {
241 self.soft_delete_disabled
242 }
243
244 pub fn cache_ttl(mut self, ttl: Duration) -> Self {
264 self.cache_ttl = Some(ttl);
265 self
266 }
267
268 pub fn get_cache_ttl(&self) -> Option<Duration> {
270 self.cache_ttl
271 }
272
273 pub fn lock_for_update(mut self) -> Result<Self, crate::error::DbError> {
298 if !self.dialect.supports_lock_for_update() {
299 return Err(crate::error::DbError::QueryError(
300 "FOR UPDATE lock is not supported by this dialect".to_string(),
301 ));
302 }
303 self.lock_type = Some(LockType::ForUpdate);
304 Ok(self)
305 }
306
307 pub fn lock_shared(mut self) -> Result<Self, crate::error::DbError> {
332 if !self.dialect.supports_lock_shared() {
333 return Err(crate::error::DbError::QueryError(
334 "Shared lock is not supported by this dialect".to_string(),
335 ));
336 }
337 self.lock_type = Some(LockType::Shared);
338 Ok(self)
339 }
340
341 pub fn insert_or_ignore(mut self) -> Self {
360 self.insert_or_ignore = true;
361 self
362 }
363
364 pub fn get_lock_type(&self) -> Option<LockType> {
366 self.lock_type
367 }
368
369 pub fn is_insert_or_ignore(&self) -> bool {
371 self.insert_or_ignore
372 }
373
374 pub fn clone_for_count(&self) -> Self {
381 Self {
382 table: self.table.clone(),
383 select_columns: vec!["*".to_string()],
384 select_mode: crate::partial_model::SelectMode::All,
385 where_conditions: self.where_conditions.clone(),
386 order_by: Vec::new(),
387 group_by: self.group_by.clone(),
388 having_conditions: self.having_conditions.clone(),
389 limit_value: None,
390 offset_value: None,
391 joins: self.joins.clone(),
392 dialect: self.dialect.clone_box(),
393 soft_delete_disabled: self.soft_delete_disabled,
394 tenant_id_value: self.tenant_id_value,
395 tenant_disabled: self.tenant_disabled,
396 keyset_cursor: None,
397 cache_ttl: None, lock_type: None, insert_or_ignore: false, model: std::marker::PhantomData,
401 }
402 }
403
404 fn soft_delete_field(&self) -> Option<&'static str> {
408 if self.soft_delete_disabled {
409 return None;
410 }
411 M::soft_delete_field()
412 }
413
414 fn build_soft_delete_condition(&self) -> Option<String> {
418 self.soft_delete_field()
419 .map(|field| format!("{} IS NULL", self.dialect.quote(field)))
420 }
421
422 pub fn with_tenant_id(mut self, tenant_id: i64) -> Self {
449 self.tenant_id_value = Some(tenant_id);
450 self
451 }
452
453 pub fn without_tenant(mut self) -> Self {
457 self.tenant_disabled = true;
458 self
459 }
460
461 pub fn is_tenant_disabled(&self) -> bool {
463 self.tenant_disabled
464 }
465
466 fn tenant_field(&self) -> Option<&'static str> {
470 if self.tenant_disabled {
471 return None;
472 }
473 M::tenant_field()
474 }
475
476 fn tenant_id_value(&self) -> Option<i64> {
478 if self.tenant_disabled {
479 return None;
480 }
481 self.tenant_id_value
482 }
483
484 fn build_tenant_condition(&self) -> Option<(String, Value)> {
489 let field = self.tenant_field()?;
490 let tid = self.tenant_id_value()?;
491 Some((
492 format!("{} = ?", self.dialect.quote(field)),
493 Value::I64(tid),
494 ))
495 }
496
497 pub fn select(mut self, columns: Vec<&str>) -> Self {
505 self.select_columns = columns.into_iter().map(|s| s.to_string()).collect();
506 self
507 }
508
509 pub fn select_quoted(mut self, columns: Vec<&str>) -> Result<Self, crate::DbError> {
518 let mut quoted = Vec::with_capacity(columns.len());
519 for col in columns {
520 crate::sql_safety::validate_identifier(col, "select column")?;
521 quoted.push(self.dialect.quote(col));
522 }
523 self.select_columns = quoted;
524 Ok(self)
525 }
526
527 pub fn where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
541 self.where_conditions
542 .push(WhereCondition::Eq(field.into(), value));
543 self
544 }
545
546 pub fn where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
548 self.where_conditions
549 .push(WhereCondition::Ne(field.into(), value));
550 self
551 }
552
553 pub fn where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
555 self.where_conditions
556 .push(WhereCondition::Gt(field.into(), value));
557 self
558 }
559
560 pub fn where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
562 self.where_conditions
563 .push(WhereCondition::Ge(field.into(), value));
564 self
565 }
566
567 pub fn where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
569 self.where_conditions
570 .push(WhereCondition::Lt(field.into(), value));
571 self
572 }
573
574 pub fn where_le(mut self, field: impl Into<String>, value: Value) -> Self {
576 self.where_conditions
577 .push(WhereCondition::Le(field.into(), value));
578 self
579 }
580
581 pub fn where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
593 self.where_conditions
594 .push(WhereCondition::Like(field.into(), pattern));
595 self
596 }
597
598 pub fn or_where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
602 self.where_conditions
603 .push(WhereCondition::OrEq(field.into(), value));
604 self
605 }
606
607 pub fn or_where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
609 self.where_conditions
610 .push(WhereCondition::OrNe(field.into(), value));
611 self
612 }
613
614 pub fn or_where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
616 self.where_conditions
617 .push(WhereCondition::OrGt(field.into(), value));
618 self
619 }
620
621 pub fn or_where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
623 self.where_conditions
624 .push(WhereCondition::OrGe(field.into(), value));
625 self
626 }
627
628 pub fn or_where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
630 self.where_conditions
631 .push(WhereCondition::OrLt(field.into(), value));
632 self
633 }
634
635 pub fn or_where_le(mut self, field: impl Into<String>, value: Value) -> Self {
637 self.where_conditions
638 .push(WhereCondition::OrLe(field.into(), value));
639 self
640 }
641
642 pub fn or_where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
644 self.where_conditions
645 .push(WhereCondition::OrLike(field.into(), pattern));
646 self
647 }
648
649 pub fn where_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
650 self.where_conditions
651 .push(WhereCondition::In(field.into(), values));
652 self
653 }
654
655 pub fn where_not_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
656 self.where_conditions
657 .push(WhereCondition::NotIn(field.into(), values));
658 self
659 }
660
661 pub fn where_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
662 self.where_conditions
663 .push(WhereCondition::Between(field.into(), start, end));
664 self
665 }
666
667 pub fn where_not_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
668 self.where_conditions
669 .push(WhereCondition::NotBetween(field.into(), start, end));
670 self
671 }
672
673 pub fn where_null(mut self, field: impl Into<String>) -> Self {
674 self.where_conditions
675 .push(WhereCondition::Null(field.into()));
676 self
677 }
678
679 pub fn where_not_null(mut self, field: impl Into<String>) -> Self {
680 self.where_conditions
681 .push(WhereCondition::NotNull(field.into()));
682 self
683 }
684
685 pub fn order_by(mut self, field: impl Into<String>) -> Self {
686 self.order_by.push(OrderClause {
687 field: field.into(),
688 direction: OrderDirection::Asc,
689 });
690 self
691 }
692
693 pub fn order_desc(mut self, field: impl Into<String>) -> Self {
694 self.order_by.push(OrderClause {
695 field: field.into(),
696 direction: OrderDirection::Desc,
697 });
698 self
699 }
700
701 pub fn group_by(mut self, field: impl Into<String>) -> Self {
702 self.group_by.push(field.into());
703 self
704 }
705
706 pub fn where_eq_typed<C: TypedColumn>(mut self, value: Value) -> Self {
725 self.where_conditions
726 .push(WhereCondition::Eq(C::NAME.to_string(), value));
727 self
728 }
729
730 pub fn where_ne_typed<C: TypedColumn>(mut self, value: Value) -> Self {
732 self.where_conditions
733 .push(WhereCondition::Ne(C::NAME.to_string(), value));
734 self
735 }
736
737 pub fn where_gt_typed<C: TypedColumn>(mut self, value: Value) -> Self {
739 self.where_conditions
740 .push(WhereCondition::Gt(C::NAME.to_string(), value));
741 self
742 }
743
744 pub fn where_ge_typed<C: TypedColumn>(mut self, value: Value) -> Self {
746 self.where_conditions
747 .push(WhereCondition::Ge(C::NAME.to_string(), value));
748 self
749 }
750
751 pub fn where_lt_typed<C: TypedColumn>(mut self, value: Value) -> Self {
753 self.where_conditions
754 .push(WhereCondition::Lt(C::NAME.to_string(), value));
755 self
756 }
757
758 pub fn where_le_typed<C: TypedColumn>(mut self, value: Value) -> Self {
760 self.where_conditions
761 .push(WhereCondition::Le(C::NAME.to_string(), value));
762 self
763 }
764
765 pub fn where_null_typed<C: TypedColumn>(mut self) -> Self {
767 self.where_conditions
768 .push(WhereCondition::Null(C::NAME.to_string()));
769 self
770 }
771
772 pub fn where_not_null_typed<C: TypedColumn>(mut self) -> Self {
774 self.where_conditions
775 .push(WhereCondition::NotNull(C::NAME.to_string()));
776 self
777 }
778
779 pub fn order_by_typed<C: TypedColumn>(mut self) -> Self {
781 self.order_by.push(OrderClause {
782 field: C::NAME.to_string(),
783 direction: OrderDirection::Asc,
784 });
785 self
786 }
787
788 pub fn order_desc_typed<C: TypedColumn>(mut self) -> Self {
790 self.order_by.push(OrderClause {
791 field: C::NAME.to_string(),
792 direction: OrderDirection::Desc,
793 });
794 self
795 }
796
797 pub fn group_by_typed<C: TypedColumn>(mut self) -> Self {
799 self.group_by.push(C::NAME.to_string());
800 self
801 }
802
803 pub fn select_typed<C: TypedColumn>(mut self) -> Self {
807 self.select_columns.push(C::NAME.to_string());
808 self
809 }
810
811 pub fn select_typed_cols<C: TypedColumn, const N: usize>(mut self) -> Self {
813 self.select_columns.push(C::NAME.to_string());
816 self
817 }
818
819 pub fn having(mut self, condition: impl Into<String>) -> Self {
820 self.having_conditions
821 .push(WhereCondition::And(condition.into()));
822 self
823 }
824
825 pub fn limit(mut self, limit: usize) -> Self {
826 self.limit_value = Some(limit);
827 self
828 }
829
830 pub fn offset(mut self, offset: usize) -> Self {
831 self.offset_value = Some(offset);
832 self
833 }
834
835 pub fn page(mut self, page: usize, page_size: usize) -> Self {
836 self.limit_value = Some(page_size);
837 self.offset_value = Some((page.saturating_sub(1)) * page_size);
838 self
839 }
840
841 pub fn keyset_after(
887 mut self,
888 field: impl Into<String>,
889 cursor_value: Value,
890 page_size: usize,
891 ) -> Self {
892 let field_str = field.into();
893 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
895 existing.direction = OrderDirection::Asc;
896 } else {
897 self.order_by.push(OrderClause {
898 field: field_str.clone(),
899 direction: OrderDirection::Asc,
900 });
901 }
902 self.limit_value = Some(page_size);
903 self.offset_value = None;
905 self.keyset_cursor = Some(KeysetCursor {
906 field: field_str,
907 value: cursor_value,
908 direction: KeysetDirection::After,
909 });
910 self
911 }
912
913 pub fn keyset_before(
959 mut self,
960 field: impl Into<String>,
961 cursor_value: Value,
962 page_size: usize,
963 ) -> Self {
964 let field_str = field.into();
965 if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
967 existing.direction = OrderDirection::Desc;
968 } else {
969 self.order_by.push(OrderClause {
970 field: field_str.clone(),
971 direction: OrderDirection::Desc,
972 });
973 }
974 self.limit_value = Some(page_size);
975 self.offset_value = None;
976 self.keyset_cursor = Some(KeysetCursor {
977 field: field_str,
978 value: cursor_value,
979 direction: KeysetDirection::Before,
980 });
981 self
982 }
983
984 pub fn join_inner(
985 mut self,
986 table: impl Into<String>,
987 on_left: impl Into<String>,
988 on_right: impl Into<String>,
989 ) -> Self {
990 self.joins.push(JoinClause::Inner(
991 table.into(),
992 on_left.into(),
993 on_right.into(),
994 ));
995 self
996 }
997
998 pub fn join_left(
999 mut self,
1000 table: impl Into<String>,
1001 on_left: impl Into<String>,
1002 on_right: impl Into<String>,
1003 ) -> Self {
1004 self.joins.push(JoinClause::Left(
1005 table.into(),
1006 on_left.into(),
1007 on_right.into(),
1008 ));
1009 self
1010 }
1011
1012 pub fn join_right(
1013 mut self,
1014 table: impl Into<String>,
1015 on_left: impl Into<String>,
1016 on_right: impl Into<String>,
1017 ) -> Self {
1018 self.joins.push(JoinClause::Right(
1019 table.into(),
1020 on_left.into(),
1021 on_right.into(),
1022 ));
1023 self
1024 }
1025
1026 pub fn join(mut self, relation: &dyn crate::relation_trait::RelationTrait) -> Self {
1046 let def = relation.def();
1047 let join_kind = def.kind.default_join_type();
1048 self.joins.push(JoinClause::Relation(
1049 join_kind,
1050 def.from_entity.to_string(),
1051 def.from_key.to_string(),
1052 def.to_entity.to_string(),
1053 def.to_key.to_string(),
1054 ));
1055 self
1056 }
1057
1058 pub fn left_join(mut self, relation: &dyn crate::relation_trait::RelationTrait) -> Self {
1062 let def = relation.def();
1063 self.joins.push(JoinClause::Relation(
1064 crate::relation_trait::JoinKind::Left,
1065 def.from_entity.to_string(),
1066 def.from_key.to_string(),
1067 def.to_entity.to_string(),
1068 def.to_key.to_string(),
1069 ));
1070 self
1071 }
1072
1073 pub fn select_only(mut self) -> Self {
1089 self.select_mode = crate::partial_model::SelectMode::Partial;
1090 self.select_columns.clear();
1091 self
1092 }
1093
1094 pub fn column(mut self, column: impl Into<String>) -> Self {
1098 self.select_columns.push(column.into());
1099 self
1100 }
1101
1102 pub fn columns(mut self, cols: Vec<impl Into<String>>) -> Self {
1104 self.select_columns.extend(cols.into_iter().map(Into::into));
1105 self
1106 }
1107
1108 pub fn column_as(mut self, expr: crate::partial_model::Expr, alias: impl Into<String>) -> Self {
1124 self.select_columns.push(expr.render_as(&alias.into()));
1125 self
1126 }
1127
1128 #[tracing::instrument(skip(self), fields(op = "select"))]
1161 pub fn build_select(&self) -> String {
1162 let table = self
1163 .table
1164 .clone()
1165 .unwrap_or_else(|| M::table_name().to_string());
1166
1167 let columns = if self.select_columns.is_empty() {
1168 "*".to_string()
1169 } else {
1170 self.select_columns.join(", ")
1171 };
1172
1173 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1174
1175 for join in &self.joins {
1176 match join {
1177 JoinClause::Inner(t, l, r) => {
1178 sql.push_str(&format!(
1179 " INNER JOIN {} ON {} = {}",
1180 self.dialect.quote(t),
1181 self.dialect.quote(l),
1182 self.dialect.quote(r)
1183 ));
1184 }
1185 JoinClause::Left(t, l, r) => {
1186 sql.push_str(&format!(
1187 " LEFT JOIN {} ON {} = {}",
1188 self.dialect.quote(t),
1189 self.dialect.quote(l),
1190 self.dialect.quote(r)
1191 ));
1192 }
1193 JoinClause::Right(t, l, r) => {
1194 sql.push_str(&format!(
1195 " RIGHT JOIN {} ON {} = {}",
1196 self.dialect.quote(t),
1197 self.dialect.quote(l),
1198 self.dialect.quote(r)
1199 ));
1200 }
1201 JoinClause::Cross(t, on) => {
1202 sql.push_str(&format!(
1203 " CROSS JOIN {} ON {}",
1204 self.dialect.quote(t),
1205 self.dialect.quote(on)
1206 ));
1207 }
1208 JoinClause::Relation(kind, ft, fk, tt, tk) => {
1209 sql.push_str(&format!(
1210 " {} {} ON {}.{} = {}.{}",
1211 kind.as_sql(),
1212 self.dialect.quote(tt),
1213 self.dialect.quote(ft),
1214 self.dialect.quote(fk),
1215 self.dialect.quote(tt),
1216 self.dialect.quote(tk)
1217 ));
1218 }
1219 }
1220 }
1221
1222 let where_clause = self.build_where_clause();
1224 if !where_clause.is_empty() {
1225 sql.push_str(&where_clause);
1226 }
1227
1228 if !self.group_by.is_empty() {
1229 let cols: Vec<String> = self
1230 .group_by
1231 .iter()
1232 .map(|c| self.dialect.quote(c))
1233 .collect();
1234 sql.push_str(" GROUP BY ");
1235 sql.push_str(&cols.join(", "));
1236 }
1237
1238 if !self.having_conditions.is_empty() {
1239 sql.push_str(" HAVING ");
1240 for (i, cond) in self.having_conditions.iter().enumerate() {
1241 if i > 0 {
1242 sql.push_str(" AND ");
1243 }
1244 if let WhereCondition::And(c) = cond {
1245 sql.push_str(c);
1246 }
1247 }
1248 }
1249
1250 if !self.order_by.is_empty() {
1251 let order_cols: Vec<String> = self
1252 .order_by
1253 .iter()
1254 .map(|o| {
1255 let dir = match o.direction {
1256 OrderDirection::Asc => " ASC",
1257 OrderDirection::Desc => " DESC",
1258 };
1259 format!("{}{}", self.dialect.quote(&o.field), dir)
1260 })
1261 .collect();
1262 sql.push_str(" ORDER BY ");
1263 sql.push_str(&order_cols.join(", "));
1264 }
1265
1266 if let Some(limit) = self.limit_value {
1267 sql.push_str(&format!(" LIMIT {}", limit));
1268 }
1269
1270 if let Some(offset) = self.offset_value {
1271 sql.push_str(&format!(" OFFSET {}", offset));
1272 }
1273
1274 sql
1275 }
1276
1277 fn build_where_clause(&self) -> String {
1283 self.build_where_clause_with_options(true)
1284 }
1285
1286 fn build_where_clause_with_options(&self, include_soft_delete: bool) -> String {
1294 let soft_delete_cond = if include_soft_delete {
1296 self.build_soft_delete_condition()
1297 } else {
1298 None
1299 };
1300
1301 let tenant_cond = self.build_tenant_condition().map(|(sql, value)| {
1303 sql.replacen('?', &value.to_param_with_dialect(&*self.dialect), 1)
1305 });
1306
1307 if self.where_conditions.is_empty()
1309 && soft_delete_cond.is_none()
1310 && tenant_cond.is_none()
1311 && self.keyset_cursor.is_none()
1312 {
1313 return String::new();
1314 }
1315
1316 let mut conditions: Vec<String> = self
1318 .where_conditions
1319 .iter()
1320 .map(|cond| match cond {
1321 WhereCondition::And(c) => c.clone(),
1322 WhereCondition::Or(c) => format!("OR {}", c),
1323 WhereCondition::Eq(f, v) => format!(
1325 "{} = {}",
1326 self.dialect.quote(f),
1327 v.to_param_with_dialect(&*self.dialect)
1328 ),
1329 WhereCondition::Ne(f, v) => format!(
1330 "{} != {}",
1331 self.dialect.quote(f),
1332 v.to_param_with_dialect(&*self.dialect)
1333 ),
1334 WhereCondition::Gt(f, v) => format!(
1335 "{} > {}",
1336 self.dialect.quote(f),
1337 v.to_param_with_dialect(&*self.dialect)
1338 ),
1339 WhereCondition::Ge(f, v) => format!(
1340 "{} >= {}",
1341 self.dialect.quote(f),
1342 v.to_param_with_dialect(&*self.dialect)
1343 ),
1344 WhereCondition::Lt(f, v) => format!(
1345 "{} < {}",
1346 self.dialect.quote(f),
1347 v.to_param_with_dialect(&*self.dialect)
1348 ),
1349 WhereCondition::Le(f, v) => format!(
1350 "{} <= {}",
1351 self.dialect.quote(f),
1352 v.to_param_with_dialect(&*self.dialect)
1353 ),
1354 WhereCondition::Like(f, v) => format!(
1355 "{} LIKE {}",
1356 self.dialect.quote(f),
1357 v.to_param_with_dialect(&*self.dialect)
1358 ),
1359 WhereCondition::OrEq(f, v) => format!(
1360 "OR {} = {}",
1361 self.dialect.quote(f),
1362 v.to_param_with_dialect(&*self.dialect)
1363 ),
1364 WhereCondition::OrNe(f, v) => format!(
1365 "OR {} != {}",
1366 self.dialect.quote(f),
1367 v.to_param_with_dialect(&*self.dialect)
1368 ),
1369 WhereCondition::OrGt(f, v) => format!(
1370 "OR {} > {}",
1371 self.dialect.quote(f),
1372 v.to_param_with_dialect(&*self.dialect)
1373 ),
1374 WhereCondition::OrGe(f, v) => format!(
1375 "OR {} >= {}",
1376 self.dialect.quote(f),
1377 v.to_param_with_dialect(&*self.dialect)
1378 ),
1379 WhereCondition::OrLt(f, v) => format!(
1380 "OR {} < {}",
1381 self.dialect.quote(f),
1382 v.to_param_with_dialect(&*self.dialect)
1383 ),
1384 WhereCondition::OrLe(f, v) => format!(
1385 "OR {} <= {}",
1386 self.dialect.quote(f),
1387 v.to_param_with_dialect(&*self.dialect)
1388 ),
1389 WhereCondition::OrLike(f, v) => format!(
1390 "OR {} LIKE {}",
1391 self.dialect.quote(f),
1392 v.to_param_with_dialect(&*self.dialect)
1393 ),
1394 WhereCondition::In(f, vals) => {
1395 let vals_str: Vec<String> = vals
1397 .iter()
1398 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1399 .collect();
1400 format!("{} IN ({})", self.dialect.quote(f), vals_str.join(", "))
1401 }
1402 WhereCondition::NotIn(f, vals) => {
1403 let vals_str: Vec<String> = vals
1404 .iter()
1405 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1406 .collect();
1407 format!("{} NOT IN ({})", self.dialect.quote(f), vals_str.join(", "))
1408 }
1409 WhereCondition::Between(f, start, end) => {
1410 format!(
1411 "{} BETWEEN {} AND {}",
1412 self.dialect.quote(f),
1413 start.to_param_with_dialect(&*self.dialect),
1414 end.to_param_with_dialect(&*self.dialect)
1415 )
1416 }
1417 WhereCondition::NotBetween(f, start, end) => {
1418 format!(
1419 "{} NOT BETWEEN {} AND {}",
1420 self.dialect.quote(f),
1421 start.to_param_with_dialect(&*self.dialect),
1422 end.to_param_with_dialect(&*self.dialect)
1423 )
1424 }
1425 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1426 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1427 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1428 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1429 })
1430 .collect();
1431
1432 if let Some(sd_cond) = soft_delete_cond {
1434 conditions.push(sd_cond);
1435 }
1436
1437 if let Some(t_cond) = tenant_cond {
1439 conditions.push(t_cond);
1440 }
1441
1442 if let Some(ref cursor) = self.keyset_cursor {
1444 let op = match cursor.direction {
1445 KeysetDirection::After => ">",
1446 KeysetDirection::Before => "<",
1447 };
1448 conditions.push(format!(
1449 "{} {} {}",
1450 self.dialect.quote(&cursor.field),
1451 op,
1452 cursor.value.to_param_with_dialect(&*self.dialect)
1453 ));
1454 }
1455
1456 if conditions.is_empty() {
1457 return String::new();
1458 }
1459
1460 let mut groups: Vec<Vec<String>> = Vec::new();
1463 let mut current_group: Vec<String> = Vec::new();
1464 for cond in conditions.iter() {
1465 if let Some(stripped) = cond.strip_prefix("OR ") {
1466 current_group.push(stripped.to_string());
1468 } else {
1469 if !current_group.is_empty() {
1471 groups.push(std::mem::take(&mut current_group));
1472 }
1473 current_group.push(cond.clone());
1474 }
1475 }
1476 if !current_group.is_empty() {
1477 groups.push(current_group);
1478 }
1479
1480 let group_strs: Vec<String> = groups
1481 .iter()
1482 .map(|g| {
1483 if g.len() == 1 {
1484 g[0].clone()
1485 } else {
1486 format!("({})", g.join(" OR "))
1487 }
1488 })
1489 .collect();
1490
1491 format!(" WHERE {}", group_strs.join(" AND "))
1493 }
1494
1495 #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1496 pub fn build_insert(&self, data: &std::collections::HashMap<String, Value>) -> String {
1497 let table = self
1498 .table
1499 .clone()
1500 .unwrap_or_else(|| M::table_name().to_string());
1501
1502 if data.is_empty() {
1503 return String::new();
1504 }
1505
1506 let columns: Vec<String> = data.keys().map(|k| self.dialect.quote(k)).collect();
1507 let values: Vec<String> = data
1509 .values()
1510 .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1511 .collect();
1512
1513 format!(
1514 "INSERT INTO {} ({}) VALUES ({})",
1515 self.dialect.quote(&table),
1516 columns.join(", "),
1517 values.join(", ")
1518 )
1519 }
1520
1521 #[tracing::instrument(skip(self, data), fields(op = "update"))]
1522 pub fn build_update(&self, data: &std::collections::HashMap<String, Value>) -> String {
1523 let table = self
1524 .table
1525 .clone()
1526 .unwrap_or_else(|| M::table_name().to_string());
1527
1528 if data.is_empty() {
1529 return String::new();
1530 }
1531
1532 let set_clauses: Vec<String> = data
1533 .iter()
1534 .map(|(k, v)| {
1535 format!(
1536 "{} = {}",
1537 self.dialect.quote(k),
1538 v.to_param_with_dialect(&*self.dialect)
1539 )
1540 })
1541 .collect();
1542
1543 let mut sql = format!(
1544 "UPDATE {} SET {}",
1545 self.dialect.quote(&table),
1546 set_clauses.join(", ")
1547 );
1548
1549 sql.push_str(&self.build_where_clause());
1550 sql
1551 }
1552
1553 #[tracing::instrument(skip(self), fields(op = "delete"))]
1564 pub fn build_delete(&self) -> String {
1565 let table = self
1566 .table
1567 .clone()
1568 .unwrap_or_else(|| M::table_name().to_string());
1569
1570 if let Some(field) = self.soft_delete_field() {
1572 let where_clause = self.build_where_clause();
1573 return format!(
1574 "UPDATE {} SET {} = NOW(){}",
1575 self.dialect.quote(&table),
1576 self.dialect.quote(field),
1577 where_clause
1578 );
1579 }
1580
1581 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1582 sql.push_str(&self.build_where_clause());
1583 sql
1584 }
1585
1586 pub fn build_force_delete(&self) -> String {
1596 let table = self
1597 .table
1598 .clone()
1599 .unwrap_or_else(|| M::table_name().to_string());
1600
1601 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1602 sql.push_str(&self.build_where_clause_with_options(false));
1604 sql
1605 }
1606
1607 fn build_where_clause_with_params(&self) -> (String, Vec<Value>) {
1617 self.build_where_clause_with_params_options(true)
1619 }
1620
1621 fn build_where_clause_with_params_options(
1630 &self,
1631 include_soft_delete: bool,
1632 ) -> (String, Vec<Value>) {
1633 let soft_delete_cond = if include_soft_delete {
1635 self.build_soft_delete_condition()
1636 } else {
1637 None
1638 };
1639
1640 let tenant_cond = self.build_tenant_condition();
1642
1643 if self.where_conditions.is_empty()
1645 && soft_delete_cond.is_none()
1646 && tenant_cond.is_none()
1647 && self.keyset_cursor.is_none()
1648 {
1649 return (String::new(), Vec::new());
1650 }
1651
1652 let mut params = Vec::new();
1653
1654 let mut conditions: Vec<String> = self
1655 .where_conditions
1656 .iter()
1657 .map(|cond| match cond {
1658 WhereCondition::And(c) => c.clone(),
1659 WhereCondition::Or(c) => format!("OR {}", c),
1660 WhereCondition::Eq(f, v) => {
1662 params.push(v.clone());
1663 format!("{} = ?", self.dialect.quote(f))
1664 }
1665 WhereCondition::Ne(f, v) => {
1666 params.push(v.clone());
1667 format!("{} != ?", self.dialect.quote(f))
1668 }
1669 WhereCondition::Gt(f, v) => {
1670 params.push(v.clone());
1671 format!("{} > ?", self.dialect.quote(f))
1672 }
1673 WhereCondition::Ge(f, v) => {
1674 params.push(v.clone());
1675 format!("{} >= ?", self.dialect.quote(f))
1676 }
1677 WhereCondition::Lt(f, v) => {
1678 params.push(v.clone());
1679 format!("{} < ?", self.dialect.quote(f))
1680 }
1681 WhereCondition::Le(f, v) => {
1682 params.push(v.clone());
1683 format!("{} <= ?", self.dialect.quote(f))
1684 }
1685 WhereCondition::Like(f, v) => {
1686 params.push(v.clone());
1687 format!("{} LIKE ?", self.dialect.quote(f))
1688 }
1689 WhereCondition::OrEq(f, v) => {
1690 params.push(v.clone());
1691 format!("OR {} = ?", self.dialect.quote(f))
1692 }
1693 WhereCondition::OrNe(f, v) => {
1694 params.push(v.clone());
1695 format!("OR {} != ?", self.dialect.quote(f))
1696 }
1697 WhereCondition::OrGt(f, v) => {
1698 params.push(v.clone());
1699 format!("OR {} > ?", self.dialect.quote(f))
1700 }
1701 WhereCondition::OrGe(f, v) => {
1702 params.push(v.clone());
1703 format!("OR {} >= ?", self.dialect.quote(f))
1704 }
1705 WhereCondition::OrLt(f, v) => {
1706 params.push(v.clone());
1707 format!("OR {} < ?", self.dialect.quote(f))
1708 }
1709 WhereCondition::OrLe(f, v) => {
1710 params.push(v.clone());
1711 format!("OR {} <= ?", self.dialect.quote(f))
1712 }
1713 WhereCondition::OrLike(f, v) => {
1714 params.push(v.clone());
1715 format!("OR {} LIKE ?", self.dialect.quote(f))
1716 }
1717 WhereCondition::In(f, vals) => {
1718 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1719 params.extend(vals.iter().cloned());
1720 format!("{} IN ({})", self.dialect.quote(f), placeholders.join(", "))
1721 }
1722 WhereCondition::NotIn(f, vals) => {
1723 let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1724 params.extend(vals.iter().cloned());
1725 format!(
1726 "{} NOT IN ({})",
1727 self.dialect.quote(f),
1728 placeholders.join(", ")
1729 )
1730 }
1731 WhereCondition::Between(f, start, end) => {
1732 params.push(start.clone());
1733 params.push(end.clone());
1734 format!("{} BETWEEN ? AND ?", self.dialect.quote(f))
1735 }
1736 WhereCondition::NotBetween(f, start, end) => {
1737 params.push(start.clone());
1738 params.push(end.clone());
1739 format!("{} NOT BETWEEN ? AND ?", self.dialect.quote(f))
1740 }
1741 WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1742 WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1743 WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1744 WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1745 })
1746 .collect();
1747
1748 if let Some(sd_cond) = soft_delete_cond {
1750 conditions.push(sd_cond);
1751 }
1752
1753 if let Some((t_sql, t_value)) = tenant_cond {
1755 conditions.push(t_sql);
1756 params.push(t_value);
1757 }
1758
1759 if let Some(ref cursor) = self.keyset_cursor {
1761 let op = match cursor.direction {
1762 KeysetDirection::After => ">",
1763 KeysetDirection::Before => "<",
1764 };
1765 conditions.push(format!("{} {} ?", self.dialect.quote(&cursor.field), op));
1766 params.push(cursor.value.clone());
1767 }
1768
1769 if conditions.is_empty() {
1770 return (String::new(), params);
1771 }
1772
1773 let mut groups: Vec<Vec<String>> = Vec::new();
1775 let mut current_group: Vec<String> = Vec::new();
1776 for cond in conditions.iter() {
1777 if let Some(stripped) = cond.strip_prefix("OR ") {
1778 current_group.push(stripped.to_string());
1779 } else {
1780 if !current_group.is_empty() {
1781 groups.push(std::mem::take(&mut current_group));
1782 }
1783 current_group.push(cond.clone());
1784 }
1785 }
1786 if !current_group.is_empty() {
1787 groups.push(current_group);
1788 }
1789
1790 let group_strs: Vec<String> = groups
1791 .iter()
1792 .map(|g| {
1793 if g.len() == 1 {
1794 g[0].clone()
1795 } else {
1796 format!("({})", g.join(" OR "))
1797 }
1798 })
1799 .collect();
1800
1801 (format!(" WHERE {}", group_strs.join(" AND ")), params)
1803 }
1804
1805 pub fn build_select_with_params(&self) -> (String, Vec<Value>) {
1810 let table = self
1811 .table
1812 .clone()
1813 .unwrap_or_else(|| M::table_name().to_string());
1814 let columns = if self.select_columns.is_empty() {
1815 "*".to_string()
1816 } else {
1817 self.select_columns.join(", ")
1818 };
1819
1820 let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1821
1822 for join in &self.joins {
1823 match join {
1824 JoinClause::Inner(t, l, r) => {
1825 sql.push_str(&format!(
1826 " INNER JOIN {} ON {} = {}",
1827 self.dialect.quote(t),
1828 self.dialect.quote(l),
1829 self.dialect.quote(r)
1830 ));
1831 }
1832 JoinClause::Left(t, l, r) => {
1833 sql.push_str(&format!(
1834 " LEFT JOIN {} ON {} = {}",
1835 self.dialect.quote(t),
1836 self.dialect.quote(l),
1837 self.dialect.quote(r)
1838 ));
1839 }
1840 JoinClause::Right(t, l, r) => {
1841 sql.push_str(&format!(
1842 " RIGHT JOIN {} ON {} = {}",
1843 self.dialect.quote(t),
1844 self.dialect.quote(l),
1845 self.dialect.quote(r)
1846 ));
1847 }
1848 JoinClause::Cross(t, on) => {
1849 sql.push_str(&format!(
1850 " CROSS JOIN {} ON {}",
1851 self.dialect.quote(t),
1852 self.dialect.quote(on)
1853 ));
1854 }
1855 JoinClause::Relation(kind, ft, fk, tt, tk) => {
1856 sql.push_str(&format!(
1857 " {} {} ON {}.{} = {}.{}",
1858 kind.as_sql(),
1859 self.dialect.quote(tt),
1860 self.dialect.quote(ft),
1861 self.dialect.quote(fk),
1862 self.dialect.quote(tt),
1863 self.dialect.quote(tk)
1864 ));
1865 }
1866 }
1867 }
1868
1869 let mut params = Vec::new();
1870 let (where_clause, where_params) = self.build_where_clause_with_params();
1872 if !where_clause.is_empty() {
1873 sql.push_str(&where_clause);
1874 params = where_params;
1875 }
1876
1877 if !self.group_by.is_empty() {
1878 let cols: Vec<String> = self
1879 .group_by
1880 .iter()
1881 .map(|c| self.dialect.quote(c))
1882 .collect();
1883 sql.push_str(" GROUP BY ");
1884 sql.push_str(&cols.join(", "));
1885 }
1886
1887 if !self.having_conditions.is_empty() {
1888 sql.push_str(" HAVING ");
1889 for (i, cond) in self.having_conditions.iter().enumerate() {
1890 if i > 0 {
1891 sql.push_str(" AND ");
1892 }
1893 if let WhereCondition::And(c) = cond {
1894 sql.push_str(c);
1895 }
1896 }
1897 }
1898
1899 if !self.order_by.is_empty() {
1900 let order_cols: Vec<String> = self
1901 .order_by
1902 .iter()
1903 .map(|o| {
1904 let dir = match o.direction {
1905 OrderDirection::Asc => " ASC",
1906 OrderDirection::Desc => " DESC",
1907 };
1908 format!("{}{}", self.dialect.quote(&o.field), dir)
1909 })
1910 .collect();
1911 sql.push_str(" ORDER BY ");
1912 sql.push_str(&order_cols.join(", "));
1913 }
1914
1915 if let Some(limit) = self.limit_value {
1916 sql.push_str(&format!(" LIMIT {}", limit));
1917 }
1918 if let Some(offset) = self.offset_value {
1919 sql.push_str(&format!(" OFFSET {}", offset));
1920 }
1921
1922 if let Some(lock_type) = &self.lock_type {
1924 if let Some(lock_clause) = self.dialect.build_lock_clause(*lock_type) {
1925 sql.push(' ');
1926 sql.push_str(&lock_clause);
1927 }
1928 }
1929
1930 (sql, params)
1931 }
1932
1933 pub fn build_insert_with_params(
1935 &self,
1936 data: &std::collections::HashMap<String, Value>,
1937 ) -> (String, Vec<Value>) {
1938 let table = self
1939 .table
1940 .clone()
1941 .unwrap_or_else(|| M::table_name().to_string());
1942 if data.is_empty() {
1943 return (String::new(), Vec::new());
1944 }
1945
1946 let mut columns = Vec::with_capacity(data.len());
1947 let mut params = Vec::with_capacity(data.len());
1948 let placeholders: Vec<&str> = data.iter().map(|_| "?").collect();
1949 for (k, v) in data.iter() {
1950 columns.push(self.dialect.quote(k));
1951 params.push(v.clone());
1952 }
1953
1954 let insert_clause = if self.insert_or_ignore {
1956 self.dialect.build_insert_or_ignore_prefix(&table)
1957 } else {
1958 format!("INSERT INTO {}", self.dialect.quote(&table))
1959 };
1960
1961 let sql = format!(
1962 "{} ({}) VALUES ({})",
1963 insert_clause,
1964 columns.join(", "),
1965 placeholders.join(", ")
1966 );
1967 (sql, params)
1968 }
1969
1970 pub fn build_batch_insert_with_params(
1977 &self,
1978 rows: &[std::collections::HashMap<String, Value>],
1979 ) -> (String, Vec<Value>) {
1980 let table = self
1981 .table
1982 .clone()
1983 .unwrap_or_else(|| M::table_name().to_string());
1984 if rows.is_empty() {
1985 return (String::new(), Vec::new());
1986 }
1987
1988 let first_row = &rows[0];
1990 let columns: Vec<String> = first_row.keys().cloned().collect();
1991 let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1992
1993 let mut params = Vec::with_capacity(rows.len() * columns.len());
1994 let mut value_groups: Vec<String> = Vec::with_capacity(rows.len());
1995 let is_pg = self.dialect.db_type() == DbType::PostgreSQL;
1996 let mut param_idx = 1usize;
1997 for row in rows {
1998 let placeholders: Vec<String> = columns
1999 .iter()
2000 .map(|col| match row.get(col) {
2001 Some(v) => {
2002 params.push(v.clone());
2003 if is_pg {
2004 let p = format!("${}", param_idx);
2005 param_idx += 1;
2006 p
2007 } else {
2008 "?".to_string()
2009 }
2010 }
2011 None => "NULL".to_string(),
2012 })
2013 .collect();
2014 value_groups.push(format!("({})", placeholders.join(", ")));
2015 }
2016
2017 let sql = format!(
2018 "INSERT INTO {} ({}) VALUES {}",
2019 self.dialect.quote(&table),
2020 quoted_columns.join(", "),
2021 value_groups.join(", ")
2022 );
2023 (sql, params)
2024 }
2025
2026 pub fn build_batch_upsert_with_params(
2047 &self,
2048 rows: &[std::collections::HashMap<String, Value>],
2049 conflict_columns: &[&str],
2050 update_columns: &[&str],
2051 ) -> Result<(String, Vec<Value>), crate::DbError> {
2052 if rows.is_empty() {
2053 return Err(crate::DbError::InvalidInput(
2054 "build_batch_upsert_with_params: rows cannot be empty".to_string(),
2055 ));
2056 }
2057
2058 let (insert_sql, params) = self.build_batch_insert_with_params(rows);
2060 if insert_sql.is_empty() {
2061 return Err(crate::DbError::InvalidInput(
2062 "build_batch_upsert_with_params: failed to build INSERT part".to_string(),
2063 ));
2064 }
2065
2066 let all_columns: Vec<String> = rows[0].keys().cloned().collect();
2068
2069 let conflict_clause = self
2071 .dialect
2072 .build_upsert_on_conflict(conflict_columns, update_columns, &all_columns)
2073 .ok_or_else(|| {
2074 crate::DbError::InvalidInput(format!(
2075 "build_batch_upsert_with_params: dialect {:?} does not support upsert (ON CONFLICT / ON DUPLICATE KEY UPDATE). Consider using MERGE statement or individual upserts instead.",
2076 self.dialect.db_type()
2077 ))
2078 })?;
2079
2080 let sql = format!("{} {}", insert_sql, conflict_clause);
2081 Ok((sql, params))
2082 }
2083
2084 pub fn build_update_with_params(
2087 &self,
2088 data: &std::collections::HashMap<String, Value>,
2089 ) -> (String, Vec<Value>) {
2090 let table = self
2091 .table
2092 .clone()
2093 .unwrap_or_else(|| M::table_name().to_string());
2094 if data.is_empty() {
2095 return (String::new(), Vec::new());
2096 }
2097
2098 let mut set_clauses = Vec::with_capacity(data.len());
2099 let mut params = Vec::with_capacity(data.len());
2100 for (k, v) in data.iter() {
2101 set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
2102 params.push(v.clone());
2103 }
2104
2105 let mut sql = format!(
2106 "UPDATE {} SET {}",
2107 self.dialect.quote(&table),
2108 set_clauses.join(", ")
2109 );
2110
2111 let (where_clause, where_params) = self.build_where_clause_with_params();
2113 if !where_clause.is_empty() {
2114 sql.push_str(&where_clause);
2115 params.extend(where_params);
2116 }
2117
2118 (sql, params)
2119 }
2120
2121 pub fn build_delete_with_params(&self) -> (String, Vec<Value>) {
2127 let table = self
2128 .table
2129 .clone()
2130 .unwrap_or_else(|| M::table_name().to_string());
2131
2132 if let Some(field) = self.soft_delete_field() {
2134 let (where_clause, where_params) = self.build_where_clause_with_params();
2135 let sql = format!(
2136 "UPDATE {} SET {} = NOW(){}",
2137 self.dialect.quote(&table),
2138 self.dialect.quote(field),
2139 where_clause
2140 );
2141 return (sql, where_params);
2142 }
2143
2144 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
2145 let mut params = Vec::new();
2146
2147 let (where_clause, where_params) = self.build_where_clause_with_params();
2148 if !where_clause.is_empty() {
2149 sql.push_str(&where_clause);
2150 params = where_params;
2151 }
2152
2153 (sql, params)
2154 }
2155
2156 pub fn build_force_delete_with_params(&self) -> (String, Vec<Value>) {
2160 let table = self
2161 .table
2162 .clone()
2163 .unwrap_or_else(|| M::table_name().to_string());
2164
2165 let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
2166 let mut params = Vec::new();
2167
2168 let (where_clause, where_params) = self.build_where_clause_with_params_options(false);
2170 if !where_clause.is_empty() {
2171 sql.push_str(&where_clause);
2172 params = where_params;
2173 }
2174
2175 (sql, params)
2176 }
2177
2178 pub fn build_count(&self) -> String {
2179 let table = self
2180 .table
2181 .clone()
2182 .unwrap_or_else(|| M::table_name().to_string());
2183
2184 let mut sql = format!(
2185 "SELECT COUNT(*) as total FROM {}",
2186 self.dialect.quote(&table)
2187 );
2188 sql.push_str(&self.build_where_clause());
2189 sql
2190 }
2191
2192 pub fn build_exists(&self) -> String {
2193 let table = self
2194 .table
2195 .clone()
2196 .unwrap_or_else(|| M::table_name().to_string());
2197
2198 let mut sql = format!("SELECT 1 FROM {}", self.dialect.quote(&table));
2199 sql.push_str(&self.build_where_clause());
2200 sql.push_str(" LIMIT 1");
2201 format!("SELECT EXISTS({})", sql)
2202 }
2203
2204 pub fn build_max(&self, field: &str) -> String {
2205 let table = self
2206 .table
2207 .clone()
2208 .unwrap_or_else(|| M::table_name().to_string());
2209
2210 let mut sql = format!(
2211 "SELECT MAX({}) as max_val FROM {}",
2212 self.dialect.quote(field),
2213 self.dialect.quote(&table)
2214 );
2215 sql.push_str(&self.build_where_clause());
2216 sql
2217 }
2218
2219 pub fn build_min(&self, field: &str) -> String {
2220 let table = self
2221 .table
2222 .clone()
2223 .unwrap_or_else(|| M::table_name().to_string());
2224
2225 let mut sql = format!(
2226 "SELECT MIN({}) as min_val FROM {}",
2227 self.dialect.quote(field),
2228 self.dialect.quote(&table)
2229 );
2230 sql.push_str(&self.build_where_clause());
2231 sql
2232 }
2233
2234 pub fn build_sum(&self, field: &str) -> String {
2235 let table = self
2236 .table
2237 .clone()
2238 .unwrap_or_else(|| M::table_name().to_string());
2239
2240 let mut sql = format!(
2241 "SELECT SUM({}) as sum_val FROM {}",
2242 self.dialect.quote(field),
2243 self.dialect.quote(&table)
2244 );
2245 sql.push_str(&self.build_where_clause());
2246 sql
2247 }
2248
2249 pub fn build_avg(&self, field: &str) -> String {
2250 let table = self
2251 .table
2252 .clone()
2253 .unwrap_or_else(|| M::table_name().to_string());
2254
2255 let mut sql = format!(
2256 "SELECT AVG({}) as avg_val FROM {}",
2257 self.dialect.quote(field),
2258 self.dialect.quote(&table)
2259 );
2260 sql.push_str(&self.build_where_clause());
2261 sql
2262 }
2263
2264 pub fn validate(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2267 let sql = self.build_select();
2268 let mut errors = Vec::new();
2269
2270 if let Err(e) = sz_orm_sql_validator::validate_select(&sql) {
2271 errors.push(e);
2272 }
2273
2274 if !self.joins.is_empty() {
2276 for join in &self.joins {
2277 match join {
2278 JoinClause::Inner(_, left, right)
2279 | JoinClause::Left(_, left, right)
2280 | JoinClause::Right(_, left, right) => {
2281 if let Err(e) = sz_orm_sql_validator::validate_column_name(left) {
2282 errors.push(e);
2283 }
2284 if let Err(e) = sz_orm_sql_validator::validate_column_name(right) {
2285 errors.push(e);
2286 }
2287 }
2288 JoinClause::Relation(_, ft, fk, tt, tk) => {
2289 for ident in [ft.as_str(), fk.as_str(), tt.as_str(), tk.as_str()] {
2290 if let Err(e) = sz_orm_sql_validator::validate_column_name(ident) {
2291 errors.push(e);
2292 }
2293 }
2294 }
2295 _ => {}
2296 }
2297 }
2298 }
2299
2300 let table = self
2302 .table
2303 .clone()
2304 .unwrap_or_else(|| M::table_name().to_string());
2305 if let Err(e) = sz_orm_sql_validator::validate_table_name(&table) {
2306 errors.push(e);
2307 }
2308
2309 if errors.is_empty() {
2310 Ok(())
2311 } else {
2312 Err(errors)
2313 }
2314 }
2315
2316 pub fn validate_insert(
2319 &self,
2320 data: &std::collections::HashMap<String, Value>,
2321 ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2322 let sql = self.build_insert(data);
2323 let mut errors = Vec::new();
2324
2325 if sql.is_empty() {
2326 errors.push(sz_orm_sql_validator::SqlValidationError::EmptyInsertData);
2327 return Err(errors);
2328 }
2329
2330 if let Err(e) = sz_orm_sql_validator::validate_insert(&sql) {
2331 errors.push(e);
2332 }
2333
2334 if errors.is_empty() {
2335 Ok(())
2336 } else {
2337 Err(errors)
2338 }
2339 }
2340
2341 pub fn validate_update(
2344 &self,
2345 data: &std::collections::HashMap<String, Value>,
2346 ) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2347 let sql = self.build_update(data);
2348 let mut errors = Vec::new();
2349
2350 if sql.is_empty() {
2351 errors.push(sz_orm_sql_validator::SqlValidationError::EmptyUpdateData);
2352 return Err(errors);
2353 }
2354
2355 if let Err(e) = sz_orm_sql_validator::validate_update(&sql) {
2356 errors.push(e);
2357 }
2358
2359 if errors.is_empty() {
2360 Ok(())
2361 } else {
2362 Err(errors)
2363 }
2364 }
2365
2366 pub fn validate_delete(&self) -> Result<(), Vec<sz_orm_sql_validator::SqlValidationError>> {
2368 let sql = self.build_delete();
2369 let mut errors = Vec::new();
2370
2371 if let Err(e) = sz_orm_sql_validator::validate_delete(&sql) {
2372 errors.push(e);
2373 }
2374
2375 if errors.is_empty() {
2376 Ok(())
2377 } else {
2378 Err(errors)
2379 }
2380 }
2381}
2382
2383impl<M: Model + crate::model::ModelExt> QueryBuilder<M> {
2385 pub fn select_exclude(mut self, fields: &[&str]) -> Result<Self, crate::DbError> {
2403 let all_columns = M::columns();
2404 let exclude_set: std::collections::HashSet<&str> = fields.iter().copied().collect();
2405
2406 for field in fields {
2407 if !all_columns.contains(field) {
2408 return Err(crate::DbError::InvalidInput(format!(
2409 "排除的字段不存在: {}",
2410 field
2411 )));
2412 }
2413 }
2414
2415 let retained: Vec<String> = all_columns
2416 .into_iter()
2417 .filter(|c| !exclude_set.contains(*c))
2418 .map(|s| s.to_string())
2419 .collect();
2420
2421 if retained.is_empty() {
2422 return Err(crate::DbError::InvalidInput("不能排除所有字段".to_string()));
2423 }
2424
2425 self.select_mode = crate::partial_model::SelectMode::Partial;
2426 self.select_columns = retained;
2427 Ok(self)
2428 }
2429}
2430
2431impl<M: Model> fmt::Debug for QueryBuilder<M> {
2432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2433 f.debug_struct("QueryBuilder")
2434 .field("table", &self.table)
2435 .field("select_columns", &self.select_columns)
2436 .field("where_conditions", &self.where_conditions.len())
2437 .field("limit", &self.limit_value)
2438 .finish()
2439 }
2440}
2441
2442#[cfg(test)]
2443#[allow(deprecated)]
2444mod tests {
2445 use super::*;
2446 use crate::db_type::DbType;
2447 use crate::dialect::get_dialect;
2448
2449 struct TestModel;
2450 impl Model for TestModel {
2451 type PrimaryKey = i64;
2452
2453 fn table_name() -> &'static str {
2454 "test_models"
2455 }
2456
2457 fn pk(&self) -> Self::PrimaryKey {
2458 1
2459 }
2460
2461 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2462 }
2463
2464 #[test]
2465 fn test_query_builder_select() -> Result<(), crate::DbError> {
2466 let dialect = get_dialect(DbType::MySQL)?;
2467 let builder = QueryBuilder::<TestModel>::new(dialect);
2468
2469 let sql = builder
2470 .table("users")
2471 .select(vec!["id", "name"])
2472 .build_select();
2473 assert!(sql.contains("SELECT id, name FROM"));
2474 assert!(sql.contains("`users`"));
2475 Ok(())
2476 }
2477
2478 #[test]
2479 fn test_query_builder_where() -> Result<(), crate::DbError> {
2480 let dialect = get_dialect(DbType::MySQL)?;
2481 let builder = QueryBuilder::<TestModel>::new(dialect);
2482
2483 let sql = builder
2484 .table("users")
2485 .where_eq("status", crate::value::Value::String("active".into()))
2486 .where_gt("age", crate::value::Value::I64(18))
2487 .build_select();
2488
2489 assert!(sql.contains("WHERE"));
2490 assert!(sql.contains("`status` = 'active'"));
2491 assert!(sql.contains("`age` > 18"));
2492 Ok(())
2493 }
2494
2495 #[test]
2496 fn test_query_builder_order_by() -> Result<(), crate::DbError> {
2497 let dialect = get_dialect(DbType::MySQL)?;
2498 let builder = QueryBuilder::<TestModel>::new(dialect);
2499
2500 let sql = builder
2501 .table("users")
2502 .order_by("created_at")
2503 .order_desc("id")
2504 .build_select();
2505
2506 assert!(sql.contains("ORDER BY"));
2507 assert!(sql.contains("`created_at` ASC"));
2508 assert!(sql.contains("`id` DESC"));
2509 Ok(())
2510 }
2511
2512 #[test]
2513 fn test_query_builder_limit_offset() -> Result<(), crate::DbError> {
2514 let dialect = get_dialect(DbType::MySQL)?;
2515 let builder = QueryBuilder::<TestModel>::new(dialect);
2516
2517 let sql = builder.table("users").limit(10).offset(20).build_select();
2518
2519 assert!(sql.contains("LIMIT 10"));
2520 assert!(sql.contains("OFFSET 20"));
2521 Ok(())
2522 }
2523
2524 #[test]
2525 fn test_query_builder_page() -> Result<(), crate::DbError> {
2526 let dialect = get_dialect(DbType::MySQL)?;
2527 let builder = QueryBuilder::<TestModel>::new(dialect);
2528
2529 let sql = builder.table("users").page(3, 20).build_select();
2530
2531 assert!(sql.contains("LIMIT 20"));
2532 assert!(sql.contains("OFFSET 40"));
2533 Ok(())
2534 }
2535
2536 #[test]
2537 fn test_query_builder_insert() -> Result<(), crate::DbError> {
2538 let dialect = get_dialect(DbType::MySQL)?;
2539 let builder = QueryBuilder::<TestModel>::new(dialect);
2540
2541 let mut data = std::collections::HashMap::new();
2542 data.insert("name".to_string(), Value::String("test".to_string()));
2543 data.insert("age".to_string(), Value::I64(25));
2544
2545 let sql = builder.table("users").build_insert(&data);
2546
2547 assert!(sql.contains("INSERT INTO"));
2548 assert!(sql.contains("`name`"));
2549 assert!(sql.contains("'test'"));
2550 Ok(())
2551 }
2552
2553 #[test]
2554 fn test_query_builder_update() -> Result<(), crate::DbError> {
2555 let dialect = get_dialect(DbType::MySQL)?;
2556 let builder = QueryBuilder::<TestModel>::new(dialect);
2557
2558 let mut data = std::collections::HashMap::new();
2559 data.insert("name".to_string(), Value::String("updated".to_string()));
2560
2561 let sql = builder
2562 .table("users")
2563 .where_eq("id", Value::I64(1))
2564 .build_update(&data);
2565
2566 assert!(sql.contains("UPDATE"));
2567 assert!(sql.contains("`name` = 'updated'"));
2568 assert!(sql.contains("WHERE"));
2569 Ok(())
2570 }
2571
2572 #[test]
2573 fn test_query_builder_delete() -> Result<(), crate::DbError> {
2574 let dialect = get_dialect(DbType::MySQL)?;
2575 let builder = QueryBuilder::<TestModel>::new(dialect);
2576
2577 let sql = builder
2578 .table("users")
2579 .where_eq("id", Value::I64(1))
2580 .build_delete();
2581
2582 assert!(sql.contains("DELETE FROM"));
2583 assert!(sql.contains("WHERE"));
2584 Ok(())
2585 }
2586
2587 #[test]
2588 fn test_query_builder_count() -> Result<(), crate::DbError> {
2589 let dialect = get_dialect(DbType::MySQL)?;
2590 let builder = QueryBuilder::<TestModel>::new(dialect);
2591
2592 let sql = builder.table("users").build_count();
2593
2594 assert!(sql.contains("SELECT COUNT(*)"));
2595 assert!(sql.contains("FROM"));
2596 Ok(())
2597 }
2598
2599 #[test]
2600 fn test_query_builder_where_in() -> Result<(), crate::DbError> {
2601 let dialect = get_dialect(DbType::MySQL)?;
2602 let builder = QueryBuilder::<TestModel>::new(dialect);
2603
2604 let sql = builder
2605 .table("users")
2606 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2607 .build_select();
2608
2609 assert!(sql.contains("IN ("));
2610 Ok(())
2611 }
2612
2613 #[test]
2614 fn test_query_builder_where_between() -> Result<(), crate::DbError> {
2615 let dialect = get_dialect(DbType::MySQL)?;
2616 let builder = QueryBuilder::<TestModel>::new(dialect);
2617
2618 let sql = builder
2619 .table("users")
2620 .where_between("age", Value::I64(18), Value::I64(30))
2621 .build_select();
2622
2623 assert!(sql.contains("BETWEEN"));
2624 Ok(())
2625 }
2626
2627 #[test]
2628 fn test_query_builder_where_null() -> Result<(), crate::DbError> {
2629 let dialect = get_dialect(DbType::MySQL)?;
2630 let builder = QueryBuilder::<TestModel>::new(dialect);
2631
2632 let sql = builder
2633 .table("users")
2634 .where_null("deleted_at")
2635 .build_select();
2636
2637 assert!(sql.contains("IS NULL"));
2638 Ok(())
2639 }
2640
2641 #[test]
2642 fn test_query_builder_join() -> Result<(), crate::DbError> {
2643 let dialect = get_dialect(DbType::MySQL)?;
2644 let builder = QueryBuilder::<TestModel>::new(dialect);
2645
2646 let sql = builder
2647 .table("users")
2648 .join_inner("posts", "users.id", "posts.user_id")
2649 .build_select();
2650
2651 assert!(sql.contains("INNER JOIN"));
2652 assert!(sql.contains("`posts`"));
2653 Ok(())
2654 }
2655
2656 #[test]
2657 fn test_query_builder_group_by() -> Result<(), crate::DbError> {
2658 let dialect = get_dialect(DbType::MySQL)?;
2659 let builder = QueryBuilder::<TestModel>::new(dialect);
2660
2661 let sql = builder.table("users").group_by("status").build_select();
2662
2663 assert!(sql.contains("GROUP BY"));
2664 assert!(sql.contains("`status`"));
2665 Ok(())
2666 }
2667
2668 #[test]
2669 fn test_query_builder_max() -> Result<(), crate::DbError> {
2670 let dialect = get_dialect(DbType::MySQL)?;
2671 let builder = QueryBuilder::<TestModel>::new(dialect);
2672
2673 let sql = builder.table("users").build_max("score");
2674
2675 assert!(sql.contains("MAX("));
2676 assert!(sql.contains("`score`"));
2677 Ok(())
2678 }
2679
2680 #[test]
2681 fn test_query_builder_min() -> Result<(), crate::DbError> {
2682 let dialect = get_dialect(DbType::MySQL)?;
2683 let builder = QueryBuilder::<TestModel>::new(dialect);
2684
2685 let sql = builder.table("users").build_min("price");
2686
2687 assert!(sql.contains("MIN("));
2688 assert!(sql.contains("`price`"));
2689 Ok(())
2690 }
2691
2692 #[test]
2693 fn test_query_builder_sum() -> Result<(), crate::DbError> {
2694 let dialect = get_dialect(DbType::MySQL)?;
2695 let builder = QueryBuilder::<TestModel>::new(dialect);
2696
2697 let sql = builder.table("orders").build_sum("amount");
2698
2699 assert!(sql.contains("SUM("));
2700 assert!(sql.contains("`amount`"));
2701 Ok(())
2702 }
2703
2704 #[test]
2705 fn test_query_builder_avg() -> Result<(), crate::DbError> {
2706 let dialect = get_dialect(DbType::MySQL)?;
2707 let builder = QueryBuilder::<TestModel>::new(dialect);
2708
2709 let sql = builder.table("scores").build_avg("value");
2710
2711 assert!(sql.contains("AVG("));
2712 assert!(sql.contains("`value`"));
2713 Ok(())
2714 }
2715
2716 #[test]
2717 fn test_validator_select() -> Result<(), crate::DbError> {
2718 let dialect = get_dialect(DbType::MySQL)?;
2719 let builder = QueryBuilder::<TestModel>::new(dialect);
2720
2721 let result = builder.table("users").select(vec!["id", "name"]).validate();
2722 assert!(result.is_ok());
2723 Ok(())
2724 }
2725
2726 #[test]
2727 fn test_validator_select_with_join() -> Result<(), crate::DbError> {
2728 let dialect = get_dialect(DbType::MySQL)?;
2729 let builder = QueryBuilder::<TestModel>::new(dialect);
2730
2731 let result = builder
2732 .table("users")
2733 .join_inner("posts", "users.id", "posts.user_id")
2734 .validate();
2735 assert!(result.is_ok());
2736 Ok(())
2737 }
2738
2739 #[test]
2740 fn test_validator_insert() -> Result<(), crate::DbError> {
2741 let dialect = get_dialect(DbType::MySQL)?;
2742 let builder = QueryBuilder::<TestModel>::new(dialect);
2743
2744 let mut data = std::collections::HashMap::new();
2745 data.insert("name".to_string(), Value::String("test".to_string()));
2746
2747 let result = builder.table("users").validate_insert(&data);
2748 assert!(result.is_ok());
2749 Ok(())
2750 }
2751
2752 #[test]
2753 fn test_validator_insert_empty_data() -> Result<(), crate::DbError> {
2754 let dialect = get_dialect(DbType::MySQL)?;
2755 let builder = QueryBuilder::<TestModel>::new(dialect);
2756
2757 let data = std::collections::HashMap::new();
2758 let result = builder.table("users").validate_insert(&data);
2759 assert!(result.is_err());
2760 Ok(())
2761 }
2762
2763 #[test]
2764 fn test_validator_update() -> Result<(), crate::DbError> {
2765 let dialect = get_dialect(DbType::MySQL)?;
2766 let builder = QueryBuilder::<TestModel>::new(dialect);
2767
2768 let mut data = std::collections::HashMap::new();
2769 data.insert("name".to_string(), Value::String("updated".to_string()));
2770
2771 let result = builder.table("users").validate_update(&data);
2772 assert!(result.is_ok());
2773 Ok(())
2774 }
2775
2776 #[test]
2777 fn test_validator_update_empty_data() -> Result<(), crate::DbError> {
2778 let dialect = get_dialect(DbType::MySQL)?;
2779 let builder = QueryBuilder::<TestModel>::new(dialect);
2780
2781 let data = std::collections::HashMap::new();
2782 let result = builder.table("users").validate_update(&data);
2783 assert!(result.is_err());
2784 Ok(())
2785 }
2786
2787 #[test]
2788 fn test_validator_delete() -> Result<(), crate::DbError> {
2789 let dialect = get_dialect(DbType::MySQL)?;
2790 let builder = QueryBuilder::<TestModel>::new(dialect);
2791
2792 let result = builder
2793 .table("users")
2794 .where_eq("id", Value::I64(1))
2795 .validate_delete();
2796 assert!(result.is_ok());
2797 Ok(())
2798 }
2799
2800 #[test]
2801 fn test_validator_delete_no_where() -> Result<(), crate::DbError> {
2802 let dialect = get_dialect(DbType::MySQL)?;
2803 let builder = QueryBuilder::<TestModel>::new(dialect);
2804
2805 let result = builder.table("users").validate_delete();
2807 assert!(result.is_ok());
2808 Ok(())
2809 }
2810
2811 #[test]
2814 fn test_m3_select_quoted_valid_columns() -> Result<(), crate::DbError> {
2815 let dialect = get_dialect(DbType::MySQL)?;
2816 let builder = QueryBuilder::<TestModel>::new(dialect);
2817 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2818 let sql = builder.build_select();
2819 assert!(sql.contains("SELECT `id`, `name` FROM"));
2821 assert!(sql.contains("`users`"));
2822 Ok(())
2823 }
2824
2825 #[test]
2826 fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), crate::DbError> {
2827 let dialect = get_dialect(DbType::MySQL)?;
2828 let builder = QueryBuilder::<TestModel>::new(dialect);
2829
2830 let result = builder
2832 .table("users")
2833 .select_quoted(vec!["id; DROP TABLE users"]);
2834 assert!(result.is_err());
2835
2836 let dialect = get_dialect(DbType::MySQL)?;
2838 let builder = QueryBuilder::<TestModel>::new(dialect);
2839 let result = builder.table("users").select_quoted(vec!["name'"]);
2840 assert!(result.is_err());
2841
2842 let dialect = get_dialect(DbType::MySQL)?;
2844 let builder = QueryBuilder::<TestModel>::new(dialect);
2845 let result = builder.table("users").select_quoted(vec!["1col"]);
2846 assert!(result.is_err());
2847
2848 let dialect = get_dialect(DbType::MySQL)?;
2850 let builder = QueryBuilder::<TestModel>::new(dialect);
2851 let result = builder.table("users").select_quoted(vec!["col name"]);
2852 assert!(result.is_err());
2853 Ok(())
2854 }
2855
2856 #[test]
2857 fn test_m3_select_quoted_postgresql_dialect() -> Result<(), crate::DbError> {
2858 let dialect = get_dialect(DbType::PostgreSQL)?;
2859 let builder = QueryBuilder::<TestModel>::new(dialect);
2860 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2861 let sql = builder.build_select();
2862 assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2864 assert!(sql.contains("\"users\""));
2865 Ok(())
2866 }
2867
2868 struct SoftDeleteModel;
2872 impl Model for SoftDeleteModel {
2873 type PrimaryKey = i64;
2874
2875 fn table_name() -> &'static str {
2876 "soft_users"
2877 }
2878
2879 fn pk(&self) -> Self::PrimaryKey {
2880 1
2881 }
2882
2883 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2884
2885 fn soft_delete_field() -> Option<&'static str> {
2886 Some("deleted_at")
2887 }
2888 }
2889
2890 #[test]
2894 fn test_p01_soft_delete_select_auto_filter() -> Result<(), crate::DbError> {
2895 let dialect = get_dialect(DbType::MySQL)?;
2896 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2897 let sql = builder.table("soft_users").build_select();
2898 assert!(
2900 sql.contains("`deleted_at` IS NULL"),
2901 "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2902 sql
2903 );
2904 Ok(())
2905 }
2906
2907 #[test]
2909 fn test_p01_soft_delete_select_with_user_where() -> Result<(), crate::DbError> {
2910 let dialect = get_dialect(DbType::MySQL)?;
2911 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2912 .table("soft_users")
2913 .where_eq("status", Value::String("active".into()))
2914 .build_select();
2915 assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2917 assert!(
2918 sql.contains("`deleted_at` IS NULL"),
2919 "软删除条件应自动追加: {}",
2920 sql
2921 );
2922 Ok(())
2923 }
2924
2925 #[test]
2929 fn test_p01_soft_delete_without_soft_delete() -> Result<(), crate::DbError> {
2930 let dialect = get_dialect(DbType::MySQL)?;
2931 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2932 .table("soft_users")
2933 .without_soft_delete()
2934 .build_select();
2935 assert!(
2937 !sql.contains("`deleted_at` IS NULL"),
2938 "without_soft_delete 应禁用过滤,实际: {}",
2939 sql
2940 );
2941 assert!(
2943 !sql.contains("WHERE"),
2944 "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2945 sql
2946 );
2947 Ok(())
2948 }
2949
2950 #[test]
2954 fn test_p01_soft_delete_delete_becomes_update() -> Result<(), crate::DbError> {
2955 let dialect = get_dialect(DbType::MySQL)?;
2956 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2957 .table("soft_users")
2958 .where_eq("id", Value::I64(42))
2959 .build_delete();
2960 assert!(
2962 sql.starts_with("UPDATE"),
2963 "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2964 sql
2965 );
2966 assert!(
2967 !sql.contains("DELETE FROM"),
2968 "不应生成 DELETE FROM: {}",
2969 sql
2970 );
2971 assert!(
2972 sql.contains("`deleted_at` = NOW()"),
2973 "应设置 deleted_at = NOW(): {}",
2974 sql
2975 );
2976 assert!(
2978 sql.contains("`deleted_at` IS NULL"),
2979 "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2980 sql
2981 );
2982 Ok(())
2983 }
2984
2985 #[test]
2989 fn test_p01_soft_delete_force_delete() -> Result<(), crate::DbError> {
2990 let dialect = get_dialect(DbType::MySQL)?;
2991 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2992 .table("soft_users")
2993 .where_eq("id", Value::I64(99))
2994 .build_force_delete();
2995 assert!(
2997 sql.starts_with("DELETE FROM"),
2998 "build_force_delete 应生成 DELETE FROM,实际: {}",
2999 sql
3000 );
3001 assert!(
3003 !sql.contains("`deleted_at` IS NULL"),
3004 "物理删除不应追加软删除过滤: {}",
3005 sql
3006 );
3007 Ok(())
3008 }
3009
3010 #[test]
3012 fn test_p01_soft_delete_select_with_params() -> Result<(), crate::DbError> {
3013 let dialect = get_dialect(DbType::MySQL)?;
3014 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3015 .table("soft_users")
3016 .where_eq("id", Value::I64(1))
3017 .build_select_with_params();
3018 assert!(
3019 sql.contains("`deleted_at` IS NULL"),
3020 "参数化版本也应自动追加软删除: {}",
3021 sql
3022 );
3023 assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
3024 assert_eq!(params[0], Value::I64(1));
3025 Ok(())
3026 }
3027
3028 #[test]
3030 fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), crate::DbError> {
3031 let dialect = get_dialect(DbType::MySQL)?;
3032 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3033 .table("soft_users")
3034 .where_eq("id", Value::I64(7))
3035 .build_delete_with_params();
3036 assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
3037 assert!(
3038 sql.contains("`deleted_at` = NOW()"),
3039 "应设置 NOW(): {}",
3040 sql
3041 );
3042 assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
3043 Ok(())
3044 }
3045
3046 #[test]
3048 fn test_p01_soft_delete_force_delete_with_params() -> Result<(), crate::DbError> {
3049 let dialect = get_dialect(DbType::MySQL)?;
3050 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3051 .table("soft_users")
3052 .where_eq("id", Value::I64(11))
3053 .build_force_delete_with_params();
3054 assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
3055 assert!(
3056 !sql.contains("`deleted_at` IS NULL"),
3057 "不应追加软删除过滤: {}",
3058 sql
3059 );
3060 assert_eq!(params.len(), 1);
3061 Ok(())
3062 }
3063
3064 #[test]
3068 fn test_p01_non_soft_delete_model_unchanged() -> Result<(), crate::DbError> {
3069 let dialect = get_dialect(DbType::MySQL)?;
3070 let sql = QueryBuilder::<TestModel>::new(dialect)
3071 .table("users")
3072 .where_eq("id", Value::I64(1))
3073 .build_select();
3074 assert!(
3075 !sql.contains("deleted_at"),
3076 "非软删除模型不应追加 deleted_at: {}",
3077 sql
3078 );
3079 let dialect = get_dialect(DbType::MySQL)?;
3081 let del_sql = QueryBuilder::<TestModel>::new(dialect)
3082 .table("users")
3083 .where_eq("id", Value::I64(1))
3084 .build_delete();
3085 assert!(
3086 del_sql.starts_with("DELETE FROM"),
3087 "非软删除模型 build_delete 应生成 DELETE: {}",
3088 del_sql
3089 );
3090 Ok(())
3091 }
3092
3093 #[test]
3095 fn test_p01_soft_delete_count_auto_filter() -> Result<(), crate::DbError> {
3096 let dialect = get_dialect(DbType::MySQL)?;
3097 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
3098 .table("soft_users")
3099 .build_count();
3100 assert!(
3101 sql.contains("`deleted_at` IS NULL"),
3102 "build_count 也应追加软删除过滤: {}",
3103 sql
3104 );
3105 Ok(())
3106 }
3107
3108 #[test]
3114 fn test_p02_where_eq_uses_placeholder() -> Result<(), crate::DbError> {
3115 let dialect = get_dialect(DbType::MySQL)?;
3116 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3117 .table("users")
3118 .where_eq("name", Value::String("alice".into()))
3119 .build_select_with_params();
3120 assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
3122 assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
3123 assert_eq!(params.len(), 1);
3124 assert_eq!(params[0], Value::String("alice".into()));
3125 Ok(())
3126 }
3127
3128 #[test]
3130 fn test_p02_where_like_uses_placeholder() -> Result<(), crate::DbError> {
3131 let dialect = get_dialect(DbType::MySQL)?;
3132 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3133 .table("users")
3134 .where_like("name", Value::String("%alice%".into()))
3135 .build_select_with_params();
3136 assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
3137 assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
3138 assert_eq!(params.len(), 1);
3139 Ok(())
3140 }
3141
3142 #[test]
3146 fn test_p02_where_ne_uses_placeholder() -> Result<(), crate::DbError> {
3147 let dialect = get_dialect(DbType::MySQL)?;
3148 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3149 .table("users")
3150 .where_ne("status", Value::I64(0))
3151 .build_select_with_params();
3152 assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
3153 assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
3154 assert_eq!(params.len(), 1);
3155 assert_eq!(params[0], Value::I64(0));
3156 Ok(())
3157 }
3158
3159 #[test]
3163 fn test_p02_where_ge_uses_placeholder() -> Result<(), crate::DbError> {
3164 let dialect = get_dialect(DbType::MySQL)?;
3165 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3166 .table("users")
3167 .where_ge("age", Value::I64(18))
3168 .build_select_with_params();
3169 assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
3170 assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
3171 assert_eq!(params.len(), 1);
3172 assert_eq!(params[0], Value::I64(18));
3173 Ok(())
3174 }
3175
3176 #[test]
3180 fn test_p02_where_lt_uses_placeholder() -> Result<(), crate::DbError> {
3181 let dialect = get_dialect(DbType::MySQL)?;
3182 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3183 .table("users")
3184 .where_lt("score", Value::F64(60.0))
3185 .build_select_with_params();
3186 assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
3187 assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
3188 assert_eq!(params.len(), 1);
3189 assert_eq!(params[0], Value::F64(60.0));
3190 Ok(())
3191 }
3192
3193 #[test]
3197 fn test_p02_injection_protection_drop_table() -> Result<(), crate::DbError> {
3198 let dialect = get_dialect(DbType::MySQL)?;
3199 let evil_input = "'; DROP TABLE users; --".to_string();
3200 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3201 .table("users")
3202 .where_eq("name", Value::String(evil_input.clone()))
3203 .build_select_with_params();
3204 assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
3206 assert_eq!(params.len(), 1);
3208 assert_eq!(params[0], Value::String(evil_input));
3209 assert_eq!(sql.matches('?').count(), 1);
3211 Ok(())
3212 }
3213
3214 #[test]
3216 fn test_p02_injection_protection_or_one_equals_one() -> Result<(), crate::DbError> {
3217 let dialect = get_dialect(DbType::MySQL)?;
3218 let evil = "' OR '1'='1".to_string();
3219 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3220 .table("users")
3221 .where_eq("name", Value::String(evil.clone()))
3222 .build_select_with_params();
3223 assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
3224 assert_eq!(params.len(), 1);
3225 assert_eq!(params[0], Value::String(evil));
3226 Ok(())
3227 }
3228
3229 #[test]
3231 fn test_p02_multiple_params_order() -> Result<(), crate::DbError> {
3232 let dialect = get_dialect(DbType::MySQL)?;
3233 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3234 .table("users")
3235 .where_eq("name", Value::String("alice".into()))
3236 .where_gt("age", Value::I64(18))
3237 .where_le("score", Value::F64(99.5))
3238 .build_select_with_params();
3239 assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
3240 assert_eq!(params.len(), 3);
3241 assert_eq!(params[0], Value::String("alice".into()));
3243 assert_eq!(params[1], Value::I64(18));
3244 assert_eq!(params[2], Value::F64(99.5));
3245 Ok(())
3246 }
3247
3248 #[test]
3250 fn test_p02_where_in_uses_placeholders() -> Result<(), crate::DbError> {
3251 let dialect = get_dialect(DbType::MySQL)?;
3252 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3253 .table("users")
3254 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
3255 .build_select_with_params();
3256 assert!(
3257 sql.contains("`id` IN (?, ?, ?)"),
3258 "应使用 3 个占位符: {}",
3259 sql
3260 );
3261 assert_eq!(params.len(), 3);
3262 Ok(())
3263 }
3264
3265 #[test]
3267 fn test_p02_where_between_uses_placeholders() -> Result<(), crate::DbError> {
3268 let dialect = get_dialect(DbType::MySQL)?;
3269 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3270 .table("users")
3271 .where_between("age", Value::I64(18), Value::I64(65))
3272 .build_select_with_params();
3273 assert!(
3274 sql.contains("`age` BETWEEN ? AND ?"),
3275 "应使用 2 个占位符: {}",
3276 sql
3277 );
3278 assert_eq!(params.len(), 2);
3279 assert_eq!(params[0], Value::I64(18));
3280 assert_eq!(params[1], Value::I64(65));
3281 Ok(())
3282 }
3283
3284 #[test]
3286 fn test_p02_update_params_order_set_before_where() -> Result<(), crate::DbError> {
3287 let dialect = get_dialect(DbType::MySQL)?;
3288 let mut data = std::collections::HashMap::new();
3289 data.insert("name".to_string(), Value::String("bob".into()));
3290 data.insert("age".to_string(), Value::I64(30));
3291 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3292 .table("users")
3293 .where_eq("id", Value::I64(99))
3294 .build_update_with_params(&data);
3295 assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
3297 assert_eq!(params.len(), 3);
3298 assert_eq!(params[2], Value::I64(99));
3301 Ok(())
3302 }
3303
3304 #[test]
3308 fn test_p02_build_where_clause_inlines_value() -> Result<(), crate::DbError> {
3309 let dialect = get_dialect(DbType::MySQL)?;
3310 let sql = QueryBuilder::<TestModel>::new(dialect)
3311 .table("users")
3312 .where_eq("name", Value::String("alice".into()))
3313 .build_select();
3314 assert!(
3316 sql.contains("`name` = "),
3317 "无参数版本应含 WHERE 条件: {}",
3318 sql
3319 );
3320 assert!(
3322 !sql.contains("`name` = ?"),
3323 "无参数版本不应使用 ? 占位符: {}",
3324 sql
3325 );
3326 Ok(())
3327 }
3328
3329 #[test]
3331 fn test_p01_is_soft_delete_disabled_flag() -> Result<(), crate::DbError> {
3332 let dialect = get_dialect(DbType::MySQL)?;
3333 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
3334 assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
3335 let builder =
3336 QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
3337 assert!(
3338 builder.is_soft_delete_disabled(),
3339 "without_soft_delete 后应反映禁用状态"
3340 );
3341 Ok(())
3342 }
3343
3344 struct TenantModel;
3348 impl Model for TenantModel {
3349 type PrimaryKey = i64;
3350
3351 fn table_name() -> &'static str {
3352 "orders"
3353 }
3354
3355 fn pk(&self) -> Self::PrimaryKey {
3356 1
3357 }
3358
3359 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3360
3361 fn tenant_field() -> Option<&'static str> {
3362 Some("tenant_id")
3363 }
3364 }
3365
3366 struct SoftDeleteAndTenantModel;
3368 impl Model for SoftDeleteAndTenantModel {
3369 type PrimaryKey = i64;
3370
3371 fn table_name() -> &'static str {
3372 "documents"
3373 }
3374
3375 fn pk(&self) -> Self::PrimaryKey {
3376 1
3377 }
3378
3379 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3380
3381 fn soft_delete_field() -> Option<&'static str> {
3382 Some("deleted_at")
3383 }
3384
3385 fn tenant_field() -> Option<&'static str> {
3386 Some("tenant_id")
3387 }
3388 }
3389
3390 #[test]
3394 fn test_p03_tenant_select_auto_filter() -> Result<(), crate::DbError> {
3395 let dialect = get_dialect(DbType::MySQL)?;
3396 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3397 .table("orders")
3398 .with_tenant_id(42)
3399 .build_select_with_params();
3400 assert!(
3401 sql.contains("`tenant_id` = ?"),
3402 "多租户模型应自动追加 tenant_id = ?: {}",
3403 sql
3404 );
3405 assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
3406 assert_eq!(params[0], Value::I64(42));
3407 Ok(())
3408 }
3409
3410 #[test]
3412 fn test_p03_tenant_select_with_user_where() -> Result<(), crate::DbError> {
3413 let dialect = get_dialect(DbType::MySQL)?;
3414 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3415 .table("orders")
3416 .with_tenant_id(7)
3417 .where_eq("status", Value::String("active".into()))
3418 .build_select_with_params();
3419 assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
3420 assert!(
3421 sql.contains("`tenant_id` = ?"),
3422 "租户条件应自动追加: {}",
3423 sql
3424 );
3425 assert_eq!(params.len(), 2, "应有 2 个参数");
3426 assert_eq!(params[0], Value::String("active".into()));
3428 assert_eq!(params[1], Value::I64(7));
3429 Ok(())
3430 }
3431
3432 #[test]
3436 fn test_p03_tenant_without_tenant() -> Result<(), crate::DbError> {
3437 let dialect = get_dialect(DbType::MySQL)?;
3438 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3439 .table("orders")
3440 .with_tenant_id(42)
3441 .without_tenant()
3442 .build_select_with_params();
3443 assert!(
3444 !sql.contains("`tenant_id` = ?"),
3445 "without_tenant 应禁用过滤: {}",
3446 sql
3447 );
3448 assert_eq!(params.len(), 0, "不应有租户参数");
3449 Ok(())
3450 }
3451
3452 #[test]
3456 fn test_p03_tenant_delete_auto_filter() -> Result<(), crate::DbError> {
3457 let dialect = get_dialect(DbType::MySQL)?;
3458 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3459 .table("orders")
3460 .with_tenant_id(99)
3461 .where_eq("id", Value::I64(1))
3462 .build_delete_with_params();
3463 assert!(
3464 sql.contains("`tenant_id` = ?"),
3465 "删除应自动追加租户条件: {}",
3466 sql
3467 );
3468 assert_eq!(params.len(), 2);
3470 assert_eq!(params[0], Value::I64(1));
3471 assert_eq!(params[1], Value::I64(99));
3472 Ok(())
3473 }
3474
3475 #[test]
3477 fn test_p03_tenant_update_auto_filter() -> Result<(), crate::DbError> {
3478 let dialect = get_dialect(DbType::MySQL)?;
3479 let mut data = std::collections::HashMap::new();
3480 data.insert("status".to_string(), Value::String("shipped".into()));
3481 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3482 .table("orders")
3483 .with_tenant_id(5)
3484 .where_eq("id", Value::I64(10))
3485 .build_update_with_params(&data);
3486 assert!(
3487 sql.contains("`tenant_id` = ?"),
3488 "更新应自动追加租户条件: {}",
3489 sql
3490 );
3491 assert_eq!(params.len(), 3);
3493 assert_eq!(params[2], Value::I64(5));
3495 Ok(())
3496 }
3497
3498 #[test]
3500 fn test_p03_tenant_count_auto_filter() -> Result<(), crate::DbError> {
3501 let dialect = get_dialect(DbType::MySQL)?;
3502 let sql = QueryBuilder::<TenantModel>::new(dialect)
3503 .table("orders")
3504 .with_tenant_id(42)
3505 .build_count();
3506 assert!(
3507 sql.contains("`tenant_id` = 42"),
3508 "build_count 应追加租户条件(无参数版本内嵌值): {}",
3509 sql
3510 );
3511 Ok(())
3512 }
3513
3514 #[test]
3518 fn test_p03_non_tenant_model_unchanged() -> Result<(), crate::DbError> {
3519 let dialect = get_dialect(DbType::MySQL)?;
3520 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3522 .table("users")
3523 .with_tenant_id(42)
3524 .build_select_with_params();
3525 assert!(
3526 !sql.contains("tenant_id"),
3527 "非多租户模型不应追加 tenant_id: {}",
3528 sql
3529 );
3530 assert_eq!(params.len(), 0);
3531 Ok(())
3532 }
3533
3534 #[test]
3538 fn test_p03_tenant_no_id_no_filter() -> Result<(), crate::DbError> {
3539 let dialect = get_dialect(DbType::MySQL)?;
3540 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3541 .table("orders")
3542 .build_select_with_params();
3543 assert!(
3544 !sql.contains("tenant_id"),
3545 "未设置 tenant_id 时不应追加过滤: {}",
3546 sql
3547 );
3548 assert_eq!(params.len(), 0);
3549 Ok(())
3550 }
3551
3552 #[test]
3556 fn test_p03_soft_delete_and_tenant_combined() -> Result<(), crate::DbError> {
3557 let dialect = get_dialect(DbType::MySQL)?;
3558 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3559 .table("documents")
3560 .with_tenant_id(100)
3561 .where_eq("title", Value::String("report".into()))
3562 .build_select_with_params();
3563 assert!(
3565 sql.contains("`deleted_at` IS NULL"),
3566 "应追加软删除条件: {}",
3567 sql
3568 );
3569 assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3571 assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3573 assert_eq!(params.len(), 2);
3575 assert_eq!(params[0], Value::String("report".into()));
3576 assert_eq!(params[1], Value::I64(100));
3577 Ok(())
3578 }
3579
3580 #[test]
3582 fn test_p03_without_tenant_and_soft_delete() -> Result<(), crate::DbError> {
3583 let dialect = get_dialect(DbType::MySQL)?;
3584 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3585 .table("documents")
3586 .with_tenant_id(100)
3587 .without_tenant()
3588 .without_soft_delete()
3589 .build_select_with_params();
3590 assert!(
3591 !sql.contains("`deleted_at` IS NULL"),
3592 "应禁用软删除: {}",
3593 sql
3594 );
3595 assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
3596 assert_eq!(params.len(), 0);
3597 Ok(())
3598 }
3599
3600 #[test]
3602 fn test_p03_is_tenant_disabled_flag() -> Result<(), crate::DbError> {
3603 let dialect = get_dialect(DbType::MySQL)?;
3604 let builder = QueryBuilder::<TenantModel>::new(dialect);
3605 assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
3606 let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
3607 .with_tenant_id(1)
3608 .without_tenant();
3609 assert!(
3610 builder.is_tenant_disabled(),
3611 "without_tenant 后应反映禁用状态"
3612 );
3613 Ok(())
3614 }
3615
3616 #[test]
3620 fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), crate::DbError> {
3621 let dialect = get_dialect(DbType::MySQL)?;
3622 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3623 .table("orders")
3624 .with_tenant_id(42)
3625 .where_eq("id", Value::I64(999))
3626 .build_force_delete_with_params();
3627 assert!(
3630 sql.contains("`tenant_id` = ?"),
3631 "物理删除应保留租户条件: {}",
3632 sql
3633 );
3634 assert_eq!(params.len(), 2);
3635 assert_eq!(params[0], Value::I64(999));
3636 assert_eq!(params[1], Value::I64(42));
3637 Ok(())
3638 }
3639
3640 struct TcUsersTable;
3643 impl crate::typed::TypedTable for TcUsersTable {
3644 const NAME: &'static str = "users";
3645 }
3646 struct TcColId;
3647 impl crate::typed::TypedColumn for TcColId {
3648 const NAME: &'static str = "id";
3649 type Table = TcUsersTable;
3650 type RustType = i64;
3651 type SqlType = crate::typed_ast::Untyped;
3652 }
3653 struct TcColName;
3654 impl crate::typed::TypedColumn for TcColName {
3655 const NAME: &'static str = "name";
3656 type Table = TcUsersTable;
3657 type RustType = String;
3658 type SqlType = crate::typed_ast::Untyped;
3659 }
3660
3661 #[test]
3662 fn test_where_eq_typed() -> Result<(), crate::DbError> {
3663 let dialect = get_dialect(DbType::MySQL)?;
3664 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3665 .where_eq_typed::<TcColId>(Value::I64(42))
3666 .build_select_with_params();
3667 assert!(sql.contains("`id` = ?"));
3668 assert_eq!(params[0], Value::I64(42));
3669 Ok(())
3670 }
3671
3672 #[test]
3673 fn test_order_by_typed() -> Result<(), crate::DbError> {
3674 let dialect = get_dialect(DbType::MySQL)?;
3675 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3676 .order_by_typed::<TcColName>()
3677 .build_select_with_params();
3678 assert!(sql.contains("ORDER BY"));
3679 assert!(sql.contains("`name`"));
3680 Ok(())
3681 }
3682
3683 #[test]
3684 fn test_select_typed() -> Result<(), crate::DbError> {
3685 let dialect = get_dialect(DbType::MySQL)?;
3686 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3687 .select_typed::<TcColId>()
3688 .select_typed::<TcColName>()
3689 .build_select_with_params();
3690 assert!(sql.contains("SELECT"));
3691 assert!(sql.contains("id"));
3693 assert!(sql.contains("name"));
3694 Ok(())
3695 }
3696
3697 #[test]
3698 fn test_where_null_typed() -> Result<(), crate::DbError> {
3699 let dialect = get_dialect(DbType::MySQL)?;
3700 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3701 .where_null_typed::<TcColName>()
3702 .build_select_with_params();
3703 assert!(sql.contains("`name` IS NULL"));
3704 Ok(())
3705 }
3706
3707 #[test]
3708 fn test_where_not_null_typed() -> Result<(), crate::DbError> {
3709 let dialect = get_dialect(DbType::MySQL)?;
3710 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3711 .where_not_null_typed::<TcColName>()
3712 .build_select_with_params();
3713 assert!(sql.contains("`name` IS NOT NULL"));
3714 Ok(())
3715 }
3716
3717 #[test]
3718 fn test_group_by_typed() -> Result<(), crate::DbError> {
3719 let dialect = get_dialect(DbType::MySQL)?;
3720 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3721 .group_by_typed::<TcColName>()
3722 .build_select_with_params();
3723 assert!(sql.contains("GROUP BY"));
3724 assert!(sql.contains("`name`"));
3725 Ok(())
3726 }
3727
3728 #[test]
3729 fn test_where_gt_typed() -> Result<(), crate::DbError> {
3730 let dialect = get_dialect(DbType::MySQL)?;
3731 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3732 .where_gt_typed::<TcColId>(Value::I64(10))
3733 .build_select_with_params();
3734 assert!(sql.contains("`id` > ?"));
3735 assert_eq!(params[0], Value::I64(10));
3736 Ok(())
3737 }
3738
3739 #[test]
3742 fn test_lock_for_update_mysql() -> Result<(), crate::DbError> {
3743 let dialect = get_dialect(DbType::MySQL)?;
3744 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3745 .table("users")
3746 .where_eq("id", Value::I64(1))
3747 .lock_for_update()?
3748 .build_select_with_params();
3749 assert!(sql.contains("SELECT * FROM `users`"));
3750 assert!(sql.contains("WHERE `id` = ?"));
3751 assert!(sql.contains("FOR UPDATE"));
3752 assert_eq!(params.len(), 1);
3753 assert_eq!(params[0], Value::I64(1));
3754 Ok(())
3755 }
3756
3757 #[test]
3758 fn test_lock_shared_mysql() -> Result<(), crate::DbError> {
3759 let dialect = get_dialect(DbType::MySQL)?;
3760 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3761 .table("users")
3762 .where_eq("id", Value::I64(1))
3763 .lock_shared()?
3764 .build_select_with_params();
3765 assert!(sql.contains("SELECT * FROM `users`"));
3766 assert!(sql.contains("WHERE `id` = ?"));
3767 assert!(sql.contains("LOCK IN SHARE MODE"));
3768 assert_eq!(params.len(), 1);
3769 assert_eq!(params[0], Value::I64(1));
3770 Ok(())
3771 }
3772
3773 #[test]
3774 fn test_lock_for_update_postgresql() -> Result<(), crate::DbError> {
3775 let dialect = get_dialect(DbType::PostgreSQL)?;
3776 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3777 .table("users")
3778 .where_eq("id", Value::I64(1))
3779 .lock_for_update()?
3780 .build_select_with_params();
3781 assert!(sql.contains("SELECT * FROM \"users\""));
3782 assert!(sql.contains("WHERE \"id\" = ?"));
3783 assert!(sql.contains("FOR UPDATE"));
3784 assert_eq!(params.len(), 1);
3785 assert_eq!(params[0], Value::I64(1));
3786 Ok(())
3787 }
3788
3789 #[test]
3790 fn test_lock_shared_postgresql() -> Result<(), crate::DbError> {
3791 let dialect = get_dialect(DbType::PostgreSQL)?;
3792 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3793 .table("users")
3794 .where_eq("id", Value::I64(1))
3795 .lock_shared()?
3796 .build_select_with_params();
3797 assert!(sql.contains("SELECT * FROM \"users\""));
3798 assert!(sql.contains("WHERE \"id\" = ?"));
3799 assert!(sql.contains("FOR SHARE"));
3800 assert_eq!(params.len(), 1);
3801 assert_eq!(params[0], Value::I64(1));
3802 Ok(())
3803 }
3804
3805 #[test]
3806 fn test_lock_for_update_sqlite_should_fail() {
3807 let dialect = get_dialect(DbType::Sqlite).unwrap();
3808 let result = QueryBuilder::<TestModel>::new(dialect)
3809 .table("users")
3810 .where_eq("id", Value::I64(1))
3811 .lock_for_update();
3812 assert!(result.is_err(), "SQLite 不应支持 FOR UPDATE 锁");
3813 let err = result.err().unwrap();
3814 assert!(
3815 format!("{:?}", err).contains("FOR UPDATE lock is not supported"),
3816 "错误信息应说明不支持行锁"
3817 );
3818 }
3819
3820 #[test]
3821 fn test_lock_shared_sqlite_should_fail() {
3822 let dialect = get_dialect(DbType::Sqlite).unwrap();
3823 let result = QueryBuilder::<TestModel>::new(dialect)
3824 .table("users")
3825 .where_eq("id", Value::I64(1))
3826 .lock_shared();
3827 assert!(result.is_err(), "SQLite 不应支持共享锁");
3828 let err = result.err().unwrap();
3829 assert!(
3830 format!("{:?}", err).contains("Shared lock is not supported"),
3831 "错误信息应说明不支持共享锁"
3832 );
3833 }
3834
3835 #[test]
3836 fn test_lock_with_limit_and_offset() -> Result<(), crate::DbError> {
3837 let dialect = get_dialect(DbType::MySQL)?;
3838 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3839 .table("users")
3840 .where_eq("status", Value::String("active".into()))
3841 .limit(10)
3842 .offset(20)
3843 .lock_for_update()?
3844 .build_select_with_params();
3845 assert!(sql.contains("WHERE `status` = ?"));
3846 assert!(sql.contains("LIMIT 10"));
3847 assert!(sql.contains("OFFSET 20"));
3848 assert!(sql.contains("FOR UPDATE"));
3849 assert_eq!(params.len(), 1);
3850 Ok(())
3851 }
3852
3853 #[test]
3856 fn test_insert_or_ignore_mysql() -> Result<(), crate::DbError> {
3857 let dialect = get_dialect(DbType::MySQL)?;
3858 let mut data = std::collections::HashMap::new();
3859 data.insert("name".to_string(), Value::String("Alice".into()));
3860 data.insert("age".to_string(), Value::I64(30));
3861
3862 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3863 .table("users")
3864 .insert_or_ignore()
3865 .build_insert_with_params(&data);
3866 assert!(sql.contains("INSERT IGNORE INTO `users`"));
3867 assert!(sql.contains("`name`"), "SQL 应包含 name 列: {}", sql);
3869 assert!(sql.contains("`age`"), "SQL 应包含 age 列: {}", sql);
3870 assert!(sql.contains("VALUES (?, ?)"));
3871 assert_eq!(params.len(), 2);
3872 Ok(())
3873 }
3874
3875 #[test]
3876 fn test_insert_or_ignore_postgresql() -> Result<(), crate::DbError> {
3877 let dialect = get_dialect(DbType::PostgreSQL)?;
3878 let mut data = std::collections::HashMap::new();
3879 data.insert("name".to_string(), Value::String("Bob".into()));
3880
3881 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3882 .table("users")
3883 .insert_or_ignore()
3884 .build_insert_with_params(&data);
3885 assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3886 assert!(sql.contains("(\"name\")"));
3887 assert!(sql.contains("VALUES (?)"));
3888 assert_eq!(params.len(), 1);
3889 assert_eq!(params[0], Value::String("Bob".into()));
3890 Ok(())
3891 }
3892
3893 #[test]
3894 fn test_insert_or_ignore_sqlite() -> Result<(), crate::DbError> {
3895 let dialect = get_dialect(DbType::Sqlite)?;
3896 let mut data = std::collections::HashMap::new();
3897 data.insert("name".to_string(), Value::String("Charlie".into()));
3898
3899 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3900 .table("users")
3901 .insert_or_ignore()
3902 .build_insert_with_params(&data);
3903 assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3904 assert!(sql.contains("(\"name\")"));
3905 assert!(sql.contains("VALUES (?)"));
3906 assert_eq!(params.len(), 1);
3907 assert_eq!(params[0], Value::String("Charlie".into()));
3908 Ok(())
3909 }
3910
3911 #[test]
3912 fn test_insert_normal_without_ignore() -> Result<(), crate::DbError> {
3913 let dialect = get_dialect(DbType::MySQL)?;
3914 let mut data = std::collections::HashMap::new();
3915 data.insert("name".to_string(), Value::String("Dave".into()));
3916
3917 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3918 .table("users")
3919 .build_insert_with_params(&data);
3920 assert!(sql.contains("INSERT INTO `users`"));
3921 assert!(!sql.contains("IGNORE"), "普通插入不应包含 IGNORE");
3922 assert_eq!(params.len(), 1);
3923 assert_eq!(params[0], Value::String("Dave".into()));
3924 Ok(())
3925 }
3926
3927 #[test]
3928 fn test_insert_or_ignore_empty_data() -> Result<(), crate::DbError> {
3929 let dialect = get_dialect(DbType::MySQL)?;
3930 let data = std::collections::HashMap::new();
3931
3932 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3933 .table("users")
3934 .insert_or_ignore()
3935 .build_insert_with_params(&data);
3936 assert!(sql.is_empty(), "空数据应返回空 SQL");
3937 assert!(params.is_empty());
3938 Ok(())
3939 }
3940
3941 #[test]
3944 fn test_dialect_supports_lock_for_update() -> Result<(), crate::DbError> {
3945 let mysql = get_dialect(DbType::MySQL)?;
3946 let pg = get_dialect(DbType::PostgreSQL)?;
3947 let sqlite = get_dialect(DbType::Sqlite)?;
3948 let clickhouse = get_dialect(DbType::ClickHouse)?;
3949 let duckdb = get_dialect(DbType::DuckDB)?;
3950
3951 assert!(mysql.supports_lock_for_update(), "MySQL 应支持 FOR UPDATE");
3952 assert!(
3953 pg.supports_lock_for_update(),
3954 "PostgreSQL 应支持 FOR UPDATE"
3955 );
3956 assert!(
3957 !sqlite.supports_lock_for_update(),
3958 "SQLite 不应支持 FOR UPDATE"
3959 );
3960 assert!(
3961 !clickhouse.supports_lock_for_update(),
3962 "ClickHouse 是列式 OLAP,不应支持 FOR UPDATE"
3963 );
3964 assert!(
3965 !duckdb.supports_lock_for_update(),
3966 "DuckDB 不应支持 FOR UPDATE"
3967 );
3968 Ok(())
3969 }
3970
3971 #[test]
3972 fn test_dialect_supports_lock_shared() -> Result<(), crate::DbError> {
3973 let mysql = get_dialect(DbType::MySQL)?;
3974 let pg = get_dialect(DbType::PostgreSQL)?;
3975 let sqlite = get_dialect(DbType::Sqlite)?;
3976 let clickhouse = get_dialect(DbType::ClickHouse)?;
3977 let duckdb = get_dialect(DbType::DuckDB)?;
3978
3979 assert!(mysql.supports_lock_shared(), "MySQL 应支持共享锁");
3980 assert!(pg.supports_lock_shared(), "PostgreSQL 应支持共享锁");
3981 assert!(!sqlite.supports_lock_shared(), "SQLite 不应支持共享锁");
3982 assert!(
3983 !clickhouse.supports_lock_shared(),
3984 "ClickHouse 是列式 OLAP,不应支持共享锁"
3985 );
3986 assert!(!duckdb.supports_lock_shared(), "DuckDB 不应支持共享锁");
3987 Ok(())
3988 }
3989
3990 #[test]
3991 fn test_get_lock_type_and_is_insert_or_ignore() -> Result<(), crate::DbError> {
3992 let dialect = get_dialect(DbType::MySQL)?;
3993
3994 let builder = QueryBuilder::<TestModel>::new(dialect);
3996 assert!(builder.get_lock_type().is_none(), "默认无锁");
3997 assert!(!builder.is_insert_or_ignore(), "默认不忽略插入");
3998
3999 let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
4001 .table("users")
4002 .lock_for_update()?;
4003 assert_eq!(builder.get_lock_type(), Some(LockType::ForUpdate));
4004
4005 let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
4007 .table("users")
4008 .insert_or_ignore();
4009 assert!(builder.is_insert_or_ignore());
4010
4011 Ok(())
4012 }
4013
4014 struct TestModelWithColumns;
4015 impl Model for TestModelWithColumns {
4016 type PrimaryKey = i64;
4017 fn table_name() -> &'static str {
4018 "test_with_cols"
4019 }
4020 fn pk(&self) -> Self::PrimaryKey {
4021 0
4022 }
4023 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
4024 }
4025 impl crate::model::ModelExt for TestModelWithColumns {
4026 fn columns() -> Vec<&'static str> {
4027 vec!["id", "name", "email", "avatar", "blob_data"]
4028 }
4029 fn fillable() -> Vec<&'static str> {
4030 vec!["name", "email", "avatar", "blob_data"]
4031 }
4032 fn guarded() -> Vec<&'static str> {
4033 vec!["id"]
4034 }
4035 fn hidden() -> Vec<&'static str> {
4036 vec!["blob_data"]
4037 }
4038 fn relations() -> std::collections::HashMap<&'static str, crate::model::Relation> {
4039 std::collections::HashMap::new()
4040 }
4041 fn fill(&mut self, _data: std::collections::HashMap<String, crate::value::Value>) {}
4042 fn to_json(&self) -> serde_json::Value {
4043 serde_json::Value::Null
4044 }
4045 }
4046
4047 #[test]
4048 fn test_select_exclude_basic() -> Result<(), crate::DbError> {
4049 let dialect = get_dialect(DbType::MySQL)?;
4050 let builder = QueryBuilder::<TestModelWithColumns>::new(dialect)
4051 .table("users")
4052 .select_exclude(&["avatar", "blob_data"])?;
4053 let sql = builder.build_select();
4054 assert!(sql.contains("id"));
4055 assert!(sql.contains("name"));
4056 assert!(sql.contains("email"));
4057 assert!(!sql.contains("avatar"));
4058 assert!(!sql.contains("blob_data"));
4059 Ok(())
4060 }
4061
4062 #[test]
4063 fn test_select_exclude_nonexistent_field() {
4064 let dialect = get_dialect(DbType::MySQL).unwrap();
4065 let result = QueryBuilder::<TestModelWithColumns>::new(dialect)
4066 .table("users")
4067 .select_exclude(&["nonexistent"]);
4068 assert!(result.is_err());
4069 }
4070
4071 #[test]
4072 fn test_select_exclude_all_fields() {
4073 let dialect = get_dialect(DbType::MySQL).unwrap();
4074 let result = QueryBuilder::<TestModelWithColumns>::new(dialect)
4075 .table("users")
4076 .select_exclude(&["id", "name", "email", "avatar", "blob_data"]);
4077 assert!(result.is_err());
4078 let err = result.unwrap_err();
4079 assert!(matches!(err, crate::DbError::InvalidInput(_)));
4080 }
4081}