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(¤t_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 // If `dependency` is an input it must be remembered both as a dependency
68 // and as an input dependency. Otherwise we cannot differentiate between
69 // computations which directly depend on inputs and those that only indirectly
70 // depend on them.
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 let dependency = self.db.cells.get(&dependency).unwrap();
82 let dependency_inputs = dependency.input_dependencies.clone();
83 drop(dependency);
84
85 // Is this check necessary? It is meant as an optimization to avoid unnecessarily acquiring
86 // `cell` but in practice the vast majority of computations will have at least 1 input dependency.
87 if !dependency_inputs.is_empty() {
88 let mut cell = self.db.cells.get_mut(&self.current_operation).unwrap();
89 for input in dependency_inputs {
90 cell.input_dependencies.insert(input);
91 }
92 }
93 }
94
95 /// Accumulate an item in the current computation. This item can be retrieved along
96 /// with all other accumulated items in this computation and its dependencies via
97 /// a call to `get_accumulated`.
98 ///
99 /// This is most often used for operations like pushing diagnostics or logs.
100 pub fn accumulate<Item>(&self, item: Item)
101 where
102 S: Accumulate<Item>,
103 {
104 self.storage().accumulate(self.current_operation, item);
105 }
106
107 /// Retrieve an accumulated value in a container of the user's choice.
108 /// This will return all the accumulated items after the given computation.
109 ///
110 /// This is most often used for operations like retrieving diagnostics or logs.
111 pub fn get_accumulated<Item, C>(&self, compute: C) -> BTreeSet<Item>
112 where
113 C: Computation,
114 Item: 'static + Ord,
115 S: StorageFor<Accumulated<Item>> + StorageFor<C> + Accumulate<Item>,
116 {
117 let dependency = self.db.get_or_insert_cell(compute);
118 self.get_accumulated_with_cell::<Item>(dependency)
119 }
120
121 /// Retrieve an accumulated value in a container of the user's choice.
122 /// This will return all the accumulated items after the given computation.
123 ///
124 /// This is the implementation of the publically accessible `db.get(Accumulated::<Item>(MyComputation))`.
125 ///
126 /// This is most often used for operations like retrieving diagnostics or logs.
127 pub(crate) fn get_accumulated_with_cell<Item>(&self, cell_id: Cell) -> BTreeSet<Item>
128 where
129 Item: 'static + Ord,
130 S: StorageFor<Accumulated<Item>> + Accumulate<Item>,
131 {
132 self.update_and_register_dependency_inner(cell_id, false);
133 let dependencies = self.db.with_cell(cell_id, |cell| cell.dependencies.clone());
134
135 // Collect `Accumulator` results from each dependency. This should also ensure we
136 // rerun this if any dependency changes, even if `cell_id` is updated such that it
137 // uses different dependencies but its output remains the same.
138 let computation_id = Accumulated::<Item>::computation_id();
139 let mut result: BTreeSet<Item> = dependencies
140 .into_iter()
141 // Filter out `Accumulated<Item>` cells from the dep list — they exist for staleness
142 // tracking only and must not be traversed for value collection, or we'd get duplicates.
143 .filter(|&dep| self.db.with_cell(dep, |cell| cell.computation_id) != computation_id)
144 .flat_map(|dependency| self.get(Accumulated::<Item>::new(dependency)))
145 .collect();
146
147 result.extend(self.storage().get_accumulated::<Vec<Item>>(cell_id));
148 result
149 }
150}
151
152impl<'db, S, C> DbGet<C> for DbHandle<'db, S>
153where
154 C: Computation,
155 S: Storage + StorageFor<C>,
156{
157 fn get(&self, key: C) -> C::Output {
158 self.get(key)
159 }
160}