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
201#[deprecated(
215 since = "3.5.0",
216 note = "v3.5.0: 请评估 docs/query-builder-guide.md 选择合适方案。标准 CRUD 推荐迁移到 sz_orm_core::QueryBuilder<M>"
217)]
218pub struct Query;
219
220#[allow(deprecated)]
221impl Query {
222 #[deprecated(
224 since = "3.5.0",
225 note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::select()。详见 docs/query-builder-guide.md"
226 )]
227 pub fn select() -> SelectQuery {
228 SelectQuery::new()
229 }
230
231 #[deprecated(
233 since = "3.5.0",
234 note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::insert()。详见 docs/query-builder-guide.md"
235 )]
236 pub fn insert() -> InsertQuery {
237 InsertQuery::new()
238 }
239
240 #[deprecated(
242 since = "3.5.0",
243 note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::update()。详见 docs/query-builder-guide.md"
244 )]
245 pub fn update() -> UpdateQuery {
246 UpdateQuery::new()
247 }
248
249 #[deprecated(
251 since = "3.5.0",
252 note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::delete()。详见 docs/query-builder-guide.md"
253 )]
254 pub fn delete() -> DeleteQuery {
255 DeleteQuery::new()
256 }
257}
258
259#[derive(Debug, Clone)]
264enum JoinOn {
265 Raw(String),
267 ColumnEq {
269 left_column: String,
270 right_column: String,
271 },
272 Param {
274 left_column: String,
275 op: String,
276 values: Vec<Value>,
277 },
278}
279
280#[derive(Debug, Clone)]
282struct JoinClause {
283 join_type: &'static str,
285 table: String,
287 on: Vec<JoinOn>,
289}
290
291#[derive(Debug, Clone, Default)]
293pub struct SelectQuery {
294 columns: Vec<String>,
295 from_table: Option<String>,
296 from_subquery: Option<(String, String)>,
298 join_clauses: Vec<JoinClause>,
304 wheres: Vec<String>,
305 param_wheres: Vec<ParamWhere>,
307 order_by: Vec<String>,
308 group_by: Vec<String>,
309 having: Vec<String>,
310 limit: Option<u64>,
311 offset: Option<u64>,
312 distinct: bool,
313 ctes: Vec<(String, String, bool)>,
315 window_columns: Vec<String>,
317 for_update: bool,
319 for_update_options: Option<String>,
321}
322
323impl SelectQuery {
324 pub fn new() -> Self {
326 Self::default()
327 }
328
329 pub fn distinct(mut self) -> Self {
331 self.distinct = true;
332 self
333 }
334
335 pub fn column(mut self, name: &str) -> Self {
337 self.columns.push(name.to_string());
338 self
339 }
340
341 pub fn columns(mut self, names: &[&str]) -> Self {
343 for n in names {
344 self.columns.push(n.to_string());
345 }
346 self
347 }
348
349 pub fn all_columns(self) -> Self {
351 self.column("*")
352 }
353
354 pub fn from(mut self, table: &str) -> Self {
356 self.from_table = Some(table.to_string());
357 self.from_subquery = None;
359 self
360 }
361
362 pub fn from_subquery(mut self, subquery_sql: &str, alias: &str) -> Self {
386 self.from_subquery = Some((subquery_sql.to_string(), alias.to_string()));
387 self.from_table = None;
389 self
390 }
391
392 pub fn inner_join(mut self, table: &str, on: &str) -> Self {
398 self.join_clauses.push(JoinClause {
399 join_type: "INNER JOIN",
400 table: Self::quote_join_table(table),
401 on: vec![JoinOn::Raw(on.to_string())],
402 });
403 self
404 }
405
406 pub fn left_join(mut self, table: &str, on: &str) -> Self {
412 self.join_clauses.push(JoinClause {
413 join_type: "LEFT JOIN",
414 table: Self::quote_join_table(table),
415 on: vec![JoinOn::Raw(on.to_string())],
416 });
417 self
418 }
419
420 pub fn right_join(mut self, table: &str, on: &str) -> Self {
426 self.join_clauses.push(JoinClause {
427 join_type: "RIGHT JOIN",
428 table: Self::quote_join_table(table),
429 on: vec![JoinOn::Raw(on.to_string())],
430 });
431 self
432 }
433
434 pub fn inner_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
472 self.join_clauses.push(JoinClause {
473 join_type: "INNER JOIN",
474 table: Self::quote_join_table(table),
475 on: vec![JoinOn::ColumnEq {
476 left_column: left_col.to_string(),
477 right_column: right_col.to_string(),
478 }],
479 });
480 self
481 }
482
483 pub fn left_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
485 self.join_clauses.push(JoinClause {
486 join_type: "LEFT JOIN",
487 table: Self::quote_join_table(table),
488 on: vec![JoinOn::ColumnEq {
489 left_column: left_col.to_string(),
490 right_column: right_col.to_string(),
491 }],
492 });
493 self
494 }
495
496 pub fn right_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
498 self.join_clauses.push(JoinClause {
499 join_type: "RIGHT JOIN",
500 table: Self::quote_join_table(table),
501 on: vec![JoinOn::ColumnEq {
502 left_column: left_col.to_string(),
503 right_column: right_col.to_string(),
504 }],
505 });
506 self
507 }
508
509 pub fn inner_join_param(
518 mut self,
519 table: &str,
520 left_col: &str,
521 op_expr: &str,
522 value: Value,
523 ) -> Self {
524 self.join_clauses.push(JoinClause {
525 join_type: "INNER JOIN",
526 table: Self::quote_join_table(table),
527 on: vec![JoinOn::Param {
528 left_column: left_col.to_string(),
529 op: op_expr.to_string(),
530 values: vec![value],
531 }],
532 });
533 self
534 }
535
536 pub fn left_join_param(
538 mut self,
539 table: &str,
540 left_col: &str,
541 op_expr: &str,
542 value: Value,
543 ) -> Self {
544 self.join_clauses.push(JoinClause {
545 join_type: "LEFT JOIN",
546 table: Self::quote_join_table(table),
547 on: vec![JoinOn::Param {
548 left_column: left_col.to_string(),
549 op: op_expr.to_string(),
550 values: vec![value],
551 }],
552 });
553 self
554 }
555
556 pub fn right_join_param(
558 mut self,
559 table: &str,
560 left_col: &str,
561 op_expr: &str,
562 value: Value,
563 ) -> Self {
564 self.join_clauses.push(JoinClause {
565 join_type: "RIGHT JOIN",
566 table: Self::quote_join_table(table),
567 on: vec![JoinOn::Param {
568 left_column: left_col.to_string(),
569 op: op_expr.to_string(),
570 values: vec![value],
571 }],
572 });
573 self
574 }
575
576 fn render_joins(&self, dialect: &dyn sz_orm_core::Dialect, params: &mut Vec<Value>) -> String {
581 let mut sql = String::new();
582 for clause in &self.join_clauses {
583 sql.push(' ');
584 sql.push_str(clause.join_type);
585 sql.push(' ');
586 sql.push_str(&clause.table);
587 sql.push_str(" ON ");
588 for (i, on) in clause.on.iter().enumerate() {
589 if i > 0 {
590 sql.push_str(" AND ");
591 }
592 match on {
593 JoinOn::Raw(raw) => sql.push_str(raw),
594 JoinOn::ColumnEq {
595 left_column,
596 right_column,
597 } => {
598 sql.push_str("e_column_dialect(dialect, left_column));
599 sql.push_str(" = ");
600 sql.push_str("e_column_dialect(dialect, right_column));
601 }
602 JoinOn::Param {
603 left_column,
604 op,
605 values,
606 } => {
607 sql.push_str("e_column_dialect(dialect, left_column));
608 sql.push_str(op);
609 params.extend(values.iter().cloned());
610 }
611 }
612 }
613 }
614 sql
615 }
616
617 fn quote_join_table(table: &str) -> String {
619 if let Some((tbl, alias)) = table.rsplit_once(' ') {
620 if alias.to_uppercase() == "AS" {
621 format!("{} AS {}", quote_ident(tbl), alias)
623 } else {
624 format!("{} {}", quote_ident(tbl), alias)
626 }
627 } else {
628 quote_ident(table)
629 }
630 }
631
632 pub fn where_clause(mut self, condition: &str) -> Self {
639 check_where_injection(condition);
640 self.wheres.push(condition.to_string());
641 self
642 }
643
644 pub fn where_eq(mut self, column: &str, value: Value) -> Self {
670 self.param_wheres.push(ParamWhere::And {
671 column: column.to_string(),
672 op: " = ?".to_string(),
673 values: vec![value],
674 });
675 self
676 }
677
678 pub fn where_ne(mut self, column: &str, value: Value) -> Self {
680 self.param_wheres.push(ParamWhere::And {
681 column: column.to_string(),
682 op: " <> ?".to_string(),
683 values: vec![value],
684 });
685 self
686 }
687
688 pub fn where_gt(mut self, column: &str, value: Value) -> Self {
690 self.param_wheres.push(ParamWhere::And {
691 column: column.to_string(),
692 op: " > ?".to_string(),
693 values: vec![value],
694 });
695 self
696 }
697
698 pub fn where_ge(mut self, column: &str, value: Value) -> Self {
700 self.param_wheres.push(ParamWhere::And {
701 column: column.to_string(),
702 op: " >= ?".to_string(),
703 values: vec![value],
704 });
705 self
706 }
707
708 pub fn where_lt(mut self, column: &str, value: Value) -> Self {
710 self.param_wheres.push(ParamWhere::And {
711 column: column.to_string(),
712 op: " < ?".to_string(),
713 values: vec![value],
714 });
715 self
716 }
717
718 pub fn where_le(mut self, column: &str, value: Value) -> Self {
720 self.param_wheres.push(ParamWhere::And {
721 column: column.to_string(),
722 op: " <= ?".to_string(),
723 values: vec![value],
724 });
725 self
726 }
727
728 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
730 self.param_wheres.push(ParamWhere::And {
731 column: column.to_string(),
732 op: " LIKE ?".to_string(),
733 values: vec![pattern],
734 });
735 self
736 }
737
738 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
742 let (column, op) = if values.is_empty() {
743 (String::new(), "1 = 0".to_string())
744 } else {
745 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
746 (
747 column.to_string(),
748 format!(" IN ({})", placeholders.join(", ")),
749 )
750 };
751 self.param_wheres
752 .push(ParamWhere::And { column, op, values });
753 self
754 }
755
756 pub fn where_not_in(mut self, column: &str, values: Vec<Value>) -> Self {
760 let (column, op) = if values.is_empty() {
761 (String::new(), "1 = 1".to_string())
762 } else {
763 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
764 (
765 column.to_string(),
766 format!(" NOT IN ({})", placeholders.join(", ")),
767 )
768 };
769 self.param_wheres
770 .push(ParamWhere::And { column, op, values });
771 self
772 }
773
774 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
776 self.param_wheres.push(ParamWhere::And {
777 column: column.to_string(),
778 op: " BETWEEN ? AND ?".to_string(),
779 values: vec![low, high],
780 });
781 self
782 }
783
784 pub fn where_null(mut self, column: &str) -> Self {
786 self.param_wheres.push(ParamWhere::And {
787 column: column.to_string(),
788 op: " IS NULL".to_string(),
789 values: vec![],
790 });
791 self
792 }
793
794 pub fn where_not_null(mut self, column: &str) -> Self {
796 self.param_wheres.push(ParamWhere::And {
797 column: column.to_string(),
798 op: " IS NOT NULL".to_string(),
799 values: vec![],
800 });
801 self
802 }
803
804 pub fn or_where_eq(mut self, column: &str, value: Value) -> Self {
806 self.param_wheres.push(ParamWhere::Or {
807 column: column.to_string(),
808 op: " = ?".to_string(),
809 values: vec![value],
810 });
811 self
812 }
813
814 pub fn or_where_ne(mut self, column: &str, value: Value) -> Self {
816 self.param_wheres.push(ParamWhere::Or {
817 column: column.to_string(),
818 op: " <> ?".to_string(),
819 values: vec![value],
820 });
821 self
822 }
823
824 pub fn or_where_gt(mut self, column: &str, value: Value) -> Self {
826 self.param_wheres.push(ParamWhere::Or {
827 column: column.to_string(),
828 op: " > ?".to_string(),
829 values: vec![value],
830 });
831 self
832 }
833
834 pub fn or_where_ge(mut self, column: &str, value: Value) -> Self {
836 self.param_wheres.push(ParamWhere::Or {
837 column: column.to_string(),
838 op: " >= ?".to_string(),
839 values: vec![value],
840 });
841 self
842 }
843
844 pub fn or_where_lt(mut self, column: &str, value: Value) -> Self {
846 self.param_wheres.push(ParamWhere::Or {
847 column: column.to_string(),
848 op: " < ?".to_string(),
849 values: vec![value],
850 });
851 self
852 }
853
854 pub fn or_where_le(mut self, column: &str, value: Value) -> Self {
856 self.param_wheres.push(ParamWhere::Or {
857 column: column.to_string(),
858 op: " <= ?".to_string(),
859 values: vec![value],
860 });
861 self
862 }
863
864 pub fn or_where_like(mut self, column: &str, pattern: Value) -> Self {
866 self.param_wheres.push(ParamWhere::Or {
867 column: column.to_string(),
868 op: " LIKE ?".to_string(),
869 values: vec![pattern],
870 });
871 self
872 }
873
874 pub fn or_where_in(mut self, column: &str, values: Vec<Value>) -> Self {
876 let (column, op) = if values.is_empty() {
877 (String::new(), "1 = 0".to_string())
878 } else {
879 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
880 (
881 column.to_string(),
882 format!(" IN ({})", placeholders.join(", ")),
883 )
884 };
885 self.param_wheres
886 .push(ParamWhere::Or { column, op, values });
887 self
888 }
889
890 pub fn or_where_between(mut self, column: &str, low: Value, high: Value) -> Self {
892 self.param_wheres.push(ParamWhere::Or {
893 column: column.to_string(),
894 op: " BETWEEN ? AND ?".to_string(),
895 values: vec![low, high],
896 });
897 self
898 }
899
900 pub fn or_where_null(mut self, column: &str) -> Self {
902 self.param_wheres.push(ParamWhere::Or {
903 column: column.to_string(),
904 op: " IS NULL".to_string(),
905 values: vec![],
906 });
907 self
908 }
909
910 pub fn or_where_not_null(mut self, column: &str) -> Self {
912 self.param_wheres.push(ParamWhere::Or {
913 column: column.to_string(),
914 op: " IS NOT NULL".to_string(),
915 values: vec![],
916 });
917 self
918 }
919
920 pub fn group_by(mut self, column: &str) -> Self {
922 self.group_by.push(column.to_string());
923 self
924 }
925
926 pub fn having(mut self, condition: &str) -> Self {
928 self.having.push(condition.to_string());
929 self
930 }
931
932 pub fn order_by(mut self, column: &str, asc: bool) -> Self {
939 let dir = if asc { "ASC" } else { "DESC" };
940 self.order_by.push(format!("{} {}", column, dir));
941 self
942 }
943
944 pub fn limit(mut self, n: u64) -> Self {
946 self.limit = Some(n);
947 self
948 }
949
950 pub fn offset(mut self, n: u64) -> Self {
952 self.offset = Some(n);
953 self
954 }
955
956 pub fn paginate(self, page: u64, size: u64) -> Self {
963 let offset = (page.saturating_sub(1)) * size;
964 self.limit(size).offset(offset)
965 }
966
967 pub fn with_cte(mut self, name: &str, subquery: &str) -> Self {
976 self.ctes
977 .push((name.to_string(), subquery.to_string(), false));
978 self
979 }
980
981 pub fn with_recursive_cte(mut self, name: &str, subquery: &str) -> Self {
988 self.ctes
989 .push((name.to_string(), subquery.to_string(), true));
990 self
991 }
992
993 pub fn window_function(mut self, expr: &str) -> Self {
1004 self.window_columns.push(expr.to_string());
1005 self
1006 }
1007
1008 pub fn row_number(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1016 let partition_clause = if partition_by.is_empty() {
1017 String::new()
1018 } else {
1019 format!("PARTITION BY {} ", partition_by)
1020 };
1021 let expr = format!(
1022 "ROW_NUMBER() OVER ({}ORDER BY {}) AS {}",
1023 partition_clause, order_by, alias
1024 );
1025 self.window_function(&expr)
1026 }
1027
1028 pub fn rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1036 let partition_clause = if partition_by.is_empty() {
1037 String::new()
1038 } else {
1039 format!("PARTITION BY {} ", partition_by)
1040 };
1041 let expr = format!(
1042 "RANK() OVER ({}ORDER BY {}) AS {}",
1043 partition_clause, order_by, alias
1044 );
1045 self.window_function(&expr)
1046 }
1047
1048 pub fn dense_rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1056 let partition_clause = if partition_by.is_empty() {
1057 String::new()
1058 } else {
1059 format!("PARTITION BY {} ", partition_by)
1060 };
1061 let expr = format!(
1062 "DENSE_RANK() OVER ({}ORDER BY {}) AS {}",
1063 partition_clause, order_by, alias
1064 );
1065 self.window_function(&expr)
1066 }
1067
1068 pub fn for_update(mut self) -> Self {
1072 self.for_update = true;
1073 self.for_update_options = None;
1074 self
1075 }
1076
1077 pub fn for_update_with_options(mut self, options: &str) -> Self {
1083 self.for_update = true;
1084 self.for_update_options = Some(options.to_string());
1085 self
1086 }
1087
1088 pub fn union(self, other: SelectQuery) -> SetQuery {
1092 SetQuery::new(self, SetOperator::Union, other)
1093 }
1094
1095 pub fn union_all(self, other: SelectQuery) -> SetQuery {
1097 SetQuery::new(self, SetOperator::UnionAll, other)
1098 }
1099
1100 pub fn intersect(self, other: SelectQuery) -> SetQuery {
1102 SetQuery::new(self, SetOperator::Intersect, other)
1103 }
1104
1105 pub fn except(self, other: SelectQuery) -> SetQuery {
1107 SetQuery::new(self, SetOperator::Except, other)
1108 }
1109
1110 pub fn build(self, db_type: DbType) -> String {
1116 let dialect = match sz_orm_core::get_dialect(db_type) {
1117 Ok(d) => d,
1118 Err(_) => return String::new(),
1119 };
1120
1121 let mut sql = String::new();
1122
1123 if !self.ctes.is_empty() {
1125 let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1126 if has_recursive {
1127 sql.push_str("WITH RECURSIVE ");
1128 } else {
1129 sql.push_str("WITH ");
1130 }
1131 let cte_strs: Vec<String> = self
1132 .ctes
1133 .iter()
1134 .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1135 .collect();
1136 sql.push_str(&cte_strs.join(", "));
1137 sql.push(' ');
1138 }
1139
1140 sql.push_str("SELECT ");
1141
1142 if self.distinct {
1143 sql.push_str("DISTINCT ");
1144 }
1145
1146 let mut all_columns: Vec<String> = self
1148 .columns
1149 .iter()
1150 .map(|c| {
1151 if c == "*" {
1152 c.clone()
1153 } else {
1154 dialect.quote(c)
1155 }
1156 })
1157 .collect();
1158 all_columns.extend(self.window_columns.iter().cloned());
1159
1160 if all_columns.is_empty() {
1161 sql.push('*');
1162 } else {
1163 sql.push_str(&all_columns.join(", "));
1164 }
1165
1166 if let Some(ref table) = self.from_table {
1167 sql.push_str(" FROM ");
1168 sql.push_str(&dialect.quote(table));
1169 } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1170 sql.push_str(" FROM (");
1172 sql.push_str(subquery);
1173 sql.push_str(") AS ");
1174 sql.push_str(&dialect.quote(alias));
1175 }
1176
1177 let mut unused_params = Vec::new();
1180 sql.push_str(&self.render_joins(&*dialect, &mut unused_params));
1181
1182 if !self.wheres.is_empty() {
1183 sql.push_str(" WHERE ");
1184 sql.push_str(&self.wheres[0]);
1186 for w in &self.wheres[1..] {
1187 if w.starts_with("OR ") {
1188 sql.push(' ');
1189 sql.push_str(w);
1190 } else {
1191 sql.push_str(" AND ");
1192 sql.push_str(w);
1193 }
1194 }
1195 }
1196
1197 if !self.group_by.is_empty() {
1198 sql.push_str(" GROUP BY ");
1199 sql.push_str(
1200 &self
1201 .group_by
1202 .iter()
1203 .map(|c| quote_ident(c))
1204 .collect::<Vec<_>>()
1205 .join(", "),
1206 );
1207 }
1208
1209 if !self.having.is_empty() {
1210 sql.push_str(" HAVING ");
1211 sql.push_str(&self.having.join(" AND "));
1212 }
1213
1214 if !self.order_by.is_empty() {
1215 sql.push_str(" ORDER BY ");
1216 sql.push_str(
1217 &self
1218 .order_by
1219 .iter()
1220 .map(|s| {
1221 if let Some((col, dir)) = s.rsplit_once(' ') {
1223 format!("{} {}", quote_ident(col), dir)
1224 } else {
1225 quote_ident(s)
1226 }
1227 })
1228 .collect::<Vec<_>>()
1229 .join(", "),
1230 );
1231 }
1232
1233 if let Some(limit) = self.limit {
1234 sql.push_str(&format!(" LIMIT {}", limit));
1235 }
1236 if let Some(offset) = self.offset {
1237 sql.push_str(&format!(" OFFSET {}", offset));
1238 }
1239
1240 if self.for_update {
1242 sql.push_str(" FOR UPDATE");
1243 if let Some(ref opts) = self.for_update_options {
1244 sql.push(' ');
1245 sql.push_str(opts);
1246 }
1247 }
1248
1249 sql
1250 }
1251
1252 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
1282 let dialect = match sz_orm_core::get_dialect(db_type) {
1283 Ok(d) => d,
1284 Err(_) => return BuiltQuery::default(),
1285 };
1286
1287 let mut sql = String::new();
1288 let mut params: Vec<Value> = Vec::new();
1289
1290 if !self.ctes.is_empty() {
1292 let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1293 if has_recursive {
1294 sql.push_str("WITH RECURSIVE ");
1295 } else {
1296 sql.push_str("WITH ");
1297 }
1298 let cte_strs: Vec<String> = self
1299 .ctes
1300 .iter()
1301 .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1302 .collect();
1303 sql.push_str(&cte_strs.join(", "));
1304 sql.push(' ');
1305 }
1306
1307 sql.push_str("SELECT ");
1308
1309 if self.distinct {
1310 sql.push_str("DISTINCT ");
1311 }
1312
1313 let mut all_columns: Vec<String> = self
1314 .columns
1315 .iter()
1316 .map(|c| {
1317 if c == "*" {
1318 c.clone()
1319 } else {
1320 dialect.quote(c)
1321 }
1322 })
1323 .collect();
1324 all_columns.extend(self.window_columns.iter().cloned());
1325
1326 if all_columns.is_empty() {
1327 sql.push('*');
1328 } else {
1329 sql.push_str(&all_columns.join(", "));
1330 }
1331
1332 if let Some(ref table) = self.from_table {
1333 sql.push_str(" FROM ");
1334 sql.push_str(&dialect.quote(table));
1335 } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1336 sql.push_str(" FROM (");
1338 sql.push_str(subquery);
1339 sql.push_str(") AS ");
1340 sql.push_str(&dialect.quote(alias));
1341 }
1342
1343 sql.push_str(&self.render_joins(&*dialect, &mut params));
1346
1347 let has_raw = !self.wheres.is_empty();
1349 let has_param = !self.param_wheres.is_empty();
1350 if has_raw || has_param {
1351 sql.push_str(" WHERE ");
1352 let mut first = true;
1353 for w in &self.wheres {
1355 if first {
1356 sql.push_str(w);
1357 first = false;
1358 } else if w.starts_with("OR ") {
1359 sql.push(' ');
1360 sql.push_str(w);
1361 } else {
1362 sql.push_str(" AND ");
1363 sql.push_str(w);
1364 }
1365 }
1366 for pw in &self.param_wheres {
1368 let (conjunction, column, op, vals) = match pw {
1369 ParamWhere::And { column, op, values } => ("AND", column, op, values),
1370 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
1371 };
1372 let expr = if column.is_empty() {
1374 op.clone()
1375 } else {
1376 format!("{}{}", quote_column_dialect(&*dialect, column), op)
1377 };
1378 if first {
1379 sql.push_str(&expr);
1380 first = false;
1381 } else {
1382 sql.push(' ');
1383 sql.push_str(conjunction);
1384 sql.push(' ');
1385 sql.push_str(&expr);
1386 }
1387 params.extend(vals.iter().cloned());
1388 }
1389 }
1390
1391 if !self.group_by.is_empty() {
1392 sql.push_str(" GROUP BY ");
1393 sql.push_str(
1394 &self
1395 .group_by
1396 .iter()
1397 .map(|c| quote_ident(c))
1398 .collect::<Vec<_>>()
1399 .join(", "),
1400 );
1401 }
1402
1403 if !self.having.is_empty() {
1404 sql.push_str(" HAVING ");
1405 sql.push_str(&self.having.join(" AND "));
1406 }
1407
1408 if !self.order_by.is_empty() {
1409 sql.push_str(" ORDER BY ");
1410 sql.push_str(
1411 &self
1412 .order_by
1413 .iter()
1414 .map(|s| {
1415 if let Some((col, dir)) = s.rsplit_once(' ') {
1416 format!("{} {}", quote_ident(col), dir)
1417 } else {
1418 quote_ident(s)
1419 }
1420 })
1421 .collect::<Vec<_>>()
1422 .join(", "),
1423 );
1424 }
1425
1426 if let Some(limit) = self.limit {
1427 sql.push_str(&format!(" LIMIT {}", limit));
1428 }
1429 if let Some(offset) = self.offset {
1430 sql.push_str(&format!(" OFFSET {}", offset));
1431 }
1432
1433 if self.for_update {
1434 sql.push_str(" FOR UPDATE");
1435 if let Some(ref opts) = self.for_update_options {
1436 sql.push(' ');
1437 sql.push_str(opts);
1438 }
1439 }
1440
1441 BuiltQuery { sql, params }
1442 }
1443}
1444
1445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1451pub enum SetOperator {
1452 Union,
1454 UnionAll,
1456 Intersect,
1458 Except,
1460}
1461
1462impl SetOperator {
1463 pub fn as_sql(&self) -> &'static str {
1465 match self {
1466 SetOperator::Union => "UNION",
1467 SetOperator::UnionAll => "UNION ALL",
1468 SetOperator::Intersect => "INTERSECT",
1469 SetOperator::Except => "EXCEPT",
1470 }
1471 }
1472}
1473
1474#[derive(Debug, Clone)]
1488pub struct SetQuery {
1489 first: SelectQuery,
1491 rest: Vec<(SetOperator, SelectQuery)>,
1493 order_by: Vec<String>,
1495 limit: Option<u64>,
1497 offset: Option<u64>,
1499}
1500
1501impl SetQuery {
1502 pub fn new(first: SelectQuery, op: SetOperator, second: SelectQuery) -> Self {
1504 Self {
1505 first,
1506 rest: vec![(op, second)],
1507 order_by: Vec::new(),
1508 limit: None,
1509 offset: None,
1510 }
1511 }
1512
1513 pub fn union(mut self, other: SelectQuery) -> Self {
1515 self.rest.push((SetOperator::Union, other));
1516 self
1517 }
1518
1519 pub fn union_all(mut self, other: SelectQuery) -> Self {
1521 self.rest.push((SetOperator::UnionAll, other));
1522 self
1523 }
1524
1525 pub fn intersect(mut self, other: SelectQuery) -> Self {
1527 self.rest.push((SetOperator::Intersect, other));
1528 self
1529 }
1530
1531 pub fn except(mut self, other: SelectQuery) -> Self {
1533 self.rest.push((SetOperator::Except, other));
1534 self
1535 }
1536
1537 pub fn order_by(mut self, column: &str, asc: bool) -> Self {
1539 let dir = if asc { "ASC" } else { "DESC" };
1540 self.order_by.push(format!("{} {}", column, dir));
1541 self
1542 }
1543
1544 pub fn limit(mut self, n: u64) -> Self {
1546 self.limit = Some(n);
1547 self
1548 }
1549
1550 pub fn offset(mut self, n: u64) -> Self {
1552 self.offset = Some(n);
1553 self
1554 }
1555
1556 pub fn build(self, db_type: DbType) -> String {
1560 let mut sql = self.first.build(db_type);
1561 for (op, query) in &self.rest {
1562 sql.push(' ');
1563 sql.push_str(op.as_sql());
1564 sql.push(' ');
1565 sql.push_str(&query.clone().build(db_type));
1566 }
1567 if !self.order_by.is_empty() {
1568 sql.push_str(" ORDER BY ");
1569 sql.push_str(
1570 &self
1571 .order_by
1572 .iter()
1573 .map(|s| {
1574 if let Some((col, dir)) = s.rsplit_once(' ') {
1575 format!("{} {}", quote_ident(col), dir)
1576 } else {
1577 quote_ident(s)
1578 }
1579 })
1580 .collect::<Vec<_>>()
1581 .join(", "),
1582 );
1583 }
1584 if let Some(limit) = self.limit {
1585 sql.push_str(&format!(" LIMIT {}", limit));
1586 }
1587 if let Some(offset) = self.offset {
1588 sql.push_str(&format!(" OFFSET {}", offset));
1589 }
1590 sql
1591 }
1592}
1593
1594#[derive(Debug, Clone, Default)]
1611pub enum UpsertStrategy {
1612 #[default]
1614 None,
1615 OnConflictDoNothing(Vec<String>),
1617 OnConflictDoUpdate(Vec<String>, Vec<(String, String)>),
1620 OnDuplicateKeyUpdate(Vec<(String, String)>),
1622 Replace,
1624}
1625
1626fn is_mysql_family(db_type: DbType) -> bool {
1628 matches!(
1629 db_type,
1630 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase | DbType::PolarDB
1631 )
1632}
1633
1634fn is_pg_family(db_type: DbType) -> bool {
1636 matches!(
1637 db_type,
1638 DbType::PostgreSQL | DbType::Kingbase | DbType::GaussDB | DbType::PolarDB
1639 ) || db_type == DbType::Sqlite
1640}
1641
1642fn render_upsert_clause(strategy: &UpsertStrategy, db_type: DbType) -> Option<String> {
1646 match strategy {
1647 UpsertStrategy::None => None,
1648 UpsertStrategy::OnConflictDoNothing(cols) => {
1649 if is_pg_family(db_type) {
1650 let cols_str = cols
1651 .iter()
1652 .map(|c| quote_ident(c))
1653 .collect::<Vec<_>>()
1654 .join(", ");
1655 Some(format!("ON CONFLICT ({}) DO NOTHING", cols_str))
1656 } else {
1657 None
1658 }
1659 }
1660 UpsertStrategy::OnConflictDoUpdate(cols, assignments) => {
1661 if is_pg_family(db_type) {
1662 let cols_str = cols
1663 .iter()
1664 .map(|c| quote_ident(c))
1665 .collect::<Vec<_>>()
1666 .join(", ");
1667 let sets = assignments
1668 .iter()
1669 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1670 .collect::<Vec<_>>()
1671 .join(", ");
1672 Some(format!("ON CONFLICT ({}) DO UPDATE SET {}", cols_str, sets))
1673 } else {
1674 None
1675 }
1676 }
1677 UpsertStrategy::OnDuplicateKeyUpdate(assignments) => {
1678 if is_mysql_family(db_type) {
1679 let sets = assignments
1680 .iter()
1681 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1682 .collect::<Vec<_>>()
1683 .join(", ");
1684 Some(format!("ON DUPLICATE KEY UPDATE {}", sets))
1685 } else {
1686 None
1687 }
1688 }
1689 UpsertStrategy::Replace => None, }
1691}
1692
1693fn render_returning_clause(columns: &Option<Vec<String>>, db_type: DbType) -> Option<String> {
1700 let cols = columns.as_ref()?;
1701 if cols.is_empty() {
1702 return None;
1703 }
1704 if is_mysql_family(db_type) {
1706 return None;
1707 }
1708 let dialect = sz_orm_core::get_dialect(db_type).ok()?;
1709 let quoted: Vec<String> = cols
1710 .iter()
1711 .map(|c| {
1712 if c == "*" {
1713 c.clone()
1714 } else {
1715 dialect.quote(c)
1716 }
1717 })
1718 .collect();
1719 Some(format!("RETURNING {}", quoted.join(", ")))
1720}
1721
1722#[derive(Debug, Clone, Default)]
1724pub struct InsertQuery {
1725 table: Option<String>,
1726 columns: Vec<String>,
1727 values: Vec<String>,
1728 upsert: UpsertStrategy,
1730 returning: Option<Vec<String>>,
1732}
1733
1734impl InsertQuery {
1735 pub fn new() -> Self {
1737 Self::default()
1738 }
1739
1740 pub fn into_table(mut self, table: &str) -> Self {
1742 self.table = Some(table.to_string());
1743 self
1744 }
1745
1746 pub fn value(mut self, column: &str, value: &str) -> Self {
1748 self.columns.push(column.to_string());
1749 self.values.push(value.to_string());
1750 self
1751 }
1752
1753 pub fn values(mut self, pairs: &[(&str, &str)]) -> Self {
1755 for (c, v) in pairs {
1756 self.columns.push(c.to_string());
1757 self.values.push(v.to_string());
1758 }
1759 self
1760 }
1761
1762 pub fn on_conflict_do_nothing(mut self, conflict_cols: &[&str]) -> Self {
1786 self.upsert = UpsertStrategy::OnConflictDoNothing(
1787 conflict_cols.iter().map(|s| s.to_string()).collect(),
1788 );
1789 self
1790 }
1791
1792 pub fn on_conflict_do_update(
1817 mut self,
1818 conflict_cols: &[&str],
1819 assignments: &[(&str, &str)],
1820 ) -> Self {
1821 self.upsert = UpsertStrategy::OnConflictDoUpdate(
1822 conflict_cols.iter().map(|s| s.to_string()).collect(),
1823 assignments
1824 .iter()
1825 .map(|(c, v)| (c.to_string(), v.to_string()))
1826 .collect(),
1827 );
1828 self
1829 }
1830
1831 pub fn on_duplicate_key_update(mut self, assignments: &[(&str, &str)]) -> Self {
1853 self.upsert = UpsertStrategy::OnDuplicateKeyUpdate(
1854 assignments
1855 .iter()
1856 .map(|(c, v)| (c.to_string(), v.to_string()))
1857 .collect(),
1858 );
1859 self
1860 }
1861
1862 pub fn replace(mut self) -> Self {
1867 self.upsert = UpsertStrategy::Replace;
1868 self
1869 }
1870
1871 pub fn returning(mut self, columns: &[&str]) -> Self {
1891 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
1892 self
1893 }
1894
1895 pub fn returning_all(mut self) -> Self {
1897 self.returning = Some(vec!["*".to_string()]);
1898 self
1899 }
1900
1901 pub fn build(self) -> String {
1913 let table = self.table.unwrap_or_default();
1914 if table.is_empty() || self.columns.is_empty() {
1915 return String::new();
1916 }
1917
1918 let cols: Vec<String> = self.columns.iter().map(|c| quote_ident(c)).collect();
1919 let vals: Vec<String> = self.values.iter().map(|v| v.to_string()).collect();
1920
1921 let verb = match &self.upsert {
1923 UpsertStrategy::Replace => "REPLACE INTO",
1924 _ => "INSERT INTO",
1925 };
1926 let mut sql = format!(
1927 "{} {} ({}) VALUES ({})",
1928 verb,
1929 quote_ident(&table),
1930 cols.join(", "),
1931 vals.join(", ")
1932 );
1933
1934 if let UpsertStrategy::OnDuplicateKeyUpdate(assignments) = &self.upsert {
1936 let sets = assignments
1937 .iter()
1938 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1939 .collect::<Vec<_>>()
1940 .join(", ");
1941 sql.push_str(&format!(" ON DUPLICATE KEY UPDATE {}", sets));
1942 }
1943
1944 sql
1945 }
1946
1947 pub fn build_with_dialect(self, db_type: DbType) -> String {
1955 let dialect = match sz_orm_core::get_dialect(db_type) {
1956 Ok(d) => d,
1957 Err(_) => return String::new(),
1958 };
1959
1960 let table = self.table.unwrap_or_default();
1961 if table.is_empty() || self.columns.is_empty() {
1962 return String::new();
1963 }
1964
1965 let cols: Vec<String> = self.columns.iter().map(|c| dialect.quote(c)).collect();
1966
1967 let verb = match (&self.upsert, db_type) {
1969 (UpsertStrategy::Replace, dt) if is_mysql_family(dt) => "REPLACE INTO",
1970 (UpsertStrategy::Replace, DbType::Sqlite) => "INSERT OR REPLACE INTO",
1971 _ => "INSERT INTO",
1972 };
1973 let mut sql = format!(
1974 "{} {} ({}) VALUES ({})",
1975 verb,
1976 dialect.quote(&table),
1977 cols.join(", "),
1978 self.values.join(", ")
1979 );
1980
1981 if let Some(clause) = render_upsert_clause(&self.upsert, db_type) {
1983 sql.push(' ');
1984 sql.push_str(&clause);
1985 }
1986
1987 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
1989 sql.push(' ');
1990 sql.push_str(&clause);
1991 }
1992
1993 sql
1994 }
1995}
1996
1997#[derive(Debug, Clone, Default)]
1999pub struct UpdateQuery {
2000 table: Option<String>,
2001 sets: Vec<(String, String)>,
2002 wheres: Vec<String>,
2003 param_wheres: Vec<ParamWhere>,
2005 returning: Option<Vec<String>>,
2007}
2008
2009impl UpdateQuery {
2010 pub fn new() -> Self {
2012 Self::default()
2013 }
2014
2015 pub fn table(mut self, table: &str) -> Self {
2017 self.table = Some(table.to_string());
2018 self
2019 }
2020
2021 pub fn set(mut self, column: &str, value: &str) -> Self {
2023 self.sets.push((column.to_string(), value.to_string()));
2024 self
2025 }
2026
2027 pub fn sets(mut self, pairs: &[(&str, &str)]) -> Self {
2029 for (c, v) in pairs {
2030 self.sets.push((c.to_string(), v.to_string()));
2031 }
2032 self
2033 }
2034
2035 pub fn where_clause(mut self, condition: &str) -> Self {
2041 check_where_injection(condition);
2042 self.wheres.push(condition.to_string());
2043 self
2044 }
2045
2046 pub fn where_eq(mut self, column: &str, value: Value) -> Self {
2048 self.param_wheres.push(ParamWhere::And {
2049 column: column.to_string(),
2050 op: " = ?".to_string(),
2051 values: vec![value],
2052 });
2053 self
2054 }
2055
2056 pub fn where_ne(mut self, column: &str, value: Value) -> Self {
2058 self.param_wheres.push(ParamWhere::And {
2059 column: column.to_string(),
2060 op: " <> ?".to_string(),
2061 values: vec![value],
2062 });
2063 self
2064 }
2065
2066 pub fn where_gt(mut self, column: &str, value: Value) -> Self {
2068 self.param_wheres.push(ParamWhere::And {
2069 column: column.to_string(),
2070 op: " > ?".to_string(),
2071 values: vec![value],
2072 });
2073 self
2074 }
2075
2076 pub fn where_ge(mut self, column: &str, value: Value) -> Self {
2078 self.param_wheres.push(ParamWhere::And {
2079 column: column.to_string(),
2080 op: " >= ?".to_string(),
2081 values: vec![value],
2082 });
2083 self
2084 }
2085
2086 pub fn where_lt(mut self, column: &str, value: Value) -> Self {
2088 self.param_wheres.push(ParamWhere::And {
2089 column: column.to_string(),
2090 op: " < ?".to_string(),
2091 values: vec![value],
2092 });
2093 self
2094 }
2095
2096 pub fn where_le(mut self, column: &str, value: Value) -> Self {
2098 self.param_wheres.push(ParamWhere::And {
2099 column: column.to_string(),
2100 op: " <= ?".to_string(),
2101 values: vec![value],
2102 });
2103 self
2104 }
2105
2106 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2108 self.param_wheres.push(ParamWhere::And {
2109 column: column.to_string(),
2110 op: " LIKE ?".to_string(),
2111 values: vec![pattern],
2112 });
2113 self
2114 }
2115
2116 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2118 let (column, op) = if values.is_empty() {
2119 (String::new(), "1 = 0".to_string())
2120 } else {
2121 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2122 (
2123 column.to_string(),
2124 format!(" IN ({})", placeholders.join(", ")),
2125 )
2126 };
2127 self.param_wheres
2128 .push(ParamWhere::And { column, op, values });
2129 self
2130 }
2131
2132 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2134 self.param_wheres.push(ParamWhere::And {
2135 column: column.to_string(),
2136 op: " BETWEEN ? AND ?".to_string(),
2137 values: vec![low, high],
2138 });
2139 self
2140 }
2141
2142 pub fn where_null(mut self, column: &str) -> Self {
2144 self.param_wheres.push(ParamWhere::And {
2145 column: column.to_string(),
2146 op: " IS NULL".to_string(),
2147 values: vec![],
2148 });
2149 self
2150 }
2151
2152 pub fn where_not_null(mut self, column: &str) -> Self {
2154 self.param_wheres.push(ParamWhere::And {
2155 column: column.to_string(),
2156 op: " IS NOT NULL".to_string(),
2157 values: vec![],
2158 });
2159 self
2160 }
2161
2162 pub fn returning(mut self, columns: &[&str]) -> Self {
2167 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2168 self
2169 }
2170
2171 pub fn returning_all(mut self) -> Self {
2173 self.returning = Some(vec!["*".to_string()]);
2174 self
2175 }
2176
2177 pub fn build(self) -> String {
2183 let table = self.table.unwrap_or_default();
2184 if table.is_empty() || self.sets.is_empty() {
2185 return String::new();
2186 }
2187
2188 let set_str: Vec<String> = self
2189 .sets
2190 .iter()
2191 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
2192 .collect();
2193
2194 let mut sql = format!("UPDATE {} SET {}", quote_ident(&table), set_str.join(", "));
2195
2196 if !self.wheres.is_empty() {
2197 sql.push_str(" WHERE ");
2198 sql.push_str(&self.wheres.join(" AND "));
2199 }
2200
2201 sql
2202 }
2203
2204 pub fn build_with_dialect(self, db_type: DbType) -> String {
2206 let dialect = match sz_orm_core::get_dialect(db_type) {
2207 Ok(d) => d,
2208 Err(_) => return String::new(),
2209 };
2210
2211 let table = self.table.unwrap_or_default();
2212 if table.is_empty() || self.sets.is_empty() {
2213 return String::new();
2214 }
2215
2216 let set_str: Vec<String> = self
2217 .sets
2218 .iter()
2219 .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2220 .collect();
2221
2222 let mut sql = format!(
2223 "UPDATE {} SET {}",
2224 dialect.quote(&table),
2225 set_str.join(", ")
2226 );
2227
2228 if !self.wheres.is_empty() {
2229 sql.push_str(" WHERE ");
2230 sql.push_str(&self.wheres.join(" AND "));
2231 }
2232
2233 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2235 sql.push(' ');
2236 sql.push_str(&clause);
2237 }
2238
2239 sql
2240 }
2241
2242 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2248 let dialect = match sz_orm_core::get_dialect(db_type) {
2249 Ok(d) => d,
2250 Err(_) => return BuiltQuery::default(),
2251 };
2252
2253 let table = self.table.unwrap_or_default();
2254 if table.is_empty() || self.sets.is_empty() {
2255 return BuiltQuery::default();
2256 }
2257
2258 let set_str: Vec<String> = self
2259 .sets
2260 .iter()
2261 .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2262 .collect();
2263
2264 let mut sql = format!(
2265 "UPDATE {} SET {}",
2266 dialect.quote(&table),
2267 set_str.join(", ")
2268 );
2269 let mut params: Vec<Value> = Vec::new();
2270
2271 let has_raw = !self.wheres.is_empty();
2272 let has_param = !self.param_wheres.is_empty();
2273 if has_raw || has_param {
2274 sql.push_str(" WHERE ");
2275 let mut first = true;
2276 for w in &self.wheres {
2277 if first {
2278 sql.push_str(w);
2279 first = false;
2280 } else {
2281 sql.push_str(" AND ");
2282 sql.push_str(w);
2283 }
2284 }
2285 for pw in &self.param_wheres {
2286 let (conjunction, column, op, vals) = match pw {
2287 ParamWhere::And { column, op, values } => ("AND", column, op, values),
2288 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2289 };
2290 let expr = if column.is_empty() {
2292 op.clone()
2293 } else {
2294 format!("{}{}", quote_column_dialect(&*dialect, column), op)
2295 };
2296 if first {
2297 sql.push_str(&expr);
2298 first = false;
2299 } else {
2300 sql.push(' ');
2301 sql.push_str(conjunction);
2302 sql.push(' ');
2303 sql.push_str(&expr);
2304 }
2305 params.extend(vals.iter().cloned());
2306 }
2307 }
2308
2309 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2311 sql.push(' ');
2312 sql.push_str(&clause);
2313 }
2314
2315 BuiltQuery { sql, params }
2316 }
2317}
2318
2319#[derive(Debug, Clone, Default)]
2321pub struct DeleteQuery {
2322 table: Option<String>,
2323 wheres: Vec<String>,
2324 param_wheres: Vec<ParamWhere>,
2326 returning: Option<Vec<String>>,
2328}
2329
2330impl DeleteQuery {
2331 pub fn new() -> Self {
2333 Self::default()
2334 }
2335
2336 pub fn from_table(mut self, table: &str) -> Self {
2338 self.table = Some(table.to_string());
2339 self
2340 }
2341
2342 pub fn where_clause(mut self, condition: &str) -> Self {
2348 check_where_injection(condition);
2349 self.wheres.push(condition.to_string());
2350 self
2351 }
2352
2353 pub fn where_eq(mut self, column: &str, value: Value) -> Self {
2355 self.param_wheres.push(ParamWhere::And {
2356 column: column.to_string(),
2357 op: " = ?".to_string(),
2358 values: vec![value],
2359 });
2360 self
2361 }
2362
2363 pub fn where_ne(mut self, column: &str, value: Value) -> Self {
2365 self.param_wheres.push(ParamWhere::And {
2366 column: column.to_string(),
2367 op: " <> ?".to_string(),
2368 values: vec![value],
2369 });
2370 self
2371 }
2372
2373 pub fn where_gt(mut self, column: &str, value: Value) -> Self {
2375 self.param_wheres.push(ParamWhere::And {
2376 column: column.to_string(),
2377 op: " > ?".to_string(),
2378 values: vec![value],
2379 });
2380 self
2381 }
2382
2383 pub fn where_ge(mut self, column: &str, value: Value) -> Self {
2385 self.param_wheres.push(ParamWhere::And {
2386 column: column.to_string(),
2387 op: " >= ?".to_string(),
2388 values: vec![value],
2389 });
2390 self
2391 }
2392
2393 pub fn where_lt(mut self, column: &str, value: Value) -> Self {
2395 self.param_wheres.push(ParamWhere::And {
2396 column: column.to_string(),
2397 op: " < ?".to_string(),
2398 values: vec![value],
2399 });
2400 self
2401 }
2402
2403 pub fn where_le(mut self, column: &str, value: Value) -> Self {
2405 self.param_wheres.push(ParamWhere::And {
2406 column: column.to_string(),
2407 op: " <= ?".to_string(),
2408 values: vec![value],
2409 });
2410 self
2411 }
2412
2413 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2415 self.param_wheres.push(ParamWhere::And {
2416 column: column.to_string(),
2417 op: " LIKE ?".to_string(),
2418 values: vec![pattern],
2419 });
2420 self
2421 }
2422
2423 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2425 let (column, op) = if values.is_empty() {
2426 (String::new(), "1 = 0".to_string())
2427 } else {
2428 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2429 (
2430 column.to_string(),
2431 format!(" IN ({})", placeholders.join(", ")),
2432 )
2433 };
2434 self.param_wheres
2435 .push(ParamWhere::And { column, op, values });
2436 self
2437 }
2438
2439 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2441 self.param_wheres.push(ParamWhere::And {
2442 column: column.to_string(),
2443 op: " BETWEEN ? AND ?".to_string(),
2444 values: vec![low, high],
2445 });
2446 self
2447 }
2448
2449 pub fn where_null(mut self, column: &str) -> Self {
2451 self.param_wheres.push(ParamWhere::And {
2452 column: column.to_string(),
2453 op: " IS NULL".to_string(),
2454 values: vec![],
2455 });
2456 self
2457 }
2458
2459 pub fn where_not_null(mut self, column: &str) -> Self {
2461 self.param_wheres.push(ParamWhere::And {
2462 column: column.to_string(),
2463 op: " IS NOT NULL".to_string(),
2464 values: vec![],
2465 });
2466 self
2467 }
2468
2469 pub fn returning(mut self, columns: &[&str]) -> Self {
2474 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2475 self
2476 }
2477
2478 pub fn returning_all(mut self) -> Self {
2480 self.returning = Some(vec!["*".to_string()]);
2481 self
2482 }
2483
2484 pub fn build(self) -> String {
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 {}", quote_ident(&table));
2496
2497 if !self.wheres.is_empty() {
2498 sql.push_str(" WHERE ");
2499 sql.push_str(&self.wheres.join(" AND "));
2500 }
2501
2502 sql
2503 }
2504
2505 pub fn build_with_dialect(self, db_type: DbType) -> String {
2507 let dialect = match sz_orm_core::get_dialect(db_type) {
2508 Ok(d) => d,
2509 Err(_) => return String::new(),
2510 };
2511
2512 let table = self.table.unwrap_or_default();
2513 if table.is_empty() {
2514 return String::new();
2515 }
2516
2517 let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2518
2519 if !self.wheres.is_empty() {
2520 sql.push_str(" WHERE ");
2521 sql.push_str(&self.wheres.join(" AND "));
2522 }
2523
2524 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2526 sql.push(' ');
2527 sql.push_str(&clause);
2528 }
2529
2530 sql
2531 }
2532
2533 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2538 let dialect = match sz_orm_core::get_dialect(db_type) {
2539 Ok(d) => d,
2540 Err(_) => return BuiltQuery::default(),
2541 };
2542
2543 let table = self.table.unwrap_or_default();
2544 if table.is_empty() {
2545 return BuiltQuery::default();
2546 }
2547
2548 let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2549 let mut params: Vec<Value> = Vec::new();
2550
2551 let has_raw = !self.wheres.is_empty();
2552 let has_param = !self.param_wheres.is_empty();
2553 if has_raw || has_param {
2554 sql.push_str(" WHERE ");
2555 let mut first = true;
2556 for w in &self.wheres {
2557 if first {
2558 sql.push_str(w);
2559 first = false;
2560 } else {
2561 sql.push_str(" AND ");
2562 sql.push_str(w);
2563 }
2564 }
2565 for pw in &self.param_wheres {
2566 let (conjunction, column, op, vals) = match pw {
2567 ParamWhere::And { column, op, values } => ("AND", column, op, values),
2568 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2569 };
2570 let expr = if column.is_empty() {
2572 op.clone()
2573 } else {
2574 format!("{}{}", quote_column_dialect(&*dialect, column), op)
2575 };
2576 if first {
2577 sql.push_str(&expr);
2578 first = false;
2579 } else {
2580 sql.push(' ');
2581 sql.push_str(conjunction);
2582 sql.push(' ');
2583 sql.push_str(&expr);
2584 }
2585 params.extend(vals.iter().cloned());
2586 }
2587 }
2588
2589 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2591 sql.push(' ');
2592 sql.push_str(&clause);
2593 }
2594
2595 BuiltQuery { sql, params }
2596 }
2597}
2598
2599#[cfg(test)]
2600#[allow(deprecated)]
2601mod tests {
2602 use super::*;
2603
2604 #[test]
2607 fn test_select_basic() {
2608 let sql = Query::select()
2609 .column("id")
2610 .column("name")
2611 .from("users")
2612 .build(DbType::MySQL);
2613 assert!(sql.starts_with("SELECT "));
2614 assert!(sql.contains("`id`"));
2615 assert!(sql.contains("`name`"));
2616 assert!(sql.contains("FROM `users`"));
2617 }
2618
2619 #[test]
2620 fn test_select_star() {
2621 let sql = Query::select()
2622 .all_columns()
2623 .from("users")
2624 .build(DbType::MySQL);
2625 assert!(sql.contains("SELECT *"));
2626 assert!(sql.contains("FROM `users`"));
2627 }
2628
2629 #[test]
2630 fn test_select_distinct() {
2631 let sql = Query::select()
2632 .distinct()
2633 .column("name")
2634 .from("users")
2635 .build(DbType::MySQL);
2636 assert!(sql.contains("SELECT DISTINCT"));
2637 }
2638
2639 #[test]
2640 fn test_select_with_where() {
2641 let sql = Query::select()
2642 .column("id")
2643 .from("users")
2644 .where_clause("age > 18")
2645 .where_clause("status = 'active'")
2646 .build(DbType::MySQL);
2647 assert!(sql.contains("WHERE age > 18 AND status = 'active'"));
2648 }
2649
2650 #[test]
2651
2652 fn test_select_with_inner_join() {
2653 let sql = Query::select()
2654 .column("u.id")
2655 .from("users u")
2656 .inner_join("orders o", "u.id = o.user_id")
2657 .build(DbType::MySQL);
2658 assert!(sql.contains("INNER JOIN `orders` o ON u.id = o.user_id"));
2659 }
2660
2661 #[test]
2662 fn test_select_with_left_join() {
2663 let sql = Query::select()
2664 .column("u.id")
2665 .from("users u")
2666 .left_join("profiles p", "u.id = p.user_id")
2667 .build(DbType::MySQL);
2668 assert!(sql.contains("LEFT JOIN `profiles` p ON u.id = p.user_id"));
2669 }
2670
2671 #[test]
2672 fn test_select_with_order_by() {
2673 let sql = Query::select()
2674 .column("id")
2675 .from("users")
2676 .order_by("created_at", true)
2677 .order_by("id", false)
2678 .build(DbType::MySQL);
2679 assert!(sql.contains("ORDER BY `created_at` ASC, `id` DESC"));
2680 }
2681
2682 #[test]
2683 fn test_select_with_limit_offset() {
2684 let sql = Query::select()
2685 .column("id")
2686 .from("users")
2687 .limit(10)
2688 .offset(20)
2689 .build(DbType::MySQL);
2690 assert!(sql.contains("LIMIT 10"));
2691 assert!(sql.contains("OFFSET 20"));
2692 }
2693
2694 #[test]
2695 fn test_select_paginate() {
2696 let sql = Query::select()
2697 .column("id")
2698 .from("users")
2699 .paginate(3, 20)
2700 .build(DbType::MySQL);
2701 assert!(sql.contains("LIMIT 20"));
2703 assert!(sql.contains("OFFSET 40"));
2704 }
2705
2706 #[test]
2707 fn test_select_with_group_by_having() {
2708 let sql = Query::select()
2709 .column("status")
2710 .from("users")
2711 .group_by("status")
2712 .having("COUNT(*) > 5")
2713 .build(DbType::MySQL);
2714 assert!(sql.contains("GROUP BY `status`"));
2715 assert!(sql.contains("HAVING COUNT(*) > 5"));
2716 }
2717
2718 #[test]
2719 fn test_select_postgres_dialect() {
2720 let sql = Query::select()
2721 .column("id")
2722 .from("users")
2723 .build(DbType::PostgreSQL);
2724 assert!(sql.contains("\"id\""));
2725 assert!(sql.contains("FROM \"users\""));
2726 }
2727
2728 #[test]
2729 fn test_select_sqlite_dialect() {
2730 let sql = Query::select()
2731 .column("id")
2732 .from("users")
2733 .build(DbType::Sqlite);
2734 assert!(sql.contains("\"id\""));
2735 }
2736
2737 #[test]
2738 fn test_select_multiple_joins() {
2739 let sql = Query::select()
2740 .column("u.id")
2741 .from("users u")
2742 .inner_join("orders o", "u.id = o.user_id")
2743 .left_join("profiles p", "u.id = p.user_id")
2744 .build(DbType::MySQL);
2745 assert!(sql.contains("INNER JOIN `orders` o"));
2746 assert!(sql.contains("LEFT JOIN `profiles` p"));
2747 }
2748
2749 #[test]
2750 fn test_select_columns_multiple() {
2751 let sql = Query::select()
2752 .columns(&["id", "name", "email"])
2753 .from("users")
2754 .build(DbType::MySQL);
2755 assert!(sql.contains("`id`, `name`, `email`"));
2756 }
2757
2758 #[test]
2759 fn test_select_no_columns_defaults_star() {
2760 let sql = Query::select().from("users").build(DbType::MySQL);
2761 assert!(sql.contains("SELECT *"));
2762 }
2763
2764 #[test]
2767 fn test_insert_basic() {
2768 let sql = Query::insert()
2769 .into_table("users")
2770 .value("name", "'Alice'")
2771 .value("age", "30")
2772 .build();
2773 assert!(sql.starts_with("INSERT INTO `users`"));
2774 assert!(sql.contains("`name`, `age`"));
2775 assert!(sql.contains("'Alice', 30"));
2776 }
2777
2778 #[test]
2779 fn test_insert_values_batch() {
2780 let sql = Query::insert()
2781 .into_table("users")
2782 .values(&[("name", "'Bob'"), ("age", "25"), ("email", "'bob@x.com'")])
2783 .build();
2784 assert!(sql.contains("`name`, `age`, `email`"));
2785 assert!(sql.contains("'Bob', 25, 'bob@x.com'"));
2786 }
2787
2788 #[test]
2789 fn test_insert_empty_returns_empty() {
2790 let sql = Query::insert().into_table("users").build();
2791 assert_eq!(sql, "");
2792 }
2793
2794 #[test]
2795 fn test_insert_with_dialect() {
2796 let sql = Query::insert()
2797 .into_table("users")
2798 .value("name", "'Alice'")
2799 .build_with_dialect(DbType::PostgreSQL);
2800 assert!(sql.contains("\"name\""));
2801 assert!(sql.contains("\"users\""));
2802 }
2803
2804 #[test]
2807 fn test_update_basic() {
2808 let sql = Query::update()
2809 .table("users")
2810 .set("name", "'Bob'")
2811 .where_clause("id = 1")
2812 .build();
2813 assert!(sql.starts_with("UPDATE `users` SET"));
2814 assert!(sql.contains("`name` = 'Bob'"));
2815 assert!(sql.contains("WHERE id = 1"));
2816 }
2817
2818 #[test]
2819 fn test_update_multiple_sets() {
2820 let sql = Query::update()
2821 .table("users")
2822 .sets(&[("name", "'Bob'"), ("age", "30")])
2823 .where_clause("id = 1")
2824 .build();
2825 assert!(sql.contains("`name` = 'Bob', `age` = 30"));
2826 }
2827
2828 #[test]
2829 fn test_update_no_where() {
2830 let sql = Query::update()
2831 .table("users")
2832 .set("status", "'active'")
2833 .build();
2834 assert!(sql.contains("UPDATE `users` SET `status` = 'active'"));
2835 assert!(!sql.contains("WHERE"));
2836 }
2837
2838 #[test]
2839 fn test_update_empty_returns_empty() {
2840 let sql = Query::update().table("users").build();
2841 assert_eq!(sql, "");
2842 }
2843
2844 #[test]
2845 fn test_update_with_dialect() {
2846 let sql = Query::update()
2847 .table("users")
2848 .set("name", "'Bob'")
2849 .build_with_dialect(DbType::PostgreSQL);
2850 assert!(sql.contains("\"users\""));
2851 assert!(sql.contains("\"name\""));
2852 }
2853
2854 #[test]
2857 fn test_delete_basic() {
2858 let sql = Query::delete()
2859 .from_table("users")
2860 .where_clause("id = 1")
2861 .build();
2862 assert!(sql.starts_with("DELETE FROM `users`"));
2863 assert!(sql.contains("WHERE id = 1"));
2864 }
2865
2866 #[test]
2867 fn test_delete_no_where() {
2868 let sql = Query::delete().from_table("users").build();
2869 assert!(sql.contains("DELETE FROM `users`"));
2870 assert!(!sql.contains("WHERE"));
2871 }
2872
2873 #[test]
2874 fn test_delete_multiple_wheres() {
2875 let sql = Query::delete()
2876 .from_table("users")
2877 .where_clause("id > 100")
2878 .where_clause("status = 'inactive'")
2879 .build();
2880 assert!(sql.contains("WHERE id > 100 AND status = 'inactive'"));
2881 }
2882
2883 #[test]
2884 fn test_delete_empty_returns_empty() {
2885 let sql = Query::delete().build();
2886 assert_eq!(sql, "");
2887 }
2888
2889 #[test]
2890 fn test_delete_with_dialect() {
2891 let sql = Query::delete()
2892 .from_table("users")
2893 .where_clause("id = 1")
2894 .build_with_dialect(DbType::PostgreSQL);
2895 assert!(sql.contains("\"users\""));
2896 }
2897
2898 #[test]
2901 fn test_full_crud_flow() {
2902 let insert = Query::insert()
2904 .into_table("users")
2905 .value("name", "'Alice'")
2906 .value("age", "30")
2907 .build();
2908 assert!(insert.contains("INSERT INTO"));
2909
2910 let select = Query::select()
2912 .column("id")
2913 .column("name")
2914 .from("users")
2915 .where_clause("age > 18")
2916 .order_by("id", true)
2917 .limit(10)
2918 .build(DbType::MySQL);
2919 assert!(select.contains("SELECT"));
2920 assert!(select.contains("FROM"));
2921 assert!(select.contains("WHERE"));
2922 assert!(select.contains("ORDER BY"));
2923 assert!(select.contains("LIMIT"));
2924
2925 let update = Query::update()
2927 .table("users")
2928 .set("name", "'Bob'")
2929 .where_clause("id = 1")
2930 .build();
2931 assert!(update.contains("UPDATE"));
2932 assert!(update.contains("SET"));
2933 assert!(update.contains("WHERE"));
2934
2935 let delete = Query::delete()
2937 .from_table("users")
2938 .where_clause("id = 1")
2939 .build();
2940 assert!(delete.contains("DELETE FROM"));
2941 }
2942
2943 #[test]
2944 fn test_complex_select_query() {
2945 let sql = Query::select()
2946 .distinct()
2947 .columns(&["u.id", "u.name", "o.total"])
2948 .from("users u")
2949 .inner_join("orders o", "u.id = o.user_id")
2950 .where_clause("u.status = 'active'")
2951 .where_clause("o.total > 100")
2952 .group_by("u.id")
2953 .having("SUM(o.total) > 1000")
2954 .order_by("u.id", true)
2955 .limit(20)
2956 .offset(40)
2957 .build(DbType::MySQL);
2958
2959 assert!(sql.contains("SELECT DISTINCT"));
2960 assert!(sql.contains("INNER JOIN `orders` o"));
2961 assert!(sql.contains("WHERE u.status = 'active' AND o.total > 100"));
2962 assert!(sql.contains("GROUP BY"));
2963 assert!(sql.contains("HAVING SUM(o.total) > 1000"));
2964 assert!(sql.contains("ORDER BY `u`.`id` ASC"));
2965 assert!(sql.contains("LIMIT 20"));
2966 assert!(sql.contains("OFFSET 40"));
2967 }
2968
2969 #[test]
2972 #[should_panic(expected = "SQL injection detected")]
2973 fn test_select_where_rejects_semicolon_drop() {
2974 let _ = Query::select()
2975 .column("id")
2976 .from("users")
2977 .where_clause("1=1; DROP TABLE users")
2978 .build(DbType::MySQL);
2979 }
2980
2981 #[test]
2982 #[should_panic(expected = "SQL injection detected")]
2983 fn test_select_where_rejects_semicolon_space_drop() {
2984 let _ = Query::select()
2985 .column("id")
2986 .from("users")
2987 .where_clause("1=1; DROP TABLE users")
2988 .build(DbType::MySQL);
2989 }
2990
2991 #[test]
2992 #[should_panic(expected = "SQL injection detected")]
2993 fn test_select_where_rejects_line_comment() {
2994 let _ = Query::select()
2995 .column("id")
2996 .from("users")
2997 .where_clause("id = 1 -- DROP TABLE users")
2998 .build(DbType::MySQL);
2999 }
3000
3001 #[test]
3002 #[should_panic(expected = "SQL injection detected")]
3003 fn test_select_where_rejects_block_comment() {
3004 let _ = Query::select()
3005 .column("id")
3006 .from("users")
3007 .where_clause("id = 1 /* comment */ OR 1=1")
3008 .build(DbType::MySQL);
3009 }
3010
3011 #[test]
3012 #[should_panic(expected = "SQL injection detected")]
3013 fn test_update_where_rejects_delete() {
3014 let _ = Query::update()
3015 .table("users")
3016 .set("name", "'x'")
3017 .where_clause("1=1; DELETE FROM users")
3018 .build();
3019 }
3020
3021 #[test]
3022 #[should_panic(expected = "SQL injection detected")]
3023 fn test_update_where_rejects_line_comment() {
3024 let _ = Query::update()
3025 .table("users")
3026 .set("name", "'x'")
3027 .where_clause("id = 1 -- bypass")
3028 .build();
3029 }
3030
3031 #[test]
3032 #[should_panic(expected = "SQL injection detected")]
3033 fn test_delete_where_rejects_drop() {
3034 let _ = Query::delete()
3035 .from_table("users")
3036 .where_clause("1=1; DROP TABLE users")
3037 .build();
3038 }
3039
3040 #[test]
3041 #[should_panic(expected = "SQL injection detected")]
3042 fn test_delete_where_rejects_block_comment() {
3043 let _ = Query::delete()
3044 .from_table("users")
3045 .where_clause("id = 1 /* */ OR 1=1")
3046 .build();
3047 }
3048
3049 #[test]
3050 #[should_panic(expected = "SQL injection detected")]
3051 fn test_delete_where_rejects_line_comment() {
3052 let _ = Query::delete()
3053 .from_table("users")
3054 .where_clause("id = 1--")
3055 .build();
3056 }
3057
3058 #[test]
3059 fn test_safe_where_clauses_pass() {
3060 let sql_str = Query::select()
3062 .column("id")
3063 .from("users")
3064 .where_clause("age > 18")
3065 .where_clause("name = 'Alice;Bob'") .where_clause("id IN (1, 2, 3)")
3067 .where_clause("created_at > '2026-01-01'")
3068 .build(DbType::MySQL);
3069 assert!(!sql_str.is_empty(), "SELECT SQL 不应为空");
3070 assert!(sql_str.contains("age > 18"), "SELECT 应包含 age > 18 条件");
3071 assert!(
3072 sql_str.contains("name = 'Alice;Bob'"),
3073 "SELECT 应包含 name 条件(含分号字面量)"
3074 );
3075 assert!(sql_str.contains("id IN (1, 2, 3)"), "SELECT 应包含 IN 子句");
3076 assert!(
3077 sql_str.contains("created_at > '2026-01-01'"),
3078 "SELECT 应包含日期条件"
3079 );
3080
3081 let sql_str = Query::update()
3082 .table("users")
3083 .set("name", "'x'")
3084 .where_clause("id = 1")
3085 .build();
3086 assert!(!sql_str.is_empty(), "UPDATE SQL 不应为空");
3087 assert!(sql_str.contains("UPDATE"), "应为 UPDATE 语句");
3088 assert!(sql_str.contains("WHERE"), "UPDATE 应包含 WHERE 子句");
3089 assert!(sql_str.contains("id = 1"), "UPDATE WHERE 应包含 id = 1");
3090
3091 let sql_str = Query::delete()
3092 .from_table("users")
3093 .where_clause("id = 1")
3094 .build();
3095 assert!(!sql_str.is_empty(), "DELETE SQL 不应为空");
3096 assert!(sql_str.contains("DELETE"), "应为 DELETE 语句");
3097 assert!(sql_str.contains("WHERE"), "DELETE 应包含 WHERE 子句");
3098 assert!(sql_str.contains("id = 1"), "DELETE WHERE 应包含 id = 1");
3099 }
3100
3101 #[test]
3105 #[should_panic(expected = "SQL injection detected")]
3106 fn test_mutant_block_comment_open_only() {
3107 let _ = Query::select()
3110 .column("id")
3111 .from("users")
3112 .where_clause("id = 1 /* OR 1=1")
3113 .build(DbType::MySQL);
3114 }
3115
3116 #[test]
3117 #[should_panic(expected = "SQL injection detected")]
3118 fn test_mutant_block_comment_close_only() {
3119 let _ = Query::select()
3121 .column("id")
3122 .from("users")
3123 .where_clause("id = 1 */")
3124 .build(DbType::MySQL);
3125 }
3126
3127 #[test]
3128 fn test_mutant_insert_dialect_table_no_columns_returns_empty() {
3129 let sql = Query::insert()
3131 .into_table("users")
3132 .build_with_dialect(DbType::MySQL);
3133 assert_eq!(sql, "", "有表无列时应返回空字符串");
3134 }
3135
3136 #[test]
3137 fn test_mutant_update_dialect_table_no_sets_returns_empty() {
3138 let sql = Query::update()
3140 .table("users")
3141 .build_with_dialect(DbType::MySQL);
3142 assert_eq!(sql, "", "有表无 SET 时应返回空字符串");
3143 }
3144
3145 #[test]
3146 fn test_mutant_update_dialect_no_table_with_sets_returns_empty() {
3147 let sql = Query::update()
3149 .set("name", "'x'")
3150 .build_with_dialect(DbType::MySQL);
3151 assert_eq!(sql, "", "无表有 SET 时应返回空字符串");
3152 }
3153
3154 #[test]
3155 fn test_mutant_delete_dialect_no_table_returns_empty() {
3156 let sql = Query::delete()
3158 .where_clause("id = 1")
3159 .build_with_dialect(DbType::MySQL);
3160 assert_eq!(sql, "", "无表时应返回空字符串");
3161 }
3162
3163 #[test]
3164 fn test_mutant_select_right_join() {
3165 let sql = Query::select()
3167 .column("u.id")
3168 .from("users u")
3169 .right_join("orders o", "u.id = o.user_id")
3170 .build(DbType::MySQL);
3171 assert!(sql.contains("RIGHT JOIN `orders` o ON u.id = o.user_id"));
3172 }
3173
3174 #[test]
3175 fn test_mutant_all_columns_with_extra() {
3176 let sql = Query::select()
3179 .all_columns()
3180 .column("extra")
3181 .from("users")
3182 .build(DbType::MySQL);
3183 assert!(
3184 sql.contains("SELECT *, `extra` FROM `users`"),
3185 "all_columns + column 应在 SELECT 列表中同时包含 * 和 extra,实际: {sql}"
3186 );
3187 }
3188
3189 #[test]
3190 fn test_mutant_update_dialect_no_where_no_where_clause() {
3191 let sql = Query::update()
3194 .table("users")
3195 .set("name", "'x'")
3196 .build_with_dialect(DbType::MySQL);
3197 assert!(
3198 !sql.contains("WHERE"),
3199 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3200 );
3201 }
3202
3203 #[test]
3204 fn test_mutant_delete_dialect_no_where_no_where_clause() {
3205 let sql = Query::delete()
3207 .from_table("users")
3208 .build_with_dialect(DbType::MySQL);
3209 assert!(
3210 !sql.contains("WHERE"),
3211 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3212 );
3213 }
3214
3215 #[test]
3220 fn test_cte_single_with_clause() {
3221 let sql = Query::select()
3222 .column("id")
3223 .column("name")
3224 .from("active_users")
3225 .with_cte(
3226 "active_users",
3227 "SELECT * FROM users WHERE status = 'active'",
3228 )
3229 .build(DbType::MySQL);
3230 assert!(sql.starts_with("WITH active_users AS ("));
3231 assert!(sql.contains("SELECT * FROM users WHERE status = 'active'"));
3232 assert!(sql.contains("SELECT `id`, `name` FROM `active_users`"));
3233 }
3234
3235 #[test]
3236 fn test_cte_multiple_with_clauses() {
3237 let sql = Query::select()
3238 .column("id")
3239 .from("combined")
3240 .with_cte("a", "SELECT id FROM table_a")
3241 .with_cte("b", "SELECT id FROM table_b")
3242 .with_cte("combined", "SELECT id FROM a UNION SELECT id FROM b")
3243 .build(DbType::MySQL);
3244 assert!(sql.starts_with(
3245 "WITH a AS (SELECT id FROM table_a), b AS (SELECT id FROM table_b), combined AS ("
3246 ));
3247 }
3248
3249 #[test]
3250 fn test_cte_recursive_with_clause() {
3251 let sql = Query::select()
3252 .column("id")
3253 .column("parent_id")
3254 .from("tree")
3255 .with_recursive_cte("tree", "SELECT id, parent_id FROM nodes WHERE id = 1")
3256 .build(DbType::MySQL);
3257 assert!(sql.starts_with("WITH RECURSIVE tree AS ("));
3258 }
3259
3260 #[test]
3261 fn test_cte_no_cte_no_with_prefix() {
3262 let sql = Query::select()
3263 .column("id")
3264 .from("users")
3265 .build(DbType::MySQL);
3266 assert!(!sql.contains("WITH"));
3267 assert!(sql.starts_with("SELECT"));
3268 }
3269
3270 #[test]
3273 fn test_window_function_raw_expr() {
3274 let sql = Query::select()
3275 .column("id")
3276 .column("salary")
3277 .from("employees")
3278 .window_function("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn")
3279 .build(DbType::MySQL);
3280 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3281 }
3282
3283 #[test]
3284 fn test_row_number_helper_with_partition() {
3285 let sql = Query::select()
3286 .column("name")
3287 .column("dept")
3288 .from("employees")
3289 .row_number("dept", "salary DESC", "row_num")
3290 .build(DbType::MySQL);
3291 assert!(
3292 sql.contains("ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS row_num")
3293 );
3294 }
3295
3296 #[test]
3297 fn test_row_number_helper_without_partition() {
3298 let sql = Query::select()
3299 .column("name")
3300 .from("employees")
3301 .row_number("", "salary DESC", "rn")
3302 .build(DbType::MySQL);
3303 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3304 assert!(!sql.contains("PARTITION BY"));
3305 }
3306
3307 #[test]
3308 fn test_rank_helper() {
3309 let sql = Query::select()
3310 .column("name")
3311 .from("scores")
3312 .rank("", "score DESC", "rank_num")
3313 .build(DbType::MySQL);
3314 assert!(sql.contains("RANK() OVER (ORDER BY score DESC) AS rank_num"));
3315 }
3316
3317 #[test]
3318 fn test_dense_rank_helper_with_partition() {
3319 let sql = Query::select()
3320 .column("name")
3321 .from("scores")
3322 .dense_rank("class", "score DESC", "dr")
3323 .build(DbType::MySQL);
3324 assert!(sql.contains("DENSE_RANK() OVER (PARTITION BY class ORDER BY score DESC) AS dr"));
3325 }
3326
3327 #[test]
3328 fn test_multiple_window_functions() {
3329 let sql = Query::select()
3330 .column("name")
3331 .column("salary")
3332 .from("employees")
3333 .row_number("dept", "salary DESC", "rn")
3334 .rank("dept", "salary DESC", "rk")
3335 .dense_rank("dept", "salary DESC", "dr")
3336 .build(DbType::MySQL);
3337 assert!(sql.contains("ROW_NUMBER()"));
3338 assert!(sql.contains("RANK()"));
3339 assert!(sql.contains("DENSE_RANK()"));
3340 }
3341
3342 #[test]
3343 fn test_window_function_with_cte_combined() {
3344 let sql = Query::select()
3345 .column("name")
3346 .from("ranked")
3347 .with_cte(
3348 "ranked",
3349 "SELECT name, ROW_NUMBER() OVER (ORDER BY salary) AS rn FROM employees",
3350 )
3351 .where_clause("rn <= 10")
3352 .build(DbType::MySQL);
3353 assert!(sql.starts_with("WITH ranked AS ("));
3354 assert!(sql.contains("FROM `ranked`"));
3355 assert!(sql.contains("WHERE rn <= 10"));
3356 }
3357
3358 #[test]
3361 fn test_for_update_basic() {
3362 let sql = Query::select()
3363 .column("id")
3364 .column("balance")
3365 .from("accounts")
3366 .where_clause("id = 1")
3367 .for_update()
3368 .build(DbType::MySQL);
3369 assert!(sql.ends_with(" FOR UPDATE"));
3370 assert!(sql.contains("WHERE id = 1"));
3371 }
3372
3373 #[test]
3374 fn test_for_update_with_nowait() {
3375 let sql = Query::select()
3376 .column("id")
3377 .from("accounts")
3378 .where_clause("id = 1")
3379 .for_update_with_options("NOWAIT")
3380 .build(DbType::MySQL);
3381 assert!(sql.ends_with(" FOR UPDATE NOWAIT"));
3382 }
3383
3384 #[test]
3385 fn test_for_update_with_skip_locked() {
3386 let sql = Query::select()
3387 .column("id")
3388 .from("accounts")
3389 .where_clause("id = 1")
3390 .for_update_with_options("SKIP LOCKED")
3391 .build(DbType::MySQL);
3392 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3393 }
3394
3395 #[test]
3396 fn test_for_update_with_limit_and_order() {
3397 let sql = Query::select()
3398 .column("id")
3399 .from("jobs")
3400 .order_by("priority", false)
3401 .limit(1)
3402 .for_update_with_options("SKIP LOCKED")
3403 .build(DbType::MySQL);
3404 assert!(sql.contains("ORDER BY `priority` DESC"));
3405 assert!(sql.contains("LIMIT 1"));
3406 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3407 }
3408
3409 #[test]
3410 fn test_no_for_update_by_default() {
3411 let sql = Query::select()
3412 .column("id")
3413 .from("users")
3414 .build(DbType::MySQL);
3415 assert!(!sql.contains("FOR UPDATE"));
3416 }
3417
3418 #[test]
3421 fn test_set_operator_as_sql() {
3422 assert_eq!(SetOperator::Union.as_sql(), "UNION");
3423 assert_eq!(SetOperator::UnionAll.as_sql(), "UNION ALL");
3424 assert_eq!(SetOperator::Intersect.as_sql(), "INTERSECT");
3425 assert_eq!(SetOperator::Except.as_sql(), "EXCEPT");
3426 }
3427
3428 #[test]
3429 fn test_union_basic() {
3430 let q1 = Query::select().column("id").from("active_users");
3431 let q2 = Query::select().column("id").from("pending_users");
3432 let sql = q1.union(q2).build(DbType::MySQL);
3433 assert!(sql.contains("SELECT `id` FROM `active_users`"));
3434 assert!(sql.contains(" UNION "));
3435 assert!(sql.contains("SELECT `id` FROM `pending_users`"));
3436 }
3437
3438 #[test]
3439 fn test_union_all_basic() {
3440 let q1 = Query::select().column("id").from("table_a");
3441 let q2 = Query::select().column("id").from("table_b");
3442 let sql = q1.union_all(q2).build(DbType::MySQL);
3443 assert!(sql.contains(" UNION ALL "));
3444 }
3445
3446 #[test]
3447 fn test_intersect_basic() {
3448 let q1 = Query::select().column("id").from("table_a");
3449 let q2 = Query::select().column("id").from("table_b");
3450 let sql = q1.intersect(q2).build(DbType::MySQL);
3451 assert!(sql.contains(" INTERSECT "));
3452 }
3453
3454 #[test]
3455 fn test_except_basic() {
3456 let q1 = Query::select().column("id").from("table_a");
3457 let q2 = Query::select().column("id").from("table_b");
3458 let sql = q1.except(q2).build(DbType::MySQL);
3459 assert!(sql.contains(" EXCEPT "));
3460 }
3461
3462 #[test]
3463 fn test_union_chained_multiple() {
3464 let q1 = Query::select().column("id").from("t1");
3465 let q2 = Query::select().column("id").from("t2");
3466 let q3 = Query::select().column("id").from("t3");
3467 let sql = q1.union(q2).union(q3).build(DbType::MySQL);
3468 assert_eq!(sql.matches("UNION").count(), 2);
3469 }
3470
3471 #[test]
3472 fn test_union_mixed_operators() {
3473 let q1 = Query::select().column("id").from("t1");
3474 let q2 = Query::select().column("id").from("t2");
3475 let q3 = Query::select().column("id").from("t3");
3476 let sql = q1.union(q2).intersect(q3).build(DbType::MySQL);
3477 assert!(sql.contains(" UNION "));
3478 assert!(sql.contains(" INTERSECT "));
3479 }
3480
3481 #[test]
3482 fn test_union_with_order_by_limit() {
3483 let q1 = Query::select().column("id").from("t1");
3484 let q2 = Query::select().column("id").from("t2");
3485 let sql = q1
3486 .union(q2)
3487 .order_by("id", true)
3488 .limit(10)
3489 .offset(5)
3490 .build(DbType::MySQL);
3491 assert!(sql.contains("ORDER BY `id` ASC"));
3492 assert!(sql.contains("LIMIT 10"));
3493 assert!(sql.contains("OFFSET 5"));
3494 }
3495
3496 #[test]
3497 fn test_union_postgres_dialect() {
3498 let q1 = Query::select().column("id").from("t1");
3499 let q2 = Query::select().column("id").from("t2");
3500 let sql = q1.union(q2).build(DbType::PostgreSQL);
3501 assert!(sql.contains("\"id\""));
3502 assert!(sql.contains(" UNION "));
3503 }
3504
3505 #[test]
3506 fn test_union_with_where_clauses() {
3507 let q1 = Query::select()
3508 .column("id")
3509 .from("active_users")
3510 .where_clause("age > 18");
3511 let q2 = Query::select()
3512 .column("id")
3513 .from("pending_users")
3514 .where_clause("age > 18");
3515 let sql = q1.union(q2).build(DbType::MySQL);
3516 assert!(sql.contains("WHERE age > 18"));
3517 assert!(sql.contains(" UNION "));
3518 }
3519
3520 #[test]
3523 fn test_cte_window_for_update_combined() {
3524 let sql = Query::select()
3526 .column("id")
3527 .column("salary")
3528 .from("ranked_salaries")
3529 .with_cte(
3530 "ranked_salaries",
3531 "SELECT id, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM employees",
3532 )
3533 .where_clause("rn = 1")
3534 .for_update()
3535 .build(DbType::MySQL);
3536 assert!(sql.starts_with("WITH ranked_salaries AS ("));
3537 assert!(sql.contains("FOR UPDATE"));
3538 assert!(sql.contains("WHERE rn = 1"));
3539 }
3540
3541 #[test]
3542 fn test_complex_window_aggregation() {
3543 let sql = Query::select()
3545 .column("user_id")
3546 .column("amount")
3547 .from("transactions")
3548 .window_function(
3549 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total",
3550 )
3551 .rank("user_id", "created_at", "tx_rank")
3552 .build(DbType::MySQL);
3553 assert!(sql.contains(
3554 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total"
3555 ));
3556 assert!(sql.contains("RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS tx_rank"));
3557 }
3558
3559 #[test]
3562 fn test_select_where_eq_params() {
3563 use sz_orm_core::Value;
3564 let built = Query::select()
3565 .column("id")
3566 .from("users")
3567 .where_eq("age", Value::I32(18))
3568 .build_with_params(DbType::MySQL);
3569 assert!(built.sql.contains("WHERE `age` = ?"));
3570 assert_eq!(built.params.len(), 1);
3571 assert_eq!(built.params[0], Value::I32(18));
3572 }
3573
3574 #[test]
3575 fn test_select_multiple_where_params() {
3576 use sz_orm_core::Value;
3577 let built = Query::select()
3578 .column("id")
3579 .from("users")
3580 .where_eq("age", Value::I32(18))
3581 .where_eq("status", Value::String("active".to_string()))
3582 .build_with_params(DbType::MySQL);
3583 assert!(built.sql.contains("WHERE `age` = ? AND `status` = ?"));
3584 assert_eq!(built.params.len(), 2);
3585 }
3586
3587 #[test]
3588 fn test_select_or_where_eq_params() {
3589 use sz_orm_core::Value;
3590 let built = Query::select()
3591 .column("id")
3592 .from("users")
3593 .where_eq("age", Value::I32(18))
3594 .or_where_eq("role", Value::String("admin".to_string()))
3595 .build_with_params(DbType::MySQL);
3596 assert!(built.sql.contains("WHERE `age` = ? OR `role` = ?"));
3597 assert_eq!(built.params.len(), 2);
3598 }
3599
3600 #[test]
3601 fn test_select_where_in_params() {
3602 use sz_orm_core::Value;
3603 let built = Query::select()
3604 .column("id")
3605 .from("users")
3606 .where_in("id", vec![Value::I32(1), Value::I32(2), Value::I32(3)])
3607 .build_with_params(DbType::MySQL);
3608 assert!(built.sql.contains("WHERE `id` IN (?, ?, ?)"));
3609 assert_eq!(built.params.len(), 3);
3610 }
3611
3612 #[test]
3613 fn test_select_where_in_empty() {
3614 let built = Query::select()
3615 .column("id")
3616 .from("users")
3617 .where_in("id", vec![])
3618 .build_with_params(DbType::MySQL);
3619 assert!(built.sql.contains("WHERE 1 = 0"));
3620 assert_eq!(built.params.len(), 0);
3621 }
3622
3623 #[test]
3624 fn test_select_where_between_params() {
3625 use sz_orm_core::Value;
3626 let built = Query::select()
3627 .column("id")
3628 .from("users")
3629 .where_between("age", Value::I32(18), Value::I32(65))
3630 .build_with_params(DbType::MySQL);
3631 assert!(built.sql.contains("WHERE `age` BETWEEN ? AND ?"));
3632 assert_eq!(built.params.len(), 2);
3633 }
3634
3635 #[test]
3636 fn test_select_where_null_params() {
3637 let built = Query::select()
3638 .column("id")
3639 .from("users")
3640 .where_null("deleted_at")
3641 .build_with_params(DbType::MySQL);
3642 assert!(built.sql.contains("WHERE `deleted_at` IS NULL"));
3643 assert_eq!(built.params.len(), 0);
3644 }
3645
3646 #[test]
3647 fn test_select_where_not_null_params() {
3648 let built = Query::select()
3649 .column("id")
3650 .from("users")
3651 .where_not_null("email")
3652 .build_with_params(DbType::MySQL);
3653 assert!(built.sql.contains("WHERE `email` IS NOT NULL"));
3654 }
3655
3656 #[test]
3657 fn test_select_mixed_raw_and_param_where() {
3658 use sz_orm_core::Value;
3659 let built = Query::select()
3660 .column("id")
3661 .from("users")
3662 .where_clause("age > 18")
3663 .where_eq("status", Value::String("active".to_string()))
3664 .build_with_params(DbType::MySQL);
3665 assert!(built.sql.contains("WHERE age > 18 AND `status` = ?"));
3666 assert_eq!(built.params.len(), 1);
3667 }
3668
3669 #[test]
3670 fn test_select_param_where_with_order_limit() {
3671 use sz_orm_core::Value;
3672 let built = Query::select()
3673 .column("id")
3674 .from("users")
3675 .where_eq("age", Value::I32(18))
3676 .order_by("id", true)
3677 .limit(10)
3678 .build_with_params(DbType::MySQL);
3679 assert!(built.sql.contains("WHERE `age` = ?"));
3680 assert!(built.sql.contains("ORDER BY `id` ASC"));
3681 assert!(built.sql.contains("LIMIT 10"));
3682 }
3683
3684 #[test]
3685 fn test_select_param_where_postgres_dialect() {
3686 use sz_orm_core::Value;
3687 let built = Query::select()
3688 .column("id")
3689 .from("users")
3690 .where_eq("age", Value::I32(18))
3691 .build_with_params(DbType::PostgreSQL);
3692 assert!(built.sql.contains("WHERE \"age\" = ?"));
3693 }
3694
3695 #[test]
3696 fn test_select_param_where_injection_safe() {
3697 use sz_orm_core::Value;
3699 let malicious = "'; DROP TABLE users; --".to_string();
3700 let built = Query::select()
3701 .column("id")
3702 .from("users")
3703 .where_eq("name", Value::String(malicious.clone()))
3704 .build_with_params(DbType::MySQL);
3705 assert!(!built.sql.contains("DROP TABLE"));
3707 assert!(!built.sql.contains(";"));
3708 assert_eq!(built.params.len(), 1);
3710 assert_eq!(built.params[0], Value::String(malicious));
3711 }
3712
3713 #[test]
3714 fn test_update_where_eq_params() {
3715 use sz_orm_core::Value;
3716 let built = Query::update()
3717 .table("users")
3718 .set("name", "'Bob'")
3719 .where_eq("id", Value::I64(1))
3720 .build_with_params(DbType::MySQL);
3721 assert!(built.sql.contains("UPDATE `users` SET"));
3722 assert!(built.sql.contains("WHERE `id` = ?"));
3723 assert_eq!(built.params.len(), 1);
3724 }
3725
3726 #[test]
3727 fn test_update_where_in_params() {
3728 use sz_orm_core::Value;
3729 let built = Query::update()
3730 .table("users")
3731 .set("status", "'inactive'")
3732 .where_in("id", vec![Value::I64(1), Value::I64(2)])
3733 .build_with_params(DbType::MySQL);
3734 assert!(built.sql.contains("WHERE `id` IN (?, ?)"));
3735 assert_eq!(built.params.len(), 2);
3736 }
3737
3738 #[test]
3739 fn test_delete_where_eq_params() {
3740 use sz_orm_core::Value;
3741 let built = Query::delete()
3742 .from_table("users")
3743 .where_eq("id", Value::I64(1))
3744 .build_with_params(DbType::MySQL);
3745 assert!(built.sql.contains("DELETE FROM `users`"));
3746 assert!(built.sql.contains("WHERE `id` = ?"));
3747 assert_eq!(built.params.len(), 1);
3748 }
3749
3750 #[test]
3751 fn test_delete_where_between_params() {
3752 use sz_orm_core::Value;
3753 let built = Query::delete()
3754 .from_table("logs")
3755 .where_between(
3756 "created_at",
3757 Value::String("2020-01-01".to_string()),
3758 Value::String("2020-12-31".to_string()),
3759 )
3760 .build_with_params(DbType::MySQL);
3761 assert!(built.sql.contains("WHERE `created_at` BETWEEN ? AND ?"));
3762 assert_eq!(built.params.len(), 2);
3763 }
3764
3765 #[test]
3768 fn test_from_subquery_basic() {
3769 let inner = Query::select()
3770 .column("id")
3771 .column("amount")
3772 .from("orders")
3773 .build(DbType::MySQL);
3774 let sql = Query::select()
3775 .column("id")
3776 .from_subquery(&inner, "t")
3777 .build(DbType::MySQL);
3778 assert!(
3779 sql.contains("FROM (SELECT `id`, `amount` FROM `orders`) AS `t`"),
3780 "FROM 子查询应渲染为 `FROM (subquery) AS alias`,实际: {sql}"
3781 );
3782 }
3783
3784 #[test]
3785 fn test_from_subquery_postgres_dialect() {
3786 let inner = Query::select()
3787 .column("id")
3788 .from("orders")
3789 .build(DbType::PostgreSQL);
3790 let sql = Query::select()
3791 .column("id")
3792 .from_subquery(&inner, "t")
3793 .build(DbType::PostgreSQL);
3794 assert!(
3795 sql.contains("FROM (SELECT \"id\" FROM \"orders\") AS \"t\""),
3796 "PG 方言下别名应使用双引号,实际: {sql}"
3797 );
3798 }
3799
3800 #[test]
3801 fn test_from_subquery_with_where_and_order() {
3802 let inner = Query::select()
3803 .column("id")
3804 .column("amount")
3805 .from("orders")
3806 .where_clause("amount > 100")
3807 .build(DbType::MySQL);
3808 let sql = Query::select()
3809 .column("id")
3810 .column("amount")
3811 .from_subquery(&inner, "t")
3812 .where_clause("t.amount > 200")
3813 .order_by("id", true)
3814 .build(DbType::MySQL);
3815 assert!(
3816 sql.contains("FROM (SELECT `id`, `amount` FROM `orders` WHERE amount > 100) AS `t`")
3817 );
3818 assert!(sql.contains("WHERE t.amount > 200"));
3819 assert!(sql.contains("ORDER BY `id` ASC"));
3820 }
3821
3822 #[test]
3823 fn test_from_subquery_with_params() {
3824 use sz_orm_core::Value;
3825 let inner = Query::select()
3826 .column("id")
3827 .from("orders")
3828 .where_eq("amount", Value::I32(100))
3829 .build_with_params(DbType::MySQL);
3830 let built = Query::select()
3831 .column("id")
3832 .from_subquery(&inner.sql, "t")
3833 .where_eq("t.id", Value::I64(1))
3834 .build_with_params(DbType::MySQL);
3835 assert!(built
3837 .sql
3838 .contains("FROM (SELECT `id` FROM `orders` WHERE `amount` = ?) AS `t`"));
3839 assert!(built.sql.contains("WHERE `t`.`id` = ?"));
3840 assert_eq!(built.params.len(), 1);
3842 }
3843
3844 #[test]
3845 fn test_from_subquery_overrides_from_table() {
3846 let sql = Query::select()
3848 .column("id")
3849 .from("users")
3850 .from_subquery("SELECT id FROM orders", "t")
3851 .build(DbType::MySQL);
3852 assert!(sql.contains("FROM (SELECT id FROM orders) AS `t`"));
3853 assert!(!sql.contains("FROM `users`"));
3854 }
3855
3856 #[test]
3857 fn test_from_table_overrides_from_subquery() {
3858 let sql = Query::select()
3860 .column("id")
3861 .from_subquery("SELECT id FROM orders", "t")
3862 .from("users")
3863 .build(DbType::MySQL);
3864 assert!(sql.contains("FROM `users`"));
3865 assert!(!sql.contains("FROM ("));
3866 }
3867
3868 #[test]
3869 fn test_from_subquery_no_from_when_neither_set() {
3870 let sql = Query::select().column("id").build(DbType::MySQL);
3871 assert!(!sql.contains("FROM"));
3872 }
3873
3874 #[test]
3877 fn test_insert_returning_postgres() {
3878 let sql = Query::insert()
3879 .into_table("users")
3880 .value("name", "'Alice'")
3881 .returning(&["id", "created_at"])
3882 .build_with_dialect(DbType::PostgreSQL);
3883 assert!(
3884 sql.contains("RETURNING \"id\", \"created_at\""),
3885 "PG 方言应渲染 RETURNING,实际: {sql}"
3886 );
3887 }
3888
3889 #[test]
3890 fn test_insert_returning_sqlite() {
3891 let sql = Query::insert()
3892 .into_table("users")
3893 .value("name", "'Alice'")
3894 .returning(&["id"])
3895 .build_with_dialect(DbType::Sqlite);
3896 assert!(
3897 sql.contains("RETURNING \"id\""),
3898 "SQLite 方言应渲染 RETURNING,实际: {sql}"
3899 );
3900 }
3901
3902 #[test]
3903 fn test_insert_returning_all() {
3904 let sql = Query::insert()
3905 .into_table("users")
3906 .value("name", "'Alice'")
3907 .returning_all()
3908 .build_with_dialect(DbType::PostgreSQL);
3909 assert!(
3910 sql.contains("RETURNING *"),
3911 "returning_all 应渲染 `RETURNING *`,实际: {sql}"
3912 );
3913 }
3914
3915 #[test]
3916 fn test_insert_returning_mysql_skipped() {
3917 let sql = Query::insert()
3919 .into_table("users")
3920 .value("name", "'Alice'")
3921 .returning(&["id"])
3922 .build_with_dialect(DbType::MySQL);
3923 assert!(
3924 !sql.contains("RETURNING"),
3925 "MySQL 方言应跳过 RETURNING,实际: {sql}"
3926 );
3927 }
3928
3929 #[test]
3930 fn test_insert_returning_with_upsert_postgres() {
3931 let sql = Query::insert()
3933 .into_table("users")
3934 .value("id", "1")
3935 .value("name", "'Alice'")
3936 .on_conflict_do_update(&["id"], &[("name", "EXCLUDED.name")])
3937 .returning(&["id", "name"])
3938 .build_with_dialect(DbType::PostgreSQL);
3939 assert!(sql.contains("ON CONFLICT"));
3940 assert!(sql.contains("RETURNING"));
3941 }
3942
3943 #[test]
3944 fn test_insert_returning_build_mysql_style_skipped() {
3945 let sql = Query::insert()
3947 .into_table("users")
3948 .value("name", "'Alice'")
3949 .returning(&["id"])
3950 .build();
3951 assert!(!sql.contains("RETURNING"));
3952 }
3953
3954 #[test]
3955 fn test_update_returning_postgres() {
3956 let sql = Query::update()
3957 .table("users")
3958 .set("status", "'active'")
3959 .where_clause("id = 1")
3960 .returning(&["id", "status"])
3961 .build_with_dialect(DbType::PostgreSQL);
3962 assert!(sql.contains("RETURNING \"id\", \"status\""));
3963 assert!(sql.contains("WHERE id = 1"));
3964 }
3965
3966 #[test]
3967 fn test_update_returning_sqlite() {
3968 let sql = Query::update()
3969 .table("users")
3970 .set("status", "'active'")
3971 .returning(&["id"])
3972 .build_with_dialect(DbType::Sqlite);
3973 assert!(sql.contains("RETURNING \"id\""));
3974 }
3975
3976 #[test]
3977 fn test_update_returning_mysql_skipped() {
3978 let sql = Query::update()
3979 .table("users")
3980 .set("status", "'active'")
3981 .returning(&["id"])
3982 .build_with_dialect(DbType::MySQL);
3983 assert!(!sql.contains("RETURNING"));
3984 }
3985
3986 #[test]
3987 fn test_update_returning_with_params() {
3988 use sz_orm_core::Value;
3989 let built = Query::update()
3990 .table("users")
3991 .set("status", "'active'")
3992 .where_eq("id", Value::I64(1))
3993 .returning(&["id", "status"])
3994 .build_with_params(DbType::PostgreSQL);
3995 assert!(built.sql.contains("WHERE \"id\" = ?"));
3996 assert!(built.sql.contains("RETURNING \"id\", \"status\""));
3997 assert_eq!(built.params.len(), 1);
3998 }
3999
4000 #[test]
4001 fn test_delete_returning_postgres() {
4002 let sql = Query::delete()
4003 .from_table("users")
4004 .where_clause("id = 1")
4005 .returning(&["id", "name"])
4006 .build_with_dialect(DbType::PostgreSQL);
4007 assert!(sql.contains("RETURNING \"id\", \"name\""));
4008 assert!(sql.contains("WHERE id = 1"));
4009 }
4010
4011 #[test]
4012 fn test_delete_returning_sqlite() {
4013 let sql = Query::delete()
4014 .from_table("users")
4015 .where_clause("id = 1")
4016 .returning(&["id"])
4017 .build_with_dialect(DbType::Sqlite);
4018 assert!(sql.contains("RETURNING \"id\""));
4019 }
4020
4021 #[test]
4022 fn test_delete_returning_mysql_skipped() {
4023 let sql = Query::delete()
4024 .from_table("users")
4025 .where_clause("id = 1")
4026 .returning(&["id"])
4027 .build_with_dialect(DbType::MySQL);
4028 assert!(!sql.contains("RETURNING"));
4029 }
4030
4031 #[test]
4032 fn test_delete_returning_with_params() {
4033 use sz_orm_core::Value;
4034 let built = Query::delete()
4035 .from_table("users")
4036 .where_eq("id", Value::I64(1))
4037 .returning(&["id", "name"])
4038 .build_with_params(DbType::PostgreSQL);
4039 assert!(built.sql.contains("WHERE \"id\" = ?"));
4040 assert!(built.sql.contains("RETURNING \"id\", \"name\""));
4041 assert_eq!(built.params.len(), 1);
4042 }
4043
4044 #[test]
4045 fn test_returning_star_not_quoted() {
4046 let sql = Query::insert()
4048 .into_table("users")
4049 .value("name", "'Alice'")
4050 .returning(&["*"])
4051 .build_with_dialect(DbType::PostgreSQL);
4052 assert!(sql.contains("RETURNING *"));
4053 assert!(!sql.contains("RETURNING \"*\""));
4054 }
4055
4056 #[test]
4059 fn test_inner_join_on_column_eq() {
4060 let sql = Query::select()
4061 .column("u.id")
4062 .from("users u")
4063 .inner_join_on("orders o", "u.id", "o.user_id")
4064 .build(DbType::MySQL);
4065 assert!(
4066 sql.contains("INNER JOIN `orders` o ON `u`.`id` = `o`.`user_id`"),
4067 "列对列等值连接应渲染转义标识符,实际: {sql}"
4068 );
4069 }
4070
4071 #[test]
4072 fn test_left_join_on_column_eq() {
4073 let sql = Query::select()
4074 .column("u.id")
4075 .from("users u")
4076 .left_join_on("profiles p", "u.id", "p.user_id")
4077 .build(DbType::MySQL);
4078 assert!(sql.contains("LEFT JOIN `profiles` p ON `u`.`id` = `p`.`user_id`"));
4079 }
4080
4081 #[test]
4082 fn test_right_join_on_column_eq() {
4083 let sql = Query::select()
4084 .column("u.id")
4085 .from("users u")
4086 .right_join_on("orders o", "u.id", "o.user_id")
4087 .build(DbType::MySQL);
4088 assert!(sql.contains("RIGHT JOIN `orders` o ON `u`.`id` = `o`.`user_id`"));
4089 }
4090
4091 #[test]
4092 fn test_inner_join_on_postgres_dialect() {
4093 let sql = Query::select()
4094 .column("u.id")
4095 .from("users u")
4096 .inner_join_on("orders o", "u.id", "o.user_id")
4097 .build(DbType::PostgreSQL);
4098 assert!(
4101 sql.contains("INNER JOIN `orders` o ON \"u\".\"id\" = \"o\".\"user_id\""),
4102 "PG 方言下 ON 条件列名应使用双引号引用,实际: {sql}"
4103 );
4104 }
4105
4106 #[test]
4107 fn test_inner_join_param_binds_value() {
4108 use sz_orm_core::Value;
4109 let built = Query::select()
4110 .column("u.id")
4111 .from("users u")
4112 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4113 .build_with_params(DbType::MySQL);
4114 assert!(
4115 built
4116 .sql
4117 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"),
4118 "参数化 JOIN 应渲染 ? 占位符,实际: {}",
4119 built.sql
4120 );
4121 assert_eq!(built.params.len(), 1);
4122 assert_eq!(built.params[0], Value::String("paid".to_string()));
4123 }
4124
4125 #[test]
4126 fn test_left_join_param_binds_value() {
4127 use sz_orm_core::Value;
4128 let built = Query::select()
4129 .column("u.id")
4130 .from("users u")
4131 .left_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4132 .build_with_params(DbType::MySQL);
4133 assert!(built
4134 .sql
4135 .contains("LEFT JOIN `orders` o ON `o`.`status` = ?"));
4136 assert_eq!(built.params.len(), 1);
4137 }
4138
4139 #[test]
4140 fn test_right_join_param_binds_value() {
4141 use sz_orm_core::Value;
4142 let built = Query::select()
4143 .column("u.id")
4144 .from("users u")
4145 .right_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4146 .build_with_params(DbType::MySQL);
4147 assert!(built
4148 .sql
4149 .contains("RIGHT JOIN `orders` o ON `o`.`status` = ?"));
4150 assert_eq!(built.params.len(), 1);
4151 }
4152
4153 #[test]
4154 fn test_param_join_injection_safe() {
4155 use sz_orm_core::Value;
4157 let malicious = "'; DROP TABLE orders; --".to_string();
4158 let built = Query::select()
4159 .column("u.id")
4160 .from("users u")
4161 .inner_join_param(
4162 "orders o",
4163 "o.status",
4164 " = ?",
4165 Value::String(malicious.clone()),
4166 )
4167 .build_with_params(DbType::MySQL);
4168 assert!(!built.sql.contains("DROP TABLE"));
4170 assert!(!built.sql.contains(";"));
4171 assert_eq!(built.params.len(), 1);
4173 assert_eq!(built.params[0], Value::String(malicious));
4174 }
4175
4176 #[test]
4177 fn test_mixed_raw_and_param_join() {
4178 use sz_orm_core::Value;
4180 let built = Query::select()
4181 .column("u.id")
4182 .from("users u")
4183 .inner_join("orders o", "u.id = o.user_id")
4184 .inner_join_param(
4185 "payments p",
4186 "p.status",
4187 " = ?",
4188 Value::String("paid".into()),
4189 )
4190 .build_with_params(DbType::MySQL);
4191 assert!(built
4192 .sql
4193 .contains("INNER JOIN `orders` o ON u.id = o.user_id"));
4194 assert!(built
4195 .sql
4196 .contains("INNER JOIN `payments` p ON `p`.`status` = ?"));
4197 assert_eq!(built.params.len(), 1);
4198 }
4199
4200 #[test]
4201 fn test_param_join_with_where_params_combined() {
4202 use sz_orm_core::Value;
4204 let built = Query::select()
4205 .column("u.id")
4206 .from("users u")
4207 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4208 .where_eq("u.age", Value::I32(18))
4209 .build_with_params(DbType::MySQL);
4210 assert!(built
4211 .sql
4212 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"));
4213 assert!(built.sql.contains("WHERE `u`.`age` = ?"));
4214 assert_eq!(built.params.len(), 2);
4216 assert_eq!(built.params[0], Value::String("paid".to_string()));
4217 assert_eq!(built.params[1], Value::I32(18));
4218 }
4219}