1use crate::Value;
51use std::fmt::Debug;
52use std::sync::RwLock;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum WhereOp {
61 Eq,
63 Ne,
65 Gt,
67 Ge,
69 Lt,
71 Le,
73 Like,
75 In,
77 NotIn,
79 IsNull,
81 IsNotNull,
83 Between,
85}
86
87impl WhereOp {
88 pub fn name(&self) -> &'static str {
90 match self {
91 WhereOp::Eq => "eq",
92 WhereOp::Ne => "ne",
93 WhereOp::Gt => "gt",
94 WhereOp::Ge => "ge",
95 WhereOp::Lt => "lt",
96 WhereOp::Le => "le",
97 WhereOp::Like => "like",
98 WhereOp::In => "in",
99 WhereOp::NotIn => "not_in",
100 WhereOp::IsNull => "is_null",
101 WhereOp::IsNotNull => "is_not_null",
102 WhereOp::Between => "between",
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct WhereCondition {
110 pub field: String,
111 pub op: WhereOp,
112 pub value: Value,
113 pub extra_values: Vec<Value>,
115}
116
117impl WhereCondition {
118 pub fn new(field: impl Into<String>, op: WhereOp, value: Value) -> Self {
120 Self {
121 field: field.into(),
122 op,
123 value,
124 extra_values: Vec::new(),
125 }
126 }
127
128 pub fn null_check(field: impl Into<String>, op: WhereOp) -> Self {
130 Self {
131 field: field.into(),
132 op,
133 value: Value::Null,
134 extra_values: Vec::new(),
135 }
136 }
137
138 pub fn in_op(field: impl Into<String>, op: WhereOp, values: Vec<Value>) -> Self {
140 Self {
141 field: field.into(),
142 op,
143 value: Value::Null,
144 extra_values: values,
145 }
146 }
147
148 pub fn between(field: impl Into<String>, low: Value, high: Value) -> Self {
150 Self {
151 field: field.into(),
152 op: WhereOp::Between,
153 value: low,
154 extra_values: vec![high],
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
165pub struct PageResult<T> {
166 pub items: Vec<T>,
167 pub total: u64,
168 pub page: u64,
169 pub page_size: u64,
170}
171
172impl<T> PageResult<T> {
173 pub fn new(items: Vec<T>, total: u64, page: u64, page_size: u64) -> Self {
175 Self {
176 items,
177 total,
178 page,
179 page_size,
180 }
181 }
182
183 pub fn total_pages(&self) -> u64 {
185 if self.page_size == 0 {
186 return 0;
187 }
188 self.total.div_ceil(self.page_size)
189 }
190
191 pub fn has_next(&self) -> bool {
193 self.page < self.total_pages()
194 }
195
196 pub fn has_prev(&self) -> bool {
198 self.page > 1
199 }
200
201 pub fn is_empty(&self) -> bool {
203 self.items.is_empty()
204 }
205
206 pub fn len(&self) -> usize {
208 self.items.len()
209 }
210
211 pub fn map<U, F: Fn(T) -> U>(self, f: F) -> PageResult<U> {
213 PageResult {
214 items: self.items.into_iter().map(f).collect(),
215 total: self.total,
216 page: self.page,
217 page_size: self.page_size,
218 }
219 }
220}
221
222#[derive(Debug, Clone)]
242pub struct BatchUpdateResult<E> {
243 pub updated: Vec<E>,
245 pub skipped: usize,
247}
248
249impl<E> BatchUpdateResult<E> {
250 pub fn new(updated: Vec<E>, skipped: usize) -> Self {
252 Self { updated, skipped }
253 }
254
255 pub fn updated_count(&self) -> usize {
257 self.updated.len()
258 }
259
260 pub fn has_skipped(&self) -> bool {
262 self.skipped > 0
263 }
264
265 pub fn all_updated(&self) -> bool {
267 self.skipped == 0
268 }
269
270 pub fn total(&self) -> usize {
272 self.updated.len() + self.skipped
273 }
274
275 pub fn map<U, F: Fn(E) -> U>(self, f: F) -> BatchUpdateResult<U> {
277 BatchUpdateResult {
278 updated: self.updated.into_iter().map(f).collect(),
279 skipped: self.skipped,
280 }
281 }
282}
283
284impl<E> Default for BatchUpdateResult<E> {
285 fn default() -> Self {
286 Self {
287 updated: Vec::new(),
288 skipped: 0,
289 }
290 }
291}
292
293pub const REPOSITORY_SCAN_LIMIT: usize = 100_000;
306
307#[derive(Debug, Clone, PartialEq)]
309pub enum RepositoryError {
310 NotFound,
312 DatabaseError(String),
314 InvalidEntity(String),
316 Other(String),
318 TooManyRows { actual: usize, limit: usize },
326}
327
328impl std::fmt::Display for RepositoryError {
329 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330 match self {
331 RepositoryError::NotFound => write!(f, "entity not found"),
332 RepositoryError::DatabaseError(msg) => write!(f, "database error: {}", msg),
333 RepositoryError::InvalidEntity(msg) => write!(f, "invalid entity: {}", msg),
334 RepositoryError::Other(msg) => write!(f, "repository error: {}", msg),
335 RepositoryError::TooManyRows { actual, limit } => write!(
336 f,
337 "scan result exceeds safety limit: {} rows (limit: {}); \
338 use QueryBuilder + limit/offset to push pagination to the database",
339 actual, limit
340 ),
341 }
342 }
343}
344
345impl std::error::Error for RepositoryError {}
346
347pub type RepositoryResult<T> = Result<T, RepositoryError>;
349
350pub trait Repository<E>: Send + Sync {
359 type Key: Clone + Debug + PartialEq + Send + Sync;
361
362 fn key_of(&self, entity: &E) -> Self::Key;
364
365 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>>;
367
368 fn find_all(&self) -> RepositoryResult<Vec<E>>;
370
371 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>>;
373
374 fn find_one_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Option<E>> {
376 let mut items = self.find_by(conditions)?;
377 if items.is_empty() {
378 Ok(None)
379 } else {
380 Ok(Some(items.remove(0)))
381 }
382 }
383
384 fn save(&self, entity: E) -> RepositoryResult<E>;
388
389 fn save_many(&self, entities: Vec<E>) -> RepositoryResult<Vec<E>> {
391 let mut saved = Vec::with_capacity(entities.len());
392 for e in entities {
393 saved.push(self.save(e)?);
394 }
395 Ok(saved)
396 }
397
398 fn batch_update(&self, entities: Vec<E>) -> RepositoryResult<BatchUpdateResult<E>> {
416 let mut updated = Vec::with_capacity(entities.len());
417 let mut skipped = 0usize;
418 for e in entities {
419 let key = self.key_of(&e);
420 if self.find_by_id(&key)?.is_some() {
421 updated.push(self.save(e)?);
422 } else {
423 skipped += 1;
424 }
425 }
426 Ok(BatchUpdateResult { updated, skipped })
427 }
428
429 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize>;
431
432 fn delete_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<usize> {
434 let items = self.find_by(conditions)?;
435 let mut count = 0;
436 for item in items {
437 let key = self.key_of(&item);
438 count += self.delete(&key)?;
439 }
440 Ok(count)
441 }
442
443 fn count(&self) -> RepositoryResult<u64>;
445
446 fn count_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<u64> {
448 let items = self.find_by(conditions)?;
449 Ok(items.len() as u64)
450 }
451
452 fn exists(&self, key: &Self::Key) -> RepositoryResult<bool> {
454 Ok(self.find_by_id(key)?.is_some())
455 }
456
457 fn paginate(&self, page: u64, page_size: u64) -> RepositoryResult<PageResult<E>>
465 where
466 E: Clone,
467 {
468 let total = self.count()? as usize;
469 if total > REPOSITORY_SCAN_LIMIT {
470 return Err(RepositoryError::TooManyRows {
471 actual: total,
472 limit: REPOSITORY_SCAN_LIMIT,
473 });
474 }
475 let all = self.find_all()?;
476 let start = ((page.saturating_sub(1)) * page_size) as usize;
477 let end = (start + page_size as usize).min(all.len());
478
479 let items = if start < all.len() {
480 all[start..end].to_vec()
481 } else {
482 Vec::new()
483 };
484
485 Ok(PageResult::new(items, total as u64, page, page_size))
486 }
487
488 fn paginate_by(
495 &self,
496 conditions: &[WhereCondition],
497 page: u64,
498 page_size: u64,
499 ) -> RepositoryResult<PageResult<E>>
500 where
501 E: Clone,
502 {
503 let total = self.count_by(conditions)? as usize;
504 if total > REPOSITORY_SCAN_LIMIT {
505 return Err(RepositoryError::TooManyRows {
506 actual: total,
507 limit: REPOSITORY_SCAN_LIMIT,
508 });
509 }
510 let all = self.find_by(conditions)?;
511 let start = ((page.saturating_sub(1)) * page_size) as usize;
512 let end = (start + page_size as usize).min(all.len());
513
514 let items = if start < all.len() {
515 all[start..end].to_vec()
516 } else {
517 Vec::new()
518 };
519
520 Ok(PageResult::new(items, total as u64, page, page_size))
521 }
522
523 fn find_by_with_or_filter(
559 &self,
560 and: &[WhereCondition],
561 or_filter: &[WhereCondition],
562 ) -> RepositoryResult<Vec<E>>
563 where
564 E: Clone + EntityAttributes,
565 {
566 let items = self.find_by(and)?;
567 if or_filter.is_empty() {
568 return Ok(items);
569 }
570 Ok(items
571 .into_iter()
572 .filter(|e| {
573 or_filter.iter().any(|c| {
574 let attr = e.get_attribute(&c.field);
575 match (attr, c.op) {
576 (None, WhereOp::IsNull) => true,
577 (None, _) => false,
578 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
579 }
580 })
581 })
582 .collect())
583 }
584}
585
586pub struct InMemoryRepository<E: Clone + Send + Sync + 'static> {
595 storage: RwLock<Vec<E>>,
596}
597
598impl<E: Clone + Send + Sync + 'static> InMemoryRepository<E> {
599 pub fn new() -> Self {
601 Self {
602 storage: RwLock::new(Vec::new()),
603 }
604 }
605
606 pub fn from_vec(items: Vec<E>) -> Self {
608 Self {
609 storage: RwLock::new(items),
610 }
611 }
612
613 pub fn len(&self) -> usize {
615 match self.storage.read() {
616 Ok(storage) => storage.len(),
617 Err(_) => 0,
618 }
619 }
620
621 pub fn is_empty(&self) -> bool {
623 self.len() == 0
624 }
625
626 pub fn clear(&self) {
628 if let Ok(mut storage) = self.storage.write() {
629 storage.clear();
630 }
631 }
632}
633
634impl<E: Clone + Send + Sync + 'static> Default for InMemoryRepository<E> {
635 fn default() -> Self {
636 Self::new()
637 }
638}
639
640fn value_matches(value: &Value, op: WhereOp, target: &Value, extras: &[Value]) -> bool {
642 use Value::*;
643 match op {
644 WhereOp::Eq => value == target,
645 WhereOp::Ne => value != target,
646 WhereOp::Gt => match (value, target) {
647 (I64(a), I64(b)) => a > b,
648 (F64(a), F64(b)) => a > b,
649 (F64(a), I64(b)) => a > &(*b as f64),
650 (I64(a), F64(b)) => a > &(*b as i64),
651 (String(a), String(b)) => a > b,
652 _ => false,
653 },
654 WhereOp::Ge => match (value, target) {
655 (I64(a), I64(b)) => a >= b,
656 (F64(a), F64(b)) => a >= b,
657 (String(a), String(b)) => a >= b,
658 _ => false,
659 },
660 WhereOp::Lt => match (value, target) {
661 (I64(a), I64(b)) => a < b,
662 (F64(a), F64(b)) => a < b,
663 (String(a), String(b)) => a < b,
664 _ => false,
665 },
666 WhereOp::Le => match (value, target) {
667 (I64(a), I64(b)) => a <= b,
668 (F64(a), F64(b)) => a <= b,
669 (String(a), String(b)) => a <= b,
670 _ => false,
671 },
672 WhereOp::Like => match (value, target) {
673 (String(a), String(b)) => {
674 let pattern = b.to_lowercase().replace('%', ".*").replace('_', ".");
677 let full_pattern = format!("^{}$", pattern);
678 if let Ok(re) = simple_regex::compile(&full_pattern) {
679 re.is_match(&a.to_lowercase())
680 } else {
681 false
682 }
683 }
684 _ => false,
685 },
686 WhereOp::In => extras.iter().any(|v| v == value),
687 WhereOp::NotIn => !extras.iter().any(|v| v == value),
688 WhereOp::IsNull => matches!(value, Null),
689 WhereOp::IsNotNull => !matches!(value, Null),
690 WhereOp::Between => {
691 if extras.is_empty() {
692 return false;
693 }
694 let low = target;
695 let high = &extras[0];
696 value_matches(value, WhereOp::Ge, low, &[])
698 && value_matches(value, WhereOp::Le, high, &[])
699 }
700 }
701}
702
703pub trait EntityAttributes: Send + Sync {
705 fn get_attribute(&self, field: &str) -> Option<Value>;
707}
708
709impl<E: Clone + Send + Sync + 'static + EntityAttributes> Repository<E> for InMemoryRepository<E> {
711 type Key = Value;
712
713 fn key_of(&self, entity: &E) -> Self::Key {
714 entity.get_attribute("id").unwrap_or(Value::Null)
715 }
716
717 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>> {
718 let storage = match self.storage.read() {
719 Ok(g) => g,
720 Err(_) => return Ok(None),
721 };
722 Ok(storage.iter().find(|e| self.key_of(e) == *key).cloned())
723 }
724
725 fn find_all(&self) -> RepositoryResult<Vec<E>> {
726 let storage = match self.storage.read() {
727 Ok(g) => g,
728 Err(_) => return Ok(Vec::new()),
729 };
730 Ok(storage.clone())
731 }
732
733 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>> {
734 let storage = match self.storage.read() {
735 Ok(g) => g,
736 Err(_) => return Ok(Vec::new()),
737 };
738 let result: Vec<E> = storage
739 .iter()
740 .filter(|e| {
741 conditions.iter().all(|c| {
742 let attr = e.get_attribute(&c.field);
743 match (attr, c.op) {
744 (None, WhereOp::IsNull) => true,
745 (None, _) => false,
746 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
747 }
748 })
749 })
750 .cloned()
751 .collect();
752 Ok(result)
753 }
754
755 fn save(&self, mut entity: E) -> RepositoryResult<E> {
756 let mut storage = match self.storage.write() {
757 Ok(g) => g,
758 Err(_) => return Ok(entity),
759 };
760 let key = self.key_of(&entity);
761
762 let existing_idx = storage.iter().position(|e| self.key_of(e) == key);
764
765 match existing_idx {
766 Some(idx) => {
767 storage[idx] = entity.clone();
768 }
769 None => {
770 storage.push(entity.clone());
771 }
772 }
773 let _ = &mut entity; Ok(entity)
776 }
777
778 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize> {
779 let mut storage = match self.storage.write() {
780 Ok(g) => g,
781 Err(_) => return Ok(0),
782 };
783 let before = storage.len();
784 storage.retain(|e| self.key_of(e) != *key);
785 Ok(before - storage.len())
786 }
787
788 fn count(&self) -> RepositoryResult<u64> {
789 Ok(self.len() as u64)
790 }
791}
792
793mod simple_regex {
798 pub struct Regex {
800 patterns: Vec<Pattern>,
801 }
802
803 enum Pattern {
804 AnyChars, Literal(char), Start, End, }
809
810 pub fn compile(pattern: &str) -> Result<Regex, String> {
811 let mut patterns = Vec::new();
812 let chars: Vec<char> = pattern.chars().collect();
813 let mut i = 0;
814 while i < chars.len() {
815 match chars[i] {
816 '^' => {
817 patterns.push(Pattern::Start);
818 i += 1;
819 }
820 '$' => {
821 patterns.push(Pattern::End);
822 i += 1;
823 }
824 '.' if i + 1 < chars.len() && chars[i + 1] == '*' => {
825 patterns.push(Pattern::AnyChars);
826 i += 2;
827 }
828 c => {
829 patterns.push(Pattern::Literal(c));
830 i += 1;
831 }
832 }
833 }
834 Ok(Regex { patterns })
835 }
836
837 impl Regex {
838 pub fn is_match(&self, text: &str) -> bool {
839 self.match_from(text, 0, 0)
840 }
841
842 fn match_from(&self, text: &str, text_idx: usize, pat_idx: usize) -> bool {
843 let chars: Vec<char> = text.chars().collect();
844 if pat_idx >= self.patterns.len() {
845 return text_idx == chars.len();
846 }
847 match &self.patterns[pat_idx] {
848 Pattern::Start => self.match_from(text, 0, pat_idx + 1),
849 Pattern::End => text_idx == chars.len(),
850 Pattern::Literal(c) => {
851 if text_idx < chars.len() && chars[text_idx] == *c {
852 self.match_from(text, text_idx + 1, pat_idx + 1)
853 } else {
854 false
855 }
856 }
857 Pattern::AnyChars => {
858 for skip in 0..=(chars.len() - text_idx) {
860 if self.match_from(text, text_idx + skip, pat_idx + 1) {
861 return true;
862 }
863 }
864 false
865 }
866 }
867 }
868 }
869}
870
871pub trait EntityKey<K>: Send + Sync {
877 fn key(&self) -> K;
879}
880
881pub struct GenericKeyRepository<E, K>
883where
884 E: Clone + Send + Sync + 'static,
885 K: Clone + Debug + PartialEq + Send + Sync + 'static,
886{
887 storage: RwLock<Vec<E>>,
888 _phantom: std::marker::PhantomData<K>,
889}
890
891impl<E, K> GenericKeyRepository<E, K>
892where
893 E: Clone + Send + Sync + 'static,
894 K: Clone + Debug + PartialEq + Send + Sync + 'static,
895{
896 pub fn new() -> Self {
897 Self {
898 storage: RwLock::new(Vec::new()),
899 _phantom: std::marker::PhantomData,
900 }
901 }
902
903 pub fn from_vec(items: Vec<E>) -> Self {
904 Self {
905 storage: RwLock::new(items),
906 _phantom: std::marker::PhantomData,
907 }
908 }
909
910 pub fn len(&self) -> usize {
911 match self.storage.read() {
912 Ok(g) => g.len(),
913 Err(_) => 0,
914 }
915 }
916
917 pub fn is_empty(&self) -> bool {
918 self.len() == 0
919 }
920
921 pub fn clear(&self) {
922 if let Ok(mut storage) = self.storage.write() {
923 storage.clear();
924 }
925 }
926}
927
928impl<E, K> Default for GenericKeyRepository<E, K>
929where
930 E: Clone + Send + Sync + 'static,
931 K: Clone + Debug + PartialEq + Send + Sync + 'static,
932{
933 fn default() -> Self {
934 Self::new()
935 }
936}
937
938impl<E, K> Repository<E> for GenericKeyRepository<E, K>
939where
940 E: Clone + Send + Sync + 'static + EntityKey<K> + EntityAttributes,
941 K: Clone + Debug + PartialEq + Send + Sync + 'static,
942{
943 type Key = K;
944
945 fn key_of(&self, entity: &E) -> Self::Key {
946 entity.key()
947 }
948
949 fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>> {
950 let storage = match self.storage.read() {
951 Ok(g) => g,
952 Err(_) => return Ok(None),
953 };
954 Ok(storage.iter().find(|e| &e.key() == key).cloned())
955 }
956
957 fn find_all(&self) -> RepositoryResult<Vec<E>> {
958 let storage = match self.storage.read() {
959 Ok(g) => g,
960 Err(_) => return Ok(Vec::new()),
961 };
962 Ok(storage.clone())
963 }
964
965 fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>> {
966 let storage = match self.storage.read() {
967 Ok(g) => g,
968 Err(_) => return Ok(Vec::new()),
969 };
970 let result: Vec<E> = storage
971 .iter()
972 .filter(|e| {
973 conditions.iter().all(|c| {
974 let attr = e.get_attribute(&c.field);
975 match (attr, c.op) {
976 (None, WhereOp::IsNull) => true,
977 (None, _) => false,
978 (Some(v), _) => value_matches(&v, c.op, &c.value, &c.extra_values),
979 }
980 })
981 })
982 .cloned()
983 .collect();
984 Ok(result)
985 }
986
987 fn save(&self, entity: E) -> RepositoryResult<E> {
988 let mut storage = match self.storage.write() {
989 Ok(g) => g,
990 Err(_) => return Ok(entity),
991 };
992 let key = entity.key();
993 let existing_idx = storage.iter().position(|e| e.key() == key);
994 match existing_idx {
995 Some(idx) => {
996 storage[idx] = entity.clone();
997 }
998 None => {
999 storage.push(entity.clone());
1000 }
1001 }
1002 Ok(entity)
1003 }
1004
1005 fn delete(&self, key: &Self::Key) -> RepositoryResult<usize> {
1006 let mut storage = match self.storage.write() {
1007 Ok(g) => g,
1008 Err(_) => return Ok(0),
1009 };
1010 let before = storage.len();
1011 storage.retain(|e| &e.key() != key);
1012 Ok(before - storage.len())
1013 }
1014
1015 fn count(&self) -> RepositoryResult<u64> {
1016 Ok(self.len() as u64)
1017 }
1018}
1019
1020#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 #[derive(Debug, Clone, PartialEq)]
1031 struct User {
1032 id: i64,
1033 name: String,
1034 age: i64,
1035 email: String,
1036 }
1037
1038 impl User {
1039 fn new(id: i64, name: &str, age: i64, email: &str) -> Self {
1040 Self {
1041 id,
1042 name: name.to_string(),
1043 age,
1044 email: email.to_string(),
1045 }
1046 }
1047 }
1048
1049 impl EntityAttributes for User {
1050 fn get_attribute(&self, field: &str) -> Option<Value> {
1051 match field {
1052 "id" => Some(Value::I64(self.id)),
1053 "name" => Some(Value::String(self.name.clone())),
1054 "age" => Some(Value::I64(self.age)),
1055 "email" => Some(Value::String(self.email.clone())),
1056 _ => None,
1057 }
1058 }
1059 }
1060
1061 impl EntityKey<i64> for User {
1062 fn key(&self) -> i64 {
1063 self.id
1064 }
1065 }
1066
1067 #[test]
1070 fn test_where_op_name() {
1071 assert_eq!(WhereOp::Eq.name(), "eq");
1072 assert_eq!(WhereOp::Ne.name(), "ne");
1073 assert_eq!(WhereOp::Gt.name(), "gt");
1074 assert_eq!(WhereOp::Like.name(), "like");
1075 assert_eq!(WhereOp::In.name(), "in");
1076 assert_eq!(WhereOp::IsNull.name(), "is_null");
1077 assert_eq!(WhereOp::Between.name(), "between");
1078 }
1079
1080 #[test]
1081 fn test_where_condition_new() {
1082 let c = WhereCondition::new("age", WhereOp::Ge, Value::I64(18));
1083 assert_eq!(c.field, "age");
1084 assert_eq!(c.op, WhereOp::Ge);
1085 assert_eq!(c.value, Value::I64(18));
1086 assert!(c.extra_values.is_empty());
1087 }
1088
1089 #[test]
1090 fn test_where_condition_null_check() {
1091 let c = WhereCondition::null_check("deleted_at", WhereOp::IsNull);
1092 assert_eq!(c.field, "deleted_at");
1093 assert_eq!(c.op, WhereOp::IsNull);
1094 assert_eq!(c.value, Value::Null);
1095 }
1096
1097 #[test]
1098 fn test_where_condition_in() {
1099 let c = WhereCondition::in_op(
1100 "id",
1101 WhereOp::In,
1102 vec![Value::I64(1), Value::I64(2), Value::I64(3)],
1103 );
1104 assert_eq!(c.field, "id");
1105 assert_eq!(c.op, WhereOp::In);
1106 assert_eq!(c.extra_values.len(), 3);
1107 }
1108
1109 #[test]
1110 fn test_where_condition_between() {
1111 let c = WhereCondition::between("age", Value::I64(18), Value::I64(30));
1112 assert_eq!(c.field, "age");
1113 assert_eq!(c.op, WhereOp::Between);
1114 assert_eq!(c.value, Value::I64(18));
1115 assert_eq!(c.extra_values, vec![Value::I64(30)]);
1116 }
1117
1118 #[test]
1121 fn test_page_result_total_pages() {
1122 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1123 assert_eq!(pr.total_pages(), 10);
1124 }
1125
1126 #[test]
1127 fn test_page_result_total_pages_with_remainder() {
1128 let pr: PageResult<i32> = PageResult::new(vec![], 105, 1, 10);
1129 assert_eq!(pr.total_pages(), 11);
1130 }
1131
1132 #[test]
1133 fn test_page_result_total_pages_zero_size() {
1134 let pr: PageResult<i32> = PageResult::new(vec![], 100, 1, 0);
1135 assert_eq!(pr.total_pages(), 0);
1136 }
1137
1138 #[test]
1139 fn test_page_result_has_next() {
1140 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1141 assert!(pr.has_next());
1142 assert!(!pr.has_prev());
1143 }
1144
1145 #[test]
1146 fn test_page_result_has_prev() {
1147 let pr = PageResult::new(vec![1, 2, 3], 100, 5, 10);
1148 assert!(pr.has_prev());
1149 assert!(pr.has_next()); }
1151
1152 #[test]
1153 fn test_page_result_is_empty() {
1154 let pr: PageResult<i32> = PageResult::new(vec![], 0, 1, 10);
1155 assert!(pr.is_empty());
1156 assert_eq!(pr.len(), 0);
1157 }
1158
1159 #[test]
1160 fn test_page_result_map() {
1161 let pr = PageResult::new(vec![1, 2, 3], 100, 1, 10);
1162 let mapped = pr.map(|x| x * 2);
1163 assert_eq!(mapped.items, vec![2, 4, 6]);
1164 assert_eq!(mapped.total, 100);
1165 }
1166
1167 #[test]
1170 fn test_repository_error_display() {
1171 let e = RepositoryError::NotFound;
1172 assert_eq!(e.to_string(), "entity not found");
1173
1174 let e = RepositoryError::DatabaseError("conn refused".to_string());
1175 assert_eq!(e.to_string(), "database error: conn refused");
1176
1177 let e = RepositoryError::InvalidEntity("missing id".to_string());
1178 assert_eq!(e.to_string(), "invalid entity: missing id");
1179
1180 let e = RepositoryError::Other("custom".to_string());
1181 assert_eq!(e.to_string(), "repository error: custom");
1182 }
1183
1184 #[test]
1185 fn test_repository_error_eq() {
1186 assert_eq!(RepositoryError::NotFound, RepositoryError::NotFound);
1187 assert_ne!(
1188 RepositoryError::NotFound,
1189 RepositoryError::Other("x".to_string())
1190 );
1191 }
1192
1193 #[test]
1196 fn test_inmemory_create_empty() {
1197 let repo = InMemoryRepository::<User>::new();
1198 assert!(repo.is_empty());
1199 assert_eq!(repo.len(), 0);
1200 }
1201
1202 #[test]
1203 fn test_inmemory_from_vec() {
1204 let repo = InMemoryRepository::from_vec(vec![
1205 User::new(1, "Alice", 30, "alice@example.com"),
1206 User::new(2, "Bob", 25, "bob@example.com"),
1207 ]);
1208 assert_eq!(repo.len(), 2);
1209 }
1210
1211 #[test]
1212 fn test_inmemory_clear() {
1213 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1214 assert_eq!(repo.len(), 1);
1215 repo.clear();
1216 assert_eq!(repo.len(), 0);
1217 }
1218
1219 #[test]
1222 fn test_repo_save_and_find_by_id() {
1223 let repo = InMemoryRepository::<User>::new();
1224 let user = User::new(1, "Alice", 30, "alice@example.com");
1225 let saved = repo.save(user.clone()).unwrap();
1226 assert_eq!(saved, user);
1227
1228 let found = repo.find_by_id(&Value::I64(1)).unwrap();
1229 assert_eq!(found, Some(user));
1230 }
1231
1232 #[test]
1233 fn test_repo_find_by_id_missing() {
1234 let repo = InMemoryRepository::<User>::new();
1235 let found = repo.find_by_id(&Value::I64(999)).unwrap();
1236 assert_eq!(found, None);
1237 }
1238
1239 #[test]
1240 fn test_repo_save_many() {
1241 let repo = InMemoryRepository::<User>::new();
1242 let users = vec![
1243 User::new(1, "Alice", 30, "alice@example.com"),
1244 User::new(2, "Bob", 25, "bob@example.com"),
1245 User::new(3, "Carol", 28, "carol@example.com"),
1246 ];
1247 let saved = repo.save_many(users.clone()).unwrap();
1248 assert_eq!(saved.len(), 3);
1249 assert_eq!(repo.len(), 3);
1250 }
1251
1252 #[test]
1255 fn test_batch_update_result_new() {
1256 let result = BatchUpdateResult::new(vec![1, 2, 3], 2);
1257 assert_eq!(result.updated_count(), 3);
1258 assert_eq!(result.skipped, 2);
1259 assert_eq!(result.total(), 5);
1260 assert!(result.has_skipped());
1261 assert!(!result.all_updated());
1262 }
1263
1264 #[test]
1265 fn test_batch_update_result_all_updated() {
1266 let result: BatchUpdateResult<i32> = BatchUpdateResult::new(vec![1, 2], 0);
1267 assert!(!result.has_skipped());
1268 assert!(result.all_updated());
1269 assert_eq!(result.total(), 2);
1270 }
1271
1272 #[test]
1273 fn test_batch_update_result_default() {
1274 let result: BatchUpdateResult<i32> = BatchUpdateResult::default();
1275 assert_eq!(result.updated_count(), 0);
1276 assert_eq!(result.skipped, 0);
1277 assert_eq!(result.total(), 0);
1278 }
1279
1280 #[test]
1281 fn test_batch_update_result_map() {
1282 let result = BatchUpdateResult::new(vec![1, 2, 3], 1);
1283 let mapped = result.map(|x| x * 10);
1284 assert_eq!(mapped.updated, vec![10, 20, 30]);
1285 assert_eq!(mapped.skipped, 1);
1286 }
1287
1288 #[test]
1289 fn test_repo_batch_update_all_existing() {
1290 let repo = InMemoryRepository::from_vec(vec![
1292 User::new(1, "Alice", 30, "a@b.com"),
1293 User::new(2, "Bob", 25, "b@b.com"),
1294 ]);
1295 let updates = vec![
1296 User::new(1, "Alice Updated", 31, "a2@b.com"),
1297 User::new(2, "Bob Updated", 26, "b2@b.com"),
1298 ];
1299 let result = repo.batch_update(updates).unwrap();
1300 assert_eq!(result.updated_count(), 2);
1301 assert_eq!(result.skipped, 0);
1302 assert!(result.all_updated());
1303
1304 let alice = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1306 assert_eq!(alice.name, "Alice Updated");
1307 assert_eq!(alice.age, 31);
1308 let bob = repo.find_by_id(&Value::I64(2)).unwrap().unwrap();
1309 assert_eq!(bob.name, "Bob Updated");
1310 assert_eq!(bob.age, 26);
1311 assert_eq!(repo.len(), 2);
1313 }
1314
1315 #[test]
1316 fn test_repo_batch_update_partial_missing() {
1317 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1319 let updates = vec![
1320 User::new(1, "Alice Updated", 31, "a2@b.com"),
1321 User::new(999, "Ghost", 1, "ghost@b.com"), ];
1323 let result = repo.batch_update(updates).unwrap();
1324 assert_eq!(result.updated_count(), 1);
1325 assert_eq!(result.skipped, 1);
1326 assert!(result.has_skipped());
1327
1328 let alice = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1330 assert_eq!(alice.name, "Alice Updated");
1331 assert!(repo.find_by_id(&Value::I64(999)).unwrap().is_none());
1333 assert_eq!(repo.len(), 1);
1334 }
1335
1336 #[test]
1337 fn test_repo_batch_update_all_missing() {
1338 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1340 let updates = vec![
1341 User::new(100, "Ghost1", 1, "g1@b.com"),
1342 User::new(200, "Ghost2", 2, "g2@b.com"),
1343 ];
1344 let result = repo.batch_update(updates).unwrap();
1345 assert_eq!(result.updated_count(), 0);
1346 assert_eq!(result.skipped, 2);
1347 assert_eq!(repo.len(), 1); }
1349
1350 #[test]
1351 fn test_repo_batch_update_empty() {
1352 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1353 let result = repo.batch_update(vec![]).unwrap();
1354 assert_eq!(result.updated_count(), 0);
1355 assert_eq!(result.skipped, 0);
1356 assert_eq!(result.total(), 0);
1357 }
1358
1359 #[test]
1360 fn test_repo_batch_update_distinct_from_save_many() {
1361 let repo1 = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1365 let repo2 = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1366
1367 let updates = vec![
1368 User::new(1, "Alice Updated", 31, "a2@b.com"),
1369 User::new(999, "New User", 1, "new@b.com"),
1370 ];
1371
1372 let saved = repo1.save_many(updates.clone()).unwrap();
1374 assert_eq!(saved.len(), 2);
1375 assert_eq!(repo1.len(), 2); let result = repo2.batch_update(updates).unwrap();
1379 assert_eq!(result.updated_count(), 1);
1380 assert_eq!(result.skipped, 1);
1381 assert_eq!(repo2.len(), 1); }
1383
1384 #[test]
1385 fn test_repo_save_update_existing() {
1386 let repo = InMemoryRepository::<User>::new();
1387 repo.save(User::new(1, "Alice", 30, "alice@example.com"))
1388 .unwrap();
1389
1390 repo.save(User::new(1, "Alice Updated", 31, "alice2@example.com"))
1392 .unwrap();
1393
1394 assert_eq!(repo.len(), 1);
1395 let found = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1396 assert_eq!(found.name, "Alice Updated");
1397 assert_eq!(found.age, 31);
1398 }
1399
1400 #[test]
1401 fn test_repo_find_all() {
1402 let repo = InMemoryRepository::from_vec(vec![
1403 User::new(1, "Alice", 30, "a@b.com"),
1404 User::new(2, "Bob", 25, "b@b.com"),
1405 ]);
1406 let all = repo.find_all().unwrap();
1407 assert_eq!(all.len(), 2);
1408 }
1409
1410 #[test]
1411 fn test_repo_find_all_empty() {
1412 let repo = InMemoryRepository::<User>::new();
1413 let all = repo.find_all().unwrap();
1414 assert!(all.is_empty());
1415 }
1416
1417 #[test]
1418 fn test_repo_delete() {
1419 let repo = InMemoryRepository::from_vec(vec![
1420 User::new(1, "Alice", 30, "a@b.com"),
1421 User::new(2, "Bob", 25, "b@b.com"),
1422 ]);
1423 let deleted = repo.delete(&Value::I64(1)).unwrap();
1424 assert_eq!(deleted, 1);
1425 assert_eq!(repo.len(), 1);
1426 }
1427
1428 #[test]
1429 fn test_repo_delete_missing() {
1430 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1431 let deleted = repo.delete(&Value::I64(999)).unwrap();
1432 assert_eq!(deleted, 0);
1433 assert_eq!(repo.len(), 1);
1434 }
1435
1436 #[test]
1437 fn test_repo_count() {
1438 let repo = InMemoryRepository::from_vec(vec![
1439 User::new(1, "Alice", 30, "a@b.com"),
1440 User::new(2, "Bob", 25, "b@b.com"),
1441 User::new(3, "Carol", 28, "c@b.com"),
1442 ]);
1443 assert_eq!(repo.count().unwrap(), 3);
1444 }
1445
1446 #[test]
1447 fn test_repo_count_empty() {
1448 let repo = InMemoryRepository::<User>::new();
1449 assert_eq!(repo.count().unwrap(), 0);
1450 }
1451
1452 #[test]
1453 fn test_repo_exists() {
1454 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1455 assert!(repo.exists(&Value::I64(1)).unwrap());
1456 assert!(!repo.exists(&Value::I64(999)).unwrap());
1457 }
1458
1459 #[test]
1462 fn test_repo_find_by_eq() {
1463 let repo = InMemoryRepository::from_vec(vec![
1464 User::new(1, "Alice", 30, "a@b.com"),
1465 User::new(2, "Bob", 30, "b@b.com"),
1466 User::new(3, "Carol", 25, "c@b.com"),
1467 ]);
1468
1469 let result = repo
1470 .find_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1471 .unwrap();
1472 assert_eq!(result.len(), 2);
1473 }
1474
1475 #[test]
1476 fn test_repo_find_by_gt() {
1477 let repo = InMemoryRepository::from_vec(vec![
1478 User::new(1, "Alice", 30, "a@b.com"),
1479 User::new(2, "Bob", 25, "b@b.com"),
1480 User::new(3, "Carol", 35, "c@b.com"),
1481 ]);
1482
1483 let result = repo
1484 .find_by(&[WhereCondition::new("age", WhereOp::Gt, Value::I64(28))])
1485 .unwrap();
1486 assert_eq!(result.len(), 2);
1487 }
1488
1489 #[test]
1490 fn test_repo_find_by_like() {
1491 let repo = InMemoryRepository::from_vec(vec![
1492 User::new(1, "Alice", 30, "alice@example.com"),
1493 User::new(2, "Bob", 25, "bob@example.com"),
1494 User::new(3, "Alicia", 28, "alicia@test.com"),
1495 ]);
1496
1497 let result = repo
1498 .find_by(&[WhereCondition::new(
1499 "name",
1500 WhereOp::Like,
1501 Value::String("Ali%".to_string()),
1502 )])
1503 .unwrap();
1504 assert_eq!(result.len(), 2);
1505 }
1506
1507 #[test]
1508 fn test_repo_find_by_like_case_insensitive() {
1509 let repo = InMemoryRepository::from_vec(vec![
1511 User::new(1, "Alice", 30, "alice@example.com"),
1512 User::new(2, "bob", 25, "bob@example.com"),
1513 User::new(3, "ALICIA", 28, "alicia@test.com"),
1514 ]);
1515
1516 let result = repo
1518 .find_by(&[WhereCondition::new(
1519 "name",
1520 WhereOp::Like,
1521 Value::String("ali%".to_string()),
1522 )])
1523 .unwrap();
1524 assert_eq!(result.len(), 2, "LIKE should be case-insensitive");
1525
1526 let result = repo
1528 .find_by(&[WhereCondition::new(
1529 "name",
1530 WhereOp::Like,
1531 Value::String("BOB".to_string()),
1532 )])
1533 .unwrap();
1534 assert_eq!(
1535 result.len(),
1536 1,
1537 "LIKE exact match should be case-insensitive"
1538 );
1539 }
1540
1541 #[test]
1542 fn test_repo_find_by_with_or_filter_multi_field_keyword() {
1543 let repo = InMemoryRepository::from_vec(vec![
1545 User::new(1, "Alice", 30, "alice@example.com"),
1546 User::new(2, "Bob", 25, "bob@example.com"),
1547 User::new(3, "Carol", 28, "carol@kw.com"), User::new(4, "Dave", 32, "dave@example.com"),
1549 ]);
1550
1551 let and = vec![]; let or = vec![
1553 WhereCondition::new("name", WhereOp::Like, Value::String("%kw%".to_string())),
1554 WhereCondition::new("email", WhereOp::Like, Value::String("%kw%".to_string())),
1555 ];
1556
1557 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1558 assert_eq!(result.len(), 1);
1560 assert_eq!(result[0].key(), 3);
1561 }
1562
1563 #[test]
1564 fn test_repo_find_by_with_or_filter_combined_with_and() {
1565 let repo = InMemoryRepository::from_vec(vec![
1567 User::new(1, "Alice_kw", 30, "alice@example.com"), User::new(2, "Bob", 25, "bob@kw.com"), User::new(3, "Carol_kw", 28, "carol@example.com"), User::new(4, "Dave", 32, "dave@example.com"), ]);
1572
1573 let and = vec![WhereCondition::new("age", WhereOp::Ge, Value::I64(28))];
1575 let or = vec![
1576 WhereCondition::new("name", WhereOp::Like, Value::String("%kw%".to_string())),
1577 WhereCondition::new("email", WhereOp::Like, Value::String("%kw%".to_string())),
1578 ];
1579
1580 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1581 assert_eq!(result.len(), 2);
1585 let ids: Vec<i64> = result.iter().map(|u| u.key()).collect();
1586 assert!(ids.contains(&1));
1587 assert!(ids.contains(&3));
1588 }
1589
1590 #[test]
1591 fn test_repo_find_by_with_or_filter_empty_or() {
1592 let repo = InMemoryRepository::from_vec(vec![
1594 User::new(1, "Alice", 30, "alice@example.com"),
1595 User::new(2, "Bob", 25, "bob@example.com"),
1596 ]);
1597
1598 let and = vec![WhereCondition::new("age", WhereOp::Ge, Value::I64(28))];
1599 let or: Vec<WhereCondition> = vec![];
1600
1601 let result = repo.find_by_with_or_filter(&and, &or).unwrap();
1602 assert_eq!(result.len(), 1);
1603 assert_eq!(result[0].key(), 1);
1604 }
1605
1606 #[test]
1607 fn test_repo_find_by_in() {
1608 let repo = InMemoryRepository::from_vec(vec![
1609 User::new(1, "Alice", 30, "a@b.com"),
1610 User::new(2, "Bob", 25, "b@b.com"),
1611 User::new(3, "Carol", 28, "c@b.com"),
1612 User::new(4, "Dave", 32, "d@b.com"),
1613 ]);
1614
1615 let result = repo
1616 .find_by(&[WhereCondition::in_op(
1617 "id",
1618 WhereOp::In,
1619 vec![Value::I64(1), Value::I64(3)],
1620 )])
1621 .unwrap();
1622 assert_eq!(result.len(), 2);
1623 }
1624
1625 #[test]
1626 fn test_repo_find_by_not_in() {
1627 let repo = InMemoryRepository::from_vec(vec![
1628 User::new(1, "Alice", 30, "a@b.com"),
1629 User::new(2, "Bob", 25, "b@b.com"),
1630 User::new(3, "Carol", 28, "c@b.com"),
1631 ]);
1632
1633 let result = repo
1634 .find_by(&[WhereCondition::in_op(
1635 "id",
1636 WhereOp::NotIn,
1637 vec![Value::I64(1)],
1638 )])
1639 .unwrap();
1640 assert_eq!(result.len(), 2);
1641 assert!(result.iter().all(|u| u.id != 1));
1642 }
1643
1644 #[test]
1645 fn test_repo_find_by_between() {
1646 let repo = InMemoryRepository::from_vec(vec![
1647 User::new(1, "Alice", 30, "a@b.com"),
1648 User::new(2, "Bob", 25, "b@b.com"),
1649 User::new(3, "Carol", 35, "c@b.com"),
1650 User::new(4, "Dave", 22, "d@b.com"),
1651 ]);
1652
1653 let result = repo
1654 .find_by(&[WhereCondition::between(
1655 "age",
1656 Value::I64(25),
1657 Value::I64(35),
1658 )])
1659 .unwrap();
1660 assert_eq!(result.len(), 3);
1661 }
1662
1663 #[test]
1664 fn test_repo_find_by_multiple_conditions() {
1665 let repo = InMemoryRepository::from_vec(vec![
1666 User::new(1, "Alice", 30, "a@b.com"),
1667 User::new(2, "Bob", 30, "b@b.com"),
1668 User::new(3, "Alice", 25, "c@b.com"),
1669 ]);
1670
1671 let result = repo
1672 .find_by(&[
1673 WhereCondition::new("name", WhereOp::Eq, Value::String("Alice".to_string())),
1674 WhereCondition::new("age", WhereOp::Ge, Value::I64(30)),
1675 ])
1676 .unwrap();
1677 assert_eq!(result.len(), 1);
1678 assert_eq!(result[0].id, 1);
1679 }
1680
1681 #[test]
1682 fn test_repo_find_one_by() {
1683 let repo = InMemoryRepository::from_vec(vec![
1684 User::new(1, "Alice", 30, "a@b.com"),
1685 User::new(2, "Bob", 25, "b@b.com"),
1686 ]);
1687
1688 let result = repo
1689 .find_one_by(&[WhereCondition::new(
1690 "name",
1691 WhereOp::Eq,
1692 Value::String("Bob".to_string()),
1693 )])
1694 .unwrap();
1695 assert!(result.is_some());
1696 assert_eq!(result.unwrap().id, 2);
1697 }
1698
1699 #[test]
1700 fn test_repo_find_one_by_missing() {
1701 let repo = InMemoryRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1702
1703 let result = repo
1704 .find_one_by(&[WhereCondition::new(
1705 "name",
1706 WhereOp::Eq,
1707 Value::String("Missing".to_string()),
1708 )])
1709 .unwrap();
1710 assert!(result.is_none());
1711 }
1712
1713 #[test]
1714 fn test_repo_count_by() {
1715 let repo = InMemoryRepository::from_vec(vec![
1716 User::new(1, "Alice", 30, "a@b.com"),
1717 User::new(2, "Bob", 30, "b@b.com"),
1718 User::new(3, "Carol", 25, "c@b.com"),
1719 ]);
1720
1721 let count = repo
1722 .count_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1723 .unwrap();
1724 assert_eq!(count, 2);
1725 }
1726
1727 #[test]
1728 fn test_repo_delete_by() {
1729 let repo = InMemoryRepository::from_vec(vec![
1730 User::new(1, "Alice", 30, "a@b.com"),
1731 User::new(2, "Bob", 30, "b@b.com"),
1732 User::new(3, "Carol", 25, "c@b.com"),
1733 ]);
1734
1735 let deleted = repo
1736 .delete_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1737 .unwrap();
1738 assert_eq!(deleted, 2);
1739 assert_eq!(repo.len(), 1);
1740 }
1741
1742 #[test]
1745 fn test_repo_paginate() {
1746 let users: Vec<User> = (1..=25)
1747 .map(|i| User::new(i, &format!("User{}", i), 20 + (i % 30), "u@b.com"))
1748 .collect();
1749 let repo = InMemoryRepository::from_vec(users);
1750
1751 let page = repo.paginate(1, 10).unwrap();
1752 assert_eq!(page.page, 1);
1753 assert_eq!(page.page_size, 10);
1754 assert_eq!(page.total, 25);
1755 assert_eq!(page.total_pages(), 3);
1756 assert_eq!(page.items.len(), 10);
1757 assert!(page.has_next());
1758 assert!(!page.has_prev());
1759 }
1760
1761 #[test]
1762 fn test_repo_paginate_last_page() {
1763 let users: Vec<User> = (1..=25)
1764 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1765 .collect();
1766 let repo = InMemoryRepository::from_vec(users);
1767
1768 let page = repo.paginate(3, 10).unwrap();
1769 assert_eq!(page.items.len(), 5);
1770 assert!(page.has_prev());
1771 assert!(!page.has_next());
1772 }
1773
1774 #[test]
1775 fn test_repo_paginate_out_of_range() {
1776 let users: Vec<User> = (1..=5)
1777 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1778 .collect();
1779 let repo = InMemoryRepository::from_vec(users);
1780
1781 let page = repo.paginate(10, 10).unwrap();
1782 assert_eq!(page.items.len(), 0);
1783 assert_eq!(page.total, 5);
1784 }
1785
1786 #[test]
1787 fn test_repo_paginate_by() {
1788 let users: Vec<User> = (1..=20)
1789 .map(|i| User::new(i, &format!("User{}", i), 20 + (i % 5), "u@b.com"))
1790 .collect();
1791 let repo = InMemoryRepository::from_vec(users);
1792
1793 let page = repo
1795 .paginate_by(
1796 &[WhereCondition::new("age", WhereOp::Eq, Value::I64(22))],
1797 1,
1798 2,
1799 )
1800 .unwrap();
1801 assert_eq!(page.total, 4);
1802 assert_eq!(page.items.len(), 2);
1803 assert_eq!(page.total_pages(), 2);
1804 }
1805
1806 #[test]
1809 fn test_generic_key_repo_basic() {
1810 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::new();
1811 assert!(repo.is_empty());
1812
1813 let user = User::new(1, "Alice", 30, "a@b.com");
1814 repo.save(user.clone()).unwrap();
1815 assert_eq!(repo.len(), 1);
1816
1817 let found = repo.find_by_id(&1).unwrap();
1818 assert_eq!(found, Some(user));
1819 }
1820
1821 #[test]
1822 fn test_generic_key_repo_delete() {
1823 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1824 User::new(1, "Alice", 30, "a@b.com"),
1825 User::new(2, "Bob", 25, "b@b.com"),
1826 ]);
1827
1828 let deleted = repo.delete(&1).unwrap();
1829 assert_eq!(deleted, 1);
1830 assert_eq!(repo.len(), 1);
1831
1832 let remaining = repo.find_all().unwrap();
1833 assert_eq!(remaining[0].id, 2);
1834 }
1835
1836 #[test]
1837 fn test_generic_key_repo_find_by() {
1838 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1839 User::new(1, "Alice", 30, "a@b.com"),
1840 User::new(2, "Bob", 30, "b@b.com"),
1841 User::new(3, "Carol", 25, "c@b.com"),
1842 ]);
1843
1844 let result = repo
1845 .find_by(&[WhereCondition::new("age", WhereOp::Eq, Value::I64(30))])
1846 .unwrap();
1847 assert_eq!(result.len(), 2);
1848 }
1849
1850 #[test]
1851 fn test_generic_key_repo_paginate() {
1852 let users: Vec<User> = (1..=15)
1853 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1854 .collect();
1855 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(users);
1856
1857 let page = repo.paginate(2, 10).unwrap();
1858 assert_eq!(page.items.len(), 5);
1859 assert_eq!(page.total, 15);
1860 assert_eq!(page.page, 2);
1861 }
1862
1863 #[test]
1864 fn test_generic_key_repo_count() {
1865 let repo: GenericKeyRepository<User, i64> = GenericKeyRepository::from_vec(vec![
1866 User::new(1, "Alice", 30, "a@b.com"),
1867 User::new(2, "Bob", 25, "b@b.com"),
1868 ]);
1869 assert_eq!(repo.count().unwrap(), 2);
1870 }
1871
1872 #[test]
1873 fn test_generic_key_repo_exists() {
1874 let repo: GenericKeyRepository<User, i64> =
1875 GenericKeyRepository::from_vec(vec![User::new(1, "Alice", 30, "a@b.com")]);
1876 assert!(repo.exists(&1).unwrap());
1877 assert!(!repo.exists(&999).unwrap());
1878 }
1879
1880 #[test]
1883 fn test_simple_regex_literal() {
1884 let re = simple_regex::compile("^abc$").unwrap();
1885 assert!(re.is_match("abc"));
1886 assert!(!re.is_match("abcd"));
1887 }
1888
1889 #[test]
1890 fn test_simple_regex_wildcard() {
1891 let re = simple_regex::compile("^Ali.*$").unwrap();
1892 assert!(re.is_match("Alice"));
1893 assert!(re.is_match("Alicia"));
1894 assert!(!re.is_match("Bob"));
1895 }
1896
1897 #[test]
1898 fn test_simple_regex_no_anchors() {
1899 let re = simple_regex::compile("ab").unwrap();
1900 assert!(re.is_match("ab"));
1901 }
1902
1903 #[test]
1906 fn test_e2e_repository_workflow() {
1907 let repo = InMemoryRepository::<User>::new();
1908
1909 let users = vec![
1911 User::new(1, "Alice", 30, "alice@example.com"),
1912 User::new(2, "Bob", 25, "bob@example.com"),
1913 User::new(3, "Carol", 35, "carol@example.com"),
1914 User::new(4, "Dave", 28, "dave@example.com"),
1915 User::new(5, "Eve", 32, "eve@example.com"),
1916 ];
1917 repo.save_many(users).unwrap();
1918 assert_eq!(repo.count().unwrap(), 5);
1919
1920 let adults = repo
1922 .find_by(&[WhereCondition::new("age", WhereOp::Ge, Value::I64(30))])
1923 .unwrap();
1924 assert_eq!(adults.len(), 3);
1925
1926 let page1 = repo.paginate(1, 2).unwrap();
1928 assert_eq!(page1.items.len(), 2);
1929 assert_eq!(page1.total_pages(), 3);
1930
1931 let page2 = repo.paginate(2, 2).unwrap();
1932 assert_eq!(page2.items.len(), 2);
1933
1934 let page3 = repo.paginate(3, 2).unwrap();
1935 assert_eq!(page3.items.len(), 1);
1936
1937 let adult_page = repo
1939 .paginate_by(
1940 &[WhereCondition::new("age", WhereOp::Ge, Value::I64(30))],
1941 1,
1942 2,
1943 )
1944 .unwrap();
1945 assert_eq!(adult_page.total, 3);
1946 assert_eq!(adult_page.items.len(), 2);
1947
1948 repo.save(User::new(1, "Alice Smith", 31, "alice.smith@example.com"))
1950 .unwrap();
1951 let updated = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1952 assert_eq!(updated.name, "Alice Smith");
1953 assert_eq!(updated.age, 31);
1954
1955 let deleted = repo.delete(&Value::I64(2)).unwrap();
1957 assert_eq!(deleted, 1);
1958 assert_eq!(repo.count().unwrap(), 4);
1959 assert!(!repo.exists(&Value::I64(2)).unwrap());
1960
1961 let deleted_by = repo
1963 .delete_by(&[WhereCondition::new("age", WhereOp::Ge, Value::I64(31))])
1964 .unwrap();
1965 assert_eq!(deleted_by, 3);
1966 assert_eq!(repo.count().unwrap(), 1); }
1968
1969 #[test]
1970 fn test_e2e_pagination_navigation() {
1971 let users: Vec<User> = (1..=100)
1972 .map(|i| User::new(i, &format!("User{}", i), 20, "u@b.com"))
1973 .collect();
1974 let repo = InMemoryRepository::from_vec(users);
1975
1976 let mut current_page = 1u64;
1977 let mut visited: Vec<u64> = Vec::new();
1978 loop {
1979 let page = repo.paginate(current_page, 10).unwrap();
1980 visited.push(current_page);
1981 if !page.has_next() {
1982 break;
1983 }
1984 current_page += 1;
1985 }
1986 assert_eq!(visited.len(), 10);
1987 assert_eq!(visited, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1988 }
1989
1990 #[test]
1993 fn test_paginate_too_many_rows() {
1994 let err = RepositoryError::TooManyRows {
1996 actual: 200_000,
1997 limit: REPOSITORY_SCAN_LIMIT,
1998 };
1999 assert!(matches!(err, RepositoryError::TooManyRows { actual: 200_000, limit } if limit == REPOSITORY_SCAN_LIMIT));
2000 let msg = err.to_string();
2001 assert!(msg.contains("200000"), "msg should contain actual count: {}", msg);
2002 assert!(msg.contains("100000"), "msg should contain limit: {}", msg);
2003 }
2004
2005 #[test]
2006 fn test_paginate_boundary_at_limit() {
2007 let n: usize = 50;
2010 let users: Vec<User> = (1..=n as i64)
2011 .map(|i| User::new(i, &format!("U{}", i), 20, "u@b.com"))
2012 .collect();
2013 let repo = InMemoryRepository::from_vec(users);
2014 assert_eq!(repo.count().unwrap() as usize, n);
2016 let page = repo.paginate(1, 10).unwrap();
2017 assert_eq!(page.total, n as u64);
2018 assert_eq!(page.items.len(), 10);
2019 let err = RepositoryError::TooManyRows {
2021 actual: REPOSITORY_SCAN_LIMIT + 1,
2022 limit: REPOSITORY_SCAN_LIMIT,
2023 };
2024 let msg = err.to_string();
2025 assert!(msg.contains("QueryBuilder"), "msg should suggest QueryBuilder: {}", msg);
2026 }
2027}