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