1use std::{mem, ops::Deref, sync::Arc};
5
6use reifydb_catalog::{
7 cache::CatalogCache,
8 catalog::{
9 Catalog,
10 namespace::NamespaceToCreate,
11 table::{TableColumnToCreate, TableToCreate},
12 },
13};
14#[cfg(not(target_arch = "wasm32"))]
15use reifydb_cdc::storage::recent_cache::RecentCdcCache;
16use reifydb_cdc::{
17 consume::wake::CdcWakeRegistry,
18 produce::{
19 producer::{CdcProducerEventListener, spawn_cdc_producer},
20 watermark::CdcProducerWatermark,
21 },
22 storage::CdcStore,
23};
24use reifydb_core::{
25 actors::cdc::CdcProduceHandle,
26 event::{EventBus, transaction::PostCommitEvent},
27 interface::catalog::id::NamespaceId,
28 util::ioc::IocContainer,
29};
30use reifydb_extension::transform::registry::Transforms;
31use reifydb_routine::{
32 function::default_native_functions, procedure::default_native_procedures, routine::registry::Routines,
33};
34use reifydb_runtime::{
35 Runtime, RuntimeConfig,
36 actor::system::{ActorSpawner, ActorSystem},
37 context::{
38 RuntimeContext,
39 clock::{Clock, MockClock},
40 rng::Rng,
41 },
42 pool::{PoolConfig, Pools},
43};
44#[cfg(not(target_arch = "wasm32"))]
45use reifydb_sqlite::SqliteConfig;
46use reifydb_store_multi::{MultiStore, gc::epoch::listener::VersionEpochListener};
47use reifydb_store_single::SingleStore;
48use reifydb_transaction::{
49 dictionary::DictionaryAllocatorRegistry,
50 interceptor::{factory::InterceptorFactory, interceptors::Interceptors},
51 multi::transaction::MultiTransaction,
52 single::SingleTransaction,
53 transaction::admin::AdminTransaction,
54};
55use reifydb_value::{
56 fragment::Fragment,
57 params::Params,
58 value::{constraint::TypeConstraint, frame::frame::Frame, identity::IdentityId, value_type::ValueType},
59};
60
61use crate::{engine::StandardEngine, vm::services::EngineConfig};
62
63pub struct TestEngine {
64 engine: StandardEngine,
65 mock_clock: MockClock,
66 _runtime: Runtime,
67}
68
69impl Default for TestEngine {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl TestEngine {
76 pub fn new() -> Self {
77 Self::builder().with_cdc().build()
78 }
79
80 pub fn builder() -> TestEngineBuilder {
81 TestEngineBuilder::default()
82 }
83
84 pub fn admin(&self, rql: &str) -> Vec<Frame> {
85 let r = self.engine.admin_as(IdentityId::system(), rql, Params::None);
86 if let Some(e) = r.error {
87 panic!("admin failed: {e:?}\nrql: {rql}")
88 }
89 r.frames
90 }
91
92 pub fn command(&self, rql: &str) -> Vec<Frame> {
93 let r = self.engine.command_as(IdentityId::system(), rql, Params::None);
94 if let Some(e) = r.error {
95 panic!("command failed: {e:?}\nrql: {rql}")
96 }
97 r.frames
98 }
99
100 pub fn query(&self, rql: &str) -> Vec<Frame> {
101 let r = self.engine.query_as(IdentityId::system(), rql, Params::None);
102 if let Some(e) = r.error {
103 panic!("query failed: {e:?}\nrql: {rql}")
104 }
105 r.frames
106 }
107
108 pub fn admin_err(&self, rql: &str) -> String {
109 let r = self.engine.admin_as(IdentityId::system(), rql, Params::None);
110 match r.error {
111 Some(e) => format!("{e:?}"),
112 None => panic!("Expected error but admin succeeded\nrql: {rql}"),
113 }
114 }
115
116 pub fn command_err(&self, rql: &str) -> String {
117 let r = self.engine.command_as(IdentityId::system(), rql, Params::None);
118 match r.error {
119 Some(e) => format!("{e:?}"),
120 None => panic!("Expected error but command succeeded\nrql: {rql}"),
121 }
122 }
123
124 pub fn query_err(&self, rql: &str) -> String {
125 let r = self.engine.query_as(IdentityId::system(), rql, Params::None);
126 match r.error {
127 Some(e) => format!("{e:?}"),
128 None => panic!("Expected error but query succeeded\nrql: {rql}"),
129 }
130 }
131
132 pub fn row_count(frames: &[Frame]) -> usize {
133 frames.first().map(|f| f.row_count()).unwrap_or(0)
134 }
135
136 pub fn identity() -> IdentityId {
137 IdentityId::system()
138 }
139
140 pub fn inner(&self) -> &StandardEngine {
141 &self.engine
142 }
143
144 pub fn mock_clock(&self) -> MockClock {
145 self.mock_clock.clone()
146 }
147}
148
149impl Deref for TestEngine {
150 type Target = StandardEngine;
151
152 fn deref(&self) -> &StandardEngine {
153 &self.engine
154 }
155}
156
157#[derive(Default)]
158pub struct TestEngineBuilder {
159 cdc: bool,
160 #[cfg(not(target_arch = "wasm32"))]
161 sqlite_cdc: Option<SqliteConfig>,
162}
163
164impl TestEngineBuilder {
165 pub fn with_cdc(mut self) -> Self {
166 self.cdc = true;
167 self
168 }
169
170 #[cfg(not(target_arch = "wasm32"))]
171 pub fn with_sqlite_cdc(mut self, config: SqliteConfig) -> Self {
172 self.cdc = true;
173 self.sqlite_cdc = Some(config);
174 self
175 }
176
177 pub fn build(self) -> TestEngine {
178 let mock_clock = MockClock::from_millis(1000);
179 let runtime = make_test_runtime(&mock_clock);
180 let spawner = runtime.spawner();
181 let clock = runtime.clock().clone();
182 let rng = runtime.rng().clone();
183
184 let eventbus = EventBus::new(&spawner);
185 let multi_store = MultiStore::testing_memory_with_eventbus(eventbus.clone());
186 let single_store = SingleStore::testing_memory();
187 let single = SingleTransaction::new(single_store.clone(), eventbus.clone());
188 let catalog_cache = CatalogCache::new();
189 let multi = MultiTransaction::new(
190 multi_store.clone(),
191 single.clone(),
192 eventbus.clone(),
193 spawner.clone(),
194 clock.clone(),
195 rng.clone(),
196 Arc::new(catalog_cache.clone()),
197 )
198 .unwrap();
199
200 let mut ioc = IocContainer::new();
201 ioc = ioc.register(catalog_cache.clone());
202 ioc = ioc.register(spawner.clone()).register(clock.clone()).register(rng.clone());
203 ioc = ioc.register(single_store.clone());
204 ioc = ioc.register(eventbus.clone());
205
206 #[cfg(not(target_arch = "wasm32"))]
207 let cdc_store = match self.sqlite_cdc {
208 Some(config) => CdcStore::sqlite(config, RecentCdcCache::DEFAULT_CAPACITY),
209 None => CdcStore::memory(),
210 };
211 #[cfg(target_arch = "wasm32")]
212 let cdc_store = CdcStore::memory();
213 ioc = ioc.register(cdc_store.clone());
214
215 let cdc_producer_watermark = CdcProducerWatermark::new();
216 ioc = ioc.register(cdc_producer_watermark.clone());
217
218 let cdc_wake_registry = CdcWakeRegistry::new();
219 ioc = ioc.register(cdc_wake_registry.clone());
220
221 let ioc_for_cdc = ioc.clone();
222
223 let engine = StandardEngine::new(
224 multi,
225 single.clone(),
226 eventbus.clone(),
227 InterceptorFactory::default(),
228 Catalog::new(catalog_cache),
229 EngineConfig {
230 runtime_context: RuntimeContext::new(clock.clone(), rng.clone()),
231 routines: {
232 let b = Routines::builder();
233 let b = default_native_functions(b);
234 default_native_procedures(b).configure()
235 },
236 transforms: Transforms::empty(),
237 ioc,
238 #[cfg(not(reifydb_single_threaded))]
239 remote_registry: None,
240 },
241 );
242
243 if self.cdc {
244 register_cdc_producer(
245 &spawner,
246 clock.clone(),
247 cdc_store,
248 multi_store,
249 &engine,
250 &eventbus,
251 ioc_for_cdc,
252 cdc_producer_watermark,
253 cdc_wake_registry,
254 );
255 }
256
257 TestEngine {
258 engine,
259 mock_clock,
260 _runtime: runtime,
261 }
262 }
263}
264
265#[inline]
266fn make_test_runtime(mock_clock: &MockClock) -> Runtime {
267 let config = RuntimeConfig::default().seeded(1000);
268 let config = RuntimeConfig {
269 clock: Clock::Mock(mock_clock.clone()),
270 ..config
271 };
272 let pools = PoolConfig {
273 coordination_threads: 2,
274 flow_threads: 2,
275 task_threads: 2,
276 compute_threads: 2,
277 async_threads: 2,
278 };
279 Runtime::from_config(config, pools)
280}
281
282#[allow(clippy::too_many_arguments)]
283fn register_cdc_producer(
284 spawner: &ActorSpawner,
285 clock: Clock,
286 cdc_store: CdcStore,
287 multi_store: MultiStore,
288 engine: &StandardEngine,
289 eventbus: &EventBus,
290 ioc_for_cdc: IocContainer,
291 watermark: CdcProducerWatermark,
292 wake_registry: CdcWakeRegistry,
293) {
294 let cdc_handle = spawn_cdc_producer(
295 spawner,
296 cdc_store,
297 multi_store,
298 engine.clone(),
299 eventbus.clone(),
300 clock.clone(),
301 watermark,
302 wake_registry,
303 );
304 eventbus.register::<PostCommitEvent, _>(CdcProducerEventListener::new(
305 cdc_handle.actor_ref().clone(),
306 clock.clone(),
307 ));
308 eventbus.register::<PostCommitEvent, _>(VersionEpochListener::new(engine.version_epoch().clone(), clock));
309 ioc_for_cdc.register_service::<Arc<CdcProduceHandle>>(Arc::new(cdc_handle));
310}
311
312pub fn create_test_admin_transaction() -> AdminTransaction {
313 let multi_store = MultiStore::testing_memory();
314 let single_store = SingleStore::testing_memory();
315
316 let pools = Pools::new(PoolConfig::sync_only());
317 let actor_system = ActorSystem::new(pools, Clock::Real);
318 let spawner = actor_system.spawner();
319 mem::forget(actor_system);
320 let event_bus = EventBus::new(&spawner);
321 let single = SingleTransaction::new(single_store, event_bus.clone());
322 let multi = MultiTransaction::new(
323 multi_store,
324 single.clone(),
325 event_bus.clone(),
326 spawner,
327 Clock::Mock(MockClock::from_millis(1000)),
328 Rng::seeded(42),
329 Arc::new(CatalogCache::new()),
330 )
331 .unwrap();
332
333 let mut txn = AdminTransaction::new(
334 multi,
335 single,
336 event_bus,
337 Interceptors::new(),
338 IdentityId::system(),
339 Clock::Mock(MockClock::from_millis(1000)),
340 )
341 .unwrap();
342 txn.set_dictionary_allocators(DictionaryAllocatorRegistry::new());
343 txn
344}
345
346pub fn create_test_admin_transaction_with_internal_shape() -> AdminTransaction {
347 let multi_store = MultiStore::testing_memory();
348 let single_store = SingleStore::testing_memory();
349
350 let pools = Pools::new(PoolConfig::sync_only());
351 let actor_system = ActorSystem::new(pools, Clock::Real);
352 let spawner = actor_system.spawner();
353 mem::forget(actor_system);
354 let event_bus = EventBus::new(&spawner);
355 let single = SingleTransaction::new(single_store, event_bus.clone());
356 let multi = MultiTransaction::new(
357 multi_store,
358 single.clone(),
359 event_bus.clone(),
360 spawner,
361 Clock::Mock(MockClock::from_millis(1000)),
362 Rng::seeded(42),
363 Arc::new(CatalogCache::new()),
364 )
365 .unwrap();
366 let mut result = AdminTransaction::new(
367 multi,
368 single.clone(),
369 event_bus.clone(),
370 Interceptors::new(),
371 IdentityId::system(),
372 Clock::Mock(MockClock::from_millis(1000)),
373 )
374 .unwrap();
375 result.set_dictionary_allocators(DictionaryAllocatorRegistry::new());
376
377 let catalog_cache = CatalogCache::new();
378 let catalog = Catalog::new(catalog_cache);
379
380 let namespace = catalog
381 .create_namespace(
382 &mut result,
383 NamespaceToCreate {
384 namespace_fragment: None,
385 name: "reifydb".to_string(),
386 local_name: "reifydb".to_string(),
387 parent_id: NamespaceId::ROOT,
388 grpc: None,
389 token: None,
390 },
391 )
392 .unwrap();
393
394 catalog.create_table(
395 &mut result,
396 TableToCreate {
397 name: Fragment::internal("flows"),
398 namespace: namespace.id(),
399 columns: vec![
400 TableColumnToCreate {
401 name: Fragment::internal("id"),
402 fragment: Fragment::None,
403 constraint: TypeConstraint::unconstrained(ValueType::Int8),
404 properties: vec![],
405 auto_increment: true,
406 dictionary_id: None,
407 },
408 TableColumnToCreate {
409 name: Fragment::internal("data"),
410 fragment: Fragment::None,
411 constraint: TypeConstraint::unconstrained(ValueType::Blob),
412 properties: vec![],
413 auto_increment: false,
414 dictionary_id: None,
415 },
416 ],
417 retention_strategy: None,
418 primary_key_columns: None,
419 partition_by: vec![],
420 underlying: false,
421 },
422 )
423 .unwrap();
424
425 result
426}