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