1use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
9
10use crate::{
11 dsl::{
12 builder::InternalStreamsBuilder,
13 config::Materialized,
14 graph::{GraphNodeKind, LowerState, NodeId},
15 kgrouped::mint_store_name,
16 ktable::KTable,
17 names,
18 processors::{
19 change::Change, table_aggregate::KTableAggregateProcessor,
20 tuple_forwarder::TupleForwarder,
21 },
22 },
23 processor::serde::{Changed, Consumed, DefaultSerde, I64Serde, Produced, Serde},
24 topology::NodeHandle,
25};
26
27pub(crate) type ChangedRepartitionLowerFn =
34 Box<dyn FnOnce(&mut LowerState, String, String, String, String) + Send>;
35
36pub(crate) fn repartition_lower_changed<KR, VR, KS, VS>(
40 key_serde: KS,
41 value_serde: VS,
42) -> ChangedRepartitionLowerFn
43where
44 KR: Any + Send + Sync + Clone,
45 VR: Any + Send + Sync + Clone,
46 KS: Serde<KR> + Clone + 'static,
47 VS: Serde<VR> + Clone + 'static,
48{
49 Box::new(
50 move |state: &mut LowerState,
51 parent_name: String,
52 sink_name: String,
53 source_name: String,
54 topic: String| {
55 let parent = NodeHandle::<KR, Change<VR>>::from_name(parent_name);
56 state
57 .topology
58 .add_sink_explicit::<KR, Change<VR>, KS, Changed<VS>, _, _>(
59 sink_name,
60 topic.clone(),
61 [parent],
62 Produced::with(key_serde.clone(), Changed::new(value_serde.clone())),
63 );
64 state.topology.add_repartition_topic(topic.clone());
65 state
66 .topology
67 .add_source_explicit::<KR, Change<VR>, KS, Changed<VS>>(
68 source_name,
69 [topic],
70 Consumed::with(key_serde, Changed::new(value_serde)),
71 );
72 },
73 )
74}
75
76pub struct KGroupedTable<KR, VR> {
78 builder: Rc<RefCell<InternalStreamsBuilder>>,
79 select_node: NodeId,
86 repartition_lower: Option<ChangedRepartitionLowerFn>,
87 _pd: PhantomData<fn() -> (KR, VR)>,
88}
89
90impl<KR, VR> KGroupedTable<KR, VR>
91where
92 KR: Any + Send + Sync + Clone + PartialEq,
93 VR: Any + Send + Sync + Clone,
94{
95 pub(crate) fn new(
96 builder: Rc<RefCell<InternalStreamsBuilder>>,
97 select_node: NodeId,
98 repartition_lower: ChangedRepartitionLowerFn,
99 ) -> Self {
100 Self {
101 builder,
102 select_node,
103 repartition_lower: Some(repartition_lower),
104 _pd: PhantomData,
105 }
106 }
107
108 pub fn count_explicit<KS, VS>(
110 self,
111 materialized: impl Into<Materialized<KS, VS>>,
112 ) -> KTable<KR, i64, KS, VS>
113 where
114 KS: Serde<KR> + Clone + 'static,
115 VS: Serde<i64> + Clone + 'static,
116 {
117 self.aggregate_inner(
118 materialized.into(),
119 names::KTABLE_AGGREGATE_STORE,
120 || 0i64,
121 |_k: &KR, _v: &VR, a: i64| a + 1,
122 |_k: &KR, _v: &VR, a: i64| a - 1,
123 )
124 }
125
126 pub fn reduce_explicit<KS, VS, Add, Sub>(
133 self,
134 adder: Add,
135 subtractor: Sub,
136 materialized: impl Into<Materialized<KS, VS>>,
137 ) -> KTable<KR, VR, KS, VS>
138 where
139 VR: Default,
140 KS: Serde<KR> + Clone + 'static,
141 VS: Serde<VR> + Clone + 'static,
142 Add: Fn(&VR, &VR) -> VR + Clone + Send + Sync + 'static,
143 Sub: Fn(&VR, &VR) -> VR + Clone + Send + Sync + 'static,
144 {
145 let materialized = materialized.into();
146 let store_name = mint_store_name(&self.builder, &materialized, names::KTABLE_REDUCE_STORE);
147 self.lower::<KS, VS, VR, _, _, _>(
148 materialized,
149 store_name,
150 VR::default,
151 move |_k: &KR, v: &VR, acc: VR| adder(&acc, v),
152 move |_k: &KR, v: &VR, acc: VR| subtractor(&acc, v),
153 )
154 }
155
156 pub fn aggregate_explicit<KS, VS, T, I, Add, Sub>(
159 self,
160 init: I,
161 adder: Add,
162 subtractor: Sub,
163 materialized: impl Into<Materialized<KS, VS>>,
164 ) -> KTable<KR, T, KS, VS>
165 where
166 T: Any + Send + Clone,
167 KS: Serde<KR> + Clone + 'static,
168 VS: Serde<T> + Clone + 'static,
169 I: Fn() -> T + Clone + Send + Sync + 'static,
170 Add: Fn(&KR, &VR, T) -> T + Clone + Send + Sync + 'static,
171 Sub: Fn(&KR, &VR, T) -> T + Clone + Send + Sync + 'static,
172 {
173 self.aggregate_inner(
174 materialized.into(),
175 names::KTABLE_AGGREGATE_STORE,
176 init,
177 adder,
178 subtractor,
179 )
180 }
181
182 pub fn count(
184 self,
185 store_name: impl Into<String>,
186 ) -> KTable<KR, i64, <KR as DefaultSerde>::Serde, I64Serde>
187 where
188 KR: DefaultSerde,
189 <KR as DefaultSerde>::Serde: Serde<KR> + Clone,
190 {
191 self.count_explicit(
192 Materialized::with(<KR as DefaultSerde>::Serde::default(), I64Serde)
193 .as_store(store_name),
194 )
195 }
196
197 pub fn aggregate<T, I, Add, Sub>(
199 self,
200 init: I,
201 adder: Add,
202 subtractor: Sub,
203 store_name: impl Into<String>,
204 ) -> KTable<KR, T, <KR as DefaultSerde>::Serde, <T as DefaultSerde>::Serde>
205 where
206 T: DefaultSerde + Any + Send + Clone,
207 KR: DefaultSerde,
208 <KR as DefaultSerde>::Serde: Serde<KR> + Clone,
209 <T as DefaultSerde>::Serde: Serde<T> + Clone,
210 I: Fn() -> T + Clone + Send + Sync + 'static,
211 Add: Fn(&KR, &VR, T) -> T + Clone + Send + Sync + 'static,
212 Sub: Fn(&KR, &VR, T) -> T + Clone + Send + Sync + 'static,
213 {
214 self.aggregate_explicit(
215 init,
216 adder,
217 subtractor,
218 Materialized::with(
219 <KR as DefaultSerde>::Serde::default(),
220 <T as DefaultSerde>::Serde::default(),
221 )
222 .as_store(store_name),
223 )
224 }
225
226 fn aggregate_inner<KS, VS, T, I, Add, Sub>(
227 self,
228 materialized: Materialized<KS, VS>,
229 store_prefix: &'static str,
230 init: I,
231 adder: Add,
232 subtractor: Sub,
233 ) -> KTable<KR, T, KS, VS>
234 where
235 T: Any + Send + Clone,
236 KS: Serde<KR> + Clone + 'static,
237 VS: Serde<T> + Clone + 'static,
238 I: Fn() -> T + Clone + Send + Sync + 'static,
239 Add: Fn(&KR, &VR, T) -> T + Clone + Send + Sync + 'static,
240 Sub: Fn(&KR, &VR, T) -> T + Clone + Send + Sync + 'static,
241 {
242 let store_name = mint_store_name(&self.builder, &materialized, store_prefix);
243 self.lower::<KS, VS, T, I, Add, Sub>(materialized, store_name, init, adder, subtractor)
244 }
245
246 fn lower<KS, VS, T, I, Add, Sub>(
249 mut self,
250 materialized: Materialized<KS, VS>,
251 store_name: String,
252 init: I,
253 adder: Add,
254 subtractor: Sub,
255 ) -> KTable<KR, T, KS, VS>
256 where
257 T: Any + Send + Clone,
258 KS: Serde<KR> + Clone + 'static,
259 VS: Serde<T> + Clone + 'static,
260 I: Fn() -> T + Clone + Send + Sync + 'static,
261 Add: Fn(&KR, &VR, T) -> T + Clone + Send + Sync + 'static,
262 Sub: Fn(&KR, &VR, T) -> T + Clone + Send + Sync + 'static,
263 {
264 let Materialized {
265 key_serde,
266 value_serde,
267 logging,
268 caching,
269 ..
270 } = materialized;
271 let suppress_factory = crate::dsl::ktable::kv_suppress_factory::<KR, T, KS, VS>(
272 key_serde.clone(),
273 value_serde.clone(),
274 );
275 let rp_lower = self
276 .repartition_lower
277 .take()
278 .expect("repartition_lower consumed");
279 let select_id = self.select_node;
282 let mut g = self.builder.borrow_mut();
283
284 let sink_name = g.new_processor_name(names::SINK);
289 let source_name = g.new_processor_name(names::SOURCE);
290 let topic_store = store_name.clone();
291 let rp_id = g.graph.add(
292 source_name.clone(),
293 GraphNodeKind::Repartition {
294 topic: format!("{topic_store}{}", names::REPARTITION_SUFFIX),
295 partitions: None,
296 },
297 vec![select_id],
298 );
299 g.graph.nodes[rp_id].lower = Some(Box::new(move |state: &mut LowerState| {
300 let parent_name = state.handle_name[&select_id].clone();
301 let topic = format!(
302 "{}-{topic_store}{}",
303 state.app_id,
304 names::REPARTITION_SUFFIX
305 );
306 rp_lower(
307 state,
308 parent_name,
309 sink_name.clone(),
310 source_name.clone(),
311 topic,
312 );
313 state.handle_name.insert(rp_id, source_name.clone());
314 }));
315
316 let agg_name = g.new_processor_name(names::KTABLE_AGGREGATE);
318 let agg_id = g.graph.add(
319 agg_name.clone(),
320 GraphNodeKind::Aggregate {
321 store_name: store_name.clone(),
322 changelog: logging,
323 },
324 vec![rp_id],
325 );
326 let store_for_thunk = store_name.clone();
327 let key_serde_lower = key_serde.clone();
328 let value_serde_lower = value_serde.clone();
329 g.graph.nodes[agg_id].lower = Some(Box::new(move |state: &mut LowerState| {
330 let parent = NodeHandle::<KR, Change<VR>>::from_name(state.handle_name[&rp_id].clone());
331 let store_for_proc = store_for_thunk.clone();
332 let h = state
333 .topology
334 .add_processor::<KR, Change<VR>, KR, Change<T>, _, _, _>(
335 agg_name.clone(),
336 move || KTableAggregateProcessor {
337 store_name: store_for_proc.clone(),
338 init: init.clone(),
339 adder: adder.clone(),
340 subtractor: subtractor.clone(),
341 forwarder: TupleForwarder::default(),
342 _pd: PhantomData,
343 },
344 [parent],
345 );
346 if logging {
347 state.topology.add_state_store::<KR, T, KS, VS>(
348 store_for_thunk.clone(),
349 key_serde_lower.clone(),
350 value_serde_lower.clone(),
351 [h.name().to_string()],
352 );
353 } else {
354 state
355 .topology
356 .add_state_store_no_changelog::<KR, T, KS, VS>(
357 store_for_thunk.clone(),
358 key_serde_lower.clone(),
359 value_serde_lower.clone(),
360 );
361 }
362 state.topology.mark_store_caching(&store_for_thunk, caching);
363 state.handle_name.insert(agg_id, h.name().to_string());
364 }));
365
366 drop(g);
367 KTable::new(
368 Rc::clone(&self.builder),
369 agg_id,
370 Some(store_name),
371 None,
372 key_serde,
373 value_serde,
374 )
375 .with_suppress_factory(Some(suppress_factory))
376 }
377}