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