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