Skip to main content

reifydb_sub_flow/operator/stateful/
single.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3use reifydb_codec::{
4	encoded::{row::EncodedRow, shape::RowShape},
5	key::encoded::EncodedKey,
6};
7use reifydb_value::Result;
8
9use super::utils;
10use crate::{operator::stateful::raw::RawStatefulOperator, transaction::FlowTransaction};
11
12pub trait SingleStateful: RawStatefulOperator {
13	fn layout(&self) -> RowShape;
14
15	fn key(&self) -> EncodedKey {
16		utils::empty_key()
17	}
18
19	fn create_state(&self) -> EncodedRow {
20		let layout = self.layout();
21		layout.allocate()
22	}
23
24	fn load_state(&self, txn: &mut FlowTransaction) -> Result<EncodedRow> {
25		let key = self.key();
26		utils::load_or_create_row(self.id(), txn, &key, &self.layout())
27	}
28
29	fn save_state(&self, txn: &mut FlowTransaction, row: EncodedRow) -> Result<()> {
30		let key = self.key();
31		utils::save_row(self.id(), txn, &key, row)
32	}
33
34	fn update_state<F>(&self, txn: &mut FlowTransaction, f: F) -> Result<EncodedRow>
35	where
36		F: FnOnce(&RowShape, &mut EncodedRow) -> Result<()>,
37	{
38		let shape = self.layout();
39		let mut row = self.load_state(txn)?;
40		f(&shape, &mut row)?;
41		self.save_state(txn, row.clone())?;
42		Ok(row)
43	}
44
45	fn clear_state(&self, txn: &mut FlowTransaction) -> Result<()> {
46		let key = self.key();
47		utils::state_remove(self.id(), txn, &key)
48	}
49}
50
51#[cfg(test)]
52pub mod tests {
53	use reifydb_catalog::catalog::Catalog;
54	use reifydb_core::{common::CommitVersion, interface::catalog::flow::FlowNodeId};
55	use reifydb_runtime::context::clock::{Clock, MockClock};
56	use reifydb_transaction::interceptor::interceptors::Interceptors;
57
58	use super::*;
59	use crate::{operator::stateful::test_utils::test::*, transaction::FlowTransaction};
60
61	// Extend TestOperator to implement SingleStateful
62	impl SingleStateful for TestOperator {
63		fn layout(&self) -> RowShape {
64			self.layout.clone()
65		}
66	}
67
68	#[test]
69	fn testault_key() {
70		let operator = TestOperator::simple(FlowNodeId(1));
71		let key = operator.key();
72
73		// Default key should be empty
74		assert_eq!(key.len(), 0);
75	}
76
77	#[test]
78	fn test_create_state() {
79		let operator = TestOperator::simple(FlowNodeId(1));
80		let state = operator.create_state();
81
82		// State should be allocated based on layout
83		assert!(state.len() > 0);
84	}
85
86	#[test]
87	fn test_load_save_state() {
88		let mut txn = create_test_transaction();
89		let mut txn = FlowTransaction::deferred(
90			&mut txn,
91			CommitVersion(1),
92			Catalog::testing(),
93			Interceptors::new(),
94			Clock::Mock(MockClock::from_millis(1000)),
95		);
96		let operator = TestOperator::simple(FlowNodeId(1));
97
98		// Initially should create new state
99		let state1 = operator.load_state(&mut txn).unwrap();
100
101		// Modify and save
102		let mut modified = state1.clone();
103		let layout = operator.layout();
104		layout.set_i64(&mut modified, 0, 0x33);
105		operator.save_state(&mut txn, modified.clone()).unwrap();
106
107		// Load should return modified state
108		let state2 = operator.load_state(&mut txn).unwrap();
109		assert_eq!(layout.get_i64(&state2, 0), 0x33);
110	}
111
112	#[test]
113	fn test_update_state() {
114		let mut txn = create_test_transaction();
115		let mut txn = FlowTransaction::deferred(
116			&mut txn,
117			CommitVersion(1),
118			Catalog::testing(),
119			Interceptors::new(),
120			Clock::Mock(MockClock::from_millis(1000)),
121		);
122		let operator = TestOperator::simple(FlowNodeId(1));
123
124		// Update state with a function
125		let result = operator
126			.update_state(&mut txn, |shape, row| {
127				shape.set_i64(row, 0, 0x77);
128				Ok(())
129			})
130			.unwrap();
131
132		let layout = operator.layout();
133		assert_eq!(layout.get_i64(&result, 0), 0x77);
134
135		// Verify persistence
136		let loaded = operator.load_state(&mut txn).unwrap();
137		assert_eq!(layout.get_i64(&loaded, 0), 0x77);
138	}
139
140	#[test]
141	fn test_clear_state() {
142		let mut txn = create_test_transaction();
143		let mut txn = FlowTransaction::deferred(
144			&mut txn,
145			CommitVersion(1),
146			Catalog::testing(),
147			Interceptors::new(),
148			Clock::Mock(MockClock::from_millis(1000)),
149		);
150		let operator = TestOperator::simple(FlowNodeId(1));
151
152		// Create and modify state
153		operator.update_state(&mut txn, |shape, row| {
154			shape.set_i64(row, 0, 0x99);
155			Ok(())
156		})
157		.unwrap();
158
159		// Clear state
160		operator.clear_state(&mut txn).unwrap();
161
162		// Loading should create new default state
163		let new_state = operator.load_state(&mut txn).unwrap();
164		let layout = operator.layout();
165		assert_eq!(layout.get_i64(&new_state, 0), 0); // Should be default initialized
166	}
167
168	#[test]
169	fn test_multiple_operators_isolated() {
170		let mut txn = create_test_transaction();
171		let mut txn = FlowTransaction::deferred(
172			&mut txn,
173			CommitVersion(1),
174			Catalog::testing(),
175			Interceptors::new(),
176			Clock::Mock(MockClock::from_millis(1000)),
177		);
178		let operator1 = TestOperator::simple(FlowNodeId(1));
179		let operator2 = TestOperator::simple(FlowNodeId(2));
180
181		// Set different states for each operator
182		operator1
183			.update_state(&mut txn, |shape, row| {
184				shape.set_i64(row, 0, 0x11);
185				Ok(())
186			})
187			.unwrap();
188
189		operator2
190			.update_state(&mut txn, |shape, row| {
191				shape.set_i64(row, 0, 0x22);
192				Ok(())
193			})
194			.unwrap();
195
196		// Verify each operator has its own state
197		let state1 = operator1.load_state(&mut txn).unwrap();
198		let state2 = operator2.load_state(&mut txn).unwrap();
199
200		let layout1 = operator1.layout();
201		let layout2 = operator2.layout();
202		assert_eq!(layout1.get_i64(&state1, 0), 0x11);
203		assert_eq!(layout2.get_i64(&state2, 0), 0x22);
204	}
205
206	#[test]
207	fn test_counter_simulation() {
208		let mut txn = create_test_transaction();
209		let mut txn = FlowTransaction::deferred(
210			&mut txn,
211			CommitVersion(1),
212			Catalog::testing(),
213			Interceptors::new(),
214			Clock::Mock(MockClock::from_millis(1000)),
215		);
216		let operator = TestOperator::new(FlowNodeId(1));
217
218		// Simulate a counter incrementing
219		for i in 1..=5 {
220			operator.update_state(&mut txn, |shape, row| {
221				// Assuming first field is an int8 counter
222				let current = shape.get_i64(row, 0);
223				shape.set_i64(row, 0, current + 1);
224				Ok(())
225			})
226			.unwrap();
227
228			let state = operator.load_state(&mut txn).unwrap();
229			let layout = operator.layout();
230			assert_eq!(layout.get_i64(&state, 0), i);
231		}
232	}
233}