Skip to main content

reifydb_engine/
engine.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	ops::Deref,
6	sync::{
7		Arc,
8		atomic::{AtomicBool, Ordering},
9	},
10};
11
12use reifydb_auth::service::AuthEngine;
13use reifydb_catalog::{
14	catalog::Catalog,
15	interceptor::CatalogCacheInterceptor,
16	metrics::storage::metrics::MetricsReader,
17	vtable::{
18		system::operator_libary::{OperatorLibrary, OperatorLibraryEventListener},
19		tables::UserVTableDataFunction,
20		user::{UserVTable, UserVTableColumn, registry::UserVTableEntry},
21	},
22};
23use reifydb_cdc::{
24	consume::{host::CdcHost, wake::CdcWakeRegistry, watermark::CdcConsumerWatermark},
25	produce::watermark::CdcProducerWatermark,
26	storage::CdcStore,
27};
28use reifydb_core::{
29	common::CommitVersion,
30	error::diagnostic::engine::read_only_rejection,
31	event::{Event, EventBus},
32	execution::ExecutionResult,
33	interface::{
34		WithEventBus,
35		catalog::{
36			column::{Column, ColumnIndex},
37			id::{ColumnId, NamespaceId},
38			vtable::{VTable, VTableId},
39		},
40	},
41	internal,
42	lifecycle::watermark::CheckpointFloor,
43	metrics::sample::MetricKind,
44	util::ioc::IocContainer,
45};
46use reifydb_runtime::{
47	actor::{mailbox::ActorRef, system::ActorSpawner},
48	context::{clock::Clock, rng::Rng},
49	shutdown::Shutdown,
50	version_epoch::VersionEpoch,
51};
52use reifydb_store_operator::store::OperatorStore;
53use reifydb_store_single::SingleStore;
54use reifydb_transaction::{
55	dictionary::{DictionaryAllocatorRegistry, store::SingleDictionaryStore},
56	error::TransactionError,
57	interceptor::{factory::InterceptorFactory, interceptors::Interceptors},
58	multi::{lease::VersionLeaseGuard, transaction::MultiTransaction},
59	single::SingleTransaction,
60	transaction::{admin::AdminTransaction, command::CommandTransaction, query::QueryTransaction},
61};
62use reifydb_value::{
63	error,
64	error::Error,
65	fragment::Fragment,
66	params::Params,
67	reifydb_assertions,
68	value::{constraint::TypeConstraint, duration::Duration, identity::IdentityId},
69};
70use tracing::instrument;
71
72use crate::{
73	Result,
74	bulk_insert::builder::{BulkInsertBuilder, Unchecked, Validated},
75	vm::{
76		Admin, Command, Query, Subscription,
77		executor::Executor,
78		flow_lineage::ViewLineage,
79		services::{EngineConfig, Services},
80	},
81};
82
83pub struct StandardEngine(Arc<Inner>);
84
85impl WithEventBus for StandardEngine {
86	fn event_bus(&self) -> &EventBus {
87		&self.event_bus
88	}
89}
90
91impl AuthEngine for StandardEngine {
92	fn begin_admin(&self) -> Result<AdminTransaction> {
93		StandardEngine::begin_admin(self, IdentityId::system())
94	}
95
96	fn begin_query(&self) -> Result<QueryTransaction> {
97		StandardEngine::begin_query(self, IdentityId::system())
98	}
99
100	fn catalog(&self) -> Catalog {
101		StandardEngine::catalog(self)
102	}
103}
104
105impl StandardEngine {
106	#[instrument(name = "engine::transaction::begin_command", level = "debug", skip(self))]
107	pub fn begin_command(&self, identity: IdentityId) -> Result<CommandTransaction> {
108		reifydb_assertions! {
109			assert!(
110				!self.is_read_only(),
111				"begin_command called on a read-only engine: writes are permanently disabled after set_read_only(), so any caller reaching this point has bypassed the reject_if_read_only guard (identity={:?})",
112				identity
113			);
114		}
115		let interceptors = self.interceptors.create();
116		let mut txn = CommandTransaction::new(
117			self.multi.clone(),
118			self.single.clone(),
119			self.event_bus.clone(),
120			interceptors,
121			identity,
122			self.executor.runtime_context.clock.clone(),
123		)?;
124		txn.set_executor(Arc::new(self.executor.clone()));
125		txn.set_dictionary_allocators(self.dictionary_allocators.clone());
126		Ok(txn)
127	}
128
129	#[instrument(name = "engine::transaction::begin_admin", level = "debug", skip(self))]
130	pub fn begin_admin(&self, identity: IdentityId) -> Result<AdminTransaction> {
131		let interceptors = self.interceptors.create();
132		let mut txn = AdminTransaction::new(
133			self.multi.clone(),
134			self.single.clone(),
135			self.event_bus.clone(),
136			interceptors,
137			identity,
138			self.executor.runtime_context.clock.clone(),
139		)?;
140		txn.set_executor(Arc::new(self.executor.clone()));
141		txn.set_dictionary_allocators(self.dictionary_allocators.clone());
142		Ok(txn)
143	}
144
145	#[instrument(name = "engine::transaction::begin_query", level = "trace", skip(self))]
146	pub fn begin_query(&self, identity: IdentityId) -> Result<QueryTransaction> {
147		let mut txn = QueryTransaction::new(self.multi.begin_query()?, self.single.clone(), identity);
148		txn.set_executor(Arc::new(self.executor.clone()));
149		Ok(txn)
150	}
151
152	pub fn clock(&self) -> &Clock {
153		&self.executor.runtime_context.clock
154	}
155
156	pub fn rng(&self) -> &Rng {
157		&self.executor.runtime_context.rng
158	}
159
160	pub fn version_epoch(&self) -> &VersionEpoch {
161		&self.executor.runtime_context.version_epoch
162	}
163
164	#[instrument(name = "engine::admin_as", level = "debug", skip(self, params), fields(rql = %rql))]
165	pub fn admin_as(&self, identity: IdentityId, rql: &str, params: Params) -> ExecutionResult {
166		if let Some(e) = self.reject_request(identity) {
167			return ExecutionResult::from_error(e);
168		}
169		let mut txn = match self.begin_admin(identity) {
170			Ok(t) => t,
171			Err(mut e) => {
172				e.with_rql(rql.to_string());
173				return ExecutionResult::from_error(e);
174			}
175		};
176		let mut outcome = self.executor.admin(
177			&mut txn,
178			Admin {
179				rql,
180				params,
181			},
182		);
183		self.commit_admin(&mut txn, &mut outcome, rql);
184		self.annotate_rql(&mut outcome, rql);
185		outcome
186	}
187
188	fn reject_request(&self, identity: IdentityId) -> Option<Error> {
189		if let Err(e) = self.reject_if_read_only() {
190			return Some(e);
191		}
192		if let Err(e) = self.reject_if_shutting_down(identity) {
193			return Some(e);
194		}
195		None
196	}
197
198	#[inline]
199	fn commit_admin(&self, txn: &mut AdminTransaction, outcome: &mut ExecutionResult, rql: &str) {
200		if outcome.is_ok()
201			&& let Err(mut e) = txn.commit()
202		{
203			e.with_rql(rql.to_string());
204			outcome.error = Some(e);
205		}
206	}
207
208	fn annotate_rql(&self, outcome: &mut ExecutionResult, rql: &str) {
209		if let Some(ref mut e) = outcome.error {
210			e.with_rql(rql.to_string());
211		}
212		reifydb_assertions! {
213			let annotated = outcome.error.as_ref().map(|e| e.rql.is_some());
214			assert!(
215				annotated != Some(false),
216				"annotate_rql is the single catch-all that attaches the originating query to every error leaving admin_as/command_as; an error reaching the user with rql=None (annotated={:?}) would render a diagnostic with no source query, defeating user-facing error reporting",
217				annotated
218			);
219		}
220	}
221
222	#[instrument(name = "engine::command_as", level = "debug", skip(self, params), fields(rql = %rql))]
223	pub fn command_as(&self, identity: IdentityId, rql: &str, params: Params) -> ExecutionResult {
224		if let Some(e) = self.reject_request(identity) {
225			return ExecutionResult::from_error(e);
226		}
227		let mut txn = match self.begin_command(identity) {
228			Ok(t) => t,
229			Err(mut e) => {
230				e.with_rql(rql.to_string());
231				return ExecutionResult::from_error(e);
232			}
233		};
234		let mut outcome = self.executor.command(
235			&mut txn,
236			Command {
237				rql,
238				params,
239			},
240		);
241		self.commit_command(&mut txn, &mut outcome, rql);
242		self.annotate_rql(&mut outcome, rql);
243		outcome
244	}
245
246	#[inline]
247	fn commit_command(&self, txn: &mut CommandTransaction, outcome: &mut ExecutionResult, rql: &str) {
248		if outcome.is_ok()
249			&& let Err(mut e) = txn.commit()
250		{
251			e.with_rql(rql.to_string());
252			outcome.error = Some(e);
253		}
254	}
255
256	#[instrument(name = "engine::query_as", level = "debug", skip(self, params), fields(rql = %rql))]
257	pub fn query_as(&self, identity: IdentityId, rql: &str, params: Params) -> ExecutionResult {
258		let mut txn = match self.begin_query(identity) {
259			Ok(t) => t,
260			Err(mut e) => {
261				e.with_rql(rql.to_string());
262				return ExecutionResult::from_error(e);
263			}
264		};
265		let mut outcome = self.executor.query(
266			&mut txn,
267			Query {
268				rql,
269				params,
270			},
271		);
272		if let Some(ref mut e) = outcome.error {
273			e.with_rql(rql.to_string());
274		}
275		outcome
276	}
277
278	#[instrument(name = "engine::query_in_txn", level = "debug", skip(self, txn, params), fields(rql = %rql))]
279	pub fn query_in_txn(&self, txn: &mut QueryTransaction, rql: &str, params: Params) -> ExecutionResult {
280		let mut outcome = self.executor.query(
281			txn,
282			Query {
283				rql,
284				params,
285			},
286		);
287		if let Some(ref mut e) = outcome.error {
288			e.with_rql(rql.to_string());
289		}
290		outcome
291	}
292
293	#[instrument(name = "engine::subscribe_as", level = "debug", skip(self, params), fields(rql = %rql))]
294	pub fn subscribe_as(&self, identity: IdentityId, rql: &str, params: Params) -> ExecutionResult {
295		let mut txn = match self.begin_query(identity) {
296			Ok(t) => t,
297			Err(mut e) => {
298				e.with_rql(rql.to_string());
299				return ExecutionResult::from_error(e);
300			}
301		};
302		let mut outcome = self.executor.subscription(
303			&mut txn,
304			Subscription {
305				rql,
306				params,
307			},
308		);
309		if let Some(ref mut e) = outcome.error {
310			e.with_rql(rql.to_string());
311		}
312		outcome
313	}
314
315	pub fn register_virtual_table<T: UserVTable>(
316		&self,
317		namespace_id: NamespaceId,
318		name: &str,
319		table: T,
320	) -> Result<VTableId> {
321		let catalog = self.catalog();
322		let table_id = self.executor.virtual_table_registry.allocate_id();
323
324		let table_columns = table.vtable();
325		if name == "current"
326			&& let Some(column) = table_columns.iter().find(|column| column.kind == MetricKind::Counter)
327		{
328			return Err(error!(internal!(
329				"virtual table '{}' in namespace {:?} declares column '{}' with kind Counter; a table named 'current' may only publish levels, deltas and distributions",
330				name,
331				namespace_id,
332				column.name
333			)));
334		}
335		let columns = convert_vtable_user_columns_to_columns(&table_columns);
336
337		let def = Arc::new(VTable {
338			id: table_id,
339			namespace: namespace_id,
340			name: name.to_string(),
341			columns,
342		});
343
344		catalog.register_vtable_user(def.clone())?;
345
346		let data_fn: UserVTableDataFunction = Arc::new(move |_params| table.get());
347
348		let entry = UserVTableEntry {
349			def: def.clone(),
350			data_fn,
351		};
352		self.executor.virtual_table_registry.register(namespace_id, name.to_string(), entry);
353		Ok(table_id)
354	}
355}
356
357impl CdcHost for StandardEngine {
358	fn begin_command(&self) -> Result<CommandTransaction> {
359		StandardEngine::begin_command(self, IdentityId::system())
360	}
361
362	fn begin_query(&self) -> Result<QueryTransaction> {
363		StandardEngine::begin_query(self, IdentityId::system())
364	}
365
366	fn current_version(&self) -> Result<CommitVersion> {
367		StandardEngine::current_version(self)
368	}
369
370	fn done_until(&self) -> CommitVersion {
371		StandardEngine::done_until(self)
372	}
373
374	fn cdc_producer_watermark(&self) -> CommitVersion {
375		StandardEngine::cdc_producer_watermark(self)
376	}
377
378	fn wait_for_mark_timeout(&self, version: CommitVersion, timeout: Duration) -> bool {
379		StandardEngine::wait_for_mark_timeout(self, version, timeout)
380	}
381
382	fn notify_on_mark(&self, version: CommitVersion, callback: Box<dyn FnOnce() + Send>) {
383		StandardEngine::notify_on_mark(self, version, callback);
384	}
385
386	fn catalog(&self) -> &Catalog {
387		&self.catalog
388	}
389}
390
391impl Clone for StandardEngine {
392	fn clone(&self) -> Self {
393		Self(self.0.clone())
394	}
395}
396
397impl Deref for StandardEngine {
398	type Target = Inner;
399
400	fn deref(&self) -> &Self::Target {
401		&self.0
402	}
403}
404
405pub struct Inner {
406	multi: MultiTransaction,
407	single: SingleTransaction,
408	event_bus: EventBus,
409	executor: Executor,
410	interceptors: Arc<InterceptorFactory>,
411	catalog: Catalog,
412	operator_library: OperatorLibrary,
413	operator_state: OperatorStore,
414	dictionary_allocators: DictionaryAllocatorRegistry,
415	read_only: AtomicBool,
416	shutting_down: AtomicBool,
417}
418
419impl StandardEngine {
420	pub fn new(
421		multi: MultiTransaction,
422		single: SingleTransaction,
423		event_bus: EventBus,
424		interceptors: InterceptorFactory,
425		catalog: Catalog,
426		config: EngineConfig,
427	) -> Self {
428		let operator_library = OperatorLibrary::new();
429
430		let listener = OperatorLibraryEventListener::new(operator_library.clone());
431		event_bus.register(listener);
432
433		let metrics_store = config
434			.ioc
435			.resolve::<SingleStore>()
436			.expect("SingleStore must be registered in IocContainer for metrics");
437		let metrics_reader = MetricsReader::new(metrics_store);
438
439		let operator_state = config
440			.ioc
441			.resolve::<OperatorStore>()
442			.expect("OperatorStore must be registered in IocContainer");
443
444		let catalog_for_interceptor = catalog.clone();
445		interceptors.add_late(Arc::new(move |interceptors: &mut Interceptors| {
446			interceptors.post_commit.add(Arc::new(CatalogCacheInterceptor::new(&catalog_for_interceptor)));
447		}));
448
449		let interceptors = Arc::new(interceptors);
450
451		let dictionary_allocators =
452			DictionaryAllocatorRegistry::new(Arc::new(SingleDictionaryStore::new(single.clone())));
453
454		Self(Arc::new(Inner {
455			multi,
456			single,
457			event_bus,
458			executor: Executor::new(catalog.clone(), config, operator_library.clone(), metrics_reader),
459			interceptors,
460			catalog,
461			operator_library,
462			operator_state,
463			dictionary_allocators,
464			read_only: AtomicBool::new(false),
465			shutting_down: AtomicBool::new(false),
466		}))
467	}
468
469	pub fn create_interceptors(&self) -> Interceptors {
470		self.interceptors.create()
471	}
472
473	pub fn dictionary_allocators(&self) -> DictionaryAllocatorRegistry {
474		self.dictionary_allocators.clone()
475	}
476
477	pub fn add_interceptor_factory(&self, factory: Arc<dyn Fn(&mut Interceptors) + Send + Sync>) {
478		self.interceptors.add_late(factory);
479	}
480
481	#[instrument(name = "engine::transaction::begin_query_at_version", level = "trace", skip(self, lease), fields(version = %lease.version().0
482    ))]
483	pub fn begin_query_at_version(
484		&self,
485		lease: &VersionLeaseGuard,
486		identity: IdentityId,
487	) -> Result<QueryTransaction> {
488		let mut txn =
489			QueryTransaction::new(self.multi.begin_query_at_version(lease)?, self.single.clone(), identity);
490		txn.set_executor(Arc::new(self.executor.clone()));
491		Ok(txn)
492	}
493
494	#[instrument(name = "engine::acquire_version_lease", level = "trace", skip(self), fields(version = %version.0))]
495	pub fn acquire_version_lease(&self, version: CommitVersion) -> Result<VersionLeaseGuard> {
496		self.multi.acquire_version_lease(version)
497	}
498
499	#[instrument(name = "engine::acquire_current_snapshot_lease", level = "trace", skip(self))]
500	pub fn acquire_current_snapshot_lease(&self) -> Result<(CommitVersion, VersionLeaseGuard)> {
501		self.multi.acquire_current_snapshot_lease()
502	}
503
504	#[inline]
505	pub fn multi(&self) -> &MultiTransaction {
506		&self.multi
507	}
508
509	#[inline]
510	pub fn multi_owned(&self) -> MultiTransaction {
511		self.multi.clone()
512	}
513
514	#[inline]
515	pub fn spawner(&self) -> ActorSpawner {
516		self.multi.spawner()
517	}
518
519	#[inline]
520	pub fn single(&self) -> &SingleTransaction {
521		&self.single
522	}
523
524	#[inline]
525	pub fn single_owned(&self) -> SingleTransaction {
526		self.single.clone()
527	}
528
529	#[inline]
530	pub fn emit<E: Event>(&self, event: E) {
531		self.event_bus.emit(event)
532	}
533
534	#[inline]
535	pub fn catalog(&self) -> Catalog {
536		self.catalog.clone()
537	}
538
539	#[inline]
540	pub fn services(&self) -> Arc<Services> {
541		self.executor.services().clone()
542	}
543
544	#[inline]
545	pub fn operator_store(&self) -> &OperatorLibrary {
546		&self.operator_library
547	}
548
549	pub fn operator_state(&self) -> OperatorStore {
550		self.operator_state.clone()
551	}
552
553	pub fn checkpoint_floor(&self) -> Arc<dyn CheckpointFloor> {
554		Arc::new(self.operator_state.clone())
555	}
556
557	#[inline]
558	pub fn current_version(&self) -> Result<CommitVersion> {
559		self.multi.current_version()
560	}
561
562	#[inline]
563	pub fn done_until(&self) -> CommitVersion {
564		self.multi.done_until()
565	}
566
567	#[inline]
568	pub fn query_done_until(&self) -> CommitVersion {
569		self.multi.query_done_until()
570	}
571
572	#[inline]
573	pub fn oracle_window_count(&self) -> usize {
574		self.multi.oracle_window_count()
575	}
576
577	#[inline]
578	pub fn wait_for_mark_timeout(&self, version: CommitVersion, timeout: Duration) -> bool {
579		self.multi.wait_for_mark_timeout(version, timeout)
580	}
581
582	#[inline]
583	pub fn notify_on_mark(&self, version: CommitVersion, callback: Box<dyn FnOnce() + Send>) {
584		self.multi.notify_on_mark(version, callback);
585	}
586
587	#[inline]
588	pub fn executor(&self) -> Executor {
589		self.executor.clone()
590	}
591
592	#[inline]
593	pub fn view_lineage(&self) -> ViewLineage {
594		self.executor.view_lineage.clone()
595	}
596
597	#[inline]
598	pub fn ioc(&self) -> &IocContainer {
599		&self.executor.ioc
600	}
601
602	#[inline]
603	pub fn cdc_store(&self) -> CdcStore {
604		self.executor.ioc.resolve::<CdcStore>().expect("CdcStore must be registered")
605	}
606
607	#[inline]
608	pub fn actor<M: 'static>(&self) -> Option<ActorRef<M>>
609	where
610		ActorRef<M>: Send + Sync,
611	{
612		self.executor.ioc.try_resolve::<ActorRef<M>>()
613	}
614
615	#[inline]
616	pub fn cdc_producer_watermark(&self) -> CommitVersion {
617		self.executor.ioc.try_resolve::<CdcProducerWatermark>().map(|w| w.get()).unwrap_or(CommitVersion(0))
618	}
619
620	#[inline]
621	pub fn cdc_consumer_watermark(&self) -> CommitVersion {
622		self.executor.ioc.try_resolve::<CdcConsumerWatermark>().map(|w| w.get()).unwrap_or(CommitVersion(0))
623	}
624
625	#[inline]
626	pub fn notify_cdc_consumers(&self) {
627		if let Some(registry) = self.executor.ioc.try_resolve::<CdcWakeRegistry>() {
628			registry.notify_all();
629		}
630	}
631
632	pub fn set_read_only(&self) {
633		self.read_only.store(true, Ordering::SeqCst);
634	}
635
636	pub fn is_read_only(&self) -> bool {
637		self.read_only.load(Ordering::SeqCst)
638	}
639
640	pub(crate) fn reject_if_read_only(&self) -> Result<()> {
641		if self.is_read_only() {
642			return Err(Error(Box::new(read_only_rejection(Fragment::None))));
643		}
644		Ok(())
645	}
646
647	pub fn set_shutting_down(&self) {
648		self.shutting_down.store(true, Ordering::SeqCst);
649	}
650
651	pub fn is_shutting_down(&self) -> bool {
652		self.shutting_down.load(Ordering::SeqCst)
653	}
654
655	pub(crate) fn reject_if_shutting_down(&self, identity: IdentityId) -> Result<()> {
656		if self.is_shutting_down() && !identity.is_system() {
657			return Err(TransactionError::ShuttingDown.into());
658		}
659		Ok(())
660	}
661
662	pub fn bulk_insert<'e>(&'e self, identity: IdentityId) -> BulkInsertBuilder<'e, Validated> {
663		BulkInsertBuilder::new(self, identity)
664	}
665
666	pub fn bulk_insert_unchecked<'e>(&'e self, identity: IdentityId) -> BulkInsertBuilder<'e, Unchecked> {
667		BulkInsertBuilder::new_unchecked(self, identity)
668	}
669}
670
671impl Shutdown for StandardEngine {
672	fn shutdown(&self) {
673		self.interceptors.clear_late();
674		self.executor.ioc.clear();
675		self.executor.virtual_table_registry.clear();
676		self.multi().store().clear_eviction_watermark();
677		#[cfg(not(reifydb_single_threaded))]
678		if let Some(registry) = self.executor.remote_registry.as_ref() {
679			registry.shutdown();
680		}
681	}
682}
683
684fn convert_vtable_user_columns_to_columns(columns: &[UserVTableColumn]) -> Vec<Column> {
685	columns.iter()
686		.enumerate()
687		.map(|(idx, col)| {
688			let constraint = TypeConstraint::unconstrained(col.data_type.clone());
689			Column {
690				id: ColumnId(idx as u64),
691				name: col.name.clone(),
692				constraint,
693				properties: vec![],
694				index: ColumnIndex(idx as u8),
695				auto_increment: false,
696				dictionary_id: None,
697			}
698		})
699		.collect()
700}