1use std::collections::VecDeque;
11
12use columnar::{Columnar, Index};
13use differential_dataflow::consolidation::ConsolidatingContainerBuilder;
14use mz_compute_types::dyncfgs::COMPUTE_FLAT_MAP_FUEL;
15use mz_compute_types::plan::scalar::LirScalarExpr;
16use mz_expr::TableFunc;
17use mz_expr::{Eval, MfpPlan};
18use mz_repr::{DatumVec, RowArena, SharedRow};
19use mz_repr::{Diff, Row, RowRef, Timestamp};
20use mz_timely_util::columnar::Column;
21use mz_timely_util::columnar::consolidate::ConsolidatingColumnBuilder;
22use mz_timely_util::operator::StreamExt;
23use timely::dataflow::channels::pact::Pipeline;
24use timely::dataflow::operators::Capability;
25use timely::dataflow::operators::generic::Session;
26use timely::dataflow::{Scope, Stream};
27use timely::progress::Antichain;
28
29use crate::render::RenderTimestamp;
30use crate::render::context::{CollectionBundle, Context};
31use crate::render::errors::DataflowErrorSer;
32
33impl<'scope, T: crate::render::RenderTimestamp> Context<'scope, T> {
34 pub fn render_flat_map(
36 &self,
37 input_key: Option<Vec<LirScalarExpr>>,
38 input: CollectionBundle<'scope, T>,
39 exprs: Vec<LirScalarExpr>,
40 func: TableFunc,
41 mfp_plan: MfpPlan<LirScalarExpr>,
42 ) -> CollectionBundle<'scope, T> {
43 let until = self.until.clone();
44 let scope = input.scope();
45
46 let budget = COMPUTE_FLAT_MAP_FUEL.get(&self.config_set);
51
52 let (edge, err_collection) = match input_key.as_deref() {
56 None => input
57 .collection
58 .clone()
59 .expect("The unarranged collection doesn't exist."),
60 Some(key) => input.as_specific_collection(Some(key)),
61 };
62
63 let (oks, errs) = flat_map_stage(edge.inner, scope, exprs, func, mfp_plan, until, budget);
64
65 use differential_dataflow::AsCollection;
66 let ok_collection = oks.as_collection();
67 let new_err_collection = errs.as_collection();
68 let err_collection = err_collection.concat(new_err_collection);
69 CollectionBundle::from_edge(ok_collection, err_collection)
70 }
71}
72
73type FlatMapOk<T> = ConsolidatingColumnBuilder<Row, T, Diff>;
78type FlatMapErr<T> = ConsolidatingContainerBuilder<Vec<(DataflowErrorSer, T, Diff)>>;
80
81fn flat_map_stage<'scope, T>(
87 stream: Stream<'scope, T, Column<(Row, T, Diff)>>,
88 scope: Scope<'scope, T>,
89 exprs: Vec<LirScalarExpr>,
90 func: TableFunc,
91 mfp_plan: MfpPlan<LirScalarExpr>,
92 until: Antichain<Timestamp>,
93 budget: usize,
94) -> (
95 Stream<'scope, T, Column<(Row, T, Diff)>>,
96 Stream<'scope, T, Vec<(DataflowErrorSer, T, Diff)>>,
97)
98where
99 T: RenderTimestamp,
100{
101 stream.unary_fallible::<FlatMapOk<T>, FlatMapErr<T>, _, _>(
102 Pipeline,
103 "FlatMapStage",
104 move |_, info| {
105 let activator = scope.activator_for(info.address);
106 let mut queue = VecDeque::new();
107 Box::new(move |input, ok_output, err_output| {
108 let mut datums = DatumVec::new();
109 let mut datums_mfp = DatumVec::new();
110
111 let mut table_func_output = Vec::new();
113
114 let mut time = T::minimum();
116 let mut diff = Diff::ZERO;
117
118 let mut budget = budget;
119
120 input.for_each(|cap, data| {
121 queue.push_back((cap.retain(0), cap.retain(1), std::mem::take(data)))
122 });
123
124 while let Some((ok_cap, err_cap, data)) = queue.pop_front() {
125 let mut ok_session = ok_output.session_with_builder(&ok_cap);
126 let mut err_session = err_output.session_with_builder(&err_cap);
127
128 for (input_row, t, d) in data.borrow().into_index_iter() {
132 time.copy_from(t);
133 diff.copy_from(d);
134 process_flat_map_row(
135 input_row,
136 &time,
137 &diff,
138 &exprs,
139 &func,
140 &mfp_plan,
141 &until,
142 &mut datums,
143 &mut datums_mfp,
144 &mut table_func_output,
145 &mut ok_session,
146 &mut err_session,
147 &mut budget,
148 );
149 }
150 if budget == 0 {
151 activator.activate();
152 break;
153 }
154 }
155 })
156 },
157 )
158}
159
160fn process_flat_map_row<T>(
165 input_row: &RowRef,
166 time: &T,
167 diff: &Diff,
168 exprs: &[LirScalarExpr],
169 func: &TableFunc,
170 mfp_plan: &MfpPlan<LirScalarExpr>,
171 until: &Antichain<Timestamp>,
172 datums: &mut DatumVec,
173 datums_mfp: &mut DatumVec,
174 table_func_output: &mut Vec<(Row, Diff)>,
175 ok_session: &mut Session<'_, '_, T, FlatMapOk<T>, Capability<T>>,
176 err_session: &mut Session<'_, '_, T, FlatMapErr<T>, Capability<T>>,
177 budget: &mut usize,
178) where
179 T: RenderTimestamp,
180{
181 let temp_storage = RowArena::new();
182
183 let datums_local = datums.borrow_with(input_row);
185 let args = exprs
186 .iter()
187 .map(|e| e.eval(&datums_local, &temp_storage))
188 .collect::<Result<Vec<_>, _>>();
189 let args = match args {
190 Ok(args) => args,
191 Err(e) => {
192 err_session.give((e.into(), time.clone(), *diff));
193 return;
194 }
195 };
196 let mut extensions = match func.eval(&args, &temp_storage) {
197 Ok(exts) => exts.fuse(),
198 Err(e) => {
199 err_session.give((e.into(), time.clone(), *diff));
200 return;
201 }
202 };
203
204 while let Some((extension, output_diff)) = extensions.next() {
206 table_func_output.push((extension, output_diff));
207 table_func_output.extend((&mut extensions).take(1023));
208 drain_through_mfp(
210 input_row,
211 time,
212 diff,
213 datums_mfp,
214 table_func_output,
215 mfp_plan,
216 until,
217 ok_session,
218 err_session,
219 budget,
220 );
221 table_func_output.clear();
222 }
223}
224
225fn drain_through_mfp<T>(
229 input_row: &RowRef,
230 input_time: &T,
231 input_diff: &Diff,
232 datum_vec: &mut DatumVec,
233 extensions: &[(Row, Diff)],
234 mfp_plan: &MfpPlan<LirScalarExpr>,
235 until: &Antichain<Timestamp>,
236 ok_output: &mut Session<'_, '_, T, FlatMapOk<T>, Capability<T>>,
237 err_output: &mut Session<'_, '_, T, FlatMapErr<T>, Capability<T>>,
238 budget: &mut usize,
239) where
240 T: RenderTimestamp,
241{
242 let temp_storage = RowArena::new();
243 let mut row_builder = SharedRow::get();
244
245 let mut datums_local = datum_vec.borrow_with(input_row);
247 let datums_len = datums_local.len();
248
249 let event_time = input_time.event_time().clone();
250
251 for (cols, diff) in extensions.iter() {
252 datums_local.truncate(datums_len);
254 datums_local.extend(cols.iter());
255
256 let results = mfp_plan.evaluate(
257 &mut datums_local,
258 &temp_storage,
259 event_time,
260 *diff * *input_diff,
261 |time| !until.less_equal(time),
262 &mut row_builder,
263 );
264
265 for result in results {
266 *budget = budget.saturating_sub(1);
267 match result {
268 Ok((row, event_time, diff)) => {
269 let mut time = input_time.clone();
271 *time.event_time_mut() = event_time;
272 ok_output.give((row, time, diff));
273 }
274 Err((err, event_time, diff)) => {
275 let mut time = input_time.clone();
277 *time.event_time_mut() = event_time;
278 err_output.give((err, time, diff));
279 }
280 };
281 }
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use std::cell::RefCell;
288 use std::rc::Rc;
289
290 use differential_dataflow::input::Input;
291 use mz_expr::MapFilterProject;
292 use mz_repr::{Datum, ReprScalarType};
293 use timely::dataflow::operators::InspectCore;
294 use timely::dataflow::operators::capture::{Capture, Extract};
295
296 use super::*;
297 use crate::render::columnar::vec_to_columnar;
298
299 fn flat_map_args() -> (Vec<LirScalarExpr>, TableFunc, MfpPlan<LirScalarExpr>) {
301 let exprs = vec![
302 LirScalarExpr::column(0),
303 LirScalarExpr::column(1),
304 LirScalarExpr::literal_ok(Datum::Int64(1), ReprScalarType::Int64),
305 ];
306 let func = TableFunc::GenerateSeriesInt64;
307 let mfp = MapFilterProject::<LirScalarExpr>::new(3)
308 .into_plan()
309 .expect("identity mfp");
310 (exprs, func, mfp)
311 }
312
313 fn input_row(stop: i64) -> Row {
314 Row::pack_slice(&[Datum::Int64(1), Datum::Int64(stop)])
315 }
316
317 fn run_fueled(batches: u64, stop: i64, budget: usize) -> (usize, usize) {
320 let expected = usize::try_from(batches).unwrap() * usize::try_from(stop).unwrap();
321 timely::execute_directly(move |worker| {
322 let collected = Rc::new(RefCell::new(0usize));
323 let sink = Rc::clone(&collected);
324 let mut input = worker.dataflow::<Timestamp, _, _>(|scope| {
325 let (input, collection) = scope.new_collection();
326 let (exprs, func, mfp) = flat_map_args();
327 let stream = vec_to_columnar(collection).inner;
330 let scope = stream.scope();
331 let (oks, _errs) =
332 flat_map_stage(stream, scope, exprs, func, mfp, Antichain::new(), budget);
333 oks.inspect_container(move |event| {
337 if let Ok((_time, data)) = event {
338 *sink.borrow_mut() += data.borrow().into_index_iter().count();
339 }
340 });
341 input
342 });
343
344 for i in 0..batches {
346 input.advance_to(Timestamp::from(i));
347 input.update(input_row(stop), Diff::ONE);
348 input.flush();
349 }
350 input.advance_to(Timestamp::from(batches));
351 input.flush();
352
353 let mut steps = 0;
354 while *collected.borrow() < expected {
355 worker.step();
356 steps += 1;
357 assert!(steps < 10_000, "flat map did not converge");
358 }
359 (steps, *collected.borrow())
360 })
361 }
362
363 #[mz_ore::test]
364 fn flat_map_fuel_bounds_per_activation() {
365 let (fueled_steps, fueled_count) = run_fueled(4, 3, 1);
366 let (unfueled_steps, unfueled_count) = run_fueled(4, 3, usize::MAX);
367 assert_eq!(fueled_count, 12);
368 assert_eq!(unfueled_count, 12);
369 assert_eq!(
370 unfueled_steps, 1,
371 "unbounded budget drains in one activation"
372 );
373 assert!(
374 fueled_steps > unfueled_steps,
375 "fuel budget must spread work across activations: fueled={fueled_steps} unfueled={unfueled_steps}"
376 );
377 }
378
379 #[mz_ore::test]
380 fn flat_map_reads_columnar_input() {
381 let captured = timely::execute_directly(move |worker| {
384 worker.dataflow::<Timestamp, _, _>(|scope| {
385 let (mut input, collection) = scope.new_collection();
386 let (exprs, func, mfp) = flat_map_args();
387 let stream = vec_to_columnar(collection).inner;
388 let scope = stream.scope();
389 let (oks, _errs) = flat_map_stage(
390 stream,
391 scope,
392 exprs,
393 func,
394 mfp,
395 Antichain::new(),
396 usize::MAX,
397 );
398 let captured = oks.capture();
399 input.advance_to(Timestamp::from(0_u64));
402 input.update(input_row(2), Diff::ONE);
403 input.advance_to(Timestamp::from(1_u64));
404 input.update(input_row(3), Diff::ONE);
405 input.advance_to(Timestamp::from(2_u64));
406 input.update(input_row(2), -Diff::ONE);
407 input.advance_to(Timestamp::from(3_u64));
408 input.flush();
409 captured
410 })
411 });
412
413 let updates = extract_sorted_columns(captured);
414 assert!(!updates.is_empty());
415 assert!(
416 updates.iter().any(|(_, _, d)| *d < Diff::ZERO),
417 "the retraction must survive as a negative diff"
418 );
419 }
420
421 fn extract_sorted_columns(
423 captured: std::sync::mpsc::Receiver<
424 timely::dataflow::operators::capture::Event<Timestamp, Column<(Row, Timestamp, Diff)>>,
425 >,
426 ) -> Vec<(Row, Timestamp, Diff)> {
427 let mut updates: Vec<(Row, Timestamp, Diff)> = captured
428 .extract()
429 .into_iter()
430 .flat_map(|(_, col)| {
431 col.borrow()
432 .into_index_iter()
433 .map(|(v, t, d)| {
434 (
435 Columnar::into_owned(v),
436 Columnar::into_owned(t),
437 Columnar::into_owned(d),
438 )
439 })
440 .collect::<Vec<_>>()
441 })
442 .collect();
443 updates.sort();
444 updates
445 }
446
447 #[mz_ore::test]
448 fn flat_map_output_consolidates_within_batch() {
449 let captured = timely::execute_directly(move |worker| {
452 worker.dataflow::<Timestamp, _, _>(|scope| {
453 let (mut input, collection) = scope.new_collection();
454 let exprs = vec![
455 LirScalarExpr::column(0),
456 LirScalarExpr::column(1),
457 LirScalarExpr::literal_ok(Datum::Int64(1), ReprScalarType::Int64),
458 ];
459 let func = TableFunc::GenerateSeriesInt64;
460 let mfp = MapFilterProject::<LirScalarExpr>::new(3)
462 .project(vec![2])
463 .into_plan()
464 .expect("project mfp");
465 let stream = vec_to_columnar(collection).inner;
466 let scope = stream.scope();
467 let (oks, _errs) = flat_map_stage(
468 stream,
469 scope,
470 exprs,
471 func,
472 mfp,
473 Antichain::new(),
474 usize::MAX,
475 );
476 let captured = oks.capture();
477 input.advance_to(Timestamp::from(0_u64));
479 input.update(input_row(2), Diff::ONE);
480 input.update(input_row(3), Diff::ONE);
481 input.advance_to(Timestamp::from(1_u64));
482 input.flush();
483 captured
484 })
485 });
486
487 let updates = extract_sorted_columns(captured);
488 let expected = vec![
489 (
490 Row::pack_slice(&[Datum::Int64(1)]),
491 Timestamp::from(0_u64),
492 Diff::from(2),
493 ),
494 (
495 Row::pack_slice(&[Datum::Int64(2)]),
496 Timestamp::from(0_u64),
497 Diff::from(2),
498 ),
499 (
500 Row::pack_slice(&[Datum::Int64(3)]),
501 Timestamp::from(0_u64),
502 Diff::ONE,
503 ),
504 ];
505 assert_eq!(updates, expected);
506 }
507}