1use firewood::api::{self, BoxKeyValueIter, DbView, HashKey, IntoBatchIter, Proposal as _};
5
6use crate::{IteratorHandle, iterator::CreateIteratorResult, metrics::MetricsContextExt};
7use firewood_metrics::{firewood_increment, firewood_record, fwd_expensive_timed_result};
8
9#[derive(Debug)]
12pub struct ProposalHandle<'db> {
13 hash_key: Option<HashKey>,
14 proposal: firewood::db::Proposal<'db>,
15 handle: &'db crate::DatabaseHandle,
16}
17
18impl<'db> DbView for ProposalHandle<'db> {
19 type Iter<'view>
20 = <firewood::db::Proposal<'db> as DbView>::Iter<'view>
21 where
22 Self: 'view;
23
24 fn root_hash(&self) -> Option<HashKey> {
25 self.proposal.root_hash()
26 }
27
28 fn val<K: api::KeyType>(&self, key: K) -> Result<Option<firewood::merkle::Value>, api::Error> {
29 self.proposal.val(key)
30 }
31
32 fn single_key_proof<K: api::KeyType>(&self, key: K) -> Result<api::FrozenProof, api::Error> {
33 self.proposal.single_key_proof(key)
34 }
35
36 fn range_proof<K: api::KeyType>(
37 &self,
38 first_key: Option<K>,
39 last_key: Option<K>,
40 limit: Option<std::num::NonZeroUsize>,
41 ) -> Result<api::FrozenRangeProof, api::Error> {
42 self.proposal.range_proof(first_key, last_key, limit)
43 }
44
45 fn iter_option<K: api::KeyType>(
46 &self,
47 first_key: Option<K>,
48 ) -> Result<Self::Iter<'_>, api::Error> {
49 self.proposal.iter_option(first_key)
50 }
51
52 fn dump_to_string(&self) -> Result<String, api::Error> {
53 self.proposal.dump_to_string()
54 }
55}
56
57impl ProposalHandle<'_> {
58 #[must_use]
60 pub fn hash_key(&self) -> Option<crate::HashKey> {
61 self.hash_key.clone().map(Into::into)
62 }
63
64 pub fn commit_proposal(self) -> Result<Option<HashKey>, api::Error> {
71 let ProposalHandle {
72 hash_key,
73 proposal,
74 handle,
75 } = self;
76
77 if let Some(ref hash_key) = hash_key {
80 _ = handle.get_root(hash_key.clone());
81 }
82
83 let (commit_result, commit_time) =
84 fwd_expensive_timed_result!(crate::registry::COMMIT_MS_BUCKET, proposal.commit());
85 commit_result?;
86
87 handle.clear_cached_view();
89
90 firewood_increment!(crate::registry::COMMIT_MS, commit_time.as_millis() as u64);
91 firewood_increment!(crate::registry::COMMIT_COUNT, 1);
92
93 Ok(hash_key)
94 }
95
96 #[must_use]
98 #[allow(clippy::missing_panics_doc)]
99 pub fn iter_from(&self, first_key: Option<&[u8]>) -> CreateIteratorResult<'_> {
100 let it = self
101 .iter_option(first_key)
102 .expect("infallible; see issue #1329");
103 CreateIteratorResult(IteratorHandle::new(
104 self.proposal.view(),
105 Box::new(it) as BoxKeyValueIter<'_>,
106 self.handle.metrics_context(),
107 ))
108 }
109}
110
111#[derive(Debug)]
112pub struct CreateProposalResult<'db> {
113 pub handle: ProposalHandle<'db>,
114}
115
116impl<'db> CreateProposalResult<'db> {
117 pub(crate) fn new(
118 handle: &'db crate::DatabaseHandle,
119 f: impl FnOnce() -> Result<firewood::db::Proposal<'db>, api::Error>,
120 ) -> Result<Self, api::Error> {
121 let (proposal_result, propose_time) =
122 fwd_expensive_timed_result!(crate::registry::PROPOSE_MS_BUCKET, f());
123 let proposal = proposal_result?;
124 firewood_increment!(crate::registry::PROPOSE_MS, propose_time.as_millis() as u64);
125 firewood_increment!(crate::registry::PROPOSE_COUNT, 1);
126 firewood_record!(
127 crate::registry::PROPOSE_MS_BUCKET,
128 propose_time.as_secs_f64() * 1000.0,
129 expensive
130 );
131
132 let hash_key = proposal.root_hash();
133
134 Ok(CreateProposalResult {
135 handle: ProposalHandle {
136 hash_key,
137 proposal,
138 handle,
139 },
140 })
141 }
142}
143
144pub trait CView<'db> {
157 fn handle(&self) -> &'db crate::DatabaseHandle;
162
163 fn create_proposal(
170 self,
171 values: impl IntoBatchIter,
172 ) -> Result<firewood::db::Proposal<'db>, api::Error>;
173
174 fn create_proposal_handle(
182 self,
183 values: impl IntoBatchIter,
184 ) -> Result<CreateProposalResult<'db>, api::Error>
185 where
186 Self: Sized,
187 {
188 let handle = self.handle();
189 CreateProposalResult::new(handle, || self.create_proposal(values))
190 }
191}
192
193impl<'db> CView<'db> for &ProposalHandle<'db> {
194 fn handle(&self) -> &'db crate::DatabaseHandle {
195 self.handle
196 }
197
198 fn create_proposal(
199 self,
200 values: impl IntoBatchIter,
201 ) -> Result<firewood::db::Proposal<'db>, api::Error> {
202 self.proposal.propose(values)
203 }
204}
205
206impl crate::MetricsContextExt for ProposalHandle<'_> {
207 fn metrics_context(&self) -> Option<firewood_metrics::MetricsContext> {
208 self.handle.metrics_context()
209 }
210}