toasty 0.11.0

An async ORM for Rust supporting SQL and NoSQL databases
Documentation
use std::ops;

use index_vec::IndexVec;

use crate::engine::mir::{Node, NodeId, Store, annotate_guards};

/// The complete operation graph for a query.
///
/// [`LogicalPlan`] is a directed acyclic graph of operations produced by the
/// planning phase. It contains all nodes, their topologically sorted execution
/// order, and the final completion node whose output is returned to the user.
#[derive(Debug)]
pub(crate) struct LogicalPlan {
    /// All nodes in the operation graph.
    store: Store,

    /// Topologically sorted order in which to execute operations.
    execution_order: Vec<NodeId>,

    /// The final node whose output is the query result.
    completion: NodeId,
}

impl LogicalPlan {
    pub(crate) fn new(mut store: Store, completion: NodeId) -> LogicalPlan {
        let execution_order = compute_operation_execution_order(completion, &store);

        // Every reserved slot must be filled by the time planning completes —
        // an unfilled slot means a statement was referenced but never planned.
        debug_assert!(
            store.all_filled(),
            "reserved MIR slot left unfilled at plan completion"
        );

        // `num_uses` counts the variable loads each node's output receives:
        // one per entry in its consumers' `input_loads()`. Ordering-only
        // `deps` edges schedule but do not count — no load ever drains them.
        // The completion node's exit use is the engine's load of the query
        // result. (Guards are annotated after this loop and peek without
        // loading.)
        store[completion].num_uses += 1;

        for node_id in &execution_order {
            for load in store[node_id].op.input_loads() {
                store[load].num_uses += 1;
            }
        }

        annotate_guards(&mut store, &execution_order, completion);

        LogicalPlan {
            store,
            execution_order,
            completion,
        }
    }

    /// Node IDs in topologically sorted execution order.
    pub(crate) fn execution_order(&self) -> &[NodeId] {
        &self.execution_order
    }

    /// The final node whose output is the query result.
    pub(crate) fn completion(&self) -> NodeId {
        self.completion
    }

    /// Number of node slots; [`NodeId`]s are dense in `0..node_count()`.
    pub(crate) fn node_count(&self) -> usize {
        self.store.node_count()
    }
}

impl ops::Index<NodeId> for LogicalPlan {
    type Output = Node;

    fn index(&self, index: NodeId) -> &Self::Output {
        self.store.index(index)
    }
}

impl ops::Index<&NodeId> for LogicalPlan {
    type Output = Node;

    fn index(&self, index: &NodeId) -> &Self::Output {
        self.store.index(index)
    }
}

fn compute_operation_execution_order(node_id: NodeId, mir: &Store) -> Vec<NodeId> {
    fn visit_operation(
        node_id: NodeId,
        mir: &Store,
        visited: &mut IndexVec<NodeId, bool>,
        execution_order: &mut Vec<NodeId>,
    ) {
        if visited[node_id] {
            return;
        }

        visited[node_id] = true;

        for &dep_id in &mir[node_id].deps {
            visit_operation(dep_id, mir, visited, execution_order);
        }

        execution_order.push(node_id);
    }

    let mut visited = IndexVec::from_vec(vec![false; mir.node_count()]);
    let mut execution_order = vec![];

    visit_operation(node_id, mir, &mut visited, &mut execution_order);

    // Nodes unreachable from the completion node are dropped from the
    // execution order and never run. That is fine for pure nodes; a
    // dropped mutation would silently lose its database effect.
    debug_assert!(
        visited
            .iter_enumerated()
            .all(|(id, visited)| *visited || !mir[id].op.is_effectful()),
        "effectful node unreachable from the completion node"
    );

    execution_order
}