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