Skip to main content

sz_orm_core/
repository.rs

1//! Repository Pattern 仓储模式
2//!
3//! 对应文档 6.8 节改进项 37(Repository Pattern 仓储模式)。
4//!
5//! # 核心概念
6//!
7//! - **Repository trait**:统一的仓储接口,定义 CRUD + 分页 + 条件查询
8//! - **InMemoryRepository**:内存仓储实现(用于测试、原型、无数据库场景)
9//! - **WhereCondition / WhereOp**:查询条件
10//! - **PageResult**:分页结果
11//! - **RepositoryError**:仓储错误
12//!
13//! # 设计灵感
14//!
15//! - Doctrine `EntityRepository`
16//! - Spring Data JPA `@Repository` / `JpaRepository`
17//! - MyBatis-Plus `IService` / `BaseMapper`
18//! - Laravel Eloquent `Repository`
19//! - DDD(领域驱动设计)的 Repository 模式
20//!
21//! # 优势
22//!
23//! 1. **分层解耦**:业务层依赖 Repository 接口,不直接依赖 Model 静态方法
24//! 2. **可替换性**:同一接口可有 InMemory / SQL / NoSQL 等多种实现
25//! 3. **可测试**:单元测试用 InMemoryRepository;生产环境请直接使用 QueryBuilder + Connection
26//! 4. **统一 API**:CRUD + 分页 + 条件查询接口统一
27//!
28//! # 重要说明
29//!
30//! 本模块**仅提供 InMemoryRepository**(内存实现),**不提供 SqlRepository**。
31//! 如需基于 SQL 的仓储实现,请直接使用 `QueryBuilder` 生成 SQL 并通过
32//! `Pool::acquire()` 获取连接后执行。InMemoryRepository 主要用于单元测试与
33//! 业务逻辑原型验证,不执行任何真实 SQL。
34//!
35//! # 使用示例
36//!
37//! ```
38//! use sz_orm_core::repository::{
39//!     InMemoryRepository, Repository, WhereCondition, WhereOp, PageResult,
40//! };
41//! use sz_orm_core::Value;
42//! use std::collections::HashMap;
43//!
44//! // 假设 User 是 Model 的实现
45//! // let repo = InMemoryRepository::<User>::new();
46//! // let user = repo.find_by_id(1)?;
47//! // let adults = repo.find_by(&[WhereCondition::new("age", WhereOp::Ge, Value::I64(18))])?;
48//! ```
49
50use crate::Value;
51use std::fmt::Debug;
52use std::sync::RwLock;
53
54// ============================================================================
55// WhereCondition — 查询条件
56// ============================================================================
57
58/// 查询操作符
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum WhereOp {
61    /// `=`
62    Eq,
63    /// `!=`
64    Ne,
65    /// `>`
66    Gt,
67    /// `>=`
68    Ge,
69    /// `<`
70    Lt,
71    /// `<=`
72    Le,
73    /// `LIKE`
74    Like,
75    /// `IN`
76    In,
77    /// `NOT IN`
78    NotIn,
79    /// `IS NULL`
80    IsNull,
81    /// `IS NOT NULL`
82    IsNotNull,
83    /// `BETWEEN`
84    Between,
85}
86
87impl WhereOp {
88    /// 操作符名称
89    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/// 查询条件
108#[derive(Debug, Clone)]
109pub struct WhereCondition {
110    pub field: String,
111    pub op: WhereOp,
112    pub value: Value,
113    /// For Between / In / NotIn,副值列表
114    pub extra_values: Vec<Value>,
115}
116
117impl WhereCondition {
118    /// 创建单值条件(Eq/Ne/Gt/Ge/Lt/Le/Like)
119    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    /// 创建 IsNull / IsNotNull 条件
129    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    /// 创建 In / NotIn 条件
139    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    /// 创建 Between 条件
149    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// ============================================================================
160// PageResult — 分页结果
161// ============================================================================
162
163/// 分页结果
164#[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    /// 创建分页结果
174    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    /// 总页数
184    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    /// 是否有下一页
192    pub fn has_next(&self) -> bool {
193        self.page < self.total_pages()
194    }
195
196    /// 是否有上一页
197    pub fn has_prev(&self) -> bool {
198        self.page > 1
199    }
200
201    /// 是否为空
202    pub fn is_empty(&self) -> bool {
203        self.items.is_empty()
204    }
205
206    /// 当前页条数
207    pub fn len(&self) -> usize {
208        self.items.len()
209    }
210
211    /// 映射为其他类型
212    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// ============================================================================
223// BatchUpdateResult — 批量更新结果(S-1:SeaORM 对标短板补全)
224// ============================================================================
225
226/// 批量更新结果
227///
228/// 由 [`Repository::batch_update`](Repository::batch_update) 返回,
229/// 区分成功更新的实体与因主键不存在而被跳过的实体数量。
230///
231/// # 字段
232///
233/// - `updated`:成功更新的实体列表(按主键匹配命中并完成 UPDATE)
234/// - `skipped`:主键不存在而被跳过的实体数量
235///
236/// # 设计动机
237///
238/// SeaORM 的 `update_many` 不区分"存在"与"不存在",只返回受影响行数。
239/// 本结构明确区分两者,便于调用方感知部分失败(如批量同步外部数据时
240/// 部分记录已被删除)并采取补偿措施(记录日志、重试插入等)。
241#[derive(Debug, Clone)]
242pub struct BatchUpdateResult<E> {
243    /// 成功更新的实体列表
244    pub updated: Vec<E>,
245    /// 主键不存在而被跳过的实体数量
246    pub skipped: usize,
247}
248
249impl<E> BatchUpdateResult<E> {
250    /// 创建批量更新结果
251    pub fn new(updated: Vec<E>, skipped: usize) -> Self {
252        Self { updated, skipped }
253    }
254
255    /// 成功更新数量
256    pub fn updated_count(&self) -> usize {
257        self.updated.len()
258    }
259
260    /// 是否有跳过的实体
261    pub fn has_skipped(&self) -> bool {
262        self.skipped > 0
263    }
264
265    /// 是否全部成功(无跳过)
266    pub fn all_updated(&self) -> bool {
267        self.skipped == 0
268    }
269
270    /// 总计处理数量(已更新 + 跳过)
271    pub fn total(&self) -> usize {
272        self.updated.len() + self.skipped
273    }
274
275    /// 映射已更新实体为其他类型(保留 skipped 计数)
276    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
293// ============================================================================
294// RepositoryError — 错误类型
295// ============================================================================
296
297/// `InMemoryRepository` 分页操作的安全扫描上限。
298///
299/// `InMemoryRepository` 的分页实现会将匹配行全部加载到内存后再切片,
300/// 当表行数过大时会导致 OOM。此常量定义了允许加载的最大行数;超过此
301/// 值时 `paginate` / `paginate_by` 直接返回 `RepositoryError::TooManyRows`。
302///
303/// 生产环境请使用 `QueryBuilder` + `limit`/`offset` 将分页下推到数据库层,
304/// 不受此限制影响。
305pub const REPOSITORY_SCAN_LIMIT: usize = 100_000;
306
307/// Repository 错误
308#[derive(Debug, Clone, PartialEq)]
309pub enum RepositoryError {
310    /// 实体未找到
311    NotFound,
312    /// 数据库错误
313    DatabaseError(String),
314    /// 实体无效(如缺主键)
315    InvalidEntity(String),
316    /// 其他错误
317    Other(String),
318    /// 扫描行数超过安全上限(防止内存型分页 OOM)
319    ///
320    /// `InMemoryRepository` 的 `paginate` / `paginate_by` 会将全表加载到内存
321    /// 后再切片。当表行数超过 `REPOSITORY_SCAN_LIMIT`(默认 100_000)时,
322    /// 返回此错误而非继续加载,避免大表直接 OOM。
323    ///
324    /// 生产环境请使用 `QueryBuilder` + `limit`/`offset` 将分页下推到数据库层。
325    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
347/// Repository 结果类型
348pub type RepositoryResult<T> = Result<T, RepositoryError>;
349
350// ============================================================================
351// Repository trait — 仓储接口
352// ============================================================================
353
354/// 仓储接口(generic over Model-like entity E, primary key K)
355///
356/// E 不必是 `Model` trait 实现,仅需满足存储基本要求。
357/// 这样设计允许存储任意结构(DTO、聚合、领域实体等)。
358pub trait Repository<E>: Send + Sync {
359    /// 主键类型
360    type Key: Clone + Debug + PartialEq + Send + Sync;
361
362    /// 提取实体的主键
363    fn key_of(&self, entity: &E) -> Self::Key;
364
365    /// 按主键查找
366    fn find_by_id(&self, key: &Self::Key) -> RepositoryResult<Option<E>>;
367
368    /// 查询所有
369    fn find_all(&self) -> RepositoryResult<Vec<E>>;
370
371    /// 按条件查询
372    fn find_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<Vec<E>>;
373
374    /// 按条件查询单条
375    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    /// 保存(INSERT 或 UPDATE)
385    ///
386    /// 返回保存后的实体(可能是克隆,主键可能被填充)
387    fn save(&self, entity: E) -> RepositoryResult<E>;
388
389    /// 批量保存
390    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    /// 批量更新(S-1:SeaORM 对标短板补全)
399    ///
400    /// 仅更新已存在的实体(按主键匹配),不存在的实体跳过并计入 `skipped`。
401    /// 与 [`save_many`](Self::save_many) 的区别:`save_many` 是 upsert(存在则更新,不存在则插入),
402    /// `batch_update` 是纯更新(不存在则跳过)。
403    ///
404    /// 默认实现为逐条调用 [`find_by_id`](Self::find_by_id) + [`save`](Self::save),
405    /// 具体实现可重写为真正的批量 UPDATE SQL(如 `UPDATE ... SET ... WHERE id IN (...)`)。
406    ///
407    /// # 参数
408    ///
409    /// - `entities`:待更新的实体列表(主键必须已填充)
410    ///
411    /// # 返回
412    ///
413    /// - `updated`:成功更新的实体列表
414    /// - `skipped`:主键不存在而被跳过的实体数量
415    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    /// 按主键删除
430    fn delete(&self, key: &Self::Key) -> RepositoryResult<usize>;
431
432    /// 按条件删除
433    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    /// 总数
444    fn count(&self) -> RepositoryResult<u64>;
445
446    /// 按条件计数
447    fn count_by(&self, conditions: &[WhereCondition]) -> RepositoryResult<u64> {
448        let items = self.find_by(conditions)?;
449        Ok(items.len() as u64)
450    }
451
452    /// 主键是否存在
453    fn exists(&self, key: &Self::Key) -> RepositoryResult<bool> {
454        Ok(self.find_by_id(key)?.is_some())
455    }
456
457    /// 分页查询
458    ///
459    /// # 安全限制
460    ///
461    /// 本实现在内存中加载全表后切片。调用前会先检查总行数,若超过
462    /// [`REPOSITORY_SCAN_LIMIT`] 则返回 [`RepositoryError::TooManyRows`],
463    /// 避免大表 OOM。生产环境请使用 `QueryBuilder` + `limit`/`offset`。
464    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    /// 按条件分页查询
489    ///
490    /// # 安全限制
491    ///
492    /// 同 [`paginate`]:匹配行数超过 [`REPOSITORY_SCAN_LIMIT`] 时返回
493    /// [`RepositoryError::TooManyRows`]。
494    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    /// 按 AND 条件查询,并对结果应用额外的 OR 过滤组
524    ///
525    /// 语义:`WHERE (and1 AND and2 AND ...) AND (or1_1 OR or1_2 OR ...)`
526    ///
527    /// 用于多字段 keyword LIKE 搜索等场景(对齐 PHP ThinkPHP
528    /// `where('field1|field2|field3','like','%kw%')` 多字段 OR LIKE 语法)。
529    ///
530    /// # 默认实现
531    ///
532    /// 基于 `find_by` + 内存 OR 过滤;SQL 后端实现可重写以将 OR 下推到 SQL,
533    /// 避免 `SELECT ... WHERE app_id=?` 全表拉取后再内存过滤的性能问题。
534    ///
535    /// # 参数
536    ///
537    /// - `and`:AND 关系的条件列表(同 `find_by`)
538    /// - `or_filter`:OR 关系的条件列表,对 `and` 结果做二次过滤
539    ///
540    /// # 示例
541    ///
542    /// ```
543    /// use sz_orm_core::repository::{Repository, WhereCondition, WhereOp};
544    /// use sz_orm_core::Value;
545    /// # use sz_orm_core::repository::InMemoryRepository;
546    /// # fn example<E: sz_orm_core::repository::EntityAttributes + Clone + Send + Sync + 'static>(repo: &dyn Repository<E, Key = Value>) {
547    /// let and = vec![
548    ///     WhereCondition::new("is_delete", WhereOp::Eq, Value::I64(0)),
549    ///     WhereCondition::new("app_id", WhereOp::Eq, Value::I64(1)),
550    /// ];
551    /// let or = vec![
552    ///     WhereCondition::new("name", WhereOp::Like, Value::String("%kw%".into())),
553    ///     WhereCondition::new("addr", WhereOp::Like, Value::String("%kw%".into())),
554    /// ];
555    /// let items = repo.find_by_with_or_filter(&and, &or).unwrap();
556    /// # }
557    /// ```
558    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
586// ============================================================================
587// InMemoryRepository — 内存仓储实现
588// ============================================================================
589
590/// 内存仓储实现
591///
592/// 使用 `Vec<E>` 存储实体,主键通过 `key_of` 提取。
593/// 适合单元测试、原型开发、无数据库场景。
594pub struct InMemoryRepository<E: Clone + Send + Sync + 'static> {
595    storage: RwLock<Vec<E>>,
596}
597
598impl<E: Clone + Send + Sync + 'static> InMemoryRepository<E> {
599    /// 创建空仓储
600    pub fn new() -> Self {
601        Self {
602            storage: RwLock::new(Vec::new()),
603        }
604    }
605
606    /// 从已有集合创建
607    pub fn from_vec(items: Vec<E>) -> Self {
608        Self {
609            storage: RwLock::new(items),
610        }
611    }
612
613    /// 当前存储条数
614    pub fn len(&self) -> usize {
615        match self.storage.read() {
616            Ok(storage) => storage.len(),
617            Err(_) => 0,
618        }
619    }
620
621    /// 是否为空
622    pub fn is_empty(&self) -> bool {
623        self.len() == 0
624    }
625
626    /// 清空
627    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
640/// 值比较辅助函数(支持基本类型 + Value 比较)
641fn 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                // 简化 LIKE:将 % 转换为 .*,其他字符转义
675                // 大小写不敏感(对齐 MySQL utf8mb4_general_ci / utf8mb4_unicode_ci 默认 collation)
676                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 >= low AND value <= high
697            value_matches(value, WhereOp::Ge, low, &[])
698                && value_matches(value, WhereOp::Le, high, &[])
699        }
700    }
701}
702
703/// 实体属性提取 trait(用户为实体实现此 trait 以支持 find_by)
704pub trait EntityAttributes: Send + Sync {
705    /// 按字段名获取属性值
706    fn get_attribute(&self, field: &str) -> Option<Value>;
707}
708
709/// InMemoryRepository 的 EntityAttributes-based 实现
710impl<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        // 查找是否已存在
763        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        // 注意:entity 可能被修改(如自增主键),这里返回原值
774        let _ = &mut entity; // 标记 mut 以符合签名
775        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
793// ============================================================================
794// simple_regex — 极简 LIKE 模式匹配(避免引入正则依赖)
795// ============================================================================
796
797mod simple_regex {
798    /// 极简正则编译器:仅支持 `.*`、`^`、`$`、字面字符
799    pub struct Regex {
800        patterns: Vec<Pattern>,
801    }
802
803    enum Pattern {
804        AnyChars,      // .*(贪婪匹配任意字符)
805        Literal(char), // 字面字符
806        Start,         // ^
807        End,           // $
808    }
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                    // 尝试匹配 0 到 len 个字符
859                    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
871// ============================================================================
872// GenericKeyRepository — 支持任意 Key 类型的内存仓储
873// ============================================================================
874
875/// 通用 Key 提取 trait(用户为实体实现此 trait 以支持任意 Key 类型)
876pub trait EntityKey<K>: Send + Sync {
877    /// 提取主键
878    fn key(&self) -> K;
879}
880
881/// 通用 Key 内存仓储
882pub 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// ============================================================================
1021// 单元测试
1022// ============================================================================
1023
1024#[cfg(test)]
1025mod tests {
1026    use super::*;
1027
1028    // ===== 测试用实体 =====
1029
1030    #[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    // ===== WhereOp / WhereCondition =====
1068
1069    #[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    // ===== PageResult =====
1119
1120    #[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()); // page 5 < total_pages 10
1150    }
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    // ===== RepositoryError =====
1168
1169    #[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    // ===== InMemoryRepository 基础 =====
1194
1195    #[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    // ===== Repository trait CRUD =====
1220
1221    #[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    // ===== S-1: batch_update 批量更新 =====
1253
1254    #[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        // 所有实体都存在:全部更新,无跳过
1291        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        // 验证实际更新生效
1305        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        // 总数不变(不是插入)
1312        assert_eq!(repo.len(), 2);
1313    }
1314
1315    #[test]
1316    fn test_repo_batch_update_partial_missing() {
1317        // 部分实体不存在:仅更新存在的,跳过不存在的
1318        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"), // 不存在
1322        ];
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        // 验证存在的实体被更新
1329        let alice = repo.find_by_id(&Value::I64(1)).unwrap().unwrap();
1330        assert_eq!(alice.name, "Alice Updated");
1331        // 不存在的实体不会被插入
1332        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        // 所有实体都不存在:全部跳过
1339        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); // 原数据不变
1348    }
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        // 验证 batch_update 与 save_many 语义不同:
1362        // - save_many 是 upsert(不存在的会插入)
1363        // - batch_update 是纯更新(不存在的会跳过)
1364        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        // save_many:两条都保存(id=999 会插入)
1373        let saved = repo1.save_many(updates.clone()).unwrap();
1374        assert_eq!(saved.len(), 2);
1375        assert_eq!(repo1.len(), 2); // 1 条原始 + 1 条新增
1376
1377        // batch_update:仅 id=1 更新,id=999 跳过
1378        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); // 不增加
1382    }
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        // 更新
1391        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    // ===== Repository 条件查询 =====
1460
1461    #[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        // 对齐 MySQL utf8mb4_general_ci / utf8mb4_unicode_ci 默认 collation(大小写不敏感)
1510        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        // 小写 pattern 应匹配大小写混合的数据
1517        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        // 大写 pattern 也应匹配小写数据
1527        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        // 对齐 PHP ThinkPHP `where('field1|field2|field3','like','%kw%')` 多字段 OR LIKE
1544        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"), // email 含 kw
1548            User::new(4, "Dave", 32, "dave@example.com"),
1549        ]);
1550
1551        let and = vec![]; // 无 AND 条件
1552        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        // 只有 Carol 的 email 含 kw
1559        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        // AND + OR 组合:对齐 PHP `where(is_delete=0 AND app_id=1) AND (name LIKE %kw% OR addr LIKE %kw%)`
1566        let repo = InMemoryRepository::from_vec(vec![
1567            User::new(1, "Alice_kw", 30, "alice@example.com"), // app_id=1, name 含 kw
1568            User::new(2, "Bob", 25, "bob@kw.com"),             // app_id=1, email 含 kw
1569            User::new(3, "Carol_kw", 28, "carol@example.com"), // app_id=2, name 含 kw(被 AND 排除)
1570            User::new(4, "Dave", 32, "dave@example.com"),      // app_id=1, 无 kw
1571        ]);
1572
1573        // User struct 没有 app_id 字段,用 age 模拟 AND 条件:age >= 28
1574        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        // age >= 28: Alice(30), Carol(28), Dave(32)
1582        // 其中 name 或 email 含 kw: Alice(name), Carol(name), Bob(email 不满足 age>=28)
1583        // 但 Carol age=28 满足 >= 28,所以 Carol 也应被选中
1584        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        // or_filter 为空时应等同于 find_by
1593        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    // ===== 分页 =====
1743
1744    #[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        // age=22 的用户:i=2,7,12,17 → 4 个
1794        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    // ===== GenericKeyRepository =====
1807
1808    #[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    // ===== simple_regex =====
1881
1882    #[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    // ===== 端到端场景 =====
1904
1905    #[test]
1906    fn test_e2e_repository_workflow() {
1907        let repo = InMemoryRepository::<User>::new();
1908
1909        // 1. 批量插入
1910        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        // 2. 查找成年人(age >= 30)
1921        let adults = repo
1922            .find_by(&[WhereCondition::new("age", WhereOp::Ge, Value::I64(30))])
1923            .unwrap();
1924        assert_eq!(adults.len(), 3);
1925
1926        // 3. 分页查询
1927        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        // 4. 条件分页查询(age >= 30)
1938        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        // 5. 更新
1949        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        // 6. 删除
1956        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        // 7. 条件删除 age >= 31:Alice(31) + Carol(35) + Eve(32) = 3 个
1962        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); // 仅剩 Dave(28)
1967    }
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    // ===== 安全限制:TooManyRows =====
1991
1992    #[test]
1993    fn test_paginate_too_many_rows() {
1994        // 直接构造 TooManyRows 错误,验证变体和 Display 信息
1995        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        // 行数恰好等于 REPOSITORY_SCAN_LIMIT:允许通过(不触发限制)
2008        // 用较小数据集验证 count 路径正确:count == total 且 paginate 正常返回
2009        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        // 远低于上限,paginate 应正常返回
2015        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        // 验证错误变体的 Display 信息包含关键数值
2020        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}