Skip to main content

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}