Skip to main content

hydro_lang/live_collections/keyed_stream/
mod.rs

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