Skip to main content

inc_complete/db/
handle.rs

1use std::collections::BTreeSet;
2
3use crate::{
4    Cell, Computation, Db, Storage,
5    accumulate::{Accumulate, Accumulated},
6    storage::StorageFor,
7};
8
9use super::DbGet;
10
11/// A handle to the database during some operation.
12///
13/// This wraps calls to the Db so that any `get` calls
14/// will be automatically registered as dependencies of
15/// the current operation.
16pub struct DbHandle<'db, S> {
17    db: &'db Db<S>,
18    current_operation: Cell,
19}
20
21impl<'db, S> DbHandle<'db, S> {
22    pub(crate) fn new(db: &'db Db<S>, current_operation: Cell) -> Self {
23        // We're re-running a cell so remove any past dependencies
24        let mut cell = db.cells.get_mut(&current_operation).unwrap();
25
26        cell.dependencies.clear();
27        cell.input_dependencies.clear();
28
29        Self {
30            db,
31            current_operation,
32        }
33    }
34
35    /// Retrieve an immutable reference to this `Db`'s storage
36    ///
37    /// Note that any mutations made to the storage using this are _not_ tracked by the database!
38    /// Using this incorrectly may break correctness!
39    pub fn storage(&self) -> &S {
40        self.db.storage()
41    }
42}
43
44impl<S: Storage> DbHandle<'_, S> {
45    /// Locking behavior: This function locks the cell corresponding to the given computation. This
46    /// can cause a deadlock if the computation recursively depends on itself.
47    pub fn get<C: Computation>(&self, compute: C) -> C::Output
48    where
49        S: StorageFor<C>,
50    {
51        // Register the dependency
52        let dependency = self.db.get_or_insert_cell(compute);
53        self.update_and_register_dependency::<C>(dependency);
54
55        // Fetch the current value of the dependency, running it if out of date
56        self.db.get_with_cell(dependency)
57    }
58
59    /// Registers the given cell as a dependency, running it and updating any required metadata
60    fn update_and_register_dependency<C: Computation>(&self, dependency: Cell) {
61        self.update_and_register_dependency_inner(dependency, C::IS_INPUT);
62    }
63
64    fn update_and_register_dependency_inner(&self, dependency: Cell, is_input: bool) {
65        let mut cell = self.db.cells.get_mut(&self.current_operation).unwrap();
66
67        // TODO: Investigate whether this is faster than a separate hashset or insertion-ordered
68        // set in practice.
69        let newly_registered = !cell.dependencies.contains(&dependency);
70        if newly_registered {
71            cell.dependencies.push(dependency);
72            if is_input {
73                cell.input_dependencies.insert(dependency);
74            }
75        }
76        drop(cell);
77
78        // Run the computation to update its dependencies before we query them afterward
79        self.db.update_cell(dependency);
80
81        if !newly_registered {
82            return;
83        }
84
85        let dependency = self.db.cells.get(&dependency).unwrap();
86        let dependency_inputs = dependency.input_dependencies.clone();
87        drop(dependency);
88
89        // TODO: Is this check necessary? It is meant as an optimization to avoid unnecessarily acquiring
90        // `cell` but in practice the vast majority of computations will have at least 1 input dependency.
91        if !dependency_inputs.is_empty() {
92            let mut cell = self.db.cells.get_mut(&self.current_operation).unwrap();
93            for input in dependency_inputs {
94                cell.input_dependencies.insert(input);
95            }
96        }
97    }
98
99    /// Accumulate an item in the current computation. This item can be retrieved along
100    /// with all other accumulated items in this computation and its dependencies via
101    /// a call to `get_accumulated`.
102    ///
103    /// This is most often used for operations like pushing diagnostics or logs.
104    pub fn accumulate<Item>(&self, item: Item)
105    where
106        S: Accumulate<Item>,
107    {
108        self.storage().accumulate(self.current_operation, item);
109    }
110
111    /// Retrieve an accumulated value in a container of the user's choice.
112    /// This will return all the accumulated items after the given computation.
113    ///
114    /// This is most often used for operations like retrieving diagnostics or logs.
115    pub fn get_accumulated<Item, C>(&self, compute: C) -> BTreeSet<Item>
116    where
117        C: Computation,
118        Item: 'static + Ord,
119        S: StorageFor<Accumulated<Item>> + StorageFor<C> + Accumulate<Item>,
120    {
121        let dependency = self.db.get_or_insert_cell(compute);
122        self.get_accumulated_with_cell::<Item>(dependency)
123    }
124
125    /// Retrieve an accumulated value in a container of the user's choice.
126    /// This will return all the accumulated items after the given computation.
127    ///
128    /// This is the implementation of the publically accessible `db.get(Accumulated::<Item>(MyComputation))`.
129    ///
130    /// This is most often used for operations like retrieving diagnostics or logs.
131    pub(crate) fn get_accumulated_with_cell<Item>(&self, cell_id: Cell) -> BTreeSet<Item>
132    where
133        Item: 'static + Ord,
134        S: StorageFor<Accumulated<Item>> + Accumulate<Item>,
135    {
136        self.update_and_register_dependency_inner(cell_id, false);
137        let dependencies = self.db.with_cell(cell_id, |cell| cell.dependencies.clone());
138
139        // Collect `Accumulator` results from each dependency. This should also ensure we
140        // rerun this if any dependency changes, even if `cell_id` is updated such that it
141        // uses different dependencies but its output remains the same.
142        let computation_id = Accumulated::<Item>::computation_id();
143        let mut result: BTreeSet<Item> = dependencies
144            .into_iter()
145            // Filter out `Accumulated<Item>` cells from the dep list — they exist for staleness
146            // tracking only and must not be traversed for value collection, or we'd get duplicates.
147            .filter(|&dep| self.db.with_cell(dep, |cell| cell.computation_id) != computation_id)
148            .flat_map(|dependency| self.get(Accumulated::<Item>::new(dependency)))
149            .collect();
150
151        result.extend(self.storage().get_accumulated::<Vec<Item>>(cell_id));
152        result
153    }
154}
155
156impl<'db, S, C> DbGet<C> for DbHandle<'db, S>
157where
158    C: Computation,
159    S: Storage + StorageFor<C>,
160{
161    fn get(&self, key: C) -> C::Output {
162        self.get(key)
163    }
164}