1use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
11
12use crate::{
13 dsl::{
14 builder::InternalStreamsBuilder,
15 config::Materialized,
16 emit::EmitStrategy,
17 graph::{GraphNodeKind, LowerState, NodeId},
18 kgrouped::{KGroupedStream, RepartitionLowerFn, mint_store_name},
19 ktable::{KTable, SuppressStoreFactory},
20 names,
21 processors::sliding_window_aggregate::{
22 KStreamSlidingWindowAggregateProcessor, KStreamSlidingWindowReduceProcessor,
23 },
24 windows::{SlidingWindows, TimeWindowedSerde, Windowed},
25 },
26 processor::serde::{DefaultSerde, Serde},
27 topology::NodeHandle,
28};
29
30pub struct SlidingWindowedKGroupedStream<K, V> {
35 builder: Rc<RefCell<InternalStreamsBuilder>>,
36 parent: NodeId,
37 key_changing_upstream: bool,
38 #[allow(dead_code)]
39 grouped_name: Option<String>,
40 repartition_lower: Option<RepartitionLowerFn>,
41 windows: SlidingWindows,
42 emit: EmitStrategy,
43 _pd: PhantomData<fn() -> (K, V)>,
44}
45
46impl<K, V> SlidingWindowedKGroupedStream<K, V>
47where
48 K: Any + Send + Sync + Clone,
49 V: Any + Send + Sync + Clone,
50{
51 pub(crate) fn new(
52 builder: Rc<RefCell<InternalStreamsBuilder>>,
53 parent: NodeId,
54 key_changing_upstream: bool,
55 grouped_name: Option<String>,
56 repartition_lower: Option<RepartitionLowerFn>,
57 windows: SlidingWindows,
58 ) -> Self {
59 Self {
60 builder,
61 parent,
62 key_changing_upstream,
63 grouped_name,
64 repartition_lower,
65 windows,
66 emit: EmitStrategy::default(),
67 _pd: PhantomData,
68 }
69 }
70
71 #[must_use]
77 pub fn emit_strategy(mut self, emit: EmitStrategy) -> Self {
78 self.emit = emit;
79 self
80 }
81
82 pub fn count_explicit<KS, VS>(
83 self,
84 materialized: impl Into<Materialized<KS, VS>>,
85 ) -> KTable<Windowed<K>, i64, TimeWindowedSerde<KS>, VS>
86 where
87 KS: Serde<K> + Clone + 'static,
88 VS: Serde<i64> + Clone + 'static,
89 {
90 self.aggregate_inner(
91 materialized.into(),
92 names::AGGREGATE_STORE,
93 || 0i64,
94 |_k: &K, _v: &V, acc: i64| acc + 1,
95 )
96 }
97
98 pub fn reduce_explicit<KS, VS, R>(
99 self,
100 reducer: R,
101 materialized: impl Into<Materialized<KS, VS>>,
102 ) -> KTable<Windowed<K>, V, TimeWindowedSerde<KS>, VS>
103 where
104 KS: Serde<K> + Clone + 'static,
105 VS: Serde<V> + Clone + 'static,
106 R: Fn(&V, &V) -> V + Clone + Send + Sync + 'static,
107 {
108 let materialized = materialized.into();
109 let store_name = mint_store_name(&self.builder, &materialized, names::REDUCE_STORE);
110 self.lower_reduce::<KS, VS, R>(materialized, store_name, reducer)
111 }
112
113 pub fn aggregate_explicit<KS, VS, VA, I, A>(
114 self,
115 init: I,
116 agg: A,
117 materialized: impl Into<Materialized<KS, VS>>,
118 ) -> KTable<Windowed<K>, VA, TimeWindowedSerde<KS>, VS>
119 where
120 VA: Any + Send + Sync + Clone,
121 KS: Serde<K> + Clone + 'static,
122 VS: Serde<VA> + Clone + 'static,
123 I: Fn() -> VA + Clone + Send + Sync + 'static,
124 A: Fn(&K, &V, VA) -> VA + Clone + Send + Sync + 'static,
125 {
126 self.aggregate_inner(materialized.into(), names::AGGREGATE_STORE, init, agg)
127 }
128
129 pub fn count(
130 self,
131 store_name: impl Into<String>,
132 ) -> KTable<
133 Windowed<K>,
134 i64,
135 TimeWindowedSerde<<K as DefaultSerde>::Serde>,
136 crate::processor::serde::I64Serde,
137 >
138 where
139 K: DefaultSerde,
140 <K as DefaultSerde>::Serde: Serde<K> + Clone,
141 {
142 self.count_explicit(
143 Materialized::with(
144 <K as DefaultSerde>::Serde::default(),
145 crate::processor::serde::I64Serde,
146 )
147 .as_store(store_name),
148 )
149 }
150
151 pub fn reduce<R>(
152 self,
153 reducer: R,
154 store_name: impl Into<String>,
155 ) -> KTable<
156 Windowed<K>,
157 V,
158 TimeWindowedSerde<<K as DefaultSerde>::Serde>,
159 <V as DefaultSerde>::Serde,
160 >
161 where
162 K: DefaultSerde,
163 V: DefaultSerde,
164 <K as DefaultSerde>::Serde: Serde<K> + Clone,
165 <V as DefaultSerde>::Serde: Serde<V> + Clone,
166 R: Fn(&V, &V) -> V + Clone + Send + Sync + 'static,
167 {
168 self.reduce_explicit(
169 reducer,
170 Materialized::with(
171 <K as DefaultSerde>::Serde::default(),
172 <V as DefaultSerde>::Serde::default(),
173 )
174 .as_store(store_name),
175 )
176 }
177
178 pub fn aggregate<VA, I, A>(
179 self,
180 init: I,
181 agg: A,
182 store_name: impl Into<String>,
183 ) -> KTable<
184 Windowed<K>,
185 VA,
186 TimeWindowedSerde<<K as DefaultSerde>::Serde>,
187 <VA as DefaultSerde>::Serde,
188 >
189 where
190 VA: DefaultSerde + Any + Send + Clone,
191 K: DefaultSerde,
192 <K as DefaultSerde>::Serde: Serde<K> + Clone,
193 <VA as DefaultSerde>::Serde: Serde<VA> + Clone,
194 I: Fn() -> VA + Clone + Send + Sync + 'static,
195 A: Fn(&K, &V, VA) -> VA + Clone + Send + Sync + 'static,
196 {
197 self.aggregate_explicit(
198 init,
199 agg,
200 Materialized::with(
201 <K as DefaultSerde>::Serde::default(),
202 <VA as DefaultSerde>::Serde::default(),
203 )
204 .as_store(store_name),
205 )
206 }
207
208 fn aggregate_inner<KS, VS, VA, I, A>(
209 self,
210 materialized: Materialized<KS, VS>,
211 store_prefix: &'static str,
212 init: I,
213 agg: A,
214 ) -> KTable<Windowed<K>, VA, TimeWindowedSerde<KS>, VS>
215 where
216 VA: Any + Send + Sync + Clone,
217 KS: Serde<K> + Clone + 'static,
218 VS: Serde<VA> + Clone + 'static,
219 I: Fn() -> VA + Clone + Send + Sync + 'static,
220 A: Fn(&K, &V, VA) -> VA + Clone + Send + Sync + 'static,
221 {
222 let store_name = mint_store_name(&self.builder, &materialized, store_prefix);
223 self.lower_aggregate::<KS, VS, VA, I, A>(materialized, store_name, init, agg)
224 }
225
226 fn lower_aggregate<KS, VS, VA, I, A>(
227 mut self,
228 materialized: Materialized<KS, VS>,
229 store_name: String,
230 init: I,
231 agg: A,
232 ) -> KTable<Windowed<K>, VA, TimeWindowedSerde<KS>, VS>
233 where
234 VA: Any + Send + Sync + Clone,
235 KS: Serde<K> + Clone + 'static,
236 VS: Serde<VA> + Clone + 'static,
237 I: Fn() -> VA + Clone + Send + Sync + 'static,
238 A: Fn(&K, &V, VA) -> VA + Clone + Send + Sync + 'static,
239 {
240 let Materialized {
241 key_serde,
242 value_serde,
243 caching,
244 ..
245 } = materialized;
246 let suppress_factory = sliding_suppress_factory::<K, VA, KS, VS>(
247 key_serde.clone(),
248 value_serde.clone(),
249 self.windows,
250 );
251 let parent = self.parent;
252 let key_changing = self.key_changing_upstream;
253 let rp_lower = self.repartition_lower.take();
254 let windows = self.windows;
255 let emit = self.emit;
256 let mut g = self.builder.borrow_mut();
257 let agg_parent = KGroupedStream::<K, V>::record_repartition(
258 &mut g,
259 &store_name,
260 parent,
261 key_changing,
262 rp_lower,
263 );
264
265 let agg_name = g.new_processor_name(names::AGGREGATE);
266 let agg_id = g.graph.add(
267 agg_name.clone(),
268 GraphNodeKind::Aggregate {
269 store_name: store_name.clone(),
270 changelog: true,
271 },
272 vec![agg_parent],
273 );
274 let store_for_thunk = store_name.clone();
275 let key_serde_for_lower = key_serde.clone();
276 let value_serde_for_lower = value_serde.clone();
277 g.graph.nodes[agg_id].lower = Some(Box::new(move |state: &mut LowerState| {
278 let parent = NodeHandle::<K, V>::from_name(state.handle_name[&agg_parent].clone());
279 let store_for_proc = store_for_thunk.clone();
280 let h = state
281 .topology
282 .add_processor::<K, V, Windowed<K>, crate::dsl::processors::change::Change<VA>, _, _, _>(
283 agg_name.clone(),
284 move || KStreamSlidingWindowAggregateProcessor {
285 store_name: store_for_proc.clone(),
286 windows,
287 init: init.clone(),
288 agg: agg.clone(),
289 stream_time: i64::MIN,
290 emit,
291 last_emitted_close: i64::MIN,
292 forwarder: crate::dsl::processors::tuple_forwarder::TupleForwarder::default(),
293 _pd: PhantomData,
294 },
295 [parent],
296 );
297 state.topology.add_window_store::<K, VA, KS, VS>(
302 store_for_thunk.clone(),
303 key_serde_for_lower.clone(),
304 value_serde_for_lower.clone(),
305 (
308 windows.time_difference * 2.0,
309 windows.time_difference,
310 windows.grace,
311 ),
312 [h.name().to_string()],
313 );
314 state
318 .topology
319 .mark_store_caching(&store_for_thunk, caching && emit.is_on_update());
320 state.handle_name.insert(agg_id, h.name().to_string());
321 }));
322
323 drop(g);
324 KTable::new(
325 Rc::clone(&self.builder),
326 agg_id,
327 Some(store_name),
328 None,
329 TimeWindowedSerde::new(key_serde.clone(), windows.time_difference),
330 value_serde.clone(),
331 )
332 .with_window_grace(Some(windows.grace))
333 .with_suppress_factory(Some(suppress_factory))
334 }
335
336 fn lower_reduce<KS, VS, R>(
337 mut self,
338 materialized: Materialized<KS, VS>,
339 store_name: String,
340 reducer: R,
341 ) -> KTable<Windowed<K>, V, TimeWindowedSerde<KS>, VS>
342 where
343 KS: Serde<K> + Clone + 'static,
344 VS: Serde<V> + Clone + 'static,
345 R: Fn(&V, &V) -> V + Clone + Send + Sync + 'static,
346 {
347 let Materialized {
348 key_serde,
349 value_serde,
350 caching,
351 ..
352 } = materialized;
353 let suppress_factory = sliding_suppress_factory::<K, V, KS, VS>(
354 key_serde.clone(),
355 value_serde.clone(),
356 self.windows,
357 );
358 let parent = self.parent;
359 let key_changing = self.key_changing_upstream;
360 let rp_lower = self.repartition_lower.take();
361 let windows = self.windows;
362 let emit = self.emit;
363 let mut g = self.builder.borrow_mut();
364 let agg_parent = KGroupedStream::<K, V>::record_repartition(
365 &mut g,
366 &store_name,
367 parent,
368 key_changing,
369 rp_lower,
370 );
371
372 let red_name = g.new_processor_name(names::REDUCE);
373 let red_id = g.graph.add(
374 red_name.clone(),
375 GraphNodeKind::Aggregate {
376 store_name: store_name.clone(),
377 changelog: true,
378 },
379 vec![agg_parent],
380 );
381 let store_for_thunk = store_name.clone();
382 let key_serde_for_lower = key_serde.clone();
383 let value_serde_for_lower = value_serde.clone();
384 g.graph.nodes[red_id].lower = Some(Box::new(move |state: &mut LowerState| {
385 let parent = NodeHandle::<K, V>::from_name(state.handle_name[&agg_parent].clone());
386 let store_for_proc = store_for_thunk.clone();
387 let reducer = reducer.clone();
388 let h = state
389 .topology
390 .add_processor::<K, V, Windowed<K>, crate::dsl::processors::change::Change<V>, _, _, _>(
391 red_name.clone(),
392 move || KStreamSlidingWindowReduceProcessor {
393 store_name: store_for_proc.clone(),
394 windows,
395 reducer: reducer.clone(),
396 stream_time: i64::MIN,
397 emit,
398 last_emitted_close: i64::MIN,
399 forwarder: crate::dsl::processors::tuple_forwarder::TupleForwarder::default(),
400 _pd: PhantomData,
401 },
402 [parent],
403 );
404 state.topology.add_window_store::<K, V, KS, VS>(
406 store_for_thunk.clone(),
407 key_serde_for_lower.clone(),
408 value_serde_for_lower.clone(),
409 (
412 windows.time_difference * 2.0,
413 windows.time_difference,
414 windows.grace,
415 ),
416 [h.name().to_string()],
417 );
418 state
420 .topology
421 .mark_store_caching(&store_for_thunk, caching && emit.is_on_update());
422 state.handle_name.insert(red_id, h.name().to_string());
423 }));
424
425 drop(g);
426 KTable::new(
427 Rc::clone(&self.builder),
428 red_id,
429 Some(store_name),
430 None,
431 TimeWindowedSerde::new(key_serde.clone(), windows.time_difference),
432 value_serde.clone(),
433 )
434 .with_window_grace(Some(windows.grace))
435 .with_suppress_factory(Some(suppress_factory))
436 }
437}
438
439pub(crate) fn sliding_suppress_factory<K, VA, KS, VS>(
440 key_serde: KS,
441 value_serde: VS,
442 windows: SlidingWindows,
443) -> SuppressStoreFactory
444where
445 K: Any + Send + Sync + Clone,
446 VA: Any + Send + Clone,
447 KS: Serde<K> + Clone + 'static,
448 VS: Serde<VA> + Clone + 'static,
449{
450 std::sync::Arc::new(
451 move |state: &mut LowerState, store_name: &str, proc_name: &str, logging: bool| {
452 state
453 .topology
454 .add_suppress_store::<Windowed<K>, VA, TimeWindowedSerde<KS>, VS>(
455 store_name.to_string(),
456 TimeWindowedSerde::new(key_serde.clone(), windows.time_difference),
457 value_serde.clone(),
458 logging,
459 [proc_name.to_string()],
460 );
461 },
462 )
463}