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