1use sz_orm_core::{DbType, Value};
64
65#[derive(Debug, Clone, Default)]
80pub struct BuiltQuery {
81 pub sql: String,
83 pub params: Vec<Value>,
85}
86
87impl BuiltQuery {
88 pub fn into_parts(self) -> (String, Vec<Value>) {
90 (self.sql, self.params)
91 }
92}
93
94#[derive(Debug, Clone)]
106enum ParamWhere {
107 And {
109 column: String,
111 op: String,
113 values: Vec<Value>,
115 },
116 Or {
118 column: String,
119 op: String,
120 values: Vec<Value>,
121 },
122}
123
124fn quote_ident(s: &str) -> String {
134 s.split('.')
135 .map(|part| format!("`{}`", part.replace('`', "``")))
136 .collect::<Vec<_>>()
137 .join(".")
138}
139
140fn quote_column_dialect(dialect: &dyn sz_orm_core::Dialect, column: &str) -> String {
153 column
154 .split('.')
155 .map(|part| dialect.quote(part))
156 .collect::<Vec<_>>()
157 .join(".")
158}
159
160fn check_where_injection(condition: &str) {
178 let upper = condition.to_uppercase();
179 const SQL_KEYWORDS: &[&str] = &[
180 "DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "TRUNCATE", "EXEC", "CREATE", "GRANT",
181 "REVOKE",
182 ];
183 for kw in SQL_KEYWORDS {
184 let pattern1 = format!(";{}", kw);
185 let pattern2 = format!("; {}", kw);
186 if upper.contains(&pattern1) || upper.contains(&pattern2) {
187 panic!(
188 "SQL injection detected in where_clause: semicolon followed by {} keyword: {:?}",
189 kw, condition
190 );
191 }
192 }
193 if condition.contains("--") {
194 panic!(
195 "SQL injection detected in where_clause: line comment '--' not allowed: {:?}",
196 condition
197 );
198 }
199 if condition.contains("/*") || condition.contains("*/") {
200 panic!(
201 "SQL injection detected in where_clause: block comment '/*' or '*/' not allowed: {:?}",
202 condition
203 );
204 }
205}
206
207#[deprecated(
222 since = "3.5.0",
223 note = "v3.5.0: 请评估 docs/query-builder-guide.md 选择合适方案。标准 CRUD 推荐迁移到 sz_orm_core::QueryBuilder<M>"
224)]
225pub struct Query;
226
227#[allow(deprecated)]
228impl Query {
229 #[deprecated(
231 since = "3.5.0",
232 note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::select()。详见 docs/query-builder-guide.md"
233 )]
234 pub fn select() -> SelectQuery {
235 SelectQuery::new()
236 }
237
238 #[deprecated(
240 since = "3.5.0",
241 note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::insert()。详见 docs/query-builder-guide.md"
242 )]
243 pub fn insert() -> InsertQuery {
244 InsertQuery::new()
245 }
246
247 #[deprecated(
249 since = "3.5.0",
250 note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::update()。详见 docs/query-builder-guide.md"
251 )]
252 pub fn update() -> UpdateQuery {
253 UpdateQuery::new()
254 }
255
256 #[deprecated(
258 since = "3.5.0",
259 note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::delete()。详见 docs/query-builder-guide.md"
260 )]
261 pub fn delete() -> DeleteQuery {
262 DeleteQuery::new()
263 }
264}
265
266#[derive(Debug, Clone)]
272enum JoinOn {
273 Raw(String),
275 ColumnEq {
277 left_column: String,
278 right_column: String,
279 },
280 Param {
282 left_column: String,
283 op: String,
284 values: Vec<Value>,
285 },
286}
287
288#[derive(Debug, Clone)]
290struct JoinClause {
291 join_type: &'static str,
293 table: String,
295 on: Vec<JoinOn>,
297}
298
299#[derive(Debug, Clone, Default)]
301pub struct SelectQuery {
302 columns: Vec<String>,
303 from_table: Option<String>,
304 from_subquery: Option<(String, String)>,
306 join_clauses: Vec<JoinClause>,
312 wheres: Vec<String>,
313 param_wheres: Vec<ParamWhere>,
315 order_by: Vec<String>,
316 group_by: Vec<String>,
317 having: Vec<String>,
318 limit: Option<u64>,
319 offset: Option<u64>,
320 distinct: bool,
321 ctes: Vec<(String, String, bool)>,
323 window_columns: Vec<String>,
325 for_update: bool,
327 for_update_options: Option<String>,
329}
330
331impl SelectQuery {
332 pub fn new() -> Self {
334 Self::default()
335 }
336
337 pub fn distinct(mut self) -> Self {
339 self.distinct = true;
340 self
341 }
342
343 pub fn column(mut self, name: &str) -> Self {
345 self.columns.push(name.to_string());
346 self
347 }
348
349 pub fn columns(mut self, names: &[&str]) -> Self {
351 for n in names {
352 self.columns.push(n.to_string());
353 }
354 self
355 }
356
357 pub fn all_columns(self) -> Self {
359 self.column("*")
360 }
361
362 pub fn from(mut self, table: &str) -> Self {
364 self.from_table = Some(table.to_string());
365 self.from_subquery = None;
367 self
368 }
369
370 pub fn from_subquery(mut self, subquery_sql: &str, alias: &str) -> Self {
395 self.from_subquery = Some((subquery_sql.to_string(), alias.to_string()));
396 self.from_table = None;
398 self
399 }
400
401 pub fn inner_join(mut self, table: &str, on: &str) -> Self {
408 self.join_clauses.push(JoinClause {
409 join_type: "INNER JOIN",
410 table: Self::quote_join_table(table),
411 on: vec![JoinOn::Raw(on.to_string())],
412 });
413 self
414 }
415
416 pub fn left_join(mut self, table: &str, on: &str) -> Self {
422 self.join_clauses.push(JoinClause {
423 join_type: "LEFT JOIN",
424 table: Self::quote_join_table(table),
425 on: vec![JoinOn::Raw(on.to_string())],
426 });
427 self
428 }
429
430 pub fn right_join(mut self, table: &str, on: &str) -> Self {
436 self.join_clauses.push(JoinClause {
437 join_type: "RIGHT JOIN",
438 table: Self::quote_join_table(table),
439 on: vec![JoinOn::Raw(on.to_string())],
440 });
441 self
442 }
443
444 pub fn inner_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
482 self.join_clauses.push(JoinClause {
483 join_type: "INNER JOIN",
484 table: Self::quote_join_table(table),
485 on: vec![JoinOn::ColumnEq {
486 left_column: left_col.to_string(),
487 right_column: right_col.to_string(),
488 }],
489 });
490 self
491 }
492
493 pub fn left_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
495 self.join_clauses.push(JoinClause {
496 join_type: "LEFT JOIN",
497 table: Self::quote_join_table(table),
498 on: vec![JoinOn::ColumnEq {
499 left_column: left_col.to_string(),
500 right_column: right_col.to_string(),
501 }],
502 });
503 self
504 }
505
506 pub fn right_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
508 self.join_clauses.push(JoinClause {
509 join_type: "RIGHT JOIN",
510 table: Self::quote_join_table(table),
511 on: vec![JoinOn::ColumnEq {
512 left_column: left_col.to_string(),
513 right_column: right_col.to_string(),
514 }],
515 });
516 self
517 }
518
519 pub fn inner_join_param(
528 mut self,
529 table: &str,
530 left_col: &str,
531 op_expr: &str,
532 value: Value,
533 ) -> Self {
534 self.join_clauses.push(JoinClause {
535 join_type: "INNER JOIN",
536 table: Self::quote_join_table(table),
537 on: vec![JoinOn::Param {
538 left_column: left_col.to_string(),
539 op: op_expr.to_string(),
540 values: vec![value],
541 }],
542 });
543 self
544 }
545
546 pub fn left_join_param(
548 mut self,
549 table: &str,
550 left_col: &str,
551 op_expr: &str,
552 value: Value,
553 ) -> Self {
554 self.join_clauses.push(JoinClause {
555 join_type: "LEFT JOIN",
556 table: Self::quote_join_table(table),
557 on: vec![JoinOn::Param {
558 left_column: left_col.to_string(),
559 op: op_expr.to_string(),
560 values: vec![value],
561 }],
562 });
563 self
564 }
565
566 pub fn right_join_param(
568 mut self,
569 table: &str,
570 left_col: &str,
571 op_expr: &str,
572 value: Value,
573 ) -> Self {
574 self.join_clauses.push(JoinClause {
575 join_type: "RIGHT JOIN",
576 table: Self::quote_join_table(table),
577 on: vec![JoinOn::Param {
578 left_column: left_col.to_string(),
579 op: op_expr.to_string(),
580 values: vec![value],
581 }],
582 });
583 self
584 }
585
586 fn render_joins(&self, dialect: &dyn sz_orm_core::Dialect, params: &mut Vec<Value>) -> String {
592 let mut sql = String::new();
593 for clause in &self.join_clauses {
594 sql.push(' ');
595 sql.push_str(clause.join_type);
596 sql.push(' ');
597 sql.push_str(&clause.table);
598 sql.push_str(" ON ");
599 for (i, on) in clause.on.iter().enumerate() {
600 if i > 0 {
601 sql.push_str(" AND ");
602 }
603 match on {
604 JoinOn::Raw(raw) => sql.push_str(raw),
605 JoinOn::ColumnEq {
606 left_column,
607 right_column,
608 } => {
609 sql.push_str("e_column_dialect(dialect, left_column));
610 sql.push_str(" = ");
611 sql.push_str("e_column_dialect(dialect, right_column));
612 }
613 JoinOn::Param {
614 left_column,
615 op,
616 values,
617 } => {
618 sql.push_str("e_column_dialect(dialect, left_column));
619 sql.push_str(op);
620 params.extend(values.iter().cloned());
621 }
622 }
623 }
624 }
625 sql
626 }
627
628 fn quote_join_table(table: &str) -> String {
630 if let Some((tbl, alias)) = table.rsplit_once(' ') {
631 if alias.to_uppercase() == "AS" {
632 format!("{} AS {}", quote_ident(tbl), alias)
634 } else {
635 format!("{} {}", quote_ident(tbl), alias)
637 }
638 } else {
639 quote_ident(table)
640 }
641 }
642
643 pub fn where_clause(mut self, condition: &str) -> Self {
651 check_where_injection(condition);
652 self.wheres.push(condition.to_string());
653 self
654 }
655
656 pub fn where_eq(mut self, column: &str, value: Value) -> Self {
682 self.param_wheres.push(ParamWhere::And {
683 column: column.to_string(),
684 op: " = ?".to_string(),
685 values: vec![value],
686 });
687 self
688 }
689
690 pub fn where_ne(mut self, column: &str, value: Value) -> Self {
692 self.param_wheres.push(ParamWhere::And {
693 column: column.to_string(),
694 op: " <> ?".to_string(),
695 values: vec![value],
696 });
697 self
698 }
699
700 pub fn where_gt(mut self, column: &str, value: Value) -> Self {
702 self.param_wheres.push(ParamWhere::And {
703 column: column.to_string(),
704 op: " > ?".to_string(),
705 values: vec![value],
706 });
707 self
708 }
709
710 pub fn where_ge(mut self, column: &str, value: Value) -> Self {
712 self.param_wheres.push(ParamWhere::And {
713 column: column.to_string(),
714 op: " >= ?".to_string(),
715 values: vec![value],
716 });
717 self
718 }
719
720 pub fn where_lt(mut self, column: &str, value: Value) -> Self {
722 self.param_wheres.push(ParamWhere::And {
723 column: column.to_string(),
724 op: " < ?".to_string(),
725 values: vec![value],
726 });
727 self
728 }
729
730 pub fn where_le(mut self, column: &str, value: Value) -> Self {
732 self.param_wheres.push(ParamWhere::And {
733 column: column.to_string(),
734 op: " <= ?".to_string(),
735 values: vec![value],
736 });
737 self
738 }
739
740 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
742 self.param_wheres.push(ParamWhere::And {
743 column: column.to_string(),
744 op: " LIKE ?".to_string(),
745 values: vec![pattern],
746 });
747 self
748 }
749
750 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
754 let (column, op) = if values.is_empty() {
755 (String::new(), "1 = 0".to_string())
756 } else {
757 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
758 (
759 column.to_string(),
760 format!(" IN ({})", placeholders.join(", ")),
761 )
762 };
763 self.param_wheres
764 .push(ParamWhere::And { column, op, values });
765 self
766 }
767
768 pub fn where_not_in(mut self, column: &str, values: Vec<Value>) -> Self {
772 let (column, op) = if values.is_empty() {
773 (String::new(), "1 = 1".to_string())
774 } else {
775 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
776 (
777 column.to_string(),
778 format!(" NOT IN ({})", placeholders.join(", ")),
779 )
780 };
781 self.param_wheres
782 .push(ParamWhere::And { column, op, values });
783 self
784 }
785
786 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
788 self.param_wheres.push(ParamWhere::And {
789 column: column.to_string(),
790 op: " BETWEEN ? AND ?".to_string(),
791 values: vec![low, high],
792 });
793 self
794 }
795
796 pub fn where_null(mut self, column: &str) -> Self {
798 self.param_wheres.push(ParamWhere::And {
799 column: column.to_string(),
800 op: " IS NULL".to_string(),
801 values: vec![],
802 });
803 self
804 }
805
806 pub fn where_not_null(mut self, column: &str) -> Self {
808 self.param_wheres.push(ParamWhere::And {
809 column: column.to_string(),
810 op: " IS NOT NULL".to_string(),
811 values: vec![],
812 });
813 self
814 }
815
816 pub fn or_where_eq(mut self, column: &str, value: Value) -> Self {
818 self.param_wheres.push(ParamWhere::Or {
819 column: column.to_string(),
820 op: " = ?".to_string(),
821 values: vec![value],
822 });
823 self
824 }
825
826 pub fn or_where_ne(mut self, column: &str, value: Value) -> Self {
828 self.param_wheres.push(ParamWhere::Or {
829 column: column.to_string(),
830 op: " <> ?".to_string(),
831 values: vec![value],
832 });
833 self
834 }
835
836 pub fn or_where_gt(mut self, column: &str, value: Value) -> Self {
838 self.param_wheres.push(ParamWhere::Or {
839 column: column.to_string(),
840 op: " > ?".to_string(),
841 values: vec![value],
842 });
843 self
844 }
845
846 pub fn or_where_ge(mut self, column: &str, value: Value) -> Self {
848 self.param_wheres.push(ParamWhere::Or {
849 column: column.to_string(),
850 op: " >= ?".to_string(),
851 values: vec![value],
852 });
853 self
854 }
855
856 pub fn or_where_lt(mut self, column: &str, value: Value) -> Self {
858 self.param_wheres.push(ParamWhere::Or {
859 column: column.to_string(),
860 op: " < ?".to_string(),
861 values: vec![value],
862 });
863 self
864 }
865
866 pub fn or_where_le(mut self, column: &str, value: Value) -> Self {
868 self.param_wheres.push(ParamWhere::Or {
869 column: column.to_string(),
870 op: " <= ?".to_string(),
871 values: vec![value],
872 });
873 self
874 }
875
876 pub fn or_where_like(mut self, column: &str, pattern: Value) -> Self {
878 self.param_wheres.push(ParamWhere::Or {
879 column: column.to_string(),
880 op: " LIKE ?".to_string(),
881 values: vec![pattern],
882 });
883 self
884 }
885
886 pub fn or_where_in(mut self, column: &str, values: Vec<Value>) -> Self {
888 let (column, op) = if values.is_empty() {
889 (String::new(), "1 = 0".to_string())
890 } else {
891 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
892 (
893 column.to_string(),
894 format!(" IN ({})", placeholders.join(", ")),
895 )
896 };
897 self.param_wheres
898 .push(ParamWhere::Or { column, op, values });
899 self
900 }
901
902 pub fn or_where_between(mut self, column: &str, low: Value, high: Value) -> Self {
904 self.param_wheres.push(ParamWhere::Or {
905 column: column.to_string(),
906 op: " BETWEEN ? AND ?".to_string(),
907 values: vec![low, high],
908 });
909 self
910 }
911
912 pub fn or_where_null(mut self, column: &str) -> Self {
914 self.param_wheres.push(ParamWhere::Or {
915 column: column.to_string(),
916 op: " IS NULL".to_string(),
917 values: vec![],
918 });
919 self
920 }
921
922 pub fn or_where_not_null(mut self, column: &str) -> Self {
924 self.param_wheres.push(ParamWhere::Or {
925 column: column.to_string(),
926 op: " IS NOT NULL".to_string(),
927 values: vec![],
928 });
929 self
930 }
931
932 pub fn group_by(mut self, column: &str) -> Self {
934 self.group_by.push(column.to_string());
935 self
936 }
937
938 pub fn having(mut self, condition: &str) -> Self {
940 self.having.push(condition.to_string());
941 self
942 }
943
944 pub fn order_by(mut self, column: &str, asc: bool) -> Self {
951 let dir = if asc { "ASC" } else { "DESC" };
952 self.order_by.push(format!("{} {}", column, dir));
953 self
954 }
955
956 pub fn limit(mut self, n: u64) -> Self {
958 self.limit = Some(n);
959 self
960 }
961
962 pub fn offset(mut self, n: u64) -> Self {
964 self.offset = Some(n);
965 self
966 }
967
968 pub fn paginate(self, page: u64, size: u64) -> Self {
975 let offset = (page.saturating_sub(1)) * size;
976 self.limit(size).offset(offset)
977 }
978
979 pub fn with_cte(mut self, name: &str, subquery: &str) -> Self {
988 self.ctes
989 .push((name.to_string(), subquery.to_string(), false));
990 self
991 }
992
993 pub fn with_recursive_cte(mut self, name: &str, subquery: &str) -> Self {
1000 self.ctes
1001 .push((name.to_string(), subquery.to_string(), true));
1002 self
1003 }
1004
1005 pub fn window_function(mut self, expr: &str) -> Self {
1016 self.window_columns.push(expr.to_string());
1017 self
1018 }
1019
1020 pub fn row_number(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1028 let partition_clause = if partition_by.is_empty() {
1029 String::new()
1030 } else {
1031 format!("PARTITION BY {} ", partition_by)
1032 };
1033 let expr = format!(
1034 "ROW_NUMBER() OVER ({}ORDER BY {}) AS {}",
1035 partition_clause, order_by, alias
1036 );
1037 self.window_function(&expr)
1038 }
1039
1040 pub fn rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1048 let partition_clause = if partition_by.is_empty() {
1049 String::new()
1050 } else {
1051 format!("PARTITION BY {} ", partition_by)
1052 };
1053 let expr = format!(
1054 "RANK() OVER ({}ORDER BY {}) AS {}",
1055 partition_clause, order_by, alias
1056 );
1057 self.window_function(&expr)
1058 }
1059
1060 pub fn dense_rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1068 let partition_clause = if partition_by.is_empty() {
1069 String::new()
1070 } else {
1071 format!("PARTITION BY {} ", partition_by)
1072 };
1073 let expr = format!(
1074 "DENSE_RANK() OVER ({}ORDER BY {}) AS {}",
1075 partition_clause, order_by, alias
1076 );
1077 self.window_function(&expr)
1078 }
1079
1080 pub fn for_update(mut self) -> Self {
1084 self.for_update = true;
1085 self.for_update_options = None;
1086 self
1087 }
1088
1089 pub fn for_update_with_options(mut self, options: &str) -> Self {
1095 self.for_update = true;
1096 self.for_update_options = Some(options.to_string());
1097 self
1098 }
1099
1100 pub fn union(self, other: SelectQuery) -> SetQuery {
1104 SetQuery::new(self, SetOperator::Union, other)
1105 }
1106
1107 pub fn union_all(self, other: SelectQuery) -> SetQuery {
1109 SetQuery::new(self, SetOperator::UnionAll, other)
1110 }
1111
1112 pub fn intersect(self, other: SelectQuery) -> SetQuery {
1114 SetQuery::new(self, SetOperator::Intersect, other)
1115 }
1116
1117 pub fn except(self, other: SelectQuery) -> SetQuery {
1119 SetQuery::new(self, SetOperator::Except, other)
1120 }
1121
1122 pub fn build(self, db_type: DbType) -> String {
1128 let dialect = match sz_orm_core::get_dialect(db_type) {
1129 Ok(d) => d,
1130 Err(_) => return String::new(),
1131 };
1132
1133 let mut sql = String::new();
1134
1135 if !self.ctes.is_empty() {
1137 let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1138 if has_recursive {
1139 sql.push_str("WITH RECURSIVE ");
1140 } else {
1141 sql.push_str("WITH ");
1142 }
1143 let cte_strs: Vec<String> = self
1144 .ctes
1145 .iter()
1146 .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1147 .collect();
1148 sql.push_str(&cte_strs.join(", "));
1149 sql.push(' ');
1150 }
1151
1152 sql.push_str("SELECT ");
1153
1154 if self.distinct {
1155 sql.push_str("DISTINCT ");
1156 }
1157
1158 let mut all_columns: Vec<String> = self
1160 .columns
1161 .iter()
1162 .map(|c| {
1163 if c == "*" {
1164 c.clone()
1165 } else {
1166 dialect.quote(c)
1167 }
1168 })
1169 .collect();
1170 all_columns.extend(self.window_columns.iter().cloned());
1171
1172 if all_columns.is_empty() {
1173 sql.push('*');
1174 } else {
1175 sql.push_str(&all_columns.join(", "));
1176 }
1177
1178 if let Some(ref table) = self.from_table {
1179 sql.push_str(" FROM ");
1180 sql.push_str(&dialect.quote(table));
1181 } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1182 sql.push_str(" FROM (");
1184 sql.push_str(subquery);
1185 sql.push_str(") AS ");
1186 sql.push_str(&dialect.quote(alias));
1187 }
1188
1189 let mut unused_params = Vec::new();
1192 sql.push_str(&self.render_joins(&*dialect, &mut unused_params));
1193
1194 if !self.wheres.is_empty() {
1195 sql.push_str(" WHERE ");
1196 sql.push_str(&self.wheres[0]);
1198 for w in &self.wheres[1..] {
1199 if w.starts_with("OR ") {
1200 sql.push(' ');
1201 sql.push_str(w);
1202 } else {
1203 sql.push_str(" AND ");
1204 sql.push_str(w);
1205 }
1206 }
1207 }
1208
1209 if !self.group_by.is_empty() {
1210 sql.push_str(" GROUP BY ");
1211 sql.push_str(
1212 &self
1213 .group_by
1214 .iter()
1215 .map(|c| quote_ident(c))
1216 .collect::<Vec<_>>()
1217 .join(", "),
1218 );
1219 }
1220
1221 if !self.having.is_empty() {
1222 sql.push_str(" HAVING ");
1223 sql.push_str(&self.having.join(" AND "));
1224 }
1225
1226 if !self.order_by.is_empty() {
1227 sql.push_str(" ORDER BY ");
1228 sql.push_str(
1229 &self
1230 .order_by
1231 .iter()
1232 .map(|s| {
1233 if let Some((col, dir)) = s.rsplit_once(' ') {
1235 format!("{} {}", quote_ident(col), dir)
1236 } else {
1237 quote_ident(s)
1238 }
1239 })
1240 .collect::<Vec<_>>()
1241 .join(", "),
1242 );
1243 }
1244
1245 if let Some(limit) = self.limit {
1246 sql.push_str(&format!(" LIMIT {}", limit));
1247 }
1248 if let Some(offset) = self.offset {
1249 sql.push_str(&format!(" OFFSET {}", offset));
1250 }
1251
1252 if self.for_update {
1254 sql.push_str(" FOR UPDATE");
1255 if let Some(ref opts) = self.for_update_options {
1256 sql.push(' ');
1257 sql.push_str(opts);
1258 }
1259 }
1260
1261 sql
1262 }
1263
1264 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
1294 let dialect = match sz_orm_core::get_dialect(db_type) {
1295 Ok(d) => d,
1296 Err(_) => return BuiltQuery::default(),
1297 };
1298
1299 let mut sql = String::new();
1300 let mut params: Vec<Value> = Vec::new();
1301
1302 if !self.ctes.is_empty() {
1304 let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1305 if has_recursive {
1306 sql.push_str("WITH RECURSIVE ");
1307 } else {
1308 sql.push_str("WITH ");
1309 }
1310 let cte_strs: Vec<String> = self
1311 .ctes
1312 .iter()
1313 .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1314 .collect();
1315 sql.push_str(&cte_strs.join(", "));
1316 sql.push(' ');
1317 }
1318
1319 sql.push_str("SELECT ");
1320
1321 if self.distinct {
1322 sql.push_str("DISTINCT ");
1323 }
1324
1325 let mut all_columns: Vec<String> = self
1326 .columns
1327 .iter()
1328 .map(|c| {
1329 if c == "*" {
1330 c.clone()
1331 } else {
1332 dialect.quote(c)
1333 }
1334 })
1335 .collect();
1336 all_columns.extend(self.window_columns.iter().cloned());
1337
1338 if all_columns.is_empty() {
1339 sql.push('*');
1340 } else {
1341 sql.push_str(&all_columns.join(", "));
1342 }
1343
1344 if let Some(ref table) = self.from_table {
1345 sql.push_str(" FROM ");
1346 sql.push_str(&dialect.quote(table));
1347 } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1348 sql.push_str(" FROM (");
1350 sql.push_str(subquery);
1351 sql.push_str(") AS ");
1352 sql.push_str(&dialect.quote(alias));
1353 }
1354
1355 sql.push_str(&self.render_joins(&*dialect, &mut params));
1358
1359 let has_raw = !self.wheres.is_empty();
1361 let has_param = !self.param_wheres.is_empty();
1362 if has_raw || has_param {
1363 sql.push_str(" WHERE ");
1364 let mut first = true;
1365 for w in &self.wheres {
1367 if first {
1368 sql.push_str(w);
1369 first = false;
1370 } else if w.starts_with("OR ") {
1371 sql.push(' ');
1372 sql.push_str(w);
1373 } else {
1374 sql.push_str(" AND ");
1375 sql.push_str(w);
1376 }
1377 }
1378 for pw in &self.param_wheres {
1380 let (conjunction, column, op, vals) = match pw {
1381 ParamWhere::And { column, op, values } => ("AND", column, op, values),
1382 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
1383 };
1384 let expr = if column.is_empty() {
1386 op.clone()
1387 } else {
1388 format!("{}{}", quote_column_dialect(&*dialect, column), op)
1389 };
1390 if first {
1391 sql.push_str(&expr);
1392 first = false;
1393 } else {
1394 sql.push(' ');
1395 sql.push_str(conjunction);
1396 sql.push(' ');
1397 sql.push_str(&expr);
1398 }
1399 params.extend(vals.iter().cloned());
1400 }
1401 }
1402
1403 if !self.group_by.is_empty() {
1404 sql.push_str(" GROUP BY ");
1405 sql.push_str(
1406 &self
1407 .group_by
1408 .iter()
1409 .map(|c| quote_ident(c))
1410 .collect::<Vec<_>>()
1411 .join(", "),
1412 );
1413 }
1414
1415 if !self.having.is_empty() {
1416 sql.push_str(" HAVING ");
1417 sql.push_str(&self.having.join(" AND "));
1418 }
1419
1420 if !self.order_by.is_empty() {
1421 sql.push_str(" ORDER BY ");
1422 sql.push_str(
1423 &self
1424 .order_by
1425 .iter()
1426 .map(|s| {
1427 if let Some((col, dir)) = s.rsplit_once(' ') {
1428 format!("{} {}", quote_ident(col), dir)
1429 } else {
1430 quote_ident(s)
1431 }
1432 })
1433 .collect::<Vec<_>>()
1434 .join(", "),
1435 );
1436 }
1437
1438 if let Some(limit) = self.limit {
1439 sql.push_str(&format!(" LIMIT {}", limit));
1440 }
1441 if let Some(offset) = self.offset {
1442 sql.push_str(&format!(" OFFSET {}", offset));
1443 }
1444
1445 if self.for_update {
1446 sql.push_str(" FOR UPDATE");
1447 if let Some(ref opts) = self.for_update_options {
1448 sql.push(' ');
1449 sql.push_str(opts);
1450 }
1451 }
1452
1453 BuiltQuery { sql, params }
1454 }
1455}
1456
1457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1463pub enum SetOperator {
1464 Union,
1466 UnionAll,
1468 Intersect,
1470 Except,
1472}
1473
1474impl SetOperator {
1475 pub fn as_sql(&self) -> &'static str {
1477 match self {
1478 SetOperator::Union => "UNION",
1479 SetOperator::UnionAll => "UNION ALL",
1480 SetOperator::Intersect => "INTERSECT",
1481 SetOperator::Except => "EXCEPT",
1482 }
1483 }
1484}
1485
1486#[derive(Debug, Clone)]
1500pub struct SetQuery {
1501 first: SelectQuery,
1503 rest: Vec<(SetOperator, SelectQuery)>,
1505 order_by: Vec<String>,
1507 limit: Option<u64>,
1509 offset: Option<u64>,
1511}
1512
1513impl SetQuery {
1514 pub fn new(first: SelectQuery, op: SetOperator, second: SelectQuery) -> Self {
1516 Self {
1517 first,
1518 rest: vec![(op, second)],
1519 order_by: Vec::new(),
1520 limit: None,
1521 offset: None,
1522 }
1523 }
1524
1525 pub fn union(mut self, other: SelectQuery) -> Self {
1527 self.rest.push((SetOperator::Union, other));
1528 self
1529 }
1530
1531 pub fn union_all(mut self, other: SelectQuery) -> Self {
1533 self.rest.push((SetOperator::UnionAll, other));
1534 self
1535 }
1536
1537 pub fn intersect(mut self, other: SelectQuery) -> Self {
1539 self.rest.push((SetOperator::Intersect, other));
1540 self
1541 }
1542
1543 pub fn except(mut self, other: SelectQuery) -> Self {
1545 self.rest.push((SetOperator::Except, other));
1546 self
1547 }
1548
1549 pub fn order_by(mut self, column: &str, asc: bool) -> Self {
1551 let dir = if asc { "ASC" } else { "DESC" };
1552 self.order_by.push(format!("{} {}", column, dir));
1553 self
1554 }
1555
1556 pub fn limit(mut self, n: u64) -> Self {
1558 self.limit = Some(n);
1559 self
1560 }
1561
1562 pub fn offset(mut self, n: u64) -> Self {
1564 self.offset = Some(n);
1565 self
1566 }
1567
1568 pub fn build(self, db_type: DbType) -> String {
1573 let mut sql = self.first.build(db_type);
1574 for (op, query) in &self.rest {
1575 sql.push(' ');
1576 sql.push_str(op.as_sql());
1577 sql.push(' ');
1578 sql.push_str(&query.clone().build(db_type));
1579 }
1580 if !self.order_by.is_empty() {
1581 sql.push_str(" ORDER BY ");
1582 sql.push_str(
1583 &self
1584 .order_by
1585 .iter()
1586 .map(|s| {
1587 if let Some((col, dir)) = s.rsplit_once(' ') {
1588 format!("{} {}", quote_ident(col), dir)
1589 } else {
1590 quote_ident(s)
1591 }
1592 })
1593 .collect::<Vec<_>>()
1594 .join(", "),
1595 );
1596 }
1597 if let Some(limit) = self.limit {
1598 sql.push_str(&format!(" LIMIT {}", limit));
1599 }
1600 if let Some(offset) = self.offset {
1601 sql.push_str(&format!(" OFFSET {}", offset));
1602 }
1603 sql
1604 }
1605}
1606
1607#[derive(Debug, Clone, Default)]
1625pub enum UpsertStrategy {
1626 #[default]
1628 None,
1629 OnConflictDoNothing(Vec<String>),
1631 OnConflictDoUpdate(Vec<String>, Vec<(String, String)>),
1634 OnDuplicateKeyUpdate(Vec<(String, String)>),
1636 Replace,
1638}
1639
1640fn is_mysql_family(db_type: DbType) -> bool {
1642 matches!(
1643 db_type,
1644 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase | DbType::PolarDB
1645 )
1646}
1647
1648fn is_pg_family(db_type: DbType) -> bool {
1650 matches!(
1651 db_type,
1652 DbType::PostgreSQL | DbType::Kingbase | DbType::GaussDB | DbType::PolarDB
1653 ) || db_type == DbType::Sqlite
1654}
1655
1656fn render_upsert_clause(strategy: &UpsertStrategy, db_type: DbType) -> Option<String> {
1661 match strategy {
1662 UpsertStrategy::None => None,
1663 UpsertStrategy::OnConflictDoNothing(cols) => {
1664 if is_pg_family(db_type) {
1665 let cols_str = cols
1666 .iter()
1667 .map(|c| quote_ident(c))
1668 .collect::<Vec<_>>()
1669 .join(", ");
1670 Some(format!("ON CONFLICT ({}) DO NOTHING", cols_str))
1671 } else {
1672 None
1673 }
1674 }
1675 UpsertStrategy::OnConflictDoUpdate(cols, assignments) => {
1676 if is_pg_family(db_type) {
1677 let cols_str = cols
1678 .iter()
1679 .map(|c| quote_ident(c))
1680 .collect::<Vec<_>>()
1681 .join(", ");
1682 let sets = assignments
1683 .iter()
1684 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1685 .collect::<Vec<_>>()
1686 .join(", ");
1687 Some(format!("ON CONFLICT ({}) DO UPDATE SET {}", cols_str, sets))
1688 } else {
1689 None
1690 }
1691 }
1692 UpsertStrategy::OnDuplicateKeyUpdate(assignments) => {
1693 if is_mysql_family(db_type) {
1694 let sets = assignments
1695 .iter()
1696 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1697 .collect::<Vec<_>>()
1698 .join(", ");
1699 Some(format!("ON DUPLICATE KEY UPDATE {}", sets))
1700 } else {
1701 None
1702 }
1703 }
1704 UpsertStrategy::Replace => None, }
1706}
1707
1708fn render_returning_clause(columns: &Option<Vec<String>>, db_type: DbType) -> Option<String> {
1715 let cols = columns.as_ref()?;
1716 if cols.is_empty() {
1717 return None;
1718 }
1719 if is_mysql_family(db_type) {
1721 return None;
1722 }
1723 let dialect = sz_orm_core::get_dialect(db_type).ok()?;
1724 let quoted: Vec<String> = cols
1725 .iter()
1726 .map(|c| {
1727 if c == "*" {
1728 c.clone()
1729 } else {
1730 dialect.quote(c)
1731 }
1732 })
1733 .collect();
1734 Some(format!("RETURNING {}", quoted.join(", ")))
1735}
1736
1737#[derive(Debug, Clone, Default)]
1739pub struct InsertQuery {
1740 table: Option<String>,
1741 columns: Vec<String>,
1742 values: Vec<String>,
1743 upsert: UpsertStrategy,
1745 returning: Option<Vec<String>>,
1747}
1748
1749impl InsertQuery {
1750 pub fn new() -> Self {
1752 Self::default()
1753 }
1754
1755 pub fn into_table(mut self, table: &str) -> Self {
1757 self.table = Some(table.to_string());
1758 self
1759 }
1760
1761 pub fn value(mut self, column: &str, value: &str) -> Self {
1763 self.columns.push(column.to_string());
1764 self.values.push(value.to_string());
1765 self
1766 }
1767
1768 pub fn values(mut self, pairs: &[(&str, &str)]) -> Self {
1770 for (c, v) in pairs {
1771 self.columns.push(c.to_string());
1772 self.values.push(v.to_string());
1773 }
1774 self
1775 }
1776
1777 pub fn on_conflict_do_nothing(mut self, conflict_cols: &[&str]) -> Self {
1801 self.upsert = UpsertStrategy::OnConflictDoNothing(
1802 conflict_cols.iter().map(|s| s.to_string()).collect(),
1803 );
1804 self
1805 }
1806
1807 pub fn on_conflict_do_update(
1832 mut self,
1833 conflict_cols: &[&str],
1834 assignments: &[(&str, &str)],
1835 ) -> Self {
1836 self.upsert = UpsertStrategy::OnConflictDoUpdate(
1837 conflict_cols.iter().map(|s| s.to_string()).collect(),
1838 assignments
1839 .iter()
1840 .map(|(c, v)| (c.to_string(), v.to_string()))
1841 .collect(),
1842 );
1843 self
1844 }
1845
1846 pub fn on_duplicate_key_update(mut self, assignments: &[(&str, &str)]) -> Self {
1869 self.upsert = UpsertStrategy::OnDuplicateKeyUpdate(
1870 assignments
1871 .iter()
1872 .map(|(c, v)| (c.to_string(), v.to_string()))
1873 .collect(),
1874 );
1875 self
1876 }
1877
1878 pub fn replace(mut self) -> Self {
1883 self.upsert = UpsertStrategy::Replace;
1884 self
1885 }
1886
1887 pub fn returning(mut self, columns: &[&str]) -> Self {
1908 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
1909 self
1910 }
1911
1912 pub fn returning_all(mut self) -> Self {
1914 self.returning = Some(vec!["*".to_string()]);
1915 self
1916 }
1917
1918 pub fn build(self) -> String {
1931 let table = self.table.unwrap_or_default();
1932 if table.is_empty() || self.columns.is_empty() {
1933 return String::new();
1934 }
1935
1936 let cols: Vec<String> = self.columns.iter().map(|c| quote_ident(c)).collect();
1937 let vals: Vec<String> = self.values.iter().map(|v| v.to_string()).collect();
1938
1939 let verb = match &self.upsert {
1941 UpsertStrategy::Replace => "REPLACE INTO",
1942 _ => "INSERT INTO",
1943 };
1944 let mut sql = format!(
1945 "{} {} ({}) VALUES ({})",
1946 verb,
1947 quote_ident(&table),
1948 cols.join(", "),
1949 vals.join(", ")
1950 );
1951
1952 if let UpsertStrategy::OnDuplicateKeyUpdate(assignments) = &self.upsert {
1954 let sets = assignments
1955 .iter()
1956 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1957 .collect::<Vec<_>>()
1958 .join(", ");
1959 sql.push_str(&format!(" ON DUPLICATE KEY UPDATE {}", sets));
1960 }
1961
1962 sql
1963 }
1964
1965 pub fn build_with_dialect(self, db_type: DbType) -> String {
1973 let dialect = match sz_orm_core::get_dialect(db_type) {
1974 Ok(d) => d,
1975 Err(_) => return String::new(),
1976 };
1977
1978 let table = self.table.unwrap_or_default();
1979 if table.is_empty() || self.columns.is_empty() {
1980 return String::new();
1981 }
1982
1983 let cols: Vec<String> = self.columns.iter().map(|c| dialect.quote(c)).collect();
1984
1985 let verb = match (&self.upsert, db_type) {
1987 (UpsertStrategy::Replace, dt) if is_mysql_family(dt) => "REPLACE INTO",
1988 (UpsertStrategy::Replace, DbType::Sqlite) => "INSERT OR REPLACE INTO",
1989 _ => "INSERT INTO",
1990 };
1991 let mut sql = format!(
1992 "{} {} ({}) VALUES ({})",
1993 verb,
1994 dialect.quote(&table),
1995 cols.join(", "),
1996 self.values.join(", ")
1997 );
1998
1999 if let Some(clause) = render_upsert_clause(&self.upsert, db_type) {
2001 sql.push(' ');
2002 sql.push_str(&clause);
2003 }
2004
2005 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2007 sql.push(' ');
2008 sql.push_str(&clause);
2009 }
2010
2011 sql
2012 }
2013}
2014
2015#[derive(Debug, Clone, Default)]
2017pub struct UpdateQuery {
2018 table: Option<String>,
2019 sets: Vec<(String, String)>,
2020 wheres: Vec<String>,
2021 param_wheres: Vec<ParamWhere>,
2023 returning: Option<Vec<String>>,
2025}
2026
2027impl UpdateQuery {
2028 pub fn new() -> Self {
2030 Self::default()
2031 }
2032
2033 pub fn table(mut self, table: &str) -> Self {
2035 self.table = Some(table.to_string());
2036 self
2037 }
2038
2039 pub fn set(mut self, column: &str, value: &str) -> Self {
2041 self.sets.push((column.to_string(), value.to_string()));
2042 self
2043 }
2044
2045 pub fn sets(mut self, pairs: &[(&str, &str)]) -> Self {
2047 for (c, v) in pairs {
2048 self.sets.push((c.to_string(), v.to_string()));
2049 }
2050 self
2051 }
2052
2053 pub fn where_clause(mut self, condition: &str) -> Self {
2059 check_where_injection(condition);
2060 self.wheres.push(condition.to_string());
2061 self
2062 }
2063
2064 pub fn where_eq(mut self, column: &str, value: Value) -> Self {
2066 self.param_wheres.push(ParamWhere::And {
2067 column: column.to_string(),
2068 op: " = ?".to_string(),
2069 values: vec![value],
2070 });
2071 self
2072 }
2073
2074 pub fn where_ne(mut self, column: &str, value: Value) -> Self {
2076 self.param_wheres.push(ParamWhere::And {
2077 column: column.to_string(),
2078 op: " <> ?".to_string(),
2079 values: vec![value],
2080 });
2081 self
2082 }
2083
2084 pub fn where_gt(mut self, column: &str, value: Value) -> Self {
2086 self.param_wheres.push(ParamWhere::And {
2087 column: column.to_string(),
2088 op: " > ?".to_string(),
2089 values: vec![value],
2090 });
2091 self
2092 }
2093
2094 pub fn where_ge(mut self, column: &str, value: Value) -> Self {
2096 self.param_wheres.push(ParamWhere::And {
2097 column: column.to_string(),
2098 op: " >= ?".to_string(),
2099 values: vec![value],
2100 });
2101 self
2102 }
2103
2104 pub fn where_lt(mut self, column: &str, value: Value) -> Self {
2106 self.param_wheres.push(ParamWhere::And {
2107 column: column.to_string(),
2108 op: " < ?".to_string(),
2109 values: vec![value],
2110 });
2111 self
2112 }
2113
2114 pub fn where_le(mut self, column: &str, value: Value) -> Self {
2116 self.param_wheres.push(ParamWhere::And {
2117 column: column.to_string(),
2118 op: " <= ?".to_string(),
2119 values: vec![value],
2120 });
2121 self
2122 }
2123
2124 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2126 self.param_wheres.push(ParamWhere::And {
2127 column: column.to_string(),
2128 op: " LIKE ?".to_string(),
2129 values: vec![pattern],
2130 });
2131 self
2132 }
2133
2134 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2136 let (column, op) = if values.is_empty() {
2137 (String::new(), "1 = 0".to_string())
2138 } else {
2139 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2140 (
2141 column.to_string(),
2142 format!(" IN ({})", placeholders.join(", ")),
2143 )
2144 };
2145 self.param_wheres
2146 .push(ParamWhere::And { column, op, values });
2147 self
2148 }
2149
2150 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2152 self.param_wheres.push(ParamWhere::And {
2153 column: column.to_string(),
2154 op: " BETWEEN ? AND ?".to_string(),
2155 values: vec![low, high],
2156 });
2157 self
2158 }
2159
2160 pub fn where_null(mut self, column: &str) -> Self {
2162 self.param_wheres.push(ParamWhere::And {
2163 column: column.to_string(),
2164 op: " IS NULL".to_string(),
2165 values: vec![],
2166 });
2167 self
2168 }
2169
2170 pub fn where_not_null(mut self, column: &str) -> Self {
2172 self.param_wheres.push(ParamWhere::And {
2173 column: column.to_string(),
2174 op: " IS NOT NULL".to_string(),
2175 values: vec![],
2176 });
2177 self
2178 }
2179
2180 pub fn returning(mut self, columns: &[&str]) -> Self {
2186 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2187 self
2188 }
2189
2190 pub fn returning_all(mut self) -> Self {
2192 self.returning = Some(vec!["*".to_string()]);
2193 self
2194 }
2195
2196 pub fn build(self) -> String {
2203 let table = self.table.unwrap_or_default();
2204 if table.is_empty() || self.sets.is_empty() {
2205 return String::new();
2206 }
2207
2208 let set_str: Vec<String> = self
2209 .sets
2210 .iter()
2211 .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
2212 .collect();
2213
2214 let mut sql = format!("UPDATE {} SET {}", quote_ident(&table), set_str.join(", "));
2215
2216 if !self.wheres.is_empty() {
2217 sql.push_str(" WHERE ");
2218 sql.push_str(&self.wheres.join(" AND "));
2219 }
2220
2221 sql
2222 }
2223
2224 pub fn build_with_dialect(self, db_type: DbType) -> String {
2226 let dialect = match sz_orm_core::get_dialect(db_type) {
2227 Ok(d) => d,
2228 Err(_) => return String::new(),
2229 };
2230
2231 let table = self.table.unwrap_or_default();
2232 if table.is_empty() || self.sets.is_empty() {
2233 return String::new();
2234 }
2235
2236 let set_str: Vec<String> = self
2237 .sets
2238 .iter()
2239 .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2240 .collect();
2241
2242 let mut sql = format!(
2243 "UPDATE {} SET {}",
2244 dialect.quote(&table),
2245 set_str.join(", ")
2246 );
2247
2248 if !self.wheres.is_empty() {
2249 sql.push_str(" WHERE ");
2250 sql.push_str(&self.wheres.join(" AND "));
2251 }
2252
2253 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2255 sql.push(' ');
2256 sql.push_str(&clause);
2257 }
2258
2259 sql
2260 }
2261
2262 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2268 let dialect = match sz_orm_core::get_dialect(db_type) {
2269 Ok(d) => d,
2270 Err(_) => return BuiltQuery::default(),
2271 };
2272
2273 let table = self.table.unwrap_or_default();
2274 if table.is_empty() || self.sets.is_empty() {
2275 return BuiltQuery::default();
2276 }
2277
2278 let set_str: Vec<String> = self
2279 .sets
2280 .iter()
2281 .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2282 .collect();
2283
2284 let mut sql = format!(
2285 "UPDATE {} SET {}",
2286 dialect.quote(&table),
2287 set_str.join(", ")
2288 );
2289 let mut params: Vec<Value> = Vec::new();
2290
2291 let has_raw = !self.wheres.is_empty();
2292 let has_param = !self.param_wheres.is_empty();
2293 if has_raw || has_param {
2294 sql.push_str(" WHERE ");
2295 let mut first = true;
2296 for w in &self.wheres {
2297 if first {
2298 sql.push_str(w);
2299 first = false;
2300 } else {
2301 sql.push_str(" AND ");
2302 sql.push_str(w);
2303 }
2304 }
2305 for pw in &self.param_wheres {
2306 let (conjunction, column, op, vals) = match pw {
2307 ParamWhere::And { column, op, values } => ("AND", column, op, values),
2308 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2309 };
2310 let expr = if column.is_empty() {
2312 op.clone()
2313 } else {
2314 format!("{}{}", quote_column_dialect(&*dialect, column), op)
2315 };
2316 if first {
2317 sql.push_str(&expr);
2318 first = false;
2319 } else {
2320 sql.push(' ');
2321 sql.push_str(conjunction);
2322 sql.push(' ');
2323 sql.push_str(&expr);
2324 }
2325 params.extend(vals.iter().cloned());
2326 }
2327 }
2328
2329 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2331 sql.push(' ');
2332 sql.push_str(&clause);
2333 }
2334
2335 BuiltQuery { sql, params }
2336 }
2337}
2338
2339#[derive(Debug, Clone, Default)]
2341pub struct DeleteQuery {
2342 table: Option<String>,
2343 wheres: Vec<String>,
2344 param_wheres: Vec<ParamWhere>,
2346 returning: Option<Vec<String>>,
2348}
2349
2350impl DeleteQuery {
2351 pub fn new() -> Self {
2353 Self::default()
2354 }
2355
2356 pub fn from_table(mut self, table: &str) -> Self {
2358 self.table = Some(table.to_string());
2359 self
2360 }
2361
2362 pub fn where_clause(mut self, condition: &str) -> Self {
2368 check_where_injection(condition);
2369 self.wheres.push(condition.to_string());
2370 self
2371 }
2372
2373 pub fn where_eq(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_ne(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_gt(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_ge(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_lt(mut self, column: &str, value: Value) -> Self {
2415 self.param_wheres.push(ParamWhere::And {
2416 column: column.to_string(),
2417 op: " < ?".to_string(),
2418 values: vec![value],
2419 });
2420 self
2421 }
2422
2423 pub fn where_le(mut self, column: &str, value: Value) -> Self {
2425 self.param_wheres.push(ParamWhere::And {
2426 column: column.to_string(),
2427 op: " <= ?".to_string(),
2428 values: vec![value],
2429 });
2430 self
2431 }
2432
2433 pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2435 self.param_wheres.push(ParamWhere::And {
2436 column: column.to_string(),
2437 op: " LIKE ?".to_string(),
2438 values: vec![pattern],
2439 });
2440 self
2441 }
2442
2443 pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2445 let (column, op) = if values.is_empty() {
2446 (String::new(), "1 = 0".to_string())
2447 } else {
2448 let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2449 (
2450 column.to_string(),
2451 format!(" IN ({})", placeholders.join(", ")),
2452 )
2453 };
2454 self.param_wheres
2455 .push(ParamWhere::And { column, op, values });
2456 self
2457 }
2458
2459 pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2461 self.param_wheres.push(ParamWhere::And {
2462 column: column.to_string(),
2463 op: " BETWEEN ? AND ?".to_string(),
2464 values: vec![low, high],
2465 });
2466 self
2467 }
2468
2469 pub fn where_null(mut self, column: &str) -> Self {
2471 self.param_wheres.push(ParamWhere::And {
2472 column: column.to_string(),
2473 op: " IS NULL".to_string(),
2474 values: vec![],
2475 });
2476 self
2477 }
2478
2479 pub fn where_not_null(mut self, column: &str) -> Self {
2481 self.param_wheres.push(ParamWhere::And {
2482 column: column.to_string(),
2483 op: " IS NOT NULL".to_string(),
2484 values: vec![],
2485 });
2486 self
2487 }
2488
2489 pub fn returning(mut self, columns: &[&str]) -> Self {
2495 self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2496 self
2497 }
2498
2499 pub fn returning_all(mut self) -> Self {
2501 self.returning = Some(vec!["*".to_string()]);
2502 self
2503 }
2504
2505 pub fn build(self) -> String {
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 {}", quote_ident(&table));
2518
2519 if !self.wheres.is_empty() {
2520 sql.push_str(" WHERE ");
2521 sql.push_str(&self.wheres.join(" AND "));
2522 }
2523
2524 sql
2525 }
2526
2527 pub fn build_with_dialect(self, db_type: DbType) -> String {
2529 let dialect = match sz_orm_core::get_dialect(db_type) {
2530 Ok(d) => d,
2531 Err(_) => return String::new(),
2532 };
2533
2534 let table = self.table.unwrap_or_default();
2535 if table.is_empty() {
2536 return String::new();
2537 }
2538
2539 let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2540
2541 if !self.wheres.is_empty() {
2542 sql.push_str(" WHERE ");
2543 sql.push_str(&self.wheres.join(" AND "));
2544 }
2545
2546 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2548 sql.push(' ');
2549 sql.push_str(&clause);
2550 }
2551
2552 sql
2553 }
2554
2555 pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2560 let dialect = match sz_orm_core::get_dialect(db_type) {
2561 Ok(d) => d,
2562 Err(_) => return BuiltQuery::default(),
2563 };
2564
2565 let table = self.table.unwrap_or_default();
2566 if table.is_empty() {
2567 return BuiltQuery::default();
2568 }
2569
2570 let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2571 let mut params: Vec<Value> = Vec::new();
2572
2573 let has_raw = !self.wheres.is_empty();
2574 let has_param = !self.param_wheres.is_empty();
2575 if has_raw || has_param {
2576 sql.push_str(" WHERE ");
2577 let mut first = true;
2578 for w in &self.wheres {
2579 if first {
2580 sql.push_str(w);
2581 first = false;
2582 } else {
2583 sql.push_str(" AND ");
2584 sql.push_str(w);
2585 }
2586 }
2587 for pw in &self.param_wheres {
2588 let (conjunction, column, op, vals) = match pw {
2589 ParamWhere::And { column, op, values } => ("AND", column, op, values),
2590 ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2591 };
2592 let expr = if column.is_empty() {
2594 op.clone()
2595 } else {
2596 format!("{}{}", quote_column_dialect(&*dialect, column), op)
2597 };
2598 if first {
2599 sql.push_str(&expr);
2600 first = false;
2601 } else {
2602 sql.push(' ');
2603 sql.push_str(conjunction);
2604 sql.push(' ');
2605 sql.push_str(&expr);
2606 }
2607 params.extend(vals.iter().cloned());
2608 }
2609 }
2610
2611 if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2613 sql.push(' ');
2614 sql.push_str(&clause);
2615 }
2616
2617 BuiltQuery { sql, params }
2618 }
2619}
2620
2621#[cfg(test)]
2622#[allow(deprecated)]
2623mod tests {
2624 use super::*;
2625
2626 #[test]
2629 fn test_select_basic() {
2630 let sql = Query::select()
2631 .column("id")
2632 .column("name")
2633 .from("users")
2634 .build(DbType::MySQL);
2635 assert!(sql.starts_with("SELECT "));
2636 assert!(sql.contains("`id`"));
2637 assert!(sql.contains("`name`"));
2638 assert!(sql.contains("FROM `users`"));
2639 }
2640
2641 #[test]
2642 fn test_select_star() {
2643 let sql = Query::select()
2644 .all_columns()
2645 .from("users")
2646 .build(DbType::MySQL);
2647 assert!(sql.contains("SELECT *"));
2648 assert!(sql.contains("FROM `users`"));
2649 }
2650
2651 #[test]
2652 fn test_select_distinct() {
2653 let sql = Query::select()
2654 .distinct()
2655 .column("name")
2656 .from("users")
2657 .build(DbType::MySQL);
2658 assert!(sql.contains("SELECT DISTINCT"));
2659 }
2660
2661 #[test]
2662 fn test_select_with_where() {
2663 let sql = Query::select()
2664 .column("id")
2665 .from("users")
2666 .where_clause("age > 18")
2667 .where_clause("status = 'active'")
2668 .build(DbType::MySQL);
2669 assert!(sql.contains("WHERE age > 18 AND status = 'active'"));
2670 }
2671
2672 #[test]
2673
2674 fn test_select_with_inner_join() {
2675 let sql = Query::select()
2676 .column("u.id")
2677 .from("users u")
2678 .inner_join("orders o", "u.id = o.user_id")
2679 .build(DbType::MySQL);
2680 assert!(sql.contains("INNER JOIN `orders` o ON u.id = o.user_id"));
2681 }
2682
2683 #[test]
2684 fn test_select_with_left_join() {
2685 let sql = Query::select()
2686 .column("u.id")
2687 .from("users u")
2688 .left_join("profiles p", "u.id = p.user_id")
2689 .build(DbType::MySQL);
2690 assert!(sql.contains("LEFT JOIN `profiles` p ON u.id = p.user_id"));
2691 }
2692
2693 #[test]
2694 fn test_select_with_order_by() {
2695 let sql = Query::select()
2696 .column("id")
2697 .from("users")
2698 .order_by("created_at", true)
2699 .order_by("id", false)
2700 .build(DbType::MySQL);
2701 assert!(sql.contains("ORDER BY `created_at` ASC, `id` DESC"));
2702 }
2703
2704 #[test]
2705 fn test_select_with_limit_offset() {
2706 let sql = Query::select()
2707 .column("id")
2708 .from("users")
2709 .limit(10)
2710 .offset(20)
2711 .build(DbType::MySQL);
2712 assert!(sql.contains("LIMIT 10"));
2713 assert!(sql.contains("OFFSET 20"));
2714 }
2715
2716 #[test]
2717 fn test_select_paginate() {
2718 let sql = Query::select()
2719 .column("id")
2720 .from("users")
2721 .paginate(3, 20)
2722 .build(DbType::MySQL);
2723 assert!(sql.contains("LIMIT 20"));
2725 assert!(sql.contains("OFFSET 40"));
2726 }
2727
2728 #[test]
2729 fn test_select_with_group_by_having() {
2730 let sql = Query::select()
2731 .column("status")
2732 .from("users")
2733 .group_by("status")
2734 .having("COUNT(*) > 5")
2735 .build(DbType::MySQL);
2736 assert!(sql.contains("GROUP BY `status`"));
2737 assert!(sql.contains("HAVING COUNT(*) > 5"));
2738 }
2739
2740 #[test]
2741 fn test_select_postgres_dialect() {
2742 let sql = Query::select()
2743 .column("id")
2744 .from("users")
2745 .build(DbType::PostgreSQL);
2746 assert!(sql.contains("\"id\""));
2747 assert!(sql.contains("FROM \"users\""));
2748 }
2749
2750 #[test]
2751 fn test_select_sqlite_dialect() {
2752 let sql = Query::select()
2753 .column("id")
2754 .from("users")
2755 .build(DbType::Sqlite);
2756 assert!(sql.contains("\"id\""));
2757 }
2758
2759 #[test]
2760 fn test_select_multiple_joins() {
2761 let sql = Query::select()
2762 .column("u.id")
2763 .from("users u")
2764 .inner_join("orders o", "u.id = o.user_id")
2765 .left_join("profiles p", "u.id = p.user_id")
2766 .build(DbType::MySQL);
2767 assert!(sql.contains("INNER JOIN `orders` o"));
2768 assert!(sql.contains("LEFT JOIN `profiles` p"));
2769 }
2770
2771 #[test]
2772 fn test_select_columns_multiple() {
2773 let sql = Query::select()
2774 .columns(&["id", "name", "email"])
2775 .from("users")
2776 .build(DbType::MySQL);
2777 assert!(sql.contains("`id`, `name`, `email`"));
2778 }
2779
2780 #[test]
2781 fn test_select_no_columns_defaults_star() {
2782 let sql = Query::select().from("users").build(DbType::MySQL);
2783 assert!(sql.contains("SELECT *"));
2784 }
2785
2786 #[test]
2789 fn test_insert_basic() {
2790 let sql = Query::insert()
2791 .into_table("users")
2792 .value("name", "'Alice'")
2793 .value("age", "30")
2794 .build();
2795 assert!(sql.starts_with("INSERT INTO `users`"));
2796 assert!(sql.contains("`name`, `age`"));
2797 assert!(sql.contains("'Alice', 30"));
2798 }
2799
2800 #[test]
2801 fn test_insert_values_batch() {
2802 let sql = Query::insert()
2803 .into_table("users")
2804 .values(&[("name", "'Bob'"), ("age", "25"), ("email", "'bob@x.com'")])
2805 .build();
2806 assert!(sql.contains("`name`, `age`, `email`"));
2807 assert!(sql.contains("'Bob', 25, 'bob@x.com'"));
2808 }
2809
2810 #[test]
2811 fn test_insert_empty_returns_empty() {
2812 let sql = Query::insert().into_table("users").build();
2813 assert_eq!(sql, "");
2814 }
2815
2816 #[test]
2817 fn test_insert_with_dialect() {
2818 let sql = Query::insert()
2819 .into_table("users")
2820 .value("name", "'Alice'")
2821 .build_with_dialect(DbType::PostgreSQL);
2822 assert!(sql.contains("\"name\""));
2823 assert!(sql.contains("\"users\""));
2824 }
2825
2826 #[test]
2829 fn test_update_basic() {
2830 let sql = Query::update()
2831 .table("users")
2832 .set("name", "'Bob'")
2833 .where_clause("id = 1")
2834 .build();
2835 assert!(sql.starts_with("UPDATE `users` SET"));
2836 assert!(sql.contains("`name` = 'Bob'"));
2837 assert!(sql.contains("WHERE id = 1"));
2838 }
2839
2840 #[test]
2841 fn test_update_multiple_sets() {
2842 let sql = Query::update()
2843 .table("users")
2844 .sets(&[("name", "'Bob'"), ("age", "30")])
2845 .where_clause("id = 1")
2846 .build();
2847 assert!(sql.contains("`name` = 'Bob', `age` = 30"));
2848 }
2849
2850 #[test]
2851 fn test_update_no_where() {
2852 let sql = Query::update()
2853 .table("users")
2854 .set("status", "'active'")
2855 .build();
2856 assert!(sql.contains("UPDATE `users` SET `status` = 'active'"));
2857 assert!(!sql.contains("WHERE"));
2858 }
2859
2860 #[test]
2861 fn test_update_empty_returns_empty() {
2862 let sql = Query::update().table("users").build();
2863 assert_eq!(sql, "");
2864 }
2865
2866 #[test]
2867 fn test_update_with_dialect() {
2868 let sql = Query::update()
2869 .table("users")
2870 .set("name", "'Bob'")
2871 .build_with_dialect(DbType::PostgreSQL);
2872 assert!(sql.contains("\"users\""));
2873 assert!(sql.contains("\"name\""));
2874 }
2875
2876 #[test]
2879 fn test_delete_basic() {
2880 let sql = Query::delete()
2881 .from_table("users")
2882 .where_clause("id = 1")
2883 .build();
2884 assert!(sql.starts_with("DELETE FROM `users`"));
2885 assert!(sql.contains("WHERE id = 1"));
2886 }
2887
2888 #[test]
2889 fn test_delete_no_where() {
2890 let sql = Query::delete().from_table("users").build();
2891 assert!(sql.contains("DELETE FROM `users`"));
2892 assert!(!sql.contains("WHERE"));
2893 }
2894
2895 #[test]
2896 fn test_delete_multiple_wheres() {
2897 let sql = Query::delete()
2898 .from_table("users")
2899 .where_clause("id > 100")
2900 .where_clause("status = 'inactive'")
2901 .build();
2902 assert!(sql.contains("WHERE id > 100 AND status = 'inactive'"));
2903 }
2904
2905 #[test]
2906 fn test_delete_empty_returns_empty() {
2907 let sql = Query::delete().build();
2908 assert_eq!(sql, "");
2909 }
2910
2911 #[test]
2912 fn test_delete_with_dialect() {
2913 let sql = Query::delete()
2914 .from_table("users")
2915 .where_clause("id = 1")
2916 .build_with_dialect(DbType::PostgreSQL);
2917 assert!(sql.contains("\"users\""));
2918 }
2919
2920 #[test]
2923 fn test_full_crud_flow() {
2924 let insert = Query::insert()
2926 .into_table("users")
2927 .value("name", "'Alice'")
2928 .value("age", "30")
2929 .build();
2930 assert!(insert.contains("INSERT INTO"));
2931
2932 let select = Query::select()
2934 .column("id")
2935 .column("name")
2936 .from("users")
2937 .where_clause("age > 18")
2938 .order_by("id", true)
2939 .limit(10)
2940 .build(DbType::MySQL);
2941 assert!(select.contains("SELECT"));
2942 assert!(select.contains("FROM"));
2943 assert!(select.contains("WHERE"));
2944 assert!(select.contains("ORDER BY"));
2945 assert!(select.contains("LIMIT"));
2946
2947 let update = Query::update()
2949 .table("users")
2950 .set("name", "'Bob'")
2951 .where_clause("id = 1")
2952 .build();
2953 assert!(update.contains("UPDATE"));
2954 assert!(update.contains("SET"));
2955 assert!(update.contains("WHERE"));
2956
2957 let delete = Query::delete()
2959 .from_table("users")
2960 .where_clause("id = 1")
2961 .build();
2962 assert!(delete.contains("DELETE FROM"));
2963 }
2964
2965 #[test]
2966 fn test_complex_select_query() {
2967 let sql = Query::select()
2968 .distinct()
2969 .columns(&["u.id", "u.name", "o.total"])
2970 .from("users u")
2971 .inner_join("orders o", "u.id = o.user_id")
2972 .where_clause("u.status = 'active'")
2973 .where_clause("o.total > 100")
2974 .group_by("u.id")
2975 .having("SUM(o.total) > 1000")
2976 .order_by("u.id", true)
2977 .limit(20)
2978 .offset(40)
2979 .build(DbType::MySQL);
2980
2981 assert!(sql.contains("SELECT DISTINCT"));
2982 assert!(sql.contains("INNER JOIN `orders` o"));
2983 assert!(sql.contains("WHERE u.status = 'active' AND o.total > 100"));
2984 assert!(sql.contains("GROUP BY"));
2985 assert!(sql.contains("HAVING SUM(o.total) > 1000"));
2986 assert!(sql.contains("ORDER BY `u`.`id` ASC"));
2987 assert!(sql.contains("LIMIT 20"));
2988 assert!(sql.contains("OFFSET 40"));
2989 }
2990
2991 #[test]
2994 #[should_panic(expected = "SQL injection detected")]
2995 fn test_select_where_rejects_semicolon_drop() {
2996 let _ = Query::select()
2997 .column("id")
2998 .from("users")
2999 .where_clause("1=1; DROP TABLE users")
3000 .build(DbType::MySQL);
3001 }
3002
3003 #[test]
3004 #[should_panic(expected = "SQL injection detected")]
3005 fn test_select_where_rejects_semicolon_space_drop() {
3006 let _ = Query::select()
3007 .column("id")
3008 .from("users")
3009 .where_clause("1=1; DROP TABLE users")
3010 .build(DbType::MySQL);
3011 }
3012
3013 #[test]
3014 #[should_panic(expected = "SQL injection detected")]
3015 fn test_select_where_rejects_line_comment() {
3016 let _ = Query::select()
3017 .column("id")
3018 .from("users")
3019 .where_clause("id = 1 -- DROP TABLE users")
3020 .build(DbType::MySQL);
3021 }
3022
3023 #[test]
3024 #[should_panic(expected = "SQL injection detected")]
3025 fn test_select_where_rejects_block_comment() {
3026 let _ = Query::select()
3027 .column("id")
3028 .from("users")
3029 .where_clause("id = 1 /* comment */ OR 1=1")
3030 .build(DbType::MySQL);
3031 }
3032
3033 #[test]
3034 #[should_panic(expected = "SQL injection detected")]
3035 fn test_update_where_rejects_delete() {
3036 let _ = Query::update()
3037 .table("users")
3038 .set("name", "'x'")
3039 .where_clause("1=1; DELETE FROM users")
3040 .build();
3041 }
3042
3043 #[test]
3044 #[should_panic(expected = "SQL injection detected")]
3045 fn test_update_where_rejects_line_comment() {
3046 let _ = Query::update()
3047 .table("users")
3048 .set("name", "'x'")
3049 .where_clause("id = 1 -- bypass")
3050 .build();
3051 }
3052
3053 #[test]
3054 #[should_panic(expected = "SQL injection detected")]
3055 fn test_delete_where_rejects_drop() {
3056 let _ = Query::delete()
3057 .from_table("users")
3058 .where_clause("1=1; DROP TABLE users")
3059 .build();
3060 }
3061
3062 #[test]
3063 #[should_panic(expected = "SQL injection detected")]
3064 fn test_delete_where_rejects_block_comment() {
3065 let _ = Query::delete()
3066 .from_table("users")
3067 .where_clause("id = 1 /* */ OR 1=1")
3068 .build();
3069 }
3070
3071 #[test]
3072 #[should_panic(expected = "SQL injection detected")]
3073 fn test_delete_where_rejects_line_comment() {
3074 let _ = Query::delete()
3075 .from_table("users")
3076 .where_clause("id = 1--")
3077 .build();
3078 }
3079
3080 #[test]
3081 fn test_safe_where_clauses_pass() {
3082 let sql_str = Query::select()
3084 .column("id")
3085 .from("users")
3086 .where_clause("age > 18")
3087 .where_clause("name = 'Alice;Bob'") .where_clause("id IN (1, 2, 3)")
3089 .where_clause("created_at > '2026-01-01'")
3090 .build(DbType::MySQL);
3091 assert!(!sql_str.is_empty(), "SELECT SQL 不应为空");
3092 assert!(sql_str.contains("age > 18"), "SELECT 应包含 age > 18 条件");
3093 assert!(
3094 sql_str.contains("name = 'Alice;Bob'"),
3095 "SELECT 应包含 name 条件(含分号字面量)"
3096 );
3097 assert!(sql_str.contains("id IN (1, 2, 3)"), "SELECT 应包含 IN 子句");
3098 assert!(
3099 sql_str.contains("created_at > '2026-01-01'"),
3100 "SELECT 应包含日期条件"
3101 );
3102
3103 let sql_str = Query::update()
3104 .table("users")
3105 .set("name", "'x'")
3106 .where_clause("id = 1")
3107 .build();
3108 assert!(!sql_str.is_empty(), "UPDATE SQL 不应为空");
3109 assert!(sql_str.contains("UPDATE"), "应为 UPDATE 语句");
3110 assert!(sql_str.contains("WHERE"), "UPDATE 应包含 WHERE 子句");
3111 assert!(sql_str.contains("id = 1"), "UPDATE WHERE 应包含 id = 1");
3112
3113 let sql_str = Query::delete()
3114 .from_table("users")
3115 .where_clause("id = 1")
3116 .build();
3117 assert!(!sql_str.is_empty(), "DELETE SQL 不应为空");
3118 assert!(sql_str.contains("DELETE"), "应为 DELETE 语句");
3119 assert!(sql_str.contains("WHERE"), "DELETE 应包含 WHERE 子句");
3120 assert!(sql_str.contains("id = 1"), "DELETE WHERE 应包含 id = 1");
3121 }
3122
3123 #[test]
3127 #[should_panic(expected = "SQL injection detected")]
3128 fn test_mutant_block_comment_open_only() {
3129 let _ = Query::select()
3132 .column("id")
3133 .from("users")
3134 .where_clause("id = 1 /* OR 1=1")
3135 .build(DbType::MySQL);
3136 }
3137
3138 #[test]
3139 #[should_panic(expected = "SQL injection detected")]
3140 fn test_mutant_block_comment_close_only() {
3141 let _ = Query::select()
3143 .column("id")
3144 .from("users")
3145 .where_clause("id = 1 */")
3146 .build(DbType::MySQL);
3147 }
3148
3149 #[test]
3150 fn test_mutant_insert_dialect_table_no_columns_returns_empty() {
3151 let sql = Query::insert()
3153 .into_table("users")
3154 .build_with_dialect(DbType::MySQL);
3155 assert_eq!(sql, "", "有表无列时应返回空字符串");
3156 }
3157
3158 #[test]
3159 fn test_mutant_update_dialect_table_no_sets_returns_empty() {
3160 let sql = Query::update()
3162 .table("users")
3163 .build_with_dialect(DbType::MySQL);
3164 assert_eq!(sql, "", "有表无 SET 时应返回空字符串");
3165 }
3166
3167 #[test]
3168 fn test_mutant_update_dialect_no_table_with_sets_returns_empty() {
3169 let sql = Query::update()
3171 .set("name", "'x'")
3172 .build_with_dialect(DbType::MySQL);
3173 assert_eq!(sql, "", "无表有 SET 时应返回空字符串");
3174 }
3175
3176 #[test]
3177 fn test_mutant_delete_dialect_no_table_returns_empty() {
3178 let sql = Query::delete()
3180 .where_clause("id = 1")
3181 .build_with_dialect(DbType::MySQL);
3182 assert_eq!(sql, "", "无表时应返回空字符串");
3183 }
3184
3185 #[test]
3186 fn test_mutant_select_right_join() {
3187 let sql = Query::select()
3189 .column("u.id")
3190 .from("users u")
3191 .right_join("orders o", "u.id = o.user_id")
3192 .build(DbType::MySQL);
3193 assert!(sql.contains("RIGHT JOIN `orders` o ON u.id = o.user_id"));
3194 }
3195
3196 #[test]
3197 fn test_mutant_all_columns_with_extra() {
3198 let sql = Query::select()
3201 .all_columns()
3202 .column("extra")
3203 .from("users")
3204 .build(DbType::MySQL);
3205 assert!(
3206 sql.contains("SELECT *, `extra` FROM `users`"),
3207 "all_columns + column 应在 SELECT 列表中同时包含 * 和 extra,实际: {sql}"
3208 );
3209 }
3210
3211 #[test]
3212 fn test_mutant_update_dialect_no_where_no_where_clause() {
3213 let sql = Query::update()
3216 .table("users")
3217 .set("name", "'x'")
3218 .build_with_dialect(DbType::MySQL);
3219 assert!(
3220 !sql.contains("WHERE"),
3221 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3222 );
3223 }
3224
3225 #[test]
3226 fn test_mutant_delete_dialect_no_where_no_where_clause() {
3227 let sql = Query::delete()
3229 .from_table("users")
3230 .build_with_dialect(DbType::MySQL);
3231 assert!(
3232 !sql.contains("WHERE"),
3233 "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3234 );
3235 }
3236
3237 #[test]
3242 fn test_cte_single_with_clause() {
3243 let sql = Query::select()
3244 .column("id")
3245 .column("name")
3246 .from("active_users")
3247 .with_cte(
3248 "active_users",
3249 "SELECT * FROM users WHERE status = 'active'",
3250 )
3251 .build(DbType::MySQL);
3252 assert!(sql.starts_with("WITH active_users AS ("));
3253 assert!(sql.contains("SELECT * FROM users WHERE status = 'active'"));
3254 assert!(sql.contains("SELECT `id`, `name` FROM `active_users`"));
3255 }
3256
3257 #[test]
3258 fn test_cte_multiple_with_clauses() {
3259 let sql = Query::select()
3260 .column("id")
3261 .from("combined")
3262 .with_cte("a", "SELECT id FROM table_a")
3263 .with_cte("b", "SELECT id FROM table_b")
3264 .with_cte("combined", "SELECT id FROM a UNION SELECT id FROM b")
3265 .build(DbType::MySQL);
3266 assert!(sql.starts_with(
3267 "WITH a AS (SELECT id FROM table_a), b AS (SELECT id FROM table_b), combined AS ("
3268 ));
3269 }
3270
3271 #[test]
3272 fn test_cte_recursive_with_clause() {
3273 let sql = Query::select()
3274 .column("id")
3275 .column("parent_id")
3276 .from("tree")
3277 .with_recursive_cte("tree", "SELECT id, parent_id FROM nodes WHERE id = 1")
3278 .build(DbType::MySQL);
3279 assert!(sql.starts_with("WITH RECURSIVE tree AS ("));
3280 }
3281
3282 #[test]
3283 fn test_cte_no_cte_no_with_prefix() {
3284 let sql = Query::select()
3285 .column("id")
3286 .from("users")
3287 .build(DbType::MySQL);
3288 assert!(!sql.contains("WITH"));
3289 assert!(sql.starts_with("SELECT"));
3290 }
3291
3292 #[test]
3295 fn test_window_function_raw_expr() {
3296 let sql = Query::select()
3297 .column("id")
3298 .column("salary")
3299 .from("employees")
3300 .window_function("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn")
3301 .build(DbType::MySQL);
3302 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3303 }
3304
3305 #[test]
3306 fn test_row_number_helper_with_partition() {
3307 let sql = Query::select()
3308 .column("name")
3309 .column("dept")
3310 .from("employees")
3311 .row_number("dept", "salary DESC", "row_num")
3312 .build(DbType::MySQL);
3313 assert!(
3314 sql.contains("ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS row_num")
3315 );
3316 }
3317
3318 #[test]
3319 fn test_row_number_helper_without_partition() {
3320 let sql = Query::select()
3321 .column("name")
3322 .from("employees")
3323 .row_number("", "salary DESC", "rn")
3324 .build(DbType::MySQL);
3325 assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3326 assert!(!sql.contains("PARTITION BY"));
3327 }
3328
3329 #[test]
3330 fn test_rank_helper() {
3331 let sql = Query::select()
3332 .column("name")
3333 .from("scores")
3334 .rank("", "score DESC", "rank_num")
3335 .build(DbType::MySQL);
3336 assert!(sql.contains("RANK() OVER (ORDER BY score DESC) AS rank_num"));
3337 }
3338
3339 #[test]
3340 fn test_dense_rank_helper_with_partition() {
3341 let sql = Query::select()
3342 .column("name")
3343 .from("scores")
3344 .dense_rank("class", "score DESC", "dr")
3345 .build(DbType::MySQL);
3346 assert!(sql.contains("DENSE_RANK() OVER (PARTITION BY class ORDER BY score DESC) AS dr"));
3347 }
3348
3349 #[test]
3350 fn test_multiple_window_functions() {
3351 let sql = Query::select()
3352 .column("name")
3353 .column("salary")
3354 .from("employees")
3355 .row_number("dept", "salary DESC", "rn")
3356 .rank("dept", "salary DESC", "rk")
3357 .dense_rank("dept", "salary DESC", "dr")
3358 .build(DbType::MySQL);
3359 assert!(sql.contains("ROW_NUMBER()"));
3360 assert!(sql.contains("RANK()"));
3361 assert!(sql.contains("DENSE_RANK()"));
3362 }
3363
3364 #[test]
3365 fn test_window_function_with_cte_combined() {
3366 let sql = Query::select()
3367 .column("name")
3368 .from("ranked")
3369 .with_cte(
3370 "ranked",
3371 "SELECT name, ROW_NUMBER() OVER (ORDER BY salary) AS rn FROM employees",
3372 )
3373 .where_clause("rn <= 10")
3374 .build(DbType::MySQL);
3375 assert!(sql.starts_with("WITH ranked AS ("));
3376 assert!(sql.contains("FROM `ranked`"));
3377 assert!(sql.contains("WHERE rn <= 10"));
3378 }
3379
3380 #[test]
3383 fn test_for_update_basic() {
3384 let sql = Query::select()
3385 .column("id")
3386 .column("balance")
3387 .from("accounts")
3388 .where_clause("id = 1")
3389 .for_update()
3390 .build(DbType::MySQL);
3391 assert!(sql.ends_with(" FOR UPDATE"));
3392 assert!(sql.contains("WHERE id = 1"));
3393 }
3394
3395 #[test]
3396 fn test_for_update_with_nowait() {
3397 let sql = Query::select()
3398 .column("id")
3399 .from("accounts")
3400 .where_clause("id = 1")
3401 .for_update_with_options("NOWAIT")
3402 .build(DbType::MySQL);
3403 assert!(sql.ends_with(" FOR UPDATE NOWAIT"));
3404 }
3405
3406 #[test]
3407 fn test_for_update_with_skip_locked() {
3408 let sql = Query::select()
3409 .column("id")
3410 .from("accounts")
3411 .where_clause("id = 1")
3412 .for_update_with_options("SKIP LOCKED")
3413 .build(DbType::MySQL);
3414 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3415 }
3416
3417 #[test]
3418 fn test_for_update_with_limit_and_order() {
3419 let sql = Query::select()
3420 .column("id")
3421 .from("jobs")
3422 .order_by("priority", false)
3423 .limit(1)
3424 .for_update_with_options("SKIP LOCKED")
3425 .build(DbType::MySQL);
3426 assert!(sql.contains("ORDER BY `priority` DESC"));
3427 assert!(sql.contains("LIMIT 1"));
3428 assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3429 }
3430
3431 #[test]
3432 fn test_no_for_update_by_default() {
3433 let sql = Query::select()
3434 .column("id")
3435 .from("users")
3436 .build(DbType::MySQL);
3437 assert!(!sql.contains("FOR UPDATE"));
3438 }
3439
3440 #[test]
3443 fn test_set_operator_as_sql() {
3444 assert_eq!(SetOperator::Union.as_sql(), "UNION");
3445 assert_eq!(SetOperator::UnionAll.as_sql(), "UNION ALL");
3446 assert_eq!(SetOperator::Intersect.as_sql(), "INTERSECT");
3447 assert_eq!(SetOperator::Except.as_sql(), "EXCEPT");
3448 }
3449
3450 #[test]
3451 fn test_union_basic() {
3452 let q1 = Query::select().column("id").from("active_users");
3453 let q2 = Query::select().column("id").from("pending_users");
3454 let sql = q1.union(q2).build(DbType::MySQL);
3455 assert!(sql.contains("SELECT `id` FROM `active_users`"));
3456 assert!(sql.contains(" UNION "));
3457 assert!(sql.contains("SELECT `id` FROM `pending_users`"));
3458 }
3459
3460 #[test]
3461 fn test_union_all_basic() {
3462 let q1 = Query::select().column("id").from("table_a");
3463 let q2 = Query::select().column("id").from("table_b");
3464 let sql = q1.union_all(q2).build(DbType::MySQL);
3465 assert!(sql.contains(" UNION ALL "));
3466 }
3467
3468 #[test]
3469 fn test_intersect_basic() {
3470 let q1 = Query::select().column("id").from("table_a");
3471 let q2 = Query::select().column("id").from("table_b");
3472 let sql = q1.intersect(q2).build(DbType::MySQL);
3473 assert!(sql.contains(" INTERSECT "));
3474 }
3475
3476 #[test]
3477 fn test_except_basic() {
3478 let q1 = Query::select().column("id").from("table_a");
3479 let q2 = Query::select().column("id").from("table_b");
3480 let sql = q1.except(q2).build(DbType::MySQL);
3481 assert!(sql.contains(" EXCEPT "));
3482 }
3483
3484 #[test]
3485 fn test_union_chained_multiple() {
3486 let q1 = Query::select().column("id").from("t1");
3487 let q2 = Query::select().column("id").from("t2");
3488 let q3 = Query::select().column("id").from("t3");
3489 let sql = q1.union(q2).union(q3).build(DbType::MySQL);
3490 assert_eq!(sql.matches("UNION").count(), 2);
3491 }
3492
3493 #[test]
3494 fn test_union_mixed_operators() {
3495 let q1 = Query::select().column("id").from("t1");
3496 let q2 = Query::select().column("id").from("t2");
3497 let q3 = Query::select().column("id").from("t3");
3498 let sql = q1.union(q2).intersect(q3).build(DbType::MySQL);
3499 assert!(sql.contains(" UNION "));
3500 assert!(sql.contains(" INTERSECT "));
3501 }
3502
3503 #[test]
3504 fn test_union_with_order_by_limit() {
3505 let q1 = Query::select().column("id").from("t1");
3506 let q2 = Query::select().column("id").from("t2");
3507 let sql = q1
3508 .union(q2)
3509 .order_by("id", true)
3510 .limit(10)
3511 .offset(5)
3512 .build(DbType::MySQL);
3513 assert!(sql.contains("ORDER BY `id` ASC"));
3514 assert!(sql.contains("LIMIT 10"));
3515 assert!(sql.contains("OFFSET 5"));
3516 }
3517
3518 #[test]
3519 fn test_union_postgres_dialect() {
3520 let q1 = Query::select().column("id").from("t1");
3521 let q2 = Query::select().column("id").from("t2");
3522 let sql = q1.union(q2).build(DbType::PostgreSQL);
3523 assert!(sql.contains("\"id\""));
3524 assert!(sql.contains(" UNION "));
3525 }
3526
3527 #[test]
3528 fn test_union_with_where_clauses() {
3529 let q1 = Query::select()
3530 .column("id")
3531 .from("active_users")
3532 .where_clause("age > 18");
3533 let q2 = Query::select()
3534 .column("id")
3535 .from("pending_users")
3536 .where_clause("age > 18");
3537 let sql = q1.union(q2).build(DbType::MySQL);
3538 assert!(sql.contains("WHERE age > 18"));
3539 assert!(sql.contains(" UNION "));
3540 }
3541
3542 #[test]
3545 fn test_cte_window_for_update_combined() {
3546 let sql = Query::select()
3548 .column("id")
3549 .column("salary")
3550 .from("ranked_salaries")
3551 .with_cte(
3552 "ranked_salaries",
3553 "SELECT id, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM employees",
3554 )
3555 .where_clause("rn = 1")
3556 .for_update()
3557 .build(DbType::MySQL);
3558 assert!(sql.starts_with("WITH ranked_salaries AS ("));
3559 assert!(sql.contains("FOR UPDATE"));
3560 assert!(sql.contains("WHERE rn = 1"));
3561 }
3562
3563 #[test]
3564 fn test_complex_window_aggregation() {
3565 let sql = Query::select()
3567 .column("user_id")
3568 .column("amount")
3569 .from("transactions")
3570 .window_function(
3571 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total",
3572 )
3573 .rank("user_id", "created_at", "tx_rank")
3574 .build(DbType::MySQL);
3575 assert!(sql.contains(
3576 "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total"
3577 ));
3578 assert!(sql.contains("RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS tx_rank"));
3579 }
3580
3581 #[test]
3584 fn test_select_where_eq_params() {
3585 use sz_orm_core::Value;
3586 let built = Query::select()
3587 .column("id")
3588 .from("users")
3589 .where_eq("age", Value::I32(18))
3590 .build_with_params(DbType::MySQL);
3591 assert!(built.sql.contains("WHERE `age` = ?"));
3592 assert_eq!(built.params.len(), 1);
3593 assert_eq!(built.params[0], Value::I32(18));
3594 }
3595
3596 #[test]
3597 fn test_select_multiple_where_params() {
3598 use sz_orm_core::Value;
3599 let built = Query::select()
3600 .column("id")
3601 .from("users")
3602 .where_eq("age", Value::I32(18))
3603 .where_eq("status", Value::String("active".to_string()))
3604 .build_with_params(DbType::MySQL);
3605 assert!(built.sql.contains("WHERE `age` = ? AND `status` = ?"));
3606 assert_eq!(built.params.len(), 2);
3607 }
3608
3609 #[test]
3610 fn test_select_or_where_eq_params() {
3611 use sz_orm_core::Value;
3612 let built = Query::select()
3613 .column("id")
3614 .from("users")
3615 .where_eq("age", Value::I32(18))
3616 .or_where_eq("role", Value::String("admin".to_string()))
3617 .build_with_params(DbType::MySQL);
3618 assert!(built.sql.contains("WHERE `age` = ? OR `role` = ?"));
3619 assert_eq!(built.params.len(), 2);
3620 }
3621
3622 #[test]
3623 fn test_select_where_in_params() {
3624 use sz_orm_core::Value;
3625 let built = Query::select()
3626 .column("id")
3627 .from("users")
3628 .where_in("id", vec![Value::I32(1), Value::I32(2), Value::I32(3)])
3629 .build_with_params(DbType::MySQL);
3630 assert!(built.sql.contains("WHERE `id` IN (?, ?, ?)"));
3631 assert_eq!(built.params.len(), 3);
3632 }
3633
3634 #[test]
3635 fn test_select_where_in_empty() {
3636 let built = Query::select()
3637 .column("id")
3638 .from("users")
3639 .where_in("id", vec![])
3640 .build_with_params(DbType::MySQL);
3641 assert!(built.sql.contains("WHERE 1 = 0"));
3642 assert_eq!(built.params.len(), 0);
3643 }
3644
3645 #[test]
3646 fn test_select_where_between_params() {
3647 use sz_orm_core::Value;
3648 let built = Query::select()
3649 .column("id")
3650 .from("users")
3651 .where_between("age", Value::I32(18), Value::I32(65))
3652 .build_with_params(DbType::MySQL);
3653 assert!(built.sql.contains("WHERE `age` BETWEEN ? AND ?"));
3654 assert_eq!(built.params.len(), 2);
3655 }
3656
3657 #[test]
3658 fn test_select_where_null_params() {
3659 let built = Query::select()
3660 .column("id")
3661 .from("users")
3662 .where_null("deleted_at")
3663 .build_with_params(DbType::MySQL);
3664 assert!(built.sql.contains("WHERE `deleted_at` IS NULL"));
3665 assert_eq!(built.params.len(), 0);
3666 }
3667
3668 #[test]
3669 fn test_select_where_not_null_params() {
3670 let built = Query::select()
3671 .column("id")
3672 .from("users")
3673 .where_not_null("email")
3674 .build_with_params(DbType::MySQL);
3675 assert!(built.sql.contains("WHERE `email` IS NOT NULL"));
3676 }
3677
3678 #[test]
3679 fn test_select_mixed_raw_and_param_where() {
3680 use sz_orm_core::Value;
3681 let built = Query::select()
3682 .column("id")
3683 .from("users")
3684 .where_clause("age > 18")
3685 .where_eq("status", Value::String("active".to_string()))
3686 .build_with_params(DbType::MySQL);
3687 assert!(built.sql.contains("WHERE age > 18 AND `status` = ?"));
3688 assert_eq!(built.params.len(), 1);
3689 }
3690
3691 #[test]
3692 fn test_select_param_where_with_order_limit() {
3693 use sz_orm_core::Value;
3694 let built = Query::select()
3695 .column("id")
3696 .from("users")
3697 .where_eq("age", Value::I32(18))
3698 .order_by("id", true)
3699 .limit(10)
3700 .build_with_params(DbType::MySQL);
3701 assert!(built.sql.contains("WHERE `age` = ?"));
3702 assert!(built.sql.contains("ORDER BY `id` ASC"));
3703 assert!(built.sql.contains("LIMIT 10"));
3704 }
3705
3706 #[test]
3707 fn test_select_param_where_postgres_dialect() {
3708 use sz_orm_core::Value;
3709 let built = Query::select()
3710 .column("id")
3711 .from("users")
3712 .where_eq("age", Value::I32(18))
3713 .build_with_params(DbType::PostgreSQL);
3714 assert!(built.sql.contains("WHERE \"age\" = ?"));
3715 }
3716
3717 #[test]
3718 fn test_select_param_where_injection_safe() {
3719 use sz_orm_core::Value;
3721 let malicious = "'; DROP TABLE users; --".to_string();
3722 let built = Query::select()
3723 .column("id")
3724 .from("users")
3725 .where_eq("name", Value::String(malicious.clone()))
3726 .build_with_params(DbType::MySQL);
3727 assert!(!built.sql.contains("DROP TABLE"));
3729 assert!(!built.sql.contains(";"));
3730 assert_eq!(built.params.len(), 1);
3732 assert_eq!(built.params[0], Value::String(malicious));
3733 }
3734
3735 #[test]
3736 fn test_update_where_eq_params() {
3737 use sz_orm_core::Value;
3738 let built = Query::update()
3739 .table("users")
3740 .set("name", "'Bob'")
3741 .where_eq("id", Value::I64(1))
3742 .build_with_params(DbType::MySQL);
3743 assert!(built.sql.contains("UPDATE `users` SET"));
3744 assert!(built.sql.contains("WHERE `id` = ?"));
3745 assert_eq!(built.params.len(), 1);
3746 }
3747
3748 #[test]
3749 fn test_update_where_in_params() {
3750 use sz_orm_core::Value;
3751 let built = Query::update()
3752 .table("users")
3753 .set("status", "'inactive'")
3754 .where_in("id", vec![Value::I64(1), Value::I64(2)])
3755 .build_with_params(DbType::MySQL);
3756 assert!(built.sql.contains("WHERE `id` IN (?, ?)"));
3757 assert_eq!(built.params.len(), 2);
3758 }
3759
3760 #[test]
3761 fn test_delete_where_eq_params() {
3762 use sz_orm_core::Value;
3763 let built = Query::delete()
3764 .from_table("users")
3765 .where_eq("id", Value::I64(1))
3766 .build_with_params(DbType::MySQL);
3767 assert!(built.sql.contains("DELETE FROM `users`"));
3768 assert!(built.sql.contains("WHERE `id` = ?"));
3769 assert_eq!(built.params.len(), 1);
3770 }
3771
3772 #[test]
3773 fn test_delete_where_between_params() {
3774 use sz_orm_core::Value;
3775 let built = Query::delete()
3776 .from_table("logs")
3777 .where_between(
3778 "created_at",
3779 Value::String("2020-01-01".to_string()),
3780 Value::String("2020-12-31".to_string()),
3781 )
3782 .build_with_params(DbType::MySQL);
3783 assert!(built.sql.contains("WHERE `created_at` BETWEEN ? AND ?"));
3784 assert_eq!(built.params.len(), 2);
3785 }
3786
3787 #[test]
3790 fn test_from_subquery_basic() {
3791 let inner = Query::select()
3792 .column("id")
3793 .column("amount")
3794 .from("orders")
3795 .build(DbType::MySQL);
3796 let sql = Query::select()
3797 .column("id")
3798 .from_subquery(&inner, "t")
3799 .build(DbType::MySQL);
3800 assert!(
3801 sql.contains("FROM (SELECT `id`, `amount` FROM `orders`) AS `t`"),
3802 "FROM 子查询应渲染为 `FROM (subquery) AS alias`,实际: {sql}"
3803 );
3804 }
3805
3806 #[test]
3807 fn test_from_subquery_postgres_dialect() {
3808 let inner = Query::select()
3809 .column("id")
3810 .from("orders")
3811 .build(DbType::PostgreSQL);
3812 let sql = Query::select()
3813 .column("id")
3814 .from_subquery(&inner, "t")
3815 .build(DbType::PostgreSQL);
3816 assert!(
3817 sql.contains("FROM (SELECT \"id\" FROM \"orders\") AS \"t\""),
3818 "PG 方言下别名应使用双引号,实际: {sql}"
3819 );
3820 }
3821
3822 #[test]
3823 fn test_from_subquery_with_where_and_order() {
3824 let inner = Query::select()
3825 .column("id")
3826 .column("amount")
3827 .from("orders")
3828 .where_clause("amount > 100")
3829 .build(DbType::MySQL);
3830 let sql = Query::select()
3831 .column("id")
3832 .column("amount")
3833 .from_subquery(&inner, "t")
3834 .where_clause("t.amount > 200")
3835 .order_by("id", true)
3836 .build(DbType::MySQL);
3837 assert!(
3838 sql.contains("FROM (SELECT `id`, `amount` FROM `orders` WHERE amount > 100) AS `t`")
3839 );
3840 assert!(sql.contains("WHERE t.amount > 200"));
3841 assert!(sql.contains("ORDER BY `id` ASC"));
3842 }
3843
3844 #[test]
3845 fn test_from_subquery_with_params() {
3846 use sz_orm_core::Value;
3847 let inner = Query::select()
3848 .column("id")
3849 .from("orders")
3850 .where_eq("amount", Value::I32(100))
3851 .build_with_params(DbType::MySQL);
3852 let built = Query::select()
3853 .column("id")
3854 .from_subquery(&inner.sql, "t")
3855 .where_eq("t.id", Value::I64(1))
3856 .build_with_params(DbType::MySQL);
3857 assert!(built
3859 .sql
3860 .contains("FROM (SELECT `id` FROM `orders` WHERE `amount` = ?) AS `t`"));
3861 assert!(built.sql.contains("WHERE `t`.`id` = ?"));
3862 assert_eq!(built.params.len(), 1);
3864 }
3865
3866 #[test]
3867 fn test_from_subquery_overrides_from_table() {
3868 let sql = Query::select()
3870 .column("id")
3871 .from("users")
3872 .from_subquery("SELECT id FROM orders", "t")
3873 .build(DbType::MySQL);
3874 assert!(sql.contains("FROM (SELECT id FROM orders) AS `t`"));
3875 assert!(!sql.contains("FROM `users`"));
3876 }
3877
3878 #[test]
3879 fn test_from_table_overrides_from_subquery() {
3880 let sql = Query::select()
3882 .column("id")
3883 .from_subquery("SELECT id FROM orders", "t")
3884 .from("users")
3885 .build(DbType::MySQL);
3886 assert!(sql.contains("FROM `users`"));
3887 assert!(!sql.contains("FROM ("));
3888 }
3889
3890 #[test]
3891 fn test_from_subquery_no_from_when_neither_set() {
3892 let sql = Query::select().column("id").build(DbType::MySQL);
3893 assert!(!sql.contains("FROM"));
3894 }
3895
3896 #[test]
3899 fn test_insert_returning_postgres() {
3900 let sql = Query::insert()
3901 .into_table("users")
3902 .value("name", "'Alice'")
3903 .returning(&["id", "created_at"])
3904 .build_with_dialect(DbType::PostgreSQL);
3905 assert!(
3906 sql.contains("RETURNING \"id\", \"created_at\""),
3907 "PG 方言应渲染 RETURNING,实际: {sql}"
3908 );
3909 }
3910
3911 #[test]
3912 fn test_insert_returning_sqlite() {
3913 let sql = Query::insert()
3914 .into_table("users")
3915 .value("name", "'Alice'")
3916 .returning(&["id"])
3917 .build_with_dialect(DbType::Sqlite);
3918 assert!(
3919 sql.contains("RETURNING \"id\""),
3920 "SQLite 方言应渲染 RETURNING,实际: {sql}"
3921 );
3922 }
3923
3924 #[test]
3925 fn test_insert_returning_all() {
3926 let sql = Query::insert()
3927 .into_table("users")
3928 .value("name", "'Alice'")
3929 .returning_all()
3930 .build_with_dialect(DbType::PostgreSQL);
3931 assert!(
3932 sql.contains("RETURNING *"),
3933 "returning_all 应渲染 `RETURNING *`,实际: {sql}"
3934 );
3935 }
3936
3937 #[test]
3938 fn test_insert_returning_mysql_skipped() {
3939 let sql = Query::insert()
3941 .into_table("users")
3942 .value("name", "'Alice'")
3943 .returning(&["id"])
3944 .build_with_dialect(DbType::MySQL);
3945 assert!(
3946 !sql.contains("RETURNING"),
3947 "MySQL 方言应跳过 RETURNING,实际: {sql}"
3948 );
3949 }
3950
3951 #[test]
3952 fn test_insert_returning_with_upsert_postgres() {
3953 let sql = Query::insert()
3955 .into_table("users")
3956 .value("id", "1")
3957 .value("name", "'Alice'")
3958 .on_conflict_do_update(&["id"], &[("name", "EXCLUDED.name")])
3959 .returning(&["id", "name"])
3960 .build_with_dialect(DbType::PostgreSQL);
3961 assert!(sql.contains("ON CONFLICT"));
3962 assert!(sql.contains("RETURNING"));
3963 }
3964
3965 #[test]
3966 fn test_insert_returning_build_mysql_style_skipped() {
3967 let sql = Query::insert()
3969 .into_table("users")
3970 .value("name", "'Alice'")
3971 .returning(&["id"])
3972 .build();
3973 assert!(!sql.contains("RETURNING"));
3974 }
3975
3976 #[test]
3977 fn test_update_returning_postgres() {
3978 let sql = Query::update()
3979 .table("users")
3980 .set("status", "'active'")
3981 .where_clause("id = 1")
3982 .returning(&["id", "status"])
3983 .build_with_dialect(DbType::PostgreSQL);
3984 assert!(sql.contains("RETURNING \"id\", \"status\""));
3985 assert!(sql.contains("WHERE id = 1"));
3986 }
3987
3988 #[test]
3989 fn test_update_returning_sqlite() {
3990 let sql = Query::update()
3991 .table("users")
3992 .set("status", "'active'")
3993 .returning(&["id"])
3994 .build_with_dialect(DbType::Sqlite);
3995 assert!(sql.contains("RETURNING \"id\""));
3996 }
3997
3998 #[test]
3999 fn test_update_returning_mysql_skipped() {
4000 let sql = Query::update()
4001 .table("users")
4002 .set("status", "'active'")
4003 .returning(&["id"])
4004 .build_with_dialect(DbType::MySQL);
4005 assert!(!sql.contains("RETURNING"));
4006 }
4007
4008 #[test]
4009 fn test_update_returning_with_params() {
4010 use sz_orm_core::Value;
4011 let built = Query::update()
4012 .table("users")
4013 .set("status", "'active'")
4014 .where_eq("id", Value::I64(1))
4015 .returning(&["id", "status"])
4016 .build_with_params(DbType::PostgreSQL);
4017 assert!(built.sql.contains("WHERE \"id\" = ?"));
4018 assert!(built.sql.contains("RETURNING \"id\", \"status\""));
4019 assert_eq!(built.params.len(), 1);
4020 }
4021
4022 #[test]
4023 fn test_delete_returning_postgres() {
4024 let sql = Query::delete()
4025 .from_table("users")
4026 .where_clause("id = 1")
4027 .returning(&["id", "name"])
4028 .build_with_dialect(DbType::PostgreSQL);
4029 assert!(sql.contains("RETURNING \"id\", \"name\""));
4030 assert!(sql.contains("WHERE id = 1"));
4031 }
4032
4033 #[test]
4034 fn test_delete_returning_sqlite() {
4035 let sql = Query::delete()
4036 .from_table("users")
4037 .where_clause("id = 1")
4038 .returning(&["id"])
4039 .build_with_dialect(DbType::Sqlite);
4040 assert!(sql.contains("RETURNING \"id\""));
4041 }
4042
4043 #[test]
4044 fn test_delete_returning_mysql_skipped() {
4045 let sql = Query::delete()
4046 .from_table("users")
4047 .where_clause("id = 1")
4048 .returning(&["id"])
4049 .build_with_dialect(DbType::MySQL);
4050 assert!(!sql.contains("RETURNING"));
4051 }
4052
4053 #[test]
4054 fn test_delete_returning_with_params() {
4055 use sz_orm_core::Value;
4056 let built = Query::delete()
4057 .from_table("users")
4058 .where_eq("id", Value::I64(1))
4059 .returning(&["id", "name"])
4060 .build_with_params(DbType::PostgreSQL);
4061 assert!(built.sql.contains("WHERE \"id\" = ?"));
4062 assert!(built.sql.contains("RETURNING \"id\", \"name\""));
4063 assert_eq!(built.params.len(), 1);
4064 }
4065
4066 #[test]
4067 fn test_returning_star_not_quoted() {
4068 let sql = Query::insert()
4070 .into_table("users")
4071 .value("name", "'Alice'")
4072 .returning(&["*"])
4073 .build_with_dialect(DbType::PostgreSQL);
4074 assert!(sql.contains("RETURNING *"));
4075 assert!(!sql.contains("RETURNING \"*\""));
4076 }
4077
4078 #[test]
4081 fn test_inner_join_on_column_eq() {
4082 let sql = Query::select()
4083 .column("u.id")
4084 .from("users u")
4085 .inner_join_on("orders o", "u.id", "o.user_id")
4086 .build(DbType::MySQL);
4087 assert!(
4088 sql.contains("INNER JOIN `orders` o ON `u`.`id` = `o`.`user_id`"),
4089 "列对列等值连接应渲染转义标识符,实际: {sql}"
4090 );
4091 }
4092
4093 #[test]
4094 fn test_left_join_on_column_eq() {
4095 let sql = Query::select()
4096 .column("u.id")
4097 .from("users u")
4098 .left_join_on("profiles p", "u.id", "p.user_id")
4099 .build(DbType::MySQL);
4100 assert!(sql.contains("LEFT JOIN `profiles` p ON `u`.`id` = `p`.`user_id`"));
4101 }
4102
4103 #[test]
4104 fn test_right_join_on_column_eq() {
4105 let sql = Query::select()
4106 .column("u.id")
4107 .from("users u")
4108 .right_join_on("orders o", "u.id", "o.user_id")
4109 .build(DbType::MySQL);
4110 assert!(sql.contains("RIGHT JOIN `orders` o ON `u`.`id` = `o`.`user_id`"));
4111 }
4112
4113 #[test]
4114 fn test_inner_join_on_postgres_dialect() {
4115 let sql = Query::select()
4116 .column("u.id")
4117 .from("users u")
4118 .inner_join_on("orders o", "u.id", "o.user_id")
4119 .build(DbType::PostgreSQL);
4120 assert!(
4123 sql.contains("INNER JOIN `orders` o ON \"u\".\"id\" = \"o\".\"user_id\""),
4124 "PG 方言下 ON 条件列名应使用双引号引用,实际: {sql}"
4125 );
4126 }
4127
4128 #[test]
4129 fn test_inner_join_param_binds_value() {
4130 use sz_orm_core::Value;
4131 let built = Query::select()
4132 .column("u.id")
4133 .from("users u")
4134 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4135 .build_with_params(DbType::MySQL);
4136 assert!(
4137 built
4138 .sql
4139 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"),
4140 "参数化 JOIN 应渲染 ? 占位符,实际: {}",
4141 built.sql
4142 );
4143 assert_eq!(built.params.len(), 1);
4144 assert_eq!(built.params[0], Value::String("paid".to_string()));
4145 }
4146
4147 #[test]
4148 fn test_left_join_param_binds_value() {
4149 use sz_orm_core::Value;
4150 let built = Query::select()
4151 .column("u.id")
4152 .from("users u")
4153 .left_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4154 .build_with_params(DbType::MySQL);
4155 assert!(built
4156 .sql
4157 .contains("LEFT JOIN `orders` o ON `o`.`status` = ?"));
4158 assert_eq!(built.params.len(), 1);
4159 }
4160
4161 #[test]
4162 fn test_right_join_param_binds_value() {
4163 use sz_orm_core::Value;
4164 let built = Query::select()
4165 .column("u.id")
4166 .from("users u")
4167 .right_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4168 .build_with_params(DbType::MySQL);
4169 assert!(built
4170 .sql
4171 .contains("RIGHT JOIN `orders` o ON `o`.`status` = ?"));
4172 assert_eq!(built.params.len(), 1);
4173 }
4174
4175 #[test]
4176 fn test_param_join_injection_safe() {
4177 use sz_orm_core::Value;
4179 let malicious = "'; DROP TABLE orders; --".to_string();
4180 let built = Query::select()
4181 .column("u.id")
4182 .from("users u")
4183 .inner_join_param(
4184 "orders o",
4185 "o.status",
4186 " = ?",
4187 Value::String(malicious.clone()),
4188 )
4189 .build_with_params(DbType::MySQL);
4190 assert!(!built.sql.contains("DROP TABLE"));
4192 assert!(!built.sql.contains(";"));
4193 assert_eq!(built.params.len(), 1);
4195 assert_eq!(built.params[0], Value::String(malicious));
4196 }
4197
4198 #[test]
4199 fn test_mixed_raw_and_param_join() {
4200 use sz_orm_core::Value;
4202 let built = Query::select()
4203 .column("u.id")
4204 .from("users u")
4205 .inner_join("orders o", "u.id = o.user_id")
4206 .inner_join_param(
4207 "payments p",
4208 "p.status",
4209 " = ?",
4210 Value::String("paid".into()),
4211 )
4212 .build_with_params(DbType::MySQL);
4213 assert!(built
4214 .sql
4215 .contains("INNER JOIN `orders` o ON u.id = o.user_id"));
4216 assert!(built
4217 .sql
4218 .contains("INNER JOIN `payments` p ON `p`.`status` = ?"));
4219 assert_eq!(built.params.len(), 1);
4220 }
4221
4222 #[test]
4223 fn test_param_join_with_where_params_combined() {
4224 use sz_orm_core::Value;
4226 let built = Query::select()
4227 .column("u.id")
4228 .from("users u")
4229 .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4230 .where_eq("u.age", Value::I32(18))
4231 .build_with_params(DbType::MySQL);
4232 assert!(built
4233 .sql
4234 .contains("INNER JOIN `orders` o ON `o`.`status` = ?"));
4235 assert!(built.sql.contains("WHERE `u`.`age` = ?"));
4236 assert_eq!(built.params.len(), 2);
4238 assert_eq!(built.params[0], Value::String("paid".to_string()));
4239 assert_eq!(built.params[1], Value::I32(18));
4240 }
4241}