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