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!(
3070 sql_str.contains("name = 'Alice;Bob'"),
3071 "SELECT 应包含 name 条件(含分号字面量)"
3072 );
3073 assert!(sql_str.contains("id IN (1, 2, 3)"), "SELECT 应包含 IN 子句");
3074 assert!(
3075 sql_str.contains("created_at > '2026-01-01'"),
3076 "SELECT 应包含日期条件"
3077 );
3078
3079 let sql_str = Query::update()
3080 .table("users")
3081 .set("name", "'x'")
3082 .where_clause("id = 1")
3083 .build();
3084 assert!(!sql_str.is_empty(), "UPDATE SQL 不应为空");
3085 assert!(sql_str.contains("UPDATE"), "应为 UPDATE 语句");
3086 assert!(sql_str.contains("WHERE"), "UPDATE 应包含 WHERE 子句");
3087 assert!(sql_str.contains("id = 1"), "UPDATE WHERE 应包含 id = 1");
3088
3089 let sql_str = Query::delete()
3090 .from_table("users")
3091 .where_clause("id = 1")
3092 .build();
3093 assert!(!sql_str.is_empty(), "DELETE SQL 不应为空");
3094 assert!(sql_str.contains("DELETE"), "应为 DELETE 语句");
3095 assert!(sql_str.contains("WHERE"), "DELETE 应包含 WHERE 子句");
3096 assert!(sql_str.contains("id = 1"), "DELETE WHERE 应包含 id = 1");
3097 }
3098
3099 #[test]
3103 #[should_panic(expected = "SQL injection detected")]
3104 fn test_mutant_block_comment_open_only() {
3105 let _ = Query::select()
3108 .column("id")
3109 .from("users")
3110 .where_clause("id = 1 /* OR 1=1")
3111 .build(DbType::MySQL);
3112 }
3113
3114 #[test]
3115 #[should_panic(expected = "SQL injection detected")]
3116 fn test_mutant_block_comment_close_only() {
3117 let _ = Query::select()
3119 .column("id")
3120 .from("users")
3121 .where_clause("id = 1 */")
3122 .build(DbType::MySQL);
3123 }
3124
3125 #[test]
3126 fn test_mutant_insert_dialect_table_no_columns_returns_empty() {
3127 let sql = Query::insert()
3129 .into_table("users")
3130 .build_with_dialect(DbType::MySQL);
3131 assert_eq!(sql, "", "有表无列时应返回空字符串");
3132 }
3133
3134 #[test]
3135 fn test_mutant_update_dialect_table_no_sets_returns_empty() {
3136 let sql = Query::update()
3138 .table("users")
3139 .build_with_dialect(DbType::MySQL);
3140 assert_eq!(sql, "", "有表无 SET 时应返回空字符串");
3141 }
3142
3143 #[test]
3144 fn test_mutant_update_dialect_no_table_with_sets_returns_empty() {
3145 let sql = Query::update()
3147 .set("name", "'x'")
3148 .build_with_dialect(DbType::MySQL);
3149 assert_eq!(sql, "", "无表有 SET 时应返回空字符串");
3150 }
3151
3152 #[test]
3153 fn test_mutant_delete_dialect_no_table_returns_empty() {
3154 let sql = Query::delete()
3156 .where_clause("id = 1")
3157 .build_with_dialect(DbType::MySQL);
3158 assert_eq!(sql, "", "无表时应返回空字符串");
3159 }
3160
3161 #[test]
3162 fn test_mutant_select_right_join() {
3163 let sql = Query::select()
3165 .column("u.id")
3166 .from("users u")
3167 .right_join("orders o", "u.id = o.user_id")
3168 .build(DbType::MySQL);
3169 assert!(sql.contains("RIGHT JOIN `orders` o ON u.id = o.user_id"));
3170 }
3171
3172 #[test]
3173 fn test_mutant_all_columns_with_extra() {
3174 let sql = Query::select()
3177 .all_columns()
3178 .column("extra")
3179 .from("users")
3180 .build(DbType::MySQL);
3181 assert!(
3182 sql.contains("SELECT *, `extra` FROM `users`"),
3183 "all_columns + column 应在 SELECT 列表中同时包含 * 和 extra,实际: {sql}"
3184 );
3185 }
3186
3187 #[test]
3188 fn test_mutant_update_dialect_no_where_no_where_clause() {
3189 let sql = Query::update()
3192 .table("users")
3193 .set("name", "'x'")
3194 .build_with_dialect(DbType::MySQL);
3195 assert!(
3196 !sql.contains("WHERE"),
3197 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3198 );
3199 }
3200
3201 #[test]
3202 fn test_mutant_delete_dialect_no_where_no_where_clause() {
3203 let sql = Query::delete()
3205 .from_table("users")
3206 .build_with_dialect(DbType::MySQL);
3207 assert!(
3208 !sql.contains("WHERE"),
3209 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3210 );
3211 }
3212
3213 #[test]
3218 fn test_cte_single_with_clause() {
3219 let sql = Query::select()
3220 .column("id")
3221 .column("name")
3222 .from("active_users")
3223 .with_cte(
3224 "active_users",
3225 "SELECT * FROM users WHERE status = 'active'",
3226 )
3227 .build(DbType::MySQL);
3228 assert!(sql.starts_with("WITH active_users AS ("));
3229 assert!(sql.contains("SELECT * FROM users WHERE status = 'active'"));
3230 assert!(sql.contains("SELECT `id`, `name` FROM `active_users`"));
3231 }
3232
3233 #[test]
3234 fn test_cte_multiple_with_clauses() {
3235 let sql = Query::select()
3236 .column("id")
3237 .from("combined")
3238 .with_cte("a", "SELECT id FROM table_a")
3239 .with_cte("b", "SELECT id FROM table_b")
3240 .with_cte("combined", "SELECT id FROM a UNION SELECT id FROM b")
3241 .build(DbType::MySQL);
3242 assert!(sql.starts_with(
3243 "WITH a AS (SELECT id FROM table_a), b AS (SELECT id FROM table_b), combined AS ("
3244 ));
3245 }
3246
3247 #[test]
3248 fn test_cte_recursive_with_clause() {
3249 let sql = Query::select()
3250 .column("id")
3251 .column("parent_id")
3252 .from("tree")
3253 .with_recursive_cte("tree", "SELECT id, parent_id FROM nodes WHERE id = 1")
3254 .build(DbType::MySQL);
3255 assert!(sql.starts_with("WITH RECURSIVE tree AS ("));
3256 }
3257
3258 #[test]
3259 fn test_cte_no_cte_no_with_prefix() {
3260 let sql = Query::select()
3261 .column("id")
3262 .from("users")
3263 .build(DbType::MySQL);
3264 assert!(!sql.contains("WITH"));
3265 assert!(sql.starts_with("SELECT"));
3266 }
3267
3268 #[test]
3271 fn test_window_function_raw_expr() {
3272 let sql = Query::select()
3273 .column("id")
3274 .column("salary")
3275 .from("employees")
3276 .window_function("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn")
3277 .build(DbType::MySQL);
3278 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3279 }
3280
3281 #[test]
3282 fn test_row_number_helper_with_partition() {
3283 let sql = Query::select()
3284 .column("name")
3285 .column("dept")
3286 .from("employees")
3287 .row_number("dept", "salary DESC", "row_num")
3288 .build(DbType::MySQL);
3289 assert!(
3290 sql.contains("ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS row_num")
3291 );
3292 }
3293
3294 #[test]
3295 fn test_row_number_helper_without_partition() {
3296 let sql = Query::select()
3297 .column("name")
3298 .from("employees")
3299 .row_number("", "salary DESC", "rn")
3300 .build(DbType::MySQL);
3301 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3302 assert!(!sql.contains("PARTITION BY"));
3303 }
3304
3305 #[test]
3306 fn test_rank_helper() {
3307 let sql = Query::select()
3308 .column("name")
3309 .from("scores")
3310 .rank("", "score DESC", "rank_num")
3311 .build(DbType::MySQL);
3312 assert!(sql.contains("RANK() OVER (ORDER BY score DESC) AS rank_num"));
3313 }
3314
3315 #[test]
3316 fn test_dense_rank_helper_with_partition() {
3317 let sql = Query::select()
3318 .column("name")
3319 .from("scores")
3320 .dense_rank("class", "score DESC", "dr")
3321 .build(DbType::MySQL);
3322 assert!(sql.contains("DENSE_RANK() OVER (PARTITION BY class ORDER BY score DESC) AS dr"));
3323 }
3324
3325 #[test]
3326 fn test_multiple_window_functions() {
3327 let sql = Query::select()
3328 .column("name")
3329 .column("salary")
3330 .from("employees")
3331 .row_number("dept", "salary DESC", "rn")
3332 .rank("dept", "salary DESC", "rk")
3333 .dense_rank("dept", "salary DESC", "dr")
3334 .build(DbType::MySQL);
3335 assert!(sql.contains("ROW_NUMBER()"));
3336 assert!(sql.contains("RANK()"));
3337 assert!(sql.contains("DENSE_RANK()"));
3338 }
3339
3340 #[test]
3341 fn test_window_function_with_cte_combined() {
3342 let sql = Query::select()
3343 .column("name")
3344 .from("ranked")
3345 .with_cte(
3346 "ranked",
3347 "SELECT name, ROW_NUMBER() OVER (ORDER BY salary) AS rn FROM employees",
3348 )
3349 .where_clause("rn <= 10")
3350 .build(DbType::MySQL);
3351 assert!(sql.starts_with("WITH ranked AS ("));
3352 assert!(sql.contains("FROM `ranked`"));
3353 assert!(sql.contains("WHERE rn <= 10"));
3354 }
3355
3356 #[test]
3359 fn test_for_update_basic() {
3360 let sql = Query::select()
3361 .column("id")
3362 .column("balance")
3363 .from("accounts")
3364 .where_clause("id = 1")
3365 .for_update()
3366 .build(DbType::MySQL);
3367 assert!(sql.ends_with(" FOR UPDATE"));
3368 assert!(sql.contains("WHERE id = 1"));
3369 }
3370
3371 #[test]
3372 fn test_for_update_with_nowait() {
3373 let sql = Query::select()
3374 .column("id")
3375 .from("accounts")
3376 .where_clause("id = 1")
3377 .for_update_with_options("NOWAIT")
3378 .build(DbType::MySQL);
3379 assert!(sql.ends_with(" FOR UPDATE NOWAIT"));
3380 }
3381
3382 #[test]
3383 fn test_for_update_with_skip_locked() {
3384 let sql = Query::select()
3385 .column("id")
3386 .from("accounts")
3387 .where_clause("id = 1")
3388 .for_update_with_options("SKIP LOCKED")
3389 .build(DbType::MySQL);
3390 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3391 }
3392
3393 #[test]
3394 fn test_for_update_with_limit_and_order() {
3395 let sql = Query::select()
3396 .column("id")
3397 .from("jobs")
3398 .order_by("priority", false)
3399 .limit(1)
3400 .for_update_with_options("SKIP LOCKED")
3401 .build(DbType::MySQL);
3402 assert!(sql.contains("ORDER BY `priority` DESC"));
3403 assert!(sql.contains("LIMIT 1"));
3404 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3405 }
3406
3407 #[test]
3408 fn test_no_for_update_by_default() {
3409 let sql = Query::select()
3410 .column("id")
3411 .from("users")
3412 .build(DbType::MySQL);
3413 assert!(!sql.contains("FOR UPDATE"));
3414 }
3415
3416 #[test]
3419 fn test_set_operator_as_sql() {
3420 assert_eq!(SetOperator::Union.as_sql(), "UNION");
3421 assert_eq!(SetOperator::UnionAll.as_sql(), "UNION ALL");
3422 assert_eq!(SetOperator::Intersect.as_sql(), "INTERSECT");
3423 assert_eq!(SetOperator::Except.as_sql(), "EXCEPT");
3424 }
3425
3426 #[test]
3427 fn test_union_basic() {
3428 let q1 = Query::select().column("id").from("active_users");
3429 let q2 = Query::select().column("id").from("pending_users");
3430 let sql = q1.union(q2).build(DbType::MySQL);
3431 assert!(sql.contains("SELECT `id` FROM `active_users`"));
3432 assert!(sql.contains(" UNION "));
3433 assert!(sql.contains("SELECT `id` FROM `pending_users`"));
3434 }
3435
3436 #[test]
3437 fn test_union_all_basic() {
3438 let q1 = Query::select().column("id").from("table_a");
3439 let q2 = Query::select().column("id").from("table_b");
3440 let sql = q1.union_all(q2).build(DbType::MySQL);
3441 assert!(sql.contains(" UNION ALL "));
3442 }
3443
3444 #[test]
3445 fn test_intersect_basic() {
3446 let q1 = Query::select().column("id").from("table_a");
3447 let q2 = Query::select().column("id").from("table_b");
3448 let sql = q1.intersect(q2).build(DbType::MySQL);
3449 assert!(sql.contains(" INTERSECT "));
3450 }
3451
3452 #[test]
3453 fn test_except_basic() {
3454 let q1 = Query::select().column("id").from("table_a");
3455 let q2 = Query::select().column("id").from("table_b");
3456 let sql = q1.except(q2).build(DbType::MySQL);
3457 assert!(sql.contains(" EXCEPT "));
3458 }
3459
3460 #[test]
3461 fn test_union_chained_multiple() {
3462 let q1 = Query::select().column("id").from("t1");
3463 let q2 = Query::select().column("id").from("t2");
3464 let q3 = Query::select().column("id").from("t3");
3465 let sql = q1.union(q2).union(q3).build(DbType::MySQL);
3466 assert_eq!(sql.matches("UNION").count(), 2);
3467 }
3468
3469 #[test]
3470 fn test_union_mixed_operators() {
3471 let q1 = Query::select().column("id").from("t1");
3472 let q2 = Query::select().column("id").from("t2");
3473 let q3 = Query::select().column("id").from("t3");
3474 let sql = q1.union(q2).intersect(q3).build(DbType::MySQL);
3475 assert!(sql.contains(" UNION "));
3476 assert!(sql.contains(" INTERSECT "));
3477 }
3478
3479 #[test]
3480 fn test_union_with_order_by_limit() {
3481 let q1 = Query::select().column("id").from("t1");
3482 let q2 = Query::select().column("id").from("t2");
3483 let sql = q1
3484 .union(q2)
3485 .order_by("id", true)
3486 .limit(10)
3487 .offset(5)
3488 .build(DbType::MySQL);
3489 assert!(sql.contains("ORDER BY `id` ASC"));
3490 assert!(sql.contains("LIMIT 10"));
3491 assert!(sql.contains("OFFSET 5"));
3492 }
3493
3494 #[test]
3495 fn test_union_postgres_dialect() {
3496 let q1 = Query::select().column("id").from("t1");
3497 let q2 = Query::select().column("id").from("t2");
3498 let sql = q1.union(q2).build(DbType::PostgreSQL);
3499 assert!(sql.contains("\"id\""));
3500 assert!(sql.contains(" UNION "));
3501 }
3502
3503 #[test]
3504 fn test_union_with_where_clauses() {
3505 let q1 = Query::select()
3506 .column("id")
3507 .from("active_users")
3508 .where_clause("age > 18");
3509 let q2 = Query::select()
3510 .column("id")
3511 .from("pending_users")
3512 .where_clause("age > 18");
3513 let sql = q1.union(q2).build(DbType::MySQL);
3514 assert!(sql.contains("WHERE age > 18"));
3515 assert!(sql.contains(" UNION "));
3516 }
3517
3518 #[test]
3521 fn test_cte_window_for_update_combined() {
3522 let sql = Query::select()
3524 .column("id")
3525 .column("salary")
3526 .from("ranked_salaries")
3527 .with_cte(
3528 "ranked_salaries",
3529 "SELECT id, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM employees",
3530 )
3531 .where_clause("rn = 1")
3532 .for_update()
3533 .build(DbType::MySQL);
3534 assert!(sql.starts_with("WITH ranked_salaries AS ("));
3535 assert!(sql.contains("FOR UPDATE"));
3536 assert!(sql.contains("WHERE rn = 1"));
3537 }
3538
3539 #[test]
3540 fn test_complex_window_aggregation() {
3541 let sql = Query::select()
3543 .column("user_id")
3544 .column("amount")
3545 .from("transactions")
3546 .window_function(
3547 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total",
3548 )
3549 .rank("user_id", "created_at", "tx_rank")
3550 .build(DbType::MySQL);
3551 assert!(sql.contains(
3552 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total"
3553 ));
3554 assert!(sql.contains("RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS tx_rank"));
3555 }
3556
3557 #[test]
3560 fn test_select_where_eq_params() {
3561 use sz_orm_core::Value;
3562 let built = Query::select()
3563 .column("id")
3564 .from("users")
3565 .where_eq("age", Value::I32(18))
3566 .build_with_params(DbType::MySQL);
3567 assert!(built.sql.contains("WHERE `age` = ?"));
3568 assert_eq!(built.params.len(), 1);
3569 assert_eq!(built.params[0], Value::I32(18));
3570 }
3571
3572 #[test]
3573 fn test_select_multiple_where_params() {
3574 use sz_orm_core::Value;
3575 let built = Query::select()
3576 .column("id")
3577 .from("users")
3578 .where_eq("age", Value::I32(18))
3579 .where_eq("status", Value::String("active".to_string()))
3580 .build_with_params(DbType::MySQL);
3581 assert!(built.sql.contains("WHERE `age` = ? AND `status` = ?"));
3582 assert_eq!(built.params.len(), 2);
3583 }
3584
3585 #[test]
3586 fn test_select_or_where_eq_params() {
3587 use sz_orm_core::Value;
3588 let built = Query::select()
3589 .column("id")
3590 .from("users")
3591 .where_eq("age", Value::I32(18))
3592 .or_where_eq("role", Value::String("admin".to_string()))
3593 .build_with_params(DbType::MySQL);
3594 assert!(built.sql.contains("WHERE `age` = ? OR `role` = ?"));
3595 assert_eq!(built.params.len(), 2);
3596 }
3597
3598 #[test]
3599 fn test_select_where_in_params() {
3600 use sz_orm_core::Value;
3601 let built = Query::select()
3602 .column("id")
3603 .from("users")
3604 .where_in("id", vec![Value::I32(1), Value::I32(2), Value::I32(3)])
3605 .build_with_params(DbType::MySQL);
3606 assert!(built.sql.contains("WHERE `id` IN (?, ?, ?)"));
3607 assert_eq!(built.params.len(), 3);
3608 }
3609
3610 #[test]
3611 fn test_select_where_in_empty() {
3612 let built = Query::select()
3613 .column("id")
3614 .from("users")
3615 .where_in("id", vec![])
3616 .build_with_params(DbType::MySQL);
3617 assert!(built.sql.contains("WHERE 1 = 0"));
3618 assert_eq!(built.params.len(), 0);
3619 }
3620
3621 #[test]
3622 fn test_select_where_between_params() {
3623 use sz_orm_core::Value;
3624 let built = Query::select()
3625 .column("id")
3626 .from("users")
3627 .where_between("age", Value::I32(18), Value::I32(65))
3628 .build_with_params(DbType::MySQL);
3629 assert!(built.sql.contains("WHERE `age` BETWEEN ? AND ?"));
3630 assert_eq!(built.params.len(), 2);
3631 }
3632
3633 #[test]
3634 fn test_select_where_null_params() {
3635 let built = Query::select()
3636 .column("id")
3637 .from("users")
3638 .where_null("deleted_at")
3639 .build_with_params(DbType::MySQL);
3640 assert!(built.sql.contains("WHERE `deleted_at` IS NULL"));
3641 assert_eq!(built.params.len(), 0);
3642 }
3643
3644 #[test]
3645 fn test_select_where_not_null_params() {
3646 let built = Query::select()
3647 .column("id")
3648 .from("users")
3649 .where_not_null("email")
3650 .build_with_params(DbType::MySQL);
3651 assert!(built.sql.contains("WHERE `email` IS NOT NULL"));
3652 }
3653
3654 #[test]
3655 fn test_select_mixed_raw_and_param_where() {
3656 use sz_orm_core::Value;
3657 let built = Query::select()
3658 .column("id")
3659 .from("users")
3660 .where_clause("age > 18")
3661 .where_eq("status", Value::String("active".to_string()))
3662 .build_with_params(DbType::MySQL);
3663 assert!(built.sql.contains("WHERE age > 18 AND `status` = ?"));
3664 assert_eq!(built.params.len(), 1);
3665 }
3666
3667 #[test]
3668 fn test_select_param_where_with_order_limit() {
3669 use sz_orm_core::Value;
3670 let built = Query::select()
3671 .column("id")
3672 .from("users")
3673 .where_eq("age", Value::I32(18))
3674 .order_by("id", true)
3675 .limit(10)
3676 .build_with_params(DbType::MySQL);
3677 assert!(built.sql.contains("WHERE `age` = ?"));
3678 assert!(built.sql.contains("ORDER BY `id` ASC"));
3679 assert!(built.sql.contains("LIMIT 10"));
3680 }
3681
3682 #[test]
3683 fn test_select_param_where_postgres_dialect() {
3684 use sz_orm_core::Value;
3685 let built = Query::select()
3686 .column("id")
3687 .from("users")
3688 .where_eq("age", Value::I32(18))
3689 .build_with_params(DbType::PostgreSQL);
3690 assert!(built.sql.contains("WHERE \"age\" = ?"));
3691 }
3692
3693 #[test]
3694 fn test_select_param_where_injection_safe() {
3695 use sz_orm_core::Value;
3697 let malicious = "'; DROP TABLE users; --".to_string();
3698 let built = Query::select()
3699 .column("id")
3700 .from("users")
3701 .where_eq("name", Value::String(malicious.clone()))
3702 .build_with_params(DbType::MySQL);
3703 assert!(!built.sql.contains("DROP TABLE"));
3705 assert!(!built.sql.contains(";"));
3706 assert_eq!(built.params.len(), 1);
3708 assert_eq!(built.params[0], Value::String(malicious));
3709 }
3710
3711 #[test]
3712 fn test_update_where_eq_params() {
3713 use sz_orm_core::Value;
3714 let built = Query::update()
3715 .table("users")
3716 .set("name", "'Bob'")
3717 .where_eq("id", Value::I64(1))
3718 .build_with_params(DbType::MySQL);
3719 assert!(built.sql.contains("UPDATE `users` SET"));
3720 assert!(built.sql.contains("WHERE `id` = ?"));
3721 assert_eq!(built.params.len(), 1);
3722 }
3723
3724 #[test]
3725 fn test_update_where_in_params() {
3726 use sz_orm_core::Value;
3727 let built = Query::update()
3728 .table("users")
3729 .set("status", "'inactive'")
3730 .where_in("id", vec![Value::I64(1), Value::I64(2)])
3731 .build_with_params(DbType::MySQL);
3732 assert!(built.sql.contains("WHERE `id` IN (?, ?)"));
3733 assert_eq!(built.params.len(), 2);
3734 }
3735
3736 #[test]
3737 fn test_delete_where_eq_params() {
3738 use sz_orm_core::Value;
3739 let built = Query::delete()
3740 .from_table("users")
3741 .where_eq("id", Value::I64(1))
3742 .build_with_params(DbType::MySQL);
3743 assert!(built.sql.contains("DELETE FROM `users`"));
3744 assert!(built.sql.contains("WHERE `id` = ?"));
3745 assert_eq!(built.params.len(), 1);
3746 }
3747
3748 #[test]
3749 fn test_delete_where_between_params() {
3750 use sz_orm_core::Value;
3751 let built = Query::delete()
3752 .from_table("logs")
3753 .where_between(
3754 "created_at",
3755 Value::String("2020-01-01".to_string()),
3756 Value::String("2020-12-31".to_string()),
3757 )
3758 .build_with_params(DbType::MySQL);
3759 assert!(built.sql.contains("WHERE `created_at` BETWEEN ? AND ?"));
3760 assert_eq!(built.params.len(), 2);
3761 }
3762
3763 #[test]
3766 fn test_from_subquery_basic() {
3767 let inner = Query::select()
3768 .column("id")
3769 .column("amount")
3770 .from("orders")
3771 .build(DbType::MySQL);
3772 let sql = Query::select()
3773 .column("id")
3774 .from_subquery(&inner, "t")
3775 .build(DbType::MySQL);
3776 assert!(
3777 sql.contains("FROM (SELECT `id`, `amount` FROM `orders`) AS `t`"),
3778 "FROM 子查询应渲染为 `FROM (subquery) AS alias`,实际: {sql}"
3779 );
3780 }
3781
3782 #[test]
3783 fn test_from_subquery_postgres_dialect() {
3784 let inner = Query::select()
3785 .column("id")
3786 .from("orders")
3787 .build(DbType::PostgreSQL);
3788 let sql = Query::select()
3789 .column("id")
3790 .from_subquery(&inner, "t")
3791 .build(DbType::PostgreSQL);
3792 assert!(
3793 sql.contains("FROM (SELECT \"id\" FROM \"orders\") AS \"t\""),
3794 "PG 方言下别名应使用双引号,实际: {sql}"
3795 );
3796 }
3797
3798 #[test]
3799 fn test_from_subquery_with_where_and_order() {
3800 let inner = Query::select()
3801 .column("id")
3802 .column("amount")
3803 .from("orders")
3804 .where_clause("amount > 100")
3805 .build(DbType::MySQL);
3806 let sql = Query::select()
3807 .column("id")
3808 .column("amount")
3809 .from_subquery(&inner, "t")
3810 .where_clause("t.amount > 200")
3811 .order_by("id", true)
3812 .build(DbType::MySQL);
3813 assert!(
3814 sql.contains("FROM (SELECT `id`, `amount` FROM `orders` WHERE amount > 100) AS `t`")
3815 );
3816 assert!(sql.contains("WHERE t.amount > 200"));
3817 assert!(sql.contains("ORDER BY `id` ASC"));
3818 }
3819
3820 #[test]
3821 fn test_from_subquery_with_params() {
3822 use sz_orm_core::Value;
3823 let inner = Query::select()
3824 .column("id")
3825 .from("orders")
3826 .where_eq("amount", Value::I32(100))
3827 .build_with_params(DbType::MySQL);
3828 let built = Query::select()
3829 .column("id")
3830 .from_subquery(&inner.sql, "t")
3831 .where_eq("t.id", Value::I64(1))
3832 .build_with_params(DbType::MySQL);
3833 assert!(built
3835 .sql
3836 .contains("FROM (SELECT `id` FROM `orders` WHERE `amount` = ?) AS `t`"));
3837 assert!(built.sql.contains("WHERE `t`.`id` = ?"));
3838 assert_eq!(built.params.len(), 1);
3840 }
3841
3842 #[test]
3843 fn test_from_subquery_overrides_from_table() {
3844 let sql = Query::select()
3846 .column("id")
3847 .from("users")
3848 .from_subquery("SELECT id FROM orders", "t")
3849 .build(DbType::MySQL);
3850 assert!(sql.contains("FROM (SELECT id FROM orders) AS `t`"));
3851 assert!(!sql.contains("FROM `users`"));
3852 }
3853
3854 #[test]
3855 fn test_from_table_overrides_from_subquery() {
3856 let sql = Query::select()
3858 .column("id")
3859 .from_subquery("SELECT id FROM orders", "t")
3860 .from("users")
3861 .build(DbType::MySQL);
3862 assert!(sql.contains("FROM `users`"));
3863 assert!(!sql.contains("FROM ("));
3864 }
3865
3866 #[test]
3867 fn test_from_subquery_no_from_when_neither_set() {
3868 let sql = Query::select().column("id").build(DbType::MySQL);
3869 assert!(!sql.contains("FROM"));
3870 }
3871
3872 #[test]
3875 fn test_insert_returning_postgres() {
3876 let sql = Query::insert()
3877 .into_table("users")
3878 .value("name", "'Alice'")
3879 .returning(&["id", "created_at"])
3880 .build_with_dialect(DbType::PostgreSQL);
3881 assert!(
3882 sql.contains("RETURNING \"id\", \"created_at\""),
3883 "PG 方言应渲染 RETURNING,实际: {sql}"
3884 );
3885 }
3886
3887 #[test]
3888 fn test_insert_returning_sqlite() {
3889 let sql = Query::insert()
3890 .into_table("users")
3891 .value("name", "'Alice'")
3892 .returning(&["id"])
3893 .build_with_dialect(DbType::Sqlite);
3894 assert!(
3895 sql.contains("RETURNING \"id\""),
3896 "SQLite 方言应渲染 RETURNING,实际: {sql}"
3897 );
3898 }
3899
3900 #[test]
3901 fn test_insert_returning_all() {
3902 let sql = Query::insert()
3903 .into_table("users")
3904 .value("name", "'Alice'")
3905 .returning_all()
3906 .build_with_dialect(DbType::PostgreSQL);
3907 assert!(
3908 sql.contains("RETURNING *"),
3909 "returning_all 应渲染 `RETURNING *`,实际: {sql}"
3910 );
3911 }
3912
3913 #[test]
3914 fn test_insert_returning_mysql_skipped() {
3915 let sql = Query::insert()
3917 .into_table("users")
3918 .value("name", "'Alice'")
3919 .returning(&["id"])
3920 .build_with_dialect(DbType::MySQL);
3921 assert!(
3922 !sql.contains("RETURNING"),
3923 "MySQL 方言应跳过 RETURNING,实际: {sql}"
3924 );
3925 }
3926
3927 #[test]
3928 fn test_insert_returning_with_upsert_postgres() {
3929 let sql = Query::insert()
3931 .into_table("users")
3932 .value("id", "1")
3933 .value("name", "'Alice'")
3934 .on_conflict_do_update(&["id"], &[("name", "EXCLUDED.name")])
3935 .returning(&["id", "name"])
3936 .build_with_dialect(DbType::PostgreSQL);
3937 assert!(sql.contains("ON CONFLICT"));
3938 assert!(sql.contains("RETURNING"));
3939 }
3940
3941 #[test]
3942 fn test_insert_returning_build_mysql_style_skipped() {
3943 let sql = Query::insert()
3945 .into_table("users")
3946 .value("name", "'Alice'")
3947 .returning(&["id"])
3948 .build();
3949 assert!(!sql.contains("RETURNING"));
3950 }
3951
3952 #[test]
3953 fn test_update_returning_postgres() {
3954 let sql = Query::update()
3955 .table("users")
3956 .set("status", "'active'")
3957 .where_clause("id = 1")
3958 .returning(&["id", "status"])
3959 .build_with_dialect(DbType::PostgreSQL);
3960 assert!(sql.contains("RETURNING \"id\", \"status\""));
3961 assert!(sql.contains("WHERE id = 1"));
3962 }
3963
3964 #[test]
3965 fn test_update_returning_sqlite() {
3966 let sql = Query::update()
3967 .table("users")
3968 .set("status", "'active'")
3969 .returning(&["id"])
3970 .build_with_dialect(DbType::Sqlite);
3971 assert!(sql.contains("RETURNING \"id\""));
3972 }
3973
3974 #[test]
3975 fn test_update_returning_mysql_skipped() {
3976 let sql = Query::update()
3977 .table("users")
3978 .set("status", "'active'")
3979 .returning(&["id"])
3980 .build_with_dialect(DbType::MySQL);
3981 assert!(!sql.contains("RETURNING"));
3982 }
3983
3984 #[test]
3985 fn test_update_returning_with_params() {
3986 use sz_orm_core::Value;
3987 let built = Query::update()
3988 .table("users")
3989 .set("status", "'active'")
3990 .where_eq("id", Value::I64(1))
3991 .returning(&["id", "status"])
3992 .build_with_params(DbType::PostgreSQL);
3993 assert!(built.sql.contains("WHERE \"id\" = ?"));
3994 assert!(built.sql.contains("RETURNING \"id\", \"status\""));
3995 assert_eq!(built.params.len(), 1);
3996 }
3997
3998 #[test]
3999 fn test_delete_returning_postgres() {
4000 let sql = Query::delete()
4001 .from_table("users")
4002 .where_clause("id = 1")
4003 .returning(&["id", "name"])
4004 .build_with_dialect(DbType::PostgreSQL);
4005 assert!(sql.contains("RETURNING \"id\", \"name\""));
4006 assert!(sql.contains("WHERE id = 1"));
4007 }
4008
4009 #[test]
4010 fn test_delete_returning_sqlite() {
4011 let sql = Query::delete()
4012 .from_table("users")
4013 .where_clause("id = 1")
4014 .returning(&["id"])
4015 .build_with_dialect(DbType::Sqlite);
4016 assert!(sql.contains("RETURNING \"id\""));
4017 }
4018
4019 #[test]
4020 fn test_delete_returning_mysql_skipped() {
4021 let sql = Query::delete()
4022 .from_table("users")
4023 .where_clause("id = 1")
4024 .returning(&["id"])
4025 .build_with_dialect(DbType::MySQL);
4026 assert!(!sql.contains("RETURNING"));
4027 }
4028
4029 #[test]
4030 fn test_delete_returning_with_params() {
4031 use sz_orm_core::Value;
4032 let built = Query::delete()
4033 .from_table("users")
4034 .where_eq("id", Value::I64(1))
4035 .returning(&["id", "name"])
4036 .build_with_params(DbType::PostgreSQL);
4037 assert!(built.sql.contains("WHERE \"id\" = ?"));
4038 assert!(built.sql.contains("RETURNING \"id\", \"name\""));
4039 assert_eq!(built.params.len(), 1);
4040 }
4041
4042 #[test]
4043 fn test_returning_star_not_quoted() {
4044 let sql = Query::insert()
4046 .into_table("users")
4047 .value("name", "'Alice'")
4048 .returning(&["*"])
4049 .build_with_dialect(DbType::PostgreSQL);
4050 assert!(sql.contains("RETURNING *"));
4051 assert!(!sql.contains("RETURNING \"*\""));
4052 }
4053
4054 #[test]
4057 fn test_inner_join_on_column_eq() {
4058 let sql = Query::select()
4059 .column("u.id")
4060 .from("users u")
4061 .inner_join_on("orders o", "u.id", "o.user_id")
4062 .build(DbType::MySQL);
4063 assert!(
4064 sql.contains("INNER JOIN `orders` o ON `u`.`id` = `o`.`user_id`"),
4065 "列对列等值连接应渲染转义标识符,实际: {sql}"
4066 );
4067 }
4068
4069 #[test]
4070 fn test_left_join_on_column_eq() {
4071 let sql = Query::select()
4072 .column("u.id")
4073 .from("users u")
4074 .left_join_on("profiles p", "u.id", "p.user_id")
4075 .build(DbType::MySQL);
4076 assert!(sql.contains("LEFT JOIN `profiles` p ON `u`.`id` = `p`.`user_id`"));
4077 }
4078
4079 #[test]
4080 fn test_right_join_on_column_eq() {
4081 let sql = Query::select()
4082 .column("u.id")
4083 .from("users u")
4084 .right_join_on("orders o", "u.id", "o.user_id")
4085 .build(DbType::MySQL);
4086 assert!(sql.contains("RIGHT JOIN `orders` o ON `u`.`id` = `o`.`user_id`"));
4087 }
4088
4089 #[test]
4090 fn test_inner_join_on_postgres_dialect() {
4091 let sql = Query::select()
4092 .column("u.id")
4093 .from("users u")
4094 .inner_join_on("orders o", "u.id", "o.user_id")
4095 .build(DbType::PostgreSQL);
4096 assert!(
4099 sql.contains("INNER JOIN `orders` o ON \"u\".\"id\" = \"o\".\"user_id\""),
4100 "PG 方言下 ON 条件列名应使用双引号引用,实际: {sql}"
4101 );
4102 }
4103
4104 #[test]
4105 fn test_inner_join_param_binds_value() {
4106 use sz_orm_core::Value;
4107 let built = Query::select()
4108 .column("u.id")
4109 .from("users u")
4110 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4111 .build_with_params(DbType::MySQL);
4112 assert!(
4113 built
4114 .sql
4115 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"),
4116 "参数化 JOIN 应渲染 ? 占位符,实际: {}",
4117 built.sql
4118 );
4119 assert_eq!(built.params.len(), 1);
4120 assert_eq!(built.params[0], Value::String("paid".to_string()));
4121 }
4122
4123 #[test]
4124 fn test_left_join_param_binds_value() {
4125 use sz_orm_core::Value;
4126 let built = Query::select()
4127 .column("u.id")
4128 .from("users u")
4129 .left_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4130 .build_with_params(DbType::MySQL);
4131 assert!(built
4132 .sql
4133 .contains("LEFT JOIN `orders` o ON `o`.`status` = ?"));
4134 assert_eq!(built.params.len(), 1);
4135 }
4136
4137 #[test]
4138 fn test_right_join_param_binds_value() {
4139 use sz_orm_core::Value;
4140 let built = Query::select()
4141 .column("u.id")
4142 .from("users u")
4143 .right_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4144 .build_with_params(DbType::MySQL);
4145 assert!(built
4146 .sql
4147 .contains("RIGHT JOIN `orders` o ON `o`.`status` = ?"));
4148 assert_eq!(built.params.len(), 1);
4149 }
4150
4151 #[test]
4152 fn test_param_join_injection_safe() {
4153 use sz_orm_core::Value;
4155 let malicious = "'; DROP TABLE orders; --".to_string();
4156 let built = Query::select()
4157 .column("u.id")
4158 .from("users u")
4159 .inner_join_param(
4160 "orders o",
4161 "o.status",
4162 " = ?",
4163 Value::String(malicious.clone()),
4164 )
4165 .build_with_params(DbType::MySQL);
4166 assert!(!built.sql.contains("DROP TABLE"));
4168 assert!(!built.sql.contains(";"));
4169 assert_eq!(built.params.len(), 1);
4171 assert_eq!(built.params[0], Value::String(malicious));
4172 }
4173
4174 #[test]
4175 fn test_mixed_raw_and_param_join() {
4176 use sz_orm_core::Value;
4178 let built = Query::select()
4179 .column("u.id")
4180 .from("users u")
4181 .inner_join("orders o", "u.id = o.user_id")
4182 .inner_join_param(
4183 "payments p",
4184 "p.status",
4185 " = ?",
4186 Value::String("paid".into()),
4187 )
4188 .build_with_params(DbType::MySQL);
4189 assert!(built
4190 .sql
4191 .contains("INNER JOIN `orders` o ON u.id = o.user_id"));
4192 assert!(built
4193 .sql
4194 .contains("INNER JOIN `payments` p ON `p`.`status` = ?"));
4195 assert_eq!(built.params.len(), 1);
4196 }
4197
4198 #[test]
4199 fn test_param_join_with_where_params_combined() {
4200 use sz_orm_core::Value;
4202 let built = Query::select()
4203 .column("u.id")
4204 .from("users u")
4205 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4206 .where_eq("u.age", Value::I32(18))
4207 .build_with_params(DbType::MySQL);
4208 assert!(built
4209 .sql
4210 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"));
4211 assert!(built.sql.contains("WHERE `u`.`age` = ?"));
4212 assert_eq!(built.params.len(), 2);
4214 assert_eq!(built.params[0], Value::String("paid".to_string()));
4215 assert_eq!(built.params[1], Value::I32(18));
4216 }
4217}