1use sz_orm_core::{DbType, Value};
64
65#[derive(Debug, Clone, Default)]
79pub struct BuiltQuery {
80 pub sql: String,
82 pub params: Vec<Value>,
84}
85
86impl BuiltQuery {
87 pub fn into_parts(self) -> (String, Vec<Value>) {
89 (self.sql, self.params)
90 }
91}
92
93#[derive(Debug, Clone)]
104enum ParamWhere {
105 And {
107 column: String,
109 op: String,
111 values: Vec<Value>,
113 },
114 Or {
116 column: String,
117 op: String,
118 values: Vec<Value>,
119 },
120}
121
122fn quote_ident(s: &str) -> String {
131 s.split('.')
132 .map(|part| format!("`{}`", part.replace('`', "``")))
133 .collect::<Vec<_>>()
134 .join(".")
135}
136
137fn quote_column_dialect(dialect: &dyn sz_orm_core::Dialect, column: &str) -> String {
149 column
150 .split('.')
151 .map(|part| dialect.quote(part))
152 .collect::<Vec<_>>()
153 .join(".")
154}
155
156fn check_where_injection(condition: &str) {
172 let upper = condition.to_uppercase();
173 const SQL_KEYWORDS: &[&str] = &[
174 "DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "TRUNCATE", "EXEC", "CREATE", "GRANT",
175 "REVOKE",
176 ];
177 for kw in SQL_KEYWORDS {
178 let pattern1 = format!(";{}", kw);
179 let pattern2 = format!("; {}", kw);
180 if upper.contains(&pattern1) || upper.contains(&pattern2) {
181 panic!(
182 "SQL injection detected in where_clause: semicolon followed by {} keyword: {:?}",
183 kw, condition
184 );
185 }
186 }
187 if condition.contains("--") {
188 panic!(
189 "SQL injection detected in where_clause: line comment '--' not allowed: {:?}",
190 condition
191 );
192 }
193 if condition.contains("/*") || condition.contains("*/") {
194 panic!(
195 "SQL injection detected in where_clause: block comment '/*' or '*/' not allowed: {:?}",
196 condition
197 );
198 }
199}
200
201pub struct Query;
203
204impl Query {
205 pub fn select() -> SelectQuery {
207 SelectQuery::new()
208 }
209
210 pub fn insert() -> InsertQuery {
212 InsertQuery::new()
213 }
214
215 pub fn update() -> UpdateQuery {
217 UpdateQuery::new()
218 }
219
220 pub fn delete() -> DeleteQuery {
222 DeleteQuery::new()
223 }
224}
225
226#[derive(Debug, Clone)]
231enum JoinOn {
232 Raw(String),
234 ColumnEq {
236 left_column: String,
237 right_column: String,
238 },
239 Param {
241 left_column: String,
242 op: String,
243 values: Vec<Value>,
244 },
245}
246
247#[derive(Debug, Clone)]
249struct JoinClause {
250 join_type: &'static str,
252 table: String,
254 on: Vec<JoinOn>,
256}
257
258#[derive(Debug, Clone, Default)]
260pub struct SelectQuery {
261 columns: Vec<String>,
262 from_table: Option<String>,
263 from_subquery: Option<(String, String)>,
265 join_clauses: Vec<JoinClause>,
271 wheres: Vec<String>,
272 param_wheres: Vec<ParamWhere>,
274 order_by: Vec<String>,
275 group_by: Vec<String>,
276 having: Vec<String>,
277 limit: Option<u64>,
278 offset: Option<u64>,
279 distinct: bool,
280 ctes: Vec<(String, String, bool)>,
282 window_columns: Vec<String>,
284 for_update: bool,
286 for_update_options: Option<String>,
288}
289
290impl SelectQuery {
291 pub fn new() -> Self {
293 Self::default()
294 }
295
296 pub fn distinct(mut self) -> Self {
298 self.distinct = true;
299 self
300 }
301
302 pub fn column(mut self, name: &str) -> Self {
304 self.columns.push(name.to_string());
305 self
306 }
307
308 pub fn columns(mut self, names: &[&str]) -> Self {
310 for n in names {
311 self.columns.push(n.to_string());
312 }
313 self
314 }
315
316 pub fn all_columns(self) -> Self {
318 self.column("*")
319 }
320
321 pub fn from(mut self, table: &str) -> Self {
323 self.from_table = Some(table.to_string());
324 self.from_subquery = None;
326 self
327 }
328
329 pub fn from_subquery(mut self, subquery_sql: &str, alias: &str) -> Self {
353 self.from_subquery = Some((subquery_sql.to_string(), alias.to_string()));
354 self.from_table = None;
356 self
357 }
358
359 pub fn inner_join(mut self, table: &str, on: &str) -> Self {
365 self.join_clauses.push(JoinClause {
366 join_type: "INNER JOIN",
367 table: Self::quote_join_table(table),
368 on: vec![JoinOn::Raw(on.to_string())],
369 });
370 self
371 }
372
373 pub fn left_join(mut self, table: &str, on: &str) -> Self {
379 self.join_clauses.push(JoinClause {
380 join_type: "LEFT JOIN",
381 table: Self::quote_join_table(table),
382 on: vec![JoinOn::Raw(on.to_string())],
383 });
384 self
385 }
386
387 pub fn right_join(mut self, table: &str, on: &str) -> Self {
393 self.join_clauses.push(JoinClause {
394 join_type: "RIGHT JOIN",
395 table: Self::quote_join_table(table),
396 on: vec![JoinOn::Raw(on.to_string())],
397 });
398 self
399 }
400
401 pub fn inner_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
439 self.join_clauses.push(JoinClause {
440 join_type: "INNER JOIN",
441 table: Self::quote_join_table(table),
442 on: vec![JoinOn::ColumnEq {
443 left_column: left_col.to_string(),
444 right_column: right_col.to_string(),
445 }],
446 });
447 self
448 }
449
450 pub fn left_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
452 self.join_clauses.push(JoinClause {
453 join_type: "LEFT JOIN",
454 table: Self::quote_join_table(table),
455 on: vec![JoinOn::ColumnEq {
456 left_column: left_col.to_string(),
457 right_column: right_col.to_string(),
458 }],
459 });
460 self
461 }
462
463 pub fn right_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
465 self.join_clauses.push(JoinClause {
466 join_type: "RIGHT JOIN",
467 table: Self::quote_join_table(table),
468 on: vec![JoinOn::ColumnEq {
469 left_column: left_col.to_string(),
470 right_column: right_col.to_string(),
471 }],
472 });
473 self
474 }
475
476 pub fn inner_join_param(
485 mut self,
486 table: &str,
487 left_col: &str,
488 op_expr: &str,
489 value: Value,
490 ) -> Self {
491 self.join_clauses.push(JoinClause {
492 join_type: "INNER JOIN",
493 table: Self::quote_join_table(table),
494 on: vec![JoinOn::Param {
495 left_column: left_col.to_string(),
496 op: op_expr.to_string(),
497 values: vec![value],
498 }],
499 });
500 self
501 }
502
503 pub fn left_join_param(
505 mut self,
506 table: &str,
507 left_col: &str,
508 op_expr: &str,
509 value: Value,
510 ) -> Self {
511 self.join_clauses.push(JoinClause {
512 join_type: "LEFT JOIN",
513 table: Self::quote_join_table(table),
514 on: vec![JoinOn::Param {
515 left_column: left_col.to_string(),
516 op: op_expr.to_string(),
517 values: vec![value],
518 }],
519 });
520 self
521 }
522
523 pub fn right_join_param(
525 mut self,
526 table: &str,
527 left_col: &str,
528 op_expr: &str,
529 value: Value,
530 ) -> Self {
531 self.join_clauses.push(JoinClause {
532 join_type: "RIGHT JOIN",
533 table: Self::quote_join_table(table),
534 on: vec![JoinOn::Param {
535 left_column: left_col.to_string(),
536 op: op_expr.to_string(),
537 values: vec![value],
538 }],
539 });
540 self
541 }
542
543 fn render_joins(&self, dialect: &dyn sz_orm_core::Dialect, params: &mut Vec<Value>) -> String {
548 let mut sql = String::new();
549 for clause in &self.join_clauses {
550 sql.push(' ');
551 sql.push_str(clause.join_type);
552 sql.push(' ');
553 sql.push_str(&clause.table);
554 sql.push_str(" ON ");
555 for (i, on) in clause.on.iter().enumerate() {
556 if i > 0 {
557 sql.push_str(" AND ");
558 }
559 match on {
560 JoinOn::Raw(raw) => sql.push_str(raw),
561 JoinOn::ColumnEq {
562 left_column,
563 right_column,
564 } => {
565 sql.push_str("e_column_dialect(dialect, left_column));
566 sql.push_str(" = ");
567 sql.push_str("e_column_dialect(dialect, right_column));
568 }
569 JoinOn::Param {
570 left_column,
571 op,
572 values,
573 } => {
574 sql.push_str("e_column_dialect(dialect, left_column));
575 sql.push_str(op);
576 params.extend(values.iter().cloned());
577 }
578 }
579 }
580 }
581 sql
582 }
583
584 fn quote_join_table(table: &str) -> String {
586 if let Some((tbl, alias)) = table.rsplit_once(' ') {
587 if alias.to_uppercase() == "AS" {
588 format!("{} AS {}", quote_ident(tbl), alias)
590 } else {
591 format!("{} {}", quote_ident(tbl), alias)
593 }
594 } else {
595 quote_ident(table)
596 }
597 }
598
599 pub fn where_clause(mut self, condition: &str) -> Self {
606 check_where_injection(condition);
607 self.wheres.push(condition.to_string());
608 self
609 }
610
611 pub fn where_eq(mut self, column: &str, value: Value) -> Self {
637 self.param_wheres.push(ParamWhere::And {
638 column: column.to_string(),
639 op: " = ?".to_string(),
640 values: vec![value],
641 });
642 self
643 }
644
645 pub fn where_ne(mut self, column: &str, value: Value) -> Self {
647 self.param_wheres.push(ParamWhere::And {
648 column: column.to_string(),
649 op: " <> ?".to_string(),
650 values: vec![value],
651 });
652 self
653 }
654
655 pub fn where_gt(mut self, column: &str, value: Value) -> Self {
657 self.param_wheres.push(ParamWhere::And {
658 column: column.to_string(),
659 op: " > ?".to_string(),
660 values: vec![value],
661 });
662 self
663 }
664
665 pub fn where_ge(mut self, column: &str, value: Value) -> Self {
667 self.param_wheres.push(ParamWhere::And {
668 column: column.to_string(),
669 op: " >= ?".to_string(),
670 values: vec![value],
671 });
672 self
673 }
674
675 pub fn where_lt(mut self, column: &str, value: Value) -> Self {
677 self.param_wheres.push(ParamWhere::And {
678 column: column.to_string(),
679 op: " < ?".to_string(),
680 values: vec![value],
681 });
682 self
683 }
684
685 pub fn where_le(mut self, column: &str, value: Value) -> Self {
687 self.param_wheres.push(ParamWhere::And {
688 column: column.to_string(),
689 op: " <= ?".to_string(),
690 values: vec![value],
691 });
692 self
693 }
694
695 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
697 self.param_wheres.push(ParamWhere::And {
698 column: column.to_string(),
699 op: " LIKE ?".to_string(),
700 values: vec![pattern],
701 });
702 self
703 }
704
705 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
709 let (column, op) = if values.is_empty() {
710 (String::new(), "1 = 0".to_string())
711 } else {
712 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
713 (
714 column.to_string(),
715 format!(" IN ({})", placeholders.join(", ")),
716 )
717 };
718 self.param_wheres
719 .push(ParamWhere::And { column, op, values });
720 self
721 }
722
723 pub fn where_not_in(mut self, column: &str, values: Vec<Value>) -> Self {
727 let (column, op) = if values.is_empty() {
728 (String::new(), "1 = 1".to_string())
729 } else {
730 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
731 (
732 column.to_string(),
733 format!(" NOT IN ({})", placeholders.join(", ")),
734 )
735 };
736 self.param_wheres
737 .push(ParamWhere::And { column, op, values });
738 self
739 }
740
741 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
743 self.param_wheres.push(ParamWhere::And {
744 column: column.to_string(),
745 op: " BETWEEN ? AND ?".to_string(),
746 values: vec![low, high],
747 });
748 self
749 }
750
751 pub fn where_null(mut self, column: &str) -> Self {
753 self.param_wheres.push(ParamWhere::And {
754 column: column.to_string(),
755 op: " IS NULL".to_string(),
756 values: vec![],
757 });
758 self
759 }
760
761 pub fn where_not_null(mut self, column: &str) -> Self {
763 self.param_wheres.push(ParamWhere::And {
764 column: column.to_string(),
765 op: " IS NOT NULL".to_string(),
766 values: vec![],
767 });
768 self
769 }
770
771 pub fn or_where_eq(mut self, column: &str, value: Value) -> Self {
773 self.param_wheres.push(ParamWhere::Or {
774 column: column.to_string(),
775 op: " = ?".to_string(),
776 values: vec![value],
777 });
778 self
779 }
780
781 pub fn or_where_ne(mut self, column: &str, value: Value) -> Self {
783 self.param_wheres.push(ParamWhere::Or {
784 column: column.to_string(),
785 op: " <> ?".to_string(),
786 values: vec![value],
787 });
788 self
789 }
790
791 pub fn or_where_gt(mut self, column: &str, value: Value) -> Self {
793 self.param_wheres.push(ParamWhere::Or {
794 column: column.to_string(),
795 op: " > ?".to_string(),
796 values: vec![value],
797 });
798 self
799 }
800
801 pub fn or_where_ge(mut self, column: &str, value: Value) -> Self {
803 self.param_wheres.push(ParamWhere::Or {
804 column: column.to_string(),
805 op: " >= ?".to_string(),
806 values: vec![value],
807 });
808 self
809 }
810
811 pub fn or_where_lt(mut self, column: &str, value: Value) -> Self {
813 self.param_wheres.push(ParamWhere::Or {
814 column: column.to_string(),
815 op: " < ?".to_string(),
816 values: vec![value],
817 });
818 self
819 }
820
821 pub fn or_where_le(mut self, column: &str, value: Value) -> Self {
823 self.param_wheres.push(ParamWhere::Or {
824 column: column.to_string(),
825 op: " <= ?".to_string(),
826 values: vec![value],
827 });
828 self
829 }
830
831 pub fn or_where_like(mut self, column: &str, pattern: Value) -> Self {
833 self.param_wheres.push(ParamWhere::Or {
834 column: column.to_string(),
835 op: " LIKE ?".to_string(),
836 values: vec![pattern],
837 });
838 self
839 }
840
841 pub fn or_where_in(mut self, column: &str, values: Vec<Value>) -> Self {
843 let (column, op) = if values.is_empty() {
844 (String::new(), "1 = 0".to_string())
845 } else {
846 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
847 (
848 column.to_string(),
849 format!(" IN ({})", placeholders.join(", ")),
850 )
851 };
852 self.param_wheres
853 .push(ParamWhere::Or { column, op, values });
854 self
855 }
856
857 pub fn or_where_between(mut self, column: &str, low: Value, high: Value) -> Self {
859 self.param_wheres.push(ParamWhere::Or {
860 column: column.to_string(),
861 op: " BETWEEN ? AND ?".to_string(),
862 values: vec![low, high],
863 });
864 self
865 }
866
867 pub fn or_where_null(mut self, column: &str) -> Self {
869 self.param_wheres.push(ParamWhere::Or {
870 column: column.to_string(),
871 op: " IS NULL".to_string(),
872 values: vec![],
873 });
874 self
875 }
876
877 pub fn or_where_not_null(mut self, column: &str) -> Self {
879 self.param_wheres.push(ParamWhere::Or {
880 column: column.to_string(),
881 op: " IS NOT NULL".to_string(),
882 values: vec![],
883 });
884 self
885 }
886
887 pub fn group_by(mut self, column: &str) -> Self {
889 self.group_by.push(column.to_string());
890 self
891 }
892
893 pub fn having(mut self, condition: &str) -> Self {
895 self.having.push(condition.to_string());
896 self
897 }
898
899 pub fn order_by(mut self, column: &str, asc: bool) -> Self {
906 let dir = if asc { "ASC" } else { "DESC" };
907 self.order_by.push(format!("{} {}", column, dir));
908 self
909 }
910
911 pub fn limit(mut self, n: u64) -> Self {
913 self.limit = Some(n);
914 self
915 }
916
917 pub fn offset(mut self, n: u64) -> Self {
919 self.offset = Some(n);
920 self
921 }
922
923 pub fn paginate(self, page: u64, size: u64) -> Self {
930 let offset = (page.saturating_sub(1)) * size;
931 self.limit(size).offset(offset)
932 }
933
934 pub fn with_cte(mut self, name: &str, subquery: &str) -> Self {
943 self.ctes
944 .push((name.to_string(), subquery.to_string(), false));
945 self
946 }
947
948 pub fn with_recursive_cte(mut self, name: &str, subquery: &str) -> Self {
955 self.ctes
956 .push((name.to_string(), subquery.to_string(), true));
957 self
958 }
959
960 pub fn window_function(mut self, expr: &str) -> Self {
971 self.window_columns.push(expr.to_string());
972 self
973 }
974
975 pub fn row_number(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
983 let partition_clause = if partition_by.is_empty() {
984 String::new()
985 } else {
986 format!("PARTITION BY {} ", partition_by)
987 };
988 let expr = format!(
989 "ROW_NUMBER() OVER ({}ORDER BY {}) AS {}",
990 partition_clause, order_by, alias
991 );
992 self.window_function(&expr)
993 }
994
995 pub fn rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1003 let partition_clause = if partition_by.is_empty() {
1004 String::new()
1005 } else {
1006 format!("PARTITION BY {} ", partition_by)
1007 };
1008 let expr = format!(
1009 "RANK() OVER ({}ORDER BY {}) AS {}",
1010 partition_clause, order_by, alias
1011 );
1012 self.window_function(&expr)
1013 }
1014
1015 pub fn dense_rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1023 let partition_clause = if partition_by.is_empty() {
1024 String::new()
1025 } else {
1026 format!("PARTITION BY {} ", partition_by)
1027 };
1028 let expr = format!(
1029 "DENSE_RANK() OVER ({}ORDER BY {}) AS {}",
1030 partition_clause, order_by, alias
1031 );
1032 self.window_function(&expr)
1033 }
1034
1035 pub fn for_update(mut self) -> Self {
1039 self.for_update = true;
1040 self.for_update_options = None;
1041 self
1042 }
1043
1044 pub fn for_update_with_options(mut self, options: &str) -> Self {
1050 self.for_update = true;
1051 self.for_update_options = Some(options.to_string());
1052 self
1053 }
1054
1055 pub fn union(self, other: SelectQuery) -> SetQuery {
1059 SetQuery::new(self, SetOperator::Union, other)
1060 }
1061
1062 pub fn union_all(self, other: SelectQuery) -> SetQuery {
1064 SetQuery::new(self, SetOperator::UnionAll, other)
1065 }
1066
1067 pub fn intersect(self, other: SelectQuery) -> SetQuery {
1069 SetQuery::new(self, SetOperator::Intersect, other)
1070 }
1071
1072 pub fn except(self, other: SelectQuery) -> SetQuery {
1074 SetQuery::new(self, SetOperator::Except, other)
1075 }
1076
1077 pub fn build(self, db_type: DbType) -> String {
1083 let dialect = match sz_orm_core::get_dialect(db_type) {
1084 Ok(d) => d,
1085 Err(_) => return String::new(),
1086 };
1087
1088 let mut sql = String::new();
1089
1090 if !self.ctes.is_empty() {
1092 let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1093 if has_recursive {
1094 sql.push_str("WITH RECURSIVE ");
1095 } else {
1096 sql.push_str("WITH ");
1097 }
1098 let cte_strs: Vec<String> = self
1099 .ctes
1100 .iter()
1101 .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1102 .collect();
1103 sql.push_str(&cte_strs.join(", "));
1104 sql.push(' ');
1105 }
1106
1107 sql.push_str("SELECT ");
1108
1109 if self.distinct {
1110 sql.push_str("DISTINCT ");
1111 }
1112
1113 let mut all_columns: Vec<String> = self
1115 .columns
1116 .iter()
1117 .map(|c| {
1118 if c == "*" {
1119 c.clone()
1120 } else {
1121 dialect.quote(c)
1122 }
1123 })
1124 .collect();
1125 all_columns.extend(self.window_columns.iter().cloned());
1126
1127 if all_columns.is_empty() {
1128 sql.push('*');
1129 } else {
1130 sql.push_str(&all_columns.join(", "));
1131 }
1132
1133 if let Some(ref table) = self.from_table {
1134 sql.push_str(" FROM ");
1135 sql.push_str(&dialect.quote(table));
1136 } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1137 sql.push_str(" FROM (");
1139 sql.push_str(subquery);
1140 sql.push_str(") AS ");
1141 sql.push_str(&dialect.quote(alias));
1142 }
1143
1144 let mut unused_params = Vec::new();
1147 sql.push_str(&self.render_joins(&*dialect, &mut unused_params));
1148
1149 if !self.wheres.is_empty() {
1150 sql.push_str(" WHERE ");
1151 sql.push_str(&self.wheres[0]);
1153 for w in &self.wheres[1..] {
1154 if w.starts_with("OR ") {
1155 sql.push(' ');
1156 sql.push_str(w);
1157 } else {
1158 sql.push_str(" AND ");
1159 sql.push_str(w);
1160 }
1161 }
1162 }
1163
1164 if !self.group_by.is_empty() {
1165 sql.push_str(" GROUP BY ");
1166 sql.push_str(
1167 &self
1168 .group_by
1169 .iter()
1170 .map(|c| quote_ident(c))
1171 .collect::<Vec<_>>()
1172 .join(", "),
1173 );
1174 }
1175
1176 if !self.having.is_empty() {
1177 sql.push_str(" HAVING ");
1178 sql.push_str(&self.having.join(" AND "));
1179 }
1180
1181 if !self.order_by.is_empty() {
1182 sql.push_str(" ORDER BY ");
1183 sql.push_str(
1184 &self
1185 .order_by
1186 .iter()
1187 .map(|s| {
1188 if let Some((col, dir)) = s.rsplit_once(' ') {
1190 format!("{} {}", quote_ident(col), dir)
1191 } else {
1192 quote_ident(s)
1193 }
1194 })
1195 .collect::<Vec<_>>()
1196 .join(", "),
1197 );
1198 }
1199
1200 if let Some(limit) = self.limit {
1201 sql.push_str(&format!(" LIMIT {}", limit));
1202 }
1203 if let Some(offset) = self.offset {
1204 sql.push_str(&format!(" OFFSET {}", offset));
1205 }
1206
1207 if self.for_update {
1209 sql.push_str(" FOR UPDATE");
1210 if let Some(ref opts) = self.for_update_options {
1211 sql.push(' ');
1212 sql.push_str(opts);
1213 }
1214 }
1215
1216 sql
1217 }
1218
1219 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
1249 let dialect = match sz_orm_core::get_dialect(db_type) {
1250 Ok(d) => d,
1251 Err(_) => return BuiltQuery::default(),
1252 };
1253
1254 let mut sql = String::new();
1255 let mut params: Vec<Value> = Vec::new();
1256
1257 if !self.ctes.is_empty() {
1259 let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1260 if has_recursive {
1261 sql.push_str("WITH RECURSIVE ");
1262 } else {
1263 sql.push_str("WITH ");
1264 }
1265 let cte_strs: Vec<String> = self
1266 .ctes
1267 .iter()
1268 .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1269 .collect();
1270 sql.push_str(&cte_strs.join(", "));
1271 sql.push(' ');
1272 }
1273
1274 sql.push_str("SELECT ");
1275
1276 if self.distinct {
1277 sql.push_str("DISTINCT ");
1278 }
1279
1280 let mut all_columns: Vec<String> = self
1281 .columns
1282 .iter()
1283 .map(|c| {
1284 if c == "*" {
1285 c.clone()
1286 } else {
1287 dialect.quote(c)
1288 }
1289 })
1290 .collect();
1291 all_columns.extend(self.window_columns.iter().cloned());
1292
1293 if all_columns.is_empty() {
1294 sql.push('*');
1295 } else {
1296 sql.push_str(&all_columns.join(", "));
1297 }
1298
1299 if let Some(ref table) = self.from_table {
1300 sql.push_str(" FROM ");
1301 sql.push_str(&dialect.quote(table));
1302 } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1303 sql.push_str(" FROM (");
1305 sql.push_str(subquery);
1306 sql.push_str(") AS ");
1307 sql.push_str(&dialect.quote(alias));
1308 }
1309
1310 sql.push_str(&self.render_joins(&*dialect, &mut params));
1313
1314 let has_raw = !self.wheres.is_empty();
1316 let has_param = !self.param_wheres.is_empty();
1317 if has_raw || has_param {
1318 sql.push_str(" WHERE ");
1319 let mut first = true;
1320 for w in &self.wheres {
1322 if first {
1323 sql.push_str(w);
1324 first = false;
1325 } else if w.starts_with("OR ") {
1326 sql.push(' ');
1327 sql.push_str(w);
1328 } else {
1329 sql.push_str(" AND ");
1330 sql.push_str(w);
1331 }
1332 }
1333 for pw in &self.param_wheres {
1335 let (conjunction, column, op, vals) = match pw {
1336 ParamWhere::And { column, op, values } => ("AND", column, op, values),
1337 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
1338 };
1339 let expr = if column.is_empty() {
1341 op.clone()
1342 } else {
1343 format!("{}{}", quote_column_dialect(&*dialect, column), op)
1344 };
1345 if first {
1346 sql.push_str(&expr);
1347 first = false;
1348 } else {
1349 sql.push(' ');
1350 sql.push_str(conjunction);
1351 sql.push(' ');
1352 sql.push_str(&expr);
1353 }
1354 params.extend(vals.iter().cloned());
1355 }
1356 }
1357
1358 if !self.group_by.is_empty() {
1359 sql.push_str(" GROUP BY ");
1360 sql.push_str(
1361 &self
1362 .group_by
1363 .iter()
1364 .map(|c| quote_ident(c))
1365 .collect::<Vec<_>>()
1366 .join(", "),
1367 );
1368 }
1369
1370 if !self.having.is_empty() {
1371 sql.push_str(" HAVING ");
1372 sql.push_str(&self.having.join(" AND "));
1373 }
1374
1375 if !self.order_by.is_empty() {
1376 sql.push_str(" ORDER BY ");
1377 sql.push_str(
1378 &self
1379 .order_by
1380 .iter()
1381 .map(|s| {
1382 if let Some((col, dir)) = s.rsplit_once(' ') {
1383 format!("{} {}", quote_ident(col), dir)
1384 } else {
1385 quote_ident(s)
1386 }
1387 })
1388 .collect::<Vec<_>>()
1389 .join(", "),
1390 );
1391 }
1392
1393 if let Some(limit) = self.limit {
1394 sql.push_str(&format!(" LIMIT {}", limit));
1395 }
1396 if let Some(offset) = self.offset {
1397 sql.push_str(&format!(" OFFSET {}", offset));
1398 }
1399
1400 if self.for_update {
1401 sql.push_str(" FOR UPDATE");
1402 if let Some(ref opts) = self.for_update_options {
1403 sql.push(' ');
1404 sql.push_str(opts);
1405 }
1406 }
1407
1408 BuiltQuery { sql, params }
1409 }
1410}
1411
1412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1418pub enum SetOperator {
1419 Union,
1421 UnionAll,
1423 Intersect,
1425 Except,
1427}
1428
1429impl SetOperator {
1430 pub fn as_sql(&self) -> &'static str {
1432 match self {
1433 SetOperator::Union => "UNION",
1434 SetOperator::UnionAll => "UNION ALL",
1435 SetOperator::Intersect => "INTERSECT",
1436 SetOperator::Except => "EXCEPT",
1437 }
1438 }
1439}
1440
1441#[derive(Debug, Clone)]
1455pub struct SetQuery {
1456 first: SelectQuery,
1458 rest: Vec<(SetOperator, SelectQuery)>,
1460 order_by: Vec<String>,
1462 limit: Option<u64>,
1464 offset: Option<u64>,
1466}
1467
1468impl SetQuery {
1469 pub fn new(first: SelectQuery, op: SetOperator, second: SelectQuery) -> Self {
1471 Self {
1472 first,
1473 rest: vec![(op, second)],
1474 order_by: Vec::new(),
1475 limit: None,
1476 offset: None,
1477 }
1478 }
1479
1480 pub fn union(mut self, other: SelectQuery) -> Self {
1482 self.rest.push((SetOperator::Union, other));
1483 self
1484 }
1485
1486 pub fn union_all(mut self, other: SelectQuery) -> Self {
1488 self.rest.push((SetOperator::UnionAll, other));
1489 self
1490 }
1491
1492 pub fn intersect(mut self, other: SelectQuery) -> Self {
1494 self.rest.push((SetOperator::Intersect, other));
1495 self
1496 }
1497
1498 pub fn except(mut self, other: SelectQuery) -> Self {
1500 self.rest.push((SetOperator::Except, other));
1501 self
1502 }
1503
1504 pub fn order_by(mut self, column: &str, asc: bool) -> Self {
1506 let dir = if asc { "ASC" } else { "DESC" };
1507 self.order_by.push(format!("{} {}", column, dir));
1508 self
1509 }
1510
1511 pub fn limit(mut self, n: u64) -> Self {
1513 self.limit = Some(n);
1514 self
1515 }
1516
1517 pub fn offset(mut self, n: u64) -> Self {
1519 self.offset = Some(n);
1520 self
1521 }
1522
1523 pub fn build(self, db_type: DbType) -> String {
1527 let mut sql = self.first.build(db_type);
1528 for (op, query) in &self.rest {
1529 sql.push(' ');
1530 sql.push_str(op.as_sql());
1531 sql.push(' ');
1532 sql.push_str(&query.clone().build(db_type));
1533 }
1534 if !self.order_by.is_empty() {
1535 sql.push_str(" ORDER BY ");
1536 sql.push_str(
1537 &self
1538 .order_by
1539 .iter()
1540 .map(|s| {
1541 if let Some((col, dir)) = s.rsplit_once(' ') {
1542 format!("{} {}", quote_ident(col), dir)
1543 } else {
1544 quote_ident(s)
1545 }
1546 })
1547 .collect::<Vec<_>>()
1548 .join(", "),
1549 );
1550 }
1551 if let Some(limit) = self.limit {
1552 sql.push_str(&format!(" LIMIT {}", limit));
1553 }
1554 if let Some(offset) = self.offset {
1555 sql.push_str(&format!(" OFFSET {}", offset));
1556 }
1557 sql
1558 }
1559}
1560
1561#[derive(Debug, Clone, Default)]
1578pub enum UpsertStrategy {
1579 #[default]
1581 None,
1582 OnConflictDoNothing(Vec<String>),
1584 OnConflictDoUpdate(Vec<String>, Vec<(String, String)>),
1587 OnDuplicateKeyUpdate(Vec<(String, String)>),
1589 Replace,
1591}
1592
1593fn is_mysql_family(db_type: DbType) -> bool {
1595 matches!(
1596 db_type,
1597 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase | DbType::PolarDB
1598 )
1599}
1600
1601fn is_pg_family(db_type: DbType) -> bool {
1603 matches!(
1604 db_type,
1605 DbType::PostgreSQL | DbType::Kingbase | DbType::GaussDB | DbType::PolarDB
1606 ) || db_type == DbType::Sqlite
1607}
1608
1609fn render_upsert_clause(strategy: &UpsertStrategy, db_type: DbType) -> Option<String> {
1613 match strategy {
1614 UpsertStrategy::None => None,
1615 UpsertStrategy::OnConflictDoNothing(cols) => {
1616 if is_pg_family(db_type) {
1617 let cols_str = cols
1618 .iter()
1619 .map(|c| quote_ident(c))
1620 .collect::<Vec<_>>()
1621 .join(", ");
1622 Some(format!("ON CONFLICT ({}) DO NOTHING", cols_str))
1623 } else {
1624 None
1625 }
1626 }
1627 UpsertStrategy::OnConflictDoUpdate(cols, assignments) => {
1628 if is_pg_family(db_type) {
1629 let cols_str = cols
1630 .iter()
1631 .map(|c| quote_ident(c))
1632 .collect::<Vec<_>>()
1633 .join(", ");
1634 let sets = assignments
1635 .iter()
1636 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1637 .collect::<Vec<_>>()
1638 .join(", ");
1639 Some(format!("ON CONFLICT ({}) DO UPDATE SET {}", cols_str, sets))
1640 } else {
1641 None
1642 }
1643 }
1644 UpsertStrategy::OnDuplicateKeyUpdate(assignments) => {
1645 if is_mysql_family(db_type) {
1646 let sets = assignments
1647 .iter()
1648 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1649 .collect::<Vec<_>>()
1650 .join(", ");
1651 Some(format!("ON DUPLICATE KEY UPDATE {}", sets))
1652 } else {
1653 None
1654 }
1655 }
1656 UpsertStrategy::Replace => None, }
1658}
1659
1660fn render_returning_clause(columns: &Option<Vec<String>>, db_type: DbType) -> Option<String> {
1667 let cols = columns.as_ref()?;
1668 if cols.is_empty() {
1669 return None;
1670 }
1671 if is_mysql_family(db_type) {
1673 return None;
1674 }
1675 let dialect = sz_orm_core::get_dialect(db_type).ok()?;
1676 let quoted: Vec<String> = cols
1677 .iter()
1678 .map(|c| {
1679 if c == "*" {
1680 c.clone()
1681 } else {
1682 dialect.quote(c)
1683 }
1684 })
1685 .collect();
1686 Some(format!("RETURNING {}", quoted.join(", ")))
1687}
1688
1689#[derive(Debug, Clone, Default)]
1691pub struct InsertQuery {
1692 table: Option<String>,
1693 columns: Vec<String>,
1694 values: Vec<String>,
1695 upsert: UpsertStrategy,
1697 returning: Option<Vec<String>>,
1699}
1700
1701impl InsertQuery {
1702 pub fn new() -> Self {
1704 Self::default()
1705 }
1706
1707 pub fn into_table(mut self, table: &str) -> Self {
1709 self.table = Some(table.to_string());
1710 self
1711 }
1712
1713 pub fn value(mut self, column: &str, value: &str) -> Self {
1715 self.columns.push(column.to_string());
1716 self.values.push(value.to_string());
1717 self
1718 }
1719
1720 pub fn values(mut self, pairs: &[(&str, &str)]) -> Self {
1722 for (c, v) in pairs {
1723 self.columns.push(c.to_string());
1724 self.values.push(v.to_string());
1725 }
1726 self
1727 }
1728
1729 pub fn on_conflict_do_nothing(mut self, conflict_cols: &[&str]) -> Self {
1753 self.upsert = UpsertStrategy::OnConflictDoNothing(
1754 conflict_cols.iter().map(|s| s.to_string()).collect(),
1755 );
1756 self
1757 }
1758
1759 pub fn on_conflict_do_update(
1784 mut self,
1785 conflict_cols: &[&str],
1786 assignments: &[(&str, &str)],
1787 ) -> Self {
1788 self.upsert = UpsertStrategy::OnConflictDoUpdate(
1789 conflict_cols.iter().map(|s| s.to_string()).collect(),
1790 assignments
1791 .iter()
1792 .map(|(c, v)| (c.to_string(), v.to_string()))
1793 .collect(),
1794 );
1795 self
1796 }
1797
1798 pub fn on_duplicate_key_update(mut self, assignments: &[(&str, &str)]) -> Self {
1820 self.upsert = UpsertStrategy::OnDuplicateKeyUpdate(
1821 assignments
1822 .iter()
1823 .map(|(c, v)| (c.to_string(), v.to_string()))
1824 .collect(),
1825 );
1826 self
1827 }
1828
1829 pub fn replace(mut self) -> Self {
1834 self.upsert = UpsertStrategy::Replace;
1835 self
1836 }
1837
1838 pub fn returning(mut self, columns: &[&str]) -> Self {
1858 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
1859 self
1860 }
1861
1862 pub fn returning_all(mut self) -> Self {
1864 self.returning = Some(vec!["*".to_string()]);
1865 self
1866 }
1867
1868 pub fn build(self) -> String {
1880 let table = self.table.unwrap_or_default();
1881 if table.is_empty() || self.columns.is_empty() {
1882 return String::new();
1883 }
1884
1885 let cols: Vec<String> = self.columns.iter().map(|c| quote_ident(c)).collect();
1886 let vals: Vec<String> = self.values.iter().map(|v| v.to_string()).collect();
1887
1888 let verb = match &self.upsert {
1890 UpsertStrategy::Replace => "REPLACE INTO",
1891 _ => "INSERT INTO",
1892 };
1893 let mut sql = format!(
1894 "{} {} ({}) VALUES ({})",
1895 verb,
1896 quote_ident(&table),
1897 cols.join(", "),
1898 vals.join(", ")
1899 );
1900
1901 if let UpsertStrategy::OnDuplicateKeyUpdate(assignments) = &self.upsert {
1903 let sets = assignments
1904 .iter()
1905 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1906 .collect::<Vec<_>>()
1907 .join(", ");
1908 sql.push_str(&format!(" ON DUPLICATE KEY UPDATE {}", sets));
1909 }
1910
1911 sql
1912 }
1913
1914 pub fn build_with_dialect(self, db_type: DbType) -> String {
1922 let dialect = match sz_orm_core::get_dialect(db_type) {
1923 Ok(d) => d,
1924 Err(_) => return String::new(),
1925 };
1926
1927 let table = self.table.unwrap_or_default();
1928 if table.is_empty() || self.columns.is_empty() {
1929 return String::new();
1930 }
1931
1932 let cols: Vec<String> = self.columns.iter().map(|c| dialect.quote(c)).collect();
1933
1934 let verb = match (&self.upsert, db_type) {
1936 (UpsertStrategy::Replace, dt) if is_mysql_family(dt) => "REPLACE INTO",
1937 (UpsertStrategy::Replace, DbType::Sqlite) => "INSERT OR REPLACE INTO",
1938 _ => "INSERT INTO",
1939 };
1940 let mut sql = format!(
1941 "{} {} ({}) VALUES ({})",
1942 verb,
1943 dialect.quote(&table),
1944 cols.join(", "),
1945 self.values.join(", ")
1946 );
1947
1948 if let Some(clause) = render_upsert_clause(&self.upsert, db_type) {
1950 sql.push(' ');
1951 sql.push_str(&clause);
1952 }
1953
1954 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
1956 sql.push(' ');
1957 sql.push_str(&clause);
1958 }
1959
1960 sql
1961 }
1962}
1963
1964#[derive(Debug, Clone, Default)]
1966pub struct UpdateQuery {
1967 table: Option<String>,
1968 sets: Vec<(String, String)>,
1969 wheres: Vec<String>,
1970 param_wheres: Vec<ParamWhere>,
1972 returning: Option<Vec<String>>,
1974}
1975
1976impl UpdateQuery {
1977 pub fn new() -> Self {
1979 Self::default()
1980 }
1981
1982 pub fn table(mut self, table: &str) -> Self {
1984 self.table = Some(table.to_string());
1985 self
1986 }
1987
1988 pub fn set(mut self, column: &str, value: &str) -> Self {
1990 self.sets.push((column.to_string(), value.to_string()));
1991 self
1992 }
1993
1994 pub fn sets(mut self, pairs: &[(&str, &str)]) -> Self {
1996 for (c, v) in pairs {
1997 self.sets.push((c.to_string(), v.to_string()));
1998 }
1999 self
2000 }
2001
2002 pub fn where_clause(mut self, condition: &str) -> Self {
2008 check_where_injection(condition);
2009 self.wheres.push(condition.to_string());
2010 self
2011 }
2012
2013 pub fn where_eq(mut self, column: &str, value: Value) -> Self {
2015 self.param_wheres.push(ParamWhere::And {
2016 column: column.to_string(),
2017 op: " = ?".to_string(),
2018 values: vec![value],
2019 });
2020 self
2021 }
2022
2023 pub fn where_ne(mut self, column: &str, value: Value) -> Self {
2025 self.param_wheres.push(ParamWhere::And {
2026 column: column.to_string(),
2027 op: " <> ?".to_string(),
2028 values: vec![value],
2029 });
2030 self
2031 }
2032
2033 pub fn where_gt(mut self, column: &str, value: Value) -> Self {
2035 self.param_wheres.push(ParamWhere::And {
2036 column: column.to_string(),
2037 op: " > ?".to_string(),
2038 values: vec![value],
2039 });
2040 self
2041 }
2042
2043 pub fn where_ge(mut self, column: &str, value: Value) -> Self {
2045 self.param_wheres.push(ParamWhere::And {
2046 column: column.to_string(),
2047 op: " >= ?".to_string(),
2048 values: vec![value],
2049 });
2050 self
2051 }
2052
2053 pub fn where_lt(mut self, column: &str, value: Value) -> Self {
2055 self.param_wheres.push(ParamWhere::And {
2056 column: column.to_string(),
2057 op: " < ?".to_string(),
2058 values: vec![value],
2059 });
2060 self
2061 }
2062
2063 pub fn where_le(mut self, column: &str, value: Value) -> Self {
2065 self.param_wheres.push(ParamWhere::And {
2066 column: column.to_string(),
2067 op: " <= ?".to_string(),
2068 values: vec![value],
2069 });
2070 self
2071 }
2072
2073 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2075 self.param_wheres.push(ParamWhere::And {
2076 column: column.to_string(),
2077 op: " LIKE ?".to_string(),
2078 values: vec![pattern],
2079 });
2080 self
2081 }
2082
2083 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2085 let (column, op) = if values.is_empty() {
2086 (String::new(), "1 = 0".to_string())
2087 } else {
2088 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2089 (
2090 column.to_string(),
2091 format!(" IN ({})", placeholders.join(", ")),
2092 )
2093 };
2094 self.param_wheres
2095 .push(ParamWhere::And { column, op, values });
2096 self
2097 }
2098
2099 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2101 self.param_wheres.push(ParamWhere::And {
2102 column: column.to_string(),
2103 op: " BETWEEN ? AND ?".to_string(),
2104 values: vec![low, high],
2105 });
2106 self
2107 }
2108
2109 pub fn where_null(mut self, column: &str) -> Self {
2111 self.param_wheres.push(ParamWhere::And {
2112 column: column.to_string(),
2113 op: " IS NULL".to_string(),
2114 values: vec![],
2115 });
2116 self
2117 }
2118
2119 pub fn where_not_null(mut self, column: &str) -> Self {
2121 self.param_wheres.push(ParamWhere::And {
2122 column: column.to_string(),
2123 op: " IS NOT NULL".to_string(),
2124 values: vec![],
2125 });
2126 self
2127 }
2128
2129 pub fn returning(mut self, columns: &[&str]) -> Self {
2134 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2135 self
2136 }
2137
2138 pub fn returning_all(mut self) -> Self {
2140 self.returning = Some(vec!["*".to_string()]);
2141 self
2142 }
2143
2144 pub fn build(self) -> String {
2150 let table = self.table.unwrap_or_default();
2151 if table.is_empty() || self.sets.is_empty() {
2152 return String::new();
2153 }
2154
2155 let set_str: Vec<String> = self
2156 .sets
2157 .iter()
2158 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
2159 .collect();
2160
2161 let mut sql = format!("UPDATE {} SET {}", quote_ident(&table), set_str.join(", "));
2162
2163 if !self.wheres.is_empty() {
2164 sql.push_str(" WHERE ");
2165 sql.push_str(&self.wheres.join(" AND "));
2166 }
2167
2168 sql
2169 }
2170
2171 pub fn build_with_dialect(self, db_type: DbType) -> String {
2173 let dialect = match sz_orm_core::get_dialect(db_type) {
2174 Ok(d) => d,
2175 Err(_) => return String::new(),
2176 };
2177
2178 let table = self.table.unwrap_or_default();
2179 if table.is_empty() || self.sets.is_empty() {
2180 return String::new();
2181 }
2182
2183 let set_str: Vec<String> = self
2184 .sets
2185 .iter()
2186 .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2187 .collect();
2188
2189 let mut sql = format!(
2190 "UPDATE {} SET {}",
2191 dialect.quote(&table),
2192 set_str.join(", ")
2193 );
2194
2195 if !self.wheres.is_empty() {
2196 sql.push_str(" WHERE ");
2197 sql.push_str(&self.wheres.join(" AND "));
2198 }
2199
2200 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2202 sql.push(' ');
2203 sql.push_str(&clause);
2204 }
2205
2206 sql
2207 }
2208
2209 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2215 let dialect = match sz_orm_core::get_dialect(db_type) {
2216 Ok(d) => d,
2217 Err(_) => return BuiltQuery::default(),
2218 };
2219
2220 let table = self.table.unwrap_or_default();
2221 if table.is_empty() || self.sets.is_empty() {
2222 return BuiltQuery::default();
2223 }
2224
2225 let set_str: Vec<String> = self
2226 .sets
2227 .iter()
2228 .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2229 .collect();
2230
2231 let mut sql = format!(
2232 "UPDATE {} SET {}",
2233 dialect.quote(&table),
2234 set_str.join(", ")
2235 );
2236 let mut params: Vec<Value> = Vec::new();
2237
2238 let has_raw = !self.wheres.is_empty();
2239 let has_param = !self.param_wheres.is_empty();
2240 if has_raw || has_param {
2241 sql.push_str(" WHERE ");
2242 let mut first = true;
2243 for w in &self.wheres {
2244 if first {
2245 sql.push_str(w);
2246 first = false;
2247 } else {
2248 sql.push_str(" AND ");
2249 sql.push_str(w);
2250 }
2251 }
2252 for pw in &self.param_wheres {
2253 let (conjunction, column, op, vals) = match pw {
2254 ParamWhere::And { column, op, values } => ("AND", column, op, values),
2255 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2256 };
2257 let expr = if column.is_empty() {
2259 op.clone()
2260 } else {
2261 format!("{}{}", quote_column_dialect(&*dialect, column), op)
2262 };
2263 if first {
2264 sql.push_str(&expr);
2265 first = false;
2266 } else {
2267 sql.push(' ');
2268 sql.push_str(conjunction);
2269 sql.push(' ');
2270 sql.push_str(&expr);
2271 }
2272 params.extend(vals.iter().cloned());
2273 }
2274 }
2275
2276 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2278 sql.push(' ');
2279 sql.push_str(&clause);
2280 }
2281
2282 BuiltQuery { sql, params }
2283 }
2284}
2285
2286#[derive(Debug, Clone, Default)]
2288pub struct DeleteQuery {
2289 table: Option<String>,
2290 wheres: Vec<String>,
2291 param_wheres: Vec<ParamWhere>,
2293 returning: Option<Vec<String>>,
2295}
2296
2297impl DeleteQuery {
2298 pub fn new() -> Self {
2300 Self::default()
2301 }
2302
2303 pub fn from_table(mut self, table: &str) -> Self {
2305 self.table = Some(table.to_string());
2306 self
2307 }
2308
2309 pub fn where_clause(mut self, condition: &str) -> Self {
2315 check_where_injection(condition);
2316 self.wheres.push(condition.to_string());
2317 self
2318 }
2319
2320 pub fn where_eq(mut self, column: &str, value: Value) -> Self {
2322 self.param_wheres.push(ParamWhere::And {
2323 column: column.to_string(),
2324 op: " = ?".to_string(),
2325 values: vec![value],
2326 });
2327 self
2328 }
2329
2330 pub fn where_ne(mut self, column: &str, value: Value) -> Self {
2332 self.param_wheres.push(ParamWhere::And {
2333 column: column.to_string(),
2334 op: " <> ?".to_string(),
2335 values: vec![value],
2336 });
2337 self
2338 }
2339
2340 pub fn where_gt(mut self, column: &str, value: Value) -> Self {
2342 self.param_wheres.push(ParamWhere::And {
2343 column: column.to_string(),
2344 op: " > ?".to_string(),
2345 values: vec![value],
2346 });
2347 self
2348 }
2349
2350 pub fn where_ge(mut self, column: &str, value: Value) -> Self {
2352 self.param_wheres.push(ParamWhere::And {
2353 column: column.to_string(),
2354 op: " >= ?".to_string(),
2355 values: vec![value],
2356 });
2357 self
2358 }
2359
2360 pub fn where_lt(mut self, column: &str, value: Value) -> Self {
2362 self.param_wheres.push(ParamWhere::And {
2363 column: column.to_string(),
2364 op: " < ?".to_string(),
2365 values: vec![value],
2366 });
2367 self
2368 }
2369
2370 pub fn where_le(mut self, column: &str, value: Value) -> Self {
2372 self.param_wheres.push(ParamWhere::And {
2373 column: column.to_string(),
2374 op: " <= ?".to_string(),
2375 values: vec![value],
2376 });
2377 self
2378 }
2379
2380 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2382 self.param_wheres.push(ParamWhere::And {
2383 column: column.to_string(),
2384 op: " LIKE ?".to_string(),
2385 values: vec![pattern],
2386 });
2387 self
2388 }
2389
2390 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2392 let (column, op) = if values.is_empty() {
2393 (String::new(), "1 = 0".to_string())
2394 } else {
2395 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2396 (
2397 column.to_string(),
2398 format!(" IN ({})", placeholders.join(", ")),
2399 )
2400 };
2401 self.param_wheres
2402 .push(ParamWhere::And { column, op, values });
2403 self
2404 }
2405
2406 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2408 self.param_wheres.push(ParamWhere::And {
2409 column: column.to_string(),
2410 op: " BETWEEN ? AND ?".to_string(),
2411 values: vec![low, high],
2412 });
2413 self
2414 }
2415
2416 pub fn where_null(mut self, column: &str) -> Self {
2418 self.param_wheres.push(ParamWhere::And {
2419 column: column.to_string(),
2420 op: " IS NULL".to_string(),
2421 values: vec![],
2422 });
2423 self
2424 }
2425
2426 pub fn where_not_null(mut self, column: &str) -> Self {
2428 self.param_wheres.push(ParamWhere::And {
2429 column: column.to_string(),
2430 op: " IS NOT NULL".to_string(),
2431 values: vec![],
2432 });
2433 self
2434 }
2435
2436 pub fn returning(mut self, columns: &[&str]) -> Self {
2441 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2442 self
2443 }
2444
2445 pub fn returning_all(mut self) -> Self {
2447 self.returning = Some(vec!["*".to_string()]);
2448 self
2449 }
2450
2451 pub fn build(self) -> String {
2457 let table = self.table.unwrap_or_default();
2458 if table.is_empty() {
2459 return String::new();
2460 }
2461
2462 let mut sql = format!("DELETE FROM {}", quote_ident(&table));
2463
2464 if !self.wheres.is_empty() {
2465 sql.push_str(" WHERE ");
2466 sql.push_str(&self.wheres.join(" AND "));
2467 }
2468
2469 sql
2470 }
2471
2472 pub fn build_with_dialect(self, db_type: DbType) -> String {
2474 let dialect = match sz_orm_core::get_dialect(db_type) {
2475 Ok(d) => d,
2476 Err(_) => return String::new(),
2477 };
2478
2479 let table = self.table.unwrap_or_default();
2480 if table.is_empty() {
2481 return String::new();
2482 }
2483
2484 let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2485
2486 if !self.wheres.is_empty() {
2487 sql.push_str(" WHERE ");
2488 sql.push_str(&self.wheres.join(" AND "));
2489 }
2490
2491 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2493 sql.push(' ');
2494 sql.push_str(&clause);
2495 }
2496
2497 sql
2498 }
2499
2500 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2505 let dialect = match sz_orm_core::get_dialect(db_type) {
2506 Ok(d) => d,
2507 Err(_) => return BuiltQuery::default(),
2508 };
2509
2510 let table = self.table.unwrap_or_default();
2511 if table.is_empty() {
2512 return BuiltQuery::default();
2513 }
2514
2515 let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2516 let mut params: Vec<Value> = Vec::new();
2517
2518 let has_raw = !self.wheres.is_empty();
2519 let has_param = !self.param_wheres.is_empty();
2520 if has_raw || has_param {
2521 sql.push_str(" WHERE ");
2522 let mut first = true;
2523 for w in &self.wheres {
2524 if first {
2525 sql.push_str(w);
2526 first = false;
2527 } else {
2528 sql.push_str(" AND ");
2529 sql.push_str(w);
2530 }
2531 }
2532 for pw in &self.param_wheres {
2533 let (conjunction, column, op, vals) = match pw {
2534 ParamWhere::And { column, op, values } => ("AND", column, op, values),
2535 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2536 };
2537 let expr = if column.is_empty() {
2539 op.clone()
2540 } else {
2541 format!("{}{}", quote_column_dialect(&*dialect, column), op)
2542 };
2543 if first {
2544 sql.push_str(&expr);
2545 first = false;
2546 } else {
2547 sql.push(' ');
2548 sql.push_str(conjunction);
2549 sql.push(' ');
2550 sql.push_str(&expr);
2551 }
2552 params.extend(vals.iter().cloned());
2553 }
2554 }
2555
2556 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2558 sql.push(' ');
2559 sql.push_str(&clause);
2560 }
2561
2562 BuiltQuery { sql, params }
2563 }
2564}
2565
2566#[cfg(test)]
2567mod tests {
2568 use super::*;
2569
2570 #[test]
2573 fn test_select_basic() {
2574 let sql = Query::select()
2575 .column("id")
2576 .column("name")
2577 .from("users")
2578 .build(DbType::MySQL);
2579 assert!(sql.starts_with("SELECT "));
2580 assert!(sql.contains("`id`"));
2581 assert!(sql.contains("`name`"));
2582 assert!(sql.contains("FROM `users`"));
2583 }
2584
2585 #[test]
2586 fn test_select_star() {
2587 let sql = Query::select()
2588 .all_columns()
2589 .from("users")
2590 .build(DbType::MySQL);
2591 assert!(sql.contains("SELECT *"));
2592 assert!(sql.contains("FROM `users`"));
2593 }
2594
2595 #[test]
2596 fn test_select_distinct() {
2597 let sql = Query::select()
2598 .distinct()
2599 .column("name")
2600 .from("users")
2601 .build(DbType::MySQL);
2602 assert!(sql.contains("SELECT DISTINCT"));
2603 }
2604
2605 #[test]
2606 fn test_select_with_where() {
2607 let sql = Query::select()
2608 .column("id")
2609 .from("users")
2610 .where_clause("age > 18")
2611 .where_clause("status = 'active'")
2612 .build(DbType::MySQL);
2613 assert!(sql.contains("WHERE age > 18 AND status = 'active'"));
2614 }
2615
2616 #[test]
2617
2618 fn test_select_with_inner_join() {
2619 let sql = Query::select()
2620 .column("u.id")
2621 .from("users u")
2622 .inner_join("orders o", "u.id = o.user_id")
2623 .build(DbType::MySQL);
2624 assert!(sql.contains("INNER JOIN `orders` o ON u.id = o.user_id"));
2625 }
2626
2627 #[test]
2628 fn test_select_with_left_join() {
2629 let sql = Query::select()
2630 .column("u.id")
2631 .from("users u")
2632 .left_join("profiles p", "u.id = p.user_id")
2633 .build(DbType::MySQL);
2634 assert!(sql.contains("LEFT JOIN `profiles` p ON u.id = p.user_id"));
2635 }
2636
2637 #[test]
2638 fn test_select_with_order_by() {
2639 let sql = Query::select()
2640 .column("id")
2641 .from("users")
2642 .order_by("created_at", true)
2643 .order_by("id", false)
2644 .build(DbType::MySQL);
2645 assert!(sql.contains("ORDER BY `created_at` ASC, `id` DESC"));
2646 }
2647
2648 #[test]
2649 fn test_select_with_limit_offset() {
2650 let sql = Query::select()
2651 .column("id")
2652 .from("users")
2653 .limit(10)
2654 .offset(20)
2655 .build(DbType::MySQL);
2656 assert!(sql.contains("LIMIT 10"));
2657 assert!(sql.contains("OFFSET 20"));
2658 }
2659
2660 #[test]
2661 fn test_select_paginate() {
2662 let sql = Query::select()
2663 .column("id")
2664 .from("users")
2665 .paginate(3, 20)
2666 .build(DbType::MySQL);
2667 assert!(sql.contains("LIMIT 20"));
2669 assert!(sql.contains("OFFSET 40"));
2670 }
2671
2672 #[test]
2673 fn test_select_with_group_by_having() {
2674 let sql = Query::select()
2675 .column("status")
2676 .from("users")
2677 .group_by("status")
2678 .having("COUNT(*) > 5")
2679 .build(DbType::MySQL);
2680 assert!(sql.contains("GROUP BY `status`"));
2681 assert!(sql.contains("HAVING COUNT(*) > 5"));
2682 }
2683
2684 #[test]
2685 fn test_select_postgres_dialect() {
2686 let sql = Query::select()
2687 .column("id")
2688 .from("users")
2689 .build(DbType::PostgreSQL);
2690 assert!(sql.contains("\"id\""));
2691 assert!(sql.contains("FROM \"users\""));
2692 }
2693
2694 #[test]
2695 fn test_select_sqlite_dialect() {
2696 let sql = Query::select()
2697 .column("id")
2698 .from("users")
2699 .build(DbType::Sqlite);
2700 assert!(sql.contains("\"id\""));
2701 }
2702
2703 #[test]
2704 fn test_select_multiple_joins() {
2705 let sql = Query::select()
2706 .column("u.id")
2707 .from("users u")
2708 .inner_join("orders o", "u.id = o.user_id")
2709 .left_join("profiles p", "u.id = p.user_id")
2710 .build(DbType::MySQL);
2711 assert!(sql.contains("INNER JOIN `orders` o"));
2712 assert!(sql.contains("LEFT JOIN `profiles` p"));
2713 }
2714
2715 #[test]
2716 fn test_select_columns_multiple() {
2717 let sql = Query::select()
2718 .columns(&["id", "name", "email"])
2719 .from("users")
2720 .build(DbType::MySQL);
2721 assert!(sql.contains("`id`, `name`, `email`"));
2722 }
2723
2724 #[test]
2725 fn test_select_no_columns_defaults_star() {
2726 let sql = Query::select().from("users").build(DbType::MySQL);
2727 assert!(sql.contains("SELECT *"));
2728 }
2729
2730 #[test]
2733 fn test_insert_basic() {
2734 let sql = Query::insert()
2735 .into_table("users")
2736 .value("name", "'Alice'")
2737 .value("age", "30")
2738 .build();
2739 assert!(sql.starts_with("INSERT INTO `users`"));
2740 assert!(sql.contains("`name`, `age`"));
2741 assert!(sql.contains("'Alice', 30"));
2742 }
2743
2744 #[test]
2745 fn test_insert_values_batch() {
2746 let sql = Query::insert()
2747 .into_table("users")
2748 .values(&[("name", "'Bob'"), ("age", "25"), ("email", "'bob@x.com'")])
2749 .build();
2750 assert!(sql.contains("`name`, `age`, `email`"));
2751 assert!(sql.contains("'Bob', 25, 'bob@x.com'"));
2752 }
2753
2754 #[test]
2755 fn test_insert_empty_returns_empty() {
2756 let sql = Query::insert().into_table("users").build();
2757 assert_eq!(sql, "");
2758 }
2759
2760 #[test]
2761 fn test_insert_with_dialect() {
2762 let sql = Query::insert()
2763 .into_table("users")
2764 .value("name", "'Alice'")
2765 .build_with_dialect(DbType::PostgreSQL);
2766 assert!(sql.contains("\"name\""));
2767 assert!(sql.contains("\"users\""));
2768 }
2769
2770 #[test]
2773 fn test_update_basic() {
2774 let sql = Query::update()
2775 .table("users")
2776 .set("name", "'Bob'")
2777 .where_clause("id = 1")
2778 .build();
2779 assert!(sql.starts_with("UPDATE `users` SET"));
2780 assert!(sql.contains("`name` = 'Bob'"));
2781 assert!(sql.contains("WHERE id = 1"));
2782 }
2783
2784 #[test]
2785 fn test_update_multiple_sets() {
2786 let sql = Query::update()
2787 .table("users")
2788 .sets(&[("name", "'Bob'"), ("age", "30")])
2789 .where_clause("id = 1")
2790 .build();
2791 assert!(sql.contains("`name` = 'Bob', `age` = 30"));
2792 }
2793
2794 #[test]
2795 fn test_update_no_where() {
2796 let sql = Query::update()
2797 .table("users")
2798 .set("status", "'active'")
2799 .build();
2800 assert!(sql.contains("UPDATE `users` SET `status` = 'active'"));
2801 assert!(!sql.contains("WHERE"));
2802 }
2803
2804 #[test]
2805 fn test_update_empty_returns_empty() {
2806 let sql = Query::update().table("users").build();
2807 assert_eq!(sql, "");
2808 }
2809
2810 #[test]
2811 fn test_update_with_dialect() {
2812 let sql = Query::update()
2813 .table("users")
2814 .set("name", "'Bob'")
2815 .build_with_dialect(DbType::PostgreSQL);
2816 assert!(sql.contains("\"users\""));
2817 assert!(sql.contains("\"name\""));
2818 }
2819
2820 #[test]
2823 fn test_delete_basic() {
2824 let sql = Query::delete()
2825 .from_table("users")
2826 .where_clause("id = 1")
2827 .build();
2828 assert!(sql.starts_with("DELETE FROM `users`"));
2829 assert!(sql.contains("WHERE id = 1"));
2830 }
2831
2832 #[test]
2833 fn test_delete_no_where() {
2834 let sql = Query::delete().from_table("users").build();
2835 assert!(sql.contains("DELETE FROM `users`"));
2836 assert!(!sql.contains("WHERE"));
2837 }
2838
2839 #[test]
2840 fn test_delete_multiple_wheres() {
2841 let sql = Query::delete()
2842 .from_table("users")
2843 .where_clause("id > 100")
2844 .where_clause("status = 'inactive'")
2845 .build();
2846 assert!(sql.contains("WHERE id > 100 AND status = 'inactive'"));
2847 }
2848
2849 #[test]
2850 fn test_delete_empty_returns_empty() {
2851 let sql = Query::delete().build();
2852 assert_eq!(sql, "");
2853 }
2854
2855 #[test]
2856 fn test_delete_with_dialect() {
2857 let sql = Query::delete()
2858 .from_table("users")
2859 .where_clause("id = 1")
2860 .build_with_dialect(DbType::PostgreSQL);
2861 assert!(sql.contains("\"users\""));
2862 }
2863
2864 #[test]
2867 fn test_full_crud_flow() {
2868 let insert = Query::insert()
2870 .into_table("users")
2871 .value("name", "'Alice'")
2872 .value("age", "30")
2873 .build();
2874 assert!(insert.contains("INSERT INTO"));
2875
2876 let select = Query::select()
2878 .column("id")
2879 .column("name")
2880 .from("users")
2881 .where_clause("age > 18")
2882 .order_by("id", true)
2883 .limit(10)
2884 .build(DbType::MySQL);
2885 assert!(select.contains("SELECT"));
2886 assert!(select.contains("FROM"));
2887 assert!(select.contains("WHERE"));
2888 assert!(select.contains("ORDER BY"));
2889 assert!(select.contains("LIMIT"));
2890
2891 let update = Query::update()
2893 .table("users")
2894 .set("name", "'Bob'")
2895 .where_clause("id = 1")
2896 .build();
2897 assert!(update.contains("UPDATE"));
2898 assert!(update.contains("SET"));
2899 assert!(update.contains("WHERE"));
2900
2901 let delete = Query::delete()
2903 .from_table("users")
2904 .where_clause("id = 1")
2905 .build();
2906 assert!(delete.contains("DELETE FROM"));
2907 }
2908
2909 #[test]
2910 fn test_complex_select_query() {
2911 let sql = Query::select()
2912 .distinct()
2913 .columns(&["u.id", "u.name", "o.total"])
2914 .from("users u")
2915 .inner_join("orders o", "u.id = o.user_id")
2916 .where_clause("u.status = 'active'")
2917 .where_clause("o.total > 100")
2918 .group_by("u.id")
2919 .having("SUM(o.total) > 1000")
2920 .order_by("u.id", true)
2921 .limit(20)
2922 .offset(40)
2923 .build(DbType::MySQL);
2924
2925 assert!(sql.contains("SELECT DISTINCT"));
2926 assert!(sql.contains("INNER JOIN `orders` o"));
2927 assert!(sql.contains("WHERE u.status = 'active' AND o.total > 100"));
2928 assert!(sql.contains("GROUP BY"));
2929 assert!(sql.contains("HAVING SUM(o.total) > 1000"));
2930 assert!(sql.contains("ORDER BY `u`.`id` ASC"));
2931 assert!(sql.contains("LIMIT 20"));
2932 assert!(sql.contains("OFFSET 40"));
2933 }
2934
2935 #[test]
2938 #[should_panic(expected = "SQL injection detected")]
2939 fn test_select_where_rejects_semicolon_drop() {
2940 let _ = Query::select()
2941 .column("id")
2942 .from("users")
2943 .where_clause("1=1; DROP TABLE users")
2944 .build(DbType::MySQL);
2945 }
2946
2947 #[test]
2948 #[should_panic(expected = "SQL injection detected")]
2949 fn test_select_where_rejects_semicolon_space_drop() {
2950 let _ = Query::select()
2951 .column("id")
2952 .from("users")
2953 .where_clause("1=1; DROP TABLE users")
2954 .build(DbType::MySQL);
2955 }
2956
2957 #[test]
2958 #[should_panic(expected = "SQL injection detected")]
2959 fn test_select_where_rejects_line_comment() {
2960 let _ = Query::select()
2961 .column("id")
2962 .from("users")
2963 .where_clause("id = 1 -- DROP TABLE users")
2964 .build(DbType::MySQL);
2965 }
2966
2967 #[test]
2968 #[should_panic(expected = "SQL injection detected")]
2969 fn test_select_where_rejects_block_comment() {
2970 let _ = Query::select()
2971 .column("id")
2972 .from("users")
2973 .where_clause("id = 1 /* comment */ OR 1=1")
2974 .build(DbType::MySQL);
2975 }
2976
2977 #[test]
2978 #[should_panic(expected = "SQL injection detected")]
2979 fn test_update_where_rejects_delete() {
2980 let _ = Query::update()
2981 .table("users")
2982 .set("name", "'x'")
2983 .where_clause("1=1; DELETE FROM users")
2984 .build();
2985 }
2986
2987 #[test]
2988 #[should_panic(expected = "SQL injection detected")]
2989 fn test_update_where_rejects_line_comment() {
2990 let _ = Query::update()
2991 .table("users")
2992 .set("name", "'x'")
2993 .where_clause("id = 1 -- bypass")
2994 .build();
2995 }
2996
2997 #[test]
2998 #[should_panic(expected = "SQL injection detected")]
2999 fn test_delete_where_rejects_drop() {
3000 let _ = Query::delete()
3001 .from_table("users")
3002 .where_clause("1=1; DROP TABLE users")
3003 .build();
3004 }
3005
3006 #[test]
3007 #[should_panic(expected = "SQL injection detected")]
3008 fn test_delete_where_rejects_block_comment() {
3009 let _ = Query::delete()
3010 .from_table("users")
3011 .where_clause("id = 1 /* */ OR 1=1")
3012 .build();
3013 }
3014
3015 #[test]
3016 #[should_panic(expected = "SQL injection detected")]
3017 fn test_delete_where_rejects_line_comment() {
3018 let _ = Query::delete()
3019 .from_table("users")
3020 .where_clause("id = 1--")
3021 .build();
3022 }
3023
3024 #[test]
3025 fn test_safe_where_clauses_pass() {
3026 let sql_str = Query::select()
3028 .column("id")
3029 .from("users")
3030 .where_clause("age > 18")
3031 .where_clause("name = 'Alice;Bob'") .where_clause("id IN (1, 2, 3)")
3033 .where_clause("created_at > '2026-01-01'")
3034 .build(DbType::MySQL);
3035 assert!(!sql_str.is_empty(), "SELECT SQL 不应为空");
3036 assert!(sql_str.contains("age > 18"), "SELECT 应包含 age > 18 条件");
3037 assert!(
3038 sql_str.contains("name = 'Alice;Bob'"),
3039 "SELECT 应包含 name 条件(含分号字面量)"
3040 );
3041 assert!(sql_str.contains("id IN (1, 2, 3)"), "SELECT 应包含 IN 子句");
3042 assert!(
3043 sql_str.contains("created_at > '2026-01-01'"),
3044 "SELECT 应包含日期条件"
3045 );
3046
3047 let sql_str = Query::update()
3048 .table("users")
3049 .set("name", "'x'")
3050 .where_clause("id = 1")
3051 .build();
3052 assert!(!sql_str.is_empty(), "UPDATE SQL 不应为空");
3053 assert!(sql_str.contains("UPDATE"), "应为 UPDATE 语句");
3054 assert!(sql_str.contains("WHERE"), "UPDATE 应包含 WHERE 子句");
3055 assert!(sql_str.contains("id = 1"), "UPDATE WHERE 应包含 id = 1");
3056
3057 let sql_str = Query::delete()
3058 .from_table("users")
3059 .where_clause("id = 1")
3060 .build();
3061 assert!(!sql_str.is_empty(), "DELETE SQL 不应为空");
3062 assert!(sql_str.contains("DELETE"), "应为 DELETE 语句");
3063 assert!(sql_str.contains("WHERE"), "DELETE 应包含 WHERE 子句");
3064 assert!(sql_str.contains("id = 1"), "DELETE WHERE 应包含 id = 1");
3065 }
3066
3067 #[test]
3071 #[should_panic(expected = "SQL injection detected")]
3072 fn test_mutant_block_comment_open_only() {
3073 let _ = Query::select()
3076 .column("id")
3077 .from("users")
3078 .where_clause("id = 1 /* OR 1=1")
3079 .build(DbType::MySQL);
3080 }
3081
3082 #[test]
3083 #[should_panic(expected = "SQL injection detected")]
3084 fn test_mutant_block_comment_close_only() {
3085 let _ = Query::select()
3087 .column("id")
3088 .from("users")
3089 .where_clause("id = 1 */")
3090 .build(DbType::MySQL);
3091 }
3092
3093 #[test]
3094 fn test_mutant_insert_dialect_table_no_columns_returns_empty() {
3095 let sql = Query::insert()
3097 .into_table("users")
3098 .build_with_dialect(DbType::MySQL);
3099 assert_eq!(sql, "", "有表无列时应返回空字符串");
3100 }
3101
3102 #[test]
3103 fn test_mutant_update_dialect_table_no_sets_returns_empty() {
3104 let sql = Query::update()
3106 .table("users")
3107 .build_with_dialect(DbType::MySQL);
3108 assert_eq!(sql, "", "有表无 SET 时应返回空字符串");
3109 }
3110
3111 #[test]
3112 fn test_mutant_update_dialect_no_table_with_sets_returns_empty() {
3113 let sql = Query::update()
3115 .set("name", "'x'")
3116 .build_with_dialect(DbType::MySQL);
3117 assert_eq!(sql, "", "无表有 SET 时应返回空字符串");
3118 }
3119
3120 #[test]
3121 fn test_mutant_delete_dialect_no_table_returns_empty() {
3122 let sql = Query::delete()
3124 .where_clause("id = 1")
3125 .build_with_dialect(DbType::MySQL);
3126 assert_eq!(sql, "", "无表时应返回空字符串");
3127 }
3128
3129 #[test]
3130 fn test_mutant_select_right_join() {
3131 let sql = Query::select()
3133 .column("u.id")
3134 .from("users u")
3135 .right_join("orders o", "u.id = o.user_id")
3136 .build(DbType::MySQL);
3137 assert!(sql.contains("RIGHT JOIN `orders` o ON u.id = o.user_id"));
3138 }
3139
3140 #[test]
3141 fn test_mutant_all_columns_with_extra() {
3142 let sql = Query::select()
3145 .all_columns()
3146 .column("extra")
3147 .from("users")
3148 .build(DbType::MySQL);
3149 assert!(
3150 sql.contains("SELECT *, `extra` FROM `users`"),
3151 "all_columns + column 应在 SELECT 列表中同时包含 * 和 extra,实际: {sql}"
3152 );
3153 }
3154
3155 #[test]
3156 fn test_mutant_update_dialect_no_where_no_where_clause() {
3157 let sql = Query::update()
3160 .table("users")
3161 .set("name", "'x'")
3162 .build_with_dialect(DbType::MySQL);
3163 assert!(
3164 !sql.contains("WHERE"),
3165 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3166 );
3167 }
3168
3169 #[test]
3170 fn test_mutant_delete_dialect_no_where_no_where_clause() {
3171 let sql = Query::delete()
3173 .from_table("users")
3174 .build_with_dialect(DbType::MySQL);
3175 assert!(
3176 !sql.contains("WHERE"),
3177 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3178 );
3179 }
3180
3181 #[test]
3186 fn test_cte_single_with_clause() {
3187 let sql = Query::select()
3188 .column("id")
3189 .column("name")
3190 .from("active_users")
3191 .with_cte(
3192 "active_users",
3193 "SELECT * FROM users WHERE status = 'active'",
3194 )
3195 .build(DbType::MySQL);
3196 assert!(sql.starts_with("WITH active_users AS ("));
3197 assert!(sql.contains("SELECT * FROM users WHERE status = 'active'"));
3198 assert!(sql.contains("SELECT `id`, `name` FROM `active_users`"));
3199 }
3200
3201 #[test]
3202 fn test_cte_multiple_with_clauses() {
3203 let sql = Query::select()
3204 .column("id")
3205 .from("combined")
3206 .with_cte("a", "SELECT id FROM table_a")
3207 .with_cte("b", "SELECT id FROM table_b")
3208 .with_cte("combined", "SELECT id FROM a UNION SELECT id FROM b")
3209 .build(DbType::MySQL);
3210 assert!(sql.starts_with(
3211 "WITH a AS (SELECT id FROM table_a), b AS (SELECT id FROM table_b), combined AS ("
3212 ));
3213 }
3214
3215 #[test]
3216 fn test_cte_recursive_with_clause() {
3217 let sql = Query::select()
3218 .column("id")
3219 .column("parent_id")
3220 .from("tree")
3221 .with_recursive_cte("tree", "SELECT id, parent_id FROM nodes WHERE id = 1")
3222 .build(DbType::MySQL);
3223 assert!(sql.starts_with("WITH RECURSIVE tree AS ("));
3224 }
3225
3226 #[test]
3227 fn test_cte_no_cte_no_with_prefix() {
3228 let sql = Query::select()
3229 .column("id")
3230 .from("users")
3231 .build(DbType::MySQL);
3232 assert!(!sql.contains("WITH"));
3233 assert!(sql.starts_with("SELECT"));
3234 }
3235
3236 #[test]
3239 fn test_window_function_raw_expr() {
3240 let sql = Query::select()
3241 .column("id")
3242 .column("salary")
3243 .from("employees")
3244 .window_function("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn")
3245 .build(DbType::MySQL);
3246 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3247 }
3248
3249 #[test]
3250 fn test_row_number_helper_with_partition() {
3251 let sql = Query::select()
3252 .column("name")
3253 .column("dept")
3254 .from("employees")
3255 .row_number("dept", "salary DESC", "row_num")
3256 .build(DbType::MySQL);
3257 assert!(
3258 sql.contains("ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS row_num")
3259 );
3260 }
3261
3262 #[test]
3263 fn test_row_number_helper_without_partition() {
3264 let sql = Query::select()
3265 .column("name")
3266 .from("employees")
3267 .row_number("", "salary DESC", "rn")
3268 .build(DbType::MySQL);
3269 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3270 assert!(!sql.contains("PARTITION BY"));
3271 }
3272
3273 #[test]
3274 fn test_rank_helper() {
3275 let sql = Query::select()
3276 .column("name")
3277 .from("scores")
3278 .rank("", "score DESC", "rank_num")
3279 .build(DbType::MySQL);
3280 assert!(sql.contains("RANK() OVER (ORDER BY score DESC) AS rank_num"));
3281 }
3282
3283 #[test]
3284 fn test_dense_rank_helper_with_partition() {
3285 let sql = Query::select()
3286 .column("name")
3287 .from("scores")
3288 .dense_rank("class", "score DESC", "dr")
3289 .build(DbType::MySQL);
3290 assert!(sql.contains("DENSE_RANK() OVER (PARTITION BY class ORDER BY score DESC) AS dr"));
3291 }
3292
3293 #[test]
3294 fn test_multiple_window_functions() {
3295 let sql = Query::select()
3296 .column("name")
3297 .column("salary")
3298 .from("employees")
3299 .row_number("dept", "salary DESC", "rn")
3300 .rank("dept", "salary DESC", "rk")
3301 .dense_rank("dept", "salary DESC", "dr")
3302 .build(DbType::MySQL);
3303 assert!(sql.contains("ROW_NUMBER()"));
3304 assert!(sql.contains("RANK()"));
3305 assert!(sql.contains("DENSE_RANK()"));
3306 }
3307
3308 #[test]
3309 fn test_window_function_with_cte_combined() {
3310 let sql = Query::select()
3311 .column("name")
3312 .from("ranked")
3313 .with_cte(
3314 "ranked",
3315 "SELECT name, ROW_NUMBER() OVER (ORDER BY salary) AS rn FROM employees",
3316 )
3317 .where_clause("rn <= 10")
3318 .build(DbType::MySQL);
3319 assert!(sql.starts_with("WITH ranked AS ("));
3320 assert!(sql.contains("FROM `ranked`"));
3321 assert!(sql.contains("WHERE rn <= 10"));
3322 }
3323
3324 #[test]
3327 fn test_for_update_basic() {
3328 let sql = Query::select()
3329 .column("id")
3330 .column("balance")
3331 .from("accounts")
3332 .where_clause("id = 1")
3333 .for_update()
3334 .build(DbType::MySQL);
3335 assert!(sql.ends_with(" FOR UPDATE"));
3336 assert!(sql.contains("WHERE id = 1"));
3337 }
3338
3339 #[test]
3340 fn test_for_update_with_nowait() {
3341 let sql = Query::select()
3342 .column("id")
3343 .from("accounts")
3344 .where_clause("id = 1")
3345 .for_update_with_options("NOWAIT")
3346 .build(DbType::MySQL);
3347 assert!(sql.ends_with(" FOR UPDATE NOWAIT"));
3348 }
3349
3350 #[test]
3351 fn test_for_update_with_skip_locked() {
3352 let sql = Query::select()
3353 .column("id")
3354 .from("accounts")
3355 .where_clause("id = 1")
3356 .for_update_with_options("SKIP LOCKED")
3357 .build(DbType::MySQL);
3358 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3359 }
3360
3361 #[test]
3362 fn test_for_update_with_limit_and_order() {
3363 let sql = Query::select()
3364 .column("id")
3365 .from("jobs")
3366 .order_by("priority", false)
3367 .limit(1)
3368 .for_update_with_options("SKIP LOCKED")
3369 .build(DbType::MySQL);
3370 assert!(sql.contains("ORDER BY `priority` DESC"));
3371 assert!(sql.contains("LIMIT 1"));
3372 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3373 }
3374
3375 #[test]
3376 fn test_no_for_update_by_default() {
3377 let sql = Query::select()
3378 .column("id")
3379 .from("users")
3380 .build(DbType::MySQL);
3381 assert!(!sql.contains("FOR UPDATE"));
3382 }
3383
3384 #[test]
3387 fn test_set_operator_as_sql() {
3388 assert_eq!(SetOperator::Union.as_sql(), "UNION");
3389 assert_eq!(SetOperator::UnionAll.as_sql(), "UNION ALL");
3390 assert_eq!(SetOperator::Intersect.as_sql(), "INTERSECT");
3391 assert_eq!(SetOperator::Except.as_sql(), "EXCEPT");
3392 }
3393
3394 #[test]
3395 fn test_union_basic() {
3396 let q1 = Query::select().column("id").from("active_users");
3397 let q2 = Query::select().column("id").from("pending_users");
3398 let sql = q1.union(q2).build(DbType::MySQL);
3399 assert!(sql.contains("SELECT `id` FROM `active_users`"));
3400 assert!(sql.contains(" UNION "));
3401 assert!(sql.contains("SELECT `id` FROM `pending_users`"));
3402 }
3403
3404 #[test]
3405 fn test_union_all_basic() {
3406 let q1 = Query::select().column("id").from("table_a");
3407 let q2 = Query::select().column("id").from("table_b");
3408 let sql = q1.union_all(q2).build(DbType::MySQL);
3409 assert!(sql.contains(" UNION ALL "));
3410 }
3411
3412 #[test]
3413 fn test_intersect_basic() {
3414 let q1 = Query::select().column("id").from("table_a");
3415 let q2 = Query::select().column("id").from("table_b");
3416 let sql = q1.intersect(q2).build(DbType::MySQL);
3417 assert!(sql.contains(" INTERSECT "));
3418 }
3419
3420 #[test]
3421 fn test_except_basic() {
3422 let q1 = Query::select().column("id").from("table_a");
3423 let q2 = Query::select().column("id").from("table_b");
3424 let sql = q1.except(q2).build(DbType::MySQL);
3425 assert!(sql.contains(" EXCEPT "));
3426 }
3427
3428 #[test]
3429 fn test_union_chained_multiple() {
3430 let q1 = Query::select().column("id").from("t1");
3431 let q2 = Query::select().column("id").from("t2");
3432 let q3 = Query::select().column("id").from("t3");
3433 let sql = q1.union(q2).union(q3).build(DbType::MySQL);
3434 assert_eq!(sql.matches("UNION").count(), 2);
3435 }
3436
3437 #[test]
3438 fn test_union_mixed_operators() {
3439 let q1 = Query::select().column("id").from("t1");
3440 let q2 = Query::select().column("id").from("t2");
3441 let q3 = Query::select().column("id").from("t3");
3442 let sql = q1.union(q2).intersect(q3).build(DbType::MySQL);
3443 assert!(sql.contains(" UNION "));
3444 assert!(sql.contains(" INTERSECT "));
3445 }
3446
3447 #[test]
3448 fn test_union_with_order_by_limit() {
3449 let q1 = Query::select().column("id").from("t1");
3450 let q2 = Query::select().column("id").from("t2");
3451 let sql = q1
3452 .union(q2)
3453 .order_by("id", true)
3454 .limit(10)
3455 .offset(5)
3456 .build(DbType::MySQL);
3457 assert!(sql.contains("ORDER BY `id` ASC"));
3458 assert!(sql.contains("LIMIT 10"));
3459 assert!(sql.contains("OFFSET 5"));
3460 }
3461
3462 #[test]
3463 fn test_union_postgres_dialect() {
3464 let q1 = Query::select().column("id").from("t1");
3465 let q2 = Query::select().column("id").from("t2");
3466 let sql = q1.union(q2).build(DbType::PostgreSQL);
3467 assert!(sql.contains("\"id\""));
3468 assert!(sql.contains(" UNION "));
3469 }
3470
3471 #[test]
3472 fn test_union_with_where_clauses() {
3473 let q1 = Query::select()
3474 .column("id")
3475 .from("active_users")
3476 .where_clause("age > 18");
3477 let q2 = Query::select()
3478 .column("id")
3479 .from("pending_users")
3480 .where_clause("age > 18");
3481 let sql = q1.union(q2).build(DbType::MySQL);
3482 assert!(sql.contains("WHERE age > 18"));
3483 assert!(sql.contains(" UNION "));
3484 }
3485
3486 #[test]
3489 fn test_cte_window_for_update_combined() {
3490 let sql = Query::select()
3492 .column("id")
3493 .column("salary")
3494 .from("ranked_salaries")
3495 .with_cte(
3496 "ranked_salaries",
3497 "SELECT id, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM employees",
3498 )
3499 .where_clause("rn = 1")
3500 .for_update()
3501 .build(DbType::MySQL);
3502 assert!(sql.starts_with("WITH ranked_salaries AS ("));
3503 assert!(sql.contains("FOR UPDATE"));
3504 assert!(sql.contains("WHERE rn = 1"));
3505 }
3506
3507 #[test]
3508 fn test_complex_window_aggregation() {
3509 let sql = Query::select()
3511 .column("user_id")
3512 .column("amount")
3513 .from("transactions")
3514 .window_function(
3515 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total",
3516 )
3517 .rank("user_id", "created_at", "tx_rank")
3518 .build(DbType::MySQL);
3519 assert!(sql.contains(
3520 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total"
3521 ));
3522 assert!(sql.contains("RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS tx_rank"));
3523 }
3524
3525 #[test]
3528 fn test_select_where_eq_params() {
3529 use sz_orm_core::Value;
3530 let built = Query::select()
3531 .column("id")
3532 .from("users")
3533 .where_eq("age", Value::I32(18))
3534 .build_with_params(DbType::MySQL);
3535 assert!(built.sql.contains("WHERE `age` = ?"));
3536 assert_eq!(built.params.len(), 1);
3537 assert_eq!(built.params[0], Value::I32(18));
3538 }
3539
3540 #[test]
3541 fn test_select_multiple_where_params() {
3542 use sz_orm_core::Value;
3543 let built = Query::select()
3544 .column("id")
3545 .from("users")
3546 .where_eq("age", Value::I32(18))
3547 .where_eq("status", Value::String("active".to_string()))
3548 .build_with_params(DbType::MySQL);
3549 assert!(built.sql.contains("WHERE `age` = ? AND `status` = ?"));
3550 assert_eq!(built.params.len(), 2);
3551 }
3552
3553 #[test]
3554 fn test_select_or_where_eq_params() {
3555 use sz_orm_core::Value;
3556 let built = Query::select()
3557 .column("id")
3558 .from("users")
3559 .where_eq("age", Value::I32(18))
3560 .or_where_eq("role", Value::String("admin".to_string()))
3561 .build_with_params(DbType::MySQL);
3562 assert!(built.sql.contains("WHERE `age` = ? OR `role` = ?"));
3563 assert_eq!(built.params.len(), 2);
3564 }
3565
3566 #[test]
3567 fn test_select_where_in_params() {
3568 use sz_orm_core::Value;
3569 let built = Query::select()
3570 .column("id")
3571 .from("users")
3572 .where_in("id", vec![Value::I32(1), Value::I32(2), Value::I32(3)])
3573 .build_with_params(DbType::MySQL);
3574 assert!(built.sql.contains("WHERE `id` IN (?, ?, ?)"));
3575 assert_eq!(built.params.len(), 3);
3576 }
3577
3578 #[test]
3579 fn test_select_where_in_empty() {
3580 let built = Query::select()
3581 .column("id")
3582 .from("users")
3583 .where_in("id", vec![])
3584 .build_with_params(DbType::MySQL);
3585 assert!(built.sql.contains("WHERE 1 = 0"));
3586 assert_eq!(built.params.len(), 0);
3587 }
3588
3589 #[test]
3590 fn test_select_where_between_params() {
3591 use sz_orm_core::Value;
3592 let built = Query::select()
3593 .column("id")
3594 .from("users")
3595 .where_between("age", Value::I32(18), Value::I32(65))
3596 .build_with_params(DbType::MySQL);
3597 assert!(built.sql.contains("WHERE `age` BETWEEN ? AND ?"));
3598 assert_eq!(built.params.len(), 2);
3599 }
3600
3601 #[test]
3602 fn test_select_where_null_params() {
3603 let built = Query::select()
3604 .column("id")
3605 .from("users")
3606 .where_null("deleted_at")
3607 .build_with_params(DbType::MySQL);
3608 assert!(built.sql.contains("WHERE `deleted_at` IS NULL"));
3609 assert_eq!(built.params.len(), 0);
3610 }
3611
3612 #[test]
3613 fn test_select_where_not_null_params() {
3614 let built = Query::select()
3615 .column("id")
3616 .from("users")
3617 .where_not_null("email")
3618 .build_with_params(DbType::MySQL);
3619 assert!(built.sql.contains("WHERE `email` IS NOT NULL"));
3620 }
3621
3622 #[test]
3623 fn test_select_mixed_raw_and_param_where() {
3624 use sz_orm_core::Value;
3625 let built = Query::select()
3626 .column("id")
3627 .from("users")
3628 .where_clause("age > 18")
3629 .where_eq("status", Value::String("active".to_string()))
3630 .build_with_params(DbType::MySQL);
3631 assert!(built.sql.contains("WHERE age > 18 AND `status` = ?"));
3632 assert_eq!(built.params.len(), 1);
3633 }
3634
3635 #[test]
3636 fn test_select_param_where_with_order_limit() {
3637 use sz_orm_core::Value;
3638 let built = Query::select()
3639 .column("id")
3640 .from("users")
3641 .where_eq("age", Value::I32(18))
3642 .order_by("id", true)
3643 .limit(10)
3644 .build_with_params(DbType::MySQL);
3645 assert!(built.sql.contains("WHERE `age` = ?"));
3646 assert!(built.sql.contains("ORDER BY `id` ASC"));
3647 assert!(built.sql.contains("LIMIT 10"));
3648 }
3649
3650 #[test]
3651 fn test_select_param_where_postgres_dialect() {
3652 use sz_orm_core::Value;
3653 let built = Query::select()
3654 .column("id")
3655 .from("users")
3656 .where_eq("age", Value::I32(18))
3657 .build_with_params(DbType::PostgreSQL);
3658 assert!(built.sql.contains("WHERE \"age\" = ?"));
3659 }
3660
3661 #[test]
3662 fn test_select_param_where_injection_safe() {
3663 use sz_orm_core::Value;
3665 let malicious = "'; DROP TABLE users; --".to_string();
3666 let built = Query::select()
3667 .column("id")
3668 .from("users")
3669 .where_eq("name", Value::String(malicious.clone()))
3670 .build_with_params(DbType::MySQL);
3671 assert!(!built.sql.contains("DROP TABLE"));
3673 assert!(!built.sql.contains(";"));
3674 assert_eq!(built.params.len(), 1);
3676 assert_eq!(built.params[0], Value::String(malicious));
3677 }
3678
3679 #[test]
3680 fn test_update_where_eq_params() {
3681 use sz_orm_core::Value;
3682 let built = Query::update()
3683 .table("users")
3684 .set("name", "'Bob'")
3685 .where_eq("id", Value::I64(1))
3686 .build_with_params(DbType::MySQL);
3687 assert!(built.sql.contains("UPDATE `users` SET"));
3688 assert!(built.sql.contains("WHERE `id` = ?"));
3689 assert_eq!(built.params.len(), 1);
3690 }
3691
3692 #[test]
3693 fn test_update_where_in_params() {
3694 use sz_orm_core::Value;
3695 let built = Query::update()
3696 .table("users")
3697 .set("status", "'inactive'")
3698 .where_in("id", vec![Value::I64(1), Value::I64(2)])
3699 .build_with_params(DbType::MySQL);
3700 assert!(built.sql.contains("WHERE `id` IN (?, ?)"));
3701 assert_eq!(built.params.len(), 2);
3702 }
3703
3704 #[test]
3705 fn test_delete_where_eq_params() {
3706 use sz_orm_core::Value;
3707 let built = Query::delete()
3708 .from_table("users")
3709 .where_eq("id", Value::I64(1))
3710 .build_with_params(DbType::MySQL);
3711 assert!(built.sql.contains("DELETE FROM `users`"));
3712 assert!(built.sql.contains("WHERE `id` = ?"));
3713 assert_eq!(built.params.len(), 1);
3714 }
3715
3716 #[test]
3717 fn test_delete_where_between_params() {
3718 use sz_orm_core::Value;
3719 let built = Query::delete()
3720 .from_table("logs")
3721 .where_between(
3722 "created_at",
3723 Value::String("2020-01-01".to_string()),
3724 Value::String("2020-12-31".to_string()),
3725 )
3726 .build_with_params(DbType::MySQL);
3727 assert!(built.sql.contains("WHERE `created_at` BETWEEN ? AND ?"));
3728 assert_eq!(built.params.len(), 2);
3729 }
3730
3731 #[test]
3734 fn test_from_subquery_basic() {
3735 let inner = Query::select()
3736 .column("id")
3737 .column("amount")
3738 .from("orders")
3739 .build(DbType::MySQL);
3740 let sql = Query::select()
3741 .column("id")
3742 .from_subquery(&inner, "t")
3743 .build(DbType::MySQL);
3744 assert!(
3745 sql.contains("FROM (SELECT `id`, `amount` FROM `orders`) AS `t`"),
3746 "FROM 子查询应渲染为 `FROM (subquery) AS alias`,实际: {sql}"
3747 );
3748 }
3749
3750 #[test]
3751 fn test_from_subquery_postgres_dialect() {
3752 let inner = Query::select()
3753 .column("id")
3754 .from("orders")
3755 .build(DbType::PostgreSQL);
3756 let sql = Query::select()
3757 .column("id")
3758 .from_subquery(&inner, "t")
3759 .build(DbType::PostgreSQL);
3760 assert!(
3761 sql.contains("FROM (SELECT \"id\" FROM \"orders\") AS \"t\""),
3762 "PG 方言下别名应使用双引号,实际: {sql}"
3763 );
3764 }
3765
3766 #[test]
3767 fn test_from_subquery_with_where_and_order() {
3768 let inner = Query::select()
3769 .column("id")
3770 .column("amount")
3771 .from("orders")
3772 .where_clause("amount > 100")
3773 .build(DbType::MySQL);
3774 let sql = Query::select()
3775 .column("id")
3776 .column("amount")
3777 .from_subquery(&inner, "t")
3778 .where_clause("t.amount > 200")
3779 .order_by("id", true)
3780 .build(DbType::MySQL);
3781 assert!(
3782 sql.contains("FROM (SELECT `id`, `amount` FROM `orders` WHERE amount > 100) AS `t`")
3783 );
3784 assert!(sql.contains("WHERE t.amount > 200"));
3785 assert!(sql.contains("ORDER BY `id` ASC"));
3786 }
3787
3788 #[test]
3789 fn test_from_subquery_with_params() {
3790 use sz_orm_core::Value;
3791 let inner = Query::select()
3792 .column("id")
3793 .from("orders")
3794 .where_eq("amount", Value::I32(100))
3795 .build_with_params(DbType::MySQL);
3796 let built = Query::select()
3797 .column("id")
3798 .from_subquery(&inner.sql, "t")
3799 .where_eq("t.id", Value::I64(1))
3800 .build_with_params(DbType::MySQL);
3801 assert!(built
3803 .sql
3804 .contains("FROM (SELECT `id` FROM `orders` WHERE `amount` = ?) AS `t`"));
3805 assert!(built.sql.contains("WHERE `t`.`id` = ?"));
3806 assert_eq!(built.params.len(), 1);
3808 }
3809
3810 #[test]
3811 fn test_from_subquery_overrides_from_table() {
3812 let sql = Query::select()
3814 .column("id")
3815 .from("users")
3816 .from_subquery("SELECT id FROM orders", "t")
3817 .build(DbType::MySQL);
3818 assert!(sql.contains("FROM (SELECT id FROM orders) AS `t`"));
3819 assert!(!sql.contains("FROM `users`"));
3820 }
3821
3822 #[test]
3823 fn test_from_table_overrides_from_subquery() {
3824 let sql = Query::select()
3826 .column("id")
3827 .from_subquery("SELECT id FROM orders", "t")
3828 .from("users")
3829 .build(DbType::MySQL);
3830 assert!(sql.contains("FROM `users`"));
3831 assert!(!sql.contains("FROM ("));
3832 }
3833
3834 #[test]
3835 fn test_from_subquery_no_from_when_neither_set() {
3836 let sql = Query::select().column("id").build(DbType::MySQL);
3837 assert!(!sql.contains("FROM"));
3838 }
3839
3840 #[test]
3843 fn test_insert_returning_postgres() {
3844 let sql = Query::insert()
3845 .into_table("users")
3846 .value("name", "'Alice'")
3847 .returning(&["id", "created_at"])
3848 .build_with_dialect(DbType::PostgreSQL);
3849 assert!(
3850 sql.contains("RETURNING \"id\", \"created_at\""),
3851 "PG 方言应渲染 RETURNING,实际: {sql}"
3852 );
3853 }
3854
3855 #[test]
3856 fn test_insert_returning_sqlite() {
3857 let sql = Query::insert()
3858 .into_table("users")
3859 .value("name", "'Alice'")
3860 .returning(&["id"])
3861 .build_with_dialect(DbType::Sqlite);
3862 assert!(
3863 sql.contains("RETURNING \"id\""),
3864 "SQLite 方言应渲染 RETURNING,实际: {sql}"
3865 );
3866 }
3867
3868 #[test]
3869 fn test_insert_returning_all() {
3870 let sql = Query::insert()
3871 .into_table("users")
3872 .value("name", "'Alice'")
3873 .returning_all()
3874 .build_with_dialect(DbType::PostgreSQL);
3875 assert!(
3876 sql.contains("RETURNING *"),
3877 "returning_all 应渲染 `RETURNING *`,实际: {sql}"
3878 );
3879 }
3880
3881 #[test]
3882 fn test_insert_returning_mysql_skipped() {
3883 let sql = Query::insert()
3885 .into_table("users")
3886 .value("name", "'Alice'")
3887 .returning(&["id"])
3888 .build_with_dialect(DbType::MySQL);
3889 assert!(
3890 !sql.contains("RETURNING"),
3891 "MySQL 方言应跳过 RETURNING,实际: {sql}"
3892 );
3893 }
3894
3895 #[test]
3896 fn test_insert_returning_with_upsert_postgres() {
3897 let sql = Query::insert()
3899 .into_table("users")
3900 .value("id", "1")
3901 .value("name", "'Alice'")
3902 .on_conflict_do_update(&["id"], &[("name", "EXCLUDED.name")])
3903 .returning(&["id", "name"])
3904 .build_with_dialect(DbType::PostgreSQL);
3905 assert!(sql.contains("ON CONFLICT"));
3906 assert!(sql.contains("RETURNING"));
3907 }
3908
3909 #[test]
3910 fn test_insert_returning_build_mysql_style_skipped() {
3911 let sql = Query::insert()
3913 .into_table("users")
3914 .value("name", "'Alice'")
3915 .returning(&["id"])
3916 .build();
3917 assert!(!sql.contains("RETURNING"));
3918 }
3919
3920 #[test]
3921 fn test_update_returning_postgres() {
3922 let sql = Query::update()
3923 .table("users")
3924 .set("status", "'active'")
3925 .where_clause("id = 1")
3926 .returning(&["id", "status"])
3927 .build_with_dialect(DbType::PostgreSQL);
3928 assert!(sql.contains("RETURNING \"id\", \"status\""));
3929 assert!(sql.contains("WHERE id = 1"));
3930 }
3931
3932 #[test]
3933 fn test_update_returning_sqlite() {
3934 let sql = Query::update()
3935 .table("users")
3936 .set("status", "'active'")
3937 .returning(&["id"])
3938 .build_with_dialect(DbType::Sqlite);
3939 assert!(sql.contains("RETURNING \"id\""));
3940 }
3941
3942 #[test]
3943 fn test_update_returning_mysql_skipped() {
3944 let sql = Query::update()
3945 .table("users")
3946 .set("status", "'active'")
3947 .returning(&["id"])
3948 .build_with_dialect(DbType::MySQL);
3949 assert!(!sql.contains("RETURNING"));
3950 }
3951
3952 #[test]
3953 fn test_update_returning_with_params() {
3954 use sz_orm_core::Value;
3955 let built = Query::update()
3956 .table("users")
3957 .set("status", "'active'")
3958 .where_eq("id", Value::I64(1))
3959 .returning(&["id", "status"])
3960 .build_with_params(DbType::PostgreSQL);
3961 assert!(built.sql.contains("WHERE \"id\" = ?"));
3962 assert!(built.sql.contains("RETURNING \"id\", \"status\""));
3963 assert_eq!(built.params.len(), 1);
3964 }
3965
3966 #[test]
3967 fn test_delete_returning_postgres() {
3968 let sql = Query::delete()
3969 .from_table("users")
3970 .where_clause("id = 1")
3971 .returning(&["id", "name"])
3972 .build_with_dialect(DbType::PostgreSQL);
3973 assert!(sql.contains("RETURNING \"id\", \"name\""));
3974 assert!(sql.contains("WHERE id = 1"));
3975 }
3976
3977 #[test]
3978 fn test_delete_returning_sqlite() {
3979 let sql = Query::delete()
3980 .from_table("users")
3981 .where_clause("id = 1")
3982 .returning(&["id"])
3983 .build_with_dialect(DbType::Sqlite);
3984 assert!(sql.contains("RETURNING \"id\""));
3985 }
3986
3987 #[test]
3988 fn test_delete_returning_mysql_skipped() {
3989 let sql = Query::delete()
3990 .from_table("users")
3991 .where_clause("id = 1")
3992 .returning(&["id"])
3993 .build_with_dialect(DbType::MySQL);
3994 assert!(!sql.contains("RETURNING"));
3995 }
3996
3997 #[test]
3998 fn test_delete_returning_with_params() {
3999 use sz_orm_core::Value;
4000 let built = Query::delete()
4001 .from_table("users")
4002 .where_eq("id", Value::I64(1))
4003 .returning(&["id", "name"])
4004 .build_with_params(DbType::PostgreSQL);
4005 assert!(built.sql.contains("WHERE \"id\" = ?"));
4006 assert!(built.sql.contains("RETURNING \"id\", \"name\""));
4007 assert_eq!(built.params.len(), 1);
4008 }
4009
4010 #[test]
4011 fn test_returning_star_not_quoted() {
4012 let sql = Query::insert()
4014 .into_table("users")
4015 .value("name", "'Alice'")
4016 .returning(&["*"])
4017 .build_with_dialect(DbType::PostgreSQL);
4018 assert!(sql.contains("RETURNING *"));
4019 assert!(!sql.contains("RETURNING \"*\""));
4020 }
4021
4022 #[test]
4025 fn test_inner_join_on_column_eq() {
4026 let sql = Query::select()
4027 .column("u.id")
4028 .from("users u")
4029 .inner_join_on("orders o", "u.id", "o.user_id")
4030 .build(DbType::MySQL);
4031 assert!(
4032 sql.contains("INNER JOIN `orders` o ON `u`.`id` = `o`.`user_id`"),
4033 "列对列等值连接应渲染转义标识符,实际: {sql}"
4034 );
4035 }
4036
4037 #[test]
4038 fn test_left_join_on_column_eq() {
4039 let sql = Query::select()
4040 .column("u.id")
4041 .from("users u")
4042 .left_join_on("profiles p", "u.id", "p.user_id")
4043 .build(DbType::MySQL);
4044 assert!(sql.contains("LEFT JOIN `profiles` p ON `u`.`id` = `p`.`user_id`"));
4045 }
4046
4047 #[test]
4048 fn test_right_join_on_column_eq() {
4049 let sql = Query::select()
4050 .column("u.id")
4051 .from("users u")
4052 .right_join_on("orders o", "u.id", "o.user_id")
4053 .build(DbType::MySQL);
4054 assert!(sql.contains("RIGHT JOIN `orders` o ON `u`.`id` = `o`.`user_id`"));
4055 }
4056
4057 #[test]
4058 fn test_inner_join_on_postgres_dialect() {
4059 let sql = Query::select()
4060 .column("u.id")
4061 .from("users u")
4062 .inner_join_on("orders o", "u.id", "o.user_id")
4063 .build(DbType::PostgreSQL);
4064 assert!(
4067 sql.contains("INNER JOIN `orders` o ON \"u\".\"id\" = \"o\".\"user_id\""),
4068 "PG 方言下 ON 条件列名应使用双引号引用,实际: {sql}"
4069 );
4070 }
4071
4072 #[test]
4073 fn test_inner_join_param_binds_value() {
4074 use sz_orm_core::Value;
4075 let built = Query::select()
4076 .column("u.id")
4077 .from("users u")
4078 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4079 .build_with_params(DbType::MySQL);
4080 assert!(
4081 built
4082 .sql
4083 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"),
4084 "参数化 JOIN 应渲染 ? 占位符,实际: {}",
4085 built.sql
4086 );
4087 assert_eq!(built.params.len(), 1);
4088 assert_eq!(built.params[0], Value::String("paid".to_string()));
4089 }
4090
4091 #[test]
4092 fn test_left_join_param_binds_value() {
4093 use sz_orm_core::Value;
4094 let built = Query::select()
4095 .column("u.id")
4096 .from("users u")
4097 .left_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4098 .build_with_params(DbType::MySQL);
4099 assert!(built
4100 .sql
4101 .contains("LEFT JOIN `orders` o ON `o`.`status` = ?"));
4102 assert_eq!(built.params.len(), 1);
4103 }
4104
4105 #[test]
4106 fn test_right_join_param_binds_value() {
4107 use sz_orm_core::Value;
4108 let built = Query::select()
4109 .column("u.id")
4110 .from("users u")
4111 .right_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4112 .build_with_params(DbType::MySQL);
4113 assert!(built
4114 .sql
4115 .contains("RIGHT JOIN `orders` o ON `o`.`status` = ?"));
4116 assert_eq!(built.params.len(), 1);
4117 }
4118
4119 #[test]
4120 fn test_param_join_injection_safe() {
4121 use sz_orm_core::Value;
4123 let malicious = "'; DROP TABLE orders; --".to_string();
4124 let built = Query::select()
4125 .column("u.id")
4126 .from("users u")
4127 .inner_join_param(
4128 "orders o",
4129 "o.status",
4130 " = ?",
4131 Value::String(malicious.clone()),
4132 )
4133 .build_with_params(DbType::MySQL);
4134 assert!(!built.sql.contains("DROP TABLE"));
4136 assert!(!built.sql.contains(";"));
4137 assert_eq!(built.params.len(), 1);
4139 assert_eq!(built.params[0], Value::String(malicious));
4140 }
4141
4142 #[test]
4143 fn test_mixed_raw_and_param_join() {
4144 use sz_orm_core::Value;
4146 let built = Query::select()
4147 .column("u.id")
4148 .from("users u")
4149 .inner_join("orders o", "u.id = o.user_id")
4150 .inner_join_param(
4151 "payments p",
4152 "p.status",
4153 " = ?",
4154 Value::String("paid".into()),
4155 )
4156 .build_with_params(DbType::MySQL);
4157 assert!(built
4158 .sql
4159 .contains("INNER JOIN `orders` o ON u.id = o.user_id"));
4160 assert!(built
4161 .sql
4162 .contains("INNER JOIN `payments` p ON `p`.`status` = ?"));
4163 assert_eq!(built.params.len(), 1);
4164 }
4165
4166 #[test]
4167 fn test_param_join_with_where_params_combined() {
4168 use sz_orm_core::Value;
4170 let built = Query::select()
4171 .column("u.id")
4172 .from("users u")
4173 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4174 .where_eq("u.age", Value::I32(18))
4175 .build_with_params(DbType::MySQL);
4176 assert!(built
4177 .sql
4178 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"));
4179 assert!(built.sql.contains("WHERE `u`.`age` = ?"));
4180 assert_eq!(built.params.len(), 2);
4182 assert_eq!(built.params[0], Value::String("paid".to_string()));
4183 assert_eq!(built.params[1], Value::I32(18));
4184 }
4185}