krabka_client_streams/dsl/
time_windowed_cogrouped.rs1use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc, sync::Arc};
8
9use crate::{
10 dsl::{
11 builder::InternalStreamsBuilder,
12 cogrouped::{
13 CogroupInput, CogroupKind, CogroupSpec, CogroupedKStream, StoreRegistrarFn,
14 lower_cogroup,
15 },
16 config::Materialized,
17 kgrouped::mint_store_name,
18 ktable::KTable,
19 names,
20 windowed_kgrouped::windowed_suppress_factory,
21 windows::{TimeWindowedSerde, TimeWindows, Windowed},
22 },
23 processor::serde::Serde,
24};
25
26impl<K, VOut> CogroupedKStream<K, VOut>
27where
28 K: Any + Send + Sync + Clone,
29 VOut: Any + Send + Sync + Clone,
30{
31 #[must_use]
33 pub fn windowed_by(self, windows: TimeWindows) -> TimeWindowedCogroupedStream<K, VOut> {
34 TimeWindowedCogroupedStream {
35 builder: self.builder,
36 inputs: self.inputs,
37 windows,
38 _pd: PhantomData,
39 }
40 }
41}
42
43pub struct TimeWindowedCogroupedStream<K, VOut> {
46 builder: Rc<RefCell<InternalStreamsBuilder>>,
47 inputs: Vec<CogroupInput<K, VOut>>,
48 windows: TimeWindows,
49 _pd: PhantomData<fn() -> (K, VOut)>,
50}
51
52impl<K, VOut> TimeWindowedCogroupedStream<K, VOut>
53where
54 K: Any + Send + Sync + Clone,
55 VOut: Any + Send + Sync + Clone,
56{
57 pub fn aggregate_explicit<KS, VS, I>(
59 self,
60 init: I,
61 materialized: impl Into<Materialized<KS, VS>>,
62 ) -> KTable<Windowed<K>, VOut, TimeWindowedSerde<KS>, VS>
63 where
64 KS: Serde<K> + Clone + 'static,
65 VS: Serde<VOut> + Clone + 'static,
66 I: Fn() -> VOut + Send + Sync + 'static,
67 {
68 let materialized = materialized.into();
69 let store_name = mint_store_name(&self.builder, &materialized, names::AGGREGATE_STORE);
70 let suppress_factory = windowed_suppress_factory::<K, VOut, KS, VS>(
71 materialized.key_serde.clone(),
72 materialized.value_serde.clone(),
73 self.windows,
74 );
75 let Materialized {
76 key_serde,
77 value_serde,
78 logging,
79 caching,
80 ..
81 } = materialized;
82 let spec = CogroupSpec::<K, VOut> {
83 kind: CogroupKind::Time(self.windows),
84 init: Arc::new(init),
85 merger: None,
86 };
87 let ks = key_serde.clone();
88 let vs = value_serde.clone();
89 let store_for_reg = store_name.clone();
90 let size = self.windows.size;
91 let grace = self.windows.grace;
92 let registrar: StoreRegistrarFn = Box::new(move |state, procs| {
93 state.topology.add_window_store::<K, VOut, KS, VS>(
94 store_for_reg.clone(),
95 ks.clone(),
96 vs.clone(),
97 (size, size, grace),
99 procs,
100 );
101 state.topology.mark_store_caching(&store_for_reg, caching);
102 });
103 let merge_id = lower_cogroup::<K, VOut, Windowed<K>>(
104 &self.builder,
105 self.inputs,
106 &store_name,
107 &spec,
108 logging,
109 registrar,
110 );
111 KTable::new(
112 Rc::clone(&self.builder),
113 merge_id,
114 Some(store_name),
115 None,
116 TimeWindowedSerde::new(key_serde, self.windows.size),
117 value_serde,
118 )
119 .with_window_grace(Some(self.windows.grace))
120 .with_suppress_factory(Some(suppress_factory))
121 }
122}
123
124#[cfg(test)]
125mod caching_tests {
126 use assert2::check;
127 use krabka_units::prelude::*;
128
129 use crate::{
130 BufferConfig, I64Serde, Materialized, Produced, StringSerde, Suppressed, TimeWindows,
131 dsl::{StreamsBuilder, windows::TimeWindowedSerde},
132 store::backend::StoreBackend,
133 };
134
135 #[test]
136 fn time_windowed_cogroup_marks_store_cached() {
137 let b = StreamsBuilder::new();
138 let g1 = b.stream::<String, String>(["in1"]).group_by_key();
139 let g2 = b.stream::<String, String>(["in2"]).group_by_key();
140 g1.cogroup::<i64, _>(|_k, v: &String, acc| {
141 acc + i64::try_from(v.len()).unwrap_or(i64::MAX)
142 })
143 .cogroup(g2, |_k, _v: &String, acc| acc + 1)
144 .windowed_by(TimeWindows::of_size(millis(100)))
145 .aggregate_explicit(
146 || 0i64,
147 Materialized::with(StringSerde, I64Serde).as_store("cg"),
148 )
149 .suppress(Suppressed::until_window_closes(BufferConfig::unbounded()))
150 .to_stream()
151 .to_explicit(
152 "out",
153 Produced::with(TimeWindowedSerde::new(StringSerde, millis(100)), I64Serde),
154 );
155 let built = b.build("app").unwrap();
156 let g = pollster::block_on(built.instantiate(&StoreBackend::InMemory, "app", kibibytes(1)))
157 .unwrap();
158 check!(g.cache_owner.contains_key("cg"));
159 }
160
161 #[test]
162 fn time_windowed_cogroup_uncached_when_off() {
163 let b = StreamsBuilder::new();
164 let g1 = b.stream::<String, String>(["in1"]).group_by_key();
165 let g2 = b.stream::<String, String>(["in2"]).group_by_key();
166 g1.cogroup::<i64, _>(|_k, v: &String, acc| {
167 acc + i64::try_from(v.len()).unwrap_or(i64::MAX)
168 })
169 .cogroup(g2, |_k, _v: &String, acc| acc + 1)
170 .windowed_by(TimeWindows::of_size(millis(100)))
171 .aggregate_explicit(
172 || 0i64,
173 Materialized::with(StringSerde, I64Serde)
174 .as_store("cg")
175 .with_caching(false),
176 )
177 .to_stream()
178 .to_explicit(
179 "out",
180 Produced::with(TimeWindowedSerde::new(StringSerde, millis(100)), I64Serde),
181 );
182 let built = b.build("app").unwrap();
183 let g = pollster::block_on(built.instantiate(&StoreBackend::InMemory, "app", kibibytes(1)))
184 .unwrap();
185 check!(!g.cache_owner.contains_key("cg"));
186 }
187}