ruvector-dag 2.0.6

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

use ruvector_dag::*;

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

    // Create a simple query plan: SeqScan -> Filter -> Sort
    let scan = dag.add_node(OperatorNode::seq_scan(0, "users").with_estimates(1000.0, 100.0));
    let filter = dag.add_node(OperatorNode::filter(0, "age > 18").with_estimates(500.0, 50.0));
    let sort =
        dag.add_node(OperatorNode::sort(0, vec!["name".to_string()]).with_estimates(500.0, 150.0));

    dag.add_edge(scan, filter).unwrap();
    dag.add_edge(filter, sort).unwrap();

    // Build min-cut engine
    let mut engine = DagMinCutEngine::new(MinCutConfig::default());
    engine.build_from_dag(&dag);

    assert_eq!(dag.node_count(), 3);
}

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

    // Create a query plan with a potential bottleneck
    let scan = dag.add_node(
        OperatorNode::seq_scan(0, "users").with_estimates(10000.0, 1000.0), // High cost
    );
    let filter = dag.add_node(
        OperatorNode::filter(0, "active = true").with_estimates(5000.0, 10.0), // Low cost
    );

    dag.add_edge(scan, filter).unwrap();

    // Compute criticality
    let mut engine = DagMinCutEngine::new(MinCutConfig::default());
    engine.build_from_dag(&dag);
    let criticality = engine.compute_criticality(&dag);

    // Analyze bottlenecks
    let analysis = BottleneckAnalysis::analyze(&dag, &criticality);

    assert!(analysis.total_cost > 0.0);
    assert!(analysis.critical_path_cost > 0.0);
}

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

    let scan = dag
        .add_node(OperatorNode::hnsw_scan(0, "embeddings_idx", 100).with_estimates(1000.0, 200.0));

    // Create a high-criticality bottleneck
    let bottleneck = Bottleneck {
        node_id: scan,
        score: 0.8,
        impact_estimate: 160.0,
        suggested_action: "Test".to_string(),
    };

    let suggestions = RedundancySuggestion::generate(&dag, &[bottleneck]);

    assert_eq!(suggestions.len(), 1);
    assert!(matches!(
        suggestions[0].strategy,
        RedundancyStrategy::Prefetch
    ));
}

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

    // Create a simple chain
    let n0 = dag.add_node(OperatorNode::seq_scan(0, "t0").with_estimates(100.0, 10.0));
    let n1 = dag.add_node(OperatorNode::filter(0, "f1").with_estimates(50.0, 20.0));
    let n2 = dag.add_node(OperatorNode::sort(0, vec!["c1".to_string()]).with_estimates(50.0, 30.0));

    dag.add_edge(n0, n1).unwrap();
    dag.add_edge(n1, n2).unwrap();

    let mut engine = DagMinCutEngine::new(MinCutConfig {
        epsilon: 0.1,
        local_search_depth: 5,
        cache_cuts: true,
    });

    engine.build_from_dag(&dag);
    let result = engine.compute_mincut(n0, n2);

    // Should find some cut
    assert!(result.cut_value >= 0.0);
}

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

    let n0 = dag.add_node(OperatorNode::seq_scan(0, "t0").with_estimates(100.0, 10.0));
    let n1 = dag.add_node(OperatorNode::filter(0, "f1").with_estimates(50.0, 20.0));
    let n2 = dag.add_node(OperatorNode::sort(0, vec!["c1".to_string()]).with_estimates(50.0, 30.0));

    dag.add_edge(n0, n1).unwrap();
    dag.add_edge(n1, n2).unwrap();

    let mut engine = DagMinCutEngine::new(MinCutConfig::default());
    engine.build_from_dag(&dag);

    // Test dynamic update - O(n^0.12) amortized
    engine.update_edge(n0, n1, 15.0);

    // Cache should be invalidated
    let result = engine.compute_mincut(n0, n2);
    assert!(result.cut_value >= 0.0);
}

#[test]
fn test_mincut_config() {
    let config = MinCutConfig {
        epsilon: 0.05,
        local_search_depth: 10,
        cache_cuts: false,
    };

    assert_eq!(config.epsilon, 0.05);
    assert_eq!(config.local_search_depth, 10);
    assert!(!config.cache_cuts);
}