ruvector-dag 2.0.6

Directed Acyclic Graph (DAG) structures for query plan optimization with neural learning
Documentation
//! DAG integration tests

use ruvector_dag::dag::{OperatorNode, OperatorType, QueryDag};

#[test]
fn test_complex_query_dag() {
    // Build a realistic query DAG
    let mut dag = QueryDag::new();

    // Add scan nodes
    let scan1 = dag.add_node(OperatorNode::seq_scan(0, "users"));
    let scan2 = dag.add_node(OperatorNode::hnsw_scan(1, "vectors_idx", 64));

    // Add join
    let join = dag.add_node(OperatorNode::hash_join(2, "user_id"));
    dag.add_edge(scan1, join).unwrap();
    dag.add_edge(scan2, join).unwrap();

    // Add filter and result
    let filter = dag.add_node(OperatorNode::filter(3, "score > 0.5"));
    dag.add_edge(join, filter).unwrap();

    let result = dag.add_node(OperatorNode::new(4, OperatorType::Result));
    dag.add_edge(filter, result).unwrap();

    // Verify structure
    assert_eq!(dag.node_count(), 5);
    assert_eq!(dag.edge_count(), 4);

    // Verify topological order
    let order = dag.topological_sort().unwrap();
    assert_eq!(order.len(), 5);

    // Scans should come before join
    let scan1_pos = order.iter().position(|&x| x == scan1).unwrap();
    let scan2_pos = order.iter().position(|&x| x == scan2).unwrap();
    let join_pos = order.iter().position(|&x| x == join).unwrap();

    assert!(scan1_pos < join_pos);
    assert!(scan2_pos < join_pos);
}

#[test]
fn test_dag_depths() {
    let mut dag = QueryDag::new();

    // Create tree structure
    // Edges: 3→1, 4→1, 1→0, 2→0
    // Leaves (no outgoing edges): only node 0
    // Depth is computed FROM LEAVES, so node 0 = depth 0
    //
    //       0  (leaf, depth 0)
    //      / \
    //     1   2  (depth 1)
    //    / \
    //   3   4  (depth 2)

    for i in 0..5 {
        dag.add_node(OperatorNode::new(i, OperatorType::Result));
    }

    dag.add_edge(3, 1).unwrap();
    dag.add_edge(4, 1).unwrap();
    dag.add_edge(1, 0).unwrap();
    dag.add_edge(2, 0).unwrap();

    let depths = dag.compute_depths();

    // All nodes should have a depth
    assert!(depths.contains_key(&0));
    assert!(depths.contains_key(&1));
    assert!(depths.contains_key(&2));
    assert!(depths.contains_key(&3));
    assert!(depths.contains_key(&4));

    // Leaf node 0 (no outgoing edges) has depth 0
    assert_eq!(depths[&0], 0);

    // Nodes 1 and 2 are parents of leaf 0, so depth 1
    assert_eq!(depths[&1], 1);
    assert_eq!(depths[&2], 1);

    // Nodes 3 and 4 are parents of 1, so depth 2
    assert_eq!(depths[&3], 2);
    assert_eq!(depths[&4], 2);
}

#[test]
fn test_dag_cycle_detection() {
    let mut dag = QueryDag::new();

    for i in 0..3 {
        dag.add_node(OperatorNode::new(i, OperatorType::Result));
    }

    // Create valid edges
    dag.add_edge(0, 1).unwrap();
    dag.add_edge(1, 2).unwrap();

    // Attempt to create cycle should fail
    let result = dag.add_edge(2, 0);
    assert!(result.is_err());
}

#[test]
fn test_dag_node_removal() {
    let mut dag = QueryDag::new();

    for i in 0..5 {
        dag.add_node(OperatorNode::new(i, OperatorType::Result));
    }

    dag.add_edge(0, 1).unwrap();
    dag.add_edge(1, 2).unwrap();
    dag.add_edge(2, 3).unwrap();
    dag.add_edge(3, 4).unwrap();

    // Remove middle node
    dag.remove_node(2);

    assert_eq!(dag.node_count(), 4);
    // Verify DAG is still valid after removal
    let topo = dag.topological_sort();
    assert!(topo.is_ok());
}

#[test]
fn test_dag_clone() {
    let mut dag = QueryDag::new();

    for i in 0..5 {
        dag.add_node(OperatorNode::new(i, OperatorType::Result));
    }

    for i in 0..4 {
        dag.add_edge(i, i + 1).unwrap();
    }

    let cloned = dag.clone();

    assert_eq!(dag.node_count(), cloned.node_count());
    assert_eq!(dag.edge_count(), cloned.edge_count());
}

#[test]
fn test_dag_topological_order() {
    let mut dag = QueryDag::new();

    // Create diamond pattern
    //    0
    //   / \
    //  1   2
    //   \ /
    //    3

    for i in 0..4 {
        dag.add_node(OperatorNode::new(i, OperatorType::Result));
    }

    dag.add_edge(0, 1).unwrap();
    dag.add_edge(0, 2).unwrap();
    dag.add_edge(1, 3).unwrap();
    dag.add_edge(2, 3).unwrap();

    let order = dag.topological_sort().unwrap();

    // Node 0 must come first
    assert_eq!(order[0], 0);

    // Node 3 must come last
    assert_eq!(order[3], 3);

    // Nodes 1 and 2 must be in the middle
    assert!(order.contains(&1));
    assert!(order.contains(&2));
}

#[test]
fn test_dag_parents_children() {
    let mut dag = QueryDag::new();

    for i in 0..4 {
        dag.add_node(OperatorNode::new(i, OperatorType::Result));
    }

    // 0 -> 1 -> 3
    //      2 ->
    dag.add_edge(0, 1).unwrap();
    dag.add_edge(1, 3).unwrap();
    dag.add_edge(2, 3).unwrap();

    // Parents of node 3
    let preds = dag.parents(3);
    assert_eq!(preds.len(), 2);
    assert!(preds.contains(&1));
    assert!(preds.contains(&2));

    // Children of node 0
    let succs = dag.children(0);
    assert_eq!(succs.len(), 1);
    assert!(succs.contains(&1));
}

#[test]
fn test_dag_leaves() {
    let mut dag = QueryDag::new();

    for i in 0..5 {
        dag.add_node(OperatorNode::new(i, OperatorType::Result));
    }

    // 0 -> 2, 1 -> 2, 2 -> 3, 2 -> 4
    dag.add_edge(0, 2).unwrap();
    dag.add_edge(1, 2).unwrap();
    dag.add_edge(2, 3).unwrap();
    dag.add_edge(2, 4).unwrap();

    // Get leaves using the API
    let leaves = dag.leaves();
    assert_eq!(leaves.len(), 2);
    assert!(leaves.contains(&3));
    assert!(leaves.contains(&4));
}

#[test]
fn test_dag_empty() {
    let dag = QueryDag::new();

    assert_eq!(dag.node_count(), 0);
    assert_eq!(dag.edge_count(), 0);

    let order = dag.topological_sort().unwrap();
    assert!(order.is_empty());
}

#[test]
fn test_dag_single_node() {
    let mut dag = QueryDag::new();
    dag.add_node(OperatorNode::new(0, OperatorType::Result));

    assert_eq!(dag.node_count(), 1);
    assert_eq!(dag.edge_count(), 0);

    let order = dag.topological_sort().unwrap();
    assert_eq!(order.len(), 1);
    assert_eq!(order[0], 0);
}