1use std::marker::PhantomData;
60use sz_orm_model::Value;
61use sz_orm_model::{Dialect, MySqlDialect};
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 sz_orm_model::sql_safety::validate_identifier(&config.column, "soft_delete column")
345 .is_ok()
346 {
347 self.soft_delete = Some(config);
348 }
349 self
350 }
351
352 pub fn with_tenant(mut self, config: TenantConfig) -> Self {
372 if sz_orm_model::sql_safety::validate_identifier(&config.column, "tenant column").is_ok() {
374 self.tenant = Some(config);
375 }
376 self
377 }
378
379 fn collect_implicit_wheres(&self) -> Vec<String> {
382 let mut implicit = Vec::new();
383 if let Some(ref sd) = self.soft_delete {
384 let col = self.dialect.quote(&sd.column);
385 let val = sd
386 .not_deleted_value
387 .to_param_with_dialect(self.dialect.as_ref());
388 implicit.push(format!("{} = {}", col, val));
389 }
390 if let Some(ref t) = self.tenant {
391 let col = self.dialect.quote(&t.column);
392 let val = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
393 implicit.push(format!("{} = {}", col, val));
394 }
395 implicit
396 }
397
398 fn render_where_clause(&self) -> String {
400 let user_conds: Vec<String> = self
401 .wheres
402 .iter()
403 .map(|w| w.render(self.dialect.as_ref()))
404 .collect();
405 let implicit_conds = self.collect_implicit_wheres();
406 let all_conds: Vec<String> = user_conds.into_iter().chain(implicit_conds).collect();
407 if all_conds.is_empty() {
408 String::new()
409 } else {
410 format!(" WHERE {}", all_conds.join(" AND "))
411 }
412 }
413
414 pub fn select<C: Column<M>>(&mut self, col: C) -> &mut Self {
421 let name = col.name();
422 if sz_orm_model::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
425 self.selects.push(name.to_string());
426 }
427 self
428 }
429
430 pub fn select_many<C: Column<M>>(&mut self, cols: &[C]) -> &mut Self {
434 for c in cols {
435 let name = c.name();
436 if sz_orm_model::sql_safety::validate_identifier(name, "lambda select column").is_ok() {
437 self.selects.push(name.to_string());
438 }
439 }
440 self
441 }
442
443 pub fn select_all(&mut self) -> &mut Self {
445 self.selects.clear();
446 self
447 }
448
449 pub fn eq<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
453 self.wheres
454 .push(WhereClause::Eq(col.name().to_string(), value));
455 self
456 }
457
458 pub fn ne<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
460 self.wheres
461 .push(WhereClause::Ne(col.name().to_string(), value));
462 self
463 }
464
465 pub fn gt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
467 self.wheres
468 .push(WhereClause::Gt(col.name().to_string(), value));
469 self
470 }
471
472 pub fn ge<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
474 self.wheres
475 .push(WhereClause::Ge(col.name().to_string(), value));
476 self
477 }
478
479 pub fn lt<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
481 self.wheres
482 .push(WhereClause::Lt(col.name().to_string(), value));
483 self
484 }
485
486 pub fn le<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
488 self.wheres
489 .push(WhereClause::Le(col.name().to_string(), value));
490 self
491 }
492
493 pub fn like<C: Column<M>>(&mut self, col: C, value: Value) -> &mut Self {
495 self.wheres
496 .push(WhereClause::Like(col.name().to_string(), value));
497 self
498 }
499
500 pub fn is_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
502 self.wheres
503 .push(WhereClause::IsNull(col.name().to_string()));
504 self
505 }
506
507 pub fn is_not_null<C: Column<M>>(&mut self, col: C) -> &mut Self {
509 self.wheres
510 .push(WhereClause::IsNotNull(col.name().to_string()));
511 self
512 }
513
514 pub fn r#in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
516 self.wheres
517 .push(WhereClause::In(col.name().to_string(), values));
518 self
519 }
520
521 pub fn not_in<C: Column<M>>(&mut self, col: C, values: Vec<Value>) -> &mut Self {
523 self.wheres
524 .push(WhereClause::NotIn(col.name().to_string(), values));
525 self
526 }
527
528 pub fn between<C: Column<M>>(&mut self, col: C, a: Value, b: Value) -> &mut Self {
530 self.wheres
531 .push(WhereClause::Between(col.name().to_string(), a, b));
532 self
533 }
534
535 pub fn raw_where(&mut self, sql: impl Into<String>) -> &mut Self {
543 self.wheres.push(WhereClause::Raw(sql.into()));
544 self
545 }
546
547 pub fn order_by_asc<C: Column<M>>(&mut self, col: C) -> &mut Self {
551 self.orders.push(OrderBy {
552 column: col.name().to_string(),
553 direction: OrderDirection::Asc,
554 });
555 self
556 }
557
558 pub fn order_by_desc<C: Column<M>>(&mut self, col: C) -> &mut Self {
560 self.orders.push(OrderBy {
561 column: col.name().to_string(),
562 direction: OrderDirection::Desc,
563 });
564 self
565 }
566
567 pub fn limit(&mut self, n: u64) -> &mut Self {
569 self.limit = Some(n);
570 self
571 }
572
573 pub fn offset(&mut self, n: u64) -> &mut Self {
575 self.offset = Some(n);
576 self
577 }
578
579 pub fn page(&mut self, page: u64, page_size: u64) -> &mut Self {
583 self.limit = Some(page_size);
584 if page > 1 {
585 self.offset = Some((page - 1) * page_size);
586 } else {
587 self.offset = None;
588 }
589 self
590 }
591
592 pub fn build_select(&self) -> String {
598 let quoted_table = self.dialect.quote(&self.table);
599
600 let select_sql = if self.selects.is_empty() {
602 "*".to_string()
603 } else {
604 self.selects
605 .iter()
606 .map(|c| self.dialect.quote(c))
607 .collect::<Vec<_>>()
608 .join(", ")
609 };
610
611 let mut sql = format!("SELECT {} FROM {}", select_sql, quoted_table);
612
613 sql.push_str(&self.render_where_clause());
615
616 if !self.orders.is_empty() {
618 let orders: Vec<String> = self
619 .orders
620 .iter()
621 .map(|o| {
622 let dir = match o.direction {
623 OrderDirection::Asc => "ASC",
624 OrderDirection::Desc => "DESC",
625 };
626 format!("{} {}", self.dialect.quote(&o.column), dir)
627 })
628 .collect();
629 sql.push_str(" ORDER BY ");
630 sql.push_str(&orders.join(", "));
631 }
632
633 if let Some(l) = self.limit {
635 sql.push_str(&format!(" LIMIT {}", l));
636 }
637 if let Some(o) = self.offset {
638 sql.push_str(&format!(" OFFSET {}", o));
639 }
640
641 sql
642 }
643
644 pub fn build_count(&self) -> String {
648 let quoted_table = self.dialect.quote(&self.table);
649 let mut sql = format!("SELECT COUNT(*) FROM {}", quoted_table);
650 sql.push_str(&self.render_where_clause());
651 sql
652 }
653
654 pub fn build_exists(&self) -> String {
658 let inner = self.build_select();
659 let inner = if let Some(pos) = inner.find(" FROM ") {
661 format!("SELECT 1{}", &inner[pos..])
662 } else {
663 inner
664 };
665 format!("SELECT EXISTS({}) AS exists_flag", inner)
666 }
667
668 pub fn build_delete(&self) -> String {
675 let quoted_table = self.dialect.quote(&self.table);
676
677 if let Some(ref sd) = self.soft_delete {
679 let col = self.dialect.quote(&sd.column);
680 let val = sd
681 .deleted_value
682 .to_param_with_dialect(self.dialect.as_ref());
683 let mut sql = format!("UPDATE {} SET {} = {}", quoted_table, col, val);
684
685 let user_conds: Vec<String> = self
687 .wheres
688 .iter()
689 .map(|w| w.render(self.dialect.as_ref()))
690 .collect();
691 let mut all_conds = user_conds;
692 if let Some(ref t) = self.tenant {
694 let tcol = self.dialect.quote(&t.column);
695 let tval = t.tenant_id.to_param_with_dialect(self.dialect.as_ref());
696 all_conds.push(format!("{} = {}", tcol, tval));
697 }
698 let not_deleted_cond = format!(
700 "{} = {}",
701 col,
702 sd.not_deleted_value
703 .to_param_with_dialect(self.dialect.as_ref())
704 );
705 all_conds.push(not_deleted_cond);
706
707 if !all_conds.is_empty() {
708 sql.push_str(" WHERE ");
709 sql.push_str(&all_conds.join(" AND "));
710 }
711 return sql;
712 }
713
714 let mut sql = format!("DELETE FROM {}", quoted_table);
716 sql.push_str(&self.render_where_clause());
717 sql
718 }
719
720 pub fn where_count(&self) -> usize {
724 self.wheres.len()
725 }
726
727 pub fn select_count(&self) -> usize {
729 self.selects.len()
730 }
731
732 pub fn table(&self) -> &str {
734 &self.table
735 }
736
737 pub fn reset(&mut self) -> &mut Self {
739 self.selects.clear();
740 self.wheres.clear();
741 self.orders.clear();
742 self.limit = None;
743 self.offset = None;
744 self
745 }
746}
747
748#[macro_export]
794macro_rules! define_columns {
795 (
796 $columns_struct:ident for $model:ident table = $table:literal {
797 $( $field:ident => $name:literal ),* $(,)?
798 }
799 ) => {
800 #[derive(Debug, Clone, Copy)]
804 pub struct $columns_struct {
805 pub name: &'static str,
807 pub table: &'static str,
809 }
810
811 impl $crate::lambda::Column<$model> for $columns_struct {
812 fn name(&self) -> &'static str {
813 self.name
814 }
815
816 fn table(&self) -> &'static str {
817 self.table
818 }
819 }
820
821 impl $columns_struct {
822 $(
824 #[allow(non_upper_case_globals, dead_code)]
825 pub const $field: $columns_struct = $columns_struct { name: $name, table: $table };
826 )*
827 }
828 };
829}
830
831#[cfg(test)]
836mod tests {
837 use super::*;
838 use sz_orm_model::dialect::get_dialect;
839 use sz_orm_model::DbType;
840 use sz_orm_model::PostgreSqlDialect;
841
842 struct User;
844
845 define_columns! {
847 UserColumns for User table = "users" {
848 Id => "id",
849 Name => "name",
850 Age => "age",
851 Email => "email",
852 }
853 }
854
855 struct Order;
857
858 define_columns! {
859 OrderColumns for Order table = "orders" {
860 OrderId => "order_id",
861 UserId => "user_id",
862 Total => "total",
863 }
864 }
865
866 #[test]
869 fn test_column_name_and_table() {
870 assert_eq!(UserColumns::Id.name(), "id");
871 assert_eq!(UserColumns::Id.table(), "users");
872 assert_eq!(UserColumns::Name.name(), "name");
873 assert_eq!(UserColumns::Age.name(), "age");
874 assert_eq!(UserColumns::Email.name(), "email");
875 }
876
877 #[test]
878 fn test_column_for_different_models() {
879 assert_eq!(OrderColumns::OrderId.name(), "order_id");
880 assert_eq!(OrderColumns::OrderId.table(), "orders");
881 assert_eq!(OrderColumns::UserId.name(), "user_id");
882 }
883
884 #[test]
887 fn test_new_wrapper() {
888 let w = LambdaWrapper::<User>::new("users");
889 assert_eq!(w.table(), "users");
890 assert_eq!(w.where_count(), 0);
891 assert_eq!(w.select_count(), 0);
892 }
893
894 #[test]
895 fn test_select_single() {
896 let mut w = LambdaWrapper::<User>::new("users");
897 w.select(UserColumns::Id);
898 let sql = w.build_select();
899 assert!(sql.contains("SELECT `id` FROM `users`"));
900 }
901
902 #[test]
903 fn test_select_multiple() {
904 let mut w = LambdaWrapper::<User>::new("users");
905 w.select(UserColumns::Id)
906 .select(UserColumns::Name)
907 .select(UserColumns::Age);
908 let sql = w.build_select();
909 assert!(sql.contains("`id`, `name`, `age`"));
910 }
911
912 #[test]
913 fn test_select_many() {
914 let mut w = LambdaWrapper::<User>::new("users");
915 w.select_many(&[UserColumns::Id, UserColumns::Name, UserColumns::Age]);
916 let sql = w.build_select();
917 assert!(sql.contains("`id`, `name`, `age`"));
918 }
919
920 #[test]
921 fn test_select_all_clears_selects() {
922 let mut w = LambdaWrapper::<User>::new("users");
923 w.select(UserColumns::Id);
924 assert_eq!(w.select_count(), 1);
925 w.select_all();
926 assert_eq!(w.select_count(), 0);
927 let sql = w.build_select();
928 assert!(sql.contains("SELECT * FROM"));
929 }
930
931 #[test]
932 fn test_default_select_is_star() {
933 let w = LambdaWrapper::<User>::new("users");
934 let sql = w.build_select();
935 assert!(sql.contains("SELECT * FROM `users`"));
936 }
937
938 #[test]
941 fn test_where_eq() {
942 let mut w = LambdaWrapper::<User>::new("users");
943 w.eq(UserColumns::Id, Value::I64(1));
944 let sql = w.build_select();
945 assert!(sql.contains("WHERE `id` = 1"));
946 }
947
948 #[test]
949 fn test_where_ne() {
950 let mut w = LambdaWrapper::<User>::new("users");
951 w.ne(UserColumns::Id, Value::I64(1));
952 let sql = w.build_select();
953 assert!(sql.contains("`id` != 1"));
954 }
955
956 #[test]
957 fn test_where_gt_ge_lt_le() {
958 let mut w = LambdaWrapper::<User>::new("users");
959 w.gt(UserColumns::Age, Value::I64(18))
960 .ge(UserColumns::Age, Value::I64(20))
961 .lt(UserColumns::Age, Value::I64(65))
962 .le(UserColumns::Age, Value::I64(60));
963 let sql = w.build_select();
964 assert!(sql.contains("`age` > 18"));
965 assert!(sql.contains("`age` >= 20"));
966 assert!(sql.contains("`age` < 65"));
967 assert!(sql.contains("`age` <= 60"));
968 }
969
970 #[test]
971 fn test_where_like() {
972 let mut w = LambdaWrapper::<User>::new("users");
973 w.like(UserColumns::Name, Value::String("%alice%".to_string()));
974 let sql = w.build_select();
975 assert!(sql.contains("`name` LIKE '%alice%'"));
976 }
977
978 #[test]
979 fn test_where_is_null() {
980 let mut w = LambdaWrapper::<User>::new("users");
981 w.is_null(UserColumns::Email);
982 let sql = w.build_select();
983 assert!(sql.contains("`email` IS NULL"));
984 }
985
986 #[test]
987 fn test_where_is_not_null() {
988 let mut w = LambdaWrapper::<User>::new("users");
989 w.is_not_null(UserColumns::Email);
990 let sql = w.build_select();
991 assert!(sql.contains("`email` IS NOT NULL"));
992 }
993
994 #[test]
995 fn test_where_in() {
996 let mut w = LambdaWrapper::<User>::new("users");
997 w.r#in(
998 UserColumns::Id,
999 vec![Value::I64(1), Value::I64(2), Value::I64(3)],
1000 );
1001 let sql = w.build_select();
1002 assert!(sql.contains("`id` IN (1, 2, 3)"));
1003 }
1004
1005 #[test]
1006 fn test_where_not_in() {
1007 let mut w = LambdaWrapper::<User>::new("users");
1008 w.not_in(UserColumns::Id, vec![Value::I64(1), Value::I64(2)]);
1009 let sql = w.build_select();
1010 assert!(sql.contains("`id` NOT IN (1, 2)"));
1011 }
1012
1013 #[test]
1014 fn test_where_between() {
1015 let mut w = LambdaWrapper::<User>::new("users");
1016 w.between(UserColumns::Age, Value::I64(18), Value::I64(65));
1017 let sql = w.build_select();
1018 assert!(sql.contains("`age` BETWEEN 18 AND 65"));
1019 }
1020
1021 #[test]
1022 fn test_where_multiple_anded() {
1023 let mut w = LambdaWrapper::<User>::new("users");
1024 w.eq(UserColumns::Id, Value::I64(1))
1025 .gt(UserColumns::Age, Value::I64(18))
1026 .like(UserColumns::Name, Value::String("alice%".to_string()));
1027 let sql = w.build_select();
1028 assert!(sql.contains("`id` = 1"));
1029 assert!(sql.contains("`age` > 18"));
1030 assert!(sql.contains("`name` LIKE 'alice%'"));
1031 assert!(sql.contains(" AND "));
1033 }
1034
1035 #[test]
1036 fn test_where_raw() {
1037 let mut w = LambdaWrapper::<User>::new("users");
1038 w.raw_where("name = 'alice' OR name = 'bob'");
1039 let sql = w.build_select();
1040 assert!(sql.contains("name = 'alice' OR name = 'bob'"));
1041 }
1042
1043 #[test]
1046 fn test_order_by_asc() {
1047 let mut w = LambdaWrapper::<User>::new("users");
1048 w.order_by_asc(UserColumns::Name);
1049 let sql = w.build_select();
1050 assert!(sql.contains("ORDER BY `name` ASC"));
1051 }
1052
1053 #[test]
1054 fn test_order_by_desc() {
1055 let mut w = LambdaWrapper::<User>::new("users");
1056 w.order_by_desc(UserColumns::Id);
1057 let sql = w.build_select();
1058 assert!(sql.contains("ORDER BY `id` DESC"));
1059 }
1060
1061 #[test]
1062 fn test_order_by_multiple() {
1063 let mut w = LambdaWrapper::<User>::new("users");
1064 w.order_by_asc(UserColumns::Name)
1065 .order_by_desc(UserColumns::Id);
1066 let sql = w.build_select();
1067 assert!(sql.contains("ORDER BY `name` ASC, `id` DESC"));
1068 }
1069
1070 #[test]
1071 fn test_limit() {
1072 let mut w = LambdaWrapper::<User>::new("users");
1073 w.limit(10);
1074 let sql = w.build_select();
1075 assert!(sql.contains("LIMIT 10"));
1076 }
1077
1078 #[test]
1079 fn test_offset() {
1080 let mut w = LambdaWrapper::<User>::new("users");
1081 w.limit(10).offset(20);
1082 let sql = w.build_select();
1083 assert!(sql.contains("LIMIT 10"));
1084 assert!(sql.contains("OFFSET 20"));
1085 }
1086
1087 #[test]
1088 fn test_page() {
1089 let mut w = LambdaWrapper::<User>::new("users");
1090 w.page(3, 20); let sql = w.build_select();
1092 assert!(sql.contains("LIMIT 20"));
1093 assert!(sql.contains("OFFSET 40")); }
1095
1096 #[test]
1097 fn test_page_1_no_offset() {
1098 let mut w = LambdaWrapper::<User>::new("users");
1099 w.page(1, 10);
1100 let sql = w.build_select();
1101 assert!(sql.contains("LIMIT 10"));
1102 assert!(!sql.contains("OFFSET")); }
1104
1105 #[test]
1108 fn test_build_count() {
1109 let mut w = LambdaWrapper::<User>::new("users");
1110 w.gt(UserColumns::Age, Value::I64(18));
1111 let sql = w.build_count();
1112 assert!(sql.contains("SELECT COUNT(*) FROM `users`"));
1113 assert!(sql.contains("`age` > 18"));
1114 assert!(!sql.contains("ORDER BY"));
1116 assert!(!sql.contains("LIMIT"));
1117 }
1118
1119 #[test]
1120 fn test_build_exists() {
1121 let mut w = LambdaWrapper::<User>::new("users");
1122 w.eq(UserColumns::Id, Value::I64(1));
1123 let sql = w.build_exists();
1124 assert!(sql.starts_with("SELECT EXISTS("));
1125 assert!(sql.contains("SELECT 1 FROM `users`"));
1126 assert!(sql.contains("`id` = 1"));
1127 assert!(sql.ends_with(") AS exists_flag"));
1128 }
1129
1130 #[test]
1131 fn test_build_delete() {
1132 let mut w = LambdaWrapper::<User>::new("users");
1133 w.eq(UserColumns::Id, Value::I64(1));
1134 let sql = w.build_delete();
1135 assert!(sql.starts_with("DELETE FROM `users`"));
1136 assert!(sql.contains("WHERE `id` = 1"));
1137 }
1138
1139 #[test]
1142 fn test_soft_delete_select_appends_filter() {
1143 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1145 column: "deleted".to_string(),
1146 not_deleted_value: Value::I64(0),
1147 deleted_value: Value::I64(1),
1148 });
1149 let sql = w.build_select();
1150 assert!(
1151 sql.contains("`deleted` = 0"),
1152 "软删除 SELECT 应追加 `deleted` = 0: {}",
1153 sql
1154 );
1155 }
1156
1157 #[test]
1158 fn test_soft_delete_count_appends_filter() {
1159 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1161 column: "deleted".to_string(),
1162 not_deleted_value: Value::I64(0),
1163 deleted_value: Value::I64(1),
1164 });
1165 let sql = w.build_count();
1166 assert!(
1167 sql.contains("`deleted` = 0"),
1168 "软删除 COUNT 应追加 `deleted` = 0: {}",
1169 sql
1170 );
1171 }
1172
1173 #[test]
1174 fn test_soft_delete_exists_appends_filter() {
1175 let w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1177 column: "deleted".to_string(),
1178 not_deleted_value: Value::I64(0),
1179 deleted_value: Value::I64(1),
1180 });
1181 let sql = w.build_exists();
1182 assert!(
1183 sql.contains("`deleted` = 0"),
1184 "软删除 EXISTS 应追加 `deleted` = 0: {}",
1185 sql
1186 );
1187 }
1188
1189 #[test]
1190 fn test_soft_delete_delete_becomes_update() {
1191 let mut w = LambdaWrapper::<User>::new("users").with_soft_delete(SoftDeleteConfig {
1193 column: "deleted".to_string(),
1194 not_deleted_value: Value::I64(0),
1195 deleted_value: Value::I64(1),
1196 });
1197 w.eq(UserColumns::Id, Value::I64(42));
1198 let sql = w.build_delete();
1199 assert!(
1200 sql.starts_with("UPDATE `users` SET `deleted` = 1"),
1201 "软删除应为 UPDATE:{}",
1202 sql
1203 );
1204 assert!(sql.contains("`id` = 42"), "应保留用户 WHERE 条件:{}", sql);
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!(
1267 sql.contains("`tenant_id` = 99"),
1268 "应包含多租户条件: {}",
1269 sql
1270 );
1271 assert!(sql.contains("`id` = 7"), "应包含用户条件: {}", sql);
1272
1273 let del_sql = w.build_delete();
1275 assert!(del_sql.starts_with("UPDATE `users` SET `deleted` = 1"));
1276 assert!(del_sql.contains("`tenant_id` = 99"));
1277 assert!(del_sql.contains("`deleted` = 0"));
1278 assert!(del_sql.contains("`id` = 7"));
1279 }
1280
1281 #[test]
1282 fn test_no_soft_delete_no_tenant_backward_compat() {
1283 let mut w = LambdaWrapper::<User>::new("users");
1285 w.eq(UserColumns::Id, Value::I64(1));
1286 let sql = w.build_select();
1287 assert!(!sql.contains("deleted"));
1288 assert!(!sql.contains("tenant_id"));
1289
1290 let del_sql = w.build_delete();
1291 assert!(del_sql.starts_with("DELETE FROM"));
1292 }
1293
1294 #[test]
1297 fn test_with_postgres_dialect() {
1298 let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1299 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1300 w.eq(UserColumns::Id, Value::I64(1));
1301 let sql = w.build_select();
1302 assert!(sql.contains("\"users\""));
1303 assert!(sql.contains("\"id\" = 1"));
1304 }
1305
1306 #[test]
1307 fn test_postgres_select() {
1308 let dialect: Box<dyn Dialect> = Box::new(PostgreSqlDialect);
1309 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1310 w.select(UserColumns::Id).select(UserColumns::Name);
1311 let sql = w.build_select();
1312 assert!(sql.contains("\"id\", \"name\""));
1313 }
1314
1315 #[test]
1318 fn test_complex_query() {
1319 let mut w = LambdaWrapper::<User>::new("users");
1320 w.select(UserColumns::Id)
1321 .select(UserColumns::Name)
1322 .select(UserColumns::Age)
1323 .gt(UserColumns::Age, Value::I64(18))
1324 .like(UserColumns::Name, Value::String("a%".to_string()))
1325 .is_not_null(UserColumns::Email)
1326 .order_by_desc(UserColumns::Id)
1327 .limit(10)
1328 .offset(20);
1329
1330 let sql = w.build_select();
1331 assert!(sql.contains("SELECT `id`, `name`, `age` FROM `users`"));
1332 assert!(sql.contains("`age` > 18"));
1333 assert!(sql.contains("`name` LIKE 'a%'"));
1334 assert!(sql.contains("`email` IS NOT NULL"));
1335 assert!(sql.contains("ORDER BY `id` DESC"));
1336 assert!(sql.contains("LIMIT 10"));
1337 assert!(sql.contains("OFFSET 20"));
1338 }
1339
1340 #[test]
1341 fn test_reset_clears_all() {
1342 let mut w = LambdaWrapper::<User>::new("users");
1343 w.select(UserColumns::Id)
1344 .eq(UserColumns::Id, Value::I64(1))
1345 .order_by_asc(UserColumns::Name)
1346 .limit(10);
1347
1348 w.reset();
1349 assert_eq!(w.select_count(), 0);
1350 assert_eq!(w.where_count(), 0);
1351 let sql = w.build_select();
1352 assert!(sql.contains("SELECT * FROM `users`"));
1353 assert!(!sql.contains("WHERE"));
1354 assert!(!sql.contains("ORDER BY"));
1355 assert!(!sql.contains("LIMIT"));
1356 }
1357
1358 #[test]
1361 fn test_different_models_dont_share_columns() {
1362 let mut user_w = LambdaWrapper::<User>::new("users");
1363 user_w.eq(UserColumns::Id, Value::I64(1));
1364
1365 let mut order_w = LambdaWrapper::<Order>::new("orders");
1366 order_w.eq(OrderColumns::OrderId, Value::I64(100));
1367
1368 let user_sql = user_w.build_select();
1369 let order_sql = order_w.build_select();
1370
1371 assert!(user_sql.contains("`users`"));
1372 assert!(user_sql.contains("`id` = 1"));
1373 assert!(order_sql.contains("`orders`"));
1374 assert!(order_sql.contains("`order_id` = 100"));
1375 }
1376
1377 #[test]
1380 fn test_type_safety_compile_time_check() {
1381 let mut w = LambdaWrapper::<User>::new("users");
1387 w.eq(UserColumns::Id, Value::I64(1));
1388 assert_eq!(w.where_count(), 1);
1389 }
1390
1391 #[test]
1392 fn test_string_value_escape() {
1393 let mut w = LambdaWrapper::<User>::new("users");
1395 w.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1396 let sql = w.build_select();
1397 assert!(sql.contains("'O\\'Brien'"));
1399
1400 let pg_dialect: Box<dyn Dialect> = get_dialect(DbType::PostgreSQL).unwrap();
1402 let mut w_pg = LambdaWrapper::<User>::with_dialect("users", pg_dialect);
1403 w_pg.eq(UserColumns::Name, Value::String("O'Brien".to_string()));
1404 let sql_pg = w_pg.build_select();
1405 assert!(sql_pg.contains("'O''Brien'"));
1406 }
1407
1408 #[test]
1411 fn test_with_real_mysql_dialect() {
1412 let dialect: Box<dyn Dialect> = get_dialect(DbType::MySQL).unwrap();
1413 let mut w = LambdaWrapper::<User>::with_dialect("users", dialect);
1414 w.select(UserColumns::Id)
1415 .select(UserColumns::Name)
1416 .eq(UserColumns::Id, Value::I64(42))
1417 .order_by_desc(UserColumns::Id);
1418
1419 let sql = w.build_select();
1420 assert!(sql.contains("SELECT `id`, `name` FROM `users`"));
1421 assert!(sql.contains("`id` = 42"));
1422 assert!(sql.contains("ORDER BY `id` DESC"));
1423 }
1424}