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> fmt::Debug for QueryBuilder<M> {
2384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2385 f.debug_struct("QueryBuilder")
2386 .field("table", &self.table)
2387 .field("select_columns", &self.select_columns)
2388 .field("where_conditions", &self.where_conditions.len())
2389 .field("limit", &self.limit_value)
2390 .finish()
2391 }
2392}
2393
2394#[cfg(test)]
2395#[allow(deprecated)]
2396mod tests {
2397 use super::*;
2398 use crate::db_type::DbType;
2399 use crate::dialect::get_dialect;
2400
2401 struct TestModel;
2402 impl Model for TestModel {
2403 type PrimaryKey = i64;
2404
2405 fn table_name() -> &'static str {
2406 "test_models"
2407 }
2408
2409 fn pk(&self) -> Self::PrimaryKey {
2410 1
2411 }
2412
2413 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2414 }
2415
2416 #[test]
2417 fn test_query_builder_select() -> Result<(), crate::DbError> {
2418 let dialect = get_dialect(DbType::MySQL)?;
2419 let builder = QueryBuilder::<TestModel>::new(dialect);
2420
2421 let sql = builder
2422 .table("users")
2423 .select(vec!["id", "name"])
2424 .build_select();
2425 assert!(sql.contains("SELECT id, name FROM"));
2426 assert!(sql.contains("`users`"));
2427 Ok(())
2428 }
2429
2430 #[test]
2431 fn test_query_builder_where() -> Result<(), crate::DbError> {
2432 let dialect = get_dialect(DbType::MySQL)?;
2433 let builder = QueryBuilder::<TestModel>::new(dialect);
2434
2435 let sql = builder
2436 .table("users")
2437 .where_eq("status", crate::value::Value::String("active".into()))
2438 .where_gt("age", crate::value::Value::I64(18))
2439 .build_select();
2440
2441 assert!(sql.contains("WHERE"));
2442 assert!(sql.contains("`status` = 'active'"));
2443 assert!(sql.contains("`age` > 18"));
2444 Ok(())
2445 }
2446
2447 #[test]
2448 fn test_query_builder_order_by() -> Result<(), crate::DbError> {
2449 let dialect = get_dialect(DbType::MySQL)?;
2450 let builder = QueryBuilder::<TestModel>::new(dialect);
2451
2452 let sql = builder
2453 .table("users")
2454 .order_by("created_at")
2455 .order_desc("id")
2456 .build_select();
2457
2458 assert!(sql.contains("ORDER BY"));
2459 assert!(sql.contains("`created_at` ASC"));
2460 assert!(sql.contains("`id` DESC"));
2461 Ok(())
2462 }
2463
2464 #[test]
2465 fn test_query_builder_limit_offset() -> Result<(), crate::DbError> {
2466 let dialect = get_dialect(DbType::MySQL)?;
2467 let builder = QueryBuilder::<TestModel>::new(dialect);
2468
2469 let sql = builder.table("users").limit(10).offset(20).build_select();
2470
2471 assert!(sql.contains("LIMIT 10"));
2472 assert!(sql.contains("OFFSET 20"));
2473 Ok(())
2474 }
2475
2476 #[test]
2477 fn test_query_builder_page() -> Result<(), crate::DbError> {
2478 let dialect = get_dialect(DbType::MySQL)?;
2479 let builder = QueryBuilder::<TestModel>::new(dialect);
2480
2481 let sql = builder.table("users").page(3, 20).build_select();
2482
2483 assert!(sql.contains("LIMIT 20"));
2484 assert!(sql.contains("OFFSET 40"));
2485 Ok(())
2486 }
2487
2488 #[test]
2489 fn test_query_builder_insert() -> Result<(), crate::DbError> {
2490 let dialect = get_dialect(DbType::MySQL)?;
2491 let builder = QueryBuilder::<TestModel>::new(dialect);
2492
2493 let mut data = std::collections::HashMap::new();
2494 data.insert("name".to_string(), Value::String("test".to_string()));
2495 data.insert("age".to_string(), Value::I64(25));
2496
2497 let sql = builder.table("users").build_insert(&data);
2498
2499 assert!(sql.contains("INSERT INTO"));
2500 assert!(sql.contains("`name`"));
2501 assert!(sql.contains("'test'"));
2502 Ok(())
2503 }
2504
2505 #[test]
2506 fn test_query_builder_update() -> Result<(), crate::DbError> {
2507 let dialect = get_dialect(DbType::MySQL)?;
2508 let builder = QueryBuilder::<TestModel>::new(dialect);
2509
2510 let mut data = std::collections::HashMap::new();
2511 data.insert("name".to_string(), Value::String("updated".to_string()));
2512
2513 let sql = builder
2514 .table("users")
2515 .where_eq("id", Value::I64(1))
2516 .build_update(&data);
2517
2518 assert!(sql.contains("UPDATE"));
2519 assert!(sql.contains("`name` = 'updated'"));
2520 assert!(sql.contains("WHERE"));
2521 Ok(())
2522 }
2523
2524 #[test]
2525 fn test_query_builder_delete() -> Result<(), crate::DbError> {
2526 let dialect = get_dialect(DbType::MySQL)?;
2527 let builder = QueryBuilder::<TestModel>::new(dialect);
2528
2529 let sql = builder
2530 .table("users")
2531 .where_eq("id", Value::I64(1))
2532 .build_delete();
2533
2534 assert!(sql.contains("DELETE FROM"));
2535 assert!(sql.contains("WHERE"));
2536 Ok(())
2537 }
2538
2539 #[test]
2540 fn test_query_builder_count() -> Result<(), crate::DbError> {
2541 let dialect = get_dialect(DbType::MySQL)?;
2542 let builder = QueryBuilder::<TestModel>::new(dialect);
2543
2544 let sql = builder.table("users").build_count();
2545
2546 assert!(sql.contains("SELECT COUNT(*)"));
2547 assert!(sql.contains("FROM"));
2548 Ok(())
2549 }
2550
2551 #[test]
2552 fn test_query_builder_where_in() -> Result<(), crate::DbError> {
2553 let dialect = get_dialect(DbType::MySQL)?;
2554 let builder = QueryBuilder::<TestModel>::new(dialect);
2555
2556 let sql = builder
2557 .table("users")
2558 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2559 .build_select();
2560
2561 assert!(sql.contains("IN ("));
2562 Ok(())
2563 }
2564
2565 #[test]
2566 fn test_query_builder_where_between() -> Result<(), crate::DbError> {
2567 let dialect = get_dialect(DbType::MySQL)?;
2568 let builder = QueryBuilder::<TestModel>::new(dialect);
2569
2570 let sql = builder
2571 .table("users")
2572 .where_between("age", Value::I64(18), Value::I64(30))
2573 .build_select();
2574
2575 assert!(sql.contains("BETWEEN"));
2576 Ok(())
2577 }
2578
2579 #[test]
2580 fn test_query_builder_where_null() -> Result<(), crate::DbError> {
2581 let dialect = get_dialect(DbType::MySQL)?;
2582 let builder = QueryBuilder::<TestModel>::new(dialect);
2583
2584 let sql = builder
2585 .table("users")
2586 .where_null("deleted_at")
2587 .build_select();
2588
2589 assert!(sql.contains("IS NULL"));
2590 Ok(())
2591 }
2592
2593 #[test]
2594 fn test_query_builder_join() -> Result<(), crate::DbError> {
2595 let dialect = get_dialect(DbType::MySQL)?;
2596 let builder = QueryBuilder::<TestModel>::new(dialect);
2597
2598 let sql = builder
2599 .table("users")
2600 .join_inner("posts", "users.id", "posts.user_id")
2601 .build_select();
2602
2603 assert!(sql.contains("INNER JOIN"));
2604 assert!(sql.contains("`posts`"));
2605 Ok(())
2606 }
2607
2608 #[test]
2609 fn test_query_builder_group_by() -> Result<(), crate::DbError> {
2610 let dialect = get_dialect(DbType::MySQL)?;
2611 let builder = QueryBuilder::<TestModel>::new(dialect);
2612
2613 let sql = builder.table("users").group_by("status").build_select();
2614
2615 assert!(sql.contains("GROUP BY"));
2616 assert!(sql.contains("`status`"));
2617 Ok(())
2618 }
2619
2620 #[test]
2621 fn test_query_builder_max() -> Result<(), crate::DbError> {
2622 let dialect = get_dialect(DbType::MySQL)?;
2623 let builder = QueryBuilder::<TestModel>::new(dialect);
2624
2625 let sql = builder.table("users").build_max("score");
2626
2627 assert!(sql.contains("MAX("));
2628 assert!(sql.contains("`score`"));
2629 Ok(())
2630 }
2631
2632 #[test]
2633 fn test_query_builder_min() -> Result<(), crate::DbError> {
2634 let dialect = get_dialect(DbType::MySQL)?;
2635 let builder = QueryBuilder::<TestModel>::new(dialect);
2636
2637 let sql = builder.table("users").build_min("price");
2638
2639 assert!(sql.contains("MIN("));
2640 assert!(sql.contains("`price`"));
2641 Ok(())
2642 }
2643
2644 #[test]
2645 fn test_query_builder_sum() -> Result<(), crate::DbError> {
2646 let dialect = get_dialect(DbType::MySQL)?;
2647 let builder = QueryBuilder::<TestModel>::new(dialect);
2648
2649 let sql = builder.table("orders").build_sum("amount");
2650
2651 assert!(sql.contains("SUM("));
2652 assert!(sql.contains("`amount`"));
2653 Ok(())
2654 }
2655
2656 #[test]
2657 fn test_query_builder_avg() -> Result<(), crate::DbError> {
2658 let dialect = get_dialect(DbType::MySQL)?;
2659 let builder = QueryBuilder::<TestModel>::new(dialect);
2660
2661 let sql = builder.table("scores").build_avg("value");
2662
2663 assert!(sql.contains("AVG("));
2664 assert!(sql.contains("`value`"));
2665 Ok(())
2666 }
2667
2668 #[test]
2669 fn test_validator_select() -> Result<(), crate::DbError> {
2670 let dialect = get_dialect(DbType::MySQL)?;
2671 let builder = QueryBuilder::<TestModel>::new(dialect);
2672
2673 let result = builder.table("users").select(vec!["id", "name"]).validate();
2674 assert!(result.is_ok());
2675 Ok(())
2676 }
2677
2678 #[test]
2679 fn test_validator_select_with_join() -> Result<(), crate::DbError> {
2680 let dialect = get_dialect(DbType::MySQL)?;
2681 let builder = QueryBuilder::<TestModel>::new(dialect);
2682
2683 let result = builder
2684 .table("users")
2685 .join_inner("posts", "users.id", "posts.user_id")
2686 .validate();
2687 assert!(result.is_ok());
2688 Ok(())
2689 }
2690
2691 #[test]
2692 fn test_validator_insert() -> Result<(), crate::DbError> {
2693 let dialect = get_dialect(DbType::MySQL)?;
2694 let builder = QueryBuilder::<TestModel>::new(dialect);
2695
2696 let mut data = std::collections::HashMap::new();
2697 data.insert("name".to_string(), Value::String("test".to_string()));
2698
2699 let result = builder.table("users").validate_insert(&data);
2700 assert!(result.is_ok());
2701 Ok(())
2702 }
2703
2704 #[test]
2705 fn test_validator_insert_empty_data() -> Result<(), crate::DbError> {
2706 let dialect = get_dialect(DbType::MySQL)?;
2707 let builder = QueryBuilder::<TestModel>::new(dialect);
2708
2709 let data = std::collections::HashMap::new();
2710 let result = builder.table("users").validate_insert(&data);
2711 assert!(result.is_err());
2712 Ok(())
2713 }
2714
2715 #[test]
2716 fn test_validator_update() -> Result<(), crate::DbError> {
2717 let dialect = get_dialect(DbType::MySQL)?;
2718 let builder = QueryBuilder::<TestModel>::new(dialect);
2719
2720 let mut data = std::collections::HashMap::new();
2721 data.insert("name".to_string(), Value::String("updated".to_string()));
2722
2723 let result = builder.table("users").validate_update(&data);
2724 assert!(result.is_ok());
2725 Ok(())
2726 }
2727
2728 #[test]
2729 fn test_validator_update_empty_data() -> Result<(), crate::DbError> {
2730 let dialect = get_dialect(DbType::MySQL)?;
2731 let builder = QueryBuilder::<TestModel>::new(dialect);
2732
2733 let data = std::collections::HashMap::new();
2734 let result = builder.table("users").validate_update(&data);
2735 assert!(result.is_err());
2736 Ok(())
2737 }
2738
2739 #[test]
2740 fn test_validator_delete() -> Result<(), crate::DbError> {
2741 let dialect = get_dialect(DbType::MySQL)?;
2742 let builder = QueryBuilder::<TestModel>::new(dialect);
2743
2744 let result = builder
2745 .table("users")
2746 .where_eq("id", Value::I64(1))
2747 .validate_delete();
2748 assert!(result.is_ok());
2749 Ok(())
2750 }
2751
2752 #[test]
2753 fn test_validator_delete_no_where() -> Result<(), crate::DbError> {
2754 let dialect = get_dialect(DbType::MySQL)?;
2755 let builder = QueryBuilder::<TestModel>::new(dialect);
2756
2757 let result = builder.table("users").validate_delete();
2759 assert!(result.is_ok());
2760 Ok(())
2761 }
2762
2763 #[test]
2766 fn test_m3_select_quoted_valid_columns() -> Result<(), crate::DbError> {
2767 let dialect = get_dialect(DbType::MySQL)?;
2768 let builder = QueryBuilder::<TestModel>::new(dialect);
2769 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2770 let sql = builder.build_select();
2771 assert!(sql.contains("SELECT `id`, `name` FROM"));
2773 assert!(sql.contains("`users`"));
2774 Ok(())
2775 }
2776
2777 #[test]
2778 fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), crate::DbError> {
2779 let dialect = get_dialect(DbType::MySQL)?;
2780 let builder = QueryBuilder::<TestModel>::new(dialect);
2781
2782 let result = builder
2784 .table("users")
2785 .select_quoted(vec!["id; DROP TABLE users"]);
2786 assert!(result.is_err());
2787
2788 let dialect = get_dialect(DbType::MySQL)?;
2790 let builder = QueryBuilder::<TestModel>::new(dialect);
2791 let result = builder.table("users").select_quoted(vec!["name'"]);
2792 assert!(result.is_err());
2793
2794 let dialect = get_dialect(DbType::MySQL)?;
2796 let builder = QueryBuilder::<TestModel>::new(dialect);
2797 let result = builder.table("users").select_quoted(vec!["1col"]);
2798 assert!(result.is_err());
2799
2800 let dialect = get_dialect(DbType::MySQL)?;
2802 let builder = QueryBuilder::<TestModel>::new(dialect);
2803 let result = builder.table("users").select_quoted(vec!["col name"]);
2804 assert!(result.is_err());
2805 Ok(())
2806 }
2807
2808 #[test]
2809 fn test_m3_select_quoted_postgresql_dialect() -> Result<(), crate::DbError> {
2810 let dialect = get_dialect(DbType::PostgreSQL)?;
2811 let builder = QueryBuilder::<TestModel>::new(dialect);
2812 let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2813 let sql = builder.build_select();
2814 assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2816 assert!(sql.contains("\"users\""));
2817 Ok(())
2818 }
2819
2820 struct SoftDeleteModel;
2824 impl Model for SoftDeleteModel {
2825 type PrimaryKey = i64;
2826
2827 fn table_name() -> &'static str {
2828 "soft_users"
2829 }
2830
2831 fn pk(&self) -> Self::PrimaryKey {
2832 1
2833 }
2834
2835 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2836
2837 fn soft_delete_field() -> Option<&'static str> {
2838 Some("deleted_at")
2839 }
2840 }
2841
2842 #[test]
2846 fn test_p01_soft_delete_select_auto_filter() -> Result<(), crate::DbError> {
2847 let dialect = get_dialect(DbType::MySQL)?;
2848 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2849 let sql = builder.table("soft_users").build_select();
2850 assert!(
2852 sql.contains("`deleted_at` IS NULL"),
2853 "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2854 sql
2855 );
2856 Ok(())
2857 }
2858
2859 #[test]
2861 fn test_p01_soft_delete_select_with_user_where() -> Result<(), crate::DbError> {
2862 let dialect = get_dialect(DbType::MySQL)?;
2863 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2864 .table("soft_users")
2865 .where_eq("status", Value::String("active".into()))
2866 .build_select();
2867 assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2869 assert!(
2870 sql.contains("`deleted_at` IS NULL"),
2871 "软删除条件应自动追加: {}",
2872 sql
2873 );
2874 Ok(())
2875 }
2876
2877 #[test]
2881 fn test_p01_soft_delete_without_soft_delete() -> Result<(), crate::DbError> {
2882 let dialect = get_dialect(DbType::MySQL)?;
2883 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2884 .table("soft_users")
2885 .without_soft_delete()
2886 .build_select();
2887 assert!(
2889 !sql.contains("`deleted_at` IS NULL"),
2890 "without_soft_delete 应禁用过滤,实际: {}",
2891 sql
2892 );
2893 assert!(
2895 !sql.contains("WHERE"),
2896 "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2897 sql
2898 );
2899 Ok(())
2900 }
2901
2902 #[test]
2906 fn test_p01_soft_delete_delete_becomes_update() -> Result<(), crate::DbError> {
2907 let dialect = get_dialect(DbType::MySQL)?;
2908 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2909 .table("soft_users")
2910 .where_eq("id", Value::I64(42))
2911 .build_delete();
2912 assert!(
2914 sql.starts_with("UPDATE"),
2915 "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2916 sql
2917 );
2918 assert!(
2919 !sql.contains("DELETE FROM"),
2920 "不应生成 DELETE FROM: {}",
2921 sql
2922 );
2923 assert!(
2924 sql.contains("`deleted_at` = NOW()"),
2925 "应设置 deleted_at = NOW(): {}",
2926 sql
2927 );
2928 assert!(
2930 sql.contains("`deleted_at` IS NULL"),
2931 "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2932 sql
2933 );
2934 Ok(())
2935 }
2936
2937 #[test]
2941 fn test_p01_soft_delete_force_delete() -> Result<(), crate::DbError> {
2942 let dialect = get_dialect(DbType::MySQL)?;
2943 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2944 .table("soft_users")
2945 .where_eq("id", Value::I64(99))
2946 .build_force_delete();
2947 assert!(
2949 sql.starts_with("DELETE FROM"),
2950 "build_force_delete 应生成 DELETE FROM,实际: {}",
2951 sql
2952 );
2953 assert!(
2955 !sql.contains("`deleted_at` IS NULL"),
2956 "物理删除不应追加软删除过滤: {}",
2957 sql
2958 );
2959 Ok(())
2960 }
2961
2962 #[test]
2964 fn test_p01_soft_delete_select_with_params() -> Result<(), crate::DbError> {
2965 let dialect = get_dialect(DbType::MySQL)?;
2966 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2967 .table("soft_users")
2968 .where_eq("id", Value::I64(1))
2969 .build_select_with_params();
2970 assert!(
2971 sql.contains("`deleted_at` IS NULL"),
2972 "参数化版本也应自动追加软删除: {}",
2973 sql
2974 );
2975 assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
2976 assert_eq!(params[0], Value::I64(1));
2977 Ok(())
2978 }
2979
2980 #[test]
2982 fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), crate::DbError> {
2983 let dialect = get_dialect(DbType::MySQL)?;
2984 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2985 .table("soft_users")
2986 .where_eq("id", Value::I64(7))
2987 .build_delete_with_params();
2988 assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
2989 assert!(
2990 sql.contains("`deleted_at` = NOW()"),
2991 "应设置 NOW(): {}",
2992 sql
2993 );
2994 assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
2995 Ok(())
2996 }
2997
2998 #[test]
3000 fn test_p01_soft_delete_force_delete_with_params() -> Result<(), crate::DbError> {
3001 let dialect = get_dialect(DbType::MySQL)?;
3002 let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
3003 .table("soft_users")
3004 .where_eq("id", Value::I64(11))
3005 .build_force_delete_with_params();
3006 assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
3007 assert!(
3008 !sql.contains("`deleted_at` IS NULL"),
3009 "不应追加软删除过滤: {}",
3010 sql
3011 );
3012 assert_eq!(params.len(), 1);
3013 Ok(())
3014 }
3015
3016 #[test]
3020 fn test_p01_non_soft_delete_model_unchanged() -> Result<(), crate::DbError> {
3021 let dialect = get_dialect(DbType::MySQL)?;
3022 let sql = QueryBuilder::<TestModel>::new(dialect)
3023 .table("users")
3024 .where_eq("id", Value::I64(1))
3025 .build_select();
3026 assert!(
3027 !sql.contains("deleted_at"),
3028 "非软删除模型不应追加 deleted_at: {}",
3029 sql
3030 );
3031 let dialect = get_dialect(DbType::MySQL)?;
3033 let del_sql = QueryBuilder::<TestModel>::new(dialect)
3034 .table("users")
3035 .where_eq("id", Value::I64(1))
3036 .build_delete();
3037 assert!(
3038 del_sql.starts_with("DELETE FROM"),
3039 "非软删除模型 build_delete 应生成 DELETE: {}",
3040 del_sql
3041 );
3042 Ok(())
3043 }
3044
3045 #[test]
3047 fn test_p01_soft_delete_count_auto_filter() -> Result<(), crate::DbError> {
3048 let dialect = get_dialect(DbType::MySQL)?;
3049 let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
3050 .table("soft_users")
3051 .build_count();
3052 assert!(
3053 sql.contains("`deleted_at` IS NULL"),
3054 "build_count 也应追加软删除过滤: {}",
3055 sql
3056 );
3057 Ok(())
3058 }
3059
3060 #[test]
3066 fn test_p02_where_eq_uses_placeholder() -> Result<(), crate::DbError> {
3067 let dialect = get_dialect(DbType::MySQL)?;
3068 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3069 .table("users")
3070 .where_eq("name", Value::String("alice".into()))
3071 .build_select_with_params();
3072 assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
3074 assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
3075 assert_eq!(params.len(), 1);
3076 assert_eq!(params[0], Value::String("alice".into()));
3077 Ok(())
3078 }
3079
3080 #[test]
3082 fn test_p02_where_like_uses_placeholder() -> Result<(), crate::DbError> {
3083 let dialect = get_dialect(DbType::MySQL)?;
3084 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3085 .table("users")
3086 .where_like("name", Value::String("%alice%".into()))
3087 .build_select_with_params();
3088 assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
3089 assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
3090 assert_eq!(params.len(), 1);
3091 Ok(())
3092 }
3093
3094 #[test]
3098 fn test_p02_where_ne_uses_placeholder() -> Result<(), crate::DbError> {
3099 let dialect = get_dialect(DbType::MySQL)?;
3100 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3101 .table("users")
3102 .where_ne("status", Value::I64(0))
3103 .build_select_with_params();
3104 assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
3105 assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
3106 assert_eq!(params.len(), 1);
3107 assert_eq!(params[0], Value::I64(0));
3108 Ok(())
3109 }
3110
3111 #[test]
3115 fn test_p02_where_ge_uses_placeholder() -> Result<(), crate::DbError> {
3116 let dialect = get_dialect(DbType::MySQL)?;
3117 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3118 .table("users")
3119 .where_ge("age", Value::I64(18))
3120 .build_select_with_params();
3121 assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
3122 assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
3123 assert_eq!(params.len(), 1);
3124 assert_eq!(params[0], Value::I64(18));
3125 Ok(())
3126 }
3127
3128 #[test]
3132 fn test_p02_where_lt_uses_placeholder() -> Result<(), crate::DbError> {
3133 let dialect = get_dialect(DbType::MySQL)?;
3134 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3135 .table("users")
3136 .where_lt("score", Value::F64(60.0))
3137 .build_select_with_params();
3138 assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
3139 assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
3140 assert_eq!(params.len(), 1);
3141 assert_eq!(params[0], Value::F64(60.0));
3142 Ok(())
3143 }
3144
3145 #[test]
3149 fn test_p02_injection_protection_drop_table() -> Result<(), crate::DbError> {
3150 let dialect = get_dialect(DbType::MySQL)?;
3151 let evil_input = "'; DROP TABLE users; --".to_string();
3152 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3153 .table("users")
3154 .where_eq("name", Value::String(evil_input.clone()))
3155 .build_select_with_params();
3156 assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
3158 assert_eq!(params.len(), 1);
3160 assert_eq!(params[0], Value::String(evil_input));
3161 assert_eq!(sql.matches('?').count(), 1);
3163 Ok(())
3164 }
3165
3166 #[test]
3168 fn test_p02_injection_protection_or_one_equals_one() -> Result<(), crate::DbError> {
3169 let dialect = get_dialect(DbType::MySQL)?;
3170 let evil = "' OR '1'='1".to_string();
3171 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3172 .table("users")
3173 .where_eq("name", Value::String(evil.clone()))
3174 .build_select_with_params();
3175 assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
3176 assert_eq!(params.len(), 1);
3177 assert_eq!(params[0], Value::String(evil));
3178 Ok(())
3179 }
3180
3181 #[test]
3183 fn test_p02_multiple_params_order() -> Result<(), crate::DbError> {
3184 let dialect = get_dialect(DbType::MySQL)?;
3185 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3186 .table("users")
3187 .where_eq("name", Value::String("alice".into()))
3188 .where_gt("age", Value::I64(18))
3189 .where_le("score", Value::F64(99.5))
3190 .build_select_with_params();
3191 assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
3192 assert_eq!(params.len(), 3);
3193 assert_eq!(params[0], Value::String("alice".into()));
3195 assert_eq!(params[1], Value::I64(18));
3196 assert_eq!(params[2], Value::F64(99.5));
3197 Ok(())
3198 }
3199
3200 #[test]
3202 fn test_p02_where_in_uses_placeholders() -> Result<(), crate::DbError> {
3203 let dialect = get_dialect(DbType::MySQL)?;
3204 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3205 .table("users")
3206 .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
3207 .build_select_with_params();
3208 assert!(
3209 sql.contains("`id` IN (?, ?, ?)"),
3210 "应使用 3 个占位符: {}",
3211 sql
3212 );
3213 assert_eq!(params.len(), 3);
3214 Ok(())
3215 }
3216
3217 #[test]
3219 fn test_p02_where_between_uses_placeholders() -> Result<(), crate::DbError> {
3220 let dialect = get_dialect(DbType::MySQL)?;
3221 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3222 .table("users")
3223 .where_between("age", Value::I64(18), Value::I64(65))
3224 .build_select_with_params();
3225 assert!(
3226 sql.contains("`age` BETWEEN ? AND ?"),
3227 "应使用 2 个占位符: {}",
3228 sql
3229 );
3230 assert_eq!(params.len(), 2);
3231 assert_eq!(params[0], Value::I64(18));
3232 assert_eq!(params[1], Value::I64(65));
3233 Ok(())
3234 }
3235
3236 #[test]
3238 fn test_p02_update_params_order_set_before_where() -> Result<(), crate::DbError> {
3239 let dialect = get_dialect(DbType::MySQL)?;
3240 let mut data = std::collections::HashMap::new();
3241 data.insert("name".to_string(), Value::String("bob".into()));
3242 data.insert("age".to_string(), Value::I64(30));
3243 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3244 .table("users")
3245 .where_eq("id", Value::I64(99))
3246 .build_update_with_params(&data);
3247 assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
3249 assert_eq!(params.len(), 3);
3250 assert_eq!(params[2], Value::I64(99));
3253 Ok(())
3254 }
3255
3256 #[test]
3260 fn test_p02_build_where_clause_inlines_value() -> Result<(), crate::DbError> {
3261 let dialect = get_dialect(DbType::MySQL)?;
3262 let sql = QueryBuilder::<TestModel>::new(dialect)
3263 .table("users")
3264 .where_eq("name", Value::String("alice".into()))
3265 .build_select();
3266 assert!(
3268 sql.contains("`name` = "),
3269 "无参数版本应含 WHERE 条件: {}",
3270 sql
3271 );
3272 assert!(
3274 !sql.contains("`name` = ?"),
3275 "无参数版本不应使用 ? 占位符: {}",
3276 sql
3277 );
3278 Ok(())
3279 }
3280
3281 #[test]
3283 fn test_p01_is_soft_delete_disabled_flag() -> Result<(), crate::DbError> {
3284 let dialect = get_dialect(DbType::MySQL)?;
3285 let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
3286 assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
3287 let builder =
3288 QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
3289 assert!(
3290 builder.is_soft_delete_disabled(),
3291 "without_soft_delete 后应反映禁用状态"
3292 );
3293 Ok(())
3294 }
3295
3296 struct TenantModel;
3300 impl Model for TenantModel {
3301 type PrimaryKey = i64;
3302
3303 fn table_name() -> &'static str {
3304 "orders"
3305 }
3306
3307 fn pk(&self) -> Self::PrimaryKey {
3308 1
3309 }
3310
3311 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3312
3313 fn tenant_field() -> Option<&'static str> {
3314 Some("tenant_id")
3315 }
3316 }
3317
3318 struct SoftDeleteAndTenantModel;
3320 impl Model for SoftDeleteAndTenantModel {
3321 type PrimaryKey = i64;
3322
3323 fn table_name() -> &'static str {
3324 "documents"
3325 }
3326
3327 fn pk(&self) -> Self::PrimaryKey {
3328 1
3329 }
3330
3331 fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3332
3333 fn soft_delete_field() -> Option<&'static str> {
3334 Some("deleted_at")
3335 }
3336
3337 fn tenant_field() -> Option<&'static str> {
3338 Some("tenant_id")
3339 }
3340 }
3341
3342 #[test]
3346 fn test_p03_tenant_select_auto_filter() -> Result<(), crate::DbError> {
3347 let dialect = get_dialect(DbType::MySQL)?;
3348 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3349 .table("orders")
3350 .with_tenant_id(42)
3351 .build_select_with_params();
3352 assert!(
3353 sql.contains("`tenant_id` = ?"),
3354 "多租户模型应自动追加 tenant_id = ?: {}",
3355 sql
3356 );
3357 assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
3358 assert_eq!(params[0], Value::I64(42));
3359 Ok(())
3360 }
3361
3362 #[test]
3364 fn test_p03_tenant_select_with_user_where() -> Result<(), crate::DbError> {
3365 let dialect = get_dialect(DbType::MySQL)?;
3366 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3367 .table("orders")
3368 .with_tenant_id(7)
3369 .where_eq("status", Value::String("active".into()))
3370 .build_select_with_params();
3371 assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
3372 assert!(
3373 sql.contains("`tenant_id` = ?"),
3374 "租户条件应自动追加: {}",
3375 sql
3376 );
3377 assert_eq!(params.len(), 2, "应有 2 个参数");
3378 assert_eq!(params[0], Value::String("active".into()));
3380 assert_eq!(params[1], Value::I64(7));
3381 Ok(())
3382 }
3383
3384 #[test]
3388 fn test_p03_tenant_without_tenant() -> Result<(), crate::DbError> {
3389 let dialect = get_dialect(DbType::MySQL)?;
3390 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3391 .table("orders")
3392 .with_tenant_id(42)
3393 .without_tenant()
3394 .build_select_with_params();
3395 assert!(
3396 !sql.contains("`tenant_id` = ?"),
3397 "without_tenant 应禁用过滤: {}",
3398 sql
3399 );
3400 assert_eq!(params.len(), 0, "不应有租户参数");
3401 Ok(())
3402 }
3403
3404 #[test]
3408 fn test_p03_tenant_delete_auto_filter() -> Result<(), crate::DbError> {
3409 let dialect = get_dialect(DbType::MySQL)?;
3410 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3411 .table("orders")
3412 .with_tenant_id(99)
3413 .where_eq("id", Value::I64(1))
3414 .build_delete_with_params();
3415 assert!(
3416 sql.contains("`tenant_id` = ?"),
3417 "删除应自动追加租户条件: {}",
3418 sql
3419 );
3420 assert_eq!(params.len(), 2);
3422 assert_eq!(params[0], Value::I64(1));
3423 assert_eq!(params[1], Value::I64(99));
3424 Ok(())
3425 }
3426
3427 #[test]
3429 fn test_p03_tenant_update_auto_filter() -> Result<(), crate::DbError> {
3430 let dialect = get_dialect(DbType::MySQL)?;
3431 let mut data = std::collections::HashMap::new();
3432 data.insert("status".to_string(), Value::String("shipped".into()));
3433 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3434 .table("orders")
3435 .with_tenant_id(5)
3436 .where_eq("id", Value::I64(10))
3437 .build_update_with_params(&data);
3438 assert!(
3439 sql.contains("`tenant_id` = ?"),
3440 "更新应自动追加租户条件: {}",
3441 sql
3442 );
3443 assert_eq!(params.len(), 3);
3445 assert_eq!(params[2], Value::I64(5));
3447 Ok(())
3448 }
3449
3450 #[test]
3452 fn test_p03_tenant_count_auto_filter() -> Result<(), crate::DbError> {
3453 let dialect = get_dialect(DbType::MySQL)?;
3454 let sql = QueryBuilder::<TenantModel>::new(dialect)
3455 .table("orders")
3456 .with_tenant_id(42)
3457 .build_count();
3458 assert!(
3459 sql.contains("`tenant_id` = 42"),
3460 "build_count 应追加租户条件(无参数版本内嵌值): {}",
3461 sql
3462 );
3463 Ok(())
3464 }
3465
3466 #[test]
3470 fn test_p03_non_tenant_model_unchanged() -> Result<(), crate::DbError> {
3471 let dialect = get_dialect(DbType::MySQL)?;
3472 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3474 .table("users")
3475 .with_tenant_id(42)
3476 .build_select_with_params();
3477 assert!(
3478 !sql.contains("tenant_id"),
3479 "非多租户模型不应追加 tenant_id: {}",
3480 sql
3481 );
3482 assert_eq!(params.len(), 0);
3483 Ok(())
3484 }
3485
3486 #[test]
3490 fn test_p03_tenant_no_id_no_filter() -> Result<(), crate::DbError> {
3491 let dialect = get_dialect(DbType::MySQL)?;
3492 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3493 .table("orders")
3494 .build_select_with_params();
3495 assert!(
3496 !sql.contains("tenant_id"),
3497 "未设置 tenant_id 时不应追加过滤: {}",
3498 sql
3499 );
3500 assert_eq!(params.len(), 0);
3501 Ok(())
3502 }
3503
3504 #[test]
3508 fn test_p03_soft_delete_and_tenant_combined() -> Result<(), crate::DbError> {
3509 let dialect = get_dialect(DbType::MySQL)?;
3510 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3511 .table("documents")
3512 .with_tenant_id(100)
3513 .where_eq("title", Value::String("report".into()))
3514 .build_select_with_params();
3515 assert!(
3517 sql.contains("`deleted_at` IS NULL"),
3518 "应追加软删除条件: {}",
3519 sql
3520 );
3521 assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3523 assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3525 assert_eq!(params.len(), 2);
3527 assert_eq!(params[0], Value::String("report".into()));
3528 assert_eq!(params[1], Value::I64(100));
3529 Ok(())
3530 }
3531
3532 #[test]
3534 fn test_p03_without_tenant_and_soft_delete() -> Result<(), crate::DbError> {
3535 let dialect = get_dialect(DbType::MySQL)?;
3536 let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3537 .table("documents")
3538 .with_tenant_id(100)
3539 .without_tenant()
3540 .without_soft_delete()
3541 .build_select_with_params();
3542 assert!(
3543 !sql.contains("`deleted_at` IS NULL"),
3544 "应禁用软删除: {}",
3545 sql
3546 );
3547 assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
3548 assert_eq!(params.len(), 0);
3549 Ok(())
3550 }
3551
3552 #[test]
3554 fn test_p03_is_tenant_disabled_flag() -> Result<(), crate::DbError> {
3555 let dialect = get_dialect(DbType::MySQL)?;
3556 let builder = QueryBuilder::<TenantModel>::new(dialect);
3557 assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
3558 let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
3559 .with_tenant_id(1)
3560 .without_tenant();
3561 assert!(
3562 builder.is_tenant_disabled(),
3563 "without_tenant 后应反映禁用状态"
3564 );
3565 Ok(())
3566 }
3567
3568 #[test]
3572 fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), crate::DbError> {
3573 let dialect = get_dialect(DbType::MySQL)?;
3574 let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3575 .table("orders")
3576 .with_tenant_id(42)
3577 .where_eq("id", Value::I64(999))
3578 .build_force_delete_with_params();
3579 assert!(
3582 sql.contains("`tenant_id` = ?"),
3583 "物理删除应保留租户条件: {}",
3584 sql
3585 );
3586 assert_eq!(params.len(), 2);
3587 assert_eq!(params[0], Value::I64(999));
3588 assert_eq!(params[1], Value::I64(42));
3589 Ok(())
3590 }
3591
3592 struct TcUsersTable;
3595 impl crate::typed::TypedTable for TcUsersTable {
3596 const NAME: &'static str = "users";
3597 }
3598 struct TcColId;
3599 impl crate::typed::TypedColumn for TcColId {
3600 const NAME: &'static str = "id";
3601 type Table = TcUsersTable;
3602 type RustType = i64;
3603 type SqlType = crate::typed_ast::Untyped;
3604 }
3605 struct TcColName;
3606 impl crate::typed::TypedColumn for TcColName {
3607 const NAME: &'static str = "name";
3608 type Table = TcUsersTable;
3609 type RustType = String;
3610 type SqlType = crate::typed_ast::Untyped;
3611 }
3612
3613 #[test]
3614 fn test_where_eq_typed() -> Result<(), crate::DbError> {
3615 let dialect = get_dialect(DbType::MySQL)?;
3616 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3617 .where_eq_typed::<TcColId>(Value::I64(42))
3618 .build_select_with_params();
3619 assert!(sql.contains("`id` = ?"));
3620 assert_eq!(params[0], Value::I64(42));
3621 Ok(())
3622 }
3623
3624 #[test]
3625 fn test_order_by_typed() -> Result<(), crate::DbError> {
3626 let dialect = get_dialect(DbType::MySQL)?;
3627 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3628 .order_by_typed::<TcColName>()
3629 .build_select_with_params();
3630 assert!(sql.contains("ORDER BY"));
3631 assert!(sql.contains("`name`"));
3632 Ok(())
3633 }
3634
3635 #[test]
3636 fn test_select_typed() -> Result<(), crate::DbError> {
3637 let dialect = get_dialect(DbType::MySQL)?;
3638 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3639 .select_typed::<TcColId>()
3640 .select_typed::<TcColName>()
3641 .build_select_with_params();
3642 assert!(sql.contains("SELECT"));
3643 assert!(sql.contains("id"));
3645 assert!(sql.contains("name"));
3646 Ok(())
3647 }
3648
3649 #[test]
3650 fn test_where_null_typed() -> Result<(), crate::DbError> {
3651 let dialect = get_dialect(DbType::MySQL)?;
3652 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3653 .where_null_typed::<TcColName>()
3654 .build_select_with_params();
3655 assert!(sql.contains("`name` IS NULL"));
3656 Ok(())
3657 }
3658
3659 #[test]
3660 fn test_where_not_null_typed() -> Result<(), crate::DbError> {
3661 let dialect = get_dialect(DbType::MySQL)?;
3662 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3663 .where_not_null_typed::<TcColName>()
3664 .build_select_with_params();
3665 assert!(sql.contains("`name` IS NOT NULL"));
3666 Ok(())
3667 }
3668
3669 #[test]
3670 fn test_group_by_typed() -> Result<(), crate::DbError> {
3671 let dialect = get_dialect(DbType::MySQL)?;
3672 let (sql, _) = QueryBuilder::<TestModel>::new(dialect)
3673 .group_by_typed::<TcColName>()
3674 .build_select_with_params();
3675 assert!(sql.contains("GROUP BY"));
3676 assert!(sql.contains("`name`"));
3677 Ok(())
3678 }
3679
3680 #[test]
3681 fn test_where_gt_typed() -> Result<(), crate::DbError> {
3682 let dialect = get_dialect(DbType::MySQL)?;
3683 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3684 .where_gt_typed::<TcColId>(Value::I64(10))
3685 .build_select_with_params();
3686 assert!(sql.contains("`id` > ?"));
3687 assert_eq!(params[0], Value::I64(10));
3688 Ok(())
3689 }
3690
3691 #[test]
3694 fn test_lock_for_update_mysql() -> Result<(), crate::DbError> {
3695 let dialect = get_dialect(DbType::MySQL)?;
3696 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3697 .table("users")
3698 .where_eq("id", Value::I64(1))
3699 .lock_for_update()?
3700 .build_select_with_params();
3701 assert!(sql.contains("SELECT * FROM `users`"));
3702 assert!(sql.contains("WHERE `id` = ?"));
3703 assert!(sql.contains("FOR UPDATE"));
3704 assert_eq!(params.len(), 1);
3705 assert_eq!(params[0], Value::I64(1));
3706 Ok(())
3707 }
3708
3709 #[test]
3710 fn test_lock_shared_mysql() -> Result<(), crate::DbError> {
3711 let dialect = get_dialect(DbType::MySQL)?;
3712 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3713 .table("users")
3714 .where_eq("id", Value::I64(1))
3715 .lock_shared()?
3716 .build_select_with_params();
3717 assert!(sql.contains("SELECT * FROM `users`"));
3718 assert!(sql.contains("WHERE `id` = ?"));
3719 assert!(sql.contains("LOCK IN SHARE MODE"));
3720 assert_eq!(params.len(), 1);
3721 assert_eq!(params[0], Value::I64(1));
3722 Ok(())
3723 }
3724
3725 #[test]
3726 fn test_lock_for_update_postgresql() -> Result<(), crate::DbError> {
3727 let dialect = get_dialect(DbType::PostgreSQL)?;
3728 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3729 .table("users")
3730 .where_eq("id", Value::I64(1))
3731 .lock_for_update()?
3732 .build_select_with_params();
3733 assert!(sql.contains("SELECT * FROM \"users\""));
3734 assert!(sql.contains("WHERE \"id\" = ?"));
3735 assert!(sql.contains("FOR UPDATE"));
3736 assert_eq!(params.len(), 1);
3737 assert_eq!(params[0], Value::I64(1));
3738 Ok(())
3739 }
3740
3741 #[test]
3742 fn test_lock_shared_postgresql() -> Result<(), crate::DbError> {
3743 let dialect = get_dialect(DbType::PostgreSQL)?;
3744 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3745 .table("users")
3746 .where_eq("id", Value::I64(1))
3747 .lock_shared()?
3748 .build_select_with_params();
3749 assert!(sql.contains("SELECT * FROM \"users\""));
3750 assert!(sql.contains("WHERE \"id\" = ?"));
3751 assert!(sql.contains("FOR SHARE"));
3752 assert_eq!(params.len(), 1);
3753 assert_eq!(params[0], Value::I64(1));
3754 Ok(())
3755 }
3756
3757 #[test]
3758 fn test_lock_for_update_sqlite_should_fail() {
3759 let dialect = get_dialect(DbType::Sqlite).unwrap();
3760 let result = QueryBuilder::<TestModel>::new(dialect)
3761 .table("users")
3762 .where_eq("id", Value::I64(1))
3763 .lock_for_update();
3764 assert!(result.is_err(), "SQLite 不应支持 FOR UPDATE 锁");
3765 let err = result.err().unwrap();
3766 assert!(
3767 format!("{:?}", err).contains("FOR UPDATE lock is not supported"),
3768 "错误信息应说明不支持行锁"
3769 );
3770 }
3771
3772 #[test]
3773 fn test_lock_shared_sqlite_should_fail() {
3774 let dialect = get_dialect(DbType::Sqlite).unwrap();
3775 let result = QueryBuilder::<TestModel>::new(dialect)
3776 .table("users")
3777 .where_eq("id", Value::I64(1))
3778 .lock_shared();
3779 assert!(result.is_err(), "SQLite 不应支持共享锁");
3780 let err = result.err().unwrap();
3781 assert!(
3782 format!("{:?}", err).contains("Shared lock is not supported"),
3783 "错误信息应说明不支持共享锁"
3784 );
3785 }
3786
3787 #[test]
3788 fn test_lock_with_limit_and_offset() -> Result<(), crate::DbError> {
3789 let dialect = get_dialect(DbType::MySQL)?;
3790 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3791 .table("users")
3792 .where_eq("status", Value::String("active".into()))
3793 .limit(10)
3794 .offset(20)
3795 .lock_for_update()?
3796 .build_select_with_params();
3797 assert!(sql.contains("WHERE `status` = ?"));
3798 assert!(sql.contains("LIMIT 10"));
3799 assert!(sql.contains("OFFSET 20"));
3800 assert!(sql.contains("FOR UPDATE"));
3801 assert_eq!(params.len(), 1);
3802 Ok(())
3803 }
3804
3805 #[test]
3808 fn test_insert_or_ignore_mysql() -> Result<(), crate::DbError> {
3809 let dialect = get_dialect(DbType::MySQL)?;
3810 let mut data = std::collections::HashMap::new();
3811 data.insert("name".to_string(), Value::String("Alice".into()));
3812 data.insert("age".to_string(), Value::I64(30));
3813
3814 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3815 .table("users")
3816 .insert_or_ignore()
3817 .build_insert_with_params(&data);
3818 assert!(sql.contains("INSERT IGNORE INTO `users`"));
3819 assert!(sql.contains("`name`"), "SQL 应包含 name 列: {}", sql);
3821 assert!(sql.contains("`age`"), "SQL 应包含 age 列: {}", sql);
3822 assert!(sql.contains("VALUES (?, ?)"));
3823 assert_eq!(params.len(), 2);
3824 Ok(())
3825 }
3826
3827 #[test]
3828 fn test_insert_or_ignore_postgresql() -> Result<(), crate::DbError> {
3829 let dialect = get_dialect(DbType::PostgreSQL)?;
3830 let mut data = std::collections::HashMap::new();
3831 data.insert("name".to_string(), Value::String("Bob".into()));
3832
3833 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3834 .table("users")
3835 .insert_or_ignore()
3836 .build_insert_with_params(&data);
3837 assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3838 assert!(sql.contains("(\"name\")"));
3839 assert!(sql.contains("VALUES (?)"));
3840 assert_eq!(params.len(), 1);
3841 assert_eq!(params[0], Value::String("Bob".into()));
3842 Ok(())
3843 }
3844
3845 #[test]
3846 fn test_insert_or_ignore_sqlite() -> Result<(), crate::DbError> {
3847 let dialect = get_dialect(DbType::Sqlite)?;
3848 let mut data = std::collections::HashMap::new();
3849 data.insert("name".to_string(), Value::String("Charlie".into()));
3850
3851 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3852 .table("users")
3853 .insert_or_ignore()
3854 .build_insert_with_params(&data);
3855 assert!(sql.contains("INSERT OR IGNORE INTO \"users\""));
3856 assert!(sql.contains("(\"name\")"));
3857 assert!(sql.contains("VALUES (?)"));
3858 assert_eq!(params.len(), 1);
3859 assert_eq!(params[0], Value::String("Charlie".into()));
3860 Ok(())
3861 }
3862
3863 #[test]
3864 fn test_insert_normal_without_ignore() -> Result<(), crate::DbError> {
3865 let dialect = get_dialect(DbType::MySQL)?;
3866 let mut data = std::collections::HashMap::new();
3867 data.insert("name".to_string(), Value::String("Dave".into()));
3868
3869 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3870 .table("users")
3871 .build_insert_with_params(&data);
3872 assert!(sql.contains("INSERT INTO `users`"));
3873 assert!(!sql.contains("IGNORE"), "普通插入不应包含 IGNORE");
3874 assert_eq!(params.len(), 1);
3875 assert_eq!(params[0], Value::String("Dave".into()));
3876 Ok(())
3877 }
3878
3879 #[test]
3880 fn test_insert_or_ignore_empty_data() -> Result<(), crate::DbError> {
3881 let dialect = get_dialect(DbType::MySQL)?;
3882 let data = std::collections::HashMap::new();
3883
3884 let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3885 .table("users")
3886 .insert_or_ignore()
3887 .build_insert_with_params(&data);
3888 assert!(sql.is_empty(), "空数据应返回空 SQL");
3889 assert!(params.is_empty());
3890 Ok(())
3891 }
3892
3893 #[test]
3896 fn test_dialect_supports_lock_for_update() -> Result<(), crate::DbError> {
3897 let mysql = get_dialect(DbType::MySQL)?;
3898 let pg = get_dialect(DbType::PostgreSQL)?;
3899 let sqlite = get_dialect(DbType::Sqlite)?;
3900 let clickhouse = get_dialect(DbType::ClickHouse)?;
3901 let duckdb = get_dialect(DbType::DuckDB)?;
3902
3903 assert!(mysql.supports_lock_for_update(), "MySQL 应支持 FOR UPDATE");
3904 assert!(
3905 pg.supports_lock_for_update(),
3906 "PostgreSQL 应支持 FOR UPDATE"
3907 );
3908 assert!(
3909 !sqlite.supports_lock_for_update(),
3910 "SQLite 不应支持 FOR UPDATE"
3911 );
3912 assert!(
3913 !clickhouse.supports_lock_for_update(),
3914 "ClickHouse 是列式 OLAP,不应支持 FOR UPDATE"
3915 );
3916 assert!(
3917 !duckdb.supports_lock_for_update(),
3918 "DuckDB 不应支持 FOR UPDATE"
3919 );
3920 Ok(())
3921 }
3922
3923 #[test]
3924 fn test_dialect_supports_lock_shared() -> Result<(), crate::DbError> {
3925 let mysql = get_dialect(DbType::MySQL)?;
3926 let pg = get_dialect(DbType::PostgreSQL)?;
3927 let sqlite = get_dialect(DbType::Sqlite)?;
3928 let clickhouse = get_dialect(DbType::ClickHouse)?;
3929 let duckdb = get_dialect(DbType::DuckDB)?;
3930
3931 assert!(mysql.supports_lock_shared(), "MySQL 应支持共享锁");
3932 assert!(pg.supports_lock_shared(), "PostgreSQL 应支持共享锁");
3933 assert!(!sqlite.supports_lock_shared(), "SQLite 不应支持共享锁");
3934 assert!(
3935 !clickhouse.supports_lock_shared(),
3936 "ClickHouse 是列式 OLAP,不应支持共享锁"
3937 );
3938 assert!(!duckdb.supports_lock_shared(), "DuckDB 不应支持共享锁");
3939 Ok(())
3940 }
3941
3942 #[test]
3943 fn test_get_lock_type_and_is_insert_or_ignore() -> Result<(), crate::DbError> {
3944 let dialect = get_dialect(DbType::MySQL)?;
3945
3946 let builder = QueryBuilder::<TestModel>::new(dialect);
3948 assert!(builder.get_lock_type().is_none(), "默认无锁");
3949 assert!(!builder.is_insert_or_ignore(), "默认不忽略插入");
3950
3951 let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
3953 .table("users")
3954 .lock_for_update()?;
3955 assert_eq!(builder.get_lock_type(), Some(LockType::ForUpdate));
3956
3957 let builder = QueryBuilder::<TestModel>::new(get_dialect(DbType::MySQL)?)
3959 .table("users")
3960 .insert_or_ignore();
3961 assert!(builder.is_insert_or_ignore());
3962
3963 Ok(())
3964 }
3965}