1use crate::{Dialect, MySqlDialect};
60use crate::Value;
61use std::marker::PhantomData;
62
63pub trait Column<M>: Send + Sync + Clone {
78 fn name(&self) -> &'static str;
80
81 fn table(&self) -> &'static str;
83}
84
85#[derive(Debug, Clone)]
91pub enum WhereClause {
92 Eq(String, Value),
94 Ne(String, Value),
96 Gt(String, Value),
98 Ge(String, Value),
100 Lt(String, Value),
102 Le(String, Value),
104 Like(String, Value),
106 IsNull(String),
108 IsNotNull(String),
110 In(String, Vec<Value>),
112 NotIn(String, Vec<Value>),
114 Between(String, Value, Value),
116 Raw(String),
118}
119
120impl WhereClause {
121 fn render(&self, dialect: &dyn Dialect) -> String {
123 match self {
124 WhereClause::Eq(col, v) => format!(
126 "{} = {}",
127 dialect.quote(col),
128 v.to_param_with_dialect(dialect)
129 ),
130 WhereClause::Ne(col, v) => format!(
131 "{} != {}",
132 dialect.quote(col),
133 v.to_param_with_dialect(dialect)
134 ),
135 WhereClause::Gt(col, v) => format!(
136 "{} > {}",
137 dialect.quote(col),
138 v.to_param_with_dialect(dialect)
139 ),
140 WhereClause::Ge(col, v) => format!(
141 "{} >= {}",
142 dialect.quote(col),
143 v.to_param_with_dialect(dialect)
144 ),
145 WhereClause::Lt(col, v) => format!(
146 "{} < {}",
147 dialect.quote(col),
148 v.to_param_with_dialect(dialect)
149 ),
150 WhereClause::Le(col, v) => format!(
151 "{} <= {}",
152 dialect.quote(col),
153 v.to_param_with_dialect(dialect)
154 ),
155 WhereClause::Like(col, v) => format!(
156 "{} LIKE {}",
157 dialect.quote(col),
158 v.to_param_with_dialect(dialect)
159 ),
160 WhereClause::IsNull(col) => format!("{} IS NULL", dialect.quote(col)),
161 WhereClause::IsNotNull(col) => format!("{} IS NOT NULL", dialect.quote(col)),
162 WhereClause::In(col, vs) => {
163 let values: Vec<String> = vs
164 .iter()
165 .map(|v| v.to_param_with_dialect(dialect).to_string())
166 .collect();
167 format!("{} IN ({})", dialect.quote(col), values.join(", "))
168 }
169 WhereClause::NotIn(col, vs) => {
170 let values: Vec<String> = vs
171 .iter()
172 .map(|v| v.to_param_with_dialect(dialect).to_string())
173 .collect();
174 format!("{} NOT IN ({})", dialect.quote(col), values.join(", "))
175 }
176 WhereClause::Between(col, a, b) => format!(
177 "{} BETWEEN {} AND {}",
178 dialect.quote(col),
179 a.to_param_with_dialect(dialect),
180 b.to_param_with_dialect(dialect)
181 ),
182 WhereClause::Raw(sql) => sql.clone(),
183 }
184 }
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub enum OrderDirection {
194 Asc,
196 Desc,
198}
199
200#[derive(Debug, Clone)]
202pub struct OrderBy {
203 pub column: String,
205 pub direction: OrderDirection,
207}
208
209pub struct LambdaWrapper<M> {
241 table: String,
243 selects: Vec<String>,
245 wheres: Vec<WhereClause>,
247 orders: Vec<OrderBy>,
249 limit: Option<u64>,
251 offset: Option<u64>,
253 dialect: Box<dyn Dialect>,
255 soft_delete: Option<SoftDeleteConfig>,
260 tenant: Option<TenantConfig>,
264 _marker: PhantomData<M>,
265}
266
267#[derive(Debug, Clone)]
269pub struct SoftDeleteConfig {
270 pub column: String,
272 pub not_deleted_value: Value,
274 pub deleted_value: Value,
276}
277
278#[derive(Debug, Clone)]
280pub struct TenantConfig {
281 pub column: String,
283 pub tenant_id: Value,
285}
286
287impl<M> LambdaWrapper<M> {
288 pub fn new(table: impl Into<String>) -> Self {
290 Self {
291 table: table.into(),
292 selects: Vec::new(),
293 wheres: Vec::new(),
294 orders: Vec::new(),
295 limit: None,
296 offset: None,
297 dialect: Box::new(MySqlDialect),
298 soft_delete: None,
299 tenant: None,
300 _marker: PhantomData,
301 }
302 }
303
304 pub fn with_dialect(table: impl Into<String>, dialect: Box<dyn Dialect>) -> Self {
306 Self {
307 table: table.into(),
308 selects: Vec::new(),
309 wheres: Vec::new(),
310 orders: Vec::new(),
311 limit: None,
312 offset: None,
313 dialect,
314 soft_delete: None,
315 tenant: None,
316 _marker: PhantomData,
317 }
318 }
319
320 pub fn with_soft_delete(mut self, config: SoftDeleteConfig) -> Self {
343 if crate::sql_safety::validate_identifier(&config.column, "soft_delete column").is_ok() {
345 self.soft_delete = Some(config);
346 }
347 self
348 }
349
350 pub fn with_tenant(mut self, config: TenantConfig) -> Self {
370 if crate::sql_safety::validate_identifier(&config.column, "tenant column").is_ok() {
372 self.tenant = Some(config);
373 }
374 self
375 }
376
377 fn collect_implicit_wheres(&self) -> Vec<String> {
380 let mut implicit = Vec::new();
381 if let Some(ref sd) = self.soft_delete {
382 let col = self.dialect.quote(&sd.column);
383 let val = sd
384 .not_deleted_value
385 .to_param_with_dialect(self.dialect.as_ref());
386 implicit.push(format!("{} = {}", col, val));
387 }
388 if let Some(ref t) = self.tenant {
389 let col = self.dialect.quote(&t.column);
390 let val = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
391 implicit.push(format!("{} = {}", col, val));
392 }
393 implicit
394 }
395
396 fn render_where_clause(&self) -> String {
398 let user_conds: Vec<String> = self
399 .wheres
400 .iter()
401 .map(|w| w.render(self.dialect.as_ref()))
402 .collect();
403 let implicit_conds = self.collect_implicit_wheres();
404 let all_conds: Vec<String> = user_conds.into_iter().chain(implicit_conds).collect();
405 if all_conds.is_empty() {
406 String::new()
407 } else {
408 format!(" WHERE {}", all_conds.join(" AND "))
409 }
410 }
411
412 pub fn select<C: Column<M>>(&mut self, col: C) -> &mut Self {
419 let name = col.name();
420 if crate::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
423 self.selects.push(name.to_string());
424 }
425 self
426 }
427
428 pub fn select_many<C: Column<M>>(&mut self, cols: &[C]) -> &mut Self {
432 for c in cols {
433 let name = c.name();
434 if crate::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
435 self.selects.push(name.to_string());
436 }
437 }
438 self
439 }
440
441 pub fn select_all(&mut self) -> &mut Self {
443 self.selects.clear();
444 self
445 }
446
447 pub fn eq<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
451 self.wheres
452 .push(WhereClause::Eq(col.name().to_string(), value));
453 self
454 }
455
456 pub fn ne<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
458 self.wheres
459 .push(WhereClause::Ne(col.name().to_string(), value));
460 self
461 }
462
463 pub fn gt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
465 self.wheres
466 .push(WhereClause::Gt(col.name().to_string(), value));
467 self
468 }
469
470 pub fn ge<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
472 self.wheres
473 .push(WhereClause::Ge(col.name().to_string(), value));
474 self
475 }
476
477 pub fn lt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
479 self.wheres
480 .push(WhereClause::Lt(col.name().to_string(), value));
481 self
482 }
483
484 pub fn le<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
486 self.wheres
487 .push(WhereClause::Le(col.name().to_string(), value));
488 self
489 }
490
491 pub fn like<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
493 self.wheres
494 .push(WhereClause::Like(col.name().to_string(), value));
495 self
496 }
497
498 pub fn is_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
500 self.wheres
501 .push(WhereClause::IsNull(col.name().to_string()));
502 self
503 }
504
505 pub fn is_not_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
507 self.wheres
508 .push(WhereClause::IsNotNull(col.name().to_string()));
509 self
510 }
511
512 pub fn r#in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
514 self.wheres
515 .push(WhereClause::In(col.name().to_string(), values));
516 self
517 }
518
519 pub fn not_in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
521 self.wheres
522 .push(WhereClause::NotIn(col.name().to_string(), values));
523 self
524 }
525
526 pub fn between<C: Column<M>>(&mut self, col: C, a: Value, b: Value) -> &mut Self {
528 self.wheres
529 .push(WhereClause::Between(col.name().to_string(), a, b));
530 self
531 }
532
533 pub fn raw_where(&mut self, sql: impl Into<String>) -> &mut Self {
541 self.wheres.push(WhereClause::Raw(sql.into()));
542 self
543 }
544
545 pub fn order_by_asc<C: Column<M>>(&mut self, col: C) -> &mut Self {
549 self.orders.push(OrderBy {
550 column: col.name().to_string(),
551 direction: OrderDirection::Asc,
552 });
553 self
554 }
555
556 pub fn order_by_desc<C: Column<M>>(&mut self, col: C) -> &mut Self {
558 self.orders.push(OrderBy {
559 column: col.name().to_string(),
560 direction: OrderDirection::Desc,
561 });
562 self
563 }
564
565 pub fn limit(&mut self, n: u64) -> &mut Self {
567 self.limit = Some(n);
568 self
569 }
570
571 pub fn offset(&mut self, n: u64) -> &mut Self {
573 self.offset = Some(n);
574 self
575 }
576
577 pub fn page(&mut self, page: u64, page_size: u64) -> &mut Self {
581 self.limit = Some(page_size);
582 if page > 1 {
583 self.offset = Some((page - 1) * page_size);
584 } else {
585 self.offset = None;
586 }
587 self
588 }
589
590 pub fn build_select(&self) -> String {
596 let quoted_table = self.dialect.quote(&self.table);
597
598 let select_sql = if self.selects.is_empty() {
600 "*".to_string()
601 } else {
602 self.selects
603 .iter()
604 .map(|c| self.dialect.quote(c))
605 .collect::<Vec<_>>()
606 .join(", ")
607 };
608
609 let mut sql = format!("SELECT {} FROM {}", select_sql, quoted_table);
610
611 sql.push_str(&self.render_where_clause());
613
614 if !self.orders.is_empty() {
616 let orders: Vec<String> = self
617 .orders
618 .iter()
619 .map(|o| {
620 let dir = match o.direction {
621 OrderDirection::Asc => "ASC",
622 OrderDirection::Desc => "DESC",
623 };
624 format!("{} {}", self.dialect.quote(&o.column), dir)
625 })
626 .collect();
627 sql.push_str(" ORDER BY ");
628 sql.push_str(&orders.join(", "));
629 }
630
631 if let Some(l) = self.limit {
633 sql.push_str(&format!(" LIMIT {}", l));
634 }
635 if let Some(o) = self.offset {
636 sql.push_str(&format!(" OFFSET {}", o));
637 }
638
639 sql
640 }
641
642 pub fn build_count(&self) -> String {
646 let quoted_table = self.dialect.quote(&self.table);
647 let mut sql = format!("SELECT COUNT(*) FROM {}", quoted_table);
648 sql.push_str(&self.render_where_clause());
649 sql
650 }
651
652 pub fn build_exists(&self) -> String {
656 let inner = self.build_select();
657 let inner = if let Some(pos) = inner.find(" FROM ") {
659 format!("SELECT 1{}", &inner[pos..])
660 } else {
661 inner
662 };
663 format!("SELECT EXISTS({}) AS exists_flag", inner)
664 }
665
666 pub fn build_delete(&self) -> String {
673 let quoted_table = self.dialect.quote(&self.table);
674
675 if let Some(ref sd) = self.soft_delete {
677 let col = self.dialect.quote(&sd.column);
678 let val = sd
679 .deleted_value
680 .to_param_with_dialect(self.dialect.as_ref());
681 let mut sql = format!("UPDATE {} SET {} = {}", quoted_table, col, val);
682
683 let user_conds: Vec<String> = self
685 .wheres
686 .iter()
687 .map(|w| w.render(self.dialect.as_ref()))
688 .collect();
689 let mut all_conds = user_conds;
690 if let Some(ref t) = self.tenant {
692 let tcol = self.dialect.quote(&t.column);
693 let tval = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
694 all_conds.push(format!("{} = {}", tcol, tval));
695 }
696 let not_deleted_cond = format!(
698 "{} = {}",
699 col,
700 sd.not_deleted_value
701 .to_param_with_dialect(self.dialect.as_ref())
702 );
703 all_conds.push(not_deleted_cond);
704
705 if !all_conds.is_empty() {
706 sql.push_str(" WHERE ");
707 sql.push_str(&all_conds.join(" AND "));
708 }
709 return sql;
710 }
711
712 let mut sql = format!("DELETE FROM {}", quoted_table);
714 sql.push_str(&self.render_where_clause());
715 sql
716 }
717
718 pub fn where_count(&self) -> usize {
722 self.wheres.len()
723 }
724
725 pub fn select_count(&self) -> usize {
727 self.selects.len()
728 }
729
730 pub fn table(&self) -> &str {
732 &self.table
733 }
734
735 pub fn reset(&mut self) -> &mut Self {
737 self.selects.clear();
738 self.wheres.clear();
739 self.orders.clear();
740 self.limit = None;
741 self.offset = None;
742 self
743 }
744}
745
746#[macro_export]
792macro_rules! define_columns {
793 (
794 $columns_struct:ident for $model:ident table = $table:literal {
795 $( $field:ident => $name:literal ),* $(,)?
796 }
797 ) => {
798 #[derive(Debug, Clone, Copy)]
802 pub struct $columns_struct {
803 pub name: &'static str,
805 pub table: &'static str,
807 }
808
809 impl $crate::lambda::Column<$model> for $columns_struct {
810 fn name(&self) -> &'static str {
811 self.name
812 }
813
814 fn table(&self) -> &'static str {
815 self.table
816 }
817 }
818
819 impl $columns_struct {
820 $(
822 #[allow(non_upper_case_globals, dead_code)]
823 pub const $field: $columns_struct = $columns_struct { name: $name, table: $table };
824 )*
825 }
826 };
827}
828
829#[cfg(test)]
834mod tests {
835 use super::*;
836 use crate::PostgreSqlDialect;
837 use crate::get_dialect;
838 use crate::DbType;
839
840 struct User;
842
843 define_columns! {
845 UserColumns for User table = "users" {
846 Id => "id",
847 Name => "name",
848 Age => "age",
849 Email => "email",
850 }
851 }
852
853 struct Order;
855
856 define_columns! {
857 OrderColumns for Order table = "orders" {
858 OrderId => "order_id",
859 UserId => "user_id",
860 Total => "total",
861 }
862 }
863
864 #[test]
867 fn test_column_name_and_table() {
868 assert_eq!(UserColumns::Id.name(), "id");
869 assert_eq!(UserColumns::Id.table(), "users");
870 assert_eq!(UserColumns::Name.name(), "name");
871 assert_eq!(UserColumns::Age.name(), "age");
872 assert_eq!(UserColumns::Email.name(), "email");
873 }
874
875 #[test]
876 fn test_column_for_different_models() {
877 assert_eq!(OrderColumns::OrderId.name(), "order_id");
878 assert_eq!(OrderColumns::OrderId.table(), "orders");
879 assert_eq!(OrderColumns::UserId.name(), "user_id");
880 }
881
882 #[test]
885 fn test_new_wrapper() {
886 let w = LambdaWrapper::<User>::new("users");
887 assert_eq!(w.table(), "users");
888 assert_eq!(w.where_count(), 0);
889 assert_eq!(w.select_count(), 0);
890 }
891
892 #[test]
893 fn test_select_single() {
894 let mut w = LambdaWrapper::<User>::new("users");
895 w.select(UserColumns::Id);
896 let sql = w.build_select();
897 assert!(sql.contains("SELECT `id` FROM `users`"));
898 }
899
900 #[test]
901 fn test_select_multiple() {
902 let mut w = LambdaWrapper::<User>::new("users");
903 w.select(UserColumns::Id)
904 .select(UserColumns::Name)
905 .select(UserColumns::Age);
906 let sql = w.build_select();
907 assert!(sql.contains("`id`, `name`, `age`"));
908 }
909
910 #[test]
911 fn test_select_many() {
912 let mut w = LambdaWrapper::<User>::new("users");
913 w.select_many(&[UserColumns::Id, UserColumns::Name, UserColumns::Age]);
914 let sql = w.build_select();
915 assert!(sql.contains("`id`, `name`, `age`"));
916 }
917
918 #[test]
919 fn test_select_all_clears_selects() {
920 let mut w = LambdaWrapper::<User>::new("users");
921 w.select(UserColumns::Id);
922 assert_eq!(w.select_count(), 1);
923 w.select_all();
924 assert_eq!(w.select_count(), 0);
925 let sql = w.build_select();
926 assert!(sql.contains("SELECT * FROM"));
927 }
928
929 #[test]
930 fn test_default_select_is_star() {
931 let w = LambdaWrapper::<User>::new("users");
932 let sql = w.build_select();
933 assert!(sql.contains("SELECT * FROM `users`"));
934 }
935
936 #[test]
939 fn test_where_eq() {
940 let mut w = LambdaWrapper::<User>::new("users");
941 w.eq(UserColumns::Id, Value::I64(1));
942 let sql = w.build_select();
943 assert!(sql.contains("WHERE `id` = 1"));
944 }
945
946 #[test]
947 fn test_where_ne() {
948 let mut w = LambdaWrapper::<User>::new("users");
949 w.ne(UserColumns::Id, Value::I64(1));
950 let sql = w.build_select();
951 assert!(sql.contains("`id` != 1"));
952 }
953
954 #[test]
955 fn test_where_gt_ge_lt_le() {
956 let mut w = LambdaWrapper::<User>::new("users");
957 w.gt(UserColumns::Age, Value::I64(18))
958 .ge(UserColumns::Age, Value::I64(20))
959 .lt(UserColumns::Age, Value::I64(65))
960 .le(UserColumns::Age, Value::I64(60));
961 let sql = w.build_select();
962 assert!(sql.contains("`age` > 18"));
963 assert!(sql.contains("`age` >= 20"));
964 assert!(sql.contains("`age` < 65"));
965 assert!(sql.contains("`age` <= 60"));
966 }
967
968 #[test]
969 fn test_where_like() {
970 let mut w = LambdaWrapper::<User>::new("users");
971 w.like(UserColumns::Name, Value::String("%alice%".to_string()));
972 let sql = w.build_select();
973 assert!(sql.contains("`name` LIKE '%alice%'"));
974 }
975
976 #[test]
977 fn test_where_is_null() {
978 let mut w = LambdaWrapper::<User>::new("users");
979 w.is_null(UserColumns::Email);
980 let sql = w.build_select();
981 assert!(sql.contains("`email` IS NULL"));
982 }
983
984 #[test]
985 fn test_where_is_not_null() {
986 let mut w = LambdaWrapper::<User>::new("users");
987 w.is_not_null(UserColumns::Email);
988 let sql = w.build_select();
989 assert!(sql.contains("`email` IS NOT NULL"));
990 }
991
992 #[test]
993 fn test_where_in() {
994 let mut w = LambdaWrapper::<User>::new("users");
995 w.r#in(
996 UserColumns::Id,
997 vec![Value::I64(1), Value::I64(2), Value::I64(3)],
998 );
999 let sql = w.build_select();
1000 assert!(sql.contains("`id` IN (1, 2, 3)"));
1001 }
1002
1003 #[test]
1004 fn test_where_not_in() {
1005 let mut w = LambdaWrapper::<User>::new("users");
1006 w.not_in(UserColumns::Id, vec![Value::I64(1), Value::I64(2)]);
1007 let sql = w.build_select();
1008 assert!(sql.contains("`id` NOT IN (1, 2)"));
1009 }
1010
1011 #[test]
1012 fn test_where_between() {
1013 let mut w = LambdaWrapper::<User>::new("users");
1014 w.between(UserColumns::Age, Value::I64(18), Value::I64(65));
1015 let sql = w.build_select();
1016 assert!(sql.contains("`age` BETWEEN 18 AND 65"));
1017 }
1018
1019 #[test]
1020 fn test_where_multiple_anded() {
1021 let mut w = LambdaWrapper::<User>::new("users");
1022 w.eq(UserColumns::Id, Value::I64(1))
1023 .gt(UserColumns::Age, Value::I64(18))
1024 .like(UserColumns::Name, Value::String("alice%".to_string()));
1025 let sql = w.build_select();
1026 assert!(sql.contains("`id` = 1"));
1027 assert!(sql.contains("`age` > 18"));
1028 assert!(sql.contains("`name` LIKE 'alice%'"));
1029 assert!(sql.contains(" AND "));
1031 }
1032
1033 #[test]
1034 fn test_where_raw() {
1035 let mut w = LambdaWrapper::<User>::new("users");
1036 w.raw_where("name = 'alice' OR name = 'bob'");
1037 let sql = w.build_select();
1038 assert!(sql.contains("name = 'alice' OR name = 'bob'"));
1039 }
1040
1041 #[test]
1044 fn test_order_by_asc() {
1045 let mut w = LambdaWrapper::<User>::new("users");
1046 w.order_by_asc(UserColumns::Name);
1047 let sql = w.build_select();
1048 assert!(sql.contains("ORDER BY `name` ASC"));
1049 }
1050
1051 #[test]
1052 fn test_order_by_desc() {
1053 let mut w = LambdaWrapper::<User>::new("users");
1054 w.order_by_desc(UserColumns::Id);
1055 let sql = w.build_select();
1056 assert!(sql.contains("ORDER BY `id` DESC"));
1057 }
1058
1059 #[test]
1060 fn test_order_by_multiple() {
1061 let mut w = LambdaWrapper::<User>::new("users");
1062 w.order_by_asc(UserColumns::Name)
1063 .order_by_desc(UserColumns::Id);
1064 let sql = w.build_select();
1065 assert!(sql.contains("ORDER BY `name` ASC, `id` DESC"));
1066 }
1067
1068 #[test]
1069 fn test_limit() {
1070 let mut w = LambdaWrapper::<User>::new("users");
1071 w.limit(10);
1072 let sql = w.build_select();
1073 assert!(sql.contains("LIMIT 10"));
1074 }
1075
1076 #[test]
1077 fn test_offset() {
1078 let mut w = LambdaWrapper::<User>::new("users");
1079 w.limit(10).offset(20);
1080 let sql = w.build_select();
1081 assert!(sql.contains("LIMIT 10"));
1082 assert!(sql.contains("OFFSET 20"));
1083 }
1084
1085 #[test]
1086 fn test_page() {
1087 let mut w = LambdaWrapper::<User>::new("users");
1088 w.page(3, 20); let sql = w.build_select();
1090 assert!(sql.contains("LIMIT 20"));
1091 assert!(sql.contains("OFFSET 40")); }
1093
1094 #[test]
1095 fn test_page_1_no_offset() {
1096 let mut w = LambdaWrapper::<User>::new("users");
1097 w.page(1, 10);
1098 let sql = w.build_select();
1099 assert!(sql.contains("LIMIT 10"));
1100 assert!(!sql.contains("OFFSET")); }
1102
1103 #[test]
1106 fn test_build_count() {
1107 let mut w = LambdaWrapper::<User>::new("users");
1108 w.gt(UserColumns::Age, Value::I64(18));
1109 let sql = w.build_count();
1110 assert!(sql.contains("SELECT COUNT(*) FROM `users`"));
1111 assert!(sql.contains("`age` > 18"));
1112 assert!(!sql.contains("ORDER BY"));
1114 assert!(!sql.contains("LIMIT"));
1115 }
1116
1117 #[test]
1118 fn test_build_exists() {
1119 let mut w = LambdaWrapper::<User>::new("users");
1120 w.eq(UserColumns::Id, Value::I64(1));
1121 let sql = w.build_exists();
1122 assert!(sql.starts_with("SELECT EXISTS("));
1123 assert!(sql.contains("SELECT 1 FROM `users`"));
1124 assert!(sql.contains("`id` = 1"));
1125 assert!(sql.ends_with(") AS exists_flag"));
1126 }
1127
1128 #[test]
1129 fn test_build_delete() {
1130 let mut w = LambdaWrapper::<User>::new("users");
1131 w.eq(UserColumns::Id, Value::I64(1));
1132 let sql = w.build_delete();
1133 assert!(sql.starts_with("DELETE FROM `users`"));
1134 assert!(sql.contains("WHERE `id` = 1"));
1135 }
1136
1137 #[test]
1140 fn test_soft_delete_select_appends_filter() {
1141 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1143 column: "deleted".to_string(),
1144 not_deleted_value: Value::I64(0),
1145 deleted_value: Value::I64(1),
1146 });
1147 let sql = w.build_select();
1148 assert!(
1149 sql.contains("`deleted` = 0"),
1150 "软删除 SELECT 应追加 `deleted` = 0: {}",
1151 sql
1152 );
1153 }
1154
1155 #[test]
1156 fn test_soft_delete_count_appends_filter() {
1157 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1159 column: "deleted".to_string(),
1160 not_deleted_value: Value::I64(0),
1161 deleted_value: Value::I64(1),
1162 });
1163 let sql = w.build_count();
1164 assert!(
1165 sql.contains("`deleted` = 0"),
1166 "软删除 COUNT 应追加 `deleted` = 0: {}",
1167 sql
1168 );
1169 }
1170
1171 #[test]
1172 fn test_soft_delete_exists_appends_filter() {
1173 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1175 column: "deleted".to_string(),
1176 not_deleted_value: Value::I64(0),
1177 deleted_value: Value::I64(1),
1178 });
1179 let sql = w.build_exists();
1180 assert!(
1181 sql.contains("`deleted` = 0"),
1182 "软删除 EXISTS 应追加 `deleted` = 0: {}",
1183 sql
1184 );
1185 }
1186
1187 #[test]
1188 fn test_soft_delete_delete_becomes_update() {
1189 let mut w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1191 column: "deleted".to_string(),
1192 not_deleted_value: Value::I64(0),
1193 deleted_value: Value::I64(1),
1194 });
1195 w.eq(UserColumns::Id, Value::I64(42));
1196 let sql = w.build_delete();
1197 assert!(
1198 sql.starts_with("UPDATE `users` SET `deleted` = 1"),
1199 "软删除应为 UPDATE:{}",
1200 sql
1201 );
1202 assert!(sql.contains("`id` = 42"), "应保留用户 WHERE 条件:{}", sql);
1203 assert!(
1204 sql.contains("`deleted` = 0"),
1205 "应追加未删除条件防止重复删除:{}",
1206 sql
1207 );
1208 assert!(
1209 !sql.starts_with("DELETE"),
1210 "软删除不应生成 DELETE 语句:{}",
1211 sql
1212 );
1213 }
1214
1215 #[test]
1216 fn test_tenant_select_appends_filter() {
1217 let w = LambdaWrapper::<User>::new("users").with_tenant(TenantConfig {
1219 column: "tenant_id".to_string(),
1220 tenant_id: Value::I64(42),
1221 });
1222 let sql = w.build_select();
1223 assert!(
1224 sql.contains("`tenant_id` = 42"),
1225 "多租户 SELECT 应追加 `tenant_id` = 42: {}",
1226 sql
1227 );
1228 }
1229
1230 #[test]
1231 fn test_tenant_delete_appends_filter() {
1232 let mut w = LambdaWrapper::<User>::new("users").with_tenant(TenantConfig {
1234 column: "tenant_id".to_string(),
1235 tenant_id: Value::I64(42),
1236 });
1237 w.eq(UserColumns::Id, Value::I64(1));
1238 let sql = w.build_delete();
1239 assert!(
1240 sql.contains("`tenant_id` = 42"),
1241 "多租户 DELETE 应追加 `tenant_id` = 42: {}",
1242 sql
1243 );
1244 }
1245
1246 #[test]
1247 fn test_soft_delete_and_tenant_combined() {
1248 let mut w = LambdaWrapper::<User>::new("users")
1250 .with_soft_delete(SoftDeleteConfig {
1251 column: "deleted".to_string(),
1252 not_deleted_value: Value::I64(0),
1253 deleted_value: Value::I64(1),
1254 })
1255 .with_tenant(TenantConfig {
1256 column: "tenant_id".to_string(),
1257 tenant_id: Value::I64(99),
1258 });
1259 w.eq(UserColumns::Id, Value::I64(7));
1260
1261 let sql = w.build_select();
1263 assert!(sql.contains("`deleted` = 0"), "应包含软删除条件: {}", sql);
1264 assert!(
1265 sql.contains("`tenant_id` = 99"),
1266 "应包含多租户条件: {}",
1267 sql
1268 );
1269 assert!(sql.contains("`id` = 7"), "应包含用户条件: {}", sql);
1270
1271 let del_sql = w.build_delete();
1273 assert!(del_sql.starts_with("UPDATE `users` SET `deleted` = 1"));
1274 assert!(del_sql.contains("`tenant_id` = 99"));
1275 assert!(del_sql.contains("`deleted` = 0"));
1276 assert!(del_sql.contains("`id` = 7"));
1277 }
1278
1279 #[test]
1280 fn test_no_soft_delete_no_tenant_backward_compat() {
1281 let mut w = LambdaWrapper::<User>::new("users");
1283 w.eq(UserColumns::Id, Value::I64(1));
1284 let sql = w.build_select();
1285 assert!(!sql.contains("deleted"));
1286 assert!(!sql.contains("tenant_id"));
1287
1288 let del_sql = w.build_delete();
1289 assert!(del_sql.starts_with("DELETE FROM"));
1290 }
1291
1292 #[test]
1295 fn test_with_postgres_dialect() {
1296 let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1297 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1298 w.eq(UserColumns::Id, Value::I64(1));
1299 let sql = w.build_select();
1300 assert!(sql.contains("\"users\""));
1301 assert!(sql.contains("\"id\" = 1"));
1302 }
1303
1304 #[test]
1305 fn test_postgres_select() {
1306 let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1307 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1308 w.select(UserColumns::Id).select(UserColumns::Name);
1309 let sql = w.build_select();
1310 assert!(sql.contains("\"id\", \"name\""));
1311 }
1312
1313 #[test]
1316 fn test_complex_query() {
1317 let mut w = LambdaWrapper::<User>::new("users");
1318 w.select(UserColumns::Id)
1319 .select(UserColumns::Name)
1320 .select(UserColumns::Age)
1321 .gt(UserColumns::Age, Value::I64(18))
1322 .like(UserColumns::Name, Value::String("a%".to_string()))
1323 .is_not_null(UserColumns::Email)
1324 .order_by_desc(UserColumns::Id)
1325 .limit(10)
1326 .offset(20);
1327
1328 let sql = w.build_select();
1329 assert!(sql.contains("SELECT `id`, `name`, `age` FROM `users`"));
1330 assert!(sql.contains("`age` > 18"));
1331 assert!(sql.contains("`name` LIKE 'a%'"));
1332 assert!(sql.contains("`email` IS NOT NULL"));
1333 assert!(sql.contains("ORDER BY `id` DESC"));
1334 assert!(sql.contains("LIMIT 10"));
1335 assert!(sql.contains("OFFSET 20"));
1336 }
1337
1338 #[test]
1339 fn test_reset_clears_all() {
1340 let mut w = LambdaWrapper::<User>::new("users");
1341 w.select(UserColumns::Id)
1342 .eq(UserColumns::Id, Value::I64(1))
1343 .order_by_asc(UserColumns::Name)
1344 .limit(10);
1345
1346 w.reset();
1347 assert_eq!(w.select_count(), 0);
1348 assert_eq!(w.where_count(), 0);
1349 let sql = w.build_select();
1350 assert!(sql.contains("SELECT * FROM `users`"));
1351 assert!(!sql.contains("WHERE"));
1352 assert!(!sql.contains("ORDER BY"));
1353 assert!(!sql.contains("LIMIT"));
1354 }
1355
1356 #[test]
1359 fn test_different_models_dont_share_columns() {
1360 let mut user_w = LambdaWrapper::<User>::new("users");
1361 user_w.eq(UserColumns::Id, Value::I64(1));
1362
1363 let mut order_w = LambdaWrapper::<Order>::new("orders");
1364 order_w.eq(OrderColumns::OrderId, Value::I64(100));
1365
1366 let user_sql = user_w.build_select();
1367 let order_sql = order_w.build_select();
1368
1369 assert!(user_sql.contains("`users`"));
1370 assert!(user_sql.contains("`id` = 1"));
1371 assert!(order_sql.contains("`orders`"));
1372 assert!(order_sql.contains("`order_id` = 100"));
1373 }
1374
1375 #[test]
1378 fn test_type_safety_compile_time_check() {
1379 let mut w = LambdaWrapper::<User>::new("users");
1385 w.eq(UserColumns::Id, Value::I64(1));
1386 assert_eq!(w.where_count(), 1);
1387 }
1388
1389 #[test]
1390 fn test_string_value_escape() {
1391 let mut w = LambdaWrapper::<User>::new("users");
1393 w.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1394 let sql = w.build_select();
1395 assert!(sql.contains("'O\\'Brien'"));
1397
1398 let pg_dialect: Box<dyn Dialect> = get_dialect(DbType::PostgreSQL).unwrap();
1400 let mut w_pg = LambdaWrapper::<User>::with_dialect("users", pg_dialect);
1401 w_pg.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1402 let sql_pg = w_pg.build_select();
1403 assert!(sql_pg.contains("'O''Brien'"));
1404 }
1405
1406 #[test]
1409 fn test_with_real_mysql_dialect() {
1410 let dialect: Box<dyn Dialect> = get_dialect(DbType::MySQL).unwrap();
1411 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1412 w.select(UserColumns::Id)
1413 .select(UserColumns::Name)
1414 .eq(UserColumns::Id, Value::I64(42))
1415 .order_by_desc(UserColumns::Id);
1416
1417 let sql = w.build_select();
1418 assert!(sql.contains("SELECT `id`, `name` FROM `users`"));
1419 assert!(sql.contains("`id` = 42"));
1420 assert!(sql.contains("ORDER BY `id` DESC"));
1421 }
1422}