1use std::{ops::Deref, sync::Arc};
13
14use reifydb_core::{
15 common::CommitVersion,
16 event::EventBus,
17 interface::{
18 catalog::config::GetConfig,
19 store::{MultiVersionCommit, MultiVersionContains, MultiVersionGet},
20 },
21 key::any::TaggedKey,
22 testing::ProfileConfig,
23};
24use reifydb_runtime::{
25 actor::system::{ActorSpawner, ActorSystem},
26 context::{
27 clock::{Clock, MockClock},
28 rng::Rng,
29 },
30 version_epoch::VersionEpoch,
31};
32use reifydb_store_multi::MultiStore;
33use reifydb_value::{Result, value::duration::Duration};
34use tracing::{instrument, warn};
35use version::{StandardVersionProvider, VersionProvider};
36
37pub(crate) use crate::multi::oracle::Oracle;
38use crate::{TransactionId, error::TransactionError, multi::types::*, single::SingleTransaction};
39
40pub mod manager;
41pub mod read;
42pub(crate) mod version;
43pub mod write;
44
45use reifydb_store_single::SingleStore;
46
47use crate::multi::{
48 MultiReadTransaction, MultiWriteTransaction,
49 lease::{VersionLeaseGuard, VersionLeases},
50 transaction::manager::TransactionManagerQuery,
51};
52
53pub struct TransactionManager<L>
54where
55 L: VersionProvider,
56{
57 inner: Arc<Oracle<L>>,
58}
59
60impl<L> Clone for TransactionManager<L>
61where
62 L: VersionProvider,
63{
64 fn clone(&self) -> Self {
65 Self {
66 inner: self.inner.clone(),
67 }
68 }
69}
70
71impl<L> TransactionManager<L>
72where
73 L: VersionProvider,
74{
75 #[allow(clippy::too_many_arguments)]
76 #[instrument(
77 name = "transaction::manager::new",
78 level = "debug",
79 skip(clock, spawner, store, metrics_clock, version_epoch, rng, config)
80 )]
81 pub fn new(
82 clock: L,
83 spawner: ActorSpawner,
84 store: Arc<dyn MultiVersionCommit>,
85 metrics_clock: Clock,
86 version_epoch: VersionEpoch,
87 rng: Rng,
88 config: Arc<dyn GetConfig>,
89 ) -> Result<Self>
90 where
91 L: 'static,
92 {
93 let version = clock.next()?;
94 let oracle = Oracle::new(clock, spawner, store, metrics_clock, version_epoch, rng, config);
95 oracle.query.advance_to(version);
96 oracle.command.advance_to(version);
97 Ok(Self {
98 inner: Arc::new(oracle),
99 })
100 }
101
102 pub fn spawner(&self) -> ActorSpawner {
103 self.inner.spawner()
104 }
105
106 pub fn config(&self) -> Arc<dyn GetConfig> {
107 self.inner.config()
108 }
109
110 pub(crate) fn oracle(&self) -> &Arc<Oracle<L>> {
111 &self.inner
112 }
113
114 pub fn bootstrapping_completed(&self) {
115 self.inner.bootstrapping_completed();
116 }
117
118 #[instrument(name = "transaction::manager::version", level = "trace", skip(self))]
119 pub fn version(&self) -> Result<CommitVersion> {
120 self.inner.version()
121 }
122}
123
124impl<L> TransactionManager<L>
125where
126 L: VersionProvider,
127{
128 #[instrument(name = "transaction::manager::query", level = "debug", skip(self), fields(as_of_version = ?version))]
129 pub fn query(&self, version: Option<CommitVersion>) -> Result<TransactionManagerQuery<L>> {
130 Ok(if let Some(version) = version {
131 let safe_version = self.inner.version()?;
132 if version > safe_version {
133 return Err(TransactionError::SnapshotVersionEvicted {
134 version,
135 cutoff: safe_version,
136 }
137 .into());
138 }
139 TransactionManagerQuery::new_time_travel(
140 TransactionId::generate(self.inner.metrics_clock(), self.inner.rng()),
141 self.clone(),
142 version,
143 )
144 } else {
145 let safe_version = self.inner.query.register_in_flight_with(|| self.inner.version())?;
146 TransactionManagerQuery::new_current(
147 TransactionId::generate(self.inner.metrics_clock(), self.inner.rng()),
148 self.clone(),
149 safe_version,
150 )
151 })
152 }
153
154 pub fn begin_commit(&self, version: CommitVersion) {
155 self.inner.command.register_in_flight(version);
156 }
157
158 pub fn done_commit(&self, version: CommitVersion) {
159 self.inner.done_commit(version);
160 }
161
162 #[instrument(name = "transaction::manager::done_until", level = "trace", skip(self))]
163 pub fn done_until(&self) -> CommitVersion {
164 self.inner.command.done_until()
165 }
166
167 #[instrument(name = "transaction::manager::query_done_until", level = "trace", skip(self))]
168 pub fn query_done_until(&self) -> CommitVersion {
169 self.inner.query.done_until()
170 }
171
172 #[instrument(name = "transaction::manager::wait_for_mark_timeout", level = "trace", skip(self))]
173 pub fn wait_for_mark_timeout(&self, version: CommitVersion, timeout: Duration) -> bool {
174 self.inner.command.wait_for_mark_timeout(version, timeout)
175 }
176
177 pub fn notify_on_mark(&self, version: CommitVersion, callback: Box<dyn FnOnce() + Send>) {
178 self.inner.command.notify_on_mark(version, callback);
179 }
180
181 pub fn advance_version_to(&self, version: CommitVersion) {
182 self.inner.clock.advance_to(version);
183 self.inner.command.advance_to(version);
184 self.inner.query.advance_to(version);
185 }
186
187 pub fn leases(&self) -> Arc<VersionLeases> {
188 self.inner.leases.clone()
189 }
190
191 pub fn acquire_version_lease(&self, version: CommitVersion) -> Result<VersionLeaseGuard> {
192 self.inner.leases.try_acquire(version, self.inner.query.done_until())
193 }
194
195 pub fn acquire_current_snapshot_lease(&self) -> Result<(CommitVersion, VersionLeaseGuard)> {
196 let oracle = self.inner.clone();
197 let (guard, version) = oracle.leases.try_acquire_with(|| {
198 let version = oracle.version()?;
199 let qdu = oracle.query.done_until();
200 Ok((version, qdu, version))
201 })?;
202 Ok((version, guard))
203 }
204}
205
206pub struct MultiTransaction(Arc<Inner>);
207
208pub struct Inner {
209 pub(crate) tm: TransactionManager<StandardVersionProvider>,
210 pub(crate) store: MultiStore,
211 pub(crate) event_bus: EventBus,
212}
213
214impl Deref for MultiTransaction {
215 type Target = Inner;
216
217 fn deref(&self) -> &Self::Target {
218 &self.0
219 }
220}
221
222impl Clone for MultiTransaction {
223 fn clone(&self) -> Self {
224 Self(self.0.clone())
225 }
226}
227
228impl Inner {
229 #[allow(clippy::too_many_arguments)]
230 fn new(
231 store: MultiStore,
232 single: SingleTransaction,
233 event_bus: EventBus,
234 spawner: ActorSpawner,
235 metrics_clock: Clock,
236 version_epoch: VersionEpoch,
237 rng: Rng,
238 config: Arc<dyn GetConfig>,
239 ) -> Result<Self> {
240 let version_provider = StandardVersionProvider::new(single)?;
241 let tm = TransactionManager::new(
242 version_provider,
243 spawner,
244 Arc::new(store.clone()),
245 metrics_clock,
246 version_epoch,
247 rng,
248 config,
249 )?;
250
251 Ok(Self {
252 tm,
253 store,
254 event_bus,
255 })
256 }
257
258 fn version(&self) -> Result<CommitVersion> {
259 self.tm.version()
260 }
261
262 fn spawner(&self) -> ActorSpawner {
263 self.tm.spawner()
264 }
265
266 fn bootstrapping_completed(&self) {
267 self.tm.bootstrapping_completed();
268 }
269}
270
271impl MultiTransaction {
272 pub fn oracle_window_count(&self) -> usize {
273 self.tm.oracle().window_count()
274 }
275
276 pub fn testing() -> Self {
277 let multi_store = MultiStore::testing_memory();
278 let single_store = SingleStore::testing_memory();
279 let actor_system = ActorSystem::testing(Clock::Real);
280 let spawner = actor_system.spawner();
281 let event_bus = EventBus::new(&spawner);
282
283 let config = Arc::new(ProfileConfig);
284
285 Self::new(
286 multi_store,
287 SingleTransaction::new(single_store, event_bus.clone()),
288 event_bus,
289 spawner,
290 Clock::Mock(MockClock::from_millis(1000)),
291 VersionEpoch::new(),
292 Rng::seeded(42),
293 config,
294 )
295 .expect("failed to create testing MultiTransaction")
296 }
297}
298
299impl MultiTransaction {
300 #[instrument(
301 name = "transaction::new",
302 level = "debug",
303 skip(store, single, event_bus, spawner, metrics_clock, version_epoch, rng, config)
304 )]
305 #[allow(clippy::too_many_arguments)]
306 pub fn new(
307 store: MultiStore,
308 single: SingleTransaction,
309 event_bus: EventBus,
310 spawner: ActorSpawner,
311 metrics_clock: Clock,
312 version_epoch: VersionEpoch,
313 rng: Rng,
314 config: Arc<dyn GetConfig>,
315 ) -> Result<Self> {
316 Ok(Self(Arc::new(Inner::new(
317 store,
318 single,
319 event_bus,
320 spawner,
321 metrics_clock,
322 version_epoch,
323 rng,
324 config,
325 )?)))
326 }
327
328 pub fn spawner(&self) -> ActorSpawner {
329 self.0.spawner()
330 }
331
332 pub fn config(&self) -> Arc<dyn GetConfig> {
333 self.0.tm.config()
334 }
335
336 pub fn advance_version_to(&self, version: CommitVersion) {
337 self.0.tm.advance_version_to(version);
338 }
339
340 pub fn bootstrapping_completed(&self) {
341 self.0.bootstrapping_completed();
342 }
343
344 #[instrument(name = "transaction::query_done_until", level = "trace", skip(self))]
345 pub fn query_done_until(&self) -> CommitVersion {
346 self.0.tm.query_done_until()
347 }
348}
349
350impl MultiTransaction {
351 #[instrument(name = "transaction::version", level = "trace", skip(self))]
352 pub fn version(&self) -> Result<CommitVersion> {
353 self.0.version()
354 }
355
356 #[instrument(name = "transaction::begin_query", level = "trace", skip(self))]
357 pub fn begin_query(&self) -> Result<MultiReadTransaction> {
358 MultiReadTransaction::new(self.clone(), None)
359 }
360
361 #[instrument(name = "transaction::begin_query_at_version", level = "trace", skip(self, lease), fields(version = %lease.version().0))]
362 pub fn begin_query_at_version(&self, lease: &VersionLeaseGuard) -> Result<MultiReadTransaction> {
363 MultiReadTransaction::new_with_lease(self.clone(), lease.clone())
364 }
365
366 pub fn acquire_version_lease(&self, version: CommitVersion) -> Result<VersionLeaseGuard> {
367 self.0.tm.acquire_version_lease(version)
368 }
369
370 pub fn acquire_current_snapshot_lease(&self) -> Result<(CommitVersion, VersionLeaseGuard)> {
371 self.0.tm.acquire_current_snapshot_lease()
372 }
373
374 pub fn leases(&self) -> Arc<VersionLeases> {
375 self.0.tm.leases()
376 }
377}
378
379impl MultiTransaction {
380 #[instrument(name = "transaction::begin_command", level = "debug", skip(self))]
381 pub fn begin_command(&self) -> Result<MultiWriteTransaction> {
382 MultiWriteTransaction::new(self.clone())
383 }
384}
385
386pub enum TransactionType {
387 Query(MultiReadTransaction),
388 Command(Box<MultiWriteTransaction>),
389}
390
391impl MultiTransaction {
392 #[instrument(name = "transaction::get", level = "trace", skip(self, key), fields(version = version.0))]
393 pub fn get(&self, key: &TaggedKey, version: CommitVersion) -> Result<Option<Committed>> {
394 Ok(MultiVersionGet::get(&self.store, key, version)?.map(|sv| sv.into()))
395 }
396
397 #[instrument(name = "transaction::contains_key", level = "trace", skip(self, key), fields(version = version.0))]
398 pub fn contains_key(&self, key: &TaggedKey, version: CommitVersion) -> Result<bool> {
399 MultiVersionContains::contains(&self.store, key, version)
400 }
401
402 pub fn store(&self) -> &MultiStore {
403 &self.store
404 }
405}