Skip to main content

dbsp/operator/dynamic/
input.rs

1use crate::{
2    Circuit, DBData, DynZWeight, NumEntries, Runtime, Stream, ZWeight,
3    algebra::{
4        IndexedZSet, OrdIndexedZSet, OrdIndexedZSetFactories, OrdZSet, OrdZSetFactories, ZSet,
5    },
6    circuit::{RootCircuit, checkpointer::Checkpoint},
7    dynamic::{
8        ClonableTrait, DataTrait, DynBool, DynData, DynOpt, DynPair, DynPairs, DynUnit,
9        DynWeightedPairs, Erase, Factory, LeanVec, WithFactory,
10    },
11    operator::{
12        Input, InputHandle, Update,
13        dynamic::{
14            input_upsert::{
15                DynUpdate, InputUpsertFactories, InputUpsertWithWaterlineFactories, PatchFunc,
16            },
17            time_series::LeastUpperBoundFunc,
18            upsert::UpdateSetFactories,
19        },
20    },
21    trace::{Batch, BatchFactories, BatchReaderFactories, Batcher, FallbackWSet, Rkyv},
22    utils::Tup2,
23};
24use std::{
25    mem::{replace, swap},
26    ops::Not,
27    panic::Location,
28    sync::{
29        Arc,
30        atomic::{AtomicUsize, Ordering},
31    },
32};
33
34pub type IndexedZSetStream<K, V> = Stream<RootCircuit, OrdIndexedZSet<K, V>>;
35pub type ZSetStream<K> = Stream<RootCircuit, OrdZSet<K>>;
36
37pub struct AddInputZSetFactories<K: DataTrait + ?Sized> {
38    zset_factories: OrdZSetFactories<K>,
39    weighted_pairs_factory: &'static dyn Factory<DynWeightedPairs<DynPair<K, DynUnit>, DynZWeight>>,
40    pairs_factory: &'static dyn Factory<DynPairs<DynPair<K, DynUnit>, DynZWeight>>,
41    pair_factory: &'static dyn Factory<DynPair<DynPair<K, DynUnit>, DynZWeight>>,
42}
43
44impl<K> AddInputZSetFactories<K>
45where
46    K: DataTrait + ?Sized,
47{
48    pub fn new<KType>() -> Self
49    where
50        KType: DBData + Erase<K>,
51    {
52        Self {
53            zset_factories: BatchReaderFactories::new::<KType, (), ZWeight>(),
54            weighted_pairs_factory: WithFactory::<LeanVec<Tup2<Tup2<KType, ()>, ZWeight>>>::FACTORY,
55            pairs_factory: WithFactory::<LeanVec<Tup2<Tup2<KType, ()>, ZWeight>>>::FACTORY,
56            pair_factory: WithFactory::<Tup2<Tup2<KType, ()>, ZWeight>>::FACTORY,
57        }
58    }
59}
60
61impl<K> Clone for AddInputZSetFactories<K>
62where
63    K: DataTrait + ?Sized,
64{
65    fn clone(&self) -> Self {
66        Self {
67            zset_factories: self.zset_factories.clone(),
68            weighted_pairs_factory: self.weighted_pairs_factory,
69            pairs_factory: self.pairs_factory,
70            pair_factory: self.pair_factory,
71        }
72    }
73}
74
75pub struct AddInputIndexedZSetFactories<K, V>
76where
77    K: DataTrait + ?Sized,
78    V: DataTrait + ?Sized,
79{
80    indexed_zset_factories: OrdIndexedZSetFactories<K, V>,
81    pair_factory: &'static dyn Factory<DynPair<K, DynPair<V, DynZWeight>>>,
82    pairs_factory: &'static dyn Factory<DynPairs<K, DynPair<V, DynZWeight>>>,
83}
84
85impl<K, V> Clone for AddInputIndexedZSetFactories<K, V>
86where
87    K: DataTrait + ?Sized,
88    V: DataTrait + ?Sized,
89{
90    fn clone(&self) -> Self {
91        Self {
92            indexed_zset_factories: self.indexed_zset_factories.clone(),
93            pair_factory: self.pair_factory,
94            pairs_factory: self.pairs_factory,
95        }
96    }
97}
98
99impl<K, V> AddInputIndexedZSetFactories<K, V>
100where
101    K: DataTrait + ?Sized,
102    V: DataTrait + ?Sized,
103{
104    pub fn new<KType, VType>() -> Self
105    where
106        KType: DBData + Erase<K>,
107        VType: DBData + Erase<V>,
108    {
109        Self {
110            indexed_zset_factories: BatchReaderFactories::new::<KType, VType, ZWeight>(),
111            pairs_factory: WithFactory::<LeanVec<Tup2<KType, Tup2<VType, ZWeight>>>>::FACTORY,
112            pair_factory: WithFactory::<Tup2<KType, Tup2<VType, ZWeight>>>::FACTORY,
113        }
114    }
115}
116
117pub struct AddInputSetFactories<B>
118where
119    B: ZSet,
120{
121    update_set_factories: UpdateSetFactories<(), B>,
122    input_pair_factory: &'static dyn Factory<DynPair<B::Key, DynBool>>,
123    input_pairs_factory: &'static dyn Factory<DynPairs<B::Key, DynBool>>,
124    upsert_pair_factory: &'static dyn Factory<DynPair<B::Key, DynOpt<DynUnit>>>,
125    upsert_pairs_factory: &'static dyn Factory<DynPairs<B::Key, DynOpt<DynUnit>>>,
126}
127
128impl<B> Clone for AddInputSetFactories<B>
129where
130    B: ZSet,
131{
132    fn clone(&self) -> Self {
133        Self {
134            update_set_factories: self.update_set_factories.clone(),
135            input_pair_factory: self.input_pair_factory,
136            input_pairs_factory: self.input_pairs_factory,
137            upsert_pair_factory: self.upsert_pair_factory,
138            upsert_pairs_factory: self.upsert_pairs_factory,
139        }
140    }
141}
142
143impl<B> AddInputSetFactories<B>
144where
145    B: ZSet,
146{
147    pub fn new<KType>() -> Self
148    where
149        KType: DBData + Erase<B::Key>,
150    {
151        Self {
152            update_set_factories: UpdateSetFactories::new::<KType>(),
153            input_pair_factory: WithFactory::<Tup2<KType, bool>>::FACTORY,
154            input_pairs_factory: WithFactory::<LeanVec<Tup2<KType, bool>>>::FACTORY,
155            upsert_pair_factory: WithFactory::<Tup2<KType, Option<()>>>::FACTORY,
156            upsert_pairs_factory: WithFactory::<LeanVec<Tup2<KType, Option<()>>>>::FACTORY,
157        }
158    }
159}
160
161pub struct AddInputMapFactories<B, U>
162where
163    B: IndexedZSet,
164    U: DataTrait + ?Sized,
165{
166    upsert_factories: InputUpsertFactories<B, U>,
167    input_pair_factory: &'static dyn Factory<DynPair<B::Key, DynUpdate<B::Val, U>>>,
168    input_pairs_factory: &'static dyn Factory<DynPairs<B::Key, DynUpdate<B::Val, U>>>,
169    upsert_pair_factory: &'static dyn Factory<DynPair<B::Key, DynOpt<DynUnit>>>,
170}
171
172impl<B, U> AddInputMapFactories<B, U>
173where
174    B: IndexedZSet,
175    U: DataTrait + ?Sized,
176{
177    pub fn new<KType, VType, UType>() -> Self
178    where
179        KType: DBData + Erase<B::Key>,
180        VType: DBData + Erase<B::Val>,
181        UType: DBData + Erase<U>,
182    {
183        Self {
184            upsert_factories: InputUpsertFactories::new::<KType, VType, UType>(),
185            input_pair_factory: WithFactory::<Tup2<KType, Update<VType, UType>>>::FACTORY,
186            input_pairs_factory: WithFactory::<LeanVec<Tup2<KType, Update<VType, UType>>>>::FACTORY,
187            upsert_pair_factory: WithFactory::<Tup2<KType, Option<()>>>::FACTORY,
188        }
189    }
190}
191
192impl<B, U> Clone for AddInputMapFactories<B, U>
193where
194    B: IndexedZSet,
195    U: DataTrait + ?Sized,
196{
197    fn clone(&self) -> Self {
198        Self {
199            upsert_factories: self.upsert_factories.clone(),
200            input_pair_factory: self.input_pair_factory,
201            input_pairs_factory: self.input_pairs_factory,
202            upsert_pair_factory: self.upsert_pair_factory,
203        }
204    }
205}
206
207pub struct AddInputMapWithWaterlineFactories<B, U, E>
208where
209    B: IndexedZSet,
210    U: DataTrait + ?Sized,
211    E: DataTrait + ?Sized,
212{
213    upsert_factories: InputUpsertWithWaterlineFactories<B, U, E>,
214    input_pair_factory: &'static dyn Factory<DynPair<B::Key, DynUpdate<B::Val, U>>>,
215    input_pairs_factory: &'static dyn Factory<DynPairs<B::Key, DynUpdate<B::Val, U>>>,
216    upsert_pair_factory: &'static dyn Factory<DynPair<B::Key, DynOpt<DynUnit>>>,
217}
218
219impl<B, U, E> AddInputMapWithWaterlineFactories<B, U, E>
220where
221    B: IndexedZSet,
222    U: DataTrait + ?Sized,
223    E: DataTrait + ?Sized,
224{
225    pub fn new<KType, VType, UType, EType>() -> Self
226    where
227        KType: DBData + Erase<B::Key>,
228        VType: DBData + Erase<B::Val>,
229        UType: DBData + Erase<U>,
230        EType: DBData + Erase<E>,
231    {
232        Self {
233            upsert_factories: InputUpsertWithWaterlineFactories::new::<KType, VType, UType, EType>(
234            ),
235            input_pair_factory: WithFactory::<Tup2<KType, Update<VType, UType>>>::FACTORY,
236            input_pairs_factory: WithFactory::<LeanVec<Tup2<KType, Update<VType, UType>>>>::FACTORY,
237            upsert_pair_factory: WithFactory::<Tup2<KType, Option<()>>>::FACTORY,
238        }
239    }
240}
241
242impl<B, U, E> Clone for AddInputMapWithWaterlineFactories<B, U, E>
243where
244    B: IndexedZSet,
245    U: DataTrait + ?Sized,
246    E: DataTrait + ?Sized,
247{
248    fn clone(&self) -> Self {
249        Self {
250            upsert_factories: self.upsert_factories.clone(),
251            input_pair_factory: self.input_pair_factory,
252            input_pairs_factory: self.input_pairs_factory,
253            upsert_pair_factory: self.upsert_pair_factory,
254        }
255    }
256}
257
258impl RootCircuit {
259    pub fn dyn_add_input_zset_mono(
260        &self,
261        factories: &AddInputZSetFactories<DynData>,
262    ) -> (
263        ZSetStream<DynData>,
264        CollectionHandle<DynPair<DynData, DynUnit>, DynZWeight>,
265    ) {
266        self.dyn_add_input_zset(factories)
267    }
268
269    #[allow(clippy::type_complexity)]
270    pub fn dyn_add_input_indexed_zset_mono(
271        &self,
272        factories: &AddInputIndexedZSetFactories<DynData, DynData>,
273    ) -> (
274        IndexedZSetStream<DynData, DynData>,
275        CollectionHandle<DynData, DynPair<DynData, DynZWeight>>,
276    ) {
277        self.dyn_add_input_indexed_zset(factories)
278    }
279
280    pub fn dyn_add_input_set_mono(
281        &self,
282        persistent_id: Option<&str>,
283        factories: &AddInputSetFactories<OrdZSet<DynData>>,
284    ) -> (ZSetStream<DynData>, UpsertHandle<DynData, DynBool>) {
285        self.dyn_add_input_set(persistent_id, factories)
286    }
287
288    pub fn dyn_add_input_map_mono(
289        &self,
290        persistent_id: Option<&str>,
291        factories: &AddInputMapFactories<OrdIndexedZSet<DynData, DynData>, DynData>,
292        patch_func: PatchFunc<DynData, DynData>,
293    ) -> (
294        IndexedZSetStream<DynData, DynData>,
295        UpsertHandle<DynData, DynUpdate<DynData, DynData>>,
296    ) {
297        self.dyn_add_input_map(persistent_id, factories, patch_func)
298    }
299
300    #[allow(clippy::too_many_arguments)]
301    pub fn dyn_add_input_map_with_waterline_mono(
302        &self,
303        persistent_id: Option<&str>,
304        factories: &AddInputMapWithWaterlineFactories<
305            OrdIndexedZSet<DynData, DynData>,
306            DynData,
307            DynData,
308        >,
309        patch_func: PatchFunc<DynData, DynData>,
310        init_waterline: Box<dyn Fn() -> Box<DynData>>,
311        extract_ts: Box<dyn Fn(&DynData, &DynData, &mut DynData)>,
312        least_upper_bound: LeastUpperBoundFunc<DynData>,
313        filter_func: Box<dyn Fn(&DynData, &DynData, &DynData) -> bool>,
314        report_func: Box<dyn Fn(&DynData, &DynData, &DynData, ZWeight, &mut DynData)>,
315    ) -> (
316        IndexedZSetStream<DynData, DynData>,
317        Stream<RootCircuit, OrdZSet<DynData>>,
318        Stream<RootCircuit, Box<DynData>>,
319        UpsertHandle<DynData, DynUpdate<DynData, DynData>>,
320    ) {
321        self.dyn_add_input_map_with_waterline(
322            persistent_id,
323            factories,
324            patch_func,
325            init_waterline,
326            extract_ts,
327            least_upper_bound,
328            filter_func,
329            report_func,
330        )
331    }
332}
333
334impl RootCircuit {
335    /// Create an input stream that carries values of type [`OrdZSet<K>`](`OrdZSet`).
336    ///
337    /// Creates an input stream that carries values of type `OrdZSet<K>` and
338    /// an input handle of type [`CollectionHandle<K, R>`](`CollectionHandle`)
339    /// used to construct input Z-sets out of individual elements.  The
340    /// client invokes [`CollectionHandle::dyn_push`] and
341    /// [`CollectionHandle::dyn_append`] any number of times to add values to
342    /// the input Z-set. These values are distributed across all worker
343    /// threads (when running in a multithreaded [`Runtime`](`crate::Runtime`))
344    /// in a round-robin fashion and buffered until the start of the next clock
345    /// cycle.  At the start of a clock cycle (triggered by
346    /// [`DBSPHandle::step`](`crate::DBSPHandle::step`) or
347    /// [`CircuitHandle::step`](`crate::CircuitHandle::step`)), the circuit
348    /// reads all buffered values and assembles them into an `OrdZSet`.
349    ///
350    /// See [`CollectionHandle`] for more details.
351    #[track_caller]
352    pub fn dyn_add_input_zset<K>(
353        &self,
354        factories: &AddInputZSetFactories<K>,
355    ) -> (
356        ZSetStream<K>,
357        CollectionHandle<DynPair<K, DynUnit>, DynZWeight>,
358    )
359    where
360        K: DataTrait + ?Sized,
361    {
362        let pairs_factory = factories.pairs_factory;
363        let weighted_pairs_factory = factories.weighted_pairs_factory;
364
365        let zset_factories = factories.zset_factories.clone();
366
367        self.region("input_zset", || {
368            let (input, input_handle) = Input::new(
369                Location::caller(),
370                move |tuples: Vec<Box<DynPairs<_, _>>>| {
371                    let mut pairs = weighted_pairs_factory.default_box();
372                    let mut batcher =
373                        <FallbackWSet<_, _> as Batch>::Batcher::new_batcher(&zset_factories, ());
374                    for mut tuples in tuples {
375                        pairs.from_pairs(tuples.as_mut());
376                        batcher.push_batch(&mut pairs);
377                    }
378                    batcher.seal()
379                },
380                Arc::new(|| vec![pairs_factory.default_box()]),
381            );
382
383            // This stream doesn't strictly need to be sharded. We shard it to make sure that when it is materialized,
384            // the resulting integral can be used to bootstrap any streams derived from it, avoiding the following
385            // situation where the integral cannot be used to backfill the bottom circuit:
386            //
387            // input--->[shard]--->[integral]
388            //       |--->[some other operator]
389            //
390            // This adds small overhead to tables that don't get materialized and hence don't need to get sharded.
391            // If this proves to be a problem in practice, we can add a variant of this function that doesn't shard
392            // its output stream for use with non-materializes tables.
393            let stream = self.add_source(input).dyn_shard(&factories.zset_factories);
394
395            let zset_handle = <CollectionHandle<DynPair<K, DynUnit>, DynZWeight>>::new(
396                factories.pair_factory,
397                factories.pairs_factory,
398                input_handle,
399            );
400
401            (stream, zset_handle)
402        })
403    }
404
405    /// Create an input stream that carries values of type
406    /// [`OrdIndexedZSet<K, V>`](`OrdIndexedZSet`).
407    ///
408    /// Creates an input stream that carries values of type `OrdIndexedZSet<K, V>`
409    /// and an input handle of type [`CollectionHandle<K, (V, R)>`](`CollectionHandle`)
410    /// used to construct input Z-sets out of individual elements.  The client invokes
411    /// [`CollectionHandle::dyn_push`] and [`CollectionHandle::dyn_append`] any number
412    /// of times to add `key/value/weight` triples the indexed Z-set. These triples are
413    /// distributed across all worker threads (when running in a multithreaded
414    /// [`Runtime`](`crate::Runtime`)) in a round-robin fashion, and buffered until the
415    /// start of the next clock cycle.  At the start of a clock cycle (triggered by
416    /// [`DBSPHandle::step`](`crate::DBSPHandle::step`) or
417    /// [`CircuitHandle::step`](`crate::CircuitHandle::step`)), the circuit
418    /// reads all buffered values and assembles them into an `OrdIndexedZSet`.
419    ///
420    /// See [`CollectionHandle`] for more details.
421    #[allow(clippy::type_complexity)]
422    #[track_caller]
423    pub fn dyn_add_input_indexed_zset<K, V>(
424        &self,
425        factories: &AddInputIndexedZSetFactories<K, V>,
426    ) -> (
427        IndexedZSetStream<K, V>,
428        CollectionHandle<K, DynPair<V, DynZWeight>>,
429    )
430    where
431        K: DataTrait + ?Sized,
432        V: DataTrait + ?Sized,
433    {
434        let factories_clone = factories.clone();
435
436        let (input, input_handle) = Input::new(
437            Location::caller(),
438            move |tuples: Vec<Box<DynPairs<K, DynPair<V, DynZWeight>>>>| {
439                let mut indexed_tuples = factories_clone
440                    .indexed_zset_factories
441                    .weighted_items_factory()
442                    .default_box();
443                let mut item = factories_clone
444                    .indexed_zset_factories
445                    .weighted_item_factory()
446                    .default_box();
447
448                for mut tuples in tuples {
449                    for kvw in tuples.dyn_iter_mut() {
450                        let (k, vw) = kvw.split_mut();
451                        let (v, w) = vw.split_mut();
452                        let (kv, item_w) = item.split_mut();
453                        let (item_k, item_v) = kv.split_mut();
454                        k.clone_to(item_k);
455                        v.clone_to(item_v);
456                        w.clone_to(item_w);
457                        indexed_tuples.push_val(&mut *item);
458                    }
459                }
460                OrdIndexedZSet::dyn_from_tuples(
461                    &factories_clone.indexed_zset_factories,
462                    (),
463                    &mut indexed_tuples,
464                )
465            },
466            Arc::new(|| vec![factories.pairs_factory.default_box()]),
467        );
468
469        // This stream doesn't strictly need to be sharded. We shard it to make sure that when it is materialized,
470        // the resulting integral can be used to bootstrap any streams derived from it, avoiding the following
471        // situation where the integral cannot be used to backfill the bottom circuit:
472        //
473        // input--->[shard]--->[integral]
474        //       |--->[some other operator]
475        let stream = self
476            .add_source(input)
477            .dyn_shard(&factories.indexed_zset_factories);
478
479        let zset_handle = <CollectionHandle<K, DynPair<V, DynZWeight>>>::new(
480            factories.pair_factory,
481            factories.pairs_factory,
482            input_handle,
483        );
484
485        (stream, zset_handle)
486    }
487
488    #[track_caller]
489    fn add_set_update<K, B>(
490        &self,
491        persistent_id: Option<&str>,
492        factories: &AddInputSetFactories<B>,
493        input_stream: Stream<Self, Vec<Box<DynPairs<K, DynBool>>>>,
494    ) -> Stream<Self, B>
495    where
496        K: DataTrait + ?Sized,
497        B: ZSet<Key = K>,
498    {
499        let factories_clone = factories.clone();
500
501        let sorted = input_stream
502            .apply_owned(move |upserts| {
503                struct UpsertPosition<T> {
504                    upserts: T,
505                    position: usize,
506                }
507                // Sort the vectors by key, preserving the history of updates for each key.
508                // Upserts cannot be merged or reordered, therefore we cannot use unstable sort.
509                let mut upserts = upserts
510                    .into_iter()
511                    .filter_map(|mut upserts| {
512                        upserts.sort_by_key();
513
514                        // Find the last upsert for each key, that's the only one that matters.
515                        upserts.dedup_by_key_keep_last();
516
517                        upserts.is_empty().not().then(|| UpsertPosition {
518                            upserts,
519                            position: 0,
520                        })
521                    })
522                    .collect::<Vec<_>>();
523
524                let mut result = factories_clone.upsert_pairs_factory.default_box();
525                let mut tuple = factories_clone.upsert_pair_factory.default_box();
526
527                while !upserts.is_empty() {
528                    let min_index = (0..upserts.len())
529                        .min_by(|a, b| {
530                            let a = upserts[*a].upserts.index(upserts[*a].position);
531                            let b = upserts[*b].upserts.index(upserts[*b].position);
532                            a.cmp(b)
533                        })
534                        .unwrap();
535                    let min = &mut upserts[min_index];
536                    let upsert = min.upserts.index_mut(min.position);
537
538                    let (k, v) = upsert.split_mut();
539                    let mut v = if **v { Some(()) } else { None };
540                    tuple.from_vals(k, v.erase_mut());
541                    result.push_val(&mut *tuple);
542
543                    min.position += 1;
544                    if min.position >= min.upserts.len() {
545                        upserts.remove(min_index);
546                    }
547                }
548
549                result
550            })
551            // UpsertHandle shards its inputs.
552            .mark_sharded();
553
554        sorted.update_set::<B>(persistent_id, &factories.update_set_factories)
555    }
556
557    #[track_caller]
558    fn add_upsert_indexed<K, V, U, B>(
559        &self,
560        persistent_id: Option<&str>,
561        factories: &AddInputMapFactories<B, U>,
562        input_stream: Stream<Self, Vec<Box<DynPairs<K, DynUpdate<V, U>>>>>,
563        patch_func: PatchFunc<V, U>,
564    ) -> Stream<Self, B>
565    where
566        B: IndexedZSet<Key = K, Val = V>,
567        K: DataTrait + ?Sized,
568        V: DataTrait + ?Sized,
569        U: DataTrait + ?Sized,
570    {
571        let sorted = input_stream.apply_owned(move |mut upserts| {
572            // Sort the vectors by key, preserving the history of updates for each key.
573            // Upserts cannot be merged or reordered, therefore we cannot use unstable sort.
574            upserts.retain_mut(|pairs| {
575                pairs.sort_by_key();
576                !pairs.is_empty()
577            });
578
579            upserts
580        });
581
582        if Runtime::runtime().is_none_or(|rt| rt.layout().is_solo()) {
583            // UpsertHandle shards its inputs across workers on the current host only.
584            sorted.mark_sharded();
585        }
586        sorted.input_upsert::<B>(persistent_id, &factories.upsert_factories, patch_func)
587    }
588
589    #[allow(clippy::too_many_arguments)]
590    #[track_caller]
591    fn add_upsert_indexed_with_waterline<K, V, U, B, W, E>(
592        &self,
593        persistent_id: Option<&str>,
594        factories: &AddInputMapWithWaterlineFactories<B, U, E>,
595        input_stream: Stream<Self, Vec<Box<DynPairs<K, DynUpdate<V, U>>>>>,
596        patch_func: PatchFunc<V, U>,
597        init_waterline: Box<dyn Fn() -> Box<W>>,
598        extract_ts: Box<dyn Fn(&B::Key, &B::Val, &mut W)>,
599        least_upper_bound: LeastUpperBoundFunc<W>,
600        filter_func: Box<dyn Fn(&W, &B::Key, &B::Val) -> bool>,
601        report_func: Box<dyn Fn(&W, &B::Key, &B::Val, ZWeight, &mut E)>,
602    ) -> (
603        Stream<Self, B>,
604        Stream<Self, OrdZSet<E>>,
605        Stream<Self, Box<W>>,
606    )
607    where
608        B: IndexedZSet<Key = K, Val = V>,
609        K: DataTrait + ?Sized,
610        V: DataTrait + ?Sized,
611        U: DataTrait + ?Sized,
612        W: DataTrait + Checkpoint + ?Sized,
613        E: DataTrait + ?Sized,
614        Box<W>: Checkpoint + Clone + NumEntries + Rkyv,
615    {
616        let sorted = input_stream.apply_owned(move |mut upserts| {
617            // Sort the vectors by key, preserving the history of updates for each key.
618            // Upserts cannot be merged or reordered, therefore we cannot use unstable sort.
619            upserts.retain_mut(|pairs| {
620                pairs.sort_by_key();
621                !pairs.is_empty()
622            });
623
624            upserts
625        });
626
627        if Runtime::runtime().is_none_or(|rt| rt.layout().is_solo()) {
628            // UpsertHandle shards its inputs across workers on the current host only.
629            sorted.mark_sharded();
630        }
631
632        sorted.input_upsert_with_waterline::<B, W, E>(
633            persistent_id,
634            &factories.upsert_factories,
635            patch_func,
636            init_waterline,
637            extract_ts,
638            least_upper_bound,
639            filter_func,
640            report_func,
641        )
642    }
643
644    /// Create an input table with set semantics.
645    ///
646    /// The `dyn_add_input_set` operator creates an input table that internally
647    /// appears as a Z-set with unit weights, but that ingests input data
648    /// using set semantics. It returns a stream that carries values of type
649    /// `OrdZSet<K>` and an input handle of type
650    /// [`UpsertHandle<K,bool>`](`UpsertHandle`).  The client uses
651    /// [`UpsertHandle::dyn_push`] and [`UpsertHandle::dyn_append`] to submit
652    /// commands of the form `(val, true)` to insert an element to the set
653    /// and `(val, false) ` to delete `val` from the set.  These commands
654    /// are buffered until the start of the next clock cycle.
655    ///
656    /// At the start of a clock cycle (triggered by
657    /// [`DBSPHandle::step`](`crate::DBSPHandle::step`) or
658    /// [`CircuitHandle::step`](`crate::CircuitHandle::step`)), DBSP applies
659    /// buffered commands in order and computes an update to the input set as
660    /// an `OrdZSet` with weights `+1` and `-1` representing set insertions and
661    /// deletions respectively. The following table illustrates the
662    /// relationship between input commands, the contents of the set and the
663    /// contents of the stream produced by this operator:
664    ///
665    /// ```text
666    /// time │      input commands          │content of the   │ stream returned by     │  comment
667    ///      │                              │input set        │ `add_input_set`        │
668    /// ─────┼──────────────────────────────┼─────────────────┼────────────────────────┼───────────────────────────────────────────────────────
669    ///    1 │{("foo",true),("bar",true)}   │  {"foo","bar"}  │ {("foo",+1),("bar",+1)}│
670    ///    2 │{("foo",true),("bar",false)}  │  {"foo"}        │ {("bar",-1)}           │ignore duplicate insert of "foo"
671    ///    3 │{("foo",false),("foo",true)}  │  {"foo"}        │ {}                     │deleting and re-inserting "foo" is a no-op
672    ///    4 │{("foo",false),("bar",false)} │  {}             │ {("foo",-1)}           │deleting value "bar" that is not in the set is a no-op
673    /// ─────┴──────────────────────────────┴─────────────────┴────────────────────────┴────────────────────────────────────────────────────────
674    /// ```
675    ///
676    /// Internally, this operator maintains the contents of the input set
677    /// partitioned across all worker threads based on the hash of the
678    /// value.  Insert/delete commands are routed to the worker in charge of
679    /// the given value.
680    ///
681    /// # Data retention
682    ///
683    /// Applying [`Stream::dyn_integrate_trace_retain_keys`], and
684    /// [`Stream::dyn_integrate_trace_with_bound`] methods to the stream has the
685    /// additional effect of filtering out all values that don't satisfy the
686    /// retention policy configured by these methods from the stream.
687    /// Specifically, retention conditions configured at logical time `t`
688    /// are applied starting from logical time `t+1`.
689    // TODO: Add a version that takes a custom hash function.
690    #[track_caller]
691    pub fn dyn_add_input_set<K>(
692        &self,
693        persistent_id: Option<&str>,
694        factories: &AddInputSetFactories<OrdZSet<K>>,
695    ) -> (ZSetStream<K>, UpsertHandle<K, DynBool>)
696    where
697        K: DataTrait + ?Sized,
698    {
699        self.region("input_set", || {
700            let (input, input_handle) = Input::new(
701                Location::caller(),
702                |tuples: Vec<Box<DynPairs<K, DynBool>>>| tuples,
703                Arc::new(|| vec![factories.input_pairs_factory.default_box()]),
704            );
705            let input_stream = self.add_source(input);
706            let upsert_handle = <UpsertHandle<K, DynBool>>::new(
707                factories.input_pair_factory,
708                factories.input_pairs_factory,
709                input_handle,
710            );
711
712            let upsert: Stream<RootCircuit, OrdZSet<K>> =
713                self.add_set_update(persistent_id, factories, input_stream);
714
715            (upsert, upsert_handle)
716        })
717    }
718
719    /// Create an input table as a key-value map with upsert update semantics.
720    ///
721    /// # Details
722    ///
723    /// The `dyn_add_input_map` operator creates an input table that internally
724    /// appears as an indexed Z-set with all unit weights, but that ingests
725    /// input data using upsert semantics. It returns a stream that carries
726    /// values of type `OrdIndexedZSet<K, V>` and an input handle of type
727    /// [`UpsertHandle<K,Option<V>>`](`UpsertHandle`).  The client uses
728    /// [`UpsertHandle::dyn_push`] and [`UpsertHandle::dyn_append`] to submit
729    /// commands of the form `(key, Some(val))` to insert a new key-value
730    /// pair and `(key, None) ` to delete the value associated with `key` is
731    /// any. These commands are buffered until the start of the next clock
732    /// cycle.
733    ///
734    /// At the start of a clock cycle (triggered by
735    /// [`DBSPHandle::step`](`crate::DBSPHandle::step`) or
736    /// [`CircuitHandle::step`](`crate::CircuitHandle::step`)),
737    /// DBSP applies buffered commands in order and
738    /// computes an update to the input set as an `OrdIndexedZSet` with weights
739    /// `+1` and `-1` representing insertions and deletions respectively.
740    /// The following table illustrates the relationship between input commands,
741    /// the contents of the map and the contents of the stream produced by this
742    /// operator:
743    ///
744    /// ```text
745    /// time │      input commands               │content of the        │ stream returned by         │  comment
746    ///      │                                   │input map             │ `add_input_map`            │
747    /// ─────┼───────────────────────────────────┼──────────────────────┼────────────────────────────┼───────────────────────────────────────────────────────
748    ///    1 │{(1,Some("foo"), (2,Some("bar"))}  │{(1,"foo"),(2,"bar")} │ {(1,"foo",+1),(2,"bar",+1)}│
749    ///    2 │{(1,Some("foo"), (2,Some("baz"))}  │{(1,"foo"),(2,"baz")} │ {(2,"bar",-1),(2,"baz",+1)}│ Ignore duplicate insert of (1,"foo"). New value
750    ///      |                                   |                      |                            | "baz" for key 2 overwrites the old value "bar".
751    ///    3 │{(1,None),(2,Some("bar")),(2,None)}│{}                    │ {(1,"foo",-1),(2,"baz",-1)}│ Delete both keys. Upsert (2,"bar") is overridden
752    ///      |                                   |                      |                            | by subsequent delete command.
753    /// ─────┴───────────────────────────────────┴──────────────────────┴────────────────────────────┴────────────────────────────────────────────────────────
754    /// ```
755    ///
756    /// Note that upsert commands cannot fail.  Duplicate inserts and deletes
757    /// are simply ignored.
758    ///
759    /// Internally, this operator maintains the contents of the map
760    /// partitioned across all worker threads based on the hash of the
761    /// key.  Upsert/delete commands are routed to the worker in charge of
762    /// the given key.
763    ///
764    ///
765    /// # Data retention
766    ///
767    /// Applying the [`Stream::dyn_integrate_trace_retain_keys`] to the stream has the
768    /// additional effect of filtering out all updates that don't satisfy the
769    /// retention policy.
770    /// In particular, this means that attempts to overwrite, update, or delete
771    /// a key-value pair whose key doesn't satisfy current retention
772    /// conditions are ignored, since all these operations involve deleting
773    /// an existing tuple.
774    ///
775    /// Retention conditions configured at logical time `t`
776    /// are applied starting from logical time `t+1`.
777    ///
778    /// FIXME: see <https://github.com/feldera/feldera/issues/2669>
779    // TODO: Add a version that takes a custom hash function.
780    #[track_caller]
781    pub fn dyn_add_input_map<K, V, U>(
782        &self,
783        persistent_id: Option<&str>,
784        factories: &AddInputMapFactories<OrdIndexedZSet<K, V>, U>,
785        patch_func: PatchFunc<V, U>,
786    ) -> (IndexedZSetStream<K, V>, UpsertHandle<K, DynUpdate<V, U>>)
787    where
788        K: DataTrait + ?Sized,
789        V: DataTrait + ?Sized,
790        U: DataTrait + ?Sized,
791    {
792        self.region("input_map", || {
793            let (input, input_handle) = Input::new(
794                Location::caller(),
795                |tuples: Vec<Box<DynPairs<K, DynUpdate<V, U>>>>| tuples,
796                Arc::new(|| vec![factories.input_pairs_factory.default_box()]),
797            );
798            let input_stream = self.add_source(input);
799            let zset_handle = <UpsertHandle<K, DynUpdate<V, U>>>::new(
800                factories.input_pair_factory,
801                factories.input_pairs_factory,
802                input_handle,
803            );
804
805            let upsert =
806                self.add_upsert_indexed(persistent_id, factories, input_stream, patch_func);
807
808            (upsert, zset_handle)
809        })
810    }
811
812    #[allow(clippy::too_many_arguments)]
813    #[track_caller]
814    pub fn dyn_add_input_map_with_waterline<K, V, U, W, E>(
815        &self,
816        persistent_id: Option<&str>,
817        factories: &AddInputMapWithWaterlineFactories<OrdIndexedZSet<K, V>, U, E>,
818        patch_func: PatchFunc<V, U>,
819        init_waterline: Box<dyn Fn() -> Box<W>>,
820        extract_ts: Box<dyn Fn(&K, &V, &mut W)>,
821        least_upper_bound: LeastUpperBoundFunc<W>,
822        filter_func: Box<dyn Fn(&W, &K, &V) -> bool>,
823        report_func: Box<dyn Fn(&W, &K, &V, ZWeight, &mut E)>,
824    ) -> (
825        IndexedZSetStream<K, V>,
826        Stream<RootCircuit, OrdZSet<E>>,
827        Stream<RootCircuit, Box<W>>,
828        UpsertHandle<K, DynUpdate<V, U>>,
829    )
830    where
831        K: DataTrait + ?Sized,
832        V: DataTrait + ?Sized,
833        U: DataTrait + ?Sized,
834        W: DataTrait + Checkpoint + ?Sized,
835        E: DataTrait + ?Sized,
836        Box<W>: Checkpoint + Clone + NumEntries + Rkyv,
837    {
838        self.region("input_map_with_waterline", || {
839            let (input, input_handle) = Input::new(
840                Location::caller(),
841                |tuples: Vec<Box<DynPairs<K, DynUpdate<V, U>>>>| tuples,
842                Arc::new(|| vec![factories.input_pairs_factory.default_box()]),
843            );
844            let input_stream = self.add_source(input);
845            let zset_handle = <UpsertHandle<K, DynUpdate<V, U>>>::new(
846                factories.input_pair_factory,
847                factories.input_pairs_factory,
848                input_handle,
849            );
850
851            let (upsert, errors, waterline) = self.add_upsert_indexed_with_waterline(
852                persistent_id,
853                factories,
854                input_stream,
855                patch_func,
856                init_waterline,
857                extract_ts,
858                least_upper_bound,
859                filter_func,
860                report_func,
861            );
862
863            (upsert, errors, waterline, zset_handle)
864        })
865    }
866}
867
868/*
869// We may want to uncomment and use the following operator based on
870// profiling data.  At the moment the `Input` operator assembles input
871// tuples into batches as they are received from `CollectionHandle`s.
872// Since `CollectionHandle` doesn't consistently map keys to workers,
873// resulting batches may need to be re-sharded by the next operator.
874// It may be more efficient to shard update vectors received from
875// `CollectionHandle` directly without paying the cost of assembling
876// them into batches first.  This is what this operator does.
877impl<K, V> Stream<Circuit<()>, Vec<(K, V)>>
878where
879    K: Send + Hash + Clone + 'static,
880    V: Send + Clone + 'static,
881{
882    fn shard_vec(&self) -> Stream<Circuit<()>, Vec<(K, V)>> {
883        Runtime::runtime()
884            .map(|runtime| {
885                let num_workers = runtime.num_workers();
886
887                if num_workers == 1 {
888                    self.clone()
889                } else {
890                    let (sender, receiver) = self.circuit().new_exchange_operators(
891                        &runtime,
892                        Runtime::worker_index(),
893                        move |batch: Vec<(K, V)>, batches: &mut Vec<Vec<(K, V)>>| {
894                            for _ in 0..num_workers {
895                                batches.push(Vec::with_capacity(batch.len() / num_workers));
896                            }
897
898                            for (key, val) in batch.into_iter() {
899                                let batch_index = fxhash::hash(&key) % num_workers;
900                                batches[batch_index].push((key, val))
901                            }
902                        },
903                        move |output: &mut Vec<(K, V)>, batch: Vec<(K, V)>| {
904                            if output.is_empty() {
905                                output.reserve(batch.len() * num_workers);
906                            }
907                            output.extend(batch);
908                        },
909                    );
910
911                    self.circuit().add_exchange(sender, receiver, self)
912                }
913            })
914            .unwrap_or_else(|| self.clone())
915    }
916}
917*/
918
919/// A handle used to write data to an input stream created by
920/// [`add_input_zset`](`RootCircuit::add_input_zset`),
921/// and [`add_input_indexed_zset`](`RootCircuit::add_input_indexed_zset`)
922/// methods.
923///
924/// The handle provides an API to push updates to the stream in
925/// the form of `(key, value)` tuples:
926///
927///    * For `add_input_zset`, the tuples have the form `(key, weight)`.
928///
929///    * For `add_input_indexed_zset`, the tuples have the form `(key, (value,
930///      weight)`).
931///
932/// See [`add_input_zset`](`RootCircuit::add_input_zset`),
933/// [`add_input_indexed_zset`](`RootCircuit::add_input_indexed_zset`) and
934/// documentation for the exact semantics of these updates.
935///
936/// Internally, the handle manages an array of mailboxes, one for each worker
937/// thread on this host. It automatically partitions updates across mailboxes in
938/// a round robin fashion.  At the start of each clock cycle, the circuit
939/// consumes updates buffered in each mailbox, leaving the mailbox empty.
940pub struct CollectionHandle<K: DataTrait + ?Sized, V: DataTrait + ?Sized> {
941    pair_factory: &'static dyn Factory<DynPair<K, V>>,
942    pub pairs_factory: &'static dyn Factory<DynPairs<K, V>>,
943    pub input_handle: InputHandle<Vec<Box<DynPairs<K, V>>>>,
944    // Used to send tuples to workers in round robin.  Oftentimes the
945    // workers will immediately repartition the inputs based on the hash
946    // of the key; however this is more efficient than doing it here, as
947    // the work will be evenly split across workers.
948    next_worker: Arc<AtomicUsize>,
949}
950
951impl<K: DataTrait + ?Sized, V: DataTrait + ?Sized> Clone for CollectionHandle<K, V> {
952    fn clone(&self) -> Self {
953        Self {
954            pair_factory: self.pair_factory,
955            pairs_factory: self.pairs_factory,
956            input_handle: self.input_handle.clone(),
957            next_worker: self.next_worker.clone(),
958        }
959    }
960}
961
962impl<K: DataTrait + ?Sized, V: DataTrait + ?Sized> CollectionHandle<K, V> {
963    fn new(
964        pair_factory: &'static dyn Factory<DynPair<K, V>>,
965        pairs_factory: &'static dyn Factory<DynPairs<K, V>>,
966        input_handle: InputHandle<Vec<Box<DynPairs<K, V>>>>,
967    ) -> Self {
968        Self {
969            pair_factory,
970            pairs_factory,
971            input_handle,
972            next_worker: Arc::new(AtomicUsize::new(0)),
973        }
974    }
975
976    #[inline]
977    pub fn num_partitions(&self) -> usize {
978        self.input_handle.0.mailbox.len()
979    }
980
981    /// Push a single `(key,value)` pair to the input stream.
982    pub fn dyn_push(&self, k: &mut K, v: &mut V) {
983        let mut tuple = self.pair_factory.default_box();
984        let next_worker = match self.num_partitions() {
985            1 => 0,
986            n => self.next_worker.fetch_add(1, Ordering::AcqRel) % n,
987        };
988        self.input_handle.update_for_worker(next_worker, |tuples| {
989            tuple.from_vals(k, v);
990            tuples.first_mut().unwrap().push_val(&mut *tuple);
991        });
992    }
993
994    /// Push multiple `(key,value)` pairs to the input stream.
995    ///
996    /// This is more efficient than pushing values one-by-one using
997    /// [`Self::dyn_push`].
998    ///
999    /// # Concurrency
1000    ///
1001    /// This method partitions updates across workers and then buffers them
1002    /// atomically with respect to each worker, i.e., each worker observes
1003    /// all updates in an `append` at the same logical time.  However the
1004    /// operation is not atomic as a whole: concurrent `append` and
1005    /// `clear_input` calls (performed via clones of the
1006    /// same `CollectionHandle`) may apply in different orders in different
1007    /// worker threads.  This method is also not atomic with respect to
1008    /// [`DBSPHandle::step`](`crate::DBSPHandle::step`) and
1009    /// [`CircuitHandle::step`](`crate::CircuitHandle::step`) methods: a
1010    /// `DBSPHandle::step` call performed concurrently with `append` may
1011    /// result in only a subset of the workers observing updates from this
1012    /// `append` operation.  The remaining updates will appear
1013    /// during subsequent logical clock cycles.
1014    pub fn dyn_append(&self, vals: &mut Box<DynPairs<K, V>>) {
1015        let num_partitions = self.num_partitions();
1016        let next_worker = if num_partitions > 1 {
1017            self.next_worker.load(Ordering::Acquire)
1018        } else {
1019            0
1020        };
1021
1022        // We divide `val` across `num_partitions` workers as evenly as we can.  The
1023        // first `remainder` workers will receive `quotient + 1` values, and the
1024        // rest will receive `quotient`.
1025        let quotient = vals.len() / num_partitions;
1026        let remainder = vals.len() % num_partitions;
1027        for i in 0..num_partitions {
1028            let mut partition_size = quotient;
1029            if i < remainder {
1030                partition_size += 1;
1031            }
1032
1033            let worker = (next_worker + i) % num_partitions;
1034            if partition_size == vals.len() {
1035                self.input_handle.update_for_worker(worker, |tuples| {
1036                    let tuples = tuples.first_mut().unwrap();
1037                    if tuples.is_empty() {
1038                        swap(tuples, vals);
1039                    } else {
1040                        tuples.append(vals.as_vec_mut());
1041                    }
1042                });
1043                break;
1044            }
1045
1046            // Draining from the end should be more efficient as it doesn't
1047            // require memcpy'ing the tail of the vector to the front.
1048            self.input_handle.update_for_worker(worker, |tuples| {
1049                let tuples = tuples.first_mut().unwrap();
1050                let len = vals.len();
1051                tuples.append_range(vals.as_vec_mut(), len - partition_size, len);
1052            });
1053            vals.truncate(vals.len() - partition_size);
1054        }
1055
1056        assert_eq!(vals.len(), 0);
1057
1058        // If `remainder` is positive, then the values were not distributed completely
1059        // evenly. Advance `self.next_worker` so that the next batch of values
1060        // will give extra values to the ones that didn't get extra this time.
1061        if remainder > 0 {
1062            self.next_worker
1063                .store(next_worker + remainder, Ordering::Release);
1064        }
1065    }
1066
1067    /// Adds `vals` to `partitions`, which must be an vector with
1068    /// `self.num_partitions()` elements, evenly dividing them among the
1069    /// partitions.  If the values can't be evenly divided, then some of them
1070    /// will receive extra, starting with `*next_worker`, and `*next_worker`
1071    /// will be updated so that the next call will start with the partitions
1072    /// that didn't receive extra.
1073    ///
1074    /// This is used with [Self::dyn_append_staged].
1075    pub fn dyn_stage(
1076        &self,
1077        vals: &mut Box<DynPairs<K, V>>,
1078        next_worker: &mut usize,
1079        partitions: &mut [Box<DynPairs<K, V>>],
1080    ) {
1081        let num_partitions = self.num_partitions();
1082
1083        // We divide `val` across `num_partitions` workers as evenly as we can.  The
1084        // first `remainder` workers will receive `quotient + 1` values, and the
1085        // rest will receive `quotient`.
1086        let quotient = vals.len() / num_partitions;
1087        let remainder = vals.len() % num_partitions;
1088        for i in 0..num_partitions {
1089            let mut partition_size = quotient;
1090            if i < remainder {
1091                partition_size += 1;
1092            }
1093
1094            let worker = (*next_worker + i) % num_partitions;
1095            let len = vals.len();
1096            if partition_size == len && partitions[worker].is_empty() {
1097                swap(&mut partitions[worker], vals);
1098                break;
1099            }
1100
1101            // Draining from the end should be more efficient as it doesn't
1102            // require memcpy'ing the tail of the vector to the front.
1103            partitions[worker].append_range(vals.as_vec_mut(), len - partition_size, len);
1104            vals.truncate(len - partition_size);
1105        }
1106
1107        assert!(vals.is_empty());
1108
1109        // If `remainder` is positive, then the values were not distributed completely
1110        // evenly. Advance `self.next_worker` so that the next batch of values
1111        // will give extra values to the ones that didn't get extra this time.
1112        *next_worker = (*next_worker + remainder) % num_partitions;
1113    }
1114
1115    /// Adds `vals` to the inputs, where `vals` was previously partitioned
1116    /// evenly using [Self::dyn_stage].
1117    ///
1118    /// This is much faster than [Self::dyn_append], since the bulk of the work
1119    /// was already done in [Self::dyn_stage].  It can be valuable to do that
1120    /// work in advance if it can be done in parallel with the circuit running.
1121    ///
1122    /// # Concurrency
1123    ///
1124    /// This has the same concurrency implications as [Self::dyn_append],
1125    /// although on a per-worker basis it is atomic.
1126    pub fn dyn_append_staged(&self, vals: Vec<Box<DynPairs<K, V>>>) {
1127        for (worker, vals) in vals.into_iter().enumerate() {
1128            self.input_handle.update_for_worker(worker, |tuples| {
1129                tuples.push(vals);
1130            });
1131        }
1132    }
1133
1134    /// Clear all inputs buffered since the start of the last clock cycle.
1135    ///
1136    /// # Concurrency
1137    ///
1138    /// Similar to [`Self::dyn_append`], this method atomically clears updates
1139    /// buffered for each worker thread, i.e., the worker observes all or none
1140    /// of the updates buffered before the call to `clear_input`; however the
1141    /// operation is not atomic as a whole: concurrent `append` and
1142    /// `clear_input` calls (performed via clones of the
1143    /// same `CollectionHandle`) may apply in different orders in different
1144    /// worker threads.  This method is also not atomic with respect to
1145    /// [`DBSPHandle::step`](`crate::DBSPHandle::step`) and
1146    /// [`CircuitHandle::step`](`crate::CircuitHandle::step`) methods: a
1147    /// `DBSPHandle::step` call performed concurrently with `clear_input` may
1148    /// result in only a subset of the workers observing empty inputs, while
1149    /// other workers observe updates buffered prior to the `clear_input` call.
1150    pub fn clear_input(&self) {
1151        self.input_handle.clear_for_all();
1152    }
1153}
1154
1155pub trait HashFunc<K: ?Sized>: Fn(&K) -> u32 + Send + Sync {}
1156
1157impl<K: ?Sized, F> HashFunc<K> for F where F: Fn(&K) -> u32 + Send + Sync {}
1158
1159/// A handle used to write data to an input stream created by
1160/// [`add_input_set`](`RootCircuit::add_input_set`) and
1161/// [`add_input_map`](`RootCircuit::add_input_map`)
1162/// methods.
1163///
1164/// The handle provides an API to push updates to the stream in
1165/// the form of `(key, value)` tuples:
1166///
1167///    * For `add_input_set`, the tuples have the form `(Key, bool)`.
1168///
1169///    * For `add_input_map`, the tuples have the form `(Key, Option<Value>)`.
1170///
1171/// See [`add_input_set`](`RootCircuit::add_input_set`) and
1172/// [`add_input_map`](`RootCircuit::add_input_map`) documentation for the exact
1173/// semantics of these updates.
1174///
1175/// Internally, the handle manages an array of mailboxes, one for
1176/// each worker thread. It automatically partitions updates across
1177/// mailboxes based on the hash of the key.
1178/// At the start of each clock cycle, the
1179/// circuit consumes updates buffered in each mailbox, leaving
1180/// the mailbox empty.
1181pub struct UpsertHandle<K: DataTrait + ?Sized, V: DataTrait + ?Sized> {
1182    pair_factory: &'static dyn Factory<DynPair<K, V>>,
1183    pub pairs_factory: &'static dyn Factory<DynPairs<K, V>>,
1184    buffers: Vec<Box<DynPairs<K, V>>>,
1185    pub input_handle: InputHandle<Vec<Box<DynPairs<K, V>>>>,
1186    // Sharding the input collection based on the hash of the key is more
1187    // expensive than simple round robin partitioning used by
1188    // `CollectionHandle`; however it is necessary here, since the `Upsert`
1189    // operator requires that all updates to the same key are processed
1190    // by the same worker thread and in the same order they were pushed
1191    // by the client.
1192    pub hash_func: Arc<dyn HashFunc<K>>,
1193}
1194
1195impl<K: DataTrait + ?Sized, V: DataTrait + ?Sized> Clone for UpsertHandle<K, V> {
1196    fn clone(&self) -> Self {
1197        // Don't clone buffers.
1198        Self::with_hasher(
1199            self.pair_factory,
1200            self.pairs_factory,
1201            self.input_handle.clone(),
1202            self.hash_func.clone(),
1203        )
1204    }
1205}
1206
1207impl<K: DataTrait + ?Sized, V: DataTrait + ?Sized> UpsertHandle<K, V> {
1208    fn new(
1209        pair_factory: &'static dyn Factory<DynPair<K, V>>,
1210        pairs_factory: &'static dyn Factory<DynPairs<K, V>>,
1211        input_handle: InputHandle<Vec<Box<DynPairs<K, V>>>>,
1212    ) -> Self {
1213        Self::with_hasher(
1214            pair_factory,
1215            pairs_factory,
1216            input_handle,
1217            Arc::new(|k: &K| k.default_hash() as u32) as Arc<dyn HashFunc<K>>,
1218        )
1219    }
1220
1221    fn with_hasher(
1222        pair_factory: &'static dyn Factory<DynPair<K, V>>,
1223        pairs_factory: &'static dyn Factory<DynPairs<K, V>>,
1224        input_handle: InputHandle<Vec<Box<DynPairs<K, V>>>>,
1225        hash_func: Arc<dyn HashFunc<K>>,
1226    ) -> Self {
1227        Self {
1228            pair_factory,
1229            pairs_factory,
1230            buffers: vec![pairs_factory.default_box(); input_handle.0.mailbox.len()],
1231            input_handle,
1232            hash_func,
1233        }
1234    }
1235
1236    #[inline]
1237    pub fn num_partitions(&self) -> usize {
1238        self.input_handle.0.mailbox.len()
1239    }
1240
1241    /// Push a single `(key,value)` pair to the input stream.
1242    pub fn dyn_push(&self, k: &mut K, v: &mut V) {
1243        let num_partitions = self.num_partitions();
1244
1245        if num_partitions > 1 {
1246            self.input_handle.update_for_worker(
1247                ((self.hash_func)(k) as usize) % num_partitions,
1248                |tuples| {
1249                    let mut tuple = self.pair_factory.default_box();
1250                    tuple.from_vals(k, v);
1251                    tuples.first_mut().unwrap().push_val(&mut *tuple);
1252                },
1253            );
1254        } else {
1255            self.input_handle.update_for_worker(0, |tuples| {
1256                let mut tuple = self.pair_factory.default_box();
1257                tuple.from_vals(k, v);
1258                tuples.first_mut().unwrap().push_val(&mut *tuple);
1259            });
1260        }
1261    }
1262
1263    /// Push multiple `(key,value)` pairs to the input stream.
1264    ///
1265    /// This is more efficient than pushing values one-by-one using
1266    /// [`Self::dyn_push`].
1267    ///
1268    /// # Concurrency
1269    ///
1270    /// This method partitions updates across workers and then buffers them
1271    /// atomically with respect to each worker, i.e., each worker observes
1272    /// all updates in an `append` at the same logical time.  However the
1273    /// operation is not atomic as a whole: concurrent `append` and
1274    /// `clear_input` calls (performed via clones of the
1275    /// same `UpsertHandle`) may apply in different orders in different
1276    /// worker threads.  This method is also not atomic with respect to
1277    /// [`DBSPHandle::step`](`crate::DBSPHandle::step`) and
1278    /// [`CircuitHandle::step`](`crate::CircuitHandle::step`) methods: a
1279    /// `DBSPHandle::step` call performed concurrently with `append` may
1280    /// result in only a subset of the workers observing updates from this
1281    /// `append` operation.  The remaining updates will appear
1282    /// during subsequent logical clock cycles.
1283    pub fn dyn_append(&mut self, vals: &mut Box<DynPairs<K, V>>) {
1284        let num_partitions = self.num_partitions();
1285
1286        if num_partitions > 1 {
1287            for kv in vals.dyn_iter_mut() {
1288                let k = kv.fst();
1289                self.buffers[((self.hash_func)(k) as usize) % num_partitions].push_val(kv)
1290            }
1291            vals.clear();
1292            for worker in 0..num_partitions {
1293                self.input_handle.update_for_worker(worker, |tuples| {
1294                    let tuples = tuples.first_mut().unwrap();
1295                    if tuples.is_empty() {
1296                        *tuples =
1297                            replace(&mut self.buffers[worker], self.pairs_factory.default_box());
1298                    } else {
1299                        tuples.append(self.buffers[worker].as_vec_mut());
1300                    }
1301                })
1302            }
1303        } else {
1304            self.input_handle.update_for_worker(0, |tuples| {
1305                let tuples = tuples.first_mut().unwrap();
1306                if tuples.is_empty() {
1307                    *tuples = replace(vals, self.pairs_factory.default_box());
1308                } else {
1309                    tuples.append(vals.as_vec_mut());
1310                }
1311            });
1312        }
1313    }
1314
1315    /// Adds `vals` to `partitions`, which must be an vector with
1316    /// `self.num_partitions()` elements, evenly dividing them among the
1317    /// partitions.  If the values can't be evenly divided, then some of them
1318    /// will receive extra, starting with `*next_worker`, and `*next_worker`
1319    /// will be updated so that the next call will start with the partitions
1320    /// that didn't receive extra.
1321    ///
1322    /// This is used with [Self::dyn_append_staged].
1323    pub fn dyn_stage(
1324        &self,
1325        vals: &mut Box<DynPairs<K, V>>,
1326        partitions: &mut [Box<DynPairs<K, V>>],
1327    ) {
1328        let num_partitions = self.num_partitions();
1329
1330        for kv in vals.dyn_iter_mut() {
1331            let k = kv.fst();
1332            partitions[((self.hash_func)(k) as usize) % num_partitions].push_val(kv)
1333        }
1334    }
1335
1336    /// Adds `vals` to the inputs, where `vals` was previously partitioned
1337    /// evenly using [Self::dyn_stage].
1338    ///
1339    /// This is much faster than [Self::dyn_append], since the bulk of the work
1340    /// was already done in [Self::dyn_stage].  It can be valuable to do that
1341    /// work in advance if it can be done in parallel with the circuit running.
1342    ///
1343    /// # Concurrency
1344    ///
1345    /// This has the same concurrency implications as [Self::dyn_append],
1346    /// although on a per-worker basis it is atomic.
1347    pub fn dyn_append_staged(&self, vals: Vec<Box<DynPairs<K, V>>>) {
1348        for (worker, vals) in vals.into_iter().enumerate() {
1349            self.input_handle.update_for_worker(worker, |tuples| {
1350                tuples.push(vals);
1351            });
1352        }
1353    }
1354
1355    /// Clear all inputs buffered since the start of the last clock cycle.
1356    ///
1357    /// # Concurrency
1358    ///
1359    /// Similar to [`Self::dyn_append`], this method atomically clears updates
1360    /// buffered for each worker thread, i.e., the worker observes all or none
1361    /// of the updates buffered before the call to `clear_input`; however the
1362    /// operation is not atomic as a whole: concurrent `append` and
1363    /// `clear_input` calls (performed via clones of the
1364    /// same `UpsertHandle`) may apply in different orders in different
1365    /// worker threads.  This method is also not atomic with respect to
1366    /// [`DBSPHandle::step`](`crate::DBSPHandle::step`) and
1367    /// [`CircuitHandle::step`](`crate::CircuitHandle::step`) methods: a
1368    /// `DBSPHandle::step` call performed concurrently with `clear_input` may
1369    /// result in only a subset of the workers observing empty inputs, while
1370    /// other workers observe updates buffered prior to the `clear_input` call.
1371    pub fn clear_input(&self) {
1372        self.input_handle.clear_for_all();
1373    }
1374}
1375
1376#[cfg(test)]
1377mod test {
1378    use crate::{
1379        OutputHandle, RootCircuit, Runtime, Stream, ZWeight,
1380        dynamic::{DowncastTrait, DynData, Erase},
1381        indexed_zset,
1382        operator::{
1383            IndexedZSetHandle, MapHandle, SetHandle, StagedBuffers, Update, ZSetHandle,
1384            input::InputHandle,
1385        },
1386        trace::{BatchReaderFactories, Builder, Cursor},
1387        typed_batch::{
1388            BatchReader, DynBatch, DynBatchReader, DynOrdZSet, OrdIndexedZSet, OrdZSet, TypedBatch,
1389            TypedBox,
1390        },
1391        utils::Tup2,
1392        zset,
1393    };
1394    use anyhow::Result as AnyResult;
1395    use rand::seq::index;
1396    use rand::{Rng, SeedableRng};
1397    use rand_chacha::ChaCha8Rng;
1398    use std::{
1399        cmp::max,
1400        collections::{BTreeSet, HashMap, VecDeque},
1401        iter::once,
1402        ops::Mul,
1403    };
1404
1405    fn input_batches() -> Vec<OrdZSet<u64>> {
1406        vec![
1407            zset! { 1u64 => 1, 2 => 1, 3 => 1 },
1408            zset! { 5u64 => -1, 10 => 2, 11 => 11 },
1409            zset! {},
1410        ]
1411    }
1412
1413    fn input_vecs() -> Vec<Vec<Tup2<u64, ZWeight>>> {
1414        input_batches()
1415            .into_iter()
1416            .map(|batch| {
1417                let mut cursor = batch.cursor();
1418                let mut result = Vec::new();
1419
1420                while cursor.key_valid() {
1421                    result.push(Tup2(
1422                        *cursor.key().downcast_checked::<u64>(),
1423                        *cursor.weight().downcast_checked::<ZWeight>(),
1424                    ));
1425                    cursor.step_key();
1426                }
1427                result
1428            })
1429            .collect()
1430    }
1431
1432    fn input_test_circuit(
1433        circuit: &RootCircuit,
1434        nworkers: usize,
1435    ) -> AnyResult<InputHandle<OrdZSet<u64>>> {
1436        let (stream, handle) = circuit.add_input_stream::<OrdZSet<u64>>();
1437
1438        let mut expected_batches = input_batches().into_iter().chain(input_batches()).chain(
1439            input_batches().into_iter().map(move |batch| {
1440                //let mut result = batch.clone();
1441                let mut cursor = batch.inner().cursor();
1442                let mut result = <DynOrdZSet<DynData> as DynBatch>::Builder::with_capacity(
1443                    &BatchReaderFactories::new::<u64, (), ZWeight>(),
1444                    batch.len(),
1445                    batch.len(),
1446                );
1447
1448                while cursor.key_valid() {
1449                    let w = cursor
1450                        .weight()
1451                        .downcast_checked::<ZWeight>()
1452                        .mul(nworkers as i64);
1453                    result.push_val_diff(().erase(), w.erase());
1454                    result.push_key(cursor.key());
1455                    cursor.step_key();
1456                }
1457                TypedBatch::new(result.done())
1458            }),
1459        );
1460
1461        stream.gather(0).inspect(move |batch| {
1462            if Runtime::worker_index() == 0 {
1463                assert_eq!(batch, &expected_batches.next().unwrap())
1464            }
1465        });
1466
1467        Ok(handle)
1468    }
1469
1470    #[test]
1471    fn input_test_st() {
1472        let (circuit, input_handle) =
1473            RootCircuit::build(move |circuit| input_test_circuit(circuit, 1)).unwrap();
1474
1475        for batch in input_batches().into_iter() {
1476            input_handle.set_for_worker(0, batch);
1477            circuit.transaction().unwrap();
1478        }
1479
1480        for batch in input_batches().into_iter() {
1481            input_handle.update_for_worker(0, |b| *b = batch);
1482            circuit.transaction().unwrap();
1483        }
1484
1485        for batch in input_batches().into_iter() {
1486            input_handle.set_for_all(batch);
1487            circuit.transaction().unwrap();
1488        }
1489    }
1490
1491    fn input_test_mt(workers: usize) {
1492        let (mut dbsp, input_handle) =
1493            Runtime::init_circuit(workers, move |circuit| input_test_circuit(circuit, workers))
1494                .unwrap();
1495
1496        for (round, batch) in input_batches().into_iter().enumerate() {
1497            input_handle.set_for_worker(round % workers, batch);
1498            dbsp.transaction().unwrap();
1499        }
1500
1501        for (round, batch) in input_batches().into_iter().enumerate() {
1502            input_handle.update_for_worker(round % workers, |b| *b = batch);
1503            dbsp.transaction().unwrap();
1504        }
1505
1506        for batch in input_batches().into_iter() {
1507            input_handle.set_for_all(batch);
1508            dbsp.transaction().unwrap();
1509        }
1510
1511        dbsp.kill().unwrap();
1512    }
1513
1514    #[test]
1515    fn input_test_mt1() {
1516        input_test_mt(1);
1517    }
1518
1519    #[test]
1520    fn input_test_mt4() {
1521        input_test_mt(4);
1522    }
1523
1524    fn zset_test_circuit(circuit: &RootCircuit) -> AnyResult<ZSetHandle<u64>> {
1525        let (stream, handle) = circuit.add_input_zset::<u64>();
1526
1527        let mut expected_batches = input_batches()
1528            .into_iter()
1529            .chain(input_batches())
1530            .chain(once(zset! {}));
1531        stream.gather(0).inspect(move |batch| {
1532            if Runtime::worker_index() == 0 {
1533                assert_eq!(batch, &expected_batches.next().unwrap())
1534            }
1535        });
1536
1537        Ok(handle)
1538    }
1539
1540    #[test]
1541    fn zset_test_st() {
1542        let (circuit, input_handle) =
1543            RootCircuit::build(move |circuit| zset_test_circuit(circuit)).unwrap();
1544
1545        for mut vec in input_vecs().into_iter() {
1546            input_handle.append(&mut vec);
1547            circuit.transaction().unwrap();
1548        }
1549
1550        for vec in input_vecs().into_iter() {
1551            for Tup2(k, w) in vec.into_iter() {
1552                input_handle.push(k, w);
1553            }
1554            input_handle.push(5, 1);
1555            input_handle.push(5, -1);
1556            circuit.transaction().unwrap();
1557        }
1558
1559        for mut vec in input_vecs().into_iter() {
1560            input_handle.append(&mut vec);
1561        }
1562        input_handle.clear_input();
1563        circuit.transaction().unwrap();
1564    }
1565
1566    fn zset_test_mt(workers: usize) {
1567        let (mut dbsp, input_handle) =
1568            Runtime::init_circuit(workers, |circuit| zset_test_circuit(circuit)).unwrap();
1569
1570        for mut vec in input_vecs().into_iter() {
1571            input_handle.append(&mut vec);
1572            dbsp.transaction().unwrap();
1573        }
1574
1575        for vec in input_vecs().into_iter() {
1576            for Tup2(k, w) in vec.into_iter() {
1577                input_handle.push(k, w);
1578            }
1579            input_handle.push(5, 1);
1580            input_handle.push(5, -1);
1581            dbsp.transaction().unwrap();
1582        }
1583
1584        for mut vec in input_vecs().into_iter() {
1585            input_handle.append(&mut vec);
1586        }
1587        input_handle.clear_input();
1588        dbsp.transaction().unwrap();
1589
1590        dbsp.kill().unwrap();
1591    }
1592
1593    #[test]
1594    fn zset_test_mt1() {
1595        zset_test_mt(1);
1596    }
1597
1598    #[test]
1599    fn zset_test_mt4() {
1600        zset_test_mt(4);
1601    }
1602
1603    fn input_indexed_batches() -> Vec<OrdIndexedZSet<u64, u64>> {
1604        vec![
1605            indexed_zset! { 1u64 => {1u64 => 1, 2 => 1}, 2 => { 3 => 1 }, 3 => {4 => -1, 5 => 5} },
1606            indexed_zset! { 5u64 => {10u64 => -1}, 10 => {2 => 1, 3 => -1}, 11 => {11 => 11} },
1607            indexed_zset! {},
1608        ]
1609    }
1610
1611    fn input_indexed_vecs() -> Vec<Vec<Tup2<u64, Tup2<u64, i64>>>> {
1612        input_indexed_batches()
1613            .into_iter()
1614            .map(|batch| {
1615                let mut cursor = batch.cursor();
1616                let mut result = Vec::new();
1617
1618                while cursor.key_valid() {
1619                    while cursor.val_valid() {
1620                        result.push(Tup2(
1621                            *cursor.key().downcast_checked::<u64>(),
1622                            Tup2(
1623                                *cursor.val().downcast_checked::<u64>(),
1624                                *cursor.weight().downcast_checked::<ZWeight>(),
1625                            ),
1626                        ));
1627                        cursor.step_val();
1628                    }
1629                    cursor.step_key();
1630                }
1631                result
1632            })
1633            .collect()
1634    }
1635
1636    fn indexed_zset_test_circuit(circuit: &RootCircuit) -> AnyResult<IndexedZSetHandle<u64, u64>> {
1637        let (stream, handle) = circuit.add_input_indexed_zset::<u64, u64>();
1638
1639        let mut expected_batches = input_indexed_batches()
1640            .into_iter()
1641            .chain(input_indexed_batches());
1642        stream.gather(0).inspect(move |batch| {
1643            if Runtime::worker_index() == 0 {
1644                assert_eq!(batch, &expected_batches.next().unwrap())
1645            }
1646        });
1647
1648        Ok(handle)
1649    }
1650
1651    #[test]
1652    fn indexed_zset_test_st() {
1653        let (circuit, input_handle) =
1654            RootCircuit::build(move |circuit| indexed_zset_test_circuit(circuit)).unwrap();
1655
1656        for mut vec in input_indexed_vecs().into_iter() {
1657            input_handle.append(&mut vec);
1658            circuit.transaction().unwrap();
1659        }
1660
1661        for vec in input_indexed_vecs().into_iter() {
1662            for Tup2(k, v) in vec.into_iter() {
1663                input_handle.push(k, (v.0, v.1));
1664            }
1665            input_handle.push(5, (7, 1));
1666            input_handle.push(5, (7, -1));
1667            circuit.transaction().unwrap();
1668        }
1669    }
1670
1671    fn indexed_zset_test_mt(workers: usize) {
1672        let (mut dbsp, input_handle) =
1673            Runtime::init_circuit(workers, |circuit| indexed_zset_test_circuit(circuit)).unwrap();
1674
1675        for mut vec in input_indexed_vecs().into_iter() {
1676            input_handle.append(&mut vec);
1677            dbsp.transaction().unwrap();
1678        }
1679
1680        for vec in input_indexed_vecs().into_iter() {
1681            for Tup2(k, v) in vec.into_iter() {
1682                input_handle.push(k, (v.0, v.1));
1683            }
1684            dbsp.transaction().unwrap();
1685        }
1686
1687        dbsp.kill().unwrap();
1688    }
1689
1690    #[test]
1691    fn indexed_zset_test_mt1() {
1692        indexed_zset_test_mt(1);
1693    }
1694
1695    #[test]
1696    fn indexed_zset_test_mt4() {
1697        indexed_zset_test_mt(4);
1698    }
1699
1700    fn input_set_updates() -> Vec<Vec<Tup2<u64, bool>>> {
1701        vec![
1702            vec![Tup2(1, true), Tup2(2, true), Tup2(3, false)],
1703            vec![Tup2(1, false), Tup2(2, true), Tup2(3, true), Tup2(4, true)],
1704            vec![Tup2(2, false), Tup2(2, true), Tup2(3, true), Tup2(4, false)],
1705            vec![Tup2(2, true), Tup2(2, false)],
1706            vec![Tup2(100, true)],
1707            vec![Tup2(95, true)],
1708            // below watermark
1709            vec![Tup2(80, true)],
1710        ]
1711    }
1712
1713    fn output_set_updates() -> Vec<OrdZSet<u64>> {
1714        vec![
1715            zset! { 1u64 => 1,  2 => 1},
1716            zset! { 1 => -1, 3 => 1,  4 => 1 },
1717            zset! { 4 => -1 },
1718            zset! { 2 => -1 },
1719            zset! { 100 => 1 },
1720            zset! { 95 => 1 },
1721            zset! {},
1722        ]
1723    }
1724
1725    fn set_test_circuit(circuit: &RootCircuit) -> AnyResult<SetHandle<u64>> {
1726        let (stream, handle) = circuit.add_input_set::<u64>();
1727        let watermark: Stream<_, TypedBox<u64, DynData>> =
1728            stream.waterline(|| 0, |k, ()| *k, |k1, k2| max(*k1, *k2));
1729        stream.integrate_trace_retain_keys(&watermark, |k, ts: &u64| *k >= ts.saturating_sub(10));
1730
1731        let mut expected_batches = output_set_updates().into_iter();
1732
1733        stream.gather(0).inspect(move |batch| {
1734            if Runtime::worker_index() == 0 {
1735                assert_eq!(batch, &expected_batches.next().unwrap())
1736            }
1737        });
1738
1739        Ok(handle)
1740    }
1741
1742    #[test]
1743    fn set_test_st() {
1744        let (mut circuit, mut input_handle) =
1745            Runtime::init_circuit(1, move |circuit| set_test_circuit(circuit)).unwrap();
1746
1747        for mut vec in input_set_updates().into_iter() {
1748            input_handle.append(&mut vec);
1749            circuit.transaction().unwrap();
1750        }
1751
1752        let (mut circuit, input_handle) =
1753            Runtime::init_circuit(1, move |circuit| set_test_circuit(circuit)).unwrap();
1754
1755        for vec in input_set_updates().into_iter() {
1756            for Tup2(k, b) in vec.into_iter() {
1757                input_handle.push(k, b);
1758            }
1759            circuit.transaction().unwrap();
1760        }
1761    }
1762
1763    fn set_test_mt(workers: usize) {
1764        let (mut dbsp, mut input_handle) =
1765            Runtime::init_circuit(workers, |circuit| set_test_circuit(circuit)).unwrap();
1766
1767        for mut vec in input_set_updates().into_iter() {
1768            input_handle.append(&mut vec);
1769            dbsp.transaction().unwrap();
1770        }
1771
1772        dbsp.kill().unwrap();
1773
1774        let (mut dbsp, input_handle) =
1775            Runtime::init_circuit(workers, |circuit| set_test_circuit(circuit)).unwrap();
1776
1777        for vec in input_set_updates().into_iter() {
1778            for Tup2(k, b) in vec.into_iter() {
1779                input_handle.push(k, b);
1780            }
1781            dbsp.transaction().unwrap();
1782        }
1783
1784        dbsp.kill().unwrap();
1785    }
1786
1787    #[test]
1788    fn set_test_mt1() {
1789        set_test_mt(1);
1790    }
1791
1792    #[test]
1793    fn set_test_mt4() {
1794        set_test_mt(4);
1795    }
1796
1797    fn input_map_updates1() -> Vec<Vec<Tup2<u64, Update<u64, i64>>>> {
1798        vec![
1799            vec![
1800                Tup2(1, Update::Insert(1)),
1801                Tup2(1, Update::Insert(2)),
1802                Tup2(2, Update::Delete),
1803                Tup2(3, Update::Insert(3)),
1804            ],
1805            vec![
1806                Tup2(1, Update::Insert(1)),
1807                Tup2(1, Update::Delete),
1808                Tup2(2, Update::Insert(2)),
1809                Tup2(3, Update::Insert(4)),
1810                Tup2(4, Update::Insert(4)),
1811                Tup2(4, Update::Delete),
1812                Tup2(4, Update::Insert(5)),
1813            ],
1814            vec![
1815                Tup2(1, Update::Insert(5)),
1816                Tup2(1, Update::Insert(6)),
1817                Tup2(3, Update::Delete),
1818                Tup2(4, Update::Insert(6)),
1819            ],
1820            // bump watermark
1821            vec![Tup2(1, Update::Insert(100))],
1822            // below watermark
1823            vec![Tup2(1, Update::Insert(80))],
1824            vec![Tup2(1, Update::Insert(91))],
1825            // bump watermark more
1826            vec![Tup2(5, Update::Insert(200))],
1827            // below watermark
1828            vec![Tup2(5, Update::Insert(91))],
1829            vec![Tup2(5, Update::Insert(191))],
1830        ]
1831    }
1832
1833    fn output_map_updates1() -> Vec<OrdIndexedZSet<u64, u64>> {
1834        vec![
1835            indexed_zset! { 1u64 => {2u64 => 1},  3 => {3 => 1}},
1836            indexed_zset! { 1 => {2 => -1}, 2 => {2 => 1}, 3 => {3 => -1, 4 => 1}, 4 => {5 => 1}},
1837            indexed_zset! { 1 => {6 => 1},  3 => {4 => -1}, 4 => {5 => -1, 6 => 1}},
1838            indexed_zset! { 1 => {6 => -1, 100 => 1}},
1839            indexed_zset! { 1 => { 100 => -1, 80 => 1 }},
1840            indexed_zset! { 1 => {91 => 1, 80 => -1}},
1841            indexed_zset! { 5 => {200 => 1}},
1842            indexed_zset! { 5 => { 200 => -1, 91 => 1 }},
1843            indexed_zset! { 5 => {191 => 1, 91 => -1}},
1844        ]
1845    }
1846    fn input_map_updates2() -> Vec<Vec<Tup2<u64, Update<u64, i64>>>> {
1847        vec![
1848            vec![
1849                // Insert and instantly update: values add up.
1850                Tup2(1, Update::Insert(1)),
1851                Tup2(1, Update::Update(1)),
1852                // Insert and intantly overwrite: the last value is used.
1853                Tup2(2, Update::Insert(1)),
1854                Tup2(2, Update::Insert(1)),
1855                // Insert and instantly delete.
1856                Tup2(3, Update::Insert(1)),
1857                Tup2(3, Update::Delete),
1858                // Delete non-existing value - ignored.
1859                Tup2(4, Update::Delete),
1860            ],
1861            vec![
1862                // Two more updates added to existing value.
1863                Tup2(1, Update::Update(1)),
1864                Tup2(1, Update::Update(1)),
1865                // Delete and then try to update the value. The update is ignored.
1866                Tup2(2, Update::Delete),
1867                Tup2(2, Update::Update(1)),
1868                // Update missing value and then insert. The update is ignored.
1869                Tup2(3, Update::Update(1)),
1870                Tup2(3, Update::Insert(5)),
1871            ],
1872            vec![
1873                // Updates followed by a delete.
1874                Tup2(1, Update::Update(2)),
1875                Tup2(1, Update::Update(3)),
1876                Tup2(1, Update::Delete),
1877                // Insert -> update -> delete.
1878                Tup2(2, Update::Insert(3)),
1879                Tup2(2, Update::Update(4)),
1880                Tup2(2, Update::Delete),
1881                // Insert the same value - noop.
1882                Tup2(3, Update::Insert(5)),
1883            ],
1884            vec![Tup2(1, Update::Insert(1)), Tup2(2, Update::Insert(5))],
1885            // Push waterline to 15.
1886            vec![Tup2(3, Update::Update(10))],
1887            vec![
1888                // Attempt to update value below waterline - ignored
1889                Tup2(1, Update::Update(10)),
1890                // Update value above waterline - accepted.
1891                Tup2(2, Update::Update(10)),
1892            ],
1893            vec![
1894                // Attempt to delete value below waterline - ignored
1895                Tup2(1, Update::Delete),
1896                // Overwrite value above waterline with a value below - ignored
1897                Tup2(2, Update::Insert(4)),
1898                // Attempt to create new value below waterline - ignored
1899                Tup2(4, Update::Insert(1)),
1900            ],
1901            vec![
1902                // Attempt to insert new value overwriting value below waterline.
1903                //
1904                // This is commented out because the behavior depends on whether
1905                // the spine has already filtered out (1,1):
1906                //
1907                // - If it has, then the insertion succeeds.
1908                //
1909                // - If it hasn't, then the insertion is ignored.
1910                //Tup2(1, Update::Insert(20)),
1911                // Overwrite value above waterline with a new value above waterline - accepted
1912                Tup2(2, Update::Insert(10)),
1913                // Create new value above waterline - accepted, try to overwrite it with a value
1914                // below waterline - ignored.
1915                Tup2(4, Update::Insert(15)),
1916                Tup2(4, Update::Insert(4)),
1917            ],
1918            vec![
1919                // Attempt to update value below waterline.
1920                //
1921                // This is commented out because the behavior depends on whether
1922                // the spine has already filtered out (1,1):
1923                //
1924                // - If it has, then the update succeeds.
1925                //
1926                // - If it hasn't, then the update is ignored because the
1927                //   previous value was below waterline, even though the new value
1928                //   is above it.
1929                //Tup2(1, Update::Update(20)),
1930            ],
1931        ]
1932    }
1933
1934    fn output_map_updates2() -> Vec<OrdIndexedZSet<u64, u64>> {
1935        vec![
1936            indexed_zset! { 1 => {2 => 1}, 2 => {1 => 1}},
1937            indexed_zset! { 1 => {2 => -1, 4 => 1}, 2 => {1 => -1}, 3 => { 5 => 1 } },
1938            indexed_zset! { 1 => {4 => -1} },
1939            indexed_zset! { 1 => {1 => 1}, 2 => {5=>1} },
1940            indexed_zset! { 3 => {5 => -1, 15 => 1} },
1941            indexed_zset! { 1 => {1 => -1, 11 => 1 } , 2 => {5 => -1, 15 => 1} },
1942            indexed_zset! { 1 => {11 => -1}, 2 => { 15 => -1, 4 =>  1}, 4 => { 1 => 1}},
1943            indexed_zset! {2 => {4 => -1, 10 => 1}, 4 => {1 => -1, 4 => 1}},
1944            indexed_zset! {},
1945        ]
1946    }
1947
1948    fn map_test_circuit(
1949        circuit: &RootCircuit,
1950        expected_outputs: fn() -> Vec<OrdIndexedZSet<u64, u64>>,
1951    ) -> AnyResult<MapHandle<u64, u64, i64>> {
1952        let (stream, handle) =
1953            circuit.add_input_map::<u64, u64, i64, _>(|v, u| *v = ((*v as i64) + u) as u64);
1954
1955        let mut expected_batches = expected_outputs().into_iter();
1956
1957        stream.gather(0).inspect(move |batch| {
1958            if Runtime::worker_index() == 0 {
1959                assert_eq!(batch, &expected_batches.next().unwrap())
1960            }
1961        });
1962
1963        Ok(handle)
1964    }
1965
1966    // FIXME: the inputs to these tests are meant to exercise the logic that filters inputs based
1967    // on lateness, but it does not currently work correctly (see https://github.com/feldera/feldera/issues/2669).
1968    // We therefore don't use waterlines in tests and check for the standard upsert behavior
1969    // without filtering.
1970    #[test]
1971    fn map_test_st() {
1972        let (mut circuit, mut input_handle) = Runtime::init_circuit(1, move |circuit| {
1973            map_test_circuit(circuit, output_map_updates1)
1974        })
1975        .unwrap();
1976
1977        for mut vec in input_map_updates1().into_iter() {
1978            input_handle.append(&mut vec);
1979            circuit.transaction().unwrap();
1980        }
1981
1982        let (mut circuit, input_handle) = Runtime::init_circuit(1, move |circuit| {
1983            map_test_circuit(circuit, output_map_updates1)
1984        })
1985        .unwrap();
1986
1987        for vec in input_map_updates1().into_iter() {
1988            for Tup2(k, v) in vec.into_iter() {
1989                input_handle.push(k, v);
1990            }
1991            circuit.transaction().unwrap();
1992        }
1993    }
1994
1995    fn map_test_mt(
1996        workers: usize,
1997        inputs: fn() -> Vec<Vec<Tup2<u64, Update<u64, i64>>>>,
1998        expected_outputs: fn() -> Vec<OrdIndexedZSet<u64, u64>>,
1999    ) {
2000        let expected_outputs_clone = expected_outputs;
2001
2002        let (mut dbsp, mut input_handle) = Runtime::init_circuit(workers, move |circuit| {
2003            map_test_circuit(circuit, expected_outputs_clone)
2004        })
2005        .unwrap();
2006
2007        for mut vec in inputs().into_iter() {
2008            input_handle.append(&mut vec);
2009            dbsp.transaction().unwrap();
2010        }
2011
2012        dbsp.kill().unwrap();
2013
2014        let (mut dbsp, input_handle) = Runtime::init_circuit(workers, move |circuit| {
2015            map_test_circuit(circuit, expected_outputs)
2016        })
2017        .unwrap();
2018
2019        for vec in inputs().into_iter() {
2020            for Tup2(k, v) in vec.into_iter() {
2021                input_handle.push(k, v);
2022            }
2023            dbsp.transaction().unwrap();
2024        }
2025
2026        dbsp.kill().unwrap();
2027    }
2028
2029    // FIXME: the inputs to these tests are meant to exercise the logic that filters inputs based
2030    // on lateness, but it does not currently work correctly (see https://github.com/feldera/feldera/issues/2669).
2031    // We therefore don't use waterlines in tests and check for the standard upsert behavior
2032    // without filtering.
2033    #[test]
2034    fn map_test_mt1() {
2035        map_test_mt(1, input_map_updates1, output_map_updates1);
2036        map_test_mt(1, input_map_updates2, output_map_updates2);
2037    }
2038
2039    #[test]
2040    fn map_test_mt4() {
2041        map_test_mt(4, input_map_updates1, output_map_updates1);
2042        map_test_mt(4, input_map_updates2, output_map_updates2);
2043    }
2044
2045    /// There was a bug in InputUpsert where if within the same step the operator received two
2046    /// vectors with updates, where the first vector contained a key that was deleted and the second
2047    /// vector contained the same key that was inserted, the output would be incorrect because we
2048    /// reordered the insert and the delete.
2049    #[test]
2050    fn map_reinsert_within_step_accumulate_output() {
2051        let (mut dbsp, (input_handle, output_handle)) = Runtime::init_circuit(1, |circuit| {
2052            let (stream, handle) =
2053                circuit.add_input_map::<u64, u64, i64, _>(|v, u| *v = ((*v as i64) + u) as u64);
2054            Ok((handle, stream.accumulate_output()))
2055        })
2056        .unwrap();
2057
2058        // Seed the map with a few keys.
2059        let initial_batch = vec![
2060            Tup2(1, Update::Insert(10)),
2061            Tup2(2, Update::Insert(20)),
2062            Tup2(3, Update::Insert(30)),
2063        ];
2064        // Use stage instead of append to make sure the updates don't get merged in a single vector.
2065        input_handle
2066            .stage(vec![VecDeque::from(initial_batch)])
2067            .flush();
2068        dbsp.transaction().unwrap();
2069        assert_eq!(
2070            output_handle.concat().consolidate(),
2071            indexed_zset! { 1 => {10 => 1}, 2 => {20 => 1}, 3 => {30 => 1} }
2072        );
2073
2074        // Step 1:
2075        // - first batch deletes existing keys
2076        // - second batch reinserts them and adds one extra key
2077        let delete_batch_1 = vec![
2078            Tup2(1, Update::Delete),
2079            Tup2(2, Update::Delete),
2080            Tup2(3, Update::Delete),
2081        ];
2082        input_handle
2083            .stage(vec![VecDeque::from(delete_batch_1)])
2084            .flush();
2085        let reinsert_batch_1 = vec![
2086            Tup2(1, Update::Insert(10)),
2087            Tup2(2, Update::Insert(20)),
2088            Tup2(3, Update::Insert(30)),
2089            Tup2(4, Update::Insert(40)),
2090        ];
2091        input_handle
2092            .stage(vec![VecDeque::from(reinsert_batch_1)])
2093            .flush();
2094        dbsp.transaction().unwrap();
2095        assert_eq!(
2096            output_handle.concat().consolidate(),
2097            indexed_zset! { 4 => {40 => 1} }
2098        );
2099
2100        // Step 2: repeat with one more additional key.
2101        let delete_batch_2 = vec![
2102            Tup2(1, Update::Delete),
2103            Tup2(2, Update::Delete),
2104            Tup2(3, Update::Delete),
2105            Tup2(4, Update::Delete),
2106        ];
2107        input_handle
2108            .stage(vec![VecDeque::from(delete_batch_2)])
2109            .flush();
2110        let reinsert_batch_2 = vec![
2111            Tup2(1, Update::Insert(10)),
2112            Tup2(2, Update::Insert(20)),
2113            Tup2(3, Update::Insert(30)),
2114            Tup2(4, Update::Insert(40)),
2115            Tup2(5, Update::Insert(50)),
2116        ];
2117        input_handle
2118            .stage(vec![VecDeque::from(reinsert_batch_2)])
2119            .flush();
2120        dbsp.transaction().unwrap();
2121        assert_eq!(
2122            output_handle.concat().consolidate(),
2123            indexed_zset! {
2124                5 => {50 => 1}
2125            }
2126        );
2127
2128        dbsp.kill().unwrap();
2129    }
2130
2131    /// Split `items` into `k` contiguous non-empty segments (`k` in 1..=min(3, n)).
2132    fn partition_into_k_contiguous_batches<T: Clone>(
2133        items: Vec<T>,
2134        k: usize,
2135        rng: &mut ChaCha8Rng,
2136    ) -> Vec<Vec<T>> {
2137        let n = items.len();
2138        debug_assert!(k >= 1);
2139        if n == 0 {
2140            return vec![];
2141        }
2142        let k = k.min(n).max(1);
2143        if k == 1 {
2144            return vec![items];
2145        }
2146        let split_at: Vec<usize> = index::sample(rng, n - 1, k - 1)
2147            .into_iter()
2148            .map(|i| i + 1)
2149            .collect();
2150        let mut split_at = split_at;
2151        split_at.sort_unstable();
2152        let mut out = Vec::with_capacity(k);
2153        let mut start = 0usize;
2154        for cut in split_at {
2155            out.push(items[start..cut].to_vec());
2156            start = cut;
2157        }
2158        out.push(items[start..].to_vec());
2159        out
2160    }
2161
2162    fn apply_map_update(state: &mut HashMap<u64, u64>, key: u64, upd: Update<u64, u64>) {
2163        match upd {
2164            Update::Insert(v) => {
2165                state.insert(key, v);
2166            }
2167            Update::Delete => {
2168                state.remove(&key);
2169            }
2170            Update::Update(v) => {
2171                if state.contains_key(&key) {
2172                    state.insert(key, v);
2173                }
2174            }
2175        }
2176    }
2177
2178    fn indexed_zset_state_diff(
2179        before: &HashMap<u64, u64>,
2180        after: &HashMap<u64, u64>,
2181    ) -> OrdIndexedZSet<u64, u64> {
2182        let keys: BTreeSet<u64> = before.keys().chain(after.keys()).copied().collect();
2183        let mut tuples = Vec::new();
2184        for k in keys {
2185            let old_v = before.get(&k).copied();
2186            let new_v = after.get(&k).copied();
2187            match (old_v, new_v) {
2188                (None, None) => {}
2189                (None, Some(nv)) => tuples.push(Tup2(Tup2(k, nv), 1)),
2190                (Some(ov), None) => tuples.push(Tup2(Tup2(k, ov), -1)),
2191                (Some(ov), Some(nv)) if ov != nv => {
2192                    tuples.push(Tup2(Tup2(k, ov), -1));
2193                    tuples.push(Tup2(Tup2(k, nv), 1));
2194                }
2195                _ => {}
2196            }
2197        }
2198        OrdIndexedZSet::from_tuples((), tuples)
2199    }
2200
2201    /// Stress-test the implementation of the InputUpsert operator.
2202    ///
2203    /// Every iteration generates several upates to the same three keys and feeds them in up to three batches
2204    /// using `stage().flush()`. The accumulated output must match applying updates in order (last update per
2205    /// key wins within the folded semantics of [`InputUpsert`]).
2206    #[test]
2207    fn randomized_input_map_test() {
2208        let (mut dbsp, (input_handle, output_handle)) = Runtime::init_circuit(1, |circuit| {
2209            let (stream, handle) = circuit.add_input_map::<u64, u64, u64, _>(|v, u| *v = *u);
2210            Ok((handle, stream.accumulate_output()))
2211        })
2212        .unwrap();
2213
2214        let mut state: HashMap<u64, u64> = HashMap::new();
2215        let mut rng = ChaCha8Rng::seed_from_u64(0x_6D61_705F_7374_6167_u64);
2216
2217        for _step in 0..50000 {
2218            //println!("step {}", step);
2219            let before = state.clone();
2220            let num_updates = rng.gen_range(0..=12);
2221
2222            let updates: Vec<Tup2<u64, Update<u64, u64>>> = (0..num_updates)
2223                .map(|_| {
2224                    let key = rng.gen_range(1u64..=3);
2225                    let upd = match rng.gen_range(0..3) {
2226                        0 => Update::Insert(rng.gen_range(0u64..512)),
2227                        1 => Update::Delete,
2228                        2 => Update::Update(rng.gen_range(0u64..=512)),
2229                        _ => unreachable!(),
2230                    };
2231                    Tup2(key, upd)
2232                })
2233                .collect();
2234
2235            for Tup2(k, u) in &updates {
2236                apply_map_update(&mut state, *k, u.clone());
2237            }
2238
2239            let num_batches = if num_updates == 0 {
2240                1usize
2241            } else {
2242                rng.gen_range(1..=std::cmp::min(3, num_updates))
2243            };
2244
2245            let batches = if num_updates == 0 {
2246                vec![Vec::new()]
2247            } else {
2248                partition_into_k_contiguous_batches(updates, num_batches, &mut rng)
2249            };
2250
2251            for batch in batches {
2252                //println!("batch {:?}", batch);
2253                input_handle.stage(once(VecDeque::from(batch))).flush();
2254            }
2255
2256            dbsp.transaction().unwrap();
2257
2258            let expected = indexed_zset_state_diff(&before, &state);
2259            assert_eq!(
2260                output_handle.concat().consolidate(),
2261                expected,
2262                "accumulated output should equal the net map change for this transaction"
2263            );
2264        }
2265
2266        dbsp.kill().unwrap();
2267    }
2268
2269    fn map_with_waterline_test_circuit(
2270        circuit: &RootCircuit,
2271    ) -> (
2272        MapHandle<u64, u64, i64>,
2273        OutputHandle<TypedBox<u64, DynData>>,
2274        OutputHandle<OrdIndexedZSet<u64, u64>>,
2275        OutputHandle<OrdZSet<String>>,
2276    ) {
2277        let (stream, errors, waterline, input_handle) = circuit
2278            .add_input_map_with_waterline::<u64, u64, i64, u64, String, _, _, _, _, _, _>(
2279                |v, u| *v = ((*v as i64) + u) as u64,
2280                || 0u64,
2281                |_k, v| *v,
2282                |wl1, wl2| max(*wl1, *wl2),
2283                |wl, _k, v| *v >= *wl,
2284                |wl, k, v, w| format!("waterline: {wl}, key: {k}, value: {v}, weight: {w}"),
2285            );
2286
2287        let output_handle = stream.output();
2288        let waterline_output_handle = waterline.output();
2289        let errors_handle = errors.output();
2290
2291        (
2292            input_handle,
2293            waterline_output_handle,
2294            output_handle,
2295            errors_handle,
2296        )
2297    }
2298
2299    /// Test add_input_map_with_waterline over the value part of the tuple.
2300    fn map_with_waterline_test(
2301        workers: usize,
2302        inputs: fn() -> Vec<Vec<Tup2<u64, Update<u64, i64>>>>,
2303        expected_outputs: fn() -> Vec<(OrdIndexedZSet<u64, u64>, OrdZSet<String>, u64)>,
2304    ) {
2305        let expected_outputs = expected_outputs();
2306
2307        let (mut dbsp, (mut input_handle, waterline_handle, output_handle, errors_handle)) =
2308            Runtime::init_circuit(workers, move |circuit| {
2309                Ok(map_with_waterline_test_circuit(circuit))
2310            })
2311            .unwrap();
2312
2313        for (step, mut vec) in inputs().into_iter().enumerate() {
2314            input_handle.append(&mut vec);
2315            dbsp.transaction().unwrap();
2316            let output = output_handle.consolidate();
2317            assert_eq!(
2318                *waterline_handle.take_from_worker(0).unwrap(),
2319                expected_outputs[step].2
2320            );
2321            assert_eq!(output, expected_outputs[step].0);
2322
2323            let errors = errors_handle.consolidate();
2324            assert_eq!(errors, expected_outputs[step].1);
2325        }
2326
2327        dbsp.kill().unwrap();
2328    }
2329
2330    fn input_map_with_waterline_updates1() -> Vec<Vec<Tup2<u64, Update<u64, i64>>>> {
2331        vec![
2332            vec![
2333                Tup2(1, Update::Insert(1)),
2334                Tup2(1, Update::Insert(1)),
2335                Tup2(2, Update::Delete), // ignored
2336                Tup2(3, Update::Insert(1)),
2337            ], // waterline: 1
2338            vec![
2339                Tup2(1, Update::Insert(1)), // noop
2340                Tup2(1, Update::Delete),    // ok
2341                Tup2(2, Update::Insert(2)), // ok
2342                Tup2(3, Update::Insert(3)), // ok
2343                Tup2(4, Update::Insert(3)), // ok
2344                Tup2(4, Update::Delete),    // ok
2345                Tup2(4, Update::Insert(5)), // ok
2346            ], // waterline: 5
2347            vec![
2348                Tup2(1, Update::Insert(5)), // ok
2349                Tup2(2, Update::Insert(6)), // rejected: replaces value below waterline
2350                Tup2(3, Update::Delete),    // rejected: deletes value below waterline
2351                Tup2(4, Update::Insert(6)), // ok
2352            ], // waterline: 6
2353            vec![
2354                Tup2(5, Update::Insert(5)), // rejected: inserts value below waterline
2355                Tup2(6, Update::Insert(6)), // ok
2356                Tup2(7, Update::Insert(7)), // ok
2357                Tup2(4, Update::Insert(8)), // ok
2358            ], // waterline: 8
2359        ]
2360    }
2361
2362    fn output_map_with_waterline_updates1() -> Vec<(OrdIndexedZSet<u64, u64>, OrdZSet<String>, u64)>
2363    {
2364        vec![
2365            (
2366                indexed_zset! { 1u64 => {1u64 => 1}, 3 => {1 => 1} },
2367                zset! {},
2368                1,
2369            ),
2370            (
2371                indexed_zset! { 1 => {1 => -1}, 2 => {2 => 1}, 3 => {1 => -1, 3 => 1}, 4 => {5 => 1} },
2372                zset! {},
2373                5,
2374            ),
2375            (
2376                indexed_zset! { 1 => {5 => 1}, 4 => {5 => -1, 6 => 1} },
2377                zset! { "waterline: 5, key: 2, value: 2, weight: -1".to_string() => 1, "waterline: 5, key: 3, value: 3, weight: -1".to_string() => 1 },
2378                6,
2379            ),
2380            (
2381                indexed_zset! { 4 => {6 => -1, 8 => 1}, 6 => {6 => 1}, 7 => {7 => 1} },
2382                zset! {"waterline: 6, key: 5, value: 5, weight: 1".to_string() => 1},
2383                8,
2384            ),
2385        ]
2386    }
2387
2388    #[test]
2389    fn map_with_waterline_test_mt() {
2390        map_with_waterline_test(
2391            4,
2392            input_map_with_waterline_updates1,
2393            output_map_with_waterline_updates1,
2394        );
2395    }
2396
2397    fn map_with_waterline_gc_test_circuit(
2398        circuit: &RootCircuit,
2399    ) -> (
2400        MapHandle<u64, u64, i64>,
2401        OutputHandle<TypedBox<u64, DynData>>,
2402        OutputHandle<OrdIndexedZSet<u64, u64>>,
2403        OutputHandle<OrdZSet<String>>,
2404    ) {
2405        let (stream, errors, waterline, input_handle) = circuit
2406            .add_input_map_with_waterline::<u64, u64, i64, u64, String, _, _, _, _, _, _>(
2407                |v, u| *v = ((*v as i64) + u) as u64,
2408                || 0u64,
2409                |k, _v| *k,
2410                |wl1, wl2| max(*wl1, *wl2),
2411                |wl, k, _v| *k >= *wl,
2412                |wl, k, v, w| format!("waterline: {wl}, key: {k}, value: {v}, weight: {w}"),
2413            );
2414
2415        stream.integrate_trace_retain_keys(&waterline, |key, wl| *key >= *wl);
2416
2417        let output_handle = stream.output();
2418        let waterline_output_handle = waterline.output();
2419        let errors_handle = errors.output();
2420
2421        (
2422            input_handle,
2423            waterline_output_handle,
2424            output_handle,
2425            errors_handle,
2426        )
2427    }
2428
2429    /// Test add_input_map_with_waterline over the key part of the tuple.
2430    /// This operator can get GC'd.
2431    fn map_with_waterline_gc_test(
2432        workers: usize,
2433        inputs: fn() -> Vec<Vec<Tup2<u64, Update<u64, i64>>>>,
2434        expected_outputs: fn() -> Vec<(OrdIndexedZSet<u64, u64>, OrdZSet<String>, u64)>,
2435    ) {
2436        let expected_outputs = expected_outputs();
2437
2438        let (mut dbsp, (mut input_handle, waterline_handle, output_handle, errors_handle)) =
2439            Runtime::init_circuit(workers, move |circuit| {
2440                Ok(map_with_waterline_gc_test_circuit(circuit))
2441            })
2442            .unwrap();
2443
2444        for (step, mut vec) in inputs().into_iter().enumerate() {
2445            input_handle.append(&mut vec);
2446            dbsp.transaction().unwrap();
2447            let output = output_handle.consolidate();
2448            assert_eq!(
2449                *waterline_handle.take_from_worker(0).unwrap(),
2450                expected_outputs[step].2
2451            );
2452            assert_eq!(output, expected_outputs[step].0);
2453
2454            let errors = errors_handle.consolidate();
2455            assert_eq!(errors, expected_outputs[step].1);
2456        }
2457
2458        dbsp.kill().unwrap();
2459    }
2460
2461    fn input_map_with_waterline_gc_updates1() -> Vec<Vec<Tup2<u64, Update<u64, i64>>>> {
2462        vec![
2463            vec![
2464                Tup2(1, Update::Insert(1)),
2465                Tup2(1, Update::Insert(1)),
2466                Tup2(2, Update::Delete), // ignored
2467                Tup2(3, Update::Insert(1)),
2468            ], // waterline: 3
2469            vec![
2470                Tup2(1, Update::Insert(1)), // rejected
2471                Tup2(1, Update::Delete),    // rejected
2472                Tup2(2, Update::Insert(2)), // rejected
2473                Tup2(3, Update::Insert(3)), // ok
2474                Tup2(3, Update::Insert(4)), // ok
2475                Tup2(4, Update::Insert(3)), // ok
2476                Tup2(4, Update::Delete),    // ok
2477                Tup2(4, Update::Insert(5)), // ok
2478            ], // waterline: 4
2479            vec![
2480                Tup2(3, Update::Delete),    // rejected
2481                Tup2(5, Update::Insert(6)), // ok
2482                Tup2(5, Update::Delete),    // ok
2483            ], // waterline: (still) 4
2484            vec![
2485                Tup2(5, Update::Insert(5)), // ok
2486                Tup2(6, Update::Insert(6)), // ok
2487                Tup2(7, Update::Insert(7)), // ok
2488            ], // waterline: 7
2489        ]
2490    }
2491
2492    fn output_map_with_waterline_gc_updates1()
2493    -> Vec<(OrdIndexedZSet<u64, u64>, OrdZSet<String>, u64)> {
2494        vec![
2495            (
2496                indexed_zset! { 1u64 => {1u64 => 1}, 3 => {1 => 1} },
2497                zset! {},
2498                3,
2499            ),
2500            (
2501                indexed_zset! { 3 => {1 => -1, 4 => 1}, 4 => {5 => 1} },
2502                zset! {"waterline: 3, key: 1, value: 1, weight: -1".to_string() => 1, "waterline: 3, key: 2, value: 2, weight: 1".to_string() => 1},
2503                4,
2504            ),
2505            (
2506                indexed_zset! {},
2507                zset! {"waterline: 4, key: 3, value: 4, weight: -1".to_string() => 1},
2508                4,
2509            ),
2510            (
2511                indexed_zset! { 5 => {5 => 1}, 6 => {6 => 1}, 7 => {7 => 1} },
2512                zset! {},
2513                7,
2514            ),
2515        ]
2516    }
2517
2518    #[test]
2519    fn map_with_waterline_gc_test_mt() {
2520        map_with_waterline_gc_test(
2521            4,
2522            input_map_with_waterline_gc_updates1,
2523            output_map_with_waterline_gc_updates1,
2524        );
2525    }
2526}