hydro_lang/live_collections/keyed_stream/mod.rs
1//! Definitions for the [`KeyedStream`] live collection.
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::hash::Hash;
6use std::marker::PhantomData;
7use std::ops::Deref;
8use std::rc::Rc;
9
10use stageleft::{IntoQuotedMut, QuotedWithContext, QuotedWithContextWithProps, q};
11
12use super::boundedness::{Bounded, Boundedness, IsBounded, Unbounded};
13use super::keyed_singleton::KeyedSingleton;
14use super::optional::Optional;
15use super::stream::{
16 ExactlyOnce, IsExactlyOnce, IsOrdered, MinOrder, MinRetries, NoOrder, Stream, TotalOrder,
17};
18use crate::compile::builder::{CycleId, FlowState};
19use crate::compile::ir::{
20 CollectionKind, HydroIrOpMetadata, HydroNode, HydroRoot, SharedNode, StreamOrder, StreamRetry,
21};
22#[cfg(stageleft_runtime)]
23use crate::forward_handle::{CycleCollection, ReceiverComplete};
24use crate::forward_handle::{ForwardRef, TickCycle};
25use crate::live_collections::batch_atomic::BatchAtomic;
26use crate::live_collections::keyed_singleton::KeyedSingletonBound;
27use crate::live_collections::stream::{
28 AtLeastOnce, Ordering, Retries, WeakerOrderingThan, WeakerRetryThan,
29};
30#[cfg(stageleft_runtime)]
31use crate::location::dynamic::{DynLocation, LocationId};
32use crate::location::tick::DeferTick;
33use crate::location::{Atomic, Location, Tick, check_matching_location};
34use crate::manual_expr::ManualExpr;
35use crate::nondet::{NonDet, nondet};
36use crate::properties::{
37 AggFuncAlgebra, ApplyMonotoneKeyedStream, ValidCommutativityFor, ValidIdempotenceFor,
38 manual_proof,
39};
40
41pub mod networking;
42
43/// Streaming elements of type `V` grouped by a key of type `K`.
44///
45/// Keyed Streams capture streaming elements of type `V` grouped by a key of type `K`, where the
46/// order of keys is non-deterministic but the order *within* each group may be deterministic.
47///
48/// Although keyed streams are conceptually grouped by keys, values are not immediately grouped
49/// into buckets when constructing a keyed stream. Instead, keyed streams defer grouping until an
50/// operator such as [`KeyedStream::fold`] is called, which requires `K: Hash + Eq`.
51///
52/// Type Parameters:
53/// - `K`: the type of the key for each group
54/// - `V`: the type of the elements inside each group
55/// - `Loc`: the [`Location`] where the keyed stream is materialized
56/// - `Bound`: tracks whether the entries are [`Bounded`] (local and finite) or [`Unbounded`] (asynchronous and possibly infinite)
57/// - `Order`: tracks whether the elements within each group have deterministic order
58/// ([`TotalOrder`]) or not ([`NoOrder`])
59/// - `Retries`: tracks whether the elements within each group have deterministic cardinality
60/// ([`ExactlyOnce`]) or may have non-deterministic retries ([`crate::live_collections::stream::AtLeastOnce`])
61pub struct KeyedStream<
62 K,
63 V,
64 Loc,
65 Bound: Boundedness = Unbounded,
66 Order: Ordering = TotalOrder,
67 Retry: Retries = ExactlyOnce,
68> {
69 pub(crate) location: Loc,
70 pub(crate) ir_node: Rc<RefCell<HydroNode>>,
71 pub(crate) flow_state: FlowState,
72
73 _phantom: PhantomData<(K, V, Loc, Bound, Order, Retry)>,
74}
75
76impl<K, V, L, B: Boundedness, O: Ordering, R: Retries> Drop for KeyedStream<K, V, L, B, O, R> {
77 fn drop(&mut self) {
78 let ir_node = self.ir_node.replace(HydroNode::Placeholder);
79 if !matches!(ir_node, HydroNode::Placeholder) && !ir_node.is_shared_with_others() {
80 self.flow_state.borrow_mut().try_push_root(HydroRoot::Null {
81 input: Box::new(ir_node),
82 op_metadata: HydroIrOpMetadata::new(),
83 });
84 }
85 }
86}
87
88impl<'a, K, V, L, O: Ordering, R: Retries> From<KeyedStream<K, V, L, Bounded, O, R>>
89 for KeyedStream<K, V, L, Unbounded, O, R>
90where
91 L: Location<'a>,
92{
93 fn from(stream: KeyedStream<K, V, L, Bounded, O, R>) -> KeyedStream<K, V, L, Unbounded, O, R> {
94 let new_meta = stream
95 .location
96 .new_node_metadata(KeyedStream::<K, V, L, Unbounded, O, R>::collection_kind());
97
98 let flow_state = stream.flow_state.clone();
99 KeyedStream {
100 location: stream.location.clone(),
101 ir_node: crate::live_collections::tracked_ir_node(
102 &flow_state,
103 HydroNode::Cast {
104 inner: Box::new(stream.ir_node.replace(HydroNode::Placeholder)),
105 metadata: new_meta,
106 },
107 ),
108 flow_state,
109 _phantom: PhantomData,
110 }
111 }
112}
113
114impl<'a, K, V, L, B: Boundedness, R: Retries> From<KeyedStream<K, V, L, B, TotalOrder, R>>
115 for KeyedStream<K, V, L, B, NoOrder, R>
116where
117 L: Location<'a>,
118{
119 fn from(stream: KeyedStream<K, V, L, B, TotalOrder, R>) -> KeyedStream<K, V, L, B, NoOrder, R> {
120 stream.weaken_ordering()
121 }
122}
123
124impl<'a, K, V, L, O: Ordering, R: Retries> DeferTick for KeyedStream<K, V, Tick<L>, Bounded, O, R>
125where
126 L: Location<'a>,
127{
128 fn defer_tick(self) -> Self {
129 KeyedStream::defer_tick(self)
130 }
131}
132
133impl<'a, K, V, L, O: Ordering, R: Retries> CycleCollection<'a, TickCycle>
134 for KeyedStream<K, V, Tick<L>, Bounded, O, R>
135where
136 L: Location<'a>,
137{
138 type Location = Tick<L>;
139
140 fn create_source(cycle_id: CycleId, location: Tick<L>) -> Self {
141 let flow_state = location.flow_state().clone();
142 KeyedStream {
143 ir_node: crate::live_collections::tracked_ir_node(
144 &flow_state,
145 HydroNode::CycleSource {
146 cycle_id,
147 metadata: location.new_node_metadata(
148 KeyedStream::<K, V, Tick<L>, Bounded, O, R>::collection_kind(),
149 ),
150 },
151 ),
152 flow_state,
153 location,
154 _phantom: PhantomData,
155 }
156 }
157}
158
159impl<'a, K, V, L, O: Ordering, R: Retries> ReceiverComplete<'a, TickCycle>
160 for KeyedStream<K, V, Tick<L>, Bounded, O, R>
161where
162 L: Location<'a>,
163{
164 fn complete(self, cycle_id: CycleId, expected_location: LocationId) {
165 assert_eq!(
166 Location::id(&self.location),
167 expected_location,
168 "locations do not match"
169 );
170
171 self.location
172 .flow_state()
173 .borrow_mut()
174 .push_root(HydroRoot::CycleSink {
175 cycle_id,
176 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
177 op_metadata: HydroIrOpMetadata::new(),
178 });
179 }
180}
181
182impl<'a, K, V, L, B: Boundedness, O: Ordering, R: Retries> CycleCollection<'a, ForwardRef>
183 for KeyedStream<K, V, L, B, O, R>
184where
185 L: Location<'a>,
186{
187 type Location = L;
188
189 fn create_source(cycle_id: CycleId, location: L) -> Self {
190 let flow_state = location.flow_state().clone();
191 KeyedStream {
192 ir_node: crate::live_collections::tracked_ir_node(
193 &flow_state,
194 HydroNode::CycleSource {
195 cycle_id,
196 metadata: location
197 .new_node_metadata(KeyedStream::<K, V, L, B, O, R>::collection_kind()),
198 },
199 ),
200 flow_state,
201 location,
202 _phantom: PhantomData,
203 }
204 }
205}
206
207impl<'a, K, V, L, B: Boundedness, O: Ordering, R: Retries> ReceiverComplete<'a, ForwardRef>
208 for KeyedStream<K, V, L, B, O, R>
209where
210 L: Location<'a>,
211{
212 fn complete(self, cycle_id: CycleId, expected_location: LocationId) {
213 assert_eq!(
214 Location::id(&self.location),
215 expected_location,
216 "locations do not match"
217 );
218 self.location
219 .flow_state()
220 .borrow_mut()
221 .push_root(HydroRoot::CycleSink {
222 cycle_id,
223 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
224 op_metadata: HydroIrOpMetadata::new(),
225 });
226 }
227}
228
229impl<'a, K: Clone, V: Clone, Loc: Location<'a>, Bound: Boundedness, Order: Ordering, R: Retries>
230 Clone for KeyedStream<K, V, Loc, Bound, Order, R>
231{
232 fn clone(&self) -> Self {
233 if !matches!(self.ir_node.borrow().deref(), HydroNode::Tee { .. }) {
234 let orig_ir_node = self.ir_node.replace(HydroNode::Placeholder);
235 *self.ir_node.borrow_mut() = HydroNode::Tee {
236 inner: SharedNode(Rc::new(RefCell::new(orig_ir_node))),
237 metadata: self.location.new_node_metadata(Self::collection_kind()),
238 };
239 }
240
241 if let HydroNode::Tee { inner, metadata } = self.ir_node.borrow().deref() {
242 KeyedStream {
243 location: self.location.clone(),
244 flow_state: self.flow_state.clone(),
245 ir_node: crate::live_collections::tracked_ir_node(
246 &self.flow_state,
247 HydroNode::Tee {
248 inner: SharedNode(inner.0.clone()),
249 metadata: metadata.clone(),
250 },
251 ),
252 _phantom: PhantomData,
253 }
254 } else {
255 unreachable!()
256 }
257 }
258}
259
260/// The output of a Hydro generator created with [`KeyedStream::generator`], which can yield elements and
261/// control the processing of future elements.
262pub enum Generate<T> {
263 /// Emit the provided element, and keep processing future inputs.
264 Yield(T),
265 /// Emit the provided element as the _final_ element, do not process future inputs.
266 Return(T),
267 /// Do not emit anything, but continue processing future inputs.
268 Continue,
269 /// Do not emit anything, and do not process further inputs.
270 Break,
271}
272
273impl<'a, K, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
274 KeyedStream<K, V, L, B, O, R>
275{
276 pub(crate) fn new(location: L, ir_node: HydroNode) -> Self {
277 debug_assert_eq!(ir_node.metadata().location_id, Location::id(&location));
278 debug_assert_eq!(ir_node.metadata().collection_kind, Self::collection_kind());
279
280 let flow_state = location.flow_state().clone();
281 let ir_node = crate::live_collections::tracked_ir_node(&flow_state, ir_node);
282 KeyedStream {
283 location,
284 flow_state,
285 ir_node,
286 _phantom: PhantomData,
287 }
288 }
289
290 /// Returns the [`CollectionKind`] corresponding to this type.
291 pub fn collection_kind() -> CollectionKind {
292 CollectionKind::KeyedStream {
293 bound: B::BOUND_KIND,
294 value_order: O::ORDERING_KIND,
295 value_retry: R::RETRIES_KIND,
296 key_type: stageleft::quote_type::<K>().into(),
297 value_type: stageleft::quote_type::<V>().into(),
298 }
299 }
300
301 /// Returns the [`Location`] where this keyed stream is being materialized.
302 pub fn location(&self) -> &L {
303 &self.location
304 }
305
306 /// Weakens the consistency of this live collection to not guarantee any consistency across
307 /// cluster members (if this collection is on a cluster).
308 pub fn weaken_consistency(self) -> KeyedStream<K, V, L::DropConsistency, B, O, R>
309 where
310 L: Location<'a>,
311 {
312 if L::consistency()
313 .is_none_or(|c| c == crate::location::dynamic::ClusterConsistency::NoConsistency)
314 {
315 // already no consistency
316 KeyedStream::new(
317 self.location.drop_consistency(),
318 self.ir_node.replace(HydroNode::Placeholder),
319 )
320 } else {
321 KeyedStream::new(
322 self.location.drop_consistency(),
323 HydroNode::Cast {
324 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
325 metadata: self
326 .location
327 .drop_consistency()
328 .new_node_metadata(
329 KeyedStream::<K, V, L::DropConsistency, B>::collection_kind(),
330 ),
331 },
332 )
333 }
334 }
335
336 /// Casts this live collection to have the consistency guarantees specified in the given
337 /// location type parameter. The developer must ensure that the strengthened consistency
338 /// is actually guaranteed, via the proof field (see [`crate::prelude::manual_proof`]).
339 pub fn assert_has_consistency_of<L2: Location<'a, DropConsistency = L::DropConsistency>>(
340 self,
341 _proof: impl crate::properties::ConsistencyProof,
342 ) -> KeyedStream<K, V, L2, B, O, R>
343 where
344 L: Location<'a>,
345 {
346 if L::consistency() == L2::consistency() {
347 KeyedStream::new(
348 self.location.with_consistency_of(),
349 self.ir_node.replace(HydroNode::Placeholder),
350 )
351 } else {
352 KeyedStream::new(
353 self.location.with_consistency_of(),
354 HydroNode::AssertIsConsistent {
355 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
356 trusted: false,
357 metadata: self
358 .location
359 .clone()
360 .with_consistency_of::<L2>()
361 .new_node_metadata(KeyedStream::<K, V, L2, B, O, R>::collection_kind()),
362 },
363 )
364 }
365 }
366
367 pub(crate) fn assert_has_consistency_of_trusted<
368 L2: Location<'a, DropConsistency = L::DropConsistency>,
369 >(
370 self,
371 _proof: impl crate::properties::ConsistencyProof,
372 ) -> KeyedStream<K, V, L2, B, O, R>
373 where
374 L: Location<'a>,
375 {
376 if L::consistency() == L2::consistency() {
377 KeyedStream::new(
378 self.location.with_consistency_of(),
379 self.ir_node.replace(HydroNode::Placeholder),
380 )
381 } else {
382 KeyedStream::new(
383 self.location.with_consistency_of(),
384 HydroNode::AssertIsConsistent {
385 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
386 trusted: true,
387 metadata: self
388 .location
389 .clone()
390 .with_consistency_of::<L2>()
391 .new_node_metadata(KeyedStream::<K, V, L2, B, O, R>::collection_kind()),
392 },
393 )
394 }
395 }
396
397 /// Turns this [`KeyedStream`] into a [`Stream`] preserving ordering, under the invariant
398 /// assumption that there is at most one key. If this invariant is broken, the program
399 /// may exhibit undefined behavior, so uses must be carefully vetted.
400 pub(crate) fn cast_at_most_one_key(self) -> Stream<(K, V), L, B, O, R> {
401 Stream::new(
402 self.location.clone(),
403 HydroNode::Cast {
404 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
405 metadata: self
406 .location
407 .new_node_metadata(Stream::<(K, V), L, B, O, R>::collection_kind()),
408 },
409 )
410 }
411
412 /// Turns this [`KeyedStream`] into a [`KeyedSingleton`], under the invariant assumption that
413 /// there is at most one entry per key. If this invariant is broken, the program may exhibit
414 /// undefined behavior, so uses must be carefully vetted.
415 pub(crate) fn cast_at_most_one_entry_per_key(
416 self,
417 ) -> KeyedSingleton<K, V, L, B::WithBoundedValue> {
418 KeyedSingleton::new(
419 self.location.clone(),
420 HydroNode::Cast {
421 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
422 metadata: self.location.new_node_metadata(KeyedSingleton::<
423 K,
424 V,
425 L,
426 B::WithBoundedValue,
427 >::collection_kind()),
428 },
429 )
430 }
431
432 pub(crate) fn use_ordering_type<O2: Ordering>(self) -> KeyedStream<K, V, L, B, O2, R> {
433 if O::ORDERING_KIND == O2::ORDERING_KIND {
434 KeyedStream::new(
435 self.location.clone(),
436 self.ir_node.replace(HydroNode::Placeholder),
437 )
438 } else {
439 panic!(
440 "Runtime ordering {:?} did not match requested cast {:?}.",
441 O::ORDERING_KIND,
442 O2::ORDERING_KIND
443 )
444 }
445 }
446
447 /// Explicitly "casts" the keyed stream to a type with a different ordering
448 /// guarantee for each group. Useful in unsafe code where the ordering cannot be proven
449 /// by the type-system.
450 ///
451 /// # Non-Determinism
452 /// This function is used as an escape hatch, and any mistakes in the
453 /// provided ordering guarantee will propagate into the guarantees
454 /// for the rest of the program.
455 pub fn assume_ordering<O2: Ordering>(
456 self,
457 _nondet: NonDet,
458 ) -> KeyedStream<K, V, L::DropConsistency, B, O2, R> {
459 if O::ORDERING_KIND == O2::ORDERING_KIND {
460 self.use_ordering_type().weaken_consistency()
461 } else if O2::ORDERING_KIND == StreamOrder::NoOrder {
462 // We can always weaken the ordering guarantee
463 let target_location = self.location.drop_consistency();
464 KeyedStream::new(
465 target_location.clone(),
466 HydroNode::Cast {
467 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
468 metadata: target_location
469 .new_node_metadata(KeyedStream::<K, V, L, B, O2, R>::collection_kind()),
470 },
471 )
472 } else {
473 let target_location = self.location.drop_consistency();
474 KeyedStream::new(
475 target_location.clone(),
476 HydroNode::ObserveNonDet {
477 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
478 trusted: false,
479 metadata: target_location
480 .new_node_metadata(KeyedStream::<K, V, L, B, O2, R>::collection_kind()),
481 },
482 )
483 }
484 }
485
486 fn assume_ordering_trusted<O2: Ordering>(
487 self,
488 _nondet: NonDet,
489 ) -> KeyedStream<K, V, L, B, O2, R> {
490 if O::ORDERING_KIND == O2::ORDERING_KIND {
491 KeyedStream::new(
492 self.location.clone(),
493 self.ir_node.replace(HydroNode::Placeholder),
494 )
495 } else if O2::ORDERING_KIND == StreamOrder::NoOrder {
496 // We can always weaken the ordering guarantee
497 KeyedStream::new(
498 self.location.clone(),
499 HydroNode::Cast {
500 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
501 metadata: self
502 .location
503 .new_node_metadata(KeyedStream::<K, V, L, B, O2, R>::collection_kind()),
504 },
505 )
506 } else {
507 KeyedStream::new(
508 self.location.clone(),
509 HydroNode::ObserveNonDet {
510 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
511 trusted: true,
512 metadata: self
513 .location
514 .new_node_metadata(KeyedStream::<K, V, L, B, O2, R>::collection_kind()),
515 },
516 )
517 }
518 }
519
520 #[deprecated = "use `weaken_ordering::<NoOrder>()` instead"]
521 /// Weakens the ordering guarantee provided by the stream to [`NoOrder`],
522 /// which is always safe because that is the weakest possible guarantee.
523 pub fn weakest_ordering(self) -> KeyedStream<K, V, L, B, NoOrder, R> {
524 self.weaken_ordering::<NoOrder>()
525 }
526
527 /// Weakens the ordering guarantee provided by the stream to `O2`, with the type-system
528 /// enforcing that `O2` is weaker than the input ordering guarantee.
529 pub fn weaken_ordering<O2: WeakerOrderingThan<O>>(self) -> KeyedStream<K, V, L, B, O2, R> {
530 let nondet = nondet!(/** this is a weaker ordering guarantee, so it is safe to assume */);
531 self.assume_ordering_trusted::<O2>(nondet)
532 }
533
534 /// Strengthens the ordering guarantee to `TotalOrder`, given that `O: IsOrdered`, which
535 /// implies that `O == TotalOrder`.
536 pub fn make_totally_ordered(self) -> KeyedStream<K, V, L, B, TotalOrder, R>
537 where
538 O: IsOrdered,
539 {
540 self.assume_ordering_trusted(nondet!(/** no-op */))
541 }
542
543 /// Explicitly "casts" the keyed stream to a type with a different retries
544 /// guarantee for each group. Useful in unsafe code where the lack of retries cannot
545 /// be proven by the type-system.
546 ///
547 /// # Non-Determinism
548 /// This function is used as an escape hatch, and any mistakes in the
549 /// provided retries guarantee will propagate into the guarantees
550 /// for the rest of the program.
551 pub fn assume_retries<R2: Retries>(
552 self,
553 _nondet: NonDet,
554 ) -> KeyedStream<K, V, L::DropConsistency, B, O, R2> {
555 if R::RETRIES_KIND == R2::RETRIES_KIND {
556 KeyedStream::new(
557 self.location.drop_consistency(),
558 self.ir_node.replace(HydroNode::Placeholder),
559 )
560 } else if R2::RETRIES_KIND == StreamRetry::AtLeastOnce {
561 // We can always weaken the retries guarantee
562 let target_location = self.location.drop_consistency();
563 KeyedStream::new(
564 target_location.clone(),
565 HydroNode::Cast {
566 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
567 metadata: target_location
568 .new_node_metadata(KeyedStream::<K, V, L, B, O, R2>::collection_kind()),
569 },
570 )
571 } else {
572 let target_location = self.location.drop_consistency();
573 KeyedStream::new(
574 target_location.clone(),
575 HydroNode::ObserveNonDet {
576 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
577 trusted: false,
578 metadata: target_location
579 .new_node_metadata(KeyedStream::<K, V, L, B, O, R2>::collection_kind()),
580 },
581 )
582 }
583 }
584
585 // only for internal APIs that have been carefully vetted to ensure that the non-determinism
586 // is not observable
587 fn assume_retries_trusted<R2: Retries>(
588 self,
589 _nondet: NonDet,
590 ) -> KeyedStream<K, V, L, B, O, R2> {
591 if R::RETRIES_KIND == R2::RETRIES_KIND {
592 KeyedStream::new(
593 self.location.clone(),
594 self.ir_node.replace(HydroNode::Placeholder),
595 )
596 } else if R2::RETRIES_KIND == StreamRetry::AtLeastOnce {
597 // We can always weaken the retries guarantee
598 KeyedStream::new(
599 self.location.clone(),
600 HydroNode::Cast {
601 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
602 metadata: self
603 .location
604 .new_node_metadata(KeyedStream::<K, V, L, B, O, R2>::collection_kind()),
605 },
606 )
607 } else {
608 KeyedStream::new(
609 self.location.clone(),
610 HydroNode::ObserveNonDet {
611 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
612 trusted: true,
613 metadata: self
614 .location
615 .new_node_metadata(KeyedStream::<K, V, L, B, O, R2>::collection_kind()),
616 },
617 )
618 }
619 }
620
621 #[deprecated = "use `weaken_retries::<AtLeastOnce>()` instead"]
622 /// Weakens the retries guarantee provided by the stream to [`AtLeastOnce`],
623 /// which is always safe because that is the weakest possible guarantee.
624 pub fn weakest_retries(self) -> KeyedStream<K, V, L, B, O, AtLeastOnce> {
625 self.weaken_retries::<AtLeastOnce>()
626 }
627
628 /// Weakens the retries guarantee provided by the stream to `R2`, with the type-system
629 /// enforcing that `R2` is weaker than the input retries guarantee.
630 pub fn weaken_retries<R2: WeakerRetryThan<R>>(self) -> KeyedStream<K, V, L, B, O, R2> {
631 let nondet = nondet!(/** this is a weaker retries guarantee, so it is safe to assume */);
632 self.assume_retries_trusted::<R2>(nondet)
633 }
634
635 /// Strengthens the retry guarantee to `ExactlyOnce`, given that `R: IsExactlyOnce`, which
636 /// implies that `R == ExactlyOnce`.
637 pub fn make_exactly_once(self) -> KeyedStream<K, V, L, B, O, ExactlyOnce>
638 where
639 R: IsExactlyOnce,
640 {
641 self.assume_retries_trusted(nondet!(/** no-op */))
642 }
643
644 /// Strengthens the boundedness guarantee to `Bounded`, given that `B: IsBounded`, which
645 /// implies that `B == Bounded`.
646 pub fn make_bounded(self) -> KeyedStream<K, V, L, Bounded, O, R>
647 where
648 B: IsBounded,
649 {
650 self.weaken_boundedness()
651 }
652
653 /// Weakens the boundedness guarantee to an arbitrary boundedness `B2`, given that `B: IsBounded`,
654 /// which implies that `B == Bounded`.
655 pub fn weaken_boundedness<B2: Boundedness>(self) -> KeyedStream<K, V, L, B2, O, R> {
656 if B::BOUNDED == B2::BOUNDED {
657 KeyedStream::new(
658 self.location.clone(),
659 self.ir_node.replace(HydroNode::Placeholder),
660 )
661 } else {
662 // We can always weaken the boundedness
663 KeyedStream::new(
664 self.location.clone(),
665 HydroNode::Cast {
666 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
667 metadata: self
668 .location
669 .new_node_metadata(KeyedStream::<K, V, L, B2, O, R>::collection_kind()),
670 },
671 )
672 }
673 }
674
675 /// Flattens the keyed stream into an unordered stream of key-value pairs.
676 ///
677 /// # Example
678 /// ```rust
679 /// # #[cfg(feature = "deploy")] {
680 /// # use hydro_lang::prelude::*;
681 /// # use futures::StreamExt;
682 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
683 /// process
684 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
685 /// .into_keyed()
686 /// .entries()
687 /// # }, |mut stream| async move {
688 /// // (1, 2), (1, 3), (2, 4) in any order
689 /// # let mut results = Vec::new();
690 /// # for _ in 0..3 {
691 /// # results.push(stream.next().await.unwrap());
692 /// # }
693 /// # results.sort();
694 /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4)]);
695 /// # }));
696 /// # }
697 /// ```
698 pub fn entries(self) -> Stream<(K, V), L, B, NoOrder, R> {
699 Stream::new(
700 self.location.clone(),
701 HydroNode::Cast {
702 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
703 metadata: self
704 .location
705 .new_node_metadata(Stream::<(K, V), L, B, NoOrder, R>::collection_kind()),
706 },
707 )
708 }
709
710 /// Flattens the keyed stream into a totally ordered stream of key-value pairs,
711 /// preserving the order of values within each key group but non-deterministically
712 /// interleaving across keys.
713 ///
714 /// Requires the keyed stream to be totally ordered within each group (`O: IsOrdered`).
715 ///
716 /// # Non-Determinism
717 /// The interleaving of entries across different keys is non-deterministic.
718 /// Within each key, the original order is preserved.
719 pub fn entries_partially_ordered(
720 self,
721 _nondet: NonDet,
722 ) -> Stream<(K, V), L::DropConsistency, B, TotalOrder, R>
723 where
724 O: IsOrdered,
725 {
726 let target_location = self.location.drop_consistency();
727 Stream::new(
728 target_location.clone(),
729 HydroNode::ObserveNonDet {
730 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
731 trusted: false,
732 metadata: target_location
733 .new_node_metadata(Stream::<(K, V), L, B, TotalOrder, R>::collection_kind()),
734 },
735 )
736 }
737
738 /// Flattens the keyed stream into an unordered stream of only the values.
739 ///
740 /// # Example
741 /// ```rust
742 /// # #[cfg(feature = "deploy")] {
743 /// # use hydro_lang::prelude::*;
744 /// # use futures::StreamExt;
745 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
746 /// process
747 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
748 /// .into_keyed()
749 /// .values()
750 /// # }, |mut stream| async move {
751 /// // 2, 3, 4 in any order
752 /// # let mut results = Vec::new();
753 /// # for _ in 0..3 {
754 /// # results.push(stream.next().await.unwrap());
755 /// # }
756 /// # results.sort();
757 /// # assert_eq!(results, vec![2, 3, 4]);
758 /// # }));
759 /// # }
760 /// ```
761 pub fn values(self) -> Stream<V, L, B, NoOrder, R> {
762 self.entries().map(q!(|(_, v)| v))
763 }
764
765 /// Flattens the keyed stream into an unordered stream of just the keys.
766 ///
767 /// # Example
768 /// ```rust
769 /// # #[cfg(feature = "deploy")] {
770 /// # use hydro_lang::prelude::*;
771 /// # use futures::StreamExt;
772 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
773 /// # process
774 /// # .source_iter(q!(vec![(1, 2), (2, 4), (1, 5)]))
775 /// # .into_keyed()
776 /// # .keys()
777 /// # }, |mut stream| async move {
778 /// // 1, 2 in any order
779 /// # let mut results = Vec::new();
780 /// # for _ in 0..2 {
781 /// # results.push(stream.next().await.unwrap());
782 /// # }
783 /// # results.sort();
784 /// # assert_eq!(results, vec![1, 2]);
785 /// # }));
786 /// # }
787 /// ```
788 pub fn keys(self) -> Stream<K, L, B, NoOrder, ExactlyOnce>
789 where
790 K: Eq + Hash,
791 {
792 self.entries().map(q!(|(k, _)| k)).unique()
793 }
794
795 /// Transforms each value by invoking `f` on each element, with keys staying the same
796 /// after transformation. If you need access to the key, see [`KeyedStream::map_with_key`].
797 ///
798 /// If you do not want to modify the stream and instead only want to view
799 /// each item use [`KeyedStream::inspect`] instead.
800 ///
801 /// # Example
802 /// ```rust
803 /// # #[cfg(feature = "deploy")] {
804 /// # use hydro_lang::prelude::*;
805 /// # use futures::StreamExt;
806 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
807 /// process
808 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
809 /// .into_keyed()
810 /// .map(q!(|v| v + 1))
811 /// # .entries()
812 /// # }, |mut stream| async move {
813 /// // { 1: [3, 4], 2: [5] }
814 /// # let mut results = Vec::new();
815 /// # for _ in 0..3 {
816 /// # results.push(stream.next().await.unwrap());
817 /// # }
818 /// # results.sort();
819 /// # assert_eq!(results, vec![(1, 3), (1, 4), (2, 5)]);
820 /// # }));
821 /// # }
822 /// ```
823 pub fn map<U, F>(self, f: impl IntoQuotedMut<'a, F, L> + Copy) -> KeyedStream<K, U, L, B, O, R>
824 where
825 F: Fn(V) -> U + 'a,
826 {
827 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_ctx(ctx));
828 let map_f = q!({
829 let orig = f;
830 move |(k, v)| (k, orig(v))
831 })
832 .splice_fn1_ctx::<(K, V), (K, U)>(&self.location)
833 .into();
834
835 KeyedStream::new(
836 self.location.clone(),
837 HydroNode::Map {
838 f: map_f,
839 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
840 metadata: self
841 .location
842 .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
843 },
844 )
845 }
846
847 /// Transforms each value by invoking `f` on each key-value pair. The resulting values are **not**
848 /// re-grouped even they are tuples; instead they will be grouped under the original key.
849 ///
850 /// If you do not want to modify the stream and instead only want to view
851 /// each item use [`KeyedStream::inspect_with_key`] instead.
852 ///
853 /// # Example
854 /// ```rust
855 /// # #[cfg(feature = "deploy")] {
856 /// # use hydro_lang::prelude::*;
857 /// # use futures::StreamExt;
858 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
859 /// process
860 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
861 /// .into_keyed()
862 /// .map_with_key(q!(|(k, v)| k + v))
863 /// # .entries()
864 /// # }, |mut stream| async move {
865 /// // { 1: [3, 4], 2: [6] }
866 /// # let mut results = Vec::new();
867 /// # for _ in 0..3 {
868 /// # results.push(stream.next().await.unwrap());
869 /// # }
870 /// # results.sort();
871 /// # assert_eq!(results, vec![(1, 3), (1, 4), (2, 6)]);
872 /// # }));
873 /// # }
874 /// ```
875 pub fn map_with_key<U, F>(
876 self,
877 f: impl IntoQuotedMut<'a, F, L> + Copy,
878 ) -> KeyedStream<K, U, L, B, O, R>
879 where
880 F: Fn((K, V)) -> U + 'a,
881 K: Clone,
882 {
883 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_ctx(ctx));
884 let map_f = q!({
885 let orig = f;
886 move |(k, v)| {
887 let out = orig((Clone::clone(&k), v));
888 (k, out)
889 }
890 })
891 .splice_fn1_ctx::<(K, V), (K, U)>(&self.location)
892 .into();
893
894 KeyedStream::new(
895 self.location.clone(),
896 HydroNode::Map {
897 f: map_f,
898 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
899 metadata: self
900 .location
901 .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
902 },
903 )
904 }
905
906 /// Prepends a new value to the key of each element in the stream, producing a new
907 /// keyed stream with compound keys. Because the original key is preserved, no re-grouping
908 /// occurs and the elements in each group preserve their original order.
909 ///
910 /// # Example
911 /// ```rust
912 /// # #[cfg(feature = "deploy")] {
913 /// # use hydro_lang::prelude::*;
914 /// # use futures::StreamExt;
915 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
916 /// process
917 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
918 /// .into_keyed()
919 /// .prefix_key(q!(|&(k, _)| k % 2))
920 /// # .entries()
921 /// # }, |mut stream| async move {
922 /// // { (1, 1): [2, 3], (0, 2): [4] }
923 /// # let mut results = Vec::new();
924 /// # for _ in 0..3 {
925 /// # results.push(stream.next().await.unwrap());
926 /// # }
927 /// # results.sort();
928 /// # assert_eq!(results, vec![((0, 2), 4), ((1, 1), 2), ((1, 1), 3)]);
929 /// # }));
930 /// # }
931 /// ```
932 pub fn prefix_key<K2, F>(
933 self,
934 f: impl IntoQuotedMut<'a, F, L> + Copy,
935 ) -> KeyedStream<(K2, K), V, L, B, O, R>
936 where
937 F: Fn(&(K, V)) -> K2 + 'a,
938 {
939 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_borrow_ctx(ctx));
940 let map_f = q!({
941 let orig = f;
942 move |kv| {
943 let out = orig(&kv);
944 ((out, kv.0), kv.1)
945 }
946 })
947 .splice_fn1_ctx::<(K, V), ((K2, K), V)>(&self.location)
948 .into();
949
950 KeyedStream::new(
951 self.location.clone(),
952 HydroNode::Map {
953 f: map_f,
954 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
955 metadata: self
956 .location
957 .new_node_metadata(KeyedStream::<(K2, K), V, L, B, O, R>::collection_kind()),
958 },
959 )
960 }
961
962 /// Creates a stream containing only the elements of each group stream that satisfy a predicate
963 /// `f`, preserving the order of the elements within the group.
964 ///
965 /// The closure `f` receives a reference `&V` rather than an owned value `v` because filtering does
966 /// not modify or take ownership of the values. If you need to modify the values while filtering
967 /// use [`KeyedStream::filter_map`] instead.
968 ///
969 /// # Example
970 /// ```rust
971 /// # #[cfg(feature = "deploy")] {
972 /// # use hydro_lang::prelude::*;
973 /// # use futures::StreamExt;
974 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
975 /// process
976 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
977 /// .into_keyed()
978 /// .filter(q!(|&x| x > 2))
979 /// # .entries()
980 /// # }, |mut stream| async move {
981 /// // { 1: [3], 2: [4] }
982 /// # let mut results = Vec::new();
983 /// # for _ in 0..2 {
984 /// # results.push(stream.next().await.unwrap());
985 /// # }
986 /// # results.sort();
987 /// # assert_eq!(results, vec![(1, 3), (2, 4)]);
988 /// # }));
989 /// # }
990 /// ```
991 pub fn filter<F>(self, f: impl IntoQuotedMut<'a, F, L> + Copy) -> KeyedStream<K, V, L, B, O, R>
992 where
993 F: Fn(&V) -> bool + 'a,
994 {
995 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_borrow_ctx(ctx));
996 let filter_f = q!({
997 let orig = f;
998 move |t: &(_, _)| orig(&t.1)
999 })
1000 .splice_fn1_borrow_ctx::<(K, V), bool>(&self.location)
1001 .into();
1002
1003 KeyedStream::new(
1004 self.location.clone(),
1005 HydroNode::Filter {
1006 f: filter_f,
1007 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1008 metadata: self.location.new_node_metadata(Self::collection_kind()),
1009 },
1010 )
1011 }
1012
1013 /// Creates a stream containing only the elements of each group stream that satisfy a predicate
1014 /// `f` (which receives the key-value tuple), preserving the order of the elements within the group.
1015 ///
1016 /// The closure `f` receives a reference `&(K, V)` rather than an owned value `(K, V)` because filtering does
1017 /// not modify or take ownership of the values. If you need to modify the values while filtering
1018 /// use [`KeyedStream::filter_map_with_key`] instead.
1019 ///
1020 /// # Example
1021 /// ```rust
1022 /// # #[cfg(feature = "deploy")] {
1023 /// # use hydro_lang::prelude::*;
1024 /// # use futures::StreamExt;
1025 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1026 /// process
1027 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
1028 /// .into_keyed()
1029 /// .filter_with_key(q!(|&(k, v)| v - k == 2))
1030 /// # .entries()
1031 /// # }, |mut stream| async move {
1032 /// // { 1: [3], 2: [4] }
1033 /// # let mut results = Vec::new();
1034 /// # for _ in 0..2 {
1035 /// # results.push(stream.next().await.unwrap());
1036 /// # }
1037 /// # results.sort();
1038 /// # assert_eq!(results, vec![(1, 3), (2, 4)]);
1039 /// # }));
1040 /// # }
1041 /// ```
1042 pub fn filter_with_key<F>(
1043 self,
1044 f: impl IntoQuotedMut<'a, F, L> + Copy,
1045 ) -> KeyedStream<K, V, L, B, O, R>
1046 where
1047 F: Fn(&(K, V)) -> bool + 'a,
1048 {
1049 let filter_f = f
1050 .splice_fn1_borrow_ctx::<(K, V), bool>(&self.location)
1051 .into();
1052
1053 KeyedStream::new(
1054 self.location.clone(),
1055 HydroNode::Filter {
1056 f: filter_f,
1057 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1058 metadata: self.location.new_node_metadata(Self::collection_kind()),
1059 },
1060 )
1061 }
1062
1063 /// An operator that both filters and maps each value, with keys staying the same.
1064 /// It yields only the items for which the supplied closure `f` returns `Some(value)`.
1065 /// If you need access to the key, see [`KeyedStream::filter_map_with_key`].
1066 ///
1067 /// # Example
1068 /// ```rust
1069 /// # #[cfg(feature = "deploy")] {
1070 /// # use hydro_lang::prelude::*;
1071 /// # use futures::StreamExt;
1072 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1073 /// process
1074 /// .source_iter(q!(vec![(1, "2"), (1, "hello"), (2, "4")]))
1075 /// .into_keyed()
1076 /// .filter_map(q!(|s| s.parse::<usize>().ok()))
1077 /// # .entries()
1078 /// # }, |mut stream| async move {
1079 /// // { 1: [2], 2: [4] }
1080 /// # let mut results = Vec::new();
1081 /// # for _ in 0..2 {
1082 /// # results.push(stream.next().await.unwrap());
1083 /// # }
1084 /// # results.sort();
1085 /// # assert_eq!(results, vec![(1, 2), (2, 4)]);
1086 /// # }));
1087 /// # }
1088 /// ```
1089 pub fn filter_map<U, F>(
1090 self,
1091 f: impl IntoQuotedMut<'a, F, L> + Copy,
1092 ) -> KeyedStream<K, U, L, B, O, R>
1093 where
1094 F: Fn(V) -> Option<U> + 'a,
1095 {
1096 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_ctx(ctx));
1097 let filter_map_f = q!({
1098 let orig = f;
1099 move |(k, v)| orig(v).map(|o| (k, o))
1100 })
1101 .splice_fn1_ctx::<(K, V), Option<(K, U)>>(&self.location)
1102 .into();
1103
1104 KeyedStream::new(
1105 self.location.clone(),
1106 HydroNode::FilterMap {
1107 f: filter_map_f,
1108 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1109 metadata: self
1110 .location
1111 .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
1112 },
1113 )
1114 }
1115
1116 /// An operator that both filters and maps each key-value pair. The resulting values are **not**
1117 /// re-grouped even they are tuples; instead they will be grouped under the original key.
1118 /// It yields only the items for which the supplied closure `f` returns `Some(value)`.
1119 ///
1120 /// # Example
1121 /// ```rust
1122 /// # #[cfg(feature = "deploy")] {
1123 /// # use hydro_lang::prelude::*;
1124 /// # use futures::StreamExt;
1125 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1126 /// process
1127 /// .source_iter(q!(vec![(1, "2"), (1, "hello"), (2, "2")]))
1128 /// .into_keyed()
1129 /// .filter_map_with_key(q!(|(k, s)| s.parse::<usize>().ok().filter(|v| v == &k)))
1130 /// # .entries()
1131 /// # }, |mut stream| async move {
1132 /// // { 2: [2] }
1133 /// # let mut results = Vec::new();
1134 /// # for _ in 0..1 {
1135 /// # results.push(stream.next().await.unwrap());
1136 /// # }
1137 /// # results.sort();
1138 /// # assert_eq!(results, vec![(2, 2)]);
1139 /// # }));
1140 /// # }
1141 /// ```
1142 pub fn filter_map_with_key<U, F>(
1143 self,
1144 f: impl IntoQuotedMut<'a, F, L> + Copy,
1145 ) -> KeyedStream<K, U, L, B, O, R>
1146 where
1147 F: Fn((K, V)) -> Option<U> + 'a,
1148 K: Clone,
1149 {
1150 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_ctx(ctx));
1151 let filter_map_f = q!({
1152 let orig = f;
1153 move |(k, v)| {
1154 let out = orig((Clone::clone(&k), v));
1155 out.map(|o| (k, o))
1156 }
1157 })
1158 .splice_fn1_ctx::<(K, V), Option<(K, U)>>(&self.location)
1159 .into();
1160
1161 KeyedStream::new(
1162 self.location.clone(),
1163 HydroNode::FilterMap {
1164 f: filter_map_f,
1165 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1166 metadata: self
1167 .location
1168 .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
1169 },
1170 )
1171 }
1172
1173 /// Generates a keyed stream that maps each value `v` to a tuple `(v, x)`,
1174 /// where `v` is the value of `other`, a bounded [`super::singleton::Singleton`] or
1175 /// [`Optional`]. If `other` is an empty [`Optional`], no values will be produced.
1176 ///
1177 /// # Example
1178 /// ```rust
1179 /// # #[cfg(feature = "deploy")] {
1180 /// # use hydro_lang::prelude::*;
1181 /// # use futures::StreamExt;
1182 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1183 /// let tick = process.tick();
1184 /// let batch = process
1185 /// .source_iter(q!(vec![(1, 123), (1, 456), (2, 123)]))
1186 /// .into_keyed()
1187 /// .batch(&tick, nondet!(/** test */));
1188 /// let count = batch.clone().entries().count(); // `count()` returns a singleton
1189 /// batch.cross_singleton(count).all_ticks().entries()
1190 /// # }, |mut stream| async move {
1191 /// // { 1: [(123, 3), (456, 3)], 2: [(123, 3)] }
1192 /// # let mut results = Vec::new();
1193 /// # for _ in 0..3 {
1194 /// # results.push(stream.next().await.unwrap());
1195 /// # }
1196 /// # results.sort();
1197 /// # assert_eq!(results, vec![(1, (123, 3)), (1, (456, 3)), (2, (123, 3))]);
1198 /// # }));
1199 /// # }
1200 /// ```
1201 pub fn cross_singleton<O2>(
1202 self,
1203 other: impl Into<Optional<O2, L, Bounded>>,
1204 ) -> KeyedStream<K, (V, O2), L, B, O, R>
1205 where
1206 O2: Clone,
1207 {
1208 let other: Optional<O2, L, Bounded> = other.into();
1209 check_matching_location(&self.location, &other.location);
1210
1211 Stream::<((K, V), O2), L, B, O, R>::new(
1212 self.location.clone(),
1213 HydroNode::CrossSingleton {
1214 left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1215 right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
1216 metadata: self
1217 .location
1218 .new_node_metadata(Stream::<((K, V), O2), L, B, O, R>::collection_kind()),
1219 },
1220 )
1221 .map(q!(|((k, v), o2)| (k, (v, o2))))
1222 .into_keyed()
1223 }
1224
1225 /// For each value `v` in each group, transform `v` using `f` and then treat the
1226 /// result as an [`Iterator`] to produce values one by one within the same group.
1227 /// The implementation for [`Iterator`] for the output type `I` must produce items
1228 /// in a **deterministic** order.
1229 ///
1230 /// For example, `I` could be a `Vec`, but not a `HashSet`. If the order of the items in `I` is
1231 /// not deterministic, use [`KeyedStream::flat_map_unordered`] instead.
1232 ///
1233 /// # Example
1234 /// ```rust
1235 /// # #[cfg(feature = "deploy")] {
1236 /// # use hydro_lang::prelude::*;
1237 /// # use futures::StreamExt;
1238 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1239 /// process
1240 /// .source_iter(q!(vec![(1, vec![2, 3]), (1, vec![4]), (2, vec![5, 6])]))
1241 /// .into_keyed()
1242 /// .flat_map_ordered(q!(|x| x))
1243 /// # .entries()
1244 /// # }, |mut stream| async move {
1245 /// // { 1: [2, 3, 4], 2: [5, 6] }
1246 /// # let mut results = Vec::new();
1247 /// # for _ in 0..5 {
1248 /// # results.push(stream.next().await.unwrap());
1249 /// # }
1250 /// # results.sort();
1251 /// # assert_eq!(results, vec![(1, 2), (1, 3), (1, 4), (2, 5), (2, 6)]);
1252 /// # }));
1253 /// # }
1254 /// ```
1255 pub fn flat_map_ordered<U, I, F>(
1256 self,
1257 f: impl IntoQuotedMut<'a, F, L> + Copy,
1258 ) -> KeyedStream<K, U, L, B, O, R>
1259 where
1260 I: IntoIterator<Item = U>,
1261 F: Fn(V) -> I + 'a,
1262 K: Clone,
1263 {
1264 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_ctx(ctx));
1265 let flat_map_f = q!({
1266 let orig = f;
1267 move |(k, v)| orig(v).into_iter().map(move |u| (Clone::clone(&k), u))
1268 })
1269 .splice_fn1_ctx::<(K, V), _>(&self.location)
1270 .into();
1271
1272 KeyedStream::new(
1273 self.location.clone(),
1274 HydroNode::FlatMap {
1275 f: flat_map_f,
1276 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1277 metadata: self
1278 .location
1279 .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
1280 },
1281 )
1282 }
1283
1284 /// Like [`KeyedStream::flat_map_ordered`], but allows the implementation of [`Iterator`]
1285 /// for the output type `I` to produce items in any order.
1286 ///
1287 /// # Example
1288 /// ```rust
1289 /// # #[cfg(feature = "deploy")] {
1290 /// # use hydro_lang::{prelude::*, live_collections::stream::{NoOrder, ExactlyOnce}};
1291 /// # use futures::StreamExt;
1292 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
1293 /// process
1294 /// .source_iter(q!(vec![
1295 /// (1, std::collections::HashSet::<i32>::from_iter(vec![2, 3])),
1296 /// (2, std::collections::HashSet::from_iter(vec![4, 5]))
1297 /// ]))
1298 /// .into_keyed()
1299 /// .flat_map_unordered(q!(|x| x))
1300 /// # .entries()
1301 /// # }, |mut stream| async move {
1302 /// // { 1: [2, 3], 2: [4, 5] } with values in each group in unknown order
1303 /// # let mut results = Vec::new();
1304 /// # for _ in 0..4 {
1305 /// # results.push(stream.next().await.unwrap());
1306 /// # }
1307 /// # results.sort();
1308 /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4), (2, 5)]);
1309 /// # }));
1310 /// # }
1311 /// ```
1312 pub fn flat_map_unordered<U, I, F>(
1313 self,
1314 f: impl IntoQuotedMut<'a, F, L> + Copy,
1315 ) -> KeyedStream<K, U, L, B, NoOrder, R>
1316 where
1317 I: IntoIterator<Item = U>,
1318 F: Fn(V) -> I + 'a,
1319 K: Clone,
1320 {
1321 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_ctx(ctx));
1322 let flat_map_f = q!({
1323 let orig = f;
1324 move |(k, v)| orig(v).into_iter().map(move |u| (Clone::clone(&k), u))
1325 })
1326 .splice_fn1_ctx::<(K, V), _>(&self.location)
1327 .into();
1328
1329 KeyedStream::new(
1330 self.location.clone(),
1331 HydroNode::FlatMap {
1332 f: flat_map_f,
1333 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1334 metadata: self
1335 .location
1336 .new_node_metadata(KeyedStream::<K, U, L, B, NoOrder, R>::collection_kind()),
1337 },
1338 )
1339 }
1340
1341 /// For each value `v` in each group, treat `v` as an [`Iterator`] and produce its items one by one
1342 /// within the same group. The implementation for [`Iterator`] for the value type `V` must produce
1343 /// items in a **deterministic** order.
1344 ///
1345 /// For example, `V` could be a `Vec`, but not a `HashSet`. If the order of the items in `V` is
1346 /// not deterministic, use [`KeyedStream::flatten_unordered`] instead.
1347 ///
1348 /// # Example
1349 /// ```rust
1350 /// # #[cfg(feature = "deploy")] {
1351 /// # use hydro_lang::prelude::*;
1352 /// # use futures::StreamExt;
1353 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1354 /// process
1355 /// .source_iter(q!(vec![(1, vec![2, 3]), (1, vec![4]), (2, vec![5, 6])]))
1356 /// .into_keyed()
1357 /// .flatten_ordered()
1358 /// # .entries()
1359 /// # }, |mut stream| async move {
1360 /// // { 1: [2, 3, 4], 2: [5, 6] }
1361 /// # let mut results = Vec::new();
1362 /// # for _ in 0..5 {
1363 /// # results.push(stream.next().await.unwrap());
1364 /// # }
1365 /// # results.sort();
1366 /// # assert_eq!(results, vec![(1, 2), (1, 3), (1, 4), (2, 5), (2, 6)]);
1367 /// # }));
1368 /// # }
1369 /// ```
1370 pub fn flatten_ordered<U>(self) -> KeyedStream<K, U, L, B, O, R>
1371 where
1372 V: IntoIterator<Item = U>,
1373 K: Clone,
1374 {
1375 self.flat_map_ordered(q!(|d| d))
1376 }
1377
1378 /// Like [`KeyedStream::flatten_ordered`], but allows the implementation of [`Iterator`]
1379 /// for the value type `V` to produce items in any order.
1380 ///
1381 /// # Example
1382 /// ```rust
1383 /// # #[cfg(feature = "deploy")] {
1384 /// # use hydro_lang::{prelude::*, live_collections::stream::{NoOrder, ExactlyOnce}};
1385 /// # use futures::StreamExt;
1386 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
1387 /// process
1388 /// .source_iter(q!(vec![
1389 /// (1, std::collections::HashSet::<i32>::from_iter(vec![2, 3])),
1390 /// (2, std::collections::HashSet::from_iter(vec![4, 5]))
1391 /// ]))
1392 /// .into_keyed()
1393 /// .flatten_unordered()
1394 /// # .entries()
1395 /// # }, |mut stream| async move {
1396 /// // { 1: [2, 3], 2: [4, 5] } with values in each group in unknown order
1397 /// # let mut results = Vec::new();
1398 /// # for _ in 0..4 {
1399 /// # results.push(stream.next().await.unwrap());
1400 /// # }
1401 /// # results.sort();
1402 /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4), (2, 5)]);
1403 /// # }));
1404 /// # }
1405 /// ```
1406 pub fn flatten_unordered<U>(self) -> KeyedStream<K, U, L, B, NoOrder, R>
1407 where
1408 V: IntoIterator<Item = U>,
1409 K: Clone,
1410 {
1411 self.flat_map_unordered(q!(|d| d))
1412 }
1413
1414 /// An operator which allows you to "inspect" each element of a stream without
1415 /// modifying it. The closure `f` is called on a reference to each value. This is
1416 /// mainly useful for debugging, and should not be used to generate side-effects.
1417 ///
1418 /// # Example
1419 /// ```rust
1420 /// # #[cfg(feature = "deploy")] {
1421 /// # use hydro_lang::prelude::*;
1422 /// # use futures::StreamExt;
1423 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1424 /// process
1425 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
1426 /// .into_keyed()
1427 /// .inspect(q!(|v| println!("{}", v)))
1428 /// # .entries()
1429 /// # }, |mut stream| async move {
1430 /// # let mut results = Vec::new();
1431 /// # for _ in 0..3 {
1432 /// # results.push(stream.next().await.unwrap());
1433 /// # }
1434 /// # results.sort();
1435 /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4)]);
1436 /// # }));
1437 /// # }
1438 /// ```
1439 pub fn inspect<F>(self, f: impl IntoQuotedMut<'a, F, L> + Copy) -> Self
1440 where
1441 F: Fn(&V) + 'a,
1442 {
1443 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn1_borrow_ctx(ctx));
1444 let inspect_f = q!({
1445 let orig = f;
1446 move |t: &(_, _)| orig(&t.1)
1447 })
1448 .splice_fn1_borrow_ctx::<(K, V), ()>(&self.location)
1449 .into();
1450
1451 KeyedStream::new(
1452 self.location.clone(),
1453 HydroNode::Inspect {
1454 f: inspect_f,
1455 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1456 metadata: self.location.new_node_metadata(Self::collection_kind()),
1457 },
1458 )
1459 }
1460
1461 /// An operator which allows you to "inspect" each element of a stream without
1462 /// modifying it. The closure `f` is called on a reference to each key-value pair. This is
1463 /// mainly useful for debugging, and should not be used to generate side-effects.
1464 ///
1465 /// # Example
1466 /// ```rust
1467 /// # #[cfg(feature = "deploy")] {
1468 /// # use hydro_lang::prelude::*;
1469 /// # use futures::StreamExt;
1470 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1471 /// process
1472 /// .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
1473 /// .into_keyed()
1474 /// .inspect_with_key(q!(|(k, v)| println!("{}: {}", k, v)))
1475 /// # .entries()
1476 /// # }, |mut stream| async move {
1477 /// # let mut results = Vec::new();
1478 /// # for _ in 0..3 {
1479 /// # results.push(stream.next().await.unwrap());
1480 /// # }
1481 /// # results.sort();
1482 /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4)]);
1483 /// # }));
1484 /// # }
1485 /// ```
1486 pub fn inspect_with_key<F>(self, f: impl IntoQuotedMut<'a, F, L>) -> Self
1487 where
1488 F: Fn(&(K, V)) + 'a,
1489 {
1490 let inspect_f = f.splice_fn1_borrow_ctx::<(K, V), ()>(&self.location).into();
1491
1492 KeyedStream::new(
1493 self.location.clone(),
1494 HydroNode::Inspect {
1495 f: inspect_f,
1496 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1497 metadata: self.location.new_node_metadata(Self::collection_kind()),
1498 },
1499 )
1500 }
1501
1502 /// An operator which allows you to "name" a `HydroNode`.
1503 /// This is only used for testing, to correlate certain `HydroNode`s with IDs.
1504 pub fn ir_node_named(self, name: &str) -> KeyedStream<K, V, L, B, O, R> {
1505 {
1506 let mut node = self.ir_node.borrow_mut();
1507 let metadata = node.metadata_mut();
1508 metadata.tag = Some(name.to_owned());
1509 }
1510 self
1511 }
1512
1513 /// A special case of [`Stream::scan`] for keyed streams. For each key group the values are transformed via the `f` combinator.
1514 ///
1515 /// Unlike [`KeyedStream::fold`] which only returns the final accumulated value, `scan` produces a new stream
1516 /// containing all intermediate accumulated values paired with the key. The scan operation can also terminate
1517 /// early by returning `None`.
1518 ///
1519 /// The function takes a mutable reference to the accumulator and the current element, and returns
1520 /// an `Option<U>`. If the function returns `Some(value)`, `value` is emitted to the output stream.
1521 /// If the function returns `None`, the stream is terminated and no more elements are processed.
1522 ///
1523 /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1524 /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref).
1525 ///
1526 /// # Example
1527 /// ```rust
1528 /// # #[cfg(feature = "deploy")] {
1529 /// # use hydro_lang::prelude::*;
1530 /// # use futures::StreamExt;
1531 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1532 /// process
1533 /// .source_iter(q!(vec![(0, 1), (0, 3), (1, 3), (1, 4)]))
1534 /// .into_keyed()
1535 /// .scan(
1536 /// q!(|| 0),
1537 /// q!(|acc, x| {
1538 /// *acc += x;
1539 /// if *acc % 2 == 0 { None } else { Some(*acc) }
1540 /// }),
1541 /// )
1542 /// # .entries()
1543 /// # }, |mut stream| async move {
1544 /// // Output: { 0: [1], 1: [3, 7] }
1545 /// # let mut results = Vec::new();
1546 /// # for _ in 0..3 {
1547 /// # results.push(stream.next().await.unwrap());
1548 /// # }
1549 /// # results.sort();
1550 /// # assert_eq!(results, vec![(0, 1), (1, 3), (1, 7)]);
1551 /// # }));
1552 /// # }
1553 /// ```
1554 pub fn scan<A, U, I, F>(
1555 self,
1556 init: impl IntoQuotedMut<'a, I, L> + Copy,
1557 f: impl IntoQuotedMut<'a, F, L> + Copy,
1558 ) -> KeyedStream<K, U, L, B, TotalOrder, ExactlyOnce>
1559 where
1560 O: IsOrdered,
1561 R: IsExactlyOnce,
1562 K: Clone + Eq + Hash,
1563 I: Fn() -> A + 'a,
1564 F: Fn(&mut A, V) -> Option<U> + 'a,
1565 {
1566 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn2_borrow_mut_ctx(ctx));
1567 self.make_totally_ordered().make_exactly_once().generator(
1568 init,
1569 q!({
1570 let orig = f;
1571 move |state, v| {
1572 if let Some(out) = orig(state, v) {
1573 Generate::Yield(out)
1574 } else {
1575 Generate::Break
1576 }
1577 }
1578 }),
1579 )
1580 }
1581
1582 /// Iteratively processes the elements in each group using a state machine that can yield
1583 /// elements as it processes its inputs. This is designed to mirror the unstable generator
1584 /// syntax in Rust, without requiring special syntax.
1585 ///
1586 /// Like [`KeyedStream::scan`], this function takes in an initializer that emits the initial
1587 /// state for each group. The second argument defines the processing logic, taking in a
1588 /// mutable reference to the group's state and the value to be processed. It emits a
1589 /// [`Generate`] value, whose variants define what is emitted and whether further inputs
1590 /// should be processed.
1591 ///
1592 /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1593 /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref).
1594 ///
1595 /// # Example
1596 /// ```rust
1597 /// # #[cfg(feature = "deploy")] {
1598 /// # use hydro_lang::prelude::*;
1599 /// # use futures::StreamExt;
1600 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1601 /// process
1602 /// .source_iter(q!(vec![(0, 1), (0, 3), (0, 100), (0, 10), (1, 3), (1, 4), (1, 3)]))
1603 /// .into_keyed()
1604 /// .generator(
1605 /// q!(|| 0),
1606 /// q!(|acc, x| {
1607 /// *acc += x;
1608 /// if *acc > 100 {
1609 /// hydro_lang::live_collections::keyed_stream::Generate::Return(
1610 /// "done!".to_owned()
1611 /// )
1612 /// } else if *acc % 2 == 0 {
1613 /// hydro_lang::live_collections::keyed_stream::Generate::Yield(
1614 /// "even".to_owned()
1615 /// )
1616 /// } else {
1617 /// hydro_lang::live_collections::keyed_stream::Generate::Continue
1618 /// }
1619 /// }),
1620 /// )
1621 /// # .entries()
1622 /// # }, |mut stream| async move {
1623 /// // Output: { 0: ["even", "done!"], 1: ["even"] }
1624 /// # let mut results = Vec::new();
1625 /// # for _ in 0..3 {
1626 /// # results.push(stream.next().await.unwrap());
1627 /// # }
1628 /// # results.sort();
1629 /// # assert_eq!(results, vec![(0, "done!".to_owned()), (0, "even".to_owned()), (1, "even".to_owned())]);
1630 /// # }));
1631 /// # }
1632 /// ```
1633 pub fn generator<A, U, I, F>(
1634 self,
1635 init: impl IntoQuotedMut<'a, I, L> + Copy,
1636 f: impl IntoQuotedMut<'a, F, L> + Copy,
1637 ) -> KeyedStream<K, U, L, B, TotalOrder, ExactlyOnce>
1638 where
1639 O: IsOrdered,
1640 R: IsExactlyOnce,
1641 K: Clone + Eq + Hash,
1642 I: Fn() -> A + 'a,
1643 F: Fn(&mut A, V) -> Generate<U> + 'a,
1644 {
1645 let init: ManualExpr<I, _> = ManualExpr::new(move |ctx: &L| init.splice_fn0_ctx(ctx));
1646 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn2_borrow_mut_ctx(ctx));
1647
1648 let this = self.make_totally_ordered().make_exactly_once();
1649
1650 let scan_init = crate::handoff_ref::with_ref_capture(|| {
1651 q!(|| HashMap::new())
1652 .splice_fn0_ctx::<HashMap<K, Option<A>>>(&this.location)
1653 .into()
1654 });
1655 let scan_f = crate::handoff_ref::with_ref_capture(|| {
1656 q!(move |acc: &mut HashMap<_, _>, (k, v)| {
1657 let existing_state = acc.entry(Clone::clone(&k)).or_insert_with(|| Some(init()));
1658 if let Some(existing_state_value) = existing_state {
1659 match f(existing_state_value, v) {
1660 Generate::Yield(out) => Some(Some((k, out))),
1661 Generate::Return(out) => {
1662 let _ = existing_state.take(); // TODO(shadaj): garbage collect with termination markers
1663 Some(Some((k, out)))
1664 }
1665 Generate::Break => {
1666 let _ = existing_state.take(); // TODO(shadaj): garbage collect with termination markers
1667 Some(None)
1668 }
1669 Generate::Continue => Some(None),
1670 }
1671 } else {
1672 Some(None)
1673 }
1674 })
1675 .splice_fn2_borrow_mut_ctx::<HashMap<K, Option<A>>, (K, V), _>(&this.location)
1676 .into()
1677 });
1678
1679 let scan_node = HydroNode::Scan {
1680 init: scan_init,
1681 acc: scan_f,
1682 input: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
1683 metadata: this.location.new_node_metadata(Stream::<
1684 Option<(K, U)>,
1685 L,
1686 B,
1687 TotalOrder,
1688 ExactlyOnce,
1689 >::collection_kind()),
1690 };
1691
1692 let flatten_f = q!(|d| d)
1693 .splice_fn1_ctx::<Option<(K, U)>, _>(&this.location)
1694 .into();
1695 let flatten_node = HydroNode::FlatMap {
1696 f: flatten_f,
1697 input: Box::new(scan_node),
1698 metadata: this.location.new_node_metadata(KeyedStream::<
1699 K,
1700 U,
1701 L,
1702 B,
1703 TotalOrder,
1704 ExactlyOnce,
1705 >::collection_kind()),
1706 };
1707
1708 KeyedStream::new(this.location.clone(), flatten_node)
1709 }
1710
1711 /// A variant of [`Stream::fold`], intended for keyed streams. The aggregation is executed
1712 /// in-order across the values in each group. But the aggregation function returns a boolean,
1713 /// which when true indicates that the aggregated result is complete and can be released to
1714 /// downstream computation. Unlike [`KeyedStream::fold`], this means that even if the input
1715 /// stream is [`super::boundedness::Unbounded`], the outputs of the fold can be processed like
1716 /// normal stream elements.
1717 ///
1718 /// # Example
1719 /// ```rust
1720 /// # #[cfg(feature = "deploy")] {
1721 /// # use hydro_lang::prelude::*;
1722 /// # use futures::StreamExt;
1723 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1724 /// process
1725 /// .source_iter(q!(vec![(0, 2), (0, 3), (1, 3), (1, 6)]))
1726 /// .into_keyed()
1727 /// .fold_early_stop(
1728 /// q!(|| 0),
1729 /// q!(|acc, x| {
1730 /// *acc += x;
1731 /// x % 2 == 0
1732 /// }),
1733 /// )
1734 /// # .entries()
1735 /// # }, |mut stream| async move {
1736 /// // Output: { 0: 2, 1: 9 }
1737 /// # let mut results = Vec::new();
1738 /// # for _ in 0..2 {
1739 /// # results.push(stream.next().await.unwrap());
1740 /// # }
1741 /// # results.sort();
1742 /// # assert_eq!(results, vec![(0, 2), (1, 9)]);
1743 /// # }));
1744 /// # }
1745 /// ```
1746 pub fn fold_early_stop<A, I, F>(
1747 self,
1748 init: impl IntoQuotedMut<'a, I, L> + Copy,
1749 f: impl IntoQuotedMut<'a, F, L> + Copy,
1750 ) -> KeyedSingleton<K, A, L, B::WithBoundedValue>
1751 where
1752 O: IsOrdered,
1753 R: IsExactlyOnce,
1754 K: Clone + Eq + Hash,
1755 I: Fn() -> A + 'a,
1756 F: Fn(&mut A, V) -> bool + 'a,
1757 {
1758 let init: ManualExpr<I, _> = ManualExpr::new(move |ctx: &L| init.splice_fn0_ctx(ctx));
1759 let f: ManualExpr<F, _> = ManualExpr::new(move |ctx: &L| f.splice_fn2_borrow_mut_ctx(ctx));
1760 let out_without_bound_cast = self.generator(
1761 q!(move || Some(init())),
1762 q!(move |key_state, v| {
1763 if let Some(key_state_value) = key_state.as_mut() {
1764 if f(key_state_value, v) {
1765 Generate::Return(key_state.take().unwrap())
1766 } else {
1767 Generate::Continue
1768 }
1769 } else {
1770 unreachable!()
1771 }
1772 }),
1773 );
1774
1775 // SAFETY: The generator will only ever return at most one value per key, since once it
1776 // returns a value for a key it will never process any more values for that key.
1777 out_without_bound_cast.cast_at_most_one_entry_per_key()
1778 }
1779
1780 /// Gets the first element inside each group of values as a [`KeyedSingleton`] that preserves
1781 /// the original group keys. Requires the input stream to have [`TotalOrder`] guarantees,
1782 /// otherwise the first element would be non-deterministic.
1783 ///
1784 /// # Example
1785 /// ```rust
1786 /// # #[cfg(feature = "deploy")] {
1787 /// # use hydro_lang::prelude::*;
1788 /// # use futures::StreamExt;
1789 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1790 /// process
1791 /// .source_iter(q!(vec![(0, 2), (0, 3), (1, 3), (1, 6)]))
1792 /// .into_keyed()
1793 /// .first()
1794 /// # .entries()
1795 /// # }, |mut stream| async move {
1796 /// // Output: { 0: 2, 1: 3 }
1797 /// # let mut results = Vec::new();
1798 /// # for _ in 0..2 {
1799 /// # results.push(stream.next().await.unwrap());
1800 /// # }
1801 /// # results.sort();
1802 /// # assert_eq!(results, vec![(0, 2), (1, 3)]);
1803 /// # }));
1804 /// # }
1805 /// ```
1806 pub fn first(self) -> KeyedSingleton<K, V, L, B::WithBoundedValue>
1807 where
1808 O: IsOrdered,
1809 R: IsExactlyOnce,
1810 K: Clone + Eq + Hash,
1811 {
1812 self.fold_early_stop(
1813 q!(|| None),
1814 q!(|acc, v| {
1815 *acc = Some(v);
1816 true
1817 }),
1818 )
1819 .map(q!(|v| v.unwrap()))
1820 }
1821
1822 /// Returns a keyed stream containing at most the first `n` values per key,
1823 /// preserving the original order within each group. Similar to SQL `LIMIT`
1824 /// applied per group.
1825 ///
1826 /// This requires the stream to have a [`TotalOrder`] guarantee and [`ExactlyOnce`]
1827 /// retries, since the result depends on the order and cardinality of elements
1828 /// within each group.
1829 ///
1830 /// # Example
1831 /// ```rust
1832 /// # #[cfg(feature = "deploy")] {
1833 /// # use hydro_lang::prelude::*;
1834 /// # use futures::StreamExt;
1835 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1836 /// process
1837 /// .source_iter(q!(vec![(1, 10), (1, 20), (1, 30), (2, 40), (2, 50)]))
1838 /// .into_keyed()
1839 /// .limit(q!(2))
1840 /// # .entries()
1841 /// # }, |mut stream| async move {
1842 /// // { 1: [10, 20], 2: [40, 50] }
1843 /// # let mut results = Vec::new();
1844 /// # for _ in 0..4 {
1845 /// # results.push(stream.next().await.unwrap());
1846 /// # }
1847 /// # results.sort();
1848 /// # assert_eq!(results, vec![(1, 10), (1, 20), (2, 40), (2, 50)]);
1849 /// # }));
1850 /// # }
1851 /// ```
1852 pub fn limit(
1853 self,
1854 n: impl QuotedWithContext<'a, usize, L> + Copy + 'a,
1855 ) -> KeyedStream<K, V, L, B, TotalOrder, ExactlyOnce>
1856 where
1857 O: IsOrdered,
1858 R: IsExactlyOnce,
1859 K: Clone + Eq + Hash,
1860 {
1861 self.generator(
1862 q!(|| 0usize),
1863 q!(move |count, item| {
1864 if *count == n {
1865 Generate::Break
1866 } else {
1867 *count += 1;
1868 if *count == n {
1869 Generate::Return(item)
1870 } else {
1871 Generate::Yield(item)
1872 }
1873 }
1874 }),
1875 )
1876 }
1877
1878 /// Assigns a zero-based index to each value within each key group, emitting
1879 /// `(K, (index, V))` tuples with per-key sequential indices.
1880 ///
1881 /// The output keyed stream has [`TotalOrder`] and [`ExactlyOnce`] guarantees.
1882 /// This is a streaming operator that processes elements as they arrive.
1883 ///
1884 /// # Example
1885 /// ```rust
1886 /// # #[cfg(feature = "deploy")] {
1887 /// # use hydro_lang::prelude::*;
1888 /// # use futures::StreamExt;
1889 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1890 /// process
1891 /// .source_iter(q!(vec![(1, 10), (2, 20), (1, 30)]))
1892 /// .into_keyed()
1893 /// .enumerate()
1894 /// # .entries()
1895 /// # }, |mut stream| async move {
1896 /// // per-key indices: { 1: [(0, 10), (1, 30)], 2: [(0, 20)] }
1897 /// # let mut results = Vec::new();
1898 /// # for _ in 0..3 {
1899 /// # results.push(stream.next().await.unwrap());
1900 /// # }
1901 /// # let key1: Vec<_> = results.iter().filter(|(k, _)| *k == 1).map(|(_, v)| *v).collect();
1902 /// # let key2: Vec<_> = results.iter().filter(|(k, _)| *k == 2).map(|(_, v)| *v).collect();
1903 /// # assert_eq!(key1, vec![(0, 10), (1, 30)]);
1904 /// # assert_eq!(key2, vec![(0, 20)]);
1905 /// # }));
1906 /// # }
1907 /// ```
1908 pub fn enumerate(self) -> KeyedStream<K, (usize, V), L, B, TotalOrder, ExactlyOnce>
1909 where
1910 O: IsOrdered,
1911 R: IsExactlyOnce,
1912 K: Eq + Hash + Clone,
1913 {
1914 self.scan(
1915 q!(|| 0),
1916 q!(|acc, next| {
1917 let curr = *acc;
1918 *acc += 1;
1919 Some((curr, next))
1920 }),
1921 )
1922 }
1923
1924 /// Counts the number of elements in each group, producing a [`KeyedSingleton`] with the counts.
1925 ///
1926 /// # Example
1927 /// ```rust
1928 /// # #[cfg(feature = "deploy")] {
1929 /// # use hydro_lang::prelude::*;
1930 /// # use futures::StreamExt;
1931 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1932 /// let tick = process.tick();
1933 /// let numbers = process
1934 /// .source_iter(q!(vec![(1, 2), (2, 3), (1, 3), (2, 4), (1, 5)]))
1935 /// .into_keyed();
1936 /// let batch = numbers.batch(&tick, nondet!(/** test */));
1937 /// batch
1938 /// .value_counts()
1939 /// .entries()
1940 /// .all_ticks()
1941 /// # }, |mut stream| async move {
1942 /// // (1, 3), (2, 2)
1943 /// # let mut results = Vec::new();
1944 /// # for _ in 0..2 {
1945 /// # results.push(stream.next().await.unwrap());
1946 /// # }
1947 /// # results.sort();
1948 /// # assert_eq!(results, vec![(1, 3), (2, 2)]);
1949 /// # }));
1950 /// # }
1951 /// ```
1952 pub fn value_counts(
1953 self,
1954 ) -> KeyedSingleton<K, usize, L, <B as KeyedSingletonBound>::KeyedStreamToMonotone>
1955 where
1956 R: IsExactlyOnce,
1957 K: Eq + Hash,
1958 {
1959 self.make_exactly_once()
1960 .assume_ordering_trusted(
1961 nondet!(/** ordering within each group affects neither result nor intermediates */),
1962 )
1963 .fold(
1964 q!(|| 0),
1965 q!(
1966 |acc, _| *acc += 1,
1967 monotone = manual_proof!(/** += 1 is monotonic */)
1968 ),
1969 )
1970 }
1971
1972 /// Like [`Stream::fold`] but in the spirit of SQL `GROUP BY`, aggregates the values in each
1973 /// group via the `comb` closure.
1974 ///
1975 /// Depending on the input stream guarantees, the closure may need to be commutative
1976 /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
1977 ///
1978 /// If the input and output value types are the same and do not require initialization then use
1979 /// [`KeyedStream::reduce`].
1980 ///
1981 /// # Example
1982 /// ```rust
1983 /// # #[cfg(feature = "deploy")] {
1984 /// # use hydro_lang::prelude::*;
1985 /// # use futures::StreamExt;
1986 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1987 /// let tick = process.tick();
1988 /// let numbers = process
1989 /// .source_iter(q!(vec![(1, false), (2, true), (1, false), (2, false)]))
1990 /// .into_keyed();
1991 /// let batch = numbers.batch(&tick, nondet!(/** test */));
1992 /// batch
1993 /// .fold(q!(|| false), q!(|acc, x| *acc |= x))
1994 /// .entries()
1995 /// .all_ticks()
1996 /// # }, |mut stream| async move {
1997 /// // (1, false), (2, true)
1998 /// # let mut results = Vec::new();
1999 /// # for _ in 0..2 {
2000 /// # results.push(stream.next().await.unwrap());
2001 /// # }
2002 /// # results.sort();
2003 /// # assert_eq!(results, vec![(1, false), (2, true)]);
2004 /// # }));
2005 /// # }
2006 /// ```
2007 pub fn fold<A, I: Fn() -> A + 'a, F: 'a + Fn(&mut A, V), C, Idemp, M, B2: KeyedSingletonBound>(
2008 self,
2009 init: impl IntoQuotedMut<'a, I, L>,
2010 comb: impl IntoQuotedMut<'a, F, L, AggFuncAlgebra<C, Idemp, M>>,
2011 ) -> KeyedSingleton<K, A, L, B2>
2012 where
2013 K: Eq + Hash,
2014 C: ValidCommutativityFor<O>,
2015 Idemp: ValidIdempotenceFor<R>,
2016 B: ApplyMonotoneKeyedStream<M, B2>,
2017 {
2018 let init = init.splice_fn0_ctx(&self.location).into();
2019 let (comb, proof) = comb.splice_fn2_borrow_mut_ctx_props(&self.location);
2020 proof.register_proof(&comb);
2021
2022 let retried = self
2023 .assume_retries::<ExactlyOnce>(nondet!(/** the combinator function is idempotent */));
2024
2025 KeyedSingleton::new(
2026 retried.location.clone(),
2027 HydroNode::FoldKeyed {
2028 init,
2029 acc: comb.into(),
2030 input: Box::new(retried.ir_node.replace(HydroNode::Placeholder)),
2031 metadata: retried
2032 .location
2033 .new_node_metadata(KeyedSingleton::<K, A, L, B2>::collection_kind()),
2034 },
2035 )
2036 .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
2037 }
2038
2039 /// Like [`Stream::reduce`] but in the spirit of SQL `GROUP BY`, aggregates the values in each
2040 /// group via the `comb` closure.
2041 ///
2042 /// Depending on the input stream guarantees, the closure may need to be commutative
2043 /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
2044 ///
2045 /// If you need the accumulated value to have a different type than the input, use [`KeyedStream::fold`].
2046 ///
2047 /// # Example
2048 /// ```rust
2049 /// # #[cfg(feature = "deploy")] {
2050 /// # use hydro_lang::prelude::*;
2051 /// # use futures::StreamExt;
2052 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2053 /// let tick = process.tick();
2054 /// let numbers = process
2055 /// .source_iter(q!(vec![(1, false), (2, true), (1, false), (2, false)]))
2056 /// .into_keyed();
2057 /// let batch = numbers.batch(&tick, nondet!(/** test */));
2058 /// batch
2059 /// .reduce(q!(|acc, x| *acc |= x))
2060 /// .entries()
2061 /// .all_ticks()
2062 /// # }, |mut stream| async move {
2063 /// // (1, false), (2, true)
2064 /// # let mut results = Vec::new();
2065 /// # for _ in 0..2 {
2066 /// # results.push(stream.next().await.unwrap());
2067 /// # }
2068 /// # results.sort();
2069 /// # assert_eq!(results, vec![(1, false), (2, true)]);
2070 /// # }));
2071 /// # }
2072 /// ```
2073 pub fn reduce<F: Fn(&mut V, V) + 'a, C, Idemp>(
2074 self,
2075 comb: impl IntoQuotedMut<'a, F, L, AggFuncAlgebra<C, Idemp>>,
2076 ) -> KeyedSingleton<K, V, L, B>
2077 where
2078 K: Eq + Hash,
2079 C: ValidCommutativityFor<O>,
2080 Idemp: ValidIdempotenceFor<R>,
2081 {
2082 let (f, proof) = comb.splice_fn2_borrow_mut_ctx_props(&self.location);
2083 proof.register_proof(&f);
2084
2085 let ordered = self
2086 .assume_retries::<ExactlyOnce>(nondet!(/** the combinator function is idempotent */))
2087 .assume_ordering::<TotalOrder>(nondet!(/** the combinator function is commutative */));
2088
2089 KeyedSingleton::new(
2090 ordered.location.clone(),
2091 HydroNode::ReduceKeyed {
2092 f: f.into(),
2093 input: Box::new(ordered.ir_node.replace(HydroNode::Placeholder)),
2094 metadata: ordered
2095 .location
2096 .new_node_metadata(KeyedSingleton::<K, V, L, B>::collection_kind()),
2097 },
2098 )
2099 .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
2100 }
2101
2102 /// A special case of [`KeyedStream::reduce`] where tuples with keys less than the watermark
2103 /// are automatically deleted.
2104 ///
2105 /// Depending on the input stream guarantees, the closure may need to be commutative
2106 /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
2107 ///
2108 /// # Example
2109 /// ```rust
2110 /// # #[cfg(feature = "deploy")] {
2111 /// # use hydro_lang::prelude::*;
2112 /// # use futures::StreamExt;
2113 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2114 /// let tick = process.tick();
2115 /// let watermark = tick.singleton(q!(2));
2116 /// let numbers = process
2117 /// .source_iter(q!([(0, false), (1, false), (2, false), (2, true)]))
2118 /// .into_keyed();
2119 /// let batch = numbers.batch(&tick, nondet!(/** test */));
2120 /// batch
2121 /// .reduce_watermark(watermark, q!(|acc, x| *acc |= x))
2122 /// .entries()
2123 /// .all_ticks()
2124 /// # }, |mut stream| async move {
2125 /// // (2, true)
2126 /// # assert_eq!(stream.next().await.unwrap(), (2, true));
2127 /// # }));
2128 /// # }
2129 /// ```
2130 pub fn reduce_watermark<O2, F, C, Idemp>(
2131 self,
2132 other: impl Into<Optional<O2, Tick<L::Root>, Bounded>>,
2133 comb: impl IntoQuotedMut<'a, F, L, AggFuncAlgebra<C, Idemp>>,
2134 ) -> KeyedSingleton<K, V, L, B>
2135 where
2136 K: Eq + Hash,
2137 O2: Clone,
2138 F: Fn(&mut V, V) + 'a,
2139 C: ValidCommutativityFor<O>,
2140 Idemp: ValidIdempotenceFor<R>,
2141 {
2142 let other: Optional<O2, Tick<L::Root>, Bounded> = other.into();
2143 check_matching_location(&self.location.root(), other.location.outer());
2144 let (f, proof) = comb.splice_fn2_borrow_mut_ctx_props(&self.location);
2145 proof.register_proof(&f);
2146
2147 let ordered = self
2148 .assume_retries::<ExactlyOnce>(nondet!(/** the combinator function is idempotent */))
2149 .assume_ordering::<TotalOrder>(nondet!(/** the combinator function is commutative */));
2150
2151 KeyedSingleton::new(
2152 ordered.location.clone(),
2153 HydroNode::ReduceKeyedWatermark {
2154 f: f.into(),
2155 input: Box::new(ordered.ir_node.replace(HydroNode::Placeholder)),
2156 watermark: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2157 metadata: ordered
2158 .location
2159 .new_node_metadata(KeyedSingleton::<K, V, L, B>::collection_kind()),
2160 },
2161 )
2162 .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
2163 }
2164
2165 /// Given a bounded stream of keys `K`, returns a new keyed stream containing only the groups
2166 /// whose keys are not in the bounded stream.
2167 ///
2168 /// # Example
2169 /// ```rust
2170 /// # #[cfg(feature = "deploy")] {
2171 /// # use hydro_lang::prelude::*;
2172 /// # use futures::StreamExt;
2173 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2174 /// let tick = process.tick();
2175 /// let keyed_stream = process
2176 /// .source_iter(q!(vec![ (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd') ]))
2177 /// .batch(&tick, nondet!(/** test */))
2178 /// .into_keyed();
2179 /// let keys_to_remove = process
2180 /// .source_iter(q!(vec![1, 2]))
2181 /// .batch(&tick, nondet!(/** test */));
2182 /// keyed_stream.filter_key_not_in(keys_to_remove).all_ticks()
2183 /// # .entries()
2184 /// # }, |mut stream| async move {
2185 /// // { 3: ['c'], 4: ['d'] }
2186 /// # let mut results = Vec::new();
2187 /// # for _ in 0..2 {
2188 /// # results.push(stream.next().await.unwrap());
2189 /// # }
2190 /// # results.sort();
2191 /// # assert_eq!(results, vec![(3, 'c'), (4, 'd')]);
2192 /// # }));
2193 /// # }
2194 /// ```
2195 pub fn filter_key_not_in<O2: Ordering, R2: Retries>(
2196 self,
2197 other: Stream<K, L, Bounded, O2, R2>,
2198 ) -> Self
2199 where
2200 K: Eq + Hash,
2201 {
2202 check_matching_location(&self.location, &other.location);
2203
2204 KeyedStream::new(
2205 self.location.clone(),
2206 HydroNode::AntiJoin {
2207 pos: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2208 neg: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2209 metadata: self.location.new_node_metadata(Self::collection_kind()),
2210 },
2211 )
2212 }
2213
2214 /// Emit a keyed stream containing keys shared between two keyed streams,
2215 /// where each value in the output keyed stream is a tuple of
2216 /// (self's value, other's value).
2217 /// If there are multiple values for the same key, this performs a cross product
2218 /// for each matching key.
2219 ///
2220 /// # Example
2221 /// ```rust
2222 /// # #[cfg(feature = "deploy")] {
2223 /// # use hydro_lang::prelude::*;
2224 /// # use futures::StreamExt;
2225 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2226 /// let tick = process.tick();
2227 /// let keyed_data = process
2228 /// .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2229 /// .into_keyed()
2230 /// .batch(&tick, nondet!(/** test */));
2231 /// let other_data = process
2232 /// .source_iter(q!(vec![(1, 100), (2, 200), (2, 201)]))
2233 /// .into_keyed()
2234 /// .batch(&tick, nondet!(/** test */));
2235 /// keyed_data.join_keyed_stream(other_data).entries().all_ticks()
2236 /// # }, |mut stream| async move {
2237 /// // { 1: [(10, 100), (11, 100)], 2: [(20, 200), (20, 201)] } in any order
2238 /// # let mut results = vec![];
2239 /// # for _ in 0..4 {
2240 /// # results.push(stream.next().await.unwrap());
2241 /// # }
2242 /// # results.sort();
2243 /// # assert_eq!(results, vec![(1, (10, 100)), (1, (11, 100)), (2, (20, 200)), (2, (20, 201))]);
2244 /// # }));
2245 /// # }
2246 /// ```
2247 pub fn join_keyed_stream<V2, B2: Boundedness, O2: Ordering, R2: Retries>(
2248 self,
2249 other: KeyedStream<K, V2, L, B2, O2, R2>,
2250 ) -> KeyedStream<
2251 K,
2252 (V, V2),
2253 L,
2254 B,
2255 B2::PreserveOrderIfBounded<NoOrder>,
2256 <R as MinRetries<R2>>::Min,
2257 >
2258 where
2259 K: Eq + Hash + Clone,
2260 R: MinRetries<R2>,
2261 V: Clone,
2262 V2: Clone,
2263 {
2264 self.entries().join(other.entries()).into_keyed()
2265 }
2266
2267 /// Deduplicates values within each key group, emitting each unique value per key
2268 /// exactly once.
2269 ///
2270 /// # Example
2271 /// ```rust
2272 /// # #[cfg(feature = "deploy")] {
2273 /// # use hydro_lang::prelude::*;
2274 /// # use futures::StreamExt;
2275 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2276 /// process
2277 /// .source_iter(q!(vec![(1, 10), (2, 20), (1, 10), (2, 30), (1, 20)]))
2278 /// .into_keyed()
2279 /// .unique()
2280 /// # .entries()
2281 /// # }, |mut stream| async move {
2282 /// // unique values per key: { 1: [10, 20], 2: [20, 30] }
2283 /// # let mut results = Vec::new();
2284 /// # for _ in 0..4 {
2285 /// # results.push(stream.next().await.unwrap());
2286 /// # }
2287 /// # let mut key1: Vec<_> = results.iter().filter(|(k, _)| *k == 1).map(|(_, v)| *v).collect();
2288 /// # let mut key2: Vec<_> = results.iter().filter(|(k, _)| *k == 2).map(|(_, v)| *v).collect();
2289 /// # key1.sort();
2290 /// # key2.sort();
2291 /// # assert_eq!(key1, vec![10, 20]);
2292 /// # assert_eq!(key2, vec![20, 30]);
2293 /// # }));
2294 /// # }
2295 /// ```
2296 pub fn unique(self) -> KeyedStream<K, V, L, B, NoOrder, ExactlyOnce>
2297 where
2298 K: Eq + Hash + Clone,
2299 V: Eq + Hash + Clone,
2300 {
2301 self.entries().unique().into_keyed()
2302 }
2303
2304 /// Sorts the values within each key group in ascending order.
2305 ///
2306 /// The output keyed stream has a [`TotalOrder`] guarantee on the values within
2307 /// each group. This operator will block until all elements in the input stream
2308 /// are available, so it requires the input stream to be [`Bounded`].
2309 ///
2310 /// # Example
2311 /// ```rust
2312 /// # #[cfg(feature = "deploy")] {
2313 /// # use hydro_lang::prelude::*;
2314 /// # use futures::StreamExt;
2315 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2316 /// let tick = process.tick();
2317 /// let numbers = process
2318 /// .source_iter(q!(vec![(1, 3), (2, 1), (1, 1), (2, 2)]))
2319 /// .into_keyed();
2320 /// let batch = numbers.batch(&tick, nondet!(/** test */));
2321 /// batch.sort().all_ticks()
2322 /// # .entries()
2323 /// # }, |mut stream| async move {
2324 /// // values sorted within each key: { 1: [1, 3], 2: [1, 2] }
2325 /// # let mut results = Vec::new();
2326 /// # for _ in 0..4 {
2327 /// # results.push(stream.next().await.unwrap());
2328 /// # }
2329 /// # let key1_vals: Vec<_> = results.iter().filter(|(k, _)| *k == 1).map(|(_, v)| *v).collect();
2330 /// # let key2_vals: Vec<_> = results.iter().filter(|(k, _)| *k == 2).map(|(_, v)| *v).collect();
2331 /// # assert_eq!(key1_vals, vec![1, 3]);
2332 /// # assert_eq!(key2_vals, vec![1, 2]);
2333 /// # }));
2334 /// # }
2335 /// ```
2336 pub fn sort(self) -> KeyedStream<K, V, L, Bounded, TotalOrder, R>
2337 where
2338 B: IsBounded,
2339 K: Ord,
2340 V: Ord,
2341 {
2342 self.entries().sort().into_keyed()
2343 }
2344
2345 /// Produces a new keyed stream that combines the groups of the inputs by first emitting the
2346 /// elements of the `self` stream, and then emits the elements of the `other` stream (if a key
2347 /// is only present in one of the inputs, its values are passed through as-is). The output has
2348 /// a [`TotalOrder`] guarantee if and only if both inputs have a [`TotalOrder`] guarantee.
2349 ///
2350 /// Currently, both input streams must be [`Bounded`]. This operator will block
2351 /// on the first stream until all its elements are available. In a future version,
2352 /// we will relax the requirement on the `other` stream.
2353 ///
2354 /// # Example
2355 /// ```rust
2356 /// # #[cfg(feature = "deploy")] {
2357 /// # use hydro_lang::prelude::*;
2358 /// # use futures::StreamExt;
2359 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2360 /// let tick = process.tick();
2361 /// let numbers = process.source_iter(q!(vec![(0, 1), (1, 3)])).into_keyed();
2362 /// let batch = numbers.batch(&tick, nondet!(/** test */));
2363 /// batch.clone().map(q!(|x| x + 1)).chain(batch).all_ticks()
2364 /// # .entries()
2365 /// # }, |mut stream| async move {
2366 /// // { 0: [2, 1], 1: [4, 3] }
2367 /// # let mut results = Vec::new();
2368 /// # for _ in 0..4 {
2369 /// # results.push(stream.next().await.unwrap());
2370 /// # }
2371 /// # results.sort();
2372 /// # assert_eq!(results, vec![(0, 1), (0, 2), (1, 3), (1, 4)]);
2373 /// # }));
2374 /// # }
2375 /// ```
2376 pub fn chain<O2: Ordering, R2: Retries>(
2377 self,
2378 other: KeyedStream<K, V, L, Bounded, O2, R2>,
2379 ) -> KeyedStream<K, V, L, Bounded, <O as MinOrder<O2>>::Min, <R as MinRetries<R2>>::Min>
2380 where
2381 B: IsBounded,
2382 O: MinOrder<O2>,
2383 R: MinRetries<R2>,
2384 {
2385 let this = self.make_bounded();
2386 check_matching_location(&this.location, &other.location);
2387
2388 KeyedStream::new(
2389 this.location.clone(),
2390 HydroNode::Chain {
2391 first: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
2392 second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2393 metadata: this.location.new_node_metadata(KeyedStream::<
2394 K,
2395 V,
2396 L,
2397 Bounded,
2398 <O as MinOrder<O2>>::Min,
2399 <R as MinRetries<R2>>::Min,
2400 >::collection_kind()),
2401 },
2402 )
2403 }
2404
2405 /// Emit a keyed stream containing keys shared between the keyed stream and the
2406 /// keyed singleton, where each value in the output keyed stream is a tuple of
2407 /// (the keyed stream's value, the keyed singleton's value).
2408 ///
2409 /// # Example
2410 /// ```rust
2411 /// # #[cfg(feature = "deploy")] {
2412 /// # use hydro_lang::prelude::*;
2413 /// # use futures::StreamExt;
2414 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2415 /// let tick = process.tick();
2416 /// let keyed_data = process
2417 /// .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2418 /// .into_keyed()
2419 /// .batch(&tick, nondet!(/** test */));
2420 /// let singleton_data = process
2421 /// .source_iter(q!(vec![(1, 100), (2, 200)]))
2422 /// .into_keyed()
2423 /// .batch(&tick, nondet!(/** test */))
2424 /// .first();
2425 /// keyed_data.join_keyed_singleton(singleton_data).entries().all_ticks()
2426 /// # }, |mut stream| async move {
2427 /// // { 1: [(10, 100), (11, 100)], 2: [(20, 200)] } in any order
2428 /// # let mut results = vec![];
2429 /// # for _ in 0..3 {
2430 /// # results.push(stream.next().await.unwrap());
2431 /// # }
2432 /// # results.sort();
2433 /// # assert_eq!(results, vec![(1, (10, 100)), (1, (11, 100)), (2, (20, 200))]);
2434 /// # }));
2435 /// # }
2436 /// ```
2437 pub fn join_keyed_singleton<V2: Clone, B2: IsBounded>(
2438 self,
2439 other: KeyedSingleton<K, V2, L, B2>,
2440 ) -> KeyedStream<K, (V, V2), L, B, O, R>
2441 where
2442 K: Eq + Hash + Clone,
2443 V: Clone,
2444 {
2445 let ir_node = if B2::BOUNDED {
2446 HydroNode::JoinHalf {
2447 left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2448 right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2449 metadata: self
2450 .location
2451 .new_node_metadata(KeyedStream::<K, (V, V2), L, B, O, R>::collection_kind()),
2452 }
2453 } else {
2454 HydroNode::Join {
2455 left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2456 right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2457 metadata: self
2458 .location
2459 .new_node_metadata(KeyedStream::<K, (V, V2), L, B, O, R>::collection_kind()),
2460 }
2461 };
2462
2463 KeyedStream::new(self.location.clone(), ir_node)
2464 }
2465
2466 /// Gets the values associated with a specific key from the keyed stream.
2467 /// Returns an empty stream if the key is `None` or there are no associated values.
2468 ///
2469 /// # Example
2470 /// ```rust
2471 /// # #[cfg(feature = "deploy")] {
2472 /// # use hydro_lang::prelude::*;
2473 /// # use futures::StreamExt;
2474 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2475 /// let tick = process.tick();
2476 /// let keyed_data = process
2477 /// .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2478 /// .into_keyed()
2479 /// .batch(&tick, nondet!(/** test */));
2480 /// let key = tick.singleton(q!(1));
2481 /// keyed_data.get(key).all_ticks()
2482 /// # }, |mut stream| async move {
2483 /// // 10, 11
2484 /// # let mut results = vec![];
2485 /// # for _ in 0..2 {
2486 /// # results.push(stream.next().await.unwrap());
2487 /// # }
2488 /// # results.sort();
2489 /// # assert_eq!(results, vec![10, 11]);
2490 /// # }));
2491 /// # }
2492 /// ```
2493 pub fn get(self, key: impl Into<Optional<K, L, Bounded>>) -> Stream<V, L, B, O, R>
2494 where
2495 K: Eq + Hash + Clone,
2496 V: Clone,
2497 {
2498 let joined =
2499 self.join_keyed_singleton(key.into().map(q!(|k| (k, ()))).into_keyed_singleton());
2500
2501 if O::ORDERING_KIND == StreamOrder::TotalOrder {
2502 joined
2503 .use_ordering_type::<TotalOrder>()
2504 .cast_at_most_one_key()
2505 .map(q!(|(_, (v, _))| v))
2506 .weaken_ordering()
2507 } else {
2508 joined.values().map(q!(|(v, _)| v)).use_ordering_type()
2509 }
2510 }
2511
2512 /// For each value in `self`, find the matching key in `lookup`.
2513 /// The output is a keyed stream with the key from `self`, and a value
2514 /// that is a tuple of (`self`'s value, Option<`lookup`'s value>).
2515 /// If the key is not present in `lookup`, the option will be [`None`].
2516 ///
2517 /// # Example
2518 /// ```rust
2519 /// # #[cfg(feature = "deploy")] {
2520 /// # use hydro_lang::prelude::*;
2521 /// # use futures::StreamExt;
2522 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2523 /// # let tick = process.tick();
2524 /// let requests = // { 1: [10, 11], 2: 20 }
2525 /// # process
2526 /// # .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2527 /// # .into_keyed()
2528 /// # .batch(&tick, nondet!(/** test */));
2529 /// let other_data = // { 10: 100, 11: 110 }
2530 /// # process
2531 /// # .source_iter(q!(vec![(10, 100), (11, 110)]))
2532 /// # .into_keyed()
2533 /// # .batch(&tick, nondet!(/** test */))
2534 /// # .first();
2535 /// requests.lookup_keyed_singleton(other_data)
2536 /// # .entries().all_ticks()
2537 /// # }, |mut stream| async move {
2538 /// // { 1: [(10, Some(100)), (11, Some(110))], 2: (20, None) }
2539 /// # let mut results = vec![];
2540 /// # for _ in 0..3 {
2541 /// # results.push(stream.next().await.unwrap());
2542 /// # }
2543 /// # results.sort();
2544 /// # assert_eq!(results, vec![(1, (10, Some(100))), (1, (11, Some(110))), (2, (20, None))]);
2545 /// # }));
2546 /// # }
2547 /// ```
2548 pub fn lookup_keyed_singleton<V2>(
2549 self,
2550 lookup: KeyedSingleton<V, V2, L, Bounded>,
2551 ) -> KeyedStream<K, (V, Option<V2>), L, Bounded, NoOrder, R>
2552 where
2553 B: IsBounded,
2554 K: Eq + Hash + Clone,
2555 V: Eq + Hash + Clone,
2556 V2: Clone,
2557 {
2558 self.lookup_keyed_stream(lookup.into_keyed_stream().weaken_retries::<R>())
2559 }
2560
2561 /// For each value in `self`, find the matching key in `lookup`.
2562 /// The output is a keyed stream with the key from `self`, and a value
2563 /// that is a tuple of (`self`'s value, Option<`lookup`'s value>).
2564 /// If the key is not present in `lookup`, the option will be [`None`].
2565 ///
2566 /// # Example
2567 /// ```rust
2568 /// # #[cfg(feature = "deploy")] {
2569 /// # use hydro_lang::prelude::*;
2570 /// # use futures::StreamExt;
2571 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2572 /// # let tick = process.tick();
2573 /// let requests = // { 1: [10, 11], 2: 20 }
2574 /// # process
2575 /// # .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2576 /// # .into_keyed()
2577 /// # .batch(&tick, nondet!(/** test */));
2578 /// let other_data = // { 10: [100, 101], 11: 110 }
2579 /// # process
2580 /// # .source_iter(q!(vec![(10, 100), (10, 101), (11, 110)]))
2581 /// # .into_keyed()
2582 /// # .batch(&tick, nondet!(/** test */));
2583 /// requests.lookup_keyed_stream(other_data)
2584 /// # .entries().all_ticks()
2585 /// # }, |mut stream| async move {
2586 /// // { 1: [(10, Some(100)), (10, Some(101)), (11, Some(110))], 2: (20, None) }
2587 /// # let mut results = vec![];
2588 /// # for _ in 0..4 {
2589 /// # results.push(stream.next().await.unwrap());
2590 /// # }
2591 /// # results.sort();
2592 /// # assert_eq!(results, vec![(1, (10, Some(100))), (1, (10, Some(101))), (1, (11, Some(110))), (2, (20, None))]);
2593 /// # }));
2594 /// # }
2595 /// ```
2596 pub fn lookup_keyed_stream<V2, O2: Ordering, R2: Retries>(
2597 self,
2598 lookup: KeyedStream<V, V2, L, Bounded, O2, R2>,
2599 ) -> KeyedStream<K, (V, Option<V2>), L, Bounded, NoOrder, <R as MinRetries<R2>>::Min>
2600 where
2601 B: IsBounded,
2602 K: Eq + Hash + Clone,
2603 V: Eq + Hash + Clone,
2604 V2: Clone,
2605 R: MinRetries<R2>,
2606 {
2607 let inverted = self
2608 .make_bounded()
2609 .entries()
2610 .map(q!(|(key, lookup_value)| (lookup_value, key)))
2611 .into_keyed();
2612 let found = inverted
2613 .clone()
2614 .join_keyed_stream(lookup.clone())
2615 .entries()
2616 .map(q!(|(lookup_value, (key, value))| (
2617 key,
2618 (lookup_value, Some(value))
2619 )))
2620 .into_keyed();
2621 let not_found = inverted
2622 .filter_key_not_in(lookup.keys())
2623 .entries()
2624 .map(q!(|(lookup_value, key)| (key, (lookup_value, None))))
2625 .into_keyed();
2626
2627 found.chain(not_found.weaken_retries::<<R as MinRetries<R2>>::Min>())
2628 }
2629
2630 /// Shifts this keyed stream into an atomic context, which guarantees that any downstream logic
2631 /// will all be executed synchronously before any outputs are yielded (in [`KeyedStream::end_atomic`]).
2632 ///
2633 /// This is useful to enforce local consistency constraints, such as ensuring that a write is
2634 /// processed before an acknowledgement is emitted.
2635 pub fn atomic(self) -> KeyedStream<K, V, Atomic<L>, B, O, R> {
2636 let id = self.location.flow_state().borrow_mut().next_clock_id();
2637 let out_location = Atomic {
2638 tick: Tick {
2639 id,
2640 l: self.location.clone(),
2641 },
2642 };
2643 KeyedStream::new(
2644 out_location.clone(),
2645 HydroNode::BeginAtomic {
2646 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2647 metadata: out_location
2648 .new_node_metadata(KeyedStream::<K, V, Atomic<L>, B, O, R>::collection_kind()),
2649 },
2650 )
2651 }
2652
2653 /// Given a tick, returns a keyed stream corresponding to a batch of elements segmented by
2654 /// that tick. These batches are guaranteed to be contiguous across ticks and preserve
2655 /// the order of the input.
2656 ///
2657 /// # Non-Determinism
2658 /// The batch boundaries are non-deterministic and may change across executions.
2659 pub fn batch<L2: Location<'a, DropConsistency = L::DropConsistency>>(
2660 self,
2661 tick: &Tick<L2>,
2662 nondet: NonDet,
2663 ) -> KeyedStream<K, V, Tick<L::DropConsistency>, Bounded, O, R> {
2664 let _ = nondet;
2665 assert_eq!(Location::id(tick.outer()), Location::id(&self.location));
2666 KeyedStream::new(
2667 tick.drop_consistency(),
2668 HydroNode::Batch {
2669 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2670 metadata: tick.new_node_metadata(
2671 KeyedStream::<K, V, Tick<L>, Bounded, O, R>::collection_kind(),
2672 ),
2673 },
2674 )
2675 }
2676}
2677
2678impl<'a, K1, K2, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
2679 KeyedStream<(K1, K2), V, L, B, O, R>
2680{
2681 /// Produces a new keyed stream by dropping the first element of the compound key.
2682 ///
2683 /// Because multiple keys may share the same suffix, this operation results in re-grouping
2684 /// of the values under the new keys. The values across groups with the same new key
2685 /// will be interleaved, so the resulting stream has [`NoOrder`] within each group.
2686 ///
2687 /// # Example
2688 /// ```rust
2689 /// # #[cfg(feature = "deploy")] {
2690 /// # use hydro_lang::prelude::*;
2691 /// # use futures::StreamExt;
2692 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2693 /// process
2694 /// .source_iter(q!(vec![((1, 10), 2), ((1, 10), 3), ((2, 20), 4)]))
2695 /// .into_keyed()
2696 /// .drop_key_prefix()
2697 /// # .entries()
2698 /// # }, |mut stream| async move {
2699 /// // { 10: [2, 3], 20: [4] }
2700 /// # let mut results = Vec::new();
2701 /// # for _ in 0..3 {
2702 /// # results.push(stream.next().await.unwrap());
2703 /// # }
2704 /// # results.sort();
2705 /// # assert_eq!(results, vec![(10, 2), (10, 3), (20, 4)]);
2706 /// # }));
2707 /// # }
2708 /// ```
2709 pub fn drop_key_prefix(self) -> KeyedStream<K2, V, L, B, NoOrder, R> {
2710 self.entries()
2711 .map(q!(|((_k1, k2), v)| (k2, v)))
2712 .into_keyed()
2713 }
2714}
2715
2716impl<'a, K, V, L: Location<'a>, O: Ordering, R: Retries> KeyedStream<K, V, L, Unbounded, O, R> {
2717 /// Produces a new keyed stream that "merges" the inputs by interleaving the elements
2718 /// of any overlapping groups. The result has [`NoOrder`] on each group because the
2719 /// order of interleaving is not guaranteed. If the keys across both inputs do not overlap,
2720 /// the ordering will be deterministic and you can safely use [`Self::assume_ordering`].
2721 ///
2722 /// Currently, both input streams must be [`Unbounded`].
2723 ///
2724 /// # Example
2725 /// ```rust
2726 /// # #[cfg(feature = "deploy")] {
2727 /// # use hydro_lang::prelude::*;
2728 /// # use futures::StreamExt;
2729 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2730 /// let numbers1: KeyedStream<i32, i32, _> = // { 1: [2], 3: [4] }
2731 /// # process.source_iter(q!(vec![(1, 2), (3, 4)])).into_keyed().into();
2732 /// let numbers2: KeyedStream<i32, i32, _> = // { 1: [3], 3: [5] }
2733 /// # process.source_iter(q!(vec![(1, 3), (3, 5)])).into_keyed().into();
2734 /// numbers1.merge_unordered(numbers2)
2735 /// # .entries()
2736 /// # }, |mut stream| async move {
2737 /// // { 1: [2, 3], 3: [4, 5] } with each group in unknown order
2738 /// # let mut results = Vec::new();
2739 /// # for _ in 0..4 {
2740 /// # results.push(stream.next().await.unwrap());
2741 /// # }
2742 /// # results.sort();
2743 /// # assert_eq!(results, vec![(1, 2), (1, 3), (3, 4), (3, 5)]);
2744 /// # }));
2745 /// # }
2746 /// ```
2747 pub fn merge_unordered<O2: Ordering, R2: Retries>(
2748 self,
2749 other: KeyedStream<K, V, L, Unbounded, O2, R2>,
2750 ) -> KeyedStream<K, V, L, Unbounded, NoOrder, <R as MinRetries<R2>>::Min>
2751 where
2752 R: MinRetries<R2>,
2753 {
2754 KeyedStream::new(
2755 self.location.clone(),
2756 HydroNode::Chain {
2757 first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2758 second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2759 metadata: self.location.new_node_metadata(KeyedStream::<
2760 K,
2761 V,
2762 L,
2763 Unbounded,
2764 NoOrder,
2765 <R as MinRetries<R2>>::Min,
2766 >::collection_kind()),
2767 },
2768 )
2769 }
2770
2771 /// Deprecated: use [`KeyedStream::merge_unordered`] instead.
2772 #[deprecated(note = "use `merge_unordered` instead")]
2773 pub fn interleave<O2: Ordering, R2: Retries>(
2774 self,
2775 other: KeyedStream<K, V, L, Unbounded, O2, R2>,
2776 ) -> KeyedStream<K, V, L, Unbounded, NoOrder, <R as MinRetries<R2>>::Min>
2777 where
2778 R: MinRetries<R2>,
2779 {
2780 self.merge_unordered(other)
2781 }
2782}
2783
2784impl<'a, K, V, L: Location<'a>, B: Boundedness, R: Retries> KeyedStream<K, V, L, B, TotalOrder, R> {
2785 /// Produces a new keyed stream that combines the elements of the two input keyed streams,
2786 /// preserving the relative order of elements within each group of each input.
2787 ///
2788 /// Because each group in both inputs is [`TotalOrder`], the output preserves the relative
2789 /// order of elements within each group of each input, and the result is [`TotalOrder`].
2790 ///
2791 /// # Non-Determinism
2792 /// For groups whose key appears in both inputs, the order in which the elements of the two
2793 /// inputs are interleaved *within that group* is non-deterministic, so the order of elements
2794 /// will vary across runs. If the keys across both inputs do not overlap, the ordering is
2795 /// deterministic. If the output order within each group is irrelevant, use
2796 /// [`KeyedStream::merge_unordered`] instead, which is deterministic but emits an unordered
2797 /// keyed stream.
2798 ///
2799 /// # Example
2800 /// ```rust
2801 /// # #[cfg(feature = "deploy")] {
2802 /// # use hydro_lang::prelude::*;
2803 /// # use futures::StreamExt;
2804 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2805 /// let numbers1: KeyedStream<i32, i32, _> = // { 1: [2], 3: [4] }
2806 /// # process.source_iter(q!(vec![(1, 2), (3, 4)])).into_keyed().into();
2807 /// let numbers2: KeyedStream<i32, i32, _> = // { 1: [3], 3: [5] }
2808 /// # process.source_iter(q!(vec![(1, 3), (3, 5)])).into_keyed().into();
2809 /// numbers1.merge_ordered(numbers2, nondet!(/** example */))
2810 /// # .entries()
2811 /// # }, |mut stream| async move {
2812 /// // { 1: [2, 3], 3: [4, 5] } with each group interleaved in some order
2813 /// # let mut results = Vec::new();
2814 /// # for _ in 0..4 {
2815 /// # results.push(stream.next().await.unwrap());
2816 /// # }
2817 /// # results.sort();
2818 /// # assert_eq!(results, vec![(1, 2), (1, 3), (3, 4), (3, 5)]);
2819 /// # }));
2820 /// # }
2821 /// ```
2822 pub fn merge_ordered<R2: Retries>(
2823 self,
2824 other: KeyedStream<K, V, L, B, TotalOrder, R2>,
2825 _nondet: NonDet,
2826 ) -> KeyedStream<K, V, L::DropConsistency, B, TotalOrder, <R as MinRetries<R2>>::Min>
2827 where
2828 R: MinRetries<R2>,
2829 {
2830 let target_location = self.location.drop_consistency();
2831 KeyedStream::new(
2832 target_location.clone(),
2833 HydroNode::MergeOrdered {
2834 first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2835 second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2836 metadata: target_location.new_node_metadata(KeyedStream::<
2837 K,
2838 V,
2839 L::DropConsistency,
2840 B,
2841 TotalOrder,
2842 <R as MinRetries<R2>>::Min,
2843 >::collection_kind()),
2844 },
2845 )
2846 }
2847}
2848
2849impl<'a, K, V, L, B: Boundedness, O: Ordering, R: Retries> KeyedStream<K, V, Atomic<L>, B, O, R>
2850where
2851 L: Location<'a>,
2852{
2853 /// Returns a keyed stream corresponding to the latest batch of elements being atomically
2854 /// processed. These batches are guaranteed to be contiguous across ticks and preserve
2855 /// the order of the input. The output keyed stream will execute in the [`Tick`] that was
2856 /// used to create the atomic section.
2857 ///
2858 /// # Non-Determinism
2859 /// The batch boundaries are non-deterministic and may change across executions.
2860 pub fn batch_atomic<L2: Location<'a, DropConsistency = L::DropConsistency>>(
2861 self,
2862 tick: &Tick<L2>,
2863 nondet: NonDet,
2864 ) -> KeyedStream<K, V, Tick<L::DropConsistency>, Bounded, O, R> {
2865 let _ = nondet;
2866 KeyedStream::new(
2867 tick.drop_consistency(),
2868 HydroNode::Batch {
2869 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2870 metadata: tick.new_node_metadata(
2871 KeyedStream::<K, V, Tick<L>, Bounded, O, R>::collection_kind(),
2872 ),
2873 },
2874 )
2875 }
2876
2877 /// Yields the elements of this keyed stream back into a top-level, asynchronous execution context.
2878 /// See [`KeyedStream::atomic`] for more details.
2879 pub fn end_atomic(self) -> KeyedStream<K, V, L, B, O, R> {
2880 KeyedStream::new(
2881 self.location.tick.l.clone(),
2882 HydroNode::EndAtomic {
2883 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2884 metadata: self
2885 .location
2886 .tick
2887 .l
2888 .new_node_metadata(KeyedStream::<K, V, L, B, O, R>::collection_kind()),
2889 },
2890 )
2891 }
2892}
2893
2894impl<'a, K, V, L, O: Ordering, R: Retries> KeyedStream<K, V, Tick<L>, Bounded, O, R>
2895where
2896 L: Location<'a>,
2897{
2898 /// Asynchronously yields this batch of keyed elements outside the tick as an unbounded keyed stream,
2899 /// which will stream all the elements across _all_ tick iterations by concatenating the batches for
2900 /// each key.
2901 pub fn all_ticks(self) -> KeyedStream<K, V, L, Unbounded, O, R> {
2902 KeyedStream::new(
2903 self.location.outer().clone(),
2904 HydroNode::YieldConcat {
2905 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2906 metadata: self.location.outer().new_node_metadata(KeyedStream::<
2907 K,
2908 V,
2909 L,
2910 Unbounded,
2911 O,
2912 R,
2913 >::collection_kind(
2914 )),
2915 },
2916 )
2917 }
2918
2919 /// Synchronously yields this batch of keyed elements outside the tick as an unbounded keyed stream,
2920 /// which will stream all the elements across _all_ tick iterations by concatenating the batches for
2921 /// each key.
2922 ///
2923 /// Unlike [`KeyedStream::all_ticks`], this preserves synchronous execution, as the output stream
2924 /// is emitted in an [`Atomic`] context that will process elements synchronously with the input
2925 /// stream's [`Tick`] context.
2926 pub fn all_ticks_atomic(self) -> KeyedStream<K, V, Atomic<L>, Unbounded, O, R> {
2927 let out_location = Atomic {
2928 tick: self.location.clone(),
2929 };
2930
2931 KeyedStream::new(
2932 out_location.clone(),
2933 HydroNode::YieldConcat {
2934 inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2935 metadata: out_location.new_node_metadata(KeyedStream::<
2936 K,
2937 V,
2938 Atomic<L>,
2939 Unbounded,
2940 O,
2941 R,
2942 >::collection_kind()),
2943 },
2944 )
2945 }
2946
2947 /// Transforms the keyed stream using the given closure in "stateful" mode, where stateful operators
2948 /// such as `fold` retrain their memory for each key across ticks rather than resetting across batches of each key.
2949 ///
2950 /// This API is particularly useful for stateful computation on batches of data, such as
2951 /// maintaining an accumulated state that is up to date with the current batch.
2952 ///
2953 /// # Example
2954 /// ```rust
2955 /// # #[cfg(feature = "deploy")] {
2956 /// # use hydro_lang::prelude::*;
2957 /// # use futures::StreamExt;
2958 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2959 /// let tick = process.tick();
2960 /// # // ticks are lazy by default, forces the second tick to run
2961 /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
2962 /// # let batch_first_tick = process
2963 /// # .source_iter(q!(vec![(0, 1), (1, 2), (2, 3), (3, 4)]))
2964 /// # .into_keyed()
2965 /// # .batch(&tick, nondet!(/** test */));
2966 /// # let batch_second_tick = process
2967 /// # .source_iter(q!(vec![(0, 5), (1, 6), (2, 7)]))
2968 /// # .into_keyed()
2969 /// # .batch(&tick, nondet!(/** test */))
2970 /// # .defer_tick(); // appears on the second tick
2971 /// let input = batch_first_tick.chain(batch_second_tick).all_ticks();
2972 ///
2973 /// input.batch(&tick, nondet!(/** test */))
2974 /// .across_ticks(|s| s.reduce(q!(|sum, new| {
2975 /// *sum += new;
2976 /// }))).entries().all_ticks()
2977 /// # }, |mut stream| async move {
2978 /// // First tick: [(0, 1), (1, 2), (2, 3), (3, 4)]
2979 /// # let mut results = Vec::new();
2980 /// # for _ in 0..4 {
2981 /// # results.push(stream.next().await.unwrap());
2982 /// # }
2983 /// # results.sort();
2984 /// # assert_eq!(results, vec![(0, 1), (1, 2), (2, 3), (3, 4)]);
2985 /// // Second tick: [(0, 6), (1, 8), (2, 10), (3, 4)]
2986 /// # results.clear();
2987 /// # for _ in 0..4 {
2988 /// # results.push(stream.next().await.unwrap());
2989 /// # }
2990 /// # results.sort();
2991 /// # assert_eq!(results, vec![(0, 6), (1, 8), (2, 10), (3, 4)]);
2992 /// # }));
2993 /// # }
2994 /// ```
2995 pub fn across_ticks<Out: BatchAtomic<'a>>(
2996 self,
2997 thunk: impl FnOnce(KeyedStream<K, V, Atomic<L>, Unbounded, O, R>) -> Out,
2998 ) -> Out::Batched {
2999 thunk(self.all_ticks_atomic()).batched_atomic()
3000 }
3001
3002 /// Shifts the entries in `self` to the **next tick**, so that the returned keyed stream at
3003 /// tick `T` always has the entries of `self` at tick `T - 1`.
3004 ///
3005 /// At tick `0`, the output keyed stream is empty, since there is no previous tick.
3006 ///
3007 /// This operator enables stateful iterative processing with ticks, by sending data from one
3008 /// tick to the next. For example, you can use it to combine inputs across consecutive batches.
3009 ///
3010 /// # Example
3011 /// ```rust
3012 /// # #[cfg(feature = "deploy")] {
3013 /// # use hydro_lang::prelude::*;
3014 /// # use futures::StreamExt;
3015 /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3016 /// let tick = process.tick();
3017 /// # // ticks are lazy by default, forces the second tick to run
3018 /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
3019 /// # let batch_first_tick = process
3020 /// # .source_iter(q!(vec![(1, 2), (1, 3)]))
3021 /// # .batch(&tick, nondet!(/** test */))
3022 /// # .into_keyed();
3023 /// # let batch_second_tick = process
3024 /// # .source_iter(q!(vec![(1, 4), (2, 5)]))
3025 /// # .batch(&tick, nondet!(/** test */))
3026 /// # .defer_tick()
3027 /// # .into_keyed(); // appears on the second tick
3028 /// let changes_across_ticks = // { 1: [2, 3] } (first tick), { 1: [4], 2: [5] } (second tick)
3029 /// # batch_first_tick.chain(batch_second_tick);
3030 /// changes_across_ticks.clone().defer_tick().chain( // from the previous tick
3031 /// changes_across_ticks // from the current tick
3032 /// )
3033 /// # .entries().all_ticks()
3034 /// # }, |mut stream| async move {
3035 /// // First tick: { 1: [2, 3] }
3036 /// # let mut results = Vec::new();
3037 /// # for _ in 0..2 {
3038 /// # results.push(stream.next().await.unwrap());
3039 /// # }
3040 /// # results.sort();
3041 /// # assert_eq!(results, vec![(1, 2), (1, 3)]);
3042 /// // Second tick: { 1: [2, 3, 4], 2: [5] }
3043 /// # results.clear();
3044 /// # for _ in 0..4 {
3045 /// # results.push(stream.next().await.unwrap());
3046 /// # }
3047 /// # results.sort();
3048 /// # assert_eq!(results, vec![(1, 2), (1, 3), (1, 4), (2, 5)]);
3049 /// // Third tick: { 1: [4], 2: [5] }
3050 /// # results.clear();
3051 /// # for _ in 0..2 {
3052 /// # results.push(stream.next().await.unwrap());
3053 /// # }
3054 /// # results.sort();
3055 /// # assert_eq!(results, vec![(1, 4), (2, 5)]);
3056 /// # }));
3057 /// # }
3058 /// ```
3059 pub fn defer_tick(self) -> KeyedStream<K, V, Tick<L>, Bounded, O, R> {
3060 KeyedStream::new(
3061 self.location.clone(),
3062 HydroNode::DeferTick {
3063 input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3064 metadata: self.location.new_node_metadata(KeyedStream::<
3065 K,
3066 V,
3067 Tick<L>,
3068 Bounded,
3069 O,
3070 R,
3071 >::collection_kind()),
3072 },
3073 )
3074 }
3075}
3076
3077#[cfg(test)]
3078mod tests {
3079 #[cfg(feature = "deploy")]
3080 use futures::{SinkExt, StreamExt};
3081 #[cfg(feature = "deploy")]
3082 use hydro_deploy::Deployment;
3083 #[cfg(any(feature = "deploy", feature = "sim"))]
3084 use stageleft::q;
3085
3086 #[cfg(any(feature = "deploy", feature = "sim"))]
3087 use crate::compile::builder::FlowBuilder;
3088 #[cfg(feature = "deploy")]
3089 use crate::live_collections::stream::ExactlyOnce;
3090 #[cfg(feature = "sim")]
3091 use crate::live_collections::stream::{NoOrder, TotalOrder};
3092 #[cfg(any(feature = "deploy", feature = "sim"))]
3093 use crate::location::Location;
3094 #[cfg(feature = "sim")]
3095 use crate::networking::TCP;
3096 #[cfg(any(feature = "deploy", feature = "sim"))]
3097 use crate::nondet::nondet;
3098 #[cfg(feature = "deploy")]
3099 use crate::properties::manual_proof;
3100
3101 #[cfg(feature = "deploy")]
3102 #[tokio::test]
3103 async fn get_unbounded_keyed_stream_bounded_singleton() {
3104 let mut deployment = Deployment::new();
3105
3106 let mut flow = FlowBuilder::new();
3107 let node = flow.process::<()>();
3108 let external = flow.external::<()>();
3109
3110 let (input_send, input_stream) =
3111 node.source_external_bincode::<_, (i32, i32), _, ExactlyOnce>(&external);
3112
3113 let key = node.singleton(q!(1));
3114
3115 let out = input_stream
3116 .into_keyed()
3117 .get(key)
3118 .send_bincode_external(&external);
3119
3120 let nodes = flow
3121 .with_process(&node, deployment.Localhost())
3122 .with_external(&external, deployment.Localhost())
3123 .deploy(&mut deployment);
3124
3125 deployment.deploy().await.unwrap();
3126
3127 let mut input_send = nodes.connect(input_send).await;
3128 let mut out = nodes.connect(out).await;
3129
3130 deployment.start().await.unwrap();
3131
3132 // First batch
3133 input_send.send((1, 10)).await.unwrap();
3134 input_send.send((2, 20)).await.unwrap();
3135 assert_eq!(out.next().await.unwrap(), 10);
3136
3137 // Second batch
3138 input_send.send((1, 11)).await.unwrap();
3139 input_send.send((2, 21)).await.unwrap();
3140 assert_eq!(out.next().await.unwrap(), 11);
3141 }
3142
3143 #[cfg(feature = "deploy")]
3144 #[tokio::test]
3145 async fn reduce_watermark_filter() {
3146 let mut deployment = Deployment::new();
3147
3148 let mut flow = FlowBuilder::new();
3149 let node = flow.process::<()>();
3150 let external = flow.external::<()>();
3151
3152 let node_tick = node.tick();
3153 let watermark = node_tick.singleton(q!(2));
3154
3155 let sum = node
3156 .source_stream(q!(tokio_stream::iter([
3157 (0, 100),
3158 (1, 101),
3159 (2, 102),
3160 (2, 102)
3161 ])))
3162 .into_keyed()
3163 .reduce_watermark(
3164 watermark,
3165 q!(|acc, v| {
3166 *acc += v;
3167 }),
3168 )
3169 .snapshot(&node_tick, nondet!(/** test */))
3170 .entries()
3171 .all_ticks()
3172 .send_bincode_external(&external);
3173
3174 let nodes = flow
3175 .with_process(&node, deployment.Localhost())
3176 .with_external(&external, deployment.Localhost())
3177 .deploy(&mut deployment);
3178
3179 deployment.deploy().await.unwrap();
3180
3181 let mut out = nodes.connect(sum).await;
3182
3183 deployment.start().await.unwrap();
3184
3185 assert_eq!(out.next().await.unwrap(), (2, 204));
3186 }
3187
3188 #[cfg(feature = "deploy")]
3189 #[tokio::test]
3190 async fn reduce_watermark_bounded() {
3191 let mut deployment = Deployment::new();
3192
3193 let mut flow = FlowBuilder::new();
3194 let node = flow.process::<()>();
3195 let external = flow.external::<()>();
3196
3197 let node_tick = node.tick();
3198 let watermark = node_tick.singleton(q!(2));
3199
3200 let sum = node
3201 .source_iter(q!([(0, 100), (1, 101), (2, 102), (2, 102)]))
3202 .into_keyed()
3203 .reduce_watermark(
3204 watermark,
3205 q!(|acc, v| {
3206 *acc += v;
3207 }),
3208 )
3209 .entries()
3210 .send_bincode_external(&external);
3211
3212 let nodes = flow
3213 .with_process(&node, deployment.Localhost())
3214 .with_external(&external, deployment.Localhost())
3215 .deploy(&mut deployment);
3216
3217 deployment.deploy().await.unwrap();
3218
3219 let mut out = nodes.connect(sum).await;
3220
3221 deployment.start().await.unwrap();
3222
3223 assert_eq!(out.next().await.unwrap(), (2, 204));
3224 }
3225
3226 #[cfg(feature = "deploy")]
3227 #[tokio::test]
3228 async fn reduce_watermark_garbage_collect() {
3229 let mut deployment = Deployment::new();
3230
3231 let mut flow = FlowBuilder::new();
3232 let node = flow.process::<()>();
3233 let external = flow.external::<()>();
3234 let (tick_send, tick_trigger) =
3235 node.source_external_bincode::<_, _, _, ExactlyOnce>(&external);
3236
3237 let node_tick = node.tick();
3238 let (watermark_complete_cycle, watermark) =
3239 node_tick.cycle_with_initial(node_tick.singleton(q!(2)));
3240 let next_watermark = watermark.clone().map(q!(|v| v + 1));
3241 watermark_complete_cycle.complete_next_tick(next_watermark);
3242
3243 let tick_triggered_input = node_tick
3244 .singleton(q!((3, 103)))
3245 .into_stream()
3246 .filter_if(
3247 tick_trigger
3248 .clone()
3249 .batch(&node_tick, nondet!(/** test */))
3250 .first()
3251 .is_some(),
3252 )
3253 .all_ticks();
3254
3255 let sum = node
3256 .source_stream(q!(tokio_stream::iter([
3257 (0, 100),
3258 (1, 101),
3259 (2, 102),
3260 (2, 102)
3261 ])))
3262 .merge_unordered(tick_triggered_input)
3263 .into_keyed()
3264 .reduce_watermark(
3265 watermark,
3266 q!(
3267 |acc, v| {
3268 *acc += v;
3269 },
3270 commutative = manual_proof!(/** integer addition is commutative */)
3271 ),
3272 )
3273 .snapshot(&node_tick, nondet!(/** test */))
3274 .entries()
3275 .all_ticks()
3276 .send_bincode_external(&external);
3277
3278 let nodes = flow
3279 .with_default_optimize()
3280 .with_process(&node, deployment.Localhost())
3281 .with_external(&external, deployment.Localhost())
3282 .deploy(&mut deployment);
3283
3284 deployment.deploy().await.unwrap();
3285
3286 let mut tick_send = nodes.connect(tick_send).await;
3287 let mut out_recv = nodes.connect(sum).await;
3288
3289 deployment.start().await.unwrap();
3290
3291 assert_eq!(out_recv.next().await.unwrap(), (2, 204));
3292
3293 tick_send.send(()).await.unwrap();
3294
3295 assert_eq!(out_recv.next().await.unwrap(), (3, 103));
3296 }
3297
3298 #[cfg(feature = "sim")]
3299 #[test]
3300 #[should_panic]
3301 fn sim_batch_nondet_size() {
3302 let mut flow = FlowBuilder::new();
3303 let node = flow.process::<()>();
3304
3305 let input = node.source_iter(q!([(1, 1), (1, 2), (2, 3)])).into_keyed();
3306
3307 let tick = node.tick();
3308 let out_recv = input
3309 .batch(&tick, nondet!(/** test */))
3310 .fold(q!(|| vec![]), q!(|acc, v| acc.push(v)))
3311 .entries()
3312 .all_ticks()
3313 .sim_output();
3314
3315 flow.sim().exhaustive(async || {
3316 out_recv
3317 .assert_yields_only_unordered([(1, vec![1, 2])])
3318 .await;
3319 });
3320 }
3321
3322 #[cfg(feature = "sim")]
3323 #[test]
3324 fn sim_batch_preserves_group_order() {
3325 let mut flow = FlowBuilder::new();
3326 let node = flow.process::<()>();
3327
3328 let input = node.source_iter(q!([(1, 1), (1, 2), (2, 3)])).into_keyed();
3329
3330 let tick = node.tick();
3331 let out_recv = input
3332 .batch(&tick, nondet!(/** test */))
3333 .all_ticks()
3334 .fold_early_stop(
3335 q!(|| 0),
3336 q!(|acc, v| {
3337 *acc = std::cmp::max(v, *acc);
3338 *acc >= 2
3339 }),
3340 )
3341 .entries()
3342 .sim_output();
3343
3344 let instances = flow.sim().exhaustive(async || {
3345 out_recv
3346 .assert_yields_only_unordered([(1, 2), (2, 3)])
3347 .await;
3348 });
3349
3350 assert_eq!(instances, 8);
3351 // - three cases: all three in a separate tick (pick where (2, 3) is)
3352 // - two cases: (1, 1) and (1, 2) together, (2, 3) before or after
3353 // - two cases: (1, 1) and (1, 2) separate, (2, 3) grouped with one of them
3354 // - one case: all three together
3355 }
3356
3357 #[cfg(feature = "sim")]
3358 #[test]
3359 fn sim_batch_unordered_shuffles() {
3360 let mut flow = FlowBuilder::new();
3361 let node = flow.process::<()>();
3362
3363 let input = node
3364 .source_iter(q!([(1, 1), (1, 2), (2, 3)]))
3365 .into_keyed()
3366 .weaken_ordering::<NoOrder>();
3367
3368 let tick = node.tick();
3369 let out_recv = input
3370 .batch(&tick, nondet!(/** test */))
3371 .all_ticks()
3372 .entries()
3373 .sim_output();
3374
3375 let instances = flow.sim().exhaustive(async || {
3376 out_recv
3377 .assert_yields_only_unordered([(1, 1), (1, 2), (2, 3)])
3378 .await;
3379 });
3380
3381 assert_eq!(instances, 13);
3382 // - 6 (3 * 2) cases: all three in a separate tick (pick where (2, 3) is), and order of (1, 1), (1, 2)
3383 // - two cases: (1, 1) and (1, 2) together, (2, 3) before or after (order of (1, 1), (1, 2) doesn't matter because batched is still unordered)
3384 // - 4 (2 * 2) cases: (1, 1) and (1, 2) separate, (2, 3) grouped with one of them, and order of (1, 1), (1, 2)
3385 // - one case: all three together (order of (1, 1), (1, 2) doesn't matter because batched is still unordered)
3386 }
3387
3388 #[cfg(feature = "sim")]
3389 #[test]
3390 #[should_panic]
3391 fn sim_observe_order_batched() {
3392 let mut flow = FlowBuilder::new();
3393 let node = flow.process::<()>();
3394
3395 let (port, input) = node.sim_input::<_, NoOrder, _>();
3396
3397 let tick = node.tick();
3398 let batch = input.into_keyed().batch(&tick, nondet!(/** test */));
3399 let out_recv = batch
3400 .assume_ordering::<TotalOrder>(nondet!(/** test */))
3401 .all_ticks()
3402 .first()
3403 .entries()
3404 .sim_output();
3405
3406 flow.sim().exhaustive(async || {
3407 port.send_many_unordered([(1, 1), (1, 2), (2, 1), (2, 2)]);
3408 out_recv
3409 .assert_yields_only_unordered([(1, 1), (2, 1)])
3410 .await; // fails with assume_ordering
3411 });
3412 }
3413
3414 #[cfg(feature = "sim")]
3415 #[test]
3416 fn sim_observe_order_batched_count() {
3417 let mut flow = FlowBuilder::new();
3418 let node = flow.process::<()>();
3419
3420 let (port, input) = node.sim_input::<_, NoOrder, _>();
3421
3422 let tick = node.tick();
3423 let batch = input.into_keyed().batch(&tick, nondet!(/** test */));
3424 let out_recv = batch
3425 .assume_ordering::<TotalOrder>(nondet!(/** test */))
3426 .all_ticks()
3427 .entries()
3428 .sim_output();
3429
3430 let instance_count = flow.sim().exhaustive(async || {
3431 port.send_many_unordered([(1, 1), (1, 2), (2, 1), (2, 2)]);
3432 let _ = out_recv.collect_sorted::<Vec<_>>().await;
3433 });
3434
3435 assert_eq!(instance_count, 104); // too complicated to enumerate here, but less than stream equivalent
3436 }
3437
3438 #[cfg(feature = "sim")]
3439 #[test]
3440 fn sim_top_level_assume_ordering() {
3441 use std::collections::HashMap;
3442
3443 let mut flow = FlowBuilder::new();
3444 let node = flow.process::<()>();
3445
3446 let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3447
3448 let out_recv = input
3449 .into_keyed()
3450 .assume_ordering::<TotalOrder>(nondet!(/** test */))
3451 .fold_early_stop(
3452 q!(|| Vec::new()),
3453 q!(|acc, v| {
3454 acc.push(v);
3455 acc.len() >= 2
3456 }),
3457 )
3458 .entries()
3459 .sim_output();
3460
3461 let instance_count = flow.sim().exhaustive(async || {
3462 in_send.send_many_unordered([(1, 'a'), (1, 'b'), (2, 'c'), (2, 'd')]);
3463 let out: HashMap<_, _> = out_recv
3464 .collect_sorted::<Vec<_>>()
3465 .await
3466 .into_iter()
3467 .collect();
3468 // Each key accumulates its values; we get one entry per key
3469 assert_eq!(out.len(), 2);
3470 });
3471
3472 assert_eq!(instance_count, 24)
3473 }
3474
3475 #[cfg(feature = "sim")]
3476 #[test]
3477 fn sim_top_level_assume_ordering_cycle_back() {
3478 use std::collections::HashMap;
3479
3480 let mut flow = FlowBuilder::new();
3481 let node = flow.process::<()>();
3482 let node2 = flow.process::<()>();
3483
3484 let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3485
3486 let (complete_cycle_back, cycle_back) =
3487 node.forward_ref::<super::KeyedStream<_, _, _, _, NoOrder>>();
3488 let ordered = input
3489 .into_keyed()
3490 .merge_unordered(cycle_back)
3491 .assume_ordering::<TotalOrder>(nondet!(/** test */));
3492 complete_cycle_back.complete(
3493 ordered
3494 .clone()
3495 .map(q!(|v| v + 1))
3496 .filter(q!(|v| v % 2 == 1))
3497 .entries()
3498 .send(&node2, TCP.fail_stop().bincode())
3499 .send(&node, TCP.fail_stop().bincode())
3500 .into_keyed(),
3501 );
3502
3503 let out_recv = ordered
3504 .fold_early_stop(
3505 q!(|| Vec::new()),
3506 q!(|acc, v| {
3507 acc.push(v);
3508 acc.len() >= 2
3509 }),
3510 )
3511 .entries()
3512 .sim_output();
3513
3514 let mut saw = false;
3515 let instance_count = flow.sim().exhaustive(async || {
3516 // Send (1, 0) and (1, 2). 0+1=1 is odd so cycles back.
3517 // We want to see [0, 1] - the cycled back value interleaved
3518 in_send.send_many_unordered([(1, 0), (1, 2)]);
3519 let out: HashMap<_, _> = out_recv
3520 .collect_sorted::<Vec<_>>()
3521 .await
3522 .into_iter()
3523 .collect();
3524
3525 // We want to see an instance where key 1 gets: 0, then 1 (cycled back from 0+1)
3526 if let Some(values) = out.get(&1)
3527 && *values == vec![0, 1]
3528 {
3529 saw = true;
3530 }
3531 });
3532
3533 assert!(
3534 saw,
3535 "did not see an instance with key 1 having [0, 1] in order"
3536 );
3537 assert_eq!(instance_count, 6);
3538 }
3539
3540 #[cfg(feature = "sim")]
3541 #[test]
3542 fn sim_top_level_assume_ordering_cross_key_cycle() {
3543 use std::collections::HashMap;
3544
3545 // This test demonstrates why releasing one entry at a time is important:
3546 // When one key's observed order cycles back into a different key, we need
3547 // to be able to interleave the cycled-back entry with pending items for
3548 // that other key.
3549 let mut flow = FlowBuilder::new();
3550 let node = flow.process::<()>();
3551 let node2 = flow.process::<()>();
3552
3553 let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3554
3555 let (complete_cycle_back, cycle_back) =
3556 node.forward_ref::<super::KeyedStream<_, _, _, _, NoOrder>>();
3557 let ordered = input
3558 .into_keyed()
3559 .merge_unordered(cycle_back)
3560 .assume_ordering::<TotalOrder>(nondet!(/** test */));
3561
3562 // Cycle back: when we see (1, 10), emit (2, 100) to key 2
3563 complete_cycle_back.complete(
3564 ordered
3565 .clone()
3566 .filter(q!(|v| *v == 10))
3567 .map(q!(|_| 100))
3568 .entries()
3569 .map(q!(|(_, v)| (2, v))) // Change key from 1 to 2
3570 .send(&node2, TCP.fail_stop().bincode())
3571 .send(&node, TCP.fail_stop().bincode())
3572 .into_keyed(),
3573 );
3574
3575 let out_recv = ordered
3576 .fold_early_stop(
3577 q!(|| Vec::new()),
3578 q!(|acc, v| {
3579 acc.push(v);
3580 acc.len() >= 2
3581 }),
3582 )
3583 .entries()
3584 .sim_output();
3585
3586 // We want to see an instance where:
3587 // - (1, 10) is released first
3588 // - This causes (2, 100) to be cycled back
3589 // - (2, 100) is released BEFORE (2, 20) which was already pending
3590 let mut saw_cross_key_interleave = false;
3591 let instance_count = flow.sim().exhaustive(async || {
3592 // Send (1, 10), (1, 11) for key 1, and (2, 20), (2, 21) for key 2
3593 in_send.send_many_unordered([(1, 10), (1, 11), (2, 20), (2, 21)]);
3594 let out: HashMap<_, _> = out_recv
3595 .collect_sorted::<Vec<_>>()
3596 .await
3597 .into_iter()
3598 .collect();
3599
3600 // Check if we see the cross-key interleaving:
3601 // key 2 should have [100, 20] or [100, 21] - cycled back 100 before a pending item
3602 if let Some(values) = out.get(&2)
3603 && values.len() >= 2
3604 && values[0] == 100
3605 {
3606 saw_cross_key_interleave = true;
3607 }
3608 });
3609
3610 assert!(
3611 saw_cross_key_interleave,
3612 "did not see an instance where cycled-back 100 was released before pending items for key 2"
3613 );
3614 assert_eq!(instance_count, 60);
3615 }
3616
3617 #[cfg(feature = "sim")]
3618 #[test]
3619 fn sim_top_level_assume_ordering_cycle_back_tick() {
3620 use std::collections::HashMap;
3621
3622 let mut flow = FlowBuilder::new();
3623 let node = flow.process::<()>();
3624 let node2 = flow.process::<()>();
3625
3626 let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3627
3628 let (complete_cycle_back, cycle_back) =
3629 node.forward_ref::<super::KeyedStream<_, _, _, _, NoOrder>>();
3630 let ordered = input
3631 .into_keyed()
3632 .merge_unordered(cycle_back)
3633 .assume_ordering::<TotalOrder>(nondet!(/** test */));
3634 complete_cycle_back.complete(
3635 ordered
3636 .clone()
3637 .batch(&node.tick(), nondet!(/** test */))
3638 .all_ticks()
3639 .map(q!(|v| v + 1))
3640 .filter(q!(|v| v % 2 == 1))
3641 .entries()
3642 .send(&node2, TCP.fail_stop().bincode())
3643 .send(&node, TCP.fail_stop().bincode())
3644 .into_keyed(),
3645 );
3646
3647 let out_recv = ordered
3648 .fold_early_stop(
3649 q!(|| Vec::new()),
3650 q!(|acc, v| {
3651 acc.push(v);
3652 acc.len() >= 2
3653 }),
3654 )
3655 .entries()
3656 .sim_output();
3657
3658 let mut saw = false;
3659 let instance_count = flow.sim().exhaustive(async || {
3660 in_send.send_many_unordered([(1, 0), (1, 2)]);
3661 let out: HashMap<_, _> = out_recv
3662 .collect_sorted::<Vec<_>>()
3663 .await
3664 .into_iter()
3665 .collect();
3666
3667 if let Some(values) = out.get(&1)
3668 && *values == vec![0, 1]
3669 {
3670 saw = true;
3671 }
3672 });
3673
3674 assert!(
3675 saw,
3676 "did not see an instance with key 1 having [0, 1] in order"
3677 );
3678 assert_eq!(instance_count, 58);
3679 }
3680
3681 #[cfg(feature = "sim")]
3682 #[test]
3683 fn sim_entries_partially_ordered_bounded() {
3684 let mut flow = FlowBuilder::new();
3685 let node = flow.process::<()>();
3686
3687 let (port, input) = node.sim_input::<_, TotalOrder, _>();
3688
3689 let tick = node.tick();
3690 let batch = input.into_keyed().batch(&tick, nondet!(/** test */));
3691 let out_recv = batch
3692 .entries_partially_ordered(nondet!(/** test */))
3693 .all_ticks()
3694 .sim_output();
3695
3696 let instance_count = flow.sim().exhaustive(async || {
3697 port.send((1, 'a'));
3698 port.send((1, 'b'));
3699 port.send((2, 'c'));
3700 let _: Vec<(i32, char)> = out_recv.collect().await;
3701 });
3702
3703 assert_eq!(instance_count, 12);
3704 }
3705
3706 #[cfg(feature = "sim")]
3707 #[test]
3708 fn sim_entries_partially_ordered_top_level() {
3709 let mut flow = FlowBuilder::new();
3710 let node = flow.process::<()>();
3711
3712 let (in_send, input) = node.sim_input::<_, TotalOrder, _>();
3713
3714 let out_recv = input
3715 .into_keyed()
3716 .entries_partially_ordered(nondet!(/** test */))
3717 .sim_output();
3718
3719 let instance_count = flow.sim().exhaustive(async || {
3720 in_send.send((1, 'a'));
3721 in_send.send((1, 'b'));
3722 in_send.send((2, 'c'));
3723 let _: Vec<(i32, char)> = out_recv.collect().await;
3724 });
3725
3726 assert_eq!(instance_count, 3);
3727 }
3728
3729 #[cfg(feature = "sim")]
3730 #[test]
3731 fn sim_entries_partially_ordered_cycle_back() {
3732 let mut flow = FlowBuilder::new();
3733 let node = flow.process::<()>();
3734 let node2 = flow.process::<()>();
3735
3736 let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3737
3738 let (complete_cycle_back, cycle_back) =
3739 node.forward_ref::<super::KeyedStream<_, _, _, _, NoOrder>>();
3740 let ordered = input
3741 .into_keyed()
3742 .merge_unordered(cycle_back)
3743 .assume_ordering::<TotalOrder>(nondet!(/** test */));
3744
3745 let flat = ordered
3746 .clone()
3747 .entries_partially_ordered(nondet!(/** test */));
3748
3749 complete_cycle_back.complete(
3750 flat.clone()
3751 .map(q!(|(k, v): (i32, i32)| (k, v + 1)))
3752 .filter(q!(|(_, v)| *v % 2 == 1))
3753 .send(&node2, TCP.fail_stop().bincode())
3754 .send(&node, TCP.fail_stop().bincode())
3755 .into_keyed(),
3756 );
3757
3758 let out_recv = flat.sim_output();
3759
3760 let mut saw = false;
3761 let instance_count = flow.sim().exhaustive(async || {
3762 // Send (1, 0) and (1, 2). 0+1=1 is odd so cycles back as (1, 1).
3763 // We want to see (1, 1) before (1, 2) - the cycled back value beats the pending one
3764 in_send.send_many_unordered([(1, 0), (1, 2)]);
3765 let results: Vec<(i32, i32)> = out_recv.collect().await;
3766
3767 let pos_1 = results.iter().position(|v| *v == (1, 1));
3768 let pos_2 = results.iter().position(|v| *v == (1, 2));
3769 if let (Some(p1), Some(p2)) = (pos_1, pos_2)
3770 && p1 < p2
3771 {
3772 saw = true;
3773 }
3774 });
3775
3776 assert!(saw, "did not see an instance with (1, 1) before (1, 2)");
3777 assert_eq!(instance_count, 78);
3778 }
3779
3780 /// Tests that `merge_ordered` on a keyed stream explores every valid
3781 /// interleaving within a shared key while always preserving per-input
3782 /// order.
3783 #[cfg(feature = "sim")]
3784 #[test]
3785 fn sim_keyed_merge_ordered() {
3786 let mut flow = FlowBuilder::new();
3787 let node = flow.process::<()>();
3788
3789 let (in_send, input) = node.sim_input::<_, TotalOrder, _>();
3790 let (in_send2, input2) = node.sim_input::<_, TotalOrder, _>();
3791
3792 let out_recv = input
3793 .into_keyed()
3794 .merge_ordered(input2.into_keyed(), nondet!(/** test */))
3795 .entries_partially_ordered(nondet!(/** test */))
3796 .sim_output();
3797
3798 let mut saw_first = false;
3799 let mut saw_interleaved = false;
3800 let mut saw_second_first = false;
3801 let instances = flow.sim().exhaustive(async || {
3802 in_send.send((1, 'a'));
3803 in_send.send((1, 'b'));
3804 in_send2.send((1, 'c'));
3805
3806 let out: Vec<(i32, char)> = out_recv.collect().await;
3807 let key1: Vec<char> = out
3808 .iter()
3809 .filter(|(k, _)| *k == 1)
3810 .map(|(_, v)| *v)
3811 .collect();
3812
3813 // Within-group order for the first input must always be preserved.
3814 let first_order: Vec<char> = key1
3815 .iter()
3816 .filter(|c| **c == 'a' || **c == 'b')
3817 .copied()
3818 .collect();
3819 assert_eq!(
3820 first_order,
3821 vec!['a', 'b'],
3822 "within-group order violated: {:?}",
3823 out
3824 );
3825
3826 match key1.as_slice() {
3827 ['a', 'b', 'c'] => saw_first = true,
3828 ['a', 'c', 'b'] => saw_interleaved = true,
3829 ['c', 'a', 'b'] => saw_second_first = true,
3830 other => panic!("unexpected interleaving: {:?}", other),
3831 }
3832 });
3833
3834 assert!(saw_first, "did not observe [a, b, c]");
3835 assert!(saw_interleaved, "did not observe [a, c, b]");
3836 assert!(saw_second_first, "did not observe [c, a, b]");
3837 assert_eq!(instances, 33);
3838 }
3839
3840 /// Tests that `merge_ordered` on a keyed stream interleaves each group
3841 /// *independently*. It must be possible to observe, in the same execution,
3842 /// key `10` taking its second-input value before its first-input value
3843 /// while key `20` does the opposite. A merge that treated the two inputs
3844 /// as a single totally-ordered sequence could not produce this combination.
3845 #[cfg(feature = "sim")]
3846 #[test]
3847 fn sim_keyed_merge_ordered_independent_keys() {
3848 let mut flow = FlowBuilder::new();
3849 let node = flow.process::<()>();
3850
3851 let (in_send, input) = node.sim_input::<_, TotalOrder, _>();
3852 let (in_send2, input2) = node.sim_input::<_, TotalOrder, _>();
3853
3854 let out_recv = input
3855 .into_keyed()
3856 .merge_ordered(input2.into_keyed(), nondet!(/** test */))
3857 .entries_partially_ordered(nondet!(/** test */))
3858 .sim_output();
3859
3860 let mut saw_independent = false;
3861 let instances = flow.sim().exhaustive(async || {
3862 // First input: key 10 -> [1], key 20 -> [2].
3863 in_send.send((10, 1));
3864 in_send.send((20, 2));
3865 // Second input: key 10 -> [4], key 20 -> [3].
3866 in_send2.send((10, 4));
3867 in_send2.send((20, 3));
3868
3869 let out: Vec<(i32, i32)> = out_recv.collect().await;
3870 let key10: Vec<i32> = out
3871 .iter()
3872 .filter(|(k, _)| *k == 10)
3873 .map(|(_, v)| *v)
3874 .collect();
3875 let key20: Vec<i32> = out
3876 .iter()
3877 .filter(|(k, _)| *k == 20)
3878 .map(|(_, v)| *v)
3879 .collect();
3880
3881 // Within-input order must be preserved within each key (each key has
3882 // a single value per input here, so only the multiset is checked).
3883 let mut s10 = key10.clone();
3884 s10.sort();
3885 assert_eq!(s10, vec![1, 4], "unexpected values for key 10: {:?}", out);
3886 let mut s20 = key20.clone();
3887 s20.sort();
3888 assert_eq!(s20, vec![2, 3], "unexpected values for key 20: {:?}", out);
3889
3890 // key 10: second-input value (4) before first-input value (1).
3891 // key 20: first-input value (2) before second-input value (3).
3892 if key10 == vec![4, 1] && key20 == vec![2, 3] {
3893 saw_independent = true;
3894 }
3895 });
3896
3897 assert!(
3898 saw_independent,
3899 "did not observe per-key-independent interleaving"
3900 );
3901 assert_eq!(instances, 2944);
3902 }
3903}