Skip to main content

reifydb_transaction/multi/transaction/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4// This file includes and modifies code from the skipdb project (https://github.com/al8n/skipdb),
5// originally licensed under the Apache License, Version 2.0.
6// Original copyright:
7//   Copyright (c) 2024 Al Liu
8//
9// The original Apache License can be found at:
10//   http://www.apache.org/licenses/LICENSE-2.0
11
12use 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}