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 or_where(mut self, condition: &str) -> Self {
617 check_where_injection(condition);
618 self.wheres.push(format!("OR {}", condition));
619 self
620 }
621
622 pub fn where_eq(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_ne(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_gt(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_ge(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_lt(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_le(mut self, column: &str, value: Value) -> Self {
698 self.param_wheres.push(ParamWhere::And {
699 column: column.to_string(),
700 op: " <= ?".to_string(),
701 values: vec![value],
702 });
703 self
704 }
705
706 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
708 self.param_wheres.push(ParamWhere::And {
709 column: column.to_string(),
710 op: " LIKE ?".to_string(),
711 values: vec![pattern],
712 });
713 self
714 }
715
716 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
720 let (column, op) = if values.is_empty() {
721 (String::new(), "1 = 0".to_string())
722 } else {
723 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
724 (
725 column.to_string(),
726 format!(" IN ({})", placeholders.join(", ")),
727 )
728 };
729 self.param_wheres
730 .push(ParamWhere::And { column, op, values });
731 self
732 }
733
734 pub fn where_not_in(mut self, column: &str, values: Vec<Value>) -> Self {
738 let (column, op) = if values.is_empty() {
739 (String::new(), "1 = 1".to_string())
740 } else {
741 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
742 (
743 column.to_string(),
744 format!(" NOT IN ({})", placeholders.join(", ")),
745 )
746 };
747 self.param_wheres
748 .push(ParamWhere::And { column, op, values });
749 self
750 }
751
752 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
754 self.param_wheres.push(ParamWhere::And {
755 column: column.to_string(),
756 op: " BETWEEN ? AND ?".to_string(),
757 values: vec![low, high],
758 });
759 self
760 }
761
762 pub fn where_null(mut self, column: &str) -> Self {
764 self.param_wheres.push(ParamWhere::And {
765 column: column.to_string(),
766 op: " IS NULL".to_string(),
767 values: vec![],
768 });
769 self
770 }
771
772 pub fn where_not_null(mut self, column: &str) -> Self {
774 self.param_wheres.push(ParamWhere::And {
775 column: column.to_string(),
776 op: " IS NOT NULL".to_string(),
777 values: vec![],
778 });
779 self
780 }
781
782 pub fn or_where_eq(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_ne(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_gt(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_ge(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_lt(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_le(mut self, column: &str, value: Value) -> Self {
834 self.param_wheres.push(ParamWhere::Or {
835 column: column.to_string(),
836 op: " <= ?".to_string(),
837 values: vec![value],
838 });
839 self
840 }
841
842 pub fn or_where_like(mut self, column: &str, pattern: Value) -> Self {
844 self.param_wheres.push(ParamWhere::Or {
845 column: column.to_string(),
846 op: " LIKE ?".to_string(),
847 values: vec![pattern],
848 });
849 self
850 }
851
852 pub fn or_where_in(mut self, column: &str, values: Vec<Value>) -> Self {
854 let (column, op) = if values.is_empty() {
855 (String::new(), "1 = 0".to_string())
856 } else {
857 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
858 (
859 column.to_string(),
860 format!(" IN ({})", placeholders.join(", ")),
861 )
862 };
863 self.param_wheres
864 .push(ParamWhere::Or { column, op, values });
865 self
866 }
867
868 pub fn or_where_between(mut self, column: &str, low: Value, high: Value) -> Self {
870 self.param_wheres.push(ParamWhere::Or {
871 column: column.to_string(),
872 op: " BETWEEN ? AND ?".to_string(),
873 values: vec![low, high],
874 });
875 self
876 }
877
878 pub fn or_where_null(mut self, column: &str) -> Self {
880 self.param_wheres.push(ParamWhere::Or {
881 column: column.to_string(),
882 op: " IS NULL".to_string(),
883 values: vec![],
884 });
885 self
886 }
887
888 pub fn or_where_not_null(mut self, column: &str) -> Self {
890 self.param_wheres.push(ParamWhere::Or {
891 column: column.to_string(),
892 op: " IS NOT NULL".to_string(),
893 values: vec![],
894 });
895 self
896 }
897
898 pub fn group_by(mut self, column: &str) -> Self {
900 self.group_by.push(column.to_string());
901 self
902 }
903
904 pub fn having(mut self, condition: &str) -> Self {
906 self.having.push(condition.to_string());
907 self
908 }
909
910 pub fn order_by(mut self, column: &str, asc: bool) -> Self {
917 let dir = if asc { "ASC" } else { "DESC" };
918 self.order_by.push(format!("{} {}", column, dir));
919 self
920 }
921
922 pub fn limit(mut self, n: u64) -> Self {
924 self.limit = Some(n);
925 self
926 }
927
928 pub fn offset(mut self, n: u64) -> Self {
930 self.offset = Some(n);
931 self
932 }
933
934 pub fn paginate(self, page: u64, size: u64) -> Self {
941 let offset = (page.saturating_sub(1)) * size;
942 self.limit(size).offset(offset)
943 }
944
945 pub fn with_cte(mut self, name: &str, subquery: &str) -> Self {
954 self.ctes
955 .push((name.to_string(), subquery.to_string(), false));
956 self
957 }
958
959 pub fn with_recursive_cte(mut self, name: &str, subquery: &str) -> Self {
966 self.ctes
967 .push((name.to_string(), subquery.to_string(), true));
968 self
969 }
970
971 pub fn window_function(mut self, expr: &str) -> Self {
982 self.window_columns.push(expr.to_string());
983 self
984 }
985
986 pub fn row_number(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
994 let partition_clause = if partition_by.is_empty() {
995 String::new()
996 } else {
997 format!("PARTITION BY {} ", partition_by)
998 };
999 let expr = format!(
1000 "ROW_NUMBER() OVER ({}ORDER BY {}) AS {}",
1001 partition_clause, order_by, alias
1002 );
1003 self.window_function(&expr)
1004 }
1005
1006 pub fn rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1014 let partition_clause = if partition_by.is_empty() {
1015 String::new()
1016 } else {
1017 format!("PARTITION BY {} ", partition_by)
1018 };
1019 let expr = format!(
1020 "RANK() OVER ({}ORDER BY {}) AS {}",
1021 partition_clause, order_by, alias
1022 );
1023 self.window_function(&expr)
1024 }
1025
1026 pub fn dense_rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1034 let partition_clause = if partition_by.is_empty() {
1035 String::new()
1036 } else {
1037 format!("PARTITION BY {} ", partition_by)
1038 };
1039 let expr = format!(
1040 "DENSE_RANK() OVER ({}ORDER BY {}) AS {}",
1041 partition_clause, order_by, alias
1042 );
1043 self.window_function(&expr)
1044 }
1045
1046 pub fn for_update(mut self) -> Self {
1050 self.for_update = true;
1051 self.for_update_options = None;
1052 self
1053 }
1054
1055 pub fn for_update_with_options(mut self, options: &str) -> Self {
1061 self.for_update = true;
1062 self.for_update_options = Some(options.to_string());
1063 self
1064 }
1065
1066 pub fn union(self, other: SelectQuery) -> SetQuery {
1070 SetQuery::new(self, SetOperator::Union, other)
1071 }
1072
1073 pub fn union_all(self, other: SelectQuery) -> SetQuery {
1075 SetQuery::new(self, SetOperator::UnionAll, other)
1076 }
1077
1078 pub fn intersect(self, other: SelectQuery) -> SetQuery {
1080 SetQuery::new(self, SetOperator::Intersect, other)
1081 }
1082
1083 pub fn except(self, other: SelectQuery) -> SetQuery {
1085 SetQuery::new(self, SetOperator::Except, other)
1086 }
1087
1088 pub fn build(self, db_type: DbType) -> String {
1094 let dialect = match sz_orm_core::get_dialect(db_type) {
1095 Ok(d) => d,
1096 Err(_) => return String::new(),
1097 };
1098
1099 let mut sql = String::new();
1100
1101 if !self.ctes.is_empty() {
1103 let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1104 if has_recursive {
1105 sql.push_str("WITH RECURSIVE ");
1106 } else {
1107 sql.push_str("WITH ");
1108 }
1109 let cte_strs: Vec<String> = self
1110 .ctes
1111 .iter()
1112 .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1113 .collect();
1114 sql.push_str(&cte_strs.join(", "));
1115 sql.push(' ');
1116 }
1117
1118 sql.push_str("SELECT ");
1119
1120 if self.distinct {
1121 sql.push_str("DISTINCT ");
1122 }
1123
1124 let mut all_columns: Vec<String> = self
1126 .columns
1127 .iter()
1128 .map(|c| {
1129 if c == "*" {
1130 c.clone()
1131 } else {
1132 dialect.quote(c)
1133 }
1134 })
1135 .collect();
1136 all_columns.extend(self.window_columns.iter().cloned());
1137
1138 if all_columns.is_empty() {
1139 sql.push('*');
1140 } else {
1141 sql.push_str(&all_columns.join(", "));
1142 }
1143
1144 if let Some(ref table) = self.from_table {
1145 sql.push_str(" FROM ");
1146 sql.push_str(&dialect.quote(table));
1147 } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1148 sql.push_str(" FROM (");
1150 sql.push_str(subquery);
1151 sql.push_str(") AS ");
1152 sql.push_str(&dialect.quote(alias));
1153 }
1154
1155 let mut unused_params = Vec::new();
1158 sql.push_str(&self.render_joins(&*dialect, &mut unused_params));
1159
1160 if !self.wheres.is_empty() {
1161 sql.push_str(" WHERE ");
1162 sql.push_str(&self.wheres[0]);
1164 for w in &self.wheres[1..] {
1165 if w.starts_with("OR ") {
1166 sql.push(' ');
1167 sql.push_str(w);
1168 } else {
1169 sql.push_str(" AND ");
1170 sql.push_str(w);
1171 }
1172 }
1173 }
1174
1175 if !self.group_by.is_empty() {
1176 sql.push_str(" GROUP BY ");
1177 sql.push_str(
1178 &self
1179 .group_by
1180 .iter()
1181 .map(|c| quote_ident(c))
1182 .collect::<Vec<_>>()
1183 .join(", "),
1184 );
1185 }
1186
1187 if !self.having.is_empty() {
1188 sql.push_str(" HAVING ");
1189 sql.push_str(&self.having.join(" AND "));
1190 }
1191
1192 if !self.order_by.is_empty() {
1193 sql.push_str(" ORDER BY ");
1194 sql.push_str(
1195 &self
1196 .order_by
1197 .iter()
1198 .map(|s| {
1199 if let Some((col, dir)) = s.rsplit_once(' ') {
1201 format!("{} {}", quote_ident(col), dir)
1202 } else {
1203 quote_ident(s)
1204 }
1205 })
1206 .collect::<Vec<_>>()
1207 .join(", "),
1208 );
1209 }
1210
1211 if let Some(limit) = self.limit {
1212 sql.push_str(&format!(" LIMIT {}", limit));
1213 }
1214 if let Some(offset) = self.offset {
1215 sql.push_str(&format!(" OFFSET {}", offset));
1216 }
1217
1218 if self.for_update {
1220 sql.push_str(" FOR UPDATE");
1221 if let Some(ref opts) = self.for_update_options {
1222 sql.push(' ');
1223 sql.push_str(opts);
1224 }
1225 }
1226
1227 sql
1228 }
1229
1230 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
1260 let dialect = match sz_orm_core::get_dialect(db_type) {
1261 Ok(d) => d,
1262 Err(_) => return BuiltQuery::default(),
1263 };
1264
1265 let mut sql = String::new();
1266 let mut params: Vec<Value> = Vec::new();
1267
1268 if !self.ctes.is_empty() {
1270 let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1271 if has_recursive {
1272 sql.push_str("WITH RECURSIVE ");
1273 } else {
1274 sql.push_str("WITH ");
1275 }
1276 let cte_strs: Vec<String> = self
1277 .ctes
1278 .iter()
1279 .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1280 .collect();
1281 sql.push_str(&cte_strs.join(", "));
1282 sql.push(' ');
1283 }
1284
1285 sql.push_str("SELECT ");
1286
1287 if self.distinct {
1288 sql.push_str("DISTINCT ");
1289 }
1290
1291 let mut all_columns: Vec<String> = self
1292 .columns
1293 .iter()
1294 .map(|c| {
1295 if c == "*" {
1296 c.clone()
1297 } else {
1298 dialect.quote(c)
1299 }
1300 })
1301 .collect();
1302 all_columns.extend(self.window_columns.iter().cloned());
1303
1304 if all_columns.is_empty() {
1305 sql.push('*');
1306 } else {
1307 sql.push_str(&all_columns.join(", "));
1308 }
1309
1310 if let Some(ref table) = self.from_table {
1311 sql.push_str(" FROM ");
1312 sql.push_str(&dialect.quote(table));
1313 } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1314 sql.push_str(" FROM (");
1316 sql.push_str(subquery);
1317 sql.push_str(") AS ");
1318 sql.push_str(&dialect.quote(alias));
1319 }
1320
1321 sql.push_str(&self.render_joins(&*dialect, &mut params));
1324
1325 let has_raw = !self.wheres.is_empty();
1327 let has_param = !self.param_wheres.is_empty();
1328 if has_raw || has_param {
1329 sql.push_str(" WHERE ");
1330 let mut first = true;
1331 for w in &self.wheres {
1333 if first {
1334 sql.push_str(w);
1335 first = false;
1336 } else if w.starts_with("OR ") {
1337 sql.push(' ');
1338 sql.push_str(w);
1339 } else {
1340 sql.push_str(" AND ");
1341 sql.push_str(w);
1342 }
1343 }
1344 for pw in &self.param_wheres {
1346 let (conjunction, column, op, vals) = match pw {
1347 ParamWhere::And { column, op, values } => ("AND", column, op, values),
1348 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
1349 };
1350 let expr = if column.is_empty() {
1352 op.clone()
1353 } else {
1354 format!("{}{}", quote_column_dialect(&*dialect, column), op)
1355 };
1356 if first {
1357 sql.push_str(&expr);
1358 first = false;
1359 } else {
1360 sql.push(' ');
1361 sql.push_str(conjunction);
1362 sql.push(' ');
1363 sql.push_str(&expr);
1364 }
1365 params.extend(vals.iter().cloned());
1366 }
1367 }
1368
1369 if !self.group_by.is_empty() {
1370 sql.push_str(" GROUP BY ");
1371 sql.push_str(
1372 &self
1373 .group_by
1374 .iter()
1375 .map(|c| quote_ident(c))
1376 .collect::<Vec<_>>()
1377 .join(", "),
1378 );
1379 }
1380
1381 if !self.having.is_empty() {
1382 sql.push_str(" HAVING ");
1383 sql.push_str(&self.having.join(" AND "));
1384 }
1385
1386 if !self.order_by.is_empty() {
1387 sql.push_str(" ORDER BY ");
1388 sql.push_str(
1389 &self
1390 .order_by
1391 .iter()
1392 .map(|s| {
1393 if let Some((col, dir)) = s.rsplit_once(' ') {
1394 format!("{} {}", quote_ident(col), dir)
1395 } else {
1396 quote_ident(s)
1397 }
1398 })
1399 .collect::<Vec<_>>()
1400 .join(", "),
1401 );
1402 }
1403
1404 if let Some(limit) = self.limit {
1405 sql.push_str(&format!(" LIMIT {}", limit));
1406 }
1407 if let Some(offset) = self.offset {
1408 sql.push_str(&format!(" OFFSET {}", offset));
1409 }
1410
1411 if self.for_update {
1412 sql.push_str(" FOR UPDATE");
1413 if let Some(ref opts) = self.for_update_options {
1414 sql.push(' ');
1415 sql.push_str(opts);
1416 }
1417 }
1418
1419 BuiltQuery { sql, params }
1420 }
1421}
1422
1423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1429pub enum SetOperator {
1430 Union,
1432 UnionAll,
1434 Intersect,
1436 Except,
1438}
1439
1440impl SetOperator {
1441 pub fn as_sql(&self) -> &'static str {
1443 match self {
1444 SetOperator::Union => "UNION",
1445 SetOperator::UnionAll => "UNION ALL",
1446 SetOperator::Intersect => "INTERSECT",
1447 SetOperator::Except => "EXCEPT",
1448 }
1449 }
1450}
1451
1452#[derive(Debug, Clone)]
1466pub struct SetQuery {
1467 first: SelectQuery,
1469 rest: Vec<(SetOperator, SelectQuery)>,
1471 order_by: Vec<String>,
1473 limit: Option<u64>,
1475 offset: Option<u64>,
1477}
1478
1479impl SetQuery {
1480 pub fn new(first: SelectQuery, op: SetOperator, second: SelectQuery) -> Self {
1482 Self {
1483 first,
1484 rest: vec![(op, second)],
1485 order_by: Vec::new(),
1486 limit: None,
1487 offset: None,
1488 }
1489 }
1490
1491 pub fn union(mut self, other: SelectQuery) -> Self {
1493 self.rest.push((SetOperator::Union, other));
1494 self
1495 }
1496
1497 pub fn union_all(mut self, other: SelectQuery) -> Self {
1499 self.rest.push((SetOperator::UnionAll, other));
1500 self
1501 }
1502
1503 pub fn intersect(mut self, other: SelectQuery) -> Self {
1505 self.rest.push((SetOperator::Intersect, other));
1506 self
1507 }
1508
1509 pub fn except(mut self, other: SelectQuery) -> Self {
1511 self.rest.push((SetOperator::Except, other));
1512 self
1513 }
1514
1515 pub fn order_by(mut self, column: &str, asc: bool) -> Self {
1517 let dir = if asc { "ASC" } else { "DESC" };
1518 self.order_by.push(format!("{} {}", column, dir));
1519 self
1520 }
1521
1522 pub fn limit(mut self, n: u64) -> Self {
1524 self.limit = Some(n);
1525 self
1526 }
1527
1528 pub fn offset(mut self, n: u64) -> Self {
1530 self.offset = Some(n);
1531 self
1532 }
1533
1534 pub fn build(self, db_type: DbType) -> String {
1538 let mut sql = self.first.build(db_type);
1539 for (op, query) in &self.rest {
1540 sql.push(' ');
1541 sql.push_str(op.as_sql());
1542 sql.push(' ');
1543 sql.push_str(&query.clone().build(db_type));
1544 }
1545 if !self.order_by.is_empty() {
1546 sql.push_str(" ORDER BY ");
1547 sql.push_str(
1548 &self
1549 .order_by
1550 .iter()
1551 .map(|s| {
1552 if let Some((col, dir)) = s.rsplit_once(' ') {
1553 format!("{} {}", quote_ident(col), dir)
1554 } else {
1555 quote_ident(s)
1556 }
1557 })
1558 .collect::<Vec<_>>()
1559 .join(", "),
1560 );
1561 }
1562 if let Some(limit) = self.limit {
1563 sql.push_str(&format!(" LIMIT {}", limit));
1564 }
1565 if let Some(offset) = self.offset {
1566 sql.push_str(&format!(" OFFSET {}", offset));
1567 }
1568 sql
1569 }
1570}
1571
1572#[derive(Debug, Clone, Default)]
1589pub enum UpsertStrategy {
1590 #[default]
1592 None,
1593 OnConflictDoNothing(Vec<String>),
1595 OnConflictDoUpdate(Vec<String>, Vec<(String, String)>),
1598 OnDuplicateKeyUpdate(Vec<(String, String)>),
1600 Replace,
1602}
1603
1604fn is_mysql_family(db_type: DbType) -> bool {
1606 matches!(
1607 db_type,
1608 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase | DbType::PolarDB
1609 )
1610}
1611
1612fn is_pg_family(db_type: DbType) -> bool {
1614 matches!(
1615 db_type,
1616 DbType::PostgreSQL | DbType::Kingbase | DbType::GaussDB | DbType::PolarDB
1617 ) || db_type == DbType::Sqlite
1618}
1619
1620fn render_upsert_clause(strategy: &UpsertStrategy, db_type: DbType) -> Option<String> {
1624 match strategy {
1625 UpsertStrategy::None => None,
1626 UpsertStrategy::OnConflictDoNothing(cols) => {
1627 if is_pg_family(db_type) {
1628 let cols_str = cols
1629 .iter()
1630 .map(|c| quote_ident(c))
1631 .collect::<Vec<_>>()
1632 .join(", ");
1633 Some(format!("ON CONFLICT ({}) DO NOTHING", cols_str))
1634 } else {
1635 None
1636 }
1637 }
1638 UpsertStrategy::OnConflictDoUpdate(cols, assignments) => {
1639 if is_pg_family(db_type) {
1640 let cols_str = cols
1641 .iter()
1642 .map(|c| quote_ident(c))
1643 .collect::<Vec<_>>()
1644 .join(", ");
1645 let sets = assignments
1646 .iter()
1647 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1648 .collect::<Vec<_>>()
1649 .join(", ");
1650 Some(format!("ON CONFLICT ({}) DO UPDATE SET {}", cols_str, sets))
1651 } else {
1652 None
1653 }
1654 }
1655 UpsertStrategy::OnDuplicateKeyUpdate(assignments) => {
1656 if is_mysql_family(db_type) {
1657 let sets = assignments
1658 .iter()
1659 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1660 .collect::<Vec<_>>()
1661 .join(", ");
1662 Some(format!("ON DUPLICATE KEY UPDATE {}", sets))
1663 } else {
1664 None
1665 }
1666 }
1667 UpsertStrategy::Replace => None, }
1669}
1670
1671fn render_returning_clause(columns: &Option<Vec<String>>, db_type: DbType) -> Option<String> {
1678 let cols = columns.as_ref()?;
1679 if cols.is_empty() {
1680 return None;
1681 }
1682 if is_mysql_family(db_type) {
1684 return None;
1685 }
1686 let dialect = sz_orm_core::get_dialect(db_type).ok()?;
1687 let quoted: Vec<String> = cols
1688 .iter()
1689 .map(|c| {
1690 if c == "*" {
1691 c.clone()
1692 } else {
1693 dialect.quote(c)
1694 }
1695 })
1696 .collect();
1697 Some(format!("RETURNING {}", quoted.join(", ")))
1698}
1699
1700#[derive(Debug, Clone, Default)]
1702pub struct InsertQuery {
1703 table: Option<String>,
1704 columns: Vec<String>,
1705 values: Vec<String>,
1706 upsert: UpsertStrategy,
1708 returning: Option<Vec<String>>,
1710}
1711
1712impl InsertQuery {
1713 pub fn new() -> Self {
1715 Self::default()
1716 }
1717
1718 pub fn into_table(mut self, table: &str) -> Self {
1720 self.table = Some(table.to_string());
1721 self
1722 }
1723
1724 pub fn value(mut self, column: &str, value: &str) -> Self {
1726 self.columns.push(column.to_string());
1727 self.values.push(value.to_string());
1728 self
1729 }
1730
1731 pub fn values(mut self, pairs: &[(&str, &str)]) -> Self {
1733 for (c, v) in pairs {
1734 self.columns.push(c.to_string());
1735 self.values.push(v.to_string());
1736 }
1737 self
1738 }
1739
1740 pub fn on_conflict_do_nothing(mut self, conflict_cols: &[&str]) -> Self {
1764 self.upsert = UpsertStrategy::OnConflictDoNothing(
1765 conflict_cols.iter().map(|s| s.to_string()).collect(),
1766 );
1767 self
1768 }
1769
1770 pub fn on_conflict_do_update(
1795 mut self,
1796 conflict_cols: &[&str],
1797 assignments: &[(&str, &str)],
1798 ) -> Self {
1799 self.upsert = UpsertStrategy::OnConflictDoUpdate(
1800 conflict_cols.iter().map(|s| s.to_string()).collect(),
1801 assignments
1802 .iter()
1803 .map(|(c, v)| (c.to_string(), v.to_string()))
1804 .collect(),
1805 );
1806 self
1807 }
1808
1809 pub fn on_duplicate_key_update(mut self, assignments: &[(&str, &str)]) -> Self {
1831 self.upsert = UpsertStrategy::OnDuplicateKeyUpdate(
1832 assignments
1833 .iter()
1834 .map(|(c, v)| (c.to_string(), v.to_string()))
1835 .collect(),
1836 );
1837 self
1838 }
1839
1840 pub fn replace(mut self) -> Self {
1845 self.upsert = UpsertStrategy::Replace;
1846 self
1847 }
1848
1849 pub fn returning(mut self, columns: &[&str]) -> Self {
1869 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
1870 self
1871 }
1872
1873 pub fn returning_all(mut self) -> Self {
1875 self.returning = Some(vec!["*".to_string()]);
1876 self
1877 }
1878
1879 pub fn build(self) -> String {
1891 let table = self.table.unwrap_or_default();
1892 if table.is_empty() || self.columns.is_empty() {
1893 return String::new();
1894 }
1895
1896 let cols: Vec<String> = self.columns.iter().map(|c| quote_ident(c)).collect();
1897 let vals: Vec<String> = self.values.iter().map(|v| v.to_string()).collect();
1898
1899 let verb = match &self.upsert {
1901 UpsertStrategy::Replace => "REPLACE INTO",
1902 _ => "INSERT INTO",
1903 };
1904 let mut sql = format!(
1905 "{} {} ({}) VALUES ({})",
1906 verb,
1907 quote_ident(&table),
1908 cols.join(", "),
1909 vals.join(", ")
1910 );
1911
1912 if let UpsertStrategy::OnDuplicateKeyUpdate(assignments) = &self.upsert {
1914 let sets = assignments
1915 .iter()
1916 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1917 .collect::<Vec<_>>()
1918 .join(", ");
1919 sql.push_str(&format!(" ON DUPLICATE KEY UPDATE {}", sets));
1920 }
1921
1922 sql
1923 }
1924
1925 pub fn build_with_dialect(self, db_type: DbType) -> String {
1933 let dialect = match sz_orm_core::get_dialect(db_type) {
1934 Ok(d) => d,
1935 Err(_) => return String::new(),
1936 };
1937
1938 let table = self.table.unwrap_or_default();
1939 if table.is_empty() || self.columns.is_empty() {
1940 return String::new();
1941 }
1942
1943 let cols: Vec<String> = self.columns.iter().map(|c| dialect.quote(c)).collect();
1944
1945 let verb = match (&self.upsert, db_type) {
1947 (UpsertStrategy::Replace, dt) if is_mysql_family(dt) => "REPLACE INTO",
1948 (UpsertStrategy::Replace, DbType::Sqlite) => "INSERT OR REPLACE INTO",
1949 _ => "INSERT INTO",
1950 };
1951 let mut sql = format!(
1952 "{} {} ({}) VALUES ({})",
1953 verb,
1954 dialect.quote(&table),
1955 cols.join(", "),
1956 self.values.join(", ")
1957 );
1958
1959 if let Some(clause) = render_upsert_clause(&self.upsert, db_type) {
1961 sql.push(' ');
1962 sql.push_str(&clause);
1963 }
1964
1965 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
1967 sql.push(' ');
1968 sql.push_str(&clause);
1969 }
1970
1971 sql
1972 }
1973}
1974
1975#[derive(Debug, Clone, Default)]
1977pub struct UpdateQuery {
1978 table: Option<String>,
1979 sets: Vec<(String, String)>,
1980 wheres: Vec<String>,
1981 param_wheres: Vec<ParamWhere>,
1983 returning: Option<Vec<String>>,
1985}
1986
1987impl UpdateQuery {
1988 pub fn new() -> Self {
1990 Self::default()
1991 }
1992
1993 pub fn table(mut self, table: &str) -> Self {
1995 self.table = Some(table.to_string());
1996 self
1997 }
1998
1999 pub fn set(mut self, column: &str, value: &str) -> Self {
2001 self.sets.push((column.to_string(), value.to_string()));
2002 self
2003 }
2004
2005 pub fn sets(mut self, pairs: &[(&str, &str)]) -> Self {
2007 for (c, v) in pairs {
2008 self.sets.push((c.to_string(), v.to_string()));
2009 }
2010 self
2011 }
2012
2013 pub fn where_clause(mut self, condition: &str) -> Self {
2019 check_where_injection(condition);
2020 self.wheres.push(condition.to_string());
2021 self
2022 }
2023
2024 pub fn where_eq(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_ne(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_gt(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_ge(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_lt(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_le(mut self, column: &str, value: Value) -> Self {
2076 self.param_wheres.push(ParamWhere::And {
2077 column: column.to_string(),
2078 op: " <= ?".to_string(),
2079 values: vec![value],
2080 });
2081 self
2082 }
2083
2084 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2086 self.param_wheres.push(ParamWhere::And {
2087 column: column.to_string(),
2088 op: " LIKE ?".to_string(),
2089 values: vec![pattern],
2090 });
2091 self
2092 }
2093
2094 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2096 let (column, op) = if values.is_empty() {
2097 (String::new(), "1 = 0".to_string())
2098 } else {
2099 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2100 (
2101 column.to_string(),
2102 format!(" IN ({})", placeholders.join(", ")),
2103 )
2104 };
2105 self.param_wheres
2106 .push(ParamWhere::And { column, op, values });
2107 self
2108 }
2109
2110 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2112 self.param_wheres.push(ParamWhere::And {
2113 column: column.to_string(),
2114 op: " BETWEEN ? AND ?".to_string(),
2115 values: vec![low, high],
2116 });
2117 self
2118 }
2119
2120 pub fn where_null(mut self, column: &str) -> Self {
2122 self.param_wheres.push(ParamWhere::And {
2123 column: column.to_string(),
2124 op: " IS NULL".to_string(),
2125 values: vec![],
2126 });
2127 self
2128 }
2129
2130 pub fn where_not_null(mut self, column: &str) -> Self {
2132 self.param_wheres.push(ParamWhere::And {
2133 column: column.to_string(),
2134 op: " IS NOT NULL".to_string(),
2135 values: vec![],
2136 });
2137 self
2138 }
2139
2140 pub fn returning(mut self, columns: &[&str]) -> Self {
2145 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2146 self
2147 }
2148
2149 pub fn returning_all(mut self) -> Self {
2151 self.returning = Some(vec!["*".to_string()]);
2152 self
2153 }
2154
2155 pub fn build(self) -> String {
2161 let table = self.table.unwrap_or_default();
2162 if table.is_empty() || self.sets.is_empty() {
2163 return String::new();
2164 }
2165
2166 let set_str: Vec<String> = self
2167 .sets
2168 .iter()
2169 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
2170 .collect();
2171
2172 let mut sql = format!("UPDATE {} SET {}", quote_ident(&table), set_str.join(", "));
2173
2174 if !self.wheres.is_empty() {
2175 sql.push_str(" WHERE ");
2176 sql.push_str(&self.wheres.join(" AND "));
2177 }
2178
2179 sql
2180 }
2181
2182 pub fn build_with_dialect(self, db_type: DbType) -> String {
2184 let dialect = match sz_orm_core::get_dialect(db_type) {
2185 Ok(d) => d,
2186 Err(_) => return String::new(),
2187 };
2188
2189 let table = self.table.unwrap_or_default();
2190 if table.is_empty() || self.sets.is_empty() {
2191 return String::new();
2192 }
2193
2194 let set_str: Vec<String> = self
2195 .sets
2196 .iter()
2197 .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2198 .collect();
2199
2200 let mut sql = format!(
2201 "UPDATE {} SET {}",
2202 dialect.quote(&table),
2203 set_str.join(", ")
2204 );
2205
2206 if !self.wheres.is_empty() {
2207 sql.push_str(" WHERE ");
2208 sql.push_str(&self.wheres.join(" AND "));
2209 }
2210
2211 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2213 sql.push(' ');
2214 sql.push_str(&clause);
2215 }
2216
2217 sql
2218 }
2219
2220 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2226 let dialect = match sz_orm_core::get_dialect(db_type) {
2227 Ok(d) => d,
2228 Err(_) => return BuiltQuery::default(),
2229 };
2230
2231 let table = self.table.unwrap_or_default();
2232 if table.is_empty() || self.sets.is_empty() {
2233 return BuiltQuery::default();
2234 }
2235
2236 let set_str: Vec<String> = self
2237 .sets
2238 .iter()
2239 .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2240 .collect();
2241
2242 let mut sql = format!(
2243 "UPDATE {} SET {}",
2244 dialect.quote(&table),
2245 set_str.join(", ")
2246 );
2247 let mut params: Vec<Value> = Vec::new();
2248
2249 let has_raw = !self.wheres.is_empty();
2250 let has_param = !self.param_wheres.is_empty();
2251 if has_raw || has_param {
2252 sql.push_str(" WHERE ");
2253 let mut first = true;
2254 for w in &self.wheres {
2255 if first {
2256 sql.push_str(w);
2257 first = false;
2258 } else {
2259 sql.push_str(" AND ");
2260 sql.push_str(w);
2261 }
2262 }
2263 for pw in &self.param_wheres {
2264 let (conjunction, column, op, vals) = match pw {
2265 ParamWhere::And { column, op, values } => ("AND", column, op, values),
2266 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2267 };
2268 let expr = if column.is_empty() {
2270 op.clone()
2271 } else {
2272 format!("{}{}", quote_column_dialect(&*dialect, column), op)
2273 };
2274 if first {
2275 sql.push_str(&expr);
2276 first = false;
2277 } else {
2278 sql.push(' ');
2279 sql.push_str(conjunction);
2280 sql.push(' ');
2281 sql.push_str(&expr);
2282 }
2283 params.extend(vals.iter().cloned());
2284 }
2285 }
2286
2287 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2289 sql.push(' ');
2290 sql.push_str(&clause);
2291 }
2292
2293 BuiltQuery { sql, params }
2294 }
2295}
2296
2297#[derive(Debug, Clone, Default)]
2299pub struct DeleteQuery {
2300 table: Option<String>,
2301 wheres: Vec<String>,
2302 param_wheres: Vec<ParamWhere>,
2304 returning: Option<Vec<String>>,
2306}
2307
2308impl DeleteQuery {
2309 pub fn new() -> Self {
2311 Self::default()
2312 }
2313
2314 pub fn from_table(mut self, table: &str) -> Self {
2316 self.table = Some(table.to_string());
2317 self
2318 }
2319
2320 pub fn where_clause(mut self, condition: &str) -> Self {
2326 check_where_injection(condition);
2327 self.wheres.push(condition.to_string());
2328 self
2329 }
2330
2331 pub fn where_eq(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_ne(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_gt(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_ge(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_lt(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_le(mut self, column: &str, value: Value) -> Self {
2383 self.param_wheres.push(ParamWhere::And {
2384 column: column.to_string(),
2385 op: " <= ?".to_string(),
2386 values: vec![value],
2387 });
2388 self
2389 }
2390
2391 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2393 self.param_wheres.push(ParamWhere::And {
2394 column: column.to_string(),
2395 op: " LIKE ?".to_string(),
2396 values: vec![pattern],
2397 });
2398 self
2399 }
2400
2401 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2403 let (column, op) = if values.is_empty() {
2404 (String::new(), "1 = 0".to_string())
2405 } else {
2406 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2407 (
2408 column.to_string(),
2409 format!(" IN ({})", placeholders.join(", ")),
2410 )
2411 };
2412 self.param_wheres
2413 .push(ParamWhere::And { column, op, values });
2414 self
2415 }
2416
2417 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2419 self.param_wheres.push(ParamWhere::And {
2420 column: column.to_string(),
2421 op: " BETWEEN ? AND ?".to_string(),
2422 values: vec![low, high],
2423 });
2424 self
2425 }
2426
2427 pub fn where_null(mut self, column: &str) -> Self {
2429 self.param_wheres.push(ParamWhere::And {
2430 column: column.to_string(),
2431 op: " IS NULL".to_string(),
2432 values: vec![],
2433 });
2434 self
2435 }
2436
2437 pub fn where_not_null(mut self, column: &str) -> Self {
2439 self.param_wheres.push(ParamWhere::And {
2440 column: column.to_string(),
2441 op: " IS NOT NULL".to_string(),
2442 values: vec![],
2443 });
2444 self
2445 }
2446
2447 pub fn returning(mut self, columns: &[&str]) -> Self {
2452 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2453 self
2454 }
2455
2456 pub fn returning_all(mut self) -> Self {
2458 self.returning = Some(vec!["*".to_string()]);
2459 self
2460 }
2461
2462 pub fn build(self) -> String {
2468 let table = self.table.unwrap_or_default();
2469 if table.is_empty() {
2470 return String::new();
2471 }
2472
2473 let mut sql = format!("DELETE FROM {}", quote_ident(&table));
2474
2475 if !self.wheres.is_empty() {
2476 sql.push_str(" WHERE ");
2477 sql.push_str(&self.wheres.join(" AND "));
2478 }
2479
2480 sql
2481 }
2482
2483 pub fn build_with_dialect(self, db_type: DbType) -> String {
2485 let dialect = match sz_orm_core::get_dialect(db_type) {
2486 Ok(d) => d,
2487 Err(_) => return String::new(),
2488 };
2489
2490 let table = self.table.unwrap_or_default();
2491 if table.is_empty() {
2492 return String::new();
2493 }
2494
2495 let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2496
2497 if !self.wheres.is_empty() {
2498 sql.push_str(" WHERE ");
2499 sql.push_str(&self.wheres.join(" AND "));
2500 }
2501
2502 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2504 sql.push(' ');
2505 sql.push_str(&clause);
2506 }
2507
2508 sql
2509 }
2510
2511 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2516 let dialect = match sz_orm_core::get_dialect(db_type) {
2517 Ok(d) => d,
2518 Err(_) => return BuiltQuery::default(),
2519 };
2520
2521 let table = self.table.unwrap_or_default();
2522 if table.is_empty() {
2523 return BuiltQuery::default();
2524 }
2525
2526 let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2527 let mut params: Vec<Value> = Vec::new();
2528
2529 let has_raw = !self.wheres.is_empty();
2530 let has_param = !self.param_wheres.is_empty();
2531 if has_raw || has_param {
2532 sql.push_str(" WHERE ");
2533 let mut first = true;
2534 for w in &self.wheres {
2535 if first {
2536 sql.push_str(w);
2537 first = false;
2538 } else {
2539 sql.push_str(" AND ");
2540 sql.push_str(w);
2541 }
2542 }
2543 for pw in &self.param_wheres {
2544 let (conjunction, column, op, vals) = match pw {
2545 ParamWhere::And { column, op, values } => ("AND", column, op, values),
2546 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2547 };
2548 let expr = if column.is_empty() {
2550 op.clone()
2551 } else {
2552 format!("{}{}", quote_column_dialect(&*dialect, column), op)
2553 };
2554 if first {
2555 sql.push_str(&expr);
2556 first = false;
2557 } else {
2558 sql.push(' ');
2559 sql.push_str(conjunction);
2560 sql.push(' ');
2561 sql.push_str(&expr);
2562 }
2563 params.extend(vals.iter().cloned());
2564 }
2565 }
2566
2567 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2569 sql.push(' ');
2570 sql.push_str(&clause);
2571 }
2572
2573 BuiltQuery { sql, params }
2574 }
2575}
2576
2577#[cfg(test)]
2578mod tests {
2579 use super::*;
2580
2581 #[test]
2584 fn test_select_basic() {
2585 let sql = Query::select()
2586 .column("id")
2587 .column("name")
2588 .from("users")
2589 .build(DbType::MySQL);
2590 assert!(sql.starts_with("SELECT "));
2591 assert!(sql.contains("`id`"));
2592 assert!(sql.contains("`name`"));
2593 assert!(sql.contains("FROM `users`"));
2594 }
2595
2596 #[test]
2597 fn test_select_star() {
2598 let sql = Query::select()
2599 .all_columns()
2600 .from("users")
2601 .build(DbType::MySQL);
2602 assert!(sql.contains("SELECT *"));
2603 assert!(sql.contains("FROM `users`"));
2604 }
2605
2606 #[test]
2607 fn test_select_distinct() {
2608 let sql = Query::select()
2609 .distinct()
2610 .column("name")
2611 .from("users")
2612 .build(DbType::MySQL);
2613 assert!(sql.contains("SELECT DISTINCT"));
2614 }
2615
2616 #[test]
2617 fn test_select_with_where() {
2618 let sql = Query::select()
2619 .column("id")
2620 .from("users")
2621 .where_clause("age > 18")
2622 .where_clause("status = 'active'")
2623 .build(DbType::MySQL);
2624 assert!(sql.contains("WHERE age > 18 AND status = 'active'"));
2625 }
2626
2627 #[test]
2628 fn test_select_with_or_where() {
2629 let sql = Query::select()
2630 .column("id")
2631 .from("users")
2632 .where_clause("age > 18")
2633 .or_where("role = 'admin'")
2634 .build(DbType::MySQL);
2635 assert!(sql.contains("WHERE age > 18 OR role = 'admin'"));
2636 }
2637
2638 #[test]
2639 fn test_select_with_inner_join() {
2640 let sql = Query::select()
2641 .column("u.id")
2642 .from("users u")
2643 .inner_join("orders o", "u.id = o.user_id")
2644 .build(DbType::MySQL);
2645 assert!(sql.contains("INNER JOIN `orders` o ON u.id = o.user_id"));
2646 }
2647
2648 #[test]
2649 fn test_select_with_left_join() {
2650 let sql = Query::select()
2651 .column("u.id")
2652 .from("users u")
2653 .left_join("profiles p", "u.id = p.user_id")
2654 .build(DbType::MySQL);
2655 assert!(sql.contains("LEFT JOIN `profiles` p ON u.id = p.user_id"));
2656 }
2657
2658 #[test]
2659 fn test_select_with_order_by() {
2660 let sql = Query::select()
2661 .column("id")
2662 .from("users")
2663 .order_by("created_at", true)
2664 .order_by("id", false)
2665 .build(DbType::MySQL);
2666 assert!(sql.contains("ORDER BY `created_at` ASC, `id` DESC"));
2667 }
2668
2669 #[test]
2670 fn test_select_with_limit_offset() {
2671 let sql = Query::select()
2672 .column("id")
2673 .from("users")
2674 .limit(10)
2675 .offset(20)
2676 .build(DbType::MySQL);
2677 assert!(sql.contains("LIMIT 10"));
2678 assert!(sql.contains("OFFSET 20"));
2679 }
2680
2681 #[test]
2682 fn test_select_paginate() {
2683 let sql = Query::select()
2684 .column("id")
2685 .from("users")
2686 .paginate(3, 20)
2687 .build(DbType::MySQL);
2688 assert!(sql.contains("LIMIT 20"));
2690 assert!(sql.contains("OFFSET 40"));
2691 }
2692
2693 #[test]
2694 fn test_select_with_group_by_having() {
2695 let sql = Query::select()
2696 .column("status")
2697 .from("users")
2698 .group_by("status")
2699 .having("COUNT(*) > 5")
2700 .build(DbType::MySQL);
2701 assert!(sql.contains("GROUP BY `status`"));
2702 assert!(sql.contains("HAVING COUNT(*) > 5"));
2703 }
2704
2705 #[test]
2706 fn test_select_postgres_dialect() {
2707 let sql = Query::select()
2708 .column("id")
2709 .from("users")
2710 .build(DbType::PostgreSQL);
2711 assert!(sql.contains("\"id\""));
2712 assert!(sql.contains("FROM \"users\""));
2713 }
2714
2715 #[test]
2716 fn test_select_sqlite_dialect() {
2717 let sql = Query::select()
2718 .column("id")
2719 .from("users")
2720 .build(DbType::Sqlite);
2721 assert!(sql.contains("\"id\""));
2722 }
2723
2724 #[test]
2725 fn test_select_multiple_joins() {
2726 let sql = Query::select()
2727 .column("u.id")
2728 .from("users u")
2729 .inner_join("orders o", "u.id = o.user_id")
2730 .left_join("profiles p", "u.id = p.user_id")
2731 .build(DbType::MySQL);
2732 assert!(sql.contains("INNER JOIN `orders` o"));
2733 assert!(sql.contains("LEFT JOIN `profiles` p"));
2734 }
2735
2736 #[test]
2737 fn test_select_columns_multiple() {
2738 let sql = Query::select()
2739 .columns(&["id", "name", "email"])
2740 .from("users")
2741 .build(DbType::MySQL);
2742 assert!(sql.contains("`id`, `name`, `email`"));
2743 }
2744
2745 #[test]
2746 fn test_select_no_columns_defaults_star() {
2747 let sql = Query::select().from("users").build(DbType::MySQL);
2748 assert!(sql.contains("SELECT *"));
2749 }
2750
2751 #[test]
2754 fn test_insert_basic() {
2755 let sql = Query::insert()
2756 .into_table("users")
2757 .value("name", "'Alice'")
2758 .value("age", "30")
2759 .build();
2760 assert!(sql.starts_with("INSERT INTO `users`"));
2761 assert!(sql.contains("`name`, `age`"));
2762 assert!(sql.contains("'Alice', 30"));
2763 }
2764
2765 #[test]
2766 fn test_insert_values_batch() {
2767 let sql = Query::insert()
2768 .into_table("users")
2769 .values(&[("name", "'Bob'"), ("age", "25"), ("email", "'bob@x.com'")])
2770 .build();
2771 assert!(sql.contains("`name`, `age`, `email`"));
2772 assert!(sql.contains("'Bob', 25, 'bob@x.com'"));
2773 }
2774
2775 #[test]
2776 fn test_insert_empty_returns_empty() {
2777 let sql = Query::insert().into_table("users").build();
2778 assert_eq!(sql, "");
2779 }
2780
2781 #[test]
2782 fn test_insert_with_dialect() {
2783 let sql = Query::insert()
2784 .into_table("users")
2785 .value("name", "'Alice'")
2786 .build_with_dialect(DbType::PostgreSQL);
2787 assert!(sql.contains("\"name\""));
2788 assert!(sql.contains("\"users\""));
2789 }
2790
2791 #[test]
2794 fn test_update_basic() {
2795 let sql = Query::update()
2796 .table("users")
2797 .set("name", "'Bob'")
2798 .where_clause("id = 1")
2799 .build();
2800 assert!(sql.starts_with("UPDATE `users` SET"));
2801 assert!(sql.contains("`name` = 'Bob'"));
2802 assert!(sql.contains("WHERE id = 1"));
2803 }
2804
2805 #[test]
2806 fn test_update_multiple_sets() {
2807 let sql = Query::update()
2808 .table("users")
2809 .sets(&[("name", "'Bob'"), ("age", "30")])
2810 .where_clause("id = 1")
2811 .build();
2812 assert!(sql.contains("`name` = 'Bob', `age` = 30"));
2813 }
2814
2815 #[test]
2816 fn test_update_no_where() {
2817 let sql = Query::update()
2818 .table("users")
2819 .set("status", "'active'")
2820 .build();
2821 assert!(sql.contains("UPDATE `users` SET `status` = 'active'"));
2822 assert!(!sql.contains("WHERE"));
2823 }
2824
2825 #[test]
2826 fn test_update_empty_returns_empty() {
2827 let sql = Query::update().table("users").build();
2828 assert_eq!(sql, "");
2829 }
2830
2831 #[test]
2832 fn test_update_with_dialect() {
2833 let sql = Query::update()
2834 .table("users")
2835 .set("name", "'Bob'")
2836 .build_with_dialect(DbType::PostgreSQL);
2837 assert!(sql.contains("\"users\""));
2838 assert!(sql.contains("\"name\""));
2839 }
2840
2841 #[test]
2844 fn test_delete_basic() {
2845 let sql = Query::delete()
2846 .from_table("users")
2847 .where_clause("id = 1")
2848 .build();
2849 assert!(sql.starts_with("DELETE FROM `users`"));
2850 assert!(sql.contains("WHERE id = 1"));
2851 }
2852
2853 #[test]
2854 fn test_delete_no_where() {
2855 let sql = Query::delete().from_table("users").build();
2856 assert!(sql.contains("DELETE FROM `users`"));
2857 assert!(!sql.contains("WHERE"));
2858 }
2859
2860 #[test]
2861 fn test_delete_multiple_wheres() {
2862 let sql = Query::delete()
2863 .from_table("users")
2864 .where_clause("id > 100")
2865 .where_clause("status = 'inactive'")
2866 .build();
2867 assert!(sql.contains("WHERE id > 100 AND status = 'inactive'"));
2868 }
2869
2870 #[test]
2871 fn test_delete_empty_returns_empty() {
2872 let sql = Query::delete().build();
2873 assert_eq!(sql, "");
2874 }
2875
2876 #[test]
2877 fn test_delete_with_dialect() {
2878 let sql = Query::delete()
2879 .from_table("users")
2880 .where_clause("id = 1")
2881 .build_with_dialect(DbType::PostgreSQL);
2882 assert!(sql.contains("\"users\""));
2883 }
2884
2885 #[test]
2888 fn test_full_crud_flow() {
2889 let insert = Query::insert()
2891 .into_table("users")
2892 .value("name", "'Alice'")
2893 .value("age", "30")
2894 .build();
2895 assert!(insert.contains("INSERT INTO"));
2896
2897 let select = Query::select()
2899 .column("id")
2900 .column("name")
2901 .from("users")
2902 .where_clause("age > 18")
2903 .order_by("id", true)
2904 .limit(10)
2905 .build(DbType::MySQL);
2906 assert!(select.contains("SELECT"));
2907 assert!(select.contains("FROM"));
2908 assert!(select.contains("WHERE"));
2909 assert!(select.contains("ORDER BY"));
2910 assert!(select.contains("LIMIT"));
2911
2912 let update = Query::update()
2914 .table("users")
2915 .set("name", "'Bob'")
2916 .where_clause("id = 1")
2917 .build();
2918 assert!(update.contains("UPDATE"));
2919 assert!(update.contains("SET"));
2920 assert!(update.contains("WHERE"));
2921
2922 let delete = Query::delete()
2924 .from_table("users")
2925 .where_clause("id = 1")
2926 .build();
2927 assert!(delete.contains("DELETE FROM"));
2928 }
2929
2930 #[test]
2931 fn test_complex_select_query() {
2932 let sql = Query::select()
2933 .distinct()
2934 .columns(&["u.id", "u.name", "o.total"])
2935 .from("users u")
2936 .inner_join("orders o", "u.id = o.user_id")
2937 .where_clause("u.status = 'active'")
2938 .where_clause("o.total > 100")
2939 .group_by("u.id")
2940 .having("SUM(o.total) > 1000")
2941 .order_by("u.id", true)
2942 .limit(20)
2943 .offset(40)
2944 .build(DbType::MySQL);
2945
2946 assert!(sql.contains("SELECT DISTINCT"));
2947 assert!(sql.contains("INNER JOIN `orders` o"));
2948 assert!(sql.contains("WHERE u.status = 'active' AND o.total > 100"));
2949 assert!(sql.contains("GROUP BY"));
2950 assert!(sql.contains("HAVING SUM(o.total) > 1000"));
2951 assert!(sql.contains("ORDER BY `u`.`id` ASC"));
2952 assert!(sql.contains("LIMIT 20"));
2953 assert!(sql.contains("OFFSET 40"));
2954 }
2955
2956 #[test]
2959 #[should_panic(expected = "SQL injection detected")]
2960 fn test_select_where_rejects_semicolon_drop() {
2961 let _ = Query::select()
2962 .column("id")
2963 .from("users")
2964 .where_clause("1=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_semicolon_space_drop() {
2971 let _ = Query::select()
2972 .column("id")
2973 .from("users")
2974 .where_clause("1=1; DROP TABLE users")
2975 .build(DbType::MySQL);
2976 }
2977
2978 #[test]
2979 #[should_panic(expected = "SQL injection detected")]
2980 fn test_select_where_rejects_line_comment() {
2981 let _ = Query::select()
2982 .column("id")
2983 .from("users")
2984 .where_clause("id = 1 -- DROP TABLE users")
2985 .build(DbType::MySQL);
2986 }
2987
2988 #[test]
2989 #[should_panic(expected = "SQL injection detected")]
2990 fn test_select_where_rejects_block_comment() {
2991 let _ = Query::select()
2992 .column("id")
2993 .from("users")
2994 .where_clause("id = 1 /* comment */ OR 1=1")
2995 .build(DbType::MySQL);
2996 }
2997
2998 #[test]
2999 #[should_panic(expected = "SQL injection detected")]
3000 fn test_select_or_where_rejects_drop() {
3001 let _ = Query::select()
3002 .column("id")
3003 .from("users")
3004 .where_clause("id = 1")
3005 .or_where("1=1; DROP TABLE users")
3006 .build(DbType::MySQL);
3007 }
3008
3009 #[test]
3010 #[should_panic(expected = "SQL injection detected")]
3011 fn test_update_where_rejects_delete() {
3012 let _ = Query::update()
3013 .table("users")
3014 .set("name", "'x'")
3015 .where_clause("1=1; DELETE FROM users")
3016 .build();
3017 }
3018
3019 #[test]
3020 #[should_panic(expected = "SQL injection detected")]
3021 fn test_update_where_rejects_line_comment() {
3022 let _ = Query::update()
3023 .table("users")
3024 .set("name", "'x'")
3025 .where_clause("id = 1 -- bypass")
3026 .build();
3027 }
3028
3029 #[test]
3030 #[should_panic(expected = "SQL injection detected")]
3031 fn test_delete_where_rejects_drop() {
3032 let _ = Query::delete()
3033 .from_table("users")
3034 .where_clause("1=1; DROP TABLE users")
3035 .build();
3036 }
3037
3038 #[test]
3039 #[should_panic(expected = "SQL injection detected")]
3040 fn test_delete_where_rejects_block_comment() {
3041 let _ = Query::delete()
3042 .from_table("users")
3043 .where_clause("id = 1 /* */ OR 1=1")
3044 .build();
3045 }
3046
3047 #[test]
3048 #[should_panic(expected = "SQL injection detected")]
3049 fn test_delete_where_rejects_line_comment() {
3050 let _ = Query::delete()
3051 .from_table("users")
3052 .where_clause("id = 1--")
3053 .build();
3054 }
3055
3056 #[test]
3057 fn test_safe_where_clauses_pass() {
3058 let sql_str = Query::select()
3060 .column("id")
3061 .from("users")
3062 .where_clause("age > 18")
3063 .where_clause("name = 'Alice;Bob'") .where_clause("id IN (1, 2, 3)")
3065 .where_clause("created_at > '2026-01-01'")
3066 .build(DbType::MySQL);
3067 assert!(!sql_str.is_empty(), "SELECT SQL 不应为空");
3068 assert!(sql_str.contains("age > 18"), "SELECT 应包含 age > 18 条件");
3069 assert!(sql_str.contains("name = 'Alice;Bob'"), "SELECT 应包含 name 条件(含分号字面量)");
3070 assert!(sql_str.contains("id IN (1, 2, 3)"), "SELECT 应包含 IN 子句");
3071 assert!(sql_str.contains("created_at > '2026-01-01'"), "SELECT 应包含日期条件");
3072
3073 let sql_str = Query::update()
3074 .table("users")
3075 .set("name", "'x'")
3076 .where_clause("id = 1")
3077 .build();
3078 assert!(!sql_str.is_empty(), "UPDATE SQL 不应为空");
3079 assert!(sql_str.contains("UPDATE"), "应为 UPDATE 语句");
3080 assert!(sql_str.contains("WHERE"), "UPDATE 应包含 WHERE 子句");
3081 assert!(sql_str.contains("id = 1"), "UPDATE WHERE 应包含 id = 1");
3082
3083 let sql_str = Query::delete()
3084 .from_table("users")
3085 .where_clause("id = 1")
3086 .build();
3087 assert!(!sql_str.is_empty(), "DELETE SQL 不应为空");
3088 assert!(sql_str.contains("DELETE"), "应为 DELETE 语句");
3089 assert!(sql_str.contains("WHERE"), "DELETE 应包含 WHERE 子句");
3090 assert!(sql_str.contains("id = 1"), "DELETE WHERE 应包含 id = 1");
3091 }
3092
3093 #[test]
3097 #[should_panic(expected = "SQL injection detected")]
3098 fn test_mutant_block_comment_open_only() {
3099 let _ = Query::select()
3102 .column("id")
3103 .from("users")
3104 .where_clause("id = 1 /* OR 1=1")
3105 .build(DbType::MySQL);
3106 }
3107
3108 #[test]
3109 #[should_panic(expected = "SQL injection detected")]
3110 fn test_mutant_block_comment_close_only() {
3111 let _ = Query::select()
3113 .column("id")
3114 .from("users")
3115 .where_clause("id = 1 */")
3116 .build(DbType::MySQL);
3117 }
3118
3119 #[test]
3120 fn test_mutant_insert_dialect_table_no_columns_returns_empty() {
3121 let sql = Query::insert()
3123 .into_table("users")
3124 .build_with_dialect(DbType::MySQL);
3125 assert_eq!(sql, "", "有表无列时应返回空字符串");
3126 }
3127
3128 #[test]
3129 fn test_mutant_update_dialect_table_no_sets_returns_empty() {
3130 let sql = Query::update()
3132 .table("users")
3133 .build_with_dialect(DbType::MySQL);
3134 assert_eq!(sql, "", "有表无 SET 时应返回空字符串");
3135 }
3136
3137 #[test]
3138 fn test_mutant_update_dialect_no_table_with_sets_returns_empty() {
3139 let sql = Query::update()
3141 .set("name", "'x'")
3142 .build_with_dialect(DbType::MySQL);
3143 assert_eq!(sql, "", "无表有 SET 时应返回空字符串");
3144 }
3145
3146 #[test]
3147 fn test_mutant_delete_dialect_no_table_returns_empty() {
3148 let sql = Query::delete()
3150 .where_clause("id = 1")
3151 .build_with_dialect(DbType::MySQL);
3152 assert_eq!(sql, "", "无表时应返回空字符串");
3153 }
3154
3155 #[test]
3156 fn test_mutant_select_right_join() {
3157 let sql = Query::select()
3159 .column("u.id")
3160 .from("users u")
3161 .right_join("orders o", "u.id = o.user_id")
3162 .build(DbType::MySQL);
3163 assert!(sql.contains("RIGHT JOIN `orders` o ON u.id = o.user_id"));
3164 }
3165
3166 #[test]
3167 fn test_mutant_all_columns_with_extra() {
3168 let sql = Query::select()
3171 .all_columns()
3172 .column("extra")
3173 .from("users")
3174 .build(DbType::MySQL);
3175 assert!(
3176 sql.contains("SELECT *, `extra` FROM `users`"),
3177 "all_columns + column 应在 SELECT 列表中同时包含 * 和 extra,实际: {sql}"
3178 );
3179 }
3180
3181 #[test]
3182 fn test_mutant_update_dialect_no_where_no_where_clause() {
3183 let sql = Query::update()
3186 .table("users")
3187 .set("name", "'x'")
3188 .build_with_dialect(DbType::MySQL);
3189 assert!(
3190 !sql.contains("WHERE"),
3191 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3192 );
3193 }
3194
3195 #[test]
3196 fn test_mutant_delete_dialect_no_where_no_where_clause() {
3197 let sql = Query::delete()
3199 .from_table("users")
3200 .build_with_dialect(DbType::MySQL);
3201 assert!(
3202 !sql.contains("WHERE"),
3203 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3204 );
3205 }
3206
3207 #[test]
3212 fn test_cte_single_with_clause() {
3213 let sql = Query::select()
3214 .column("id")
3215 .column("name")
3216 .from("active_users")
3217 .with_cte(
3218 "active_users",
3219 "SELECT * FROM users WHERE status = 'active'",
3220 )
3221 .build(DbType::MySQL);
3222 assert!(sql.starts_with("WITH active_users AS ("));
3223 assert!(sql.contains("SELECT * FROM users WHERE status = 'active'"));
3224 assert!(sql.contains("SELECT `id`, `name` FROM `active_users`"));
3225 }
3226
3227 #[test]
3228 fn test_cte_multiple_with_clauses() {
3229 let sql = Query::select()
3230 .column("id")
3231 .from("combined")
3232 .with_cte("a", "SELECT id FROM table_a")
3233 .with_cte("b", "SELECT id FROM table_b")
3234 .with_cte("combined", "SELECT id FROM a UNION SELECT id FROM b")
3235 .build(DbType::MySQL);
3236 assert!(sql.starts_with(
3237 "WITH a AS (SELECT id FROM table_a), b AS (SELECT id FROM table_b), combined AS ("
3238 ));
3239 }
3240
3241 #[test]
3242 fn test_cte_recursive_with_clause() {
3243 let sql = Query::select()
3244 .column("id")
3245 .column("parent_id")
3246 .from("tree")
3247 .with_recursive_cte("tree", "SELECT id, parent_id FROM nodes WHERE id = 1")
3248 .build(DbType::MySQL);
3249 assert!(sql.starts_with("WITH RECURSIVE tree AS ("));
3250 }
3251
3252 #[test]
3253 fn test_cte_no_cte_no_with_prefix() {
3254 let sql = Query::select()
3255 .column("id")
3256 .from("users")
3257 .build(DbType::MySQL);
3258 assert!(!sql.contains("WITH"));
3259 assert!(sql.starts_with("SELECT"));
3260 }
3261
3262 #[test]
3265 fn test_window_function_raw_expr() {
3266 let sql = Query::select()
3267 .column("id")
3268 .column("salary")
3269 .from("employees")
3270 .window_function("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn")
3271 .build(DbType::MySQL);
3272 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3273 }
3274
3275 #[test]
3276 fn test_row_number_helper_with_partition() {
3277 let sql = Query::select()
3278 .column("name")
3279 .column("dept")
3280 .from("employees")
3281 .row_number("dept", "salary DESC", "row_num")
3282 .build(DbType::MySQL);
3283 assert!(
3284 sql.contains("ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS row_num")
3285 );
3286 }
3287
3288 #[test]
3289 fn test_row_number_helper_without_partition() {
3290 let sql = Query::select()
3291 .column("name")
3292 .from("employees")
3293 .row_number("", "salary DESC", "rn")
3294 .build(DbType::MySQL);
3295 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3296 assert!(!sql.contains("PARTITION BY"));
3297 }
3298
3299 #[test]
3300 fn test_rank_helper() {
3301 let sql = Query::select()
3302 .column("name")
3303 .from("scores")
3304 .rank("", "score DESC", "rank_num")
3305 .build(DbType::MySQL);
3306 assert!(sql.contains("RANK() OVER (ORDER BY score DESC) AS rank_num"));
3307 }
3308
3309 #[test]
3310 fn test_dense_rank_helper_with_partition() {
3311 let sql = Query::select()
3312 .column("name")
3313 .from("scores")
3314 .dense_rank("class", "score DESC", "dr")
3315 .build(DbType::MySQL);
3316 assert!(sql.contains("DENSE_RANK() OVER (PARTITION BY class ORDER BY score DESC) AS dr"));
3317 }
3318
3319 #[test]
3320 fn test_multiple_window_functions() {
3321 let sql = Query::select()
3322 .column("name")
3323 .column("salary")
3324 .from("employees")
3325 .row_number("dept", "salary DESC", "rn")
3326 .rank("dept", "salary DESC", "rk")
3327 .dense_rank("dept", "salary DESC", "dr")
3328 .build(DbType::MySQL);
3329 assert!(sql.contains("ROW_NUMBER()"));
3330 assert!(sql.contains("RANK()"));
3331 assert!(sql.contains("DENSE_RANK()"));
3332 }
3333
3334 #[test]
3335 fn test_window_function_with_cte_combined() {
3336 let sql = Query::select()
3337 .column("name")
3338 .from("ranked")
3339 .with_cte(
3340 "ranked",
3341 "SELECT name, ROW_NUMBER() OVER (ORDER BY salary) AS rn FROM employees",
3342 )
3343 .where_clause("rn <= 10")
3344 .build(DbType::MySQL);
3345 assert!(sql.starts_with("WITH ranked AS ("));
3346 assert!(sql.contains("FROM `ranked`"));
3347 assert!(sql.contains("WHERE rn <= 10"));
3348 }
3349
3350 #[test]
3353 fn test_for_update_basic() {
3354 let sql = Query::select()
3355 .column("id")
3356 .column("balance")
3357 .from("accounts")
3358 .where_clause("id = 1")
3359 .for_update()
3360 .build(DbType::MySQL);
3361 assert!(sql.ends_with(" FOR UPDATE"));
3362 assert!(sql.contains("WHERE id = 1"));
3363 }
3364
3365 #[test]
3366 fn test_for_update_with_nowait() {
3367 let sql = Query::select()
3368 .column("id")
3369 .from("accounts")
3370 .where_clause("id = 1")
3371 .for_update_with_options("NOWAIT")
3372 .build(DbType::MySQL);
3373 assert!(sql.ends_with(" FOR UPDATE NOWAIT"));
3374 }
3375
3376 #[test]
3377 fn test_for_update_with_skip_locked() {
3378 let sql = Query::select()
3379 .column("id")
3380 .from("accounts")
3381 .where_clause("id = 1")
3382 .for_update_with_options("SKIP LOCKED")
3383 .build(DbType::MySQL);
3384 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3385 }
3386
3387 #[test]
3388 fn test_for_update_with_limit_and_order() {
3389 let sql = Query::select()
3390 .column("id")
3391 .from("jobs")
3392 .order_by("priority", false)
3393 .limit(1)
3394 .for_update_with_options("SKIP LOCKED")
3395 .build(DbType::MySQL);
3396 assert!(sql.contains("ORDER BY `priority` DESC"));
3397 assert!(sql.contains("LIMIT 1"));
3398 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3399 }
3400
3401 #[test]
3402 fn test_no_for_update_by_default() {
3403 let sql = Query::select()
3404 .column("id")
3405 .from("users")
3406 .build(DbType::MySQL);
3407 assert!(!sql.contains("FOR UPDATE"));
3408 }
3409
3410 #[test]
3413 fn test_set_operator_as_sql() {
3414 assert_eq!(SetOperator::Union.as_sql(), "UNION");
3415 assert_eq!(SetOperator::UnionAll.as_sql(), "UNION ALL");
3416 assert_eq!(SetOperator::Intersect.as_sql(), "INTERSECT");
3417 assert_eq!(SetOperator::Except.as_sql(), "EXCEPT");
3418 }
3419
3420 #[test]
3421 fn test_union_basic() {
3422 let q1 = Query::select().column("id").from("active_users");
3423 let q2 = Query::select().column("id").from("pending_users");
3424 let sql = q1.union(q2).build(DbType::MySQL);
3425 assert!(sql.contains("SELECT `id` FROM `active_users`"));
3426 assert!(sql.contains(" UNION "));
3427 assert!(sql.contains("SELECT `id` FROM `pending_users`"));
3428 }
3429
3430 #[test]
3431 fn test_union_all_basic() {
3432 let q1 = Query::select().column("id").from("table_a");
3433 let q2 = Query::select().column("id").from("table_b");
3434 let sql = q1.union_all(q2).build(DbType::MySQL);
3435 assert!(sql.contains(" UNION ALL "));
3436 }
3437
3438 #[test]
3439 fn test_intersect_basic() {
3440 let q1 = Query::select().column("id").from("table_a");
3441 let q2 = Query::select().column("id").from("table_b");
3442 let sql = q1.intersect(q2).build(DbType::MySQL);
3443 assert!(sql.contains(" INTERSECT "));
3444 }
3445
3446 #[test]
3447 fn test_except_basic() {
3448 let q1 = Query::select().column("id").from("table_a");
3449 let q2 = Query::select().column("id").from("table_b");
3450 let sql = q1.except(q2).build(DbType::MySQL);
3451 assert!(sql.contains(" EXCEPT "));
3452 }
3453
3454 #[test]
3455 fn test_union_chained_multiple() {
3456 let q1 = Query::select().column("id").from("t1");
3457 let q2 = Query::select().column("id").from("t2");
3458 let q3 = Query::select().column("id").from("t3");
3459 let sql = q1.union(q2).union(q3).build(DbType::MySQL);
3460 assert_eq!(sql.matches("UNION").count(), 2);
3461 }
3462
3463 #[test]
3464 fn test_union_mixed_operators() {
3465 let q1 = Query::select().column("id").from("t1");
3466 let q2 = Query::select().column("id").from("t2");
3467 let q3 = Query::select().column("id").from("t3");
3468 let sql = q1.union(q2).intersect(q3).build(DbType::MySQL);
3469 assert!(sql.contains(" UNION "));
3470 assert!(sql.contains(" INTERSECT "));
3471 }
3472
3473 #[test]
3474 fn test_union_with_order_by_limit() {
3475 let q1 = Query::select().column("id").from("t1");
3476 let q2 = Query::select().column("id").from("t2");
3477 let sql = q1
3478 .union(q2)
3479 .order_by("id", true)
3480 .limit(10)
3481 .offset(5)
3482 .build(DbType::MySQL);
3483 assert!(sql.contains("ORDER BY `id` ASC"));
3484 assert!(sql.contains("LIMIT 10"));
3485 assert!(sql.contains("OFFSET 5"));
3486 }
3487
3488 #[test]
3489 fn test_union_postgres_dialect() {
3490 let q1 = Query::select().column("id").from("t1");
3491 let q2 = Query::select().column("id").from("t2");
3492 let sql = q1.union(q2).build(DbType::PostgreSQL);
3493 assert!(sql.contains("\"id\""));
3494 assert!(sql.contains(" UNION "));
3495 }
3496
3497 #[test]
3498 fn test_union_with_where_clauses() {
3499 let q1 = Query::select()
3500 .column("id")
3501 .from("active_users")
3502 .where_clause("age > 18");
3503 let q2 = Query::select()
3504 .column("id")
3505 .from("pending_users")
3506 .where_clause("age > 18");
3507 let sql = q1.union(q2).build(DbType::MySQL);
3508 assert!(sql.contains("WHERE age > 18"));
3509 assert!(sql.contains(" UNION "));
3510 }
3511
3512 #[test]
3515 fn test_cte_window_for_update_combined() {
3516 let sql = Query::select()
3518 .column("id")
3519 .column("salary")
3520 .from("ranked_salaries")
3521 .with_cte(
3522 "ranked_salaries",
3523 "SELECT id, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM employees",
3524 )
3525 .where_clause("rn = 1")
3526 .for_update()
3527 .build(DbType::MySQL);
3528 assert!(sql.starts_with("WITH ranked_salaries AS ("));
3529 assert!(sql.contains("FOR UPDATE"));
3530 assert!(sql.contains("WHERE rn = 1"));
3531 }
3532
3533 #[test]
3534 fn test_complex_window_aggregation() {
3535 let sql = Query::select()
3537 .column("user_id")
3538 .column("amount")
3539 .from("transactions")
3540 .window_function(
3541 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total",
3542 )
3543 .rank("user_id", "created_at", "tx_rank")
3544 .build(DbType::MySQL);
3545 assert!(sql.contains(
3546 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total"
3547 ));
3548 assert!(sql.contains("RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS tx_rank"));
3549 }
3550
3551 #[test]
3554 fn test_select_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 .build_with_params(DbType::MySQL);
3561 assert!(built.sql.contains("WHERE `age` = ?"));
3562 assert_eq!(built.params.len(), 1);
3563 assert_eq!(built.params[0], Value::I32(18));
3564 }
3565
3566 #[test]
3567 fn test_select_multiple_where_params() {
3568 use sz_orm_core::Value;
3569 let built = Query::select()
3570 .column("id")
3571 .from("users")
3572 .where_eq("age", Value::I32(18))
3573 .where_eq("status", Value::String("active".to_string()))
3574 .build_with_params(DbType::MySQL);
3575 assert!(built.sql.contains("WHERE `age` = ? AND `status` = ?"));
3576 assert_eq!(built.params.len(), 2);
3577 }
3578
3579 #[test]
3580 fn test_select_or_where_eq_params() {
3581 use sz_orm_core::Value;
3582 let built = Query::select()
3583 .column("id")
3584 .from("users")
3585 .where_eq("age", Value::I32(18))
3586 .or_where_eq("role", Value::String("admin".to_string()))
3587 .build_with_params(DbType::MySQL);
3588 assert!(built.sql.contains("WHERE `age` = ? OR `role` = ?"));
3589 assert_eq!(built.params.len(), 2);
3590 }
3591
3592 #[test]
3593 fn test_select_where_in_params() {
3594 use sz_orm_core::Value;
3595 let built = Query::select()
3596 .column("id")
3597 .from("users")
3598 .where_in("id", vec![Value::I32(1), Value::I32(2), Value::I32(3)])
3599 .build_with_params(DbType::MySQL);
3600 assert!(built.sql.contains("WHERE `id` IN (?, ?, ?)"));
3601 assert_eq!(built.params.len(), 3);
3602 }
3603
3604 #[test]
3605 fn test_select_where_in_empty() {
3606 let built = Query::select()
3607 .column("id")
3608 .from("users")
3609 .where_in("id", vec![])
3610 .build_with_params(DbType::MySQL);
3611 assert!(built.sql.contains("WHERE 1 = 0"));
3612 assert_eq!(built.params.len(), 0);
3613 }
3614
3615 #[test]
3616 fn test_select_where_between_params() {
3617 use sz_orm_core::Value;
3618 let built = Query::select()
3619 .column("id")
3620 .from("users")
3621 .where_between("age", Value::I32(18), Value::I32(65))
3622 .build_with_params(DbType::MySQL);
3623 assert!(built.sql.contains("WHERE `age` BETWEEN ? AND ?"));
3624 assert_eq!(built.params.len(), 2);
3625 }
3626
3627 #[test]
3628 fn test_select_where_null_params() {
3629 let built = Query::select()
3630 .column("id")
3631 .from("users")
3632 .where_null("deleted_at")
3633 .build_with_params(DbType::MySQL);
3634 assert!(built.sql.contains("WHERE `deleted_at` IS NULL"));
3635 assert_eq!(built.params.len(), 0);
3636 }
3637
3638 #[test]
3639 fn test_select_where_not_null_params() {
3640 let built = Query::select()
3641 .column("id")
3642 .from("users")
3643 .where_not_null("email")
3644 .build_with_params(DbType::MySQL);
3645 assert!(built.sql.contains("WHERE `email` IS NOT NULL"));
3646 }
3647
3648 #[test]
3649 fn test_select_mixed_raw_and_param_where() {
3650 use sz_orm_core::Value;
3651 let built = Query::select()
3652 .column("id")
3653 .from("users")
3654 .where_clause("age > 18")
3655 .where_eq("status", Value::String("active".to_string()))
3656 .build_with_params(DbType::MySQL);
3657 assert!(built.sql.contains("WHERE age > 18 AND `status` = ?"));
3658 assert_eq!(built.params.len(), 1);
3659 }
3660
3661 #[test]
3662 fn test_select_param_where_with_order_limit() {
3663 use sz_orm_core::Value;
3664 let built = Query::select()
3665 .column("id")
3666 .from("users")
3667 .where_eq("age", Value::I32(18))
3668 .order_by("id", true)
3669 .limit(10)
3670 .build_with_params(DbType::MySQL);
3671 assert!(built.sql.contains("WHERE `age` = ?"));
3672 assert!(built.sql.contains("ORDER BY `id` ASC"));
3673 assert!(built.sql.contains("LIMIT 10"));
3674 }
3675
3676 #[test]
3677 fn test_select_param_where_postgres_dialect() {
3678 use sz_orm_core::Value;
3679 let built = Query::select()
3680 .column("id")
3681 .from("users")
3682 .where_eq("age", Value::I32(18))
3683 .build_with_params(DbType::PostgreSQL);
3684 assert!(built.sql.contains("WHERE \"age\" = ?"));
3685 }
3686
3687 #[test]
3688 fn test_select_param_where_injection_safe() {
3689 use sz_orm_core::Value;
3691 let malicious = "'; DROP TABLE users; --".to_string();
3692 let built = Query::select()
3693 .column("id")
3694 .from("users")
3695 .where_eq("name", Value::String(malicious.clone()))
3696 .build_with_params(DbType::MySQL);
3697 assert!(!built.sql.contains("DROP TABLE"));
3699 assert!(!built.sql.contains(";"));
3700 assert_eq!(built.params.len(), 1);
3702 assert_eq!(built.params[0], Value::String(malicious));
3703 }
3704
3705 #[test]
3706 fn test_update_where_eq_params() {
3707 use sz_orm_core::Value;
3708 let built = Query::update()
3709 .table("users")
3710 .set("name", "'Bob'")
3711 .where_eq("id", Value::I64(1))
3712 .build_with_params(DbType::MySQL);
3713 assert!(built.sql.contains("UPDATE `users` SET"));
3714 assert!(built.sql.contains("WHERE `id` = ?"));
3715 assert_eq!(built.params.len(), 1);
3716 }
3717
3718 #[test]
3719 fn test_update_where_in_params() {
3720 use sz_orm_core::Value;
3721 let built = Query::update()
3722 .table("users")
3723 .set("status", "'inactive'")
3724 .where_in("id", vec![Value::I64(1), Value::I64(2)])
3725 .build_with_params(DbType::MySQL);
3726 assert!(built.sql.contains("WHERE `id` IN (?, ?)"));
3727 assert_eq!(built.params.len(), 2);
3728 }
3729
3730 #[test]
3731 fn test_delete_where_eq_params() {
3732 use sz_orm_core::Value;
3733 let built = Query::delete()
3734 .from_table("users")
3735 .where_eq("id", Value::I64(1))
3736 .build_with_params(DbType::MySQL);
3737 assert!(built.sql.contains("DELETE FROM `users`"));
3738 assert!(built.sql.contains("WHERE `id` = ?"));
3739 assert_eq!(built.params.len(), 1);
3740 }
3741
3742 #[test]
3743 fn test_delete_where_between_params() {
3744 use sz_orm_core::Value;
3745 let built = Query::delete()
3746 .from_table("logs")
3747 .where_between(
3748 "created_at",
3749 Value::String("2020-01-01".to_string()),
3750 Value::String("2020-12-31".to_string()),
3751 )
3752 .build_with_params(DbType::MySQL);
3753 assert!(built.sql.contains("WHERE `created_at` BETWEEN ? AND ?"));
3754 assert_eq!(built.params.len(), 2);
3755 }
3756
3757 #[test]
3760 fn test_from_subquery_basic() {
3761 let inner = Query::select()
3762 .column("id")
3763 .column("amount")
3764 .from("orders")
3765 .build(DbType::MySQL);
3766 let sql = Query::select()
3767 .column("id")
3768 .from_subquery(&inner, "t")
3769 .build(DbType::MySQL);
3770 assert!(
3771 sql.contains("FROM (SELECT `id`, `amount` FROM `orders`) AS `t`"),
3772 "FROM 子查询应渲染为 `FROM (subquery) AS alias`,实际: {sql}"
3773 );
3774 }
3775
3776 #[test]
3777 fn test_from_subquery_postgres_dialect() {
3778 let inner = Query::select()
3779 .column("id")
3780 .from("orders")
3781 .build(DbType::PostgreSQL);
3782 let sql = Query::select()
3783 .column("id")
3784 .from_subquery(&inner, "t")
3785 .build(DbType::PostgreSQL);
3786 assert!(
3787 sql.contains("FROM (SELECT \"id\" FROM \"orders\") AS \"t\""),
3788 "PG 方言下别名应使用双引号,实际: {sql}"
3789 );
3790 }
3791
3792 #[test]
3793 fn test_from_subquery_with_where_and_order() {
3794 let inner = Query::select()
3795 .column("id")
3796 .column("amount")
3797 .from("orders")
3798 .where_clause("amount > 100")
3799 .build(DbType::MySQL);
3800 let sql = Query::select()
3801 .column("id")
3802 .column("amount")
3803 .from_subquery(&inner, "t")
3804 .where_clause("t.amount > 200")
3805 .order_by("id", true)
3806 .build(DbType::MySQL);
3807 assert!(
3808 sql.contains("FROM (SELECT `id`, `amount` FROM `orders` WHERE amount > 100) AS `t`")
3809 );
3810 assert!(sql.contains("WHERE t.amount > 200"));
3811 assert!(sql.contains("ORDER BY `id` ASC"));
3812 }
3813
3814 #[test]
3815 fn test_from_subquery_with_params() {
3816 use sz_orm_core::Value;
3817 let inner = Query::select()
3818 .column("id")
3819 .from("orders")
3820 .where_eq("amount", Value::I32(100))
3821 .build_with_params(DbType::MySQL);
3822 let built = Query::select()
3823 .column("id")
3824 .from_subquery(&inner.sql, "t")
3825 .where_eq("t.id", Value::I64(1))
3826 .build_with_params(DbType::MySQL);
3827 assert!(built
3829 .sql
3830 .contains("FROM (SELECT `id` FROM `orders` WHERE `amount` = ?) AS `t`"));
3831 assert!(built.sql.contains("WHERE `t`.`id` = ?"));
3832 assert_eq!(built.params.len(), 1);
3834 }
3835
3836 #[test]
3837 fn test_from_subquery_overrides_from_table() {
3838 let sql = Query::select()
3840 .column("id")
3841 .from("users")
3842 .from_subquery("SELECT id FROM orders", "t")
3843 .build(DbType::MySQL);
3844 assert!(sql.contains("FROM (SELECT id FROM orders) AS `t`"));
3845 assert!(!sql.contains("FROM `users`"));
3846 }
3847
3848 #[test]
3849 fn test_from_table_overrides_from_subquery() {
3850 let sql = Query::select()
3852 .column("id")
3853 .from_subquery("SELECT id FROM orders", "t")
3854 .from("users")
3855 .build(DbType::MySQL);
3856 assert!(sql.contains("FROM `users`"));
3857 assert!(!sql.contains("FROM ("));
3858 }
3859
3860 #[test]
3861 fn test_from_subquery_no_from_when_neither_set() {
3862 let sql = Query::select().column("id").build(DbType::MySQL);
3863 assert!(!sql.contains("FROM"));
3864 }
3865
3866 #[test]
3869 fn test_insert_returning_postgres() {
3870 let sql = Query::insert()
3871 .into_table("users")
3872 .value("name", "'Alice'")
3873 .returning(&["id", "created_at"])
3874 .build_with_dialect(DbType::PostgreSQL);
3875 assert!(
3876 sql.contains("RETURNING \"id\", \"created_at\""),
3877 "PG 方言应渲染 RETURNING,实际: {sql}"
3878 );
3879 }
3880
3881 #[test]
3882 fn test_insert_returning_sqlite() {
3883 let sql = Query::insert()
3884 .into_table("users")
3885 .value("name", "'Alice'")
3886 .returning(&["id"])
3887 .build_with_dialect(DbType::Sqlite);
3888 assert!(
3889 sql.contains("RETURNING \"id\""),
3890 "SQLite 方言应渲染 RETURNING,实际: {sql}"
3891 );
3892 }
3893
3894 #[test]
3895 fn test_insert_returning_all() {
3896 let sql = Query::insert()
3897 .into_table("users")
3898 .value("name", "'Alice'")
3899 .returning_all()
3900 .build_with_dialect(DbType::PostgreSQL);
3901 assert!(
3902 sql.contains("RETURNING *"),
3903 "returning_all 应渲染 `RETURNING *`,实际: {sql}"
3904 );
3905 }
3906
3907 #[test]
3908 fn test_insert_returning_mysql_skipped() {
3909 let sql = Query::insert()
3911 .into_table("users")
3912 .value("name", "'Alice'")
3913 .returning(&["id"])
3914 .build_with_dialect(DbType::MySQL);
3915 assert!(
3916 !sql.contains("RETURNING"),
3917 "MySQL 方言应跳过 RETURNING,实际: {sql}"
3918 );
3919 }
3920
3921 #[test]
3922 fn test_insert_returning_with_upsert_postgres() {
3923 let sql = Query::insert()
3925 .into_table("users")
3926 .value("id", "1")
3927 .value("name", "'Alice'")
3928 .on_conflict_do_update(&["id"], &[("name", "EXCLUDED.name")])
3929 .returning(&["id", "name"])
3930 .build_with_dialect(DbType::PostgreSQL);
3931 assert!(sql.contains("ON CONFLICT"));
3932 assert!(sql.contains("RETURNING"));
3933 }
3934
3935 #[test]
3936 fn test_insert_returning_build_mysql_style_skipped() {
3937 let sql = Query::insert()
3939 .into_table("users")
3940 .value("name", "'Alice'")
3941 .returning(&["id"])
3942 .build();
3943 assert!(!sql.contains("RETURNING"));
3944 }
3945
3946 #[test]
3947 fn test_update_returning_postgres() {
3948 let sql = Query::update()
3949 .table("users")
3950 .set("status", "'active'")
3951 .where_clause("id = 1")
3952 .returning(&["id", "status"])
3953 .build_with_dialect(DbType::PostgreSQL);
3954 assert!(sql.contains("RETURNING \"id\", \"status\""));
3955 assert!(sql.contains("WHERE id = 1"));
3956 }
3957
3958 #[test]
3959 fn test_update_returning_sqlite() {
3960 let sql = Query::update()
3961 .table("users")
3962 .set("status", "'active'")
3963 .returning(&["id"])
3964 .build_with_dialect(DbType::Sqlite);
3965 assert!(sql.contains("RETURNING \"id\""));
3966 }
3967
3968 #[test]
3969 fn test_update_returning_mysql_skipped() {
3970 let sql = Query::update()
3971 .table("users")
3972 .set("status", "'active'")
3973 .returning(&["id"])
3974 .build_with_dialect(DbType::MySQL);
3975 assert!(!sql.contains("RETURNING"));
3976 }
3977
3978 #[test]
3979 fn test_update_returning_with_params() {
3980 use sz_orm_core::Value;
3981 let built = Query::update()
3982 .table("users")
3983 .set("status", "'active'")
3984 .where_eq("id", Value::I64(1))
3985 .returning(&["id", "status"])
3986 .build_with_params(DbType::PostgreSQL);
3987 assert!(built.sql.contains("WHERE \"id\" = ?"));
3988 assert!(built.sql.contains("RETURNING \"id\", \"status\""));
3989 assert_eq!(built.params.len(), 1);
3990 }
3991
3992 #[test]
3993 fn test_delete_returning_postgres() {
3994 let sql = Query::delete()
3995 .from_table("users")
3996 .where_clause("id = 1")
3997 .returning(&["id", "name"])
3998 .build_with_dialect(DbType::PostgreSQL);
3999 assert!(sql.contains("RETURNING \"id\", \"name\""));
4000 assert!(sql.contains("WHERE id = 1"));
4001 }
4002
4003 #[test]
4004 fn test_delete_returning_sqlite() {
4005 let sql = Query::delete()
4006 .from_table("users")
4007 .where_clause("id = 1")
4008 .returning(&["id"])
4009 .build_with_dialect(DbType::Sqlite);
4010 assert!(sql.contains("RETURNING \"id\""));
4011 }
4012
4013 #[test]
4014 fn test_delete_returning_mysql_skipped() {
4015 let sql = Query::delete()
4016 .from_table("users")
4017 .where_clause("id = 1")
4018 .returning(&["id"])
4019 .build_with_dialect(DbType::MySQL);
4020 assert!(!sql.contains("RETURNING"));
4021 }
4022
4023 #[test]
4024 fn test_delete_returning_with_params() {
4025 use sz_orm_core::Value;
4026 let built = Query::delete()
4027 .from_table("users")
4028 .where_eq("id", Value::I64(1))
4029 .returning(&["id", "name"])
4030 .build_with_params(DbType::PostgreSQL);
4031 assert!(built.sql.contains("WHERE \"id\" = ?"));
4032 assert!(built.sql.contains("RETURNING \"id\", \"name\""));
4033 assert_eq!(built.params.len(), 1);
4034 }
4035
4036 #[test]
4037 fn test_returning_star_not_quoted() {
4038 let sql = Query::insert()
4040 .into_table("users")
4041 .value("name", "'Alice'")
4042 .returning(&["*"])
4043 .build_with_dialect(DbType::PostgreSQL);
4044 assert!(sql.contains("RETURNING *"));
4045 assert!(!sql.contains("RETURNING \"*\""));
4046 }
4047
4048 #[test]
4051 fn test_inner_join_on_column_eq() {
4052 let sql = Query::select()
4053 .column("u.id")
4054 .from("users u")
4055 .inner_join_on("orders o", "u.id", "o.user_id")
4056 .build(DbType::MySQL);
4057 assert!(
4058 sql.contains("INNER JOIN `orders` o ON `u`.`id` = `o`.`user_id`"),
4059 "列对列等值连接应渲染转义标识符,实际: {sql}"
4060 );
4061 }
4062
4063 #[test]
4064 fn test_left_join_on_column_eq() {
4065 let sql = Query::select()
4066 .column("u.id")
4067 .from("users u")
4068 .left_join_on("profiles p", "u.id", "p.user_id")
4069 .build(DbType::MySQL);
4070 assert!(sql.contains("LEFT JOIN `profiles` p ON `u`.`id` = `p`.`user_id`"));
4071 }
4072
4073 #[test]
4074 fn test_right_join_on_column_eq() {
4075 let sql = Query::select()
4076 .column("u.id")
4077 .from("users u")
4078 .right_join_on("orders o", "u.id", "o.user_id")
4079 .build(DbType::MySQL);
4080 assert!(sql.contains("RIGHT JOIN `orders` o ON `u`.`id` = `o`.`user_id`"));
4081 }
4082
4083 #[test]
4084 fn test_inner_join_on_postgres_dialect() {
4085 let sql = Query::select()
4086 .column("u.id")
4087 .from("users u")
4088 .inner_join_on("orders o", "u.id", "o.user_id")
4089 .build(DbType::PostgreSQL);
4090 assert!(
4093 sql.contains("INNER JOIN `orders` o ON \"u\".\"id\" = \"o\".\"user_id\""),
4094 "PG 方言下 ON 条件列名应使用双引号引用,实际: {sql}"
4095 );
4096 }
4097
4098 #[test]
4099 fn test_inner_join_param_binds_value() {
4100 use sz_orm_core::Value;
4101 let built = Query::select()
4102 .column("u.id")
4103 .from("users u")
4104 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4105 .build_with_params(DbType::MySQL);
4106 assert!(
4107 built
4108 .sql
4109 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"),
4110 "参数化 JOIN 应渲染 ? 占位符,实际: {}",
4111 built.sql
4112 );
4113 assert_eq!(built.params.len(), 1);
4114 assert_eq!(built.params[0], Value::String("paid".to_string()));
4115 }
4116
4117 #[test]
4118 fn test_left_join_param_binds_value() {
4119 use sz_orm_core::Value;
4120 let built = Query::select()
4121 .column("u.id")
4122 .from("users u")
4123 .left_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4124 .build_with_params(DbType::MySQL);
4125 assert!(built
4126 .sql
4127 .contains("LEFT JOIN `orders` o ON `o`.`status` = ?"));
4128 assert_eq!(built.params.len(), 1);
4129 }
4130
4131 #[test]
4132 fn test_right_join_param_binds_value() {
4133 use sz_orm_core::Value;
4134 let built = Query::select()
4135 .column("u.id")
4136 .from("users u")
4137 .right_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4138 .build_with_params(DbType::MySQL);
4139 assert!(built
4140 .sql
4141 .contains("RIGHT JOIN `orders` o ON `o`.`status` = ?"));
4142 assert_eq!(built.params.len(), 1);
4143 }
4144
4145 #[test]
4146 fn test_param_join_injection_safe() {
4147 use sz_orm_core::Value;
4149 let malicious = "'; DROP TABLE orders; --".to_string();
4150 let built = Query::select()
4151 .column("u.id")
4152 .from("users u")
4153 .inner_join_param(
4154 "orders o",
4155 "o.status",
4156 " = ?",
4157 Value::String(malicious.clone()),
4158 )
4159 .build_with_params(DbType::MySQL);
4160 assert!(!built.sql.contains("DROP TABLE"));
4162 assert!(!built.sql.contains(";"));
4163 assert_eq!(built.params.len(), 1);
4165 assert_eq!(built.params[0], Value::String(malicious));
4166 }
4167
4168 #[test]
4169 fn test_mixed_raw_and_param_join() {
4170 use sz_orm_core::Value;
4172 let built = Query::select()
4173 .column("u.id")
4174 .from("users u")
4175 .inner_join("orders o", "u.id = o.user_id")
4176 .inner_join_param(
4177 "payments p",
4178 "p.status",
4179 " = ?",
4180 Value::String("paid".into()),
4181 )
4182 .build_with_params(DbType::MySQL);
4183 assert!(built
4184 .sql
4185 .contains("INNER JOIN `orders` o ON u.id = o.user_id"));
4186 assert!(built
4187 .sql
4188 .contains("INNER JOIN `payments` p ON `p`.`status` = ?"));
4189 assert_eq!(built.params.len(), 1);
4190 }
4191
4192 #[test]
4193 fn test_param_join_with_where_params_combined() {
4194 use sz_orm_core::Value;
4196 let built = Query::select()
4197 .column("u.id")
4198 .from("users u")
4199 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4200 .where_eq("u.age", Value::I32(18))
4201 .build_with_params(DbType::MySQL);
4202 assert!(built
4203 .sql
4204 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"));
4205 assert!(built.sql.contains("WHERE `u`.`age` = ?"));
4206 assert_eq!(built.params.len(), 2);
4208 assert_eq!(built.params[0], Value::String("paid".to_string()));
4209 assert_eq!(built.params[1], Value::I32(18));
4210 }
4211}