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