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::CapabilitySet;
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((
123 cap.retain_stamp(0),
124 cap.retain_stamp(1),
125 std::mem::take(data),
126 ))
127 });
128
129 while let Some((ok_cap, err_cap, data)) = queue.pop_front() {
130 let mut ok_session = ok_output.session_with_builder(&ok_cap);
131 let mut err_session = err_output.session_with_builder(&err_cap);
132
133 for (input_row, t, d) in data.borrow().into_index_iter() {
137 time.copy_from(t);
138 diff.copy_from(d);
139 process_flat_map_row(
140 input_row,
141 &time,
142 &diff,
143 &exprs,
144 &func,
145 &mfp_plan,
146 &until,
147 &mut datums,
148 &mut datums_mfp,
149 &mut table_func_output,
150 &mut ok_session,
151 &mut err_session,
152 &mut budget,
153 );
154 }
155 if budget == 0 {
156 activator.activate();
157 break;
158 }
159 }
160 })
161 },
162 )
163}
164
165fn process_flat_map_row<T>(
170 input_row: &RowRef,
171 time: &T,
172 diff: &Diff,
173 exprs: &[LirScalarExpr],
174 func: &TableFunc,
175 mfp_plan: &MfpPlan<LirScalarExpr>,
176 until: &Antichain<Timestamp>,
177 datums: &mut DatumVec,
178 datums_mfp: &mut DatumVec,
179 table_func_output: &mut Vec<(Row, Diff)>,
180 ok_session: &mut Session<'_, '_, T, FlatMapOk<T>, CapabilitySet<T>>,
181 err_session: &mut Session<'_, '_, T, FlatMapErr<T>, CapabilitySet<T>>,
182 budget: &mut usize,
183) where
184 T: RenderTimestamp,
185{
186 let temp_storage = RowArena::new();
187
188 let datums_local = datums.borrow_with(input_row);
190 let args = exprs
191 .iter()
192 .map(|e| e.eval(&datums_local, &temp_storage))
193 .collect::<Result<Vec<_>, _>>();
194 let args = match args {
195 Ok(args) => args,
196 Err(e) => {
197 err_session.give((e.into(), time.clone(), *diff));
198 return;
199 }
200 };
201 let mut extensions = match func.eval(&args, &temp_storage) {
202 Ok(exts) => exts.fuse(),
203 Err(e) => {
204 err_session.give((e.into(), time.clone(), *diff));
205 return;
206 }
207 };
208
209 while let Some((extension, output_diff)) = extensions.next() {
211 table_func_output.push((extension, output_diff));
212 table_func_output.extend((&mut extensions).take(1023));
213 drain_through_mfp(
215 input_row,
216 time,
217 diff,
218 datums_mfp,
219 table_func_output,
220 mfp_plan,
221 until,
222 ok_session,
223 err_session,
224 budget,
225 );
226 table_func_output.clear();
227 }
228}
229
230fn drain_through_mfp<T>(
234 input_row: &RowRef,
235 input_time: &T,
236 input_diff: &Diff,
237 datum_vec: &mut DatumVec,
238 extensions: &[(Row, Diff)],
239 mfp_plan: &MfpPlan<LirScalarExpr>,
240 until: &Antichain<Timestamp>,
241 ok_output: &mut Session<'_, '_, T, FlatMapOk<T>, CapabilitySet<T>>,
242 err_output: &mut Session<'_, '_, T, FlatMapErr<T>, CapabilitySet<T>>,
243 budget: &mut usize,
244) where
245 T: RenderTimestamp,
246{
247 let temp_storage = RowArena::new();
248 let mut row_builder = SharedRow::get();
249
250 let mut datums_local = datum_vec.borrow_with(input_row);
252 let datums_len = datums_local.len();
253
254 let event_time = input_time.event_time().clone();
255
256 for (cols, diff) in extensions.iter() {
257 datums_local.truncate(datums_len);
259 datums_local.extend(cols.iter());
260
261 let results = mfp_plan.evaluate(
262 &mut datums_local,
263 &temp_storage,
264 event_time,
265 *diff * *input_diff,
266 |time| !until.less_equal(time),
267 &mut row_builder,
268 );
269
270 for result in results {
271 *budget = budget.saturating_sub(1);
272 match result {
273 Ok((row, event_time, diff)) => {
274 let mut time = input_time.clone();
276 *time.event_time_mut() = event_time;
277 ok_output.give((row, time, diff));
278 }
279 Err((err, event_time, diff)) => {
280 let mut time = input_time.clone();
282 *time.event_time_mut() = event_time;
283 err_output.give((err, time, diff));
284 }
285 };
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use std::cell::RefCell;
293 use std::rc::Rc;
294
295 use differential_dataflow::input::Input;
296 use mz_expr::MapFilterProject;
297 use mz_repr::{Datum, ReprScalarType};
298 use timely::dataflow::operators::Inspect;
299 use timely::dataflow::operators::capture::{Capture, Extract};
300
301 use super::*;
302 use crate::render::columnar::vec_to_columnar;
303
304 fn flat_map_args() -> (Vec<LirScalarExpr>, TableFunc, MfpPlan<LirScalarExpr>) {
306 let exprs = vec![
307 LirScalarExpr::column(0),
308 LirScalarExpr::column(1),
309 LirScalarExpr::literal_ok(Datum::Int64(1), ReprScalarType::Int64),
310 ];
311 let func = TableFunc::GenerateSeriesInt64;
312 let mfp = MapFilterProject::<LirScalarExpr>::new(3)
313 .into_plan()
314 .expect("identity mfp");
315 (exprs, func, mfp)
316 }
317
318 fn input_row(stop: i64) -> Row {
319 Row::pack_slice(&[Datum::Int64(1), Datum::Int64(stop)])
320 }
321
322 fn run_fueled(batches: u64, stop: i64, budget: usize) -> (usize, usize) {
325 let expected = usize::try_from(batches).unwrap() * usize::try_from(stop).unwrap();
326 timely::execute_directly(move |worker| {
327 let collected = Rc::new(RefCell::new(0usize));
328 let sink = Rc::clone(&collected);
329 let mut input = worker.dataflow::<Timestamp, _, _>(|scope| {
330 let (input, collection) = scope.new_collection();
331 let (exprs, func, mfp) = flat_map_args();
332 let stream = vec_to_columnar(collection).inner;
335 let scope = stream.scope();
336 let (oks, _errs) =
337 flat_map_stage(stream, scope, exprs, func, mfp, Antichain::new(), budget);
338 oks.inspect_core(move |event| {
343 if let Ok((_time, data)) = event {
344 *sink.borrow_mut() += data.borrow().into_index_iter().count();
345 }
346 });
347 input
348 });
349
350 for i in 0..batches {
352 input.advance_to(Timestamp::from(i));
353 input.update(input_row(stop), Diff::ONE);
354 input.flush();
355 }
356 input.advance_to(Timestamp::from(batches));
357 input.flush();
358
359 let mut steps = 0;
360 while *collected.borrow() < expected {
361 worker.step();
362 steps += 1;
363 assert!(steps < 10_000, "flat map did not converge");
364 }
365 (steps, *collected.borrow())
366 })
367 }
368
369 #[mz_ore::test]
370 fn flat_map_fuel_bounds_per_activation() {
371 let (fueled_steps, fueled_count) = run_fueled(4, 3, 1);
372 let (unfueled_steps, unfueled_count) = run_fueled(4, 3, usize::MAX);
373 assert_eq!(fueled_count, 12);
374 assert_eq!(unfueled_count, 12);
375 assert_eq!(
376 unfueled_steps, 1,
377 "unbounded budget drains in one activation"
378 );
379 assert!(
380 fueled_steps > unfueled_steps,
381 "fuel budget must spread work across activations: fueled={fueled_steps} unfueled={unfueled_steps}"
382 );
383 }
384
385 #[mz_ore::test]
386 fn flat_map_reads_columnar_input() {
387 let captured = timely::execute_directly(move |worker| {
390 worker.dataflow::<Timestamp, _, _>(|scope| {
391 let (mut input, collection) = scope.new_collection();
392 let (exprs, func, mfp) = flat_map_args();
393 let stream = vec_to_columnar(collection).inner;
394 let scope = stream.scope();
395 let (oks, _errs) = flat_map_stage(
396 stream,
397 scope,
398 exprs,
399 func,
400 mfp,
401 Antichain::new(),
402 usize::MAX,
403 );
404 let captured = oks.capture();
405 input.advance_to(Timestamp::from(0_u64));
408 input.update(input_row(2), Diff::ONE);
409 input.advance_to(Timestamp::from(1_u64));
410 input.update(input_row(3), Diff::ONE);
411 input.advance_to(Timestamp::from(2_u64));
412 input.update(input_row(2), -Diff::ONE);
413 input.advance_to(Timestamp::from(3_u64));
414 input.flush();
415 captured
416 })
417 });
418
419 let updates = extract_sorted_columns(captured);
420 assert!(!updates.is_empty());
421 assert!(
422 updates.iter().any(|(_, _, d)| *d < Diff::ZERO),
423 "the retraction must survive as a negative diff"
424 );
425 }
426
427 fn extract_sorted_columns(
429 captured: std::sync::mpsc::Receiver<
430 timely::dataflow::operators::capture::Event<Timestamp, Column<(Row, Timestamp, Diff)>>,
431 >,
432 ) -> Vec<(Row, Timestamp, Diff)> {
433 let mut updates: Vec<(Row, Timestamp, Diff)> = captured
434 .extract()
435 .into_iter()
436 .flat_map(|(_, col)| {
437 col.borrow()
438 .into_index_iter()
439 .map(|(v, t, d)| {
440 (
441 Columnar::into_owned(v),
442 Columnar::into_owned(t),
443 Columnar::into_owned(d),
444 )
445 })
446 .collect::<Vec<_>>()
447 })
448 .collect();
449 updates.sort();
450 updates
451 }
452
453 #[mz_ore::test]
454 fn flat_map_output_consolidates_within_batch() {
455 let captured = timely::execute_directly(move |worker| {
458 worker.dataflow::<Timestamp, _, _>(|scope| {
459 let (mut input, collection) = scope.new_collection();
460 let exprs = vec![
461 LirScalarExpr::column(0),
462 LirScalarExpr::column(1),
463 LirScalarExpr::literal_ok(Datum::Int64(1), ReprScalarType::Int64),
464 ];
465 let func = TableFunc::GenerateSeriesInt64;
466 let mfp = MapFilterProject::<LirScalarExpr>::new(3)
468 .project(vec![2])
469 .into_plan()
470 .expect("project mfp");
471 let stream = vec_to_columnar(collection).inner;
472 let scope = stream.scope();
473 let (oks, _errs) = flat_map_stage(
474 stream,
475 scope,
476 exprs,
477 func,
478 mfp,
479 Antichain::new(),
480 usize::MAX,
481 );
482 let captured = oks.capture();
483 input.advance_to(Timestamp::from(0_u64));
485 input.update(input_row(2), Diff::ONE);
486 input.update(input_row(3), Diff::ONE);
487 input.advance_to(Timestamp::from(1_u64));
488 input.flush();
489 captured
490 })
491 });
492
493 let updates = extract_sorted_columns(captured);
494 let expected = vec![
495 (
496 Row::pack_slice(&[Datum::Int64(1)]),
497 Timestamp::from(0_u64),
498 Diff::from(2),
499 ),
500 (
501 Row::pack_slice(&[Datum::Int64(2)]),
502 Timestamp::from(0_u64),
503 Diff::from(2),
504 ),
505 (
506 Row::pack_slice(&[Datum::Int64(3)]),
507 Timestamp::from(0_u64),
508 Diff::ONE,
509 ),
510 ];
511 assert_eq!(updates, expected);
512 }
513}