1use std::collections::HashMap;
5
6use reifydb_codec::{
7 encoded::{
8 row::{EncodedRow, SHAPE_HEADER_SIZE},
9 shape::RowShape,
10 },
11 key::encoded::{EncodedKey, EncodedKeyRange},
12};
13use reifydb_core::{
14 interface::{
15 catalog::flow::FlowNodeId,
16 store::{MultiVersionBatch, MultiVersionRow},
17 },
18 key::{EncodableKey, flow_node_internal_state::FlowNodeInternalStateKey, flow_node_state::FlowNodeStateKey},
19};
20use reifydb_transaction::multi::RangeScope;
21use reifydb_value::Result;
22use tracing::{Span, field, instrument};
23
24use super::FlowTransaction;
25
26#[derive(Clone, Copy)]
27enum StateScope {
28 Public,
29 Internal,
30}
31
32impl StateScope {
33 fn encode(self, id: FlowNodeId, key: &EncodedKey) -> EncodedKey {
34 match self {
35 StateScope::Public => FlowNodeStateKey::new(id, key.as_ref().to_vec()).encode(),
36 StateScope::Internal => FlowNodeInternalStateKey::new(id, key.as_ref().to_vec()).encode(),
37 }
38 }
39}
40
41impl FlowTransaction {
42 #[instrument(name = "flow::state::get", level = "trace", skip(self), fields(
43 node_id = id.0,
44 key_len = key.as_bytes().len(),
45 found = field::Empty
46 ))]
47 pub fn state_get(&mut self, id: FlowNodeId, key: &EncodedKey) -> Result<Option<EncodedRow>> {
48 let result = self.scoped_get(StateScope::Public, id, key)?;
49 Span::current().record("found", result.is_some());
50 Ok(result)
51 }
52
53 #[instrument(name = "flow::state::get_many", level = "debug", skip(self, keys), fields(
54 node_id = id.0,
55 key_count = keys.len(),
56 found_count = field::Empty
57 ))]
58 pub fn state_get_many(&mut self, id: FlowNodeId, keys: &[EncodedKey]) -> Result<MultiVersionBatch> {
59 let batch = self.scoped_get_many(StateScope::Public, id, keys)?;
60 Span::current().record("found_count", batch.items.len());
61 Ok(batch)
62 }
63
64 #[instrument(name = "flow::state::prefetch", level = "debug", skip(self, keys), fields(node_id = id.0, key_count = keys.len()))]
65 pub fn prefetch_state(&mut self, id: FlowNodeId, keys: &[EncodedKey]) -> Result<()> {
66 if keys.is_empty() {
67 return Ok(());
68 }
69
70 let batch = self.state_get_many(id, keys)?;
71 let mut found: HashMap<EncodedKey, EncodedRow> = HashMap::with_capacity(batch.items.len());
72 for item in batch.items {
73 found.insert(item.key, item.row);
74 }
75
76 let inner = self.inner_mut();
77 for key in keys {
78 let encoded_key = StateScope::Public.encode(id, key);
79 let value = found.get(&encoded_key).cloned();
80 inner.prefetch.insert(encoded_key, value);
81 }
82 Ok(())
83 }
84
85 #[instrument(name = "flow::state::set", level = "trace", skip(self, value), fields(
86 node_id = id.0,
87 key_len = key.as_bytes().len(),
88 value_len = value.len()
89 ))]
90 pub fn state_set(&mut self, id: FlowNodeId, key: &EncodedKey, value: EncodedRow) -> Result<()> {
91 self.scoped_set(StateScope::Public, id, key, value)
92 }
93
94 #[instrument(name = "flow::state::remove", level = "trace", skip(self), fields(
95 node_id = id.0,
96 key_len = key.as_bytes().len()
97 ))]
98 pub fn state_remove(&mut self, id: FlowNodeId, key: &EncodedKey) -> Result<()> {
99 self.scoped_remove(StateScope::Public, id, key)
100 }
101
102 #[instrument(name = "flow::state::drop", level = "trace", skip(self), fields(
103 node_id = id.0,
104 key_len = key.as_bytes().len()
105 ))]
106 pub fn state_drop(&mut self, id: FlowNodeId, key: &EncodedKey) -> Result<()> {
107 self.scoped_drop(StateScope::Public, id, key)
108 }
109
110 #[instrument(name = "flow::internal_state::get", level = "trace", skip(self), fields(
111 node_id = id.0,
112 key_len = key.as_bytes().len(),
113 found = field::Empty
114 ))]
115 pub fn internal_state_get(&mut self, id: FlowNodeId, key: &EncodedKey) -> Result<Option<EncodedRow>> {
116 let result = self.scoped_get(StateScope::Internal, id, key)?;
117 Span::current().record("found", result.is_some());
118 Ok(result)
119 }
120
121 #[instrument(name = "flow::internal_state::get_many", level = "debug", skip(self, keys), fields(
122 node_id = id.0,
123 key_count = keys.len(),
124 found_count = field::Empty
125 ))]
126 pub fn internal_state_get_many(&mut self, id: FlowNodeId, keys: &[EncodedKey]) -> Result<MultiVersionBatch> {
127 let batch = self.scoped_get_many(StateScope::Internal, id, keys)?;
128 Span::current().record("found_count", batch.items.len());
129 Ok(batch)
130 }
131
132 #[instrument(name = "flow::internal_state::set", level = "trace", skip(self, value), fields(
133 node_id = id.0,
134 key_len = key.as_bytes().len(),
135 value_len = value.len()
136 ))]
137 pub fn internal_state_set(&mut self, id: FlowNodeId, key: &EncodedKey, value: EncodedRow) -> Result<()> {
138 self.scoped_set(StateScope::Internal, id, key, value)
139 }
140
141 #[instrument(name = "flow::internal_state::remove", level = "trace", skip(self), fields(
142 node_id = id.0,
143 key_len = key.as_bytes().len()
144 ))]
145 pub fn internal_state_remove(&mut self, id: FlowNodeId, key: &EncodedKey) -> Result<()> {
146 self.scoped_remove(StateScope::Internal, id, key)
147 }
148
149 #[instrument(name = "flow::internal_state::drop", level = "trace", skip(self), fields(
150 node_id = id.0,
151 key_len = key.as_bytes().len()
152 ))]
153 pub fn internal_state_drop(&mut self, id: FlowNodeId, key: &EncodedKey) -> Result<()> {
154 self.scoped_drop(StateScope::Internal, id, key)
155 }
156
157 #[instrument(name = "flow::state::scan", level = "debug", skip(self), fields(
158 node_id = id.0,
159 result_count = field::Empty
160 ))]
161 pub fn state_scan_all(&mut self, id: FlowNodeId) -> Result<MultiVersionBatch> {
162 let range = FlowNodeStateKey::node_range(id);
163 let iter = self.range(range, RangeScope::All, 1024);
164 let mut items = Vec::new();
165 for result in iter {
166 items.push(result?);
167 }
168 Span::current().record("result_count", items.len());
169 Ok(MultiVersionBatch {
170 items,
171 has_more: false,
172 })
173 }
174
175 #[instrument(name = "flow::state::range", level = "debug", skip(self, range), fields(
176 node_id = id.0
177 ))]
178 pub fn state_range_all(&mut self, id: FlowNodeId, range: EncodedKeyRange) -> Result<MultiVersionBatch> {
179 let prefixed_range = range.with_prefix(FlowNodeStateKey::encoded(id, vec![]));
180 let iter = self.range(prefixed_range, RangeScope::All, 1024);
181 let mut items = Vec::new();
182 for result in iter {
183 items.push(result?);
184 }
185 Ok(MultiVersionBatch {
186 items,
187 has_more: false,
188 })
189 }
190
191 #[instrument(name = "flow::internal_state::range", level = "debug", skip(self, range), fields(
192 node_id = id.0
193 ))]
194 pub fn internal_state_range_all(
195 &mut self,
196 id: FlowNodeId,
197 range: EncodedKeyRange,
198 ) -> Result<MultiVersionBatch> {
199 let prefixed_range = range.with_prefix(FlowNodeInternalStateKey::encoded(id, vec![]));
200 let iter = self.range(prefixed_range, RangeScope::All, 1024);
201 let mut items = Vec::new();
202 for result in iter {
203 items.push(result?);
204 }
205 Ok(MultiVersionBatch {
206 items,
207 has_more: false,
208 })
209 }
210
211 #[instrument(name = "flow::state::clear", level = "trace", skip(self), fields(
212 node_id = id.0,
213 keys_removed = field::Empty
214 ))]
215 pub fn state_clear(&mut self, id: FlowNodeId) -> Result<()> {
216 let keys_to_remove = self.scan_keys_for_clear(id)?;
217
218 let count = keys_to_remove.len();
219 self.remove_keys(keys_to_remove)?;
220
221 Span::current().record("keys_removed", count);
222 Ok(())
223 }
224
225 #[inline]
226 #[instrument(name = "flow::state::clear::scan", level = "trace", skip(self), fields(node_id = id.0))]
227 fn scan_keys_for_clear(&mut self, id: FlowNodeId) -> Result<Vec<EncodedKey>> {
228 let range = FlowNodeStateKey::node_range(id);
229 let iter = self.range(range, RangeScope::All, 1024);
230 let mut keys = Vec::new();
231 for result in iter {
232 let multi = result?;
233 keys.push(multi.key);
234 }
235 Ok(keys)
236 }
237
238 #[inline]
239 #[instrument(name = "flow::state::clear::remove", level = "trace", skip(self, keys), fields(count = keys.len()))]
240 fn remove_keys(&mut self, keys: Vec<EncodedKey>) -> Result<()> {
241 for key in keys {
242 self.remove(&key)?;
243 }
244 Ok(())
245 }
246
247 #[instrument(name = "flow::state::load_or_create", level = "debug", skip(self, shape), fields(
248 node_id = id.0,
249 key_len = key.as_bytes().len(),
250 created
251 ))]
252 pub fn load_or_create_row(&mut self, id: FlowNodeId, key: &EncodedKey, shape: &RowShape) -> Result<EncodedRow> {
253 match self.state_get(id, key)? {
254 Some(row) => {
255 Span::current().record("created", false);
256 Ok(row)
257 }
258 None => {
259 Span::current().record("created", true);
260 Ok(shape.allocate())
261 }
262 }
263 }
264
265 #[instrument(name = "flow::state::save", level = "trace", skip(self, row), fields(
266 node_id = id.0,
267 key_len = key.as_bytes().len()
268 ))]
269 pub fn save_row(&mut self, id: FlowNodeId, key: &EncodedKey, row: EncodedRow) -> Result<()> {
270 self.state_set(id, key, row)
271 }
272
273 fn scoped_get(&mut self, scope: StateScope, id: FlowNodeId, key: &EncodedKey) -> Result<Option<EncodedRow>> {
274 let encoded_key = scope.encode(id, key);
275 self.get(&encoded_key)
276 }
277
278 fn scoped_get_many(
279 &mut self,
280 scope: StateScope,
281 id: FlowNodeId,
282 keys: &[EncodedKey],
283 ) -> Result<MultiVersionBatch> {
284 let version = self.version();
285 let encoded: Vec<EncodedKey> = keys.iter().map(|key| scope.encode(id, key)).collect();
286
287 let mut items: Vec<MultiVersionRow> = Vec::new();
288 let mut to_batch: Vec<EncodedKey> = Vec::new();
289
290 for encoded_key in &encoded {
291 match self.lookup_overlays(encoded_key) {
292 Some(None) => continue,
293 Some(Some(row)) => items.push(MultiVersionRow {
294 key: encoded_key.clone(),
295 row,
296 version,
297 }),
298 None => to_batch.push(encoded_key.clone()),
299 }
300 }
301
302 self.fetch_external(&to_batch, &mut items)?;
303
304 Ok(MultiVersionBatch {
305 items,
306 has_more: false,
307 })
308 }
309
310 #[inline]
311 fn lookup_overlays(&self, encoded_key: &EncodedKey) -> Option<Option<EncodedRow>> {
312 let pending = {
313 let inner = self.inner();
314 if inner.pending.is_removed(encoded_key) {
315 Some(None)
316 } else {
317 inner.pending.get(encoded_key).map(|row| Some(row.clone()))
318 }
319 };
320 if pending.is_some() {
321 return pending;
322 }
323
324 if let Self::Transactional {
325 base_pending,
326 ..
327 } = self
328 {
329 if base_pending.is_removed(encoded_key) {
330 Some(None)
331 } else {
332 base_pending.get(encoded_key).map(|row| Some(row.clone()))
333 }
334 } else {
335 None
336 }
337 }
338
339 #[inline]
340 fn fetch_external(&mut self, to_batch: &[EncodedKey], items: &mut Vec<MultiVersionRow>) -> Result<()> {
341 if to_batch.is_empty() {
342 return Ok(());
343 }
344
345 if let Self::Ephemeral {
346 inner,
347 state,
348 } = self
349 {
350 let version = inner.version;
351 for encoded_key in to_batch {
352 if let Some(row) = state.get(encoded_key) {
353 items.push(MultiVersionRow {
354 key: encoded_key.clone(),
355 row: row.clone(),
356 version,
357 });
358 }
359 }
360 } else {
361 let inner = self.inner_mut();
362 let found = inner.state_query.as_ref().unwrap().get_many(to_batch)?;
363 for encoded_key in to_batch {
364 if let Some(multi) = found.get(encoded_key) {
365 items.push(multi.clone());
366 }
367 }
368 }
369
370 Ok(())
371 }
372
373 fn scoped_set(
374 &mut self,
375 scope: StateScope,
376 id: FlowNodeId,
377 key: &EncodedKey,
378 mut value: EncodedRow,
379 ) -> Result<()> {
380 let encoded_key = scope.encode(id, key);
381
382 if value.len() >= SHAPE_HEADER_SIZE
383 && let Some(prior) = self.get(&encoded_key)?
384 && prior.len() >= SHAPE_HEADER_SIZE
385 {
386 let prior_created = prior.created_at_nanos();
387 if prior_created != 0 {
388 let updated = value.updated_at_nanos();
389 value.set_timestamps(prior_created, updated);
390 }
391 }
392
393 self.set(&encoded_key, value)
394 }
395
396 fn scoped_remove(&mut self, scope: StateScope, id: FlowNodeId, key: &EncodedKey) -> Result<()> {
397 let encoded_key = scope.encode(id, key);
398 self.remove(&encoded_key)
399 }
400
401 fn scoped_drop(&mut self, scope: StateScope, id: FlowNodeId, key: &EncodedKey) -> Result<()> {
402 let encoded_key = scope.encode(id, key);
403 self.drop_key(&encoded_key)
404 }
405}
406
407#[cfg(test)]
408pub mod tests {
409 use std::{collections::Bound, sync::Arc};
410
411 use reifydb_catalog::catalog::Catalog;
412 use reifydb_codec::{
413 encoded::{row::EncodedRow, shape::RowShape},
414 key::encoded::{EncodedKey, EncodedKeyRange},
415 };
416 use reifydb_core::{actors::pending::Pending, common::CommitVersion, interface::catalog::flow::FlowNodeId};
417 use reifydb_engine::test_harness::TestEngine;
418 use reifydb_runtime::context::clock::{Clock, MockClock};
419 use reifydb_transaction::interceptor::interceptors::Interceptors;
420 use reifydb_value::{
421 util::cowvec::CowVec,
422 value::{identity::IdentityId, value_type::ValueType},
423 };
424
425 use super::*;
426 use crate::{
427 operator::stateful::test_utils::test::create_test_transaction,
428 transaction::{CommittingParams, DeferredParams, TransactionalParams, allocators::FlowAllocators},
429 };
430
431 fn commit_state_row(engine: &TestEngine, node: FlowNodeId, key: &EncodedKey, row: EncodedRow) -> CommitVersion {
432 let mut cmd = engine.begin_command(IdentityId::system()).unwrap();
433 cmd.disable_conflict_tracking().unwrap();
434 cmd.set(&FlowNodeStateKey::new(node, key.as_ref().to_vec()).encode(), row).unwrap();
435 cmd.commit_unchecked().unwrap()
436 }
437
438 fn make_key(s: &str) -> EncodedKey {
439 EncodedKey::new(s.as_bytes().to_vec())
440 }
441
442 fn make_value(s: &str) -> EncodedRow {
443 EncodedRow(CowVec::new(s.as_bytes().to_vec()))
444 }
445
446 #[test]
447 fn test_state_get_set() {
448 let parent = create_test_transaction();
449 let mut txn = FlowTransaction::deferred(
450 &parent,
451 CommitVersion(1),
452 Catalog::testing(),
453 Interceptors::new(),
454 Clock::Mock(MockClock::from_millis(1000)),
455 );
456
457 let node_id = FlowNodeId(1);
458 let key = make_key("state_key");
459 let value = make_value("state_value");
460
461 txn.state_set(node_id, &key, value.clone()).unwrap();
463
464 let result = txn.state_get(node_id, &key).unwrap();
466 assert_eq!(result, Some(value));
467 }
468
469 #[test]
470 fn test_internal_state_get_many() {
471 let parent = create_test_transaction();
472 let mut txn = FlowTransaction::deferred(
473 &parent,
474 CommitVersion(1),
475 Catalog::testing(),
476 Interceptors::new(),
477 Clock::Mock(MockClock::from_millis(1000)),
478 );
479
480 let node_id = FlowNodeId(1);
481 txn.internal_state_set(node_id, &make_key("a"), make_value("1")).unwrap();
482 txn.internal_state_set(node_id, &make_key("b"), make_value("2")).unwrap();
483
484 txn.state_set(node_id, &make_key("a"), make_value("data")).unwrap();
487
488 let batch = txn
489 .internal_state_get_many(node_id, &[make_key("a"), make_key("b"), make_key("missing")])
490 .unwrap();
491
492 assert_eq!(batch.items.len(), 2);
494 let mut decoded: Vec<(Vec<u8>, EncodedRow)> = batch
495 .items
496 .iter()
497 .map(|item| (FlowNodeInternalStateKey::decode(&item.key).unwrap().key, item.row.clone()))
498 .collect();
499 decoded.sort_by(|a, b| a.0.cmp(&b.0));
500 assert_eq!(decoded[0], (b"a".to_vec(), make_value("1")));
501 assert_eq!(decoded[1], (b"b".to_vec(), make_value("2")));
502 }
503
504 #[test]
505 fn test_state_get_nonexistent() {
506 let parent = create_test_transaction();
507 let mut txn = FlowTransaction::deferred(
508 &parent,
509 CommitVersion(1),
510 Catalog::testing(),
511 Interceptors::new(),
512 Clock::Mock(MockClock::from_millis(1000)),
513 );
514
515 let node_id = FlowNodeId(1);
516 let key = make_key("missing");
517
518 let result = txn.state_get(node_id, &key).unwrap();
519 assert_eq!(result, None);
520 }
521
522 #[test]
523 fn test_state_remove() {
524 let parent = create_test_transaction();
525 let mut txn = FlowTransaction::deferred(
526 &parent,
527 CommitVersion(1),
528 Catalog::testing(),
529 Interceptors::new(),
530 Clock::Mock(MockClock::from_millis(1000)),
531 );
532
533 let node_id = FlowNodeId(1);
534 let key = make_key("state_key");
535 let value = make_value("state_value");
536
537 txn.state_set(node_id, &key, value.clone()).unwrap();
539 assert_eq!(txn.state_get(node_id, &key).unwrap(), Some(value));
540
541 txn.state_remove(node_id, &key).unwrap();
542 assert_eq!(txn.state_get(node_id, &key).unwrap(), None);
543 }
544
545 #[test]
546 fn test_state_isolation_between_nodes() {
547 let parent = create_test_transaction();
548 let mut txn = FlowTransaction::deferred(
549 &parent,
550 CommitVersion(1),
551 Catalog::testing(),
552 Interceptors::new(),
553 Clock::Mock(MockClock::from_millis(1000)),
554 );
555
556 let node1 = FlowNodeId(1);
557 let node2 = FlowNodeId(2);
558 let key = make_key("same_key");
559
560 txn.state_set(node1, &key, make_value("node1_value")).unwrap();
561 txn.state_set(node2, &key, make_value("node2_value")).unwrap();
562
563 assert_eq!(txn.state_get(node1, &key).unwrap(), Some(make_value("node1_value")));
565 assert_eq!(txn.state_get(node2, &key).unwrap(), Some(make_value("node2_value")));
566 }
567
568 #[test]
569 fn test_state_scan_all() {
570 let parent = create_test_transaction();
571 let mut txn = FlowTransaction::deferred(
572 &parent,
573 CommitVersion(1),
574 Catalog::testing(),
575 Interceptors::new(),
576 Clock::Mock(MockClock::from_millis(1000)),
577 );
578
579 let node_id = FlowNodeId(1);
580
581 txn.state_set(node_id, &make_key("key1"), make_value("value1")).unwrap();
582 txn.state_set(node_id, &make_key("key2"), make_value("value2")).unwrap();
583 txn.state_set(node_id, &make_key("key3"), make_value("value3")).unwrap();
584
585 let iter = txn.state_scan_all(node_id).unwrap();
586 let items: Vec<_> = iter.items.into_iter().collect();
587
588 assert_eq!(items.len(), 3);
589 }
590
591 #[test]
592 fn test_state_scan_only_own_node() {
593 let parent = create_test_transaction();
594 let mut txn = FlowTransaction::deferred(
595 &parent,
596 CommitVersion(1),
597 Catalog::testing(),
598 Interceptors::new(),
599 Clock::Mock(MockClock::from_millis(1000)),
600 );
601
602 let node1 = FlowNodeId(1);
603 let node2 = FlowNodeId(2);
604
605 txn.state_set(node1, &make_key("key1"), make_value("value1")).unwrap();
606 txn.state_set(node1, &make_key("key2"), make_value("value2")).unwrap();
607 txn.state_set(node2, &make_key("key3"), make_value("value3")).unwrap();
608
609 let items: Vec<_> = txn.state_scan_all(node1).unwrap().items.into_iter().collect();
611 assert_eq!(items.len(), 2);
612
613 let items: Vec<_> = txn.state_scan_all(node2).unwrap().items.into_iter().collect();
615 assert_eq!(items.len(), 1);
616 }
617
618 #[test]
619 fn test_state_scan_empty() {
620 let parent = create_test_transaction();
621 let mut txn = FlowTransaction::deferred(
622 &parent,
623 CommitVersion(1),
624 Catalog::testing(),
625 Interceptors::new(),
626 Clock::Mock(MockClock::from_millis(1000)),
627 );
628
629 let node_id = FlowNodeId(1);
630
631 let iter = txn.state_scan_all(node_id).unwrap();
632 assert!(iter.items.into_iter().next().is_none());
633 }
634
635 #[test]
636 fn test_state_range_all() {
637 let parent = create_test_transaction();
638 let mut txn = FlowTransaction::deferred(
639 &parent,
640 CommitVersion(1),
641 Catalog::testing(),
642 Interceptors::new(),
643 Clock::Mock(MockClock::from_millis(1000)),
644 );
645
646 let node_id = FlowNodeId(1);
647
648 txn.state_set(node_id, &make_key("a"), make_value("1")).unwrap();
649 txn.state_set(node_id, &make_key("b"), make_value("2")).unwrap();
650 txn.state_set(node_id, &make_key("c"), make_value("3")).unwrap();
651 txn.state_set(node_id, &make_key("d"), make_value("4")).unwrap();
652
653 let range = EncodedKeyRange::new(Bound::Included(make_key("b")), Bound::Excluded(make_key("d")));
655 let iter = txn.state_range_all(node_id, range).unwrap();
656 let items: Vec<_> = iter.items.into_iter().collect();
657
658 assert_eq!(items.len(), 2);
660 }
661
662 #[test]
663 fn test_state_clear() {
664 let parent = create_test_transaction();
665 let mut txn = FlowTransaction::deferred(
666 &parent,
667 CommitVersion(1),
668 Catalog::testing(),
669 Interceptors::new(),
670 Clock::Mock(MockClock::from_millis(1000)),
671 );
672
673 let node_id = FlowNodeId(1);
674
675 txn.state_set(node_id, &make_key("key1"), make_value("value1")).unwrap();
676 txn.state_set(node_id, &make_key("key2"), make_value("value2")).unwrap();
677 txn.state_set(node_id, &make_key("key3"), make_value("value3")).unwrap();
678
679 assert_eq!(txn.state_scan_all(node_id).unwrap().items.into_iter().count(), 3);
681
682 txn.state_clear(node_id).unwrap();
684
685 assert_eq!(txn.state_scan_all(node_id).unwrap().items.into_iter().count(), 0);
687 }
688
689 #[test]
690 fn test_state_clear_only_own_node() {
691 let parent = create_test_transaction();
692 let mut txn = FlowTransaction::deferred(
693 &parent,
694 CommitVersion(1),
695 Catalog::testing(),
696 Interceptors::new(),
697 Clock::Mock(MockClock::from_millis(1000)),
698 );
699
700 let node1 = FlowNodeId(1);
701 let node2 = FlowNodeId(2);
702
703 txn.state_set(node1, &make_key("key1"), make_value("value1")).unwrap();
704 txn.state_set(node1, &make_key("key2"), make_value("value2")).unwrap();
705 txn.state_set(node2, &make_key("key3"), make_value("value3")).unwrap();
706
707 txn.state_clear(node1).unwrap();
709
710 assert_eq!(txn.state_scan_all(node1).unwrap().items.into_iter().count(), 0);
712
713 assert_eq!(txn.state_scan_all(node2).unwrap().items.into_iter().count(), 1);
715 }
716
717 #[test]
718 fn test_state_clear_empty_node() {
719 let parent = create_test_transaction();
720 let mut txn = FlowTransaction::deferred(
721 &parent,
722 CommitVersion(1),
723 Catalog::testing(),
724 Interceptors::new(),
725 Clock::Mock(MockClock::from_millis(1000)),
726 );
727
728 let node_id = FlowNodeId(1);
729
730 txn.state_clear(node_id).unwrap();
732 }
733
734 #[test]
735 fn test_load_or_create_existing() {
736 let parent = create_test_transaction();
737 let mut txn = FlowTransaction::deferred(
738 &parent,
739 CommitVersion(1),
740 Catalog::testing(),
741 Interceptors::new(),
742 Clock::Mock(MockClock::from_millis(1000)),
743 );
744
745 let node_id = FlowNodeId(1);
746 let key = make_key("key1");
747 let value = make_value("existing");
748 let shape = RowShape::testing(&[ValueType::Int8, ValueType::Float8]);
749
750 txn.state_set(node_id, &key, value.clone()).unwrap();
752
753 let result = txn.load_or_create_row(node_id, &key, &shape).unwrap();
755 assert_eq!(result, value);
756 }
757
758 #[test]
759 fn test_load_or_create_new() {
760 let parent = create_test_transaction();
761 let mut txn = FlowTransaction::deferred(
762 &parent,
763 CommitVersion(1),
764 Catalog::testing(),
765 Interceptors::new(),
766 Clock::Mock(MockClock::from_millis(1000)),
767 );
768
769 let node_id = FlowNodeId(1);
770 let key = make_key("key1");
771 let shape = RowShape::testing(&[ValueType::Int8, ValueType::Float8]);
772
773 let result = txn.load_or_create_row(node_id, &key, &shape).unwrap();
775
776 assert!(!result.is_empty());
778 }
779
780 #[test]
781 fn test_save_row() {
782 let parent = create_test_transaction();
783 let mut txn = FlowTransaction::deferred(
784 &parent,
785 CommitVersion(1),
786 Catalog::testing(),
787 Interceptors::new(),
788 Clock::Mock(MockClock::from_millis(1000)),
789 );
790
791 let node_id = FlowNodeId(1);
792 let key = make_key("key1");
793 let row = make_value("row_data");
794
795 txn.save_row(node_id, &key, row.clone()).unwrap();
796
797 let result = txn.state_get(node_id, &key).unwrap();
799 assert_eq!(result, Some(row));
800 }
801
802 #[test]
803 fn test_state_multiple_nodes() {
804 let parent = create_test_transaction();
805 let mut txn = FlowTransaction::deferred(
806 &parent,
807 CommitVersion(1),
808 Catalog::testing(),
809 Interceptors::new(),
810 Clock::Mock(MockClock::from_millis(1000)),
811 );
812
813 let node1 = FlowNodeId(1);
814 let node2 = FlowNodeId(2);
815 let node3 = FlowNodeId(3);
816
817 txn.state_set(node1, &make_key("a"), make_value("n1_a")).unwrap();
818 txn.state_set(node1, &make_key("b"), make_value("n1_b")).unwrap();
819 txn.state_set(node2, &make_key("a"), make_value("n2_a")).unwrap();
820 txn.state_set(node3, &make_key("c"), make_value("n3_c")).unwrap();
821
822 assert_eq!(txn.state_get(node1, &make_key("a")).unwrap(), Some(make_value("n1_a")));
824 assert_eq!(txn.state_get(node1, &make_key("b")).unwrap(), Some(make_value("n1_b")));
825 assert_eq!(txn.state_get(node2, &make_key("a")).unwrap(), Some(make_value("n2_a")));
826 assert_eq!(txn.state_get(node3, &make_key("c")).unwrap(), Some(make_value("n3_c")));
827
828 assert_eq!(txn.state_get(node2, &make_key("b")).unwrap(), None);
830 assert_eq!(txn.state_get(node3, &make_key("a")).unwrap(), None);
831 }
832
833 #[test]
834 fn deferred_read_sees_state_committed_above_primitive_version() {
835 let engine = TestEngine::new();
844 let node_id = FlowNodeId(1);
845 let inner_key = make_key("late_right_side");
846 let value = make_value("matched_row");
847
848 let primitive_version = commit_state_row(&engine, node_id, &make_key("warmup_a"), make_value("a"));
852 commit_state_row(&engine, node_id, &make_key("warmup_b"), make_value("b"));
853 let committed_at = commit_state_row(&engine, node_id, &inner_key, value.clone());
854 assert!(
855 committed_at.0 >= primitive_version.0 + 2,
856 "operator state must commit at least two versions above the primitive version: committed_at={committed_at:?} primitive_version={primitive_version:?}"
857 );
858
859 let (state_version, lease) = engine.acquire_current_snapshot_lease().unwrap();
860 assert!(state_version >= committed_at);
861
862 let query = engine.multi().begin_query_at_version(&lease).unwrap();
863 let state_query = engine.multi().begin_query_at_version(&lease).unwrap();
864 let mut txn = FlowTransaction::deferred_from_parts(DeferredParams {
865 version: primitive_version,
866 pending: Pending::new(),
867 query,
868 state_query,
869 dictionary_query: None,
870 single: engine.single().clone(),
871 catalog: Catalog::testing(),
872 interceptors: engine.create_interceptors(),
873 clock: engine.clock().clone(),
874 allocators: FlowAllocators::new(),
875 });
876
877 let batch = txn.state_get_many(node_id, &[inner_key]).unwrap();
878 assert_eq!(
879 batch.items.len(),
880 1,
881 "operator state committed at {committed_at:?} (above primitive_version {primitive_version:?}) must be visible to a deferred read"
882 );
883 assert_eq!(batch.items[0].row, value);
884 }
885
886 #[test]
887 fn committing_persists_state_writes_and_keeps_prior_state() {
888 let engine = TestEngine::new();
894 let node_id = FlowNodeId(1);
895 let prior_key = make_key("prior");
896 let prior_value = make_value("prior_value");
897 commit_state_row(&engine, node_id, &prior_key, prior_value.clone());
898
899 let written_key = make_key("written_by_tick");
900 let written_value = make_value("tick_value");
901 {
902 let cmd = engine.begin_command(IdentityId::system()).unwrap();
903 let mut txn = FlowTransaction::committing(CommittingParams {
904 cmd,
905 catalog: Catalog::testing(),
906 interceptors: engine.create_interceptors(),
907 clock: engine.clock().clone(),
908 allocators: FlowAllocators::new(),
909 })
910 .unwrap();
911 txn.state_set(node_id, &written_key, written_value.clone()).unwrap();
912 txn.commit().unwrap();
913 }
914
915 let (_version, lease) = engine.acquire_current_snapshot_lease().unwrap();
918 let query = engine.multi().begin_query_at_version(&lease).unwrap();
919 let prior_encoded = FlowNodeStateKey::new(node_id, prior_key.as_ref().to_vec()).encode();
920 let written_encoded = FlowNodeStateKey::new(node_id, written_key.as_ref().to_vec()).encode();
921 let found = query.get_many(&[prior_encoded.clone(), written_encoded.clone()]).unwrap();
922 assert_eq!(
923 found.len(),
924 2,
925 "the committing flow's write and the prior committed state must both be durable after commit"
926 );
927 assert_eq!(found.get(&prior_encoded).unwrap().row, prior_value);
928 assert_eq!(found.get(&written_encoded).unwrap().row, written_value);
929 }
930
931 #[test]
932 fn transactional_read_sees_committed_state_below_version_and_base_pending() {
933 let engine = TestEngine::new();
940 let node_id = FlowNodeId(1);
941 let committed_key = make_key("committed");
942 let committed_value = make_value("committed_value");
943
944 let low_version = commit_state_row(&engine, node_id, &make_key("warmup"), make_value("w"));
945 commit_state_row(&engine, node_id, &make_key("bump"), make_value("bump"));
946 let committed_at = commit_state_row(&engine, node_id, &committed_key, committed_value.clone());
947 assert!(
948 committed_at.0 >= low_version.0 + 2,
949 "committed state must land at least two versions above the txn version so a wrongful bound (which resolves to version + 1) would hide it: committed_at={committed_at:?} low_version={low_version:?}"
950 );
951
952 let base_key = make_key("in_flight");
953 let base_value = make_value("in_flight_value");
954 let mut base_pending = Pending::new();
955 base_pending.insert(
956 FlowNodeStateKey::new(node_id, base_key.as_ref().to_vec()).encode(),
957 base_value.clone(),
958 );
959
960 let mut txn = FlowTransaction::transactional(TransactionalParams {
961 version: low_version,
962 pending: Pending::new(),
963 base_pending,
964 query: engine.multi().begin_query().unwrap(),
965 state_query: engine.multi().begin_query().unwrap(),
966 single: engine.single().clone(),
967 catalog: Catalog::testing(),
968 interceptors: engine.create_interceptors(),
969 clock: engine.clock().clone(),
970 view_overlay: Arc::new(Vec::new()),
971 allocators: FlowAllocators::new(),
972 });
973
974 let committed = txn.state_get_many(node_id, &[committed_key]).unwrap();
976 assert_eq!(
977 committed.items.len(),
978 1,
979 "committed state at {committed_at:?} must be visible even though the txn version is {low_version:?}"
980 );
981 assert_eq!(committed.items[0].row, committed_value);
982
983 let base = txn.state_get_many(node_id, &[base_key]).unwrap();
985 assert_eq!(base.items.len(), 1);
986 assert_eq!(base.items[0].row, base_value);
987 }
988
989 #[test]
990 fn ephemeral_read_sees_state_map_and_pending() {
991 let engine = TestEngine::new();
995 let node_id = FlowNodeId(1);
996 let seeded_key = make_key("seeded");
997 let seeded_value = make_value("seeded_value");
998
999 let mut state = HashMap::new();
1000 state.insert(
1001 FlowNodeStateKey::new(node_id, seeded_key.as_ref().to_vec()).encode(),
1002 seeded_value.clone(),
1003 );
1004
1005 let mut txn = FlowTransaction::ephemeral(
1006 CommitVersion(1),
1007 engine.multi().begin_query().unwrap(),
1008 engine.single().clone(),
1009 Catalog::testing(),
1010 state,
1011 engine.clock().clone(),
1012 );
1013
1014 let seeded = txn.state_get_many(node_id, &[seeded_key]).unwrap();
1015 assert_eq!(seeded.items.len(), 1, "seeded ephemeral state must be readable");
1016 assert_eq!(seeded.items[0].row, seeded_value);
1017
1018 let live_key = make_key("live");
1020 let live_value = make_value("live_value");
1021 txn.state_set(node_id, &live_key, live_value.clone()).unwrap();
1022 let live = txn.state_get_many(node_id, &[live_key]).unwrap();
1023 assert_eq!(live.items.len(), 1);
1024 assert_eq!(live.items[0].row, live_value);
1025 }
1026}