reifydb_sub_flow/testing/
harness.rs1use std::{
5 collections::HashMap,
6 marker::PhantomData,
7 mem,
8 ops::{Bound, Index},
9};
10
11use reifydb_catalog::catalog::Catalog;
12use reifydb_codec::{encoded::row::EncodedRow, key::encoded::EncodedKey};
13use reifydb_core::{
14 actors::pending::Pending,
15 common::CommitVersion,
16 interface::{catalog::flow::FlowNodeId, change::Change},
17 row::Row,
18};
19use reifydb_engine::test_harness::TestEngine;
20use reifydb_runtime::context::clock::{Clock, MockClock};
21use reifydb_sdk::{
22 config::Config,
23 operator::{
24 OperatorLogic, OperatorMetadata,
25 context::{OperatorContext, StateApi, StoreApi},
26 },
27 testing::builders::TestChangeBuilder,
28};
29use reifydb_transaction::interceptor::interceptors::Interceptors;
30use reifydb_value::{Result, value::Value};
31use serde::de::DeserializeOwned;
32
33use crate::{
34 operator::{
35 Operator,
36 context::native::NativeOperatorContext,
37 native::{FlowNativeBridge, NativeBridgedOperator, NativeOperatorAdapter},
38 },
39 transaction::{DeferredParams, FlowTransaction, allocators::FlowAllocators},
40};
41
42pub struct NativeOperatorHarness<C: OperatorLogic + OperatorMetadata + 'static> {
43 engine: TestEngine,
44 operator: NativeBridgedOperator,
45 node_id: FlowNodeId,
46 version: u64,
47 pending: Pending,
48 allocators: FlowAllocators,
49 current: Option<FlowTransaction>,
50 history: Vec<Change>,
51 _phantom: PhantomData<C>,
52}
53
54impl<C: OperatorLogic + OperatorMetadata + 'static> NativeOperatorHarness<C> {
55 pub fn builder() -> NativeOperatorHarnessBuilder<C> {
56 NativeOperatorHarnessBuilder::new()
57 }
58
59 fn begin_txn(&mut self) -> FlowTransaction {
60 let query = self.engine.multi().begin_query().expect("begin_query");
61 let state_query = self.engine.multi().begin_query().expect("begin_query");
62 FlowTransaction::deferred_from_parts(DeferredParams {
63 version: CommitVersion(self.version),
64 pending: mem::take(&mut self.pending),
65 query,
66 state_query,
67 dictionary_query: None,
68 single: self.engine.inner().single().clone(),
69 catalog: Catalog::testing(),
70 interceptors: Interceptors::new(),
71 clock: Clock::Mock(MockClock::from_millis(1000)),
72 allocators: self.allocators.clone(),
73 })
74 }
75
76 fn end_txn(&mut self, mut txn: FlowTransaction) {
77 self.pending = txn.take_pending();
78 self.version += 1;
79 }
80
81 pub fn apply(&mut self, input: Change) -> Result<Change> {
82 let mut txn = self.begin_txn();
83 let output = self.operator.apply(&mut txn, input)?;
84 txn.flush_operator_states()?;
85 self.end_txn(txn);
86 self.history.push(output.clone());
87 Ok(output)
88 }
89
90 pub fn apply_without_flush(&mut self, input: Change) -> Result<Change> {
91 let mut txn = self.begin_txn();
92 let output = self.operator.apply(&mut txn, input)?;
93 self.current = Some(txn);
94 self.history.push(output.clone());
95 Ok(output)
96 }
97
98 pub fn flush(&mut self) -> Result<()> {
99 let mut txn = match self.current.take() {
100 Some(txn) => txn,
101 None => self.begin_txn(),
102 };
103 txn.flush_operator_states()?;
104 self.end_txn(txn);
105 Ok(())
106 }
107
108 pub fn state_value<V: DeserializeOwned>(&mut self, key: &EncodedKey) -> Option<V> {
109 let node = self.node_id;
110 if let Some(txn) = self.current.as_mut() {
111 let mut bridge = FlowNativeBridge::new(txn, node);
112 let mut ctx = NativeOperatorContext::new(&mut bridge, node);
113 return ctx.state().get::<V>(key).expect("state get");
114 }
115 let mut txn = self.begin_txn();
116 let value = {
117 let mut bridge = FlowNativeBridge::new(&mut txn, node);
118 let mut ctx = NativeOperatorContext::new(&mut bridge, node);
119 ctx.state().get::<V>(key).expect("state get")
120 };
121 self.end_txn(txn);
122 value
123 }
124
125 pub fn seed_store(&mut self, rows: &[(EncodedKey, EncodedRow)]) {
126 let keys: Vec<EncodedKey> = rows.iter().map(|(k, _)| k.clone()).collect();
127 let values: Vec<EncodedRow> = rows.iter().map(|(_, v)| v.clone()).collect();
128 let mut txn = self.begin_txn();
129 txn.set_batch(&keys, &values).expect("seed_store set_batch");
130 self.end_txn(txn);
131 }
132
133 pub fn store_range(
134 &mut self,
135 start: Bound<&EncodedKey>,
136 end: Bound<&EncodedKey>,
137 ) -> Vec<(EncodedKey, EncodedRow)> {
138 let node = self.node_id;
139 let mut txn = self.begin_txn();
140 let rows = {
141 let mut bridge = FlowNativeBridge::new(&mut txn, node);
142 let mut ctx = NativeOperatorContext::new(&mut bridge, node);
143 ctx.store().range(start, end).expect("store range")
144 };
145 self.end_txn(txn);
146 rows
147 }
148
149 pub fn insert(&mut self, row: Row) -> &mut Self {
150 let change = TestChangeBuilder::new().insert(row).build();
151 self.apply(change).expect("insert failed");
152 self
153 }
154
155 pub fn update(&mut self, pre: Row, post: Row) -> &mut Self {
156 let change = TestChangeBuilder::new().update(pre, post).build();
157 self.apply(change).expect("update failed");
158 self
159 }
160
161 pub fn remove(&mut self, row: Row) -> &mut Self {
162 let change = TestChangeBuilder::new().remove(row).build();
163 self.apply(change).expect("remove failed");
164 self
165 }
166
167 pub fn history_len(&self) -> usize {
168 self.history.len()
169 }
170
171 pub fn last_change(&self) -> Option<&Change> {
172 self.history.last()
173 }
174
175 pub fn clear_history(&mut self) {
176 self.history.clear();
177 }
178
179 pub fn node_id(&self) -> FlowNodeId {
180 self.node_id
181 }
182}
183
184impl<C: OperatorLogic + OperatorMetadata + 'static> Index<usize> for NativeOperatorHarness<C> {
185 type Output = Change;
186
187 fn index(&self, index: usize) -> &Self::Output {
188 &self.history[index]
189 }
190}
191
192pub struct NativeOperatorHarnessBuilder<C> {
193 config: HashMap<String, Value>,
194 node_id: FlowNodeId,
195 version: CommitVersion,
196 _phantom: PhantomData<C>,
197}
198
199impl<C: OperatorLogic + OperatorMetadata + 'static> Default for NativeOperatorHarnessBuilder<C> {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205impl<C: OperatorLogic + OperatorMetadata + 'static> NativeOperatorHarnessBuilder<C> {
206 pub fn new() -> Self {
207 Self {
208 config: HashMap::new(),
209 node_id: FlowNodeId(1),
210 version: CommitVersion(1),
211 _phantom: PhantomData,
212 }
213 }
214
215 pub fn with_config<I, K>(mut self, config: I) -> Self
216 where
217 I: IntoIterator<Item = (K, Value)>,
218 K: Into<String>,
219 {
220 self.config = config.into_iter().map(|(k, v)| (k.into(), v)).collect();
221 self
222 }
223
224 pub fn add_config(mut self, key: impl Into<String>, value: Value) -> Self {
225 self.config.insert(key.into(), value);
226 self
227 }
228
229 pub fn with_node_id(mut self, node_id: FlowNodeId) -> Self {
230 self.node_id = node_id;
231 self
232 }
233
234 pub fn with_version(mut self, version: CommitVersion) -> Self {
235 self.version = version;
236 self
237 }
238
239 pub fn build(self) -> Result<NativeOperatorHarness<C>> {
240 let engine = TestEngine::new();
241 let core = C::create(
242 self.node_id,
243 &Config::new(<C as OperatorMetadata>::NAME, self.config.clone().into_iter().collect()),
244 )?;
245 let capabilities = <C as OperatorMetadata>::CAPABILITIES;
246 let adapter = NativeOperatorAdapter::new(core, self.node_id, capabilities);
247 let operator = NativeBridgedOperator::new(Box::new(adapter), self.node_id, capabilities);
248
249 Ok(NativeOperatorHarness {
250 engine,
251 operator,
252 node_id: self.node_id,
253 version: self.version.0,
254 pending: Pending::new(),
255 allocators: FlowAllocators::new(),
256 current: None,
257 history: Vec::new(),
258 _phantom: PhantomData,
259 })
260 }
261}