1use crate::dialect::{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.not_deleted_value.to_param_with_dialect(self.dialect.as_ref());
384 implicit.push(format!("{} = {}", col, val));
385 }
386 if let Some(ref t) = self.tenant {
387 let col = self.dialect.quote(&t.column);
388 let val = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
389 implicit.push(format!("{} = {}", col, val));
390 }
391 implicit
392 }
393
394 fn render_where_clause(&self) -> String {
396 let user_conds: Vec<String> = self
397 .wheres
398 .iter()
399 .map(|w| w.render(self.dialect.as_ref()))
400 .collect();
401 let implicit_conds = self.collect_implicit_wheres();
402 let all_conds: Vec<String> = user_conds.into_iter().chain(implicit_conds).collect();
403 if all_conds.is_empty() {
404 String::new()
405 } else {
406 format!(" WHERE {}", all_conds.join(" AND "))
407 }
408 }
409
410 pub fn select<C: Column<M>>(&mut self, col: C) -> &mut Self {
417 let name = col.name();
418 if crate::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
421 self.selects.push(name.to_string());
422 }
423 self
424 }
425
426 pub fn select_many<C: Column<M>>(&mut self, cols: &[C]) -> &mut Self {
430 for c in cols {
431 let name = c.name();
432 if crate::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
433 self.selects.push(name.to_string());
434 }
435 }
436 self
437 }
438
439 pub fn select_all(&mut self) -> &mut Self {
441 self.selects.clear();
442 self
443 }
444
445 pub fn eq<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
449 self.wheres
450 .push(WhereClause::Eq(col.name().to_string(), value));
451 self
452 }
453
454 pub fn ne<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
456 self.wheres
457 .push(WhereClause::Ne(col.name().to_string(), value));
458 self
459 }
460
461 pub fn gt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
463 self.wheres
464 .push(WhereClause::Gt(col.name().to_string(), value));
465 self
466 }
467
468 pub fn ge<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
470 self.wheres
471 .push(WhereClause::Ge(col.name().to_string(), value));
472 self
473 }
474
475 pub fn lt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
477 self.wheres
478 .push(WhereClause::Lt(col.name().to_string(), value));
479 self
480 }
481
482 pub fn le<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
484 self.wheres
485 .push(WhereClause::Le(col.name().to_string(), value));
486 self
487 }
488
489 pub fn like<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
491 self.wheres
492 .push(WhereClause::Like(col.name().to_string(), value));
493 self
494 }
495
496 pub fn is_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
498 self.wheres
499 .push(WhereClause::IsNull(col.name().to_string()));
500 self
501 }
502
503 pub fn is_not_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
505 self.wheres
506 .push(WhereClause::IsNotNull(col.name().to_string()));
507 self
508 }
509
510 pub fn r#in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
512 self.wheres
513 .push(WhereClause::In(col.name().to_string(), values));
514 self
515 }
516
517 pub fn not_in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
519 self.wheres
520 .push(WhereClause::NotIn(col.name().to_string(), values));
521 self
522 }
523
524 pub fn between<C: Column<M>>(&mut self, col: C, a: Value, b: Value) -> &mut Self {
526 self.wheres
527 .push(WhereClause::Between(col.name().to_string(), a, b));
528 self
529 }
530
531 pub fn raw_where(&mut self, sql: impl Into<String>) -> &mut Self {
539 self.wheres.push(WhereClause::Raw(sql.into()));
540 self
541 }
542
543 pub fn order_by_asc<C: Column<M>>(&mut self, col: C) -> &mut Self {
547 self.orders.push(OrderBy {
548 column: col.name().to_string(),
549 direction: OrderDirection::Asc,
550 });
551 self
552 }
553
554 pub fn order_by_desc<C: Column<M>>(&mut self, col: C) -> &mut Self {
556 self.orders.push(OrderBy {
557 column: col.name().to_string(),
558 direction: OrderDirection::Desc,
559 });
560 self
561 }
562
563 pub fn limit(&mut self, n: u64) -> &mut Self {
565 self.limit = Some(n);
566 self
567 }
568
569 pub fn offset(&mut self, n: u64) -> &mut Self {
571 self.offset = Some(n);
572 self
573 }
574
575 pub fn page(&mut self, page: u64, page_size: u64) -> &mut Self {
579 self.limit = Some(page_size);
580 if page > 1 {
581 self.offset = Some((page - 1) * page_size);
582 } else {
583 self.offset = None;
584 }
585 self
586 }
587
588 pub fn build_select(&self) -> String {
594 let quoted_table = self.dialect.quote(&self.table);
595
596 let select_sql = if self.selects.is_empty() {
598 "*".to_string()
599 } else {
600 self.selects
601 .iter()
602 .map(|c| self.dialect.quote(c))
603 .collect::<Vec<_>>()
604 .join(", ")
605 };
606
607 let mut sql = format!("SELECT {} FROM {}", select_sql, quoted_table);
608
609 sql.push_str(&self.render_where_clause());
611
612 if !self.orders.is_empty() {
614 let orders: Vec<String> = self
615 .orders
616 .iter()
617 .map(|o| {
618 let dir = match o.direction {
619 OrderDirection::Asc => "ASC",
620 OrderDirection::Desc => "DESC",
621 };
622 format!("{} {}", self.dialect.quote(&o.column), dir)
623 })
624 .collect();
625 sql.push_str(" ORDER BY ");
626 sql.push_str(&orders.join(", "));
627 }
628
629 if let Some(l) = self.limit {
631 sql.push_str(&format!(" LIMIT {}", l));
632 }
633 if let Some(o) = self.offset {
634 sql.push_str(&format!(" OFFSET {}", o));
635 }
636
637 sql
638 }
639
640 pub fn build_count(&self) -> String {
644 let quoted_table = self.dialect.quote(&self.table);
645 let mut sql = format!("SELECT COUNT(*) FROM {}", quoted_table);
646 sql.push_str(&self.render_where_clause());
647 sql
648 }
649
650 pub fn build_exists(&self) -> String {
654 let inner = self.build_select();
655 let inner = if let Some(pos) = inner.find(" FROM ") {
657 format!("SELECT 1{}", &inner[pos..])
658 } else {
659 inner
660 };
661 format!("SELECT EXISTS({}) AS exists_flag", inner)
662 }
663
664 pub fn build_delete(&self) -> String {
671 let quoted_table = self.dialect.quote(&self.table);
672
673 if let Some(ref sd) = self.soft_delete {
675 let col = self.dialect.quote(&sd.column);
676 let val = sd.deleted_value.to_param_with_dialect(self.dialect.as_ref());
677 let mut sql = format!(
678 "UPDATE {} SET {} = {}",
679 quoted_table, col, val
680 );
681
682 let user_conds: Vec<String> = self
684 .wheres
685 .iter()
686 .map(|w| w.render(self.dialect.as_ref()))
687 .collect();
688 let mut all_conds = user_conds;
689 if let Some(ref t) = self.tenant {
691 let tcol = self.dialect.quote(&t.column);
692 let tval = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
693 all_conds.push(format!("{} = {}", tcol, tval));
694 }
695 let not_deleted_cond = format!(
697 "{} = {}",
698 col,
699 sd.not_deleted_value.to_param_with_dialect(self.dialect.as_ref())
700 );
701 all_conds.push(not_deleted_cond);
702
703 if !all_conds.is_empty() {
704 sql.push_str(" WHERE ");
705 sql.push_str(&all_conds.join(" AND "));
706 }
707 return sql;
708 }
709
710 let mut sql = format!("DELETE FROM {}", quoted_table);
712 sql.push_str(&self.render_where_clause());
713 sql
714 }
715
716 pub fn where_count(&self) -> usize {
720 self.wheres.len()
721 }
722
723 pub fn select_count(&self) -> usize {
725 self.selects.len()
726 }
727
728 pub fn table(&self) -> &str {
730 &self.table
731 }
732
733 pub fn reset(&mut self) -> &mut Self {
735 self.selects.clear();
736 self.wheres.clear();
737 self.orders.clear();
738 self.limit = None;
739 self.offset = None;
740 self
741 }
742}
743
744#[macro_export]
790macro_rules! define_columns {
791 (
792 $columns_struct:ident for $model:ident table = $table:literal {
793 $( $field:ident => $name:literal ),* $(,)?
794 }
795 ) => {
796 #[derive(Debug, Clone, Copy)]
800 pub struct $columns_struct {
801 pub name: &'static str,
803 pub table: &'static str,
805 }
806
807 impl $crate::lambda::Column<$model> for $columns_struct {
808 fn name(&self) -> &'static str {
809 self.name
810 }
811
812 fn table(&self) -> &'static str {
813 self.table
814 }
815 }
816
817 impl $columns_struct {
818 $(
820 #[allow(non_upper_case_globals, dead_code)]
821 pub const $field: $columns_struct = $columns_struct { name: $name, table: $table };
822 )*
823 }
824 };
825}
826
827#[cfg(test)]
832mod tests {
833 use super::*;
834 use crate::dialect::PostgreSqlDialect;
835 use crate::get_dialect;
836 use crate::DbType;
837
838 struct User;
840
841 define_columns! {
843 UserColumns for User table = "users" {
844 Id => "id",
845 Name => "name",
846 Age => "age",
847 Email => "email",
848 }
849 }
850
851 struct Order;
853
854 define_columns! {
855 OrderColumns for Order table = "orders" {
856 OrderId => "order_id",
857 UserId => "user_id",
858 Total => "total",
859 }
860 }
861
862 #[test]
865 fn test_column_name_and_table() {
866 assert_eq!(UserColumns::Id.name(), "id");
867 assert_eq!(UserColumns::Id.table(), "users");
868 assert_eq!(UserColumns::Name.name(), "name");
869 assert_eq!(UserColumns::Age.name(), "age");
870 assert_eq!(UserColumns::Email.name(), "email");
871 }
872
873 #[test]
874 fn test_column_for_different_models() {
875 assert_eq!(OrderColumns::OrderId.name(), "order_id");
876 assert_eq!(OrderColumns::OrderId.table(), "orders");
877 assert_eq!(OrderColumns::UserId.name(), "user_id");
878 }
879
880 #[test]
883 fn test_new_wrapper() {
884 let w = LambdaWrapper::<User>::new("users");
885 assert_eq!(w.table(), "users");
886 assert_eq!(w.where_count(), 0);
887 assert_eq!(w.select_count(), 0);
888 }
889
890 #[test]
891 fn test_select_single() {
892 let mut w = LambdaWrapper::<User>::new("users");
893 w.select(UserColumns::Id);
894 let sql = w.build_select();
895 assert!(sql.contains("SELECT `id` FROM `users`"));
896 }
897
898 #[test]
899 fn test_select_multiple() {
900 let mut w = LambdaWrapper::<User>::new("users");
901 w.select(UserColumns::Id)
902 .select(UserColumns::Name)
903 .select(UserColumns::Age);
904 let sql = w.build_select();
905 assert!(sql.contains("`id`, `name`, `age`"));
906 }
907
908 #[test]
909 fn test_select_many() {
910 let mut w = LambdaWrapper::<User>::new("users");
911 w.select_many(&[UserColumns::Id, UserColumns::Name, UserColumns::Age]);
912 let sql = w.build_select();
913 assert!(sql.contains("`id`, `name`, `age`"));
914 }
915
916 #[test]
917 fn test_select_all_clears_selects() {
918 let mut w = LambdaWrapper::<User>::new("users");
919 w.select(UserColumns::Id);
920 assert_eq!(w.select_count(), 1);
921 w.select_all();
922 assert_eq!(w.select_count(), 0);
923 let sql = w.build_select();
924 assert!(sql.contains("SELECT * FROM"));
925 }
926
927 #[test]
928 fn test_default_select_is_star() {
929 let w = LambdaWrapper::<User>::new("users");
930 let sql = w.build_select();
931 assert!(sql.contains("SELECT * FROM `users`"));
932 }
933
934 #[test]
937 fn test_where_eq() {
938 let mut w = LambdaWrapper::<User>::new("users");
939 w.eq(UserColumns::Id, Value::I64(1));
940 let sql = w.build_select();
941 assert!(sql.contains("WHERE `id` = 1"));
942 }
943
944 #[test]
945 fn test_where_ne() {
946 let mut w = LambdaWrapper::<User>::new("users");
947 w.ne(UserColumns::Id, Value::I64(1));
948 let sql = w.build_select();
949 assert!(sql.contains("`id` != 1"));
950 }
951
952 #[test]
953 fn test_where_gt_ge_lt_le() {
954 let mut w = LambdaWrapper::<User>::new("users");
955 w.gt(UserColumns::Age, Value::I64(18))
956 .ge(UserColumns::Age, Value::I64(20))
957 .lt(UserColumns::Age, Value::I64(65))
958 .le(UserColumns::Age, Value::I64(60));
959 let sql = w.build_select();
960 assert!(sql.contains("`age` > 18"));
961 assert!(sql.contains("`age` >= 20"));
962 assert!(sql.contains("`age` < 65"));
963 assert!(sql.contains("`age` <= 60"));
964 }
965
966 #[test]
967 fn test_where_like() {
968 let mut w = LambdaWrapper::<User>::new("users");
969 w.like(UserColumns::Name, Value::String("%alice%".to_string()));
970 let sql = w.build_select();
971 assert!(sql.contains("`name` LIKE '%alice%'"));
972 }
973
974 #[test]
975 fn test_where_is_null() {
976 let mut w = LambdaWrapper::<User>::new("users");
977 w.is_null(UserColumns::Email);
978 let sql = w.build_select();
979 assert!(sql.contains("`email` IS NULL"));
980 }
981
982 #[test]
983 fn test_where_is_not_null() {
984 let mut w = LambdaWrapper::<User>::new("users");
985 w.is_not_null(UserColumns::Email);
986 let sql = w.build_select();
987 assert!(sql.contains("`email` IS NOT NULL"));
988 }
989
990 #[test]
991 fn test_where_in() {
992 let mut w = LambdaWrapper::<User>::new("users");
993 w.r#in(
994 UserColumns::Id,
995 vec![Value::I64(1), Value::I64(2), Value::I64(3)],
996 );
997 let sql = w.build_select();
998 assert!(sql.contains("`id` IN (1, 2, 3)"));
999 }
1000
1001 #[test]
1002 fn test_where_not_in() {
1003 let mut w = LambdaWrapper::<User>::new("users");
1004 w.not_in(UserColumns::Id, vec![Value::I64(1), Value::I64(2)]);
1005 let sql = w.build_select();
1006 assert!(sql.contains("`id` NOT IN (1, 2)"));
1007 }
1008
1009 #[test]
1010 fn test_where_between() {
1011 let mut w = LambdaWrapper::<User>::new("users");
1012 w.between(UserColumns::Age, Value::I64(18), Value::I64(65));
1013 let sql = w.build_select();
1014 assert!(sql.contains("`age` BETWEEN 18 AND 65"));
1015 }
1016
1017 #[test]
1018 fn test_where_multiple_anded() {
1019 let mut w = LambdaWrapper::<User>::new("users");
1020 w.eq(UserColumns::Id, Value::I64(1))
1021 .gt(UserColumns::Age, Value::I64(18))
1022 .like(UserColumns::Name, Value::String("alice%".to_string()));
1023 let sql = w.build_select();
1024 assert!(sql.contains("`id` = 1"));
1025 assert!(sql.contains("`age` > 18"));
1026 assert!(sql.contains("`name` LIKE 'alice%'"));
1027 assert!(sql.contains(" AND "));
1029 }
1030
1031 #[test]
1032 fn test_where_raw() {
1033 let mut w = LambdaWrapper::<User>::new("users");
1034 w.raw_where("name = 'alice' OR name = 'bob'");
1035 let sql = w.build_select();
1036 assert!(sql.contains("name = 'alice' OR name = 'bob'"));
1037 }
1038
1039 #[test]
1042 fn test_order_by_asc() {
1043 let mut w = LambdaWrapper::<User>::new("users");
1044 w.order_by_asc(UserColumns::Name);
1045 let sql = w.build_select();
1046 assert!(sql.contains("ORDER BY `name` ASC"));
1047 }
1048
1049 #[test]
1050 fn test_order_by_desc() {
1051 let mut w = LambdaWrapper::<User>::new("users");
1052 w.order_by_desc(UserColumns::Id);
1053 let sql = w.build_select();
1054 assert!(sql.contains("ORDER BY `id` DESC"));
1055 }
1056
1057 #[test]
1058 fn test_order_by_multiple() {
1059 let mut w = LambdaWrapper::<User>::new("users");
1060 w.order_by_asc(UserColumns::Name)
1061 .order_by_desc(UserColumns::Id);
1062 let sql = w.build_select();
1063 assert!(sql.contains("ORDER BY `name` ASC, `id` DESC"));
1064 }
1065
1066 #[test]
1067 fn test_limit() {
1068 let mut w = LambdaWrapper::<User>::new("users");
1069 w.limit(10);
1070 let sql = w.build_select();
1071 assert!(sql.contains("LIMIT 10"));
1072 }
1073
1074 #[test]
1075 fn test_offset() {
1076 let mut w = LambdaWrapper::<User>::new("users");
1077 w.limit(10).offset(20);
1078 let sql = w.build_select();
1079 assert!(sql.contains("LIMIT 10"));
1080 assert!(sql.contains("OFFSET 20"));
1081 }
1082
1083 #[test]
1084 fn test_page() {
1085 let mut w = LambdaWrapper::<User>::new("users");
1086 w.page(3, 20); let sql = w.build_select();
1088 assert!(sql.contains("LIMIT 20"));
1089 assert!(sql.contains("OFFSET 40")); }
1091
1092 #[test]
1093 fn test_page_1_no_offset() {
1094 let mut w = LambdaWrapper::<User>::new("users");
1095 w.page(1, 10);
1096 let sql = w.build_select();
1097 assert!(sql.contains("LIMIT 10"));
1098 assert!(!sql.contains("OFFSET")); }
1100
1101 #[test]
1104 fn test_build_count() {
1105 let mut w = LambdaWrapper::<User>::new("users");
1106 w.gt(UserColumns::Age, Value::I64(18));
1107 let sql = w.build_count();
1108 assert!(sql.contains("SELECT COUNT(*) FROM `users`"));
1109 assert!(sql.contains("`age` > 18"));
1110 assert!(!sql.contains("ORDER BY"));
1112 assert!(!sql.contains("LIMIT"));
1113 }
1114
1115 #[test]
1116 fn test_build_exists() {
1117 let mut w = LambdaWrapper::<User>::new("users");
1118 w.eq(UserColumns::Id, Value::I64(1));
1119 let sql = w.build_exists();
1120 assert!(sql.starts_with("SELECT EXISTS("));
1121 assert!(sql.contains("SELECT 1 FROM `users`"));
1122 assert!(sql.contains("`id` = 1"));
1123 assert!(sql.ends_with(") AS exists_flag"));
1124 }
1125
1126 #[test]
1127 fn test_build_delete() {
1128 let mut w = LambdaWrapper::<User>::new("users");
1129 w.eq(UserColumns::Id, Value::I64(1));
1130 let sql = w.build_delete();
1131 assert!(sql.starts_with("DELETE FROM `users`"));
1132 assert!(sql.contains("WHERE `id` = 1"));
1133 }
1134
1135 #[test]
1138 fn test_soft_delete_select_appends_filter() {
1139 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1141 column: "deleted".to_string(),
1142 not_deleted_value: Value::I64(0),
1143 deleted_value: Value::I64(1),
1144 });
1145 let sql = w.build_select();
1146 assert!(
1147 sql.contains("`deleted` = 0"),
1148 "软删除 SELECT 应追加 `deleted` = 0: {}",
1149 sql
1150 );
1151 }
1152
1153 #[test]
1154 fn test_soft_delete_count_appends_filter() {
1155 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1157 column: "deleted".to_string(),
1158 not_deleted_value: Value::I64(0),
1159 deleted_value: Value::I64(1),
1160 });
1161 let sql = w.build_count();
1162 assert!(
1163 sql.contains("`deleted` = 0"),
1164 "软删除 COUNT 应追加 `deleted` = 0: {}",
1165 sql
1166 );
1167 }
1168
1169 #[test]
1170 fn test_soft_delete_exists_appends_filter() {
1171 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1173 column: "deleted".to_string(),
1174 not_deleted_value: Value::I64(0),
1175 deleted_value: Value::I64(1),
1176 });
1177 let sql = w.build_exists();
1178 assert!(
1179 sql.contains("`deleted` = 0"),
1180 "软删除 EXISTS 应追加 `deleted` = 0: {}",
1181 sql
1182 );
1183 }
1184
1185 #[test]
1186 fn test_soft_delete_delete_becomes_update() {
1187 let mut w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1189 column: "deleted".to_string(),
1190 not_deleted_value: Value::I64(0),
1191 deleted_value: Value::I64(1),
1192 });
1193 w.eq(UserColumns::Id, Value::I64(42));
1194 let sql = w.build_delete();
1195 assert!(
1196 sql.starts_with("UPDATE `users` SET `deleted` = 1"),
1197 "软删除应为 UPDATE:{}",
1198 sql
1199 );
1200 assert!(
1201 sql.contains("`id` = 42"),
1202 "应保留用户 WHERE 条件:{}",
1203 sql
1204 );
1205 assert!(
1206 sql.contains("`deleted` = 0"),
1207 "应追加未删除条件防止重复删除:{}",
1208 sql
1209 );
1210 assert!(
1211 !sql.starts_with("DELETE"),
1212 "软删除不应生成 DELETE 语句:{}",
1213 sql
1214 );
1215 }
1216
1217 #[test]
1218 fn test_tenant_select_appends_filter() {
1219 let w = LambdaWrapper::<User>::new("users").with_tenant(TenantConfig {
1221 column: "tenant_id".to_string(),
1222 tenant_id: Value::I64(42),
1223 });
1224 let sql = w.build_select();
1225 assert!(
1226 sql.contains("`tenant_id` = 42"),
1227 "多租户 SELECT 应追加 `tenant_id` = 42: {}",
1228 sql
1229 );
1230 }
1231
1232 #[test]
1233 fn test_tenant_delete_appends_filter() {
1234 let mut w = LambdaWrapper::<User>::new("users").with_tenant(TenantConfig {
1236 column: "tenant_id".to_string(),
1237 tenant_id: Value::I64(42),
1238 });
1239 w.eq(UserColumns::Id, Value::I64(1));
1240 let sql = w.build_delete();
1241 assert!(
1242 sql.contains("`tenant_id` = 42"),
1243 "多租户 DELETE 应追加 `tenant_id` = 42: {}",
1244 sql
1245 );
1246 }
1247
1248 #[test]
1249 fn test_soft_delete_and_tenant_combined() {
1250 let mut w = LambdaWrapper::<User>::new("users")
1252 .with_soft_delete(SoftDeleteConfig {
1253 column: "deleted".to_string(),
1254 not_deleted_value: Value::I64(0),
1255 deleted_value: Value::I64(1),
1256 })
1257 .with_tenant(TenantConfig {
1258 column: "tenant_id".to_string(),
1259 tenant_id: Value::I64(99),
1260 });
1261 w.eq(UserColumns::Id, Value::I64(7));
1262
1263 let sql = w.build_select();
1265 assert!(sql.contains("`deleted` = 0"), "应包含软删除条件: {}", sql);
1266 assert!(sql.contains("`tenant_id` = 99"), "应包含多租户条件: {}", sql);
1267 assert!(sql.contains("`id` = 7"), "应包含用户条件: {}", sql);
1268
1269 let del_sql = w.build_delete();
1271 assert!(del_sql.starts_with("UPDATE `users` SET `deleted` = 1"));
1272 assert!(del_sql.contains("`tenant_id` = 99"));
1273 assert!(del_sql.contains("`deleted` = 0"));
1274 assert!(del_sql.contains("`id` = 7"));
1275 }
1276
1277 #[test]
1278 fn test_no_soft_delete_no_tenant_backward_compat() {
1279 let mut w = LambdaWrapper::<User>::new("users");
1281 w.eq(UserColumns::Id, Value::I64(1));
1282 let sql = w.build_select();
1283 assert!(!sql.contains("deleted"));
1284 assert!(!sql.contains("tenant_id"));
1285
1286 let del_sql = w.build_delete();
1287 assert!(del_sql.starts_with("DELETE FROM"));
1288 }
1289
1290 #[test]
1293 fn test_with_postgres_dialect() {
1294 let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1295 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1296 w.eq(UserColumns::Id, Value::I64(1));
1297 let sql = w.build_select();
1298 assert!(sql.contains("\"users\""));
1299 assert!(sql.contains("\"id\" = 1"));
1300 }
1301
1302 #[test]
1303 fn test_postgres_select() {
1304 let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1305 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1306 w.select(UserColumns::Id).select(UserColumns::Name);
1307 let sql = w.build_select();
1308 assert!(sql.contains("\"id\", \"name\""));
1309 }
1310
1311 #[test]
1314 fn test_complex_query() {
1315 let mut w = LambdaWrapper::<User>::new("users");
1316 w.select(UserColumns::Id)
1317 .select(UserColumns::Name)
1318 .select(UserColumns::Age)
1319 .gt(UserColumns::Age, Value::I64(18))
1320 .like(UserColumns::Name, Value::String("a%".to_string()))
1321 .is_not_null(UserColumns::Email)
1322 .order_by_desc(UserColumns::Id)
1323 .limit(10)
1324 .offset(20);
1325
1326 let sql = w.build_select();
1327 assert!(sql.contains("SELECT `id`, `name`, `age` FROM `users`"));
1328 assert!(sql.contains("`age` > 18"));
1329 assert!(sql.contains("`name` LIKE 'a%'"));
1330 assert!(sql.contains("`email` IS NOT NULL"));
1331 assert!(sql.contains("ORDER BY `id` DESC"));
1332 assert!(sql.contains("LIMIT 10"));
1333 assert!(sql.contains("OFFSET 20"));
1334 }
1335
1336 #[test]
1337 fn test_reset_clears_all() {
1338 let mut w = LambdaWrapper::<User>::new("users");
1339 w.select(UserColumns::Id)
1340 .eq(UserColumns::Id, Value::I64(1))
1341 .order_by_asc(UserColumns::Name)
1342 .limit(10);
1343
1344 w.reset();
1345 assert_eq!(w.select_count(), 0);
1346 assert_eq!(w.where_count(), 0);
1347 let sql = w.build_select();
1348 assert!(sql.contains("SELECT * FROM `users`"));
1349 assert!(!sql.contains("WHERE"));
1350 assert!(!sql.contains("ORDER BY"));
1351 assert!(!sql.contains("LIMIT"));
1352 }
1353
1354 #[test]
1357 fn test_different_models_dont_share_columns() {
1358 let mut user_w = LambdaWrapper::<User>::new("users");
1359 user_w.eq(UserColumns::Id, Value::I64(1));
1360
1361 let mut order_w = LambdaWrapper::<Order>::new("orders");
1362 order_w.eq(OrderColumns::OrderId, Value::I64(100));
1363
1364 let user_sql = user_w.build_select();
1365 let order_sql = order_w.build_select();
1366
1367 assert!(user_sql.contains("`users`"));
1368 assert!(user_sql.contains("`id` = 1"));
1369 assert!(order_sql.contains("`orders`"));
1370 assert!(order_sql.contains("`order_id` = 100"));
1371 }
1372
1373 #[test]
1376 fn test_type_safety_compile_time_check() {
1377 let mut w = LambdaWrapper::<User>::new("users");
1383 w.eq(UserColumns::Id, Value::I64(1));
1384 assert_eq!(w.where_count(), 1);
1385 }
1386
1387 #[test]
1388 fn test_string_value_escape() {
1389 let mut w = LambdaWrapper::<User>::new("users");
1391 w.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1392 let sql = w.build_select();
1393 assert!(sql.contains("'O\\'Brien'"));
1395
1396 let pg_dialect: Box<dyn Dialect> = get_dialect(DbType::PostgreSQL).unwrap();
1398 let mut w_pg = LambdaWrapper::<User>::with_dialect("users", pg_dialect);
1399 w_pg.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1400 let sql_pg = w_pg.build_select();
1401 assert!(sql_pg.contains("'O''Brien'"));
1402 }
1403
1404 #[test]
1407 fn test_with_real_mysql_dialect() {
1408 let dialect: Box<dyn Dialect> = get_dialect(DbType::MySQL).unwrap();
1409 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1410 w.select(UserColumns::Id)
1411 .select(UserColumns::Name)
1412 .eq(UserColumns::Id, Value::I64(42))
1413 .order_by_desc(UserColumns::Id);
1414
1415 let sql = w.build_select();
1416 assert!(sql.contains("SELECT `id`, `name` FROM `users`"));
1417 assert!(sql.contains("`id` = 42"));
1418 assert!(sql.contains("ORDER BY `id` DESC"));
1419 }
1420}