Skip to main content

sz_orm_core/
eager_loader.rs

1//! EagerLoader — Eager Loading 端到端自动执行与组装(P-F-1, v2.1.0)
2//!
3//! 一行 API `eager_load_all(conn, main_sql, relation)` 自动执行主表 + 关联表查询
4//! 并组装 `Vec<(MainRow, Vec<RelatedRow>)>`,消除 N+1。
5//!
6//! # 设计(ADR-v2.1.0-001)
7//!
8//! - **HasMany / ManyToMany**:双查询策略(主表查询 → 提取主键 → WHERE IN 批量查询 → 分组组装)
9//! - **HasOne / BelongsTo**:JOIN 策略(单条 SQL,结果集拆分组装)
10//! - 多级关联 `with()` 限 2 级(ADR-v2.1.0-006)
11//! - Oracle IN 列表 >1000 时分批查询
12//!
13//! # 用法
14//!
15//! ```ignore
16//! use sz_orm_core::eager_loader::eager_load_all;
17//!
18//! let results = eager_load_all(
19//!     &mut conn,
20//!     "SELECT * FROM users",
21//!     &order_relation,
22//! ).await?;
23//! // results: Vec<(user_row, Vec<order_row>)>
24//! ```
25
26use crate::cycle_detection::{CycleDetector, CyclePolicy};
27use crate::pool::Connection;
28use crate::relation_trait::RelationDef;
29use crate::value::Value;
30use crate::DbError;
31
32use std::collections::HashMap;
33
34/// Eager Loading 结果类型:主表行 + 关联行列表
35pub type EagerResult = (HashMap<String, Value>, Vec<HashMap<String, Value>>);
36
37/// 多级 Eager Loading 结果(递归类型,v2.2.0 新增)
38///
39/// 表示无限级嵌套的 Eager Loading 结果树:
40/// - [`NestedEagerResult::Leaf`]:叶子节点(无子级关联)
41/// - [`NestedEagerResult::Node`]:分支节点(本级行 + 子级结果)
42///
43/// # 用法
44///
45/// ```ignore
46/// use sz_orm_core::eager_loader::NestedEagerResult;
47///
48/// let leaf = NestedEagerResult::Leaf(row);
49/// assert!(leaf.is_leaf());
50///
51/// let node = NestedEagerResult::Node { row, children: vec![] };
52/// assert!(!node.is_leaf());
53/// ```
54#[derive(Debug, Clone)]
55pub enum NestedEagerResult {
56    /// 叶子节点(无子级关联)
57    Leaf(HashMap<String, Value>),
58    /// 分支节点(本级行 + 子级结果)
59    Node {
60        /// 本级行数据
61        row: HashMap<String, Value>,
62        /// 子级嵌套结果
63        children: Vec<NestedEagerResult>,
64    },
65}
66
67impl NestedEagerResult {
68    /// 返回本级行数据引用
69    pub fn row(&self) -> &HashMap<String, Value> {
70        match self {
71            NestedEagerResult::Leaf(row) => row,
72            NestedEagerResult::Node { row, .. } => row,
73        }
74    }
75
76    /// 返回子级结果切片
77    pub fn children(&self) -> &[NestedEagerResult] {
78        match self {
79            NestedEagerResult::Leaf(_) => &[],
80            NestedEagerResult::Node { children, .. } => children,
81        }
82    }
83
84    /// 是否为叶子节点
85    pub fn is_leaf(&self) -> bool {
86        matches!(self, NestedEagerResult::Leaf(_))
87    }
88}
89
90/// 子级加载配置(多级关联,v2.2.0 改为递归结构支持无限级)
91struct ChildLoadConfig {
92    relation: RelationDef,
93    /// 子级的子级(无限级嵌套)
94    children: Vec<ChildLoadConfig>,
95}
96
97impl ChildLoadConfig {
98    /// 递归追加到最深层级
99    fn push_to_deepest(&mut self, child: ChildLoadConfig) {
100        if self.children.is_empty() {
101            self.children.push(child);
102        } else {
103            self.children.last_mut().unwrap().push_to_deepest(child);
104        }
105    }
106
107    /// 递归计算链深度
108    fn chain_depth(&self) -> usize {
109        if self.children.is_empty() {
110            1
111        } else {
112            1 + self.children[0].chain_depth()
113        }
114    }
115
116    /// 递归收集关联名称
117    fn chain_names(&self) -> Vec<&str> {
118        let mut names = vec![std::borrow::Borrow::<str>::borrow(&self.relation.name)];
119        if !self.children.is_empty() {
120            names.extend(self.children[0].chain_names());
121        }
122        names
123    }
124}
125
126/// Eager Loading 执行器
127///
128/// 自动执行主表 + 关联表查询并组装结果,消除 N+1 查询。
129pub struct EagerLoader {
130    relation: RelationDef,
131    children: Vec<ChildLoadConfig>,
132    /// 循环检测策略(v2.2.0 新增)
133    cycle_policy: CyclePolicy,
134}
135
136impl EagerLoader {
137    /// 创建 EagerLoader
138    pub fn new(relation: RelationDef) -> Self {
139        Self {
140            relation,
141            children: Vec::new(),
142            cycle_policy: CyclePolicy::default(),
143        }
144    }
145
146    /// 添加子级关联(v2.2.0 扩展为无限级链式调用)
147    ///
148    /// 每次 `with()` 追加到最深层级,构建线性关联链:
149    ///
150    /// ```ignore
151    /// EagerLoader::new(order_relation)       // User → Order
152    ///     .with(order_item_relation)          // Order → OrderItem
153    ///     .with(product_relation)             // OrderItem → Product
154    /// // 构建 4 级链:User → Order → OrderItem → Product
155    /// ```
156    pub fn with(mut self, relation: RelationDef) -> Self {
157        let new_child = ChildLoadConfig {
158            relation,
159            children: Vec::new(),
160        };
161        if self.children.is_empty() {
162            self.children.push(new_child);
163        } else {
164            self.children.last_mut().unwrap().push_to_deepest(new_child);
165        }
166        self
167    }
168
169    /// 设置循环检测策略(v2.2.0 新增)
170    ///
171    /// ```ignore
172    /// use sz_orm_core::cycle_detection::CyclePolicy;
173    ///
174    /// let loader = EagerLoader::new(rel)
175    ///     .with(child_rel)
176    ///     .with_cycle_policy(CyclePolicy::Truncate);
177    /// ```
178    pub fn with_cycle_policy(mut self, policy: CyclePolicy) -> Self {
179        self.cycle_policy = policy;
180        self
181    }
182
183    /// 切换到智能策略选择模式(v2.3.0 新增)
184    ///
185    /// 返回 [`SmartEagerLoader`](crate::smart_eager_loader::SmartEagerLoader),
186    /// 基于 `RelationKind` 自动选择最优加载策略:
187    /// - HasOne / BelongsTo → JOIN(单次查询)
188    /// - HasMany → Data Loader(批量 IN 查询)
189    /// - ManyToMany → 中间表批量查询
190    ///
191    /// 原有 `EagerLoader` API(`new`/`with`/`load_many`/`load_nested`)不变,
192    /// 此方法为扩展入口,不影响 v2.2.0 代码行为。
193    ///
194    /// ```ignore
195    /// use sz_orm_core::eager_loader::EagerLoader;
196    ///
197    /// let loader = EagerLoader::new(order_rel)
198    ///     .with(item_rel)
199    ///     .smart();
200    /// let tree = loader.load(&mut conn, "SELECT id, name FROM users").await?;
201    /// ```
202    pub fn smart(self) -> crate::smart_eager_loader::SmartEagerLoader {
203        let mut smart = crate::smart_eager_loader::SmartEagerLoader::new(self.relation)
204            .with_cycle_policy(self.cycle_policy);
205        for child in &self.children {
206            let relations = collect_child_relations(child);
207            for rel in relations {
208                smart = smart.with(rel);
209            }
210        }
211        smart
212    }
213
214    /// 返回子级关联链深度(v2.2.0 改为递归计算)
215    pub fn children_count(&self) -> usize {
216        if self.children.is_empty() {
217            0
218        } else {
219            self.children[0].chain_depth()
220        }
221    }
222
223    /// 返回子级关联名称列表(v2.2.0 改为递归遍历链)
224    pub fn child_names(&self) -> Vec<&str> {
225        if self.children.is_empty() {
226            Vec::new()
227        } else {
228            self.children[0].chain_names()
229        }
230    }
231
232    /// 执行 HasMany 双查询策略
233    ///
234    /// 1. 执行主表 SQL → 提取主键列表
235    /// 2. 生成 `WHERE fk IN (?, ...)` 批量查询
236    /// 3. 执行关联表查询 → 按外键分组组装
237    /// 4. 若有 children,递归加载子级关联(限 2 级)
238    pub async fn load_many(
239        &self,
240        conn: &mut dyn Connection,
241        main_sql: &str,
242    ) -> Result<Vec<EagerResult>, DbError> {
243        let main_rows = conn.query(main_sql).await?;
244
245        if main_rows.is_empty() {
246            return Ok(Vec::new());
247        }
248
249        let pk_values = self.extract_primary_keys(&main_rows);
250        if pk_values.is_empty() {
251            return Ok(main_rows.into_iter().map(|r| (r, Vec::new())).collect());
252        }
253
254        let related_rows = self.batch_query_related(conn, &pk_values).await?;
255        let grouped = self.group_by_foreign_key(related_rows, self.relation.to_key);
256
257        // 多级关联:递归加载子级
258        if !self.children.is_empty() {
259            let all_related: Vec<&HashMap<String, Value>> = grouped.values().flatten().collect();
260            let all_related_owned: Vec<HashMap<String, Value>> =
261                all_related.into_iter().cloned().collect();
262            let _child_groups = self.load_children(conn, &all_related_owned).await?;
263            // 子级关联结果已加载,可用于后续嵌套组装
264        }
265
266        let results = main_rows
267            .into_iter()
268            .map(|row| {
269                let pk = row
270                    .get(self.relation.from_key)
271                    .cloned()
272                    .unwrap_or(Value::Null);
273                let pk_key = value_to_key(&pk);
274                let related = grouped.get(&pk_key).cloned().unwrap_or_default();
275                (row, related)
276            })
277            .collect();
278
279        Ok(results)
280    }
281
282    /// 递归加载子级关联(多级嵌套)
283    ///
284    /// 对已加载的关联行继续加载子级关联,组装嵌套结构。
285    async fn load_children(
286        &self,
287        conn: &mut dyn Connection,
288        parent_rows: &[HashMap<String, Value>],
289    ) -> Result<HashMap<String, Vec<HashMap<String, Value>>>, DbError> {
290        if self.children.is_empty() || parent_rows.is_empty() {
291            return Ok(HashMap::new());
292        }
293
294        let child_relation = &self.children[0].relation;
295        let pk_values: Vec<Value> = parent_rows
296            .iter()
297            .filter_map(|row| row.get(child_relation.from_key).cloned())
298            .collect();
299
300        if pk_values.is_empty() {
301            return Ok(HashMap::new());
302        }
303
304        let mut all_child_rows = Vec::new();
305        for chunk in pk_values.chunks(1000) {
306            let placeholders: Vec<String> = (0..chunk.len()).map(|_| "?".to_string()).collect();
307            let sql = format!(
308                "SELECT * FROM {} WHERE {} IN ({})",
309                child_relation.to_entity,
310                child_relation.to_key,
311                placeholders.join(", ")
312            );
313            let rows = conn.query_with_params(&sql, chunk).await?;
314            all_child_rows.extend(rows);
315        }
316
317        Ok(self.group_by_foreign_key(all_child_rows, child_relation.to_key))
318    }
319
320    /// 执行多级 Eager Loading,返回嵌套结果树(v2.2.0 新增)
321    ///
322    /// 自动执行主表 + 各级关联表批量查询,组装 `Vec<NestedEagerResult>` 嵌套树。
323    /// 每级使用 `WHERE fk IN (?, ...)` 参数化批量查询,消除 N+1。
324    /// 循环检测根据 `cycle_policy` 策略处理循环引用。
325    ///
326    /// # 执行流程
327    ///
328    /// 1. 初始化 `CycleDetector(cycle_policy)`
329    /// 2. 执行主表 SQL 获取根行
330    /// 3. 递归加载各子级:提取父级主键 → `WHERE fk IN (?, ...)` 批量查询 → 按外键分组 → 递归子级
331    /// 4. 返回 `NestedEagerResult` 嵌套树
332    ///
333    /// # 参数
334    ///
335    /// - `conn`:数据库连接
336    /// - `main_sql`:主表查询 SQL
337    ///
338    /// # 异常处理
339    ///
340    /// - 结果集超内存限制(>1,000,000 行)→ `Err(DbError::InvalidInput)` 含建议改用 Stream API
341    /// - 循环检测策略为 `Error` 且检测到循环 → `Err(DbError::InvalidInput)` 含循环路径
342    ///
343    /// ```ignore
344    /// let loader = EagerLoader::new(order_rel)
345    ///     .with(item_rel)
346    ///     .with(product_rel)
347    ///     .with_cycle_policy(CyclePolicy::Truncate);
348    /// let tree = loader.load_nested(&mut conn, "SELECT id, name FROM users").await?;
349    /// // tree: Vec<NestedEagerResult>(4 级嵌套树)
350    /// ```
351    pub async fn load_nested(
352        &self,
353        conn: &mut dyn Connection,
354        main_sql: &str,
355    ) -> Result<Vec<NestedEagerResult>, DbError> {
356        let mut detector = CycleDetector::new(self.cycle_policy);
357        let main_rows = conn.query(main_sql).await?;
358
359        if main_rows.is_empty() {
360            return Ok(Vec::new());
361        }
362
363        const MAX_RESULT_SIZE: usize = 1_000_000;
364        if main_rows.len() > MAX_RESULT_SIZE {
365            return Err(DbError::InvalidInput(format!(
366                "结果集超内存限制({} 行),建议改用 Stream API 处理大结果集",
367                main_rows.len()
368            )));
369        }
370
371        if self.children.is_empty() {
372            return Ok(main_rows.into_iter().map(NestedEagerResult::Leaf).collect());
373        }
374
375        let first_child = &self.children[0];
376        self.load_level_nested(
377            conn,
378            main_rows,
379            &first_child.relation,
380            &first_child.children,
381            &mut detector,
382        )
383        .await
384    }
385
386    /// 递归加载单级关联并构建嵌套树
387    async fn load_level_nested(
388        &self,
389        conn: &mut dyn Connection,
390        parent_rows: Vec<HashMap<String, Value>>,
391        relation: &RelationDef,
392        child_configs: &[ChildLoadConfig],
393        detector: &mut CycleDetector,
394    ) -> Result<Vec<NestedEagerResult>, DbError> {
395        let can_continue = detector.check(relation.from_entity, relation.name)?;
396        if !can_continue {
397            return Ok(parent_rows
398                .into_iter()
399                .map(NestedEagerResult::Leaf)
400                .collect());
401        }
402
403        detector.enter(relation.from_entity, relation.name);
404
405        let pk_values: Vec<Value> = parent_rows
406            .iter()
407            .filter_map(|row| row.get(relation.from_key).cloned())
408            .collect();
409
410        if pk_values.is_empty() {
411            detector.leave();
412            return Ok(parent_rows
413                .into_iter()
414                .map(|row| NestedEagerResult::Node {
415                    row,
416                    children: Vec::new(),
417                })
418                .collect());
419        }
420
421        let related_rows = batch_query_with_relation(conn, relation, &pk_values).await?;
422        let grouped = group_rows_by_foreign_key(related_rows, relation.to_key);
423
424        let mut results = Vec::with_capacity(parent_rows.len());
425        for parent_row in parent_rows {
426            let pk = parent_row
427                .get(relation.from_key)
428                .cloned()
429                .unwrap_or(Value::Null);
430            let pk_key = value_to_key(&pk);
431            let child_rows = grouped.get(&pk_key).cloned().unwrap_or_default();
432
433            let children = if child_rows.is_empty() {
434                Vec::new()
435            } else if child_configs.is_empty() {
436                child_rows
437                    .into_iter()
438                    .map(NestedEagerResult::Leaf)
439                    .collect()
440            } else {
441                let next_config = &child_configs[0];
442                Box::pin(self.load_level_nested(
443                    conn,
444                    child_rows,
445                    &next_config.relation,
446                    &next_config.children,
447                    detector,
448                ))
449                .await?
450            };
451
452            results.push(NestedEagerResult::Node {
453                row: parent_row,
454                children,
455            });
456        }
457
458        detector.leave();
459        Ok(results)
460    }
461
462    /// 从主表结果提取主键值列表
463    fn extract_primary_keys(&self, rows: &[HashMap<String, Value>]) -> Vec<Value> {
464        rows.iter()
465            .filter_map(|row| row.get(self.relation.from_key).cloned())
466            .collect()
467    }
468
469    /// 批量查询关联表(Oracle IN >1000 分批)
470    async fn batch_query_related(
471        &self,
472        conn: &mut dyn Connection,
473        pk_values: &[Value],
474    ) -> Result<Vec<HashMap<String, Value>>, DbError> {
475        let batch_size = 1000;
476        let mut all_rows = Vec::new();
477
478        for chunk in pk_values.chunks(batch_size) {
479            let sql = self.build_related_sql(chunk.len());
480            let rows = conn.query_with_params(&sql, chunk).await?;
481            all_rows.extend(rows);
482        }
483
484        Ok(all_rows)
485    }
486
487    /// 生成关联表查询 SQL(参数化 WHERE IN)
488    fn build_related_sql(&self, param_count: usize) -> String {
489        let placeholders: Vec<String> = (0..param_count).map(|_| "?".to_string()).collect();
490        format!(
491            "SELECT * FROM {} WHERE {} IN ({})",
492            self.relation.to_entity,
493            self.relation.to_key,
494            placeholders.join(", ")
495        )
496    }
497
498    /// 按外键值分组关联行
499    fn group_by_foreign_key(
500        &self,
501        rows: Vec<HashMap<String, Value>>,
502        fk_key: &str,
503    ) -> HashMap<String, Vec<HashMap<String, Value>>> {
504        let mut grouped: HashMap<String, Vec<HashMap<String, Value>>> = HashMap::new();
505        for row in rows {
506            let fk = row.get(fk_key).cloned().unwrap_or(Value::Null);
507            let key = value_to_key(&fk);
508            grouped.entry(key).or_default().push(row);
509        }
510        grouped
511    }
512}
513
514/// 递归收集 ChildLoadConfig 链中的所有 RelationDef(v2.3.0 smart() 转移用)
515fn collect_child_relations(config: &ChildLoadConfig) -> Vec<RelationDef> {
516    let mut relations = vec![config.relation.clone()];
517    for child in &config.children {
518        relations.extend(collect_child_relations(child));
519    }
520    relations
521}
522
523/// 将 Value 转换为字符串键(用于 HashMap 分组,因 Value 含 f32/f64 不实现 Hash/Eq)
524fn value_to_key(value: &Value) -> String {
525    match value {
526        Value::Null => "null".to_string(),
527        Value::Bool(b) => format!("bool:{}", b),
528        Value::I8(v) => format!("i8:{}", v),
529        Value::I16(v) => format!("i16:{}", v),
530        Value::I32(v) => format!("i32:{}", v),
531        Value::I64(v) => format!("i64:{}", v),
532        Value::U8(v) => format!("u8:{}", v),
533        Value::U16(v) => format!("u16:{}", v),
534        Value::U32(v) => format!("u32:{}", v),
535        Value::U64(v) => format!("u64:{}", v),
536        Value::F32(v) => format!("f32:{}", v),
537        Value::F64(v) => format!("f64:{}", v),
538        Value::String(s) => format!("str:{}", s),
539        _ => format!("other:{:?}", value),
540    }
541}
542
543/// 批量查询关联表(Oracle IN >1000 分批,v2.2.0 提取为公共函数)
544async fn batch_query_with_relation(
545    conn: &mut dyn Connection,
546    relation: &RelationDef,
547    pk_values: &[Value],
548) -> Result<Vec<HashMap<String, Value>>, DbError> {
549    let batch_size = 1000;
550    let mut all_rows = Vec::new();
551
552    for chunk in pk_values.chunks(batch_size) {
553        let placeholders: Vec<String> = (0..chunk.len()).map(|_| "?".to_string()).collect();
554        let sql = format!(
555            "SELECT * FROM {} WHERE {} IN ({})",
556            relation.to_entity,
557            relation.to_key,
558            placeholders.join(", ")
559        );
560        let rows = conn.query_with_params(&sql, chunk).await?;
561        all_rows.extend(rows);
562    }
563
564    Ok(all_rows)
565}
566
567/// 按外键值分组关联行(v2.2.0 提取为公共函数)
568fn group_rows_by_foreign_key(
569    rows: Vec<HashMap<String, Value>>,
570    fk_key: &str,
571) -> HashMap<String, Vec<HashMap<String, Value>>> {
572    let mut grouped: HashMap<String, Vec<HashMap<String, Value>>> = HashMap::new();
573    for row in rows {
574        let fk = row.get(fk_key).cloned().unwrap_or(Value::Null);
575        let key = value_to_key(&fk);
576        grouped.entry(key).or_default().push(row);
577    }
578    grouped
579}
580
581/// 一行 API:Eager Loading 端到端自动执行与组装
582///
583/// 执行主表查询 → 提取主键 → 批量查询关联表 → 分组组装
584/// 消除 N+1 查询(2 条 SQL 而非 N+1 条)。
585///
586/// # 参数
587///
588/// - `conn`:数据库连接
589/// - `main_sql`:主表查询 SQL(如 `"SELECT * FROM users"`)
590/// - `relation`:关联关系定义
591///
592/// # 返回
593///
594/// `Vec<(主表行, Vec<关联行>)>`
595///
596/// # 异常处理
597///
598/// - 主表查询失败 → 立即返回 `Err`,不执行关联查询
599/// - 关联表查询失败 → 返回 `Err`
600/// - 主表结果为空 → 返回 `Ok(Vec::new())`,不执行关联查询
601/// - 孤立关联记录(外键不匹配)→ 跳过
602pub async fn eager_load_all(
603    conn: &mut dyn Connection,
604    main_sql: &str,
605    relation: &RelationDef,
606) -> Result<Vec<EagerResult>, DbError> {
607    let loader = EagerLoader::new(relation.clone());
608    loader.load_many(conn, main_sql).await
609}
610
611/// 一行 API:HasOne / BelongsTo 单条关联加载(JOIN 策略)
612///
613/// 返回 `Vec<(主表行, Option<关联行>)>`
614pub async fn eager_load_one(
615    conn: &mut dyn Connection,
616    main_sql: &str,
617    relation: &RelationDef,
618) -> Result<Vec<(HashMap<String, Value>, Option<HashMap<String, Value>>)>, DbError> {
619    let main_rows = conn.query(main_sql).await?;
620
621    if main_rows.is_empty() {
622        return Ok(Vec::new());
623    }
624
625    let fk_values: Vec<Value> = main_rows
626        .iter()
627        .filter_map(|row| row.get(relation.to_key).cloned())
628        .collect();
629
630    if fk_values.is_empty() {
631        return Ok(main_rows.into_iter().map(|r| (r, None)).collect());
632    }
633
634    let placeholder: Vec<String> = (0..fk_values.len()).map(|_| "?".to_string()).collect();
635    let related_sql = format!(
636        "SELECT * FROM {} WHERE {} IN ({})",
637        relation.to_entity,
638        relation.from_key,
639        placeholder.join(", ")
640    );
641
642    let related_rows = conn.query_with_params(&related_sql, &fk_values).await?;
643
644    let mut related_map: HashMap<String, HashMap<String, Value>> = HashMap::new();
645    for row in related_rows {
646        let pk = row.get(relation.from_key).cloned().unwrap_or(Value::Null);
647        related_map.insert(value_to_key(&pk), row);
648    }
649
650    let results = main_rows
651        .into_iter()
652        .map(|row| {
653            let fk = row.get(relation.to_key).cloned().unwrap_or(Value::Null);
654            let related = related_map.get(&value_to_key(&fk)).cloned();
655            (row, related)
656        })
657        .collect();
658
659    Ok(results)
660}
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665    use crate::relation_trait::RelationKind;
666
667    #[test]
668    fn test_eager_loader_new() {
669        let relation = RelationDef::new(
670            "orders",
671            "users",
672            "orders",
673            "id",
674            "user_id",
675            RelationKind::HasMany,
676        );
677        let loader = EagerLoader::new(relation);
678        assert_eq!(loader.relation.name, "orders");
679        assert!(loader.children.is_empty());
680    }
681
682    #[test]
683    fn test_eager_loader_with_children() {
684        let relation = RelationDef::new(
685            "orders",
686            "users",
687            "orders",
688            "id",
689            "user_id",
690            RelationKind::HasMany,
691        );
692        let child_relation = RelationDef::new(
693            "items",
694            "orders",
695            "order_items",
696            "id",
697            "order_id",
698            RelationKind::HasMany,
699        );
700        let loader = EagerLoader::new(relation).with(child_relation);
701        assert_eq!(loader.children.len(), 1);
702    }
703
704    #[test]
705    fn test_build_related_sql() {
706        let relation = RelationDef::new(
707            "orders",
708            "users",
709            "orders",
710            "id",
711            "user_id",
712            RelationKind::HasMany,
713        );
714        let loader = EagerLoader::new(relation);
715        let sql = loader.build_related_sql(3);
716        assert!(sql.contains("SELECT * FROM orders"));
717        assert!(sql.contains("user_id IN (?, ?, ?)"));
718    }
719
720    #[test]
721    fn test_extract_primary_keys() {
722        let relation = RelationDef::new(
723            "orders",
724            "users",
725            "orders",
726            "id",
727            "user_id",
728            RelationKind::HasMany,
729        );
730        let loader = EagerLoader::new(relation);
731
732        let mut row1 = HashMap::new();
733        row1.insert("id".to_string(), Value::I64(1));
734        let mut row2 = HashMap::new();
735        row2.insert("id".to_string(), Value::I64(2));
736
737        let pks = loader.extract_primary_keys(&[row1, row2]);
738        assert_eq!(pks.len(), 2);
739    }
740
741    #[test]
742    fn test_group_by_foreign_key() {
743        let relation = RelationDef::new(
744            "orders",
745            "users",
746            "orders",
747            "id",
748            "user_id",
749            RelationKind::HasMany,
750        );
751        let loader = EagerLoader::new(relation);
752
753        let mut row1 = HashMap::new();
754        row1.insert("user_id".to_string(), Value::I64(1));
755        row1.insert("id".to_string(), Value::I64(101));
756        let mut row2 = HashMap::new();
757        row2.insert("user_id".to_string(), Value::I64(1));
758        row2.insert("id".to_string(), Value::I64(102));
759        let mut row3 = HashMap::new();
760        row3.insert("user_id".to_string(), Value::I64(2));
761        row3.insert("id".to_string(), Value::I64(103));
762
763        let grouped = loader.group_by_foreign_key(vec![row1, row2, row3], "user_id");
764        assert_eq!(grouped.len(), 2);
765        assert_eq!(grouped.get("i64:1").unwrap().len(), 2);
766        assert_eq!(grouped.get("i64:2").unwrap().len(), 1);
767    }
768
769    #[test]
770    fn test_nested_eager_result_leaf() {
771        let mut row = HashMap::new();
772        row.insert("id".to_string(), Value::I64(1));
773        let leaf = NestedEagerResult::Leaf(row.clone());
774        assert!(leaf.is_leaf());
775        assert_eq!(leaf.row().get("id"), Some(&Value::I64(1)));
776        assert!(leaf.children().is_empty());
777    }
778
779    #[test]
780    fn test_nested_eager_result_node() {
781        let mut row = HashMap::new();
782        row.insert("id".to_string(), Value::I64(1));
783        let child = NestedEagerResult::Leaf(HashMap::new());
784        let node = NestedEagerResult::Node {
785            row: row.clone(),
786            children: vec![child],
787        };
788        assert!(!node.is_leaf());
789        assert_eq!(node.row().get("id"), Some(&Value::I64(1)));
790        assert_eq!(node.children().len(), 1);
791        assert!(node.children()[0].is_leaf());
792    }
793
794    #[test]
795    fn test_eager_loader_4_level_chain() {
796        let rel1 = RelationDef::new(
797            "orders",
798            "users",
799            "orders",
800            "id",
801            "user_id",
802            RelationKind::HasMany,
803        );
804        let rel2 = RelationDef::new(
805            "items",
806            "orders",
807            "order_items",
808            "id",
809            "order_id",
810            RelationKind::HasMany,
811        );
812        let rel3 = RelationDef::new(
813            "product",
814            "order_items",
815            "products",
816            "id",
817            "product_id",
818            RelationKind::BelongsTo,
819        );
820        let loader = EagerLoader::new(rel1).with(rel2).with(rel3);
821        assert_eq!(loader.children_count(), 2);
822        assert_eq!(loader.child_names(), vec!["items", "product"]);
823    }
824
825    #[test]
826    fn test_eager_loader_with_cycle_policy() {
827        let rel = RelationDef::new(
828            "orders",
829            "users",
830            "orders",
831            "id",
832            "user_id",
833            RelationKind::HasMany,
834        );
835        let loader = EagerLoader::new(rel).with_cycle_policy(CyclePolicy::Error);
836        assert_eq!(loader.cycle_policy, CyclePolicy::Error);
837    }
838
839    #[test]
840    fn test_eager_loader_default_cycle_policy() {
841        let rel = RelationDef::new(
842            "orders",
843            "users",
844            "orders",
845            "id",
846            "user_id",
847            RelationKind::HasMany,
848        );
849        let loader = EagerLoader::new(rel);
850        assert_eq!(loader.cycle_policy, CyclePolicy::Truncate);
851    }
852
853    #[test]
854    fn test_eager_loader_chain_depth_limit() {
855        let rel1 = RelationDef::new("a", "t0", "t1", "id", "t0_id", RelationKind::HasMany);
856        let rel2 = RelationDef::new("b", "t1", "t2", "id", "t1_id", RelationKind::HasMany);
857        let rel3 = RelationDef::new("c", "t2", "t3", "id", "t2_id", RelationKind::HasMany);
858        let rel4 = RelationDef::new("d", "t3", "t4", "id", "t3_id", RelationKind::HasMany);
859        let loader = EagerLoader::new(rel1).with(rel2).with(rel3).with(rel4);
860        assert_eq!(loader.children_count(), 3);
861        assert_eq!(loader.child_names(), vec!["b", "c", "d"]);
862    }
863
864    #[test]
865    fn test_eager_loader_backward_compat_2_level() {
866        let rel1 = RelationDef::new(
867            "orders",
868            "users",
869            "orders",
870            "id",
871            "user_id",
872            RelationKind::HasMany,
873        );
874        let rel2 = RelationDef::new(
875            "items",
876            "orders",
877            "order_items",
878            "id",
879            "order_id",
880            RelationKind::HasMany,
881        );
882        let loader = EagerLoader::new(rel1).with(rel2);
883        assert_eq!(loader.children.len(), 1);
884        assert_eq!(loader.children_count(), 1);
885        assert_eq!(loader.child_names(), vec!["items"]);
886    }
887}