use std::collections::HashMap;
use sz_orm_core::eager_loader::NestedEagerResult;
use sz_orm_core::relation_trait::RelationKind;
use sz_orm_core::smart_eager_loader::{LoadStrategy, StrategyDecision};
use sz_orm_core::Value;
fn values_equivalent(a: &Value, b: &Value) -> bool {
if a == b {
return true;
}
let a_i = value_to_i64(a);
let b_i = value_to_i64(b);
if let (Some(av), Some(bv)) = (a_i, b_i) {
return av == bv;
}
let a_s = value_to_str(a);
let b_s = value_to_str(b);
if let (Some(av), Some(bv)) = (a_s, b_s) {
return av == bv;
}
false
}
fn value_to_i64(v: &Value) -> Option<i64> {
match v {
Value::I8(x) => Some(*x as i64),
Value::I16(x) => Some(*x as i64),
Value::I32(x) => Some(*x as i64),
Value::I64(x) => Some(*x),
Value::U8(x) => Some(*x as i64),
Value::U16(x) => Some(*x as i64),
Value::U32(x) => Some(*x as i64),
Value::U64(x) => Some(*x as i64),
_ => None,
}
}
fn value_to_str(v: &Value) -> Option<&str> {
match v {
Value::String(s) => Some(s),
Value::Decimal(s) => Some(s),
Value::Uuid(s) => Some(s),
_ => None,
}
}
fn rows_equivalent(a: &HashMap<String, Value>, b: &HashMap<String, Value>) -> bool {
if a.len() != b.len() {
return false;
}
for (k, av) in a {
match b.get(k) {
Some(bv) => {
if !values_equivalent(av, bv) {
return false;
}
}
None => return false,
}
}
true
}
fn sort_results_by_fk(
results: &[HashMap<String, Value>],
fk_field: &str,
) -> Vec<HashMap<String, Value>> {
let mut sorted = results.to_vec();
sorted.sort_by(|a, b| {
let av = value_to_i64(a.get(fk_field).unwrap_or(&Value::Null)).unwrap_or(0);
let bv = value_to_i64(b.get(fk_field).unwrap_or(&Value::Null)).unwrap_or(0);
av.cmp(&bv)
});
sorted
}
pub fn assert_eager_equivalent(
smart_results: &[HashMap<String, Value>],
manual_results: &[HashMap<String, Value>],
relation_kind: RelationKind,
fk_field: &str,
) {
assert_eq!(
smart_results.len(),
manual_results.len(),
"结果集行数不匹配: smart={} manual={}",
smart_results.len(),
manual_results.len()
);
let (smart_sorted, manual_sorted) = match relation_kind {
RelationKind::HasOne | RelationKind::BelongsTo => {
(smart_results.to_vec(), manual_results.to_vec())
}
RelationKind::HasMany | RelationKind::ManyToMany => {
if fk_field.is_empty() {
(smart_results.to_vec(), manual_results.to_vec())
} else {
(
sort_results_by_fk(smart_results, fk_field),
sort_results_by_fk(manual_results, fk_field),
)
}
}
};
for (i, (smart_row, manual_row)) in smart_sorted.iter().zip(manual_sorted.iter()).enumerate() {
if !rows_equivalent(smart_row, manual_row) {
let mut diffs = Vec::new();
let all_keys: std::collections::BTreeSet<&String> =
smart_row.keys().chain(manual_row.keys()).collect();
for k in &all_keys {
let sv = smart_row.get(*k);
let mv = manual_row.get(*k);
if sv != mv
&& !match (sv, mv) {
(Some(a), Some(b)) => values_equivalent(a, b),
_ => false,
}
{
diffs.push(format!(" 字段 '{k}': smart={sv:?} manual={mv:?}"));
}
}
panic!(
"结果集第 {i} 行不匹配:\n{}\nsmart_row={smart_row:?}\nmanual_row={manual_row:?}",
diffs.join("\n")
);
}
}
}
pub fn assert_strategy_selected(decision: &StrategyDecision, expected: LoadStrategy) {
if decision.strategy != expected {
panic!(
"策略选择不符: relation='{}' actual={:?} expected={:?} reason='{}'",
decision.relation_name, decision.strategy, expected, decision.reason
);
}
}
fn nested_depth(tree: &NestedEagerResult, max_recursion: usize) -> usize {
if max_recursion == 0 {
return 1;
}
match tree {
NestedEagerResult::Leaf(_) => 1,
NestedEagerResult::Node { children, .. } => {
1 + children
.iter()
.map(|c| nested_depth(c, max_recursion - 1))
.max()
.unwrap_or(0)
}
}
}
pub fn assert_nested_depth_equal(smart_tree: &NestedEagerResult, manual_tree: &NestedEagerResult) {
let smart_depth = nested_depth(smart_tree, 3);
let manual_depth = nested_depth(manual_tree, 3);
assert_eq!(
smart_depth, manual_depth,
"嵌套深度不一致: smart={smart_depth} manual={manual_depth}"
);
assert_nested_node_equal(smart_tree, manual_tree, 0, 3);
}
fn assert_nested_node_equal(
smart: &NestedEagerResult,
manual: &NestedEagerResult,
level: usize,
max_recursion: usize,
) {
if level >= max_recursion {
return;
}
match (smart, manual) {
(NestedEagerResult::Leaf(smart_row), NestedEagerResult::Leaf(manual_row)) => {
if !rows_equivalent(smart_row, manual_row) {
panic!(
"第 {level} 层 Leaf 节点 row 不匹配:\nsmart={smart_row:?}\nmanual={manual_row:?}"
);
}
}
(
NestedEagerResult::Node {
row: smart_row,
children: smart_children,
},
NestedEagerResult::Node {
row: manual_row,
children: manual_children,
},
) => {
if !rows_equivalent(smart_row, manual_row) {
panic!(
"第 {level} 层 Node 节点 row 不匹配:\nsmart={smart_row:?}\nmanual={manual_row:?}"
);
}
assert_eq!(
smart_children.len(),
manual_children.len(),
"第 {level} 层 children 数量不一致: smart={} manual={}",
smart_children.len(),
manual_children.len()
);
for (sc, mc) in smart_children.iter().zip(manual_children.iter()) {
assert_nested_node_equal(sc, mc, level + 1, max_recursion);
}
}
(smart, manual) => {
panic!("第 {level} 层节点类型不一致: smart={smart:?} manual={manual:?}");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn row(id: i64, name: &str) -> HashMap<String, Value> {
let mut r = HashMap::new();
r.insert("id".to_string(), Value::I64(id));
r.insert("name".to_string(), Value::String(name.to_string()));
r
}
#[test]
fn test_assert_eager_equivalent_equal() {
let smart = vec![row(1, "a"), row(2, "b")];
let manual = vec![row(1, "a"), row(2, "b")];
assert_eager_equivalent(&smart, &manual, RelationKind::HasOne, "");
}
#[test]
fn test_assert_eager_equivalent_hasmany_unordered() {
let smart = vec![row(2, "b"), row(1, "a")];
let manual = vec![row(1, "a"), row(2, "b")];
assert_eager_equivalent(&smart, &manual, RelationKind::HasMany, "id");
}
#[test]
#[should_panic(expected = "结果集行数不匹配")]
fn test_assert_eager_equivalent_row_count_mismatch() {
let smart = vec![row(1, "a")];
let manual = vec![row(1, "a"), row(2, "b")];
assert_eager_equivalent(&smart, &manual, RelationKind::HasOne, "");
}
#[test]
#[should_panic(expected = "结果集第 0 行不匹配")]
fn test_assert_eager_equivalent_field_mismatch() {
let smart = vec![row(1, "a")];
let manual = vec![row(1, "b")];
assert_eager_equivalent(&smart, &manual, RelationKind::HasOne, "");
}
#[test]
fn test_assert_strategy_selected_correct() {
let decision = StrategyDecision {
relation_name: "profile".to_string(),
relation_kind: RelationKind::HasOne,
strategy: LoadStrategy::Join,
reason: "HasOne → Join".to_string(),
estimated_query_count: 1,
};
assert_strategy_selected(&decision, LoadStrategy::Join);
}
#[test]
#[should_panic(expected = "策略选择不符")]
fn test_assert_strategy_selected_incorrect() {
let decision = StrategyDecision {
relation_name: "orders".to_string(),
relation_kind: RelationKind::HasMany,
strategy: LoadStrategy::Join,
reason: "HasMany → DataLoader".to_string(),
estimated_query_count: 2,
};
assert_strategy_selected(&decision, LoadStrategy::DataLoader);
}
#[test]
fn test_assert_nested_depth_equal_leaf() {
let smart = NestedEagerResult::Leaf(row(1, "a"));
let manual = NestedEagerResult::Leaf(row(1, "a"));
assert_nested_depth_equal(&smart, &manual);
}
#[test]
fn test_assert_nested_depth_equal_node() {
let smart = NestedEagerResult::Node {
row: row(1, "user"),
children: vec![NestedEagerResult::Leaf(row(1, "order"))],
};
let manual = NestedEagerResult::Node {
row: row(1, "user"),
children: vec![NestedEagerResult::Leaf(row(1, "order"))],
};
assert_nested_depth_equal(&smart, &manual);
}
#[test]
#[should_panic(expected = "嵌套深度不一致")]
fn test_assert_nested_depth_equal_mismatch() {
let smart = NestedEagerResult::Leaf(row(1, "a"));
let manual = NestedEagerResult::Node {
row: row(1, "a"),
children: vec![NestedEagerResult::Leaf(row(1, "b"))],
};
assert_nested_depth_equal(&smart, &manual);
}
#[test]
fn test_values_equivalent_cross_int_width() {
assert!(values_equivalent(&Value::I32(1), &Value::I64(1)));
assert!(values_equivalent(&Value::U8(1), &Value::I64(1)));
assert!(!values_equivalent(&Value::I32(1), &Value::I64(2)));
}
}