1use differential_dataflow::consolidation::ConsolidatingContainerBuilder;
13use std::borrow::Cow;
14use std::collections::VecDeque;
15use std::convert::Infallible;
16use std::fmt::Debug;
17use std::future::Future;
18use std::hash::Hash;
19use std::sync::Arc;
20
21use differential_dataflow::AsCollection;
22use differential_dataflow::lattice::Lattice;
23use futures::{StreamExt, future::Either};
24use mz_expr::{ColumnSpecs, EvalError, Interpreter, MfpPlan, ResultSpec, UnmaterializableFunc};
25use mz_ore::cast::CastFrom;
26use mz_ore::collections::CollectionExt;
27use mz_ore::str::redact;
28use mz_persist_client::cache::PersistClientCache;
29use mz_persist_client::cfg::{PersistConfig, RetryParameters};
30use mz_persist_client::fetch::{ExchangeableBatchPart, ShardSourcePart};
31use mz_persist_client::fetch::{FetchedBlob, FetchedPart};
32use mz_persist_client::operators::shard_source::{
33 ErrorHandler, FilterResult, SnapshotMode, shard_source,
34};
35use mz_persist_client::stats::STATS_AUDIT_PANIC;
36use mz_persist_types::Codec64;
37use mz_persist_types::codec_impls::UnitSchema;
38use mz_persist_types::columnar::{ColumnEncoder, Schema};
39use mz_repr::{
40 Datum, DatumVec, Diff, GlobalId, RelationDesc, ReprRelationType, Row, RowArena, Timestamp,
41};
42use mz_storage_types::StorageDiff;
43use mz_storage_types::controller::{CollectionMetadata, TxnsCodecRow};
44use mz_storage_types::errors::DataflowError;
45use mz_storage_types::sources::SourceData;
46use mz_storage_types::stats::RelationPartStats;
47use mz_timely_util::builder_async::{
48 Event, OperatorBuilder as AsyncOperatorBuilder, PressOnDropButton,
49};
50use mz_timely_util::probe::ProbeNotify;
51use mz_txn_wal::operator::{TxnsContext, TxnsProgress};
52use serde::{Deserialize, Serialize};
53use timely::container::{CapacityContainerBuilder, PushInto};
54use timely::dataflow::channels::pact::Pipeline;
55use timely::dataflow::operators::generic::OutputBuilder;
56use timely::dataflow::operators::generic::builder_rc::OperatorBuilder;
57use timely::dataflow::operators::{Capability, Leave};
58use timely::dataflow::operators::{CapabilitySet, ConnectLoop, Feedback};
59use timely::dataflow::{Scope, Stream, StreamVec};
60use timely::order::TotalOrder;
61use timely::progress::Antichain;
62use timely::progress::Timestamp as TimelyTimestamp;
63use timely::progress::timestamp::PathSummary;
64use timely::scheduling::Activator;
65use timely::{ContainerBuilder, PartialOrder};
66use tokio::sync::mpsc::UnboundedSender;
67use tracing::{error, trace};
68
69use crate::metrics::BackpressureOperatorMetrics;
70
71#[derive(
79 Copy,
80 Clone,
81 PartialEq,
82 Default,
83 Eq,
84 PartialOrd,
85 Ord,
86 Debug,
87 Serialize,
88 Deserialize,
89 Hash,
90 columnar::Columnar
91)]
92#[columnar(derive(PartialEq, Eq, PartialOrd, Ord))]
95pub struct Subtime(u64);
96
97impl PartialOrder for Subtime {
98 fn less_equal(&self, other: &Self) -> bool {
99 self.0.less_equal(&other.0)
100 }
101}
102
103impl TotalOrder for Subtime {}
104
105impl PathSummary<Subtime> for Subtime {
106 fn results_in(&self, src: &Subtime) -> Option<Subtime> {
107 self.0.results_in(&src.0).map(Subtime)
108 }
109
110 fn followed_by(&self, other: &Self) -> Option<Self> {
111 self.0.followed_by(&other.0).map(Subtime)
112 }
113}
114
115impl TimelyTimestamp for Subtime {
116 type Summary = Subtime;
117
118 fn minimum() -> Self {
119 Subtime(0)
120 }
121}
122
123impl columnation::Columnation for Subtime {
124 type InnerRegion = columnation::CopyRegion<Subtime>;
125}
126
127impl differential_dataflow::lattice::Lattice for Subtime {
128 fn join(&self, other: &Self) -> Self {
129 Subtime(std::cmp::max(self.0, other.0))
130 }
131 fn meet(&self, other: &Self) -> Self {
132 Subtime(std::cmp::min(self.0, other.0))
133 }
134}
135
136impl differential_dataflow::lattice::Maximum for Subtime {
137 fn maximum() -> Self {
138 Subtime(u64::MAX)
139 }
140}
141
142impl Subtime {
143 pub const fn least_summary() -> Self {
145 Subtime(1)
146 }
147}
148
149pub fn persist_source<'scope, E, CB>(
174 scope: Scope<'scope, mz_repr::Timestamp>,
175 source_id: GlobalId,
176 persist_clients: Arc<PersistClientCache>,
177 txns_ctx: &TxnsContext,
178 metadata: CollectionMetadata,
179 read_schema: Option<RelationDesc>,
180 as_of: Option<Antichain<Timestamp>>,
181 snapshot_mode: SnapshotMode,
182 until: Antichain<Timestamp>,
183 map_filter_project: Option<&mut MfpPlan>,
184 max_inflight_bytes: Option<usize>,
185 start_signal: impl Future<Output = ()> + Send + 'static,
186 error_handler: ErrorHandler,
187) -> (
188 Stream<'scope, mz_repr::Timestamp, CB::Container>,
189 StreamVec<'scope, mz_repr::Timestamp, (E, Timestamp, Diff)>,
190 Vec<PressOnDropButton>,
191)
192where
193 E: timely::ExchangeData + Ord + Clone + Debug + From<DataflowError> + From<EvalError>,
194 CB: ContainerBuilder + PushInto<(Row, mz_repr::Timestamp, Diff)>,
195 CB::Container: Clone,
196{
197 let mut tokens = vec![];
198 let name = source_id.to_string();
199
200 let outer = scope.clone();
201 let (ok_stream, err_stream) =
202 scope.scoped(&format!("granular_backpressure({})", source_id), |scope| {
203 let (flow_control, flow_control_probe) = match max_inflight_bytes {
204 Some(max_inflight_bytes) => {
205 let series = &persist_clients.metrics().backpressure;
206 let backpressure_metrics = BackpressureOperatorMetrics::new(
207 series.emitted_bytes.clone(),
208 series.last_backpressured_bytes.clone(),
209 series.retired_bytes.clone(),
210 );
211
212 let probe = mz_timely_util::probe::Handle::default();
213 let progress_stream = mz_timely_util::probe::source(
214 scope.clone(),
215 format!("decode_backpressure_probe({source_id})"),
216 probe.clone(),
217 );
218 let flow_control = FlowControl {
219 progress_stream,
220 max_inflight_bytes,
221 summary: (Default::default(), Subtime::least_summary()),
222 metrics: Some(backpressure_metrics),
223 };
224 (Some(flow_control), Some(probe))
225 }
226 None => (None, None),
227 };
228
229 let cfg = Arc::clone(&persist_clients.cfg().configs);
235 let subscribe_sleep = match metadata.txns_shard {
236 Some(_) => Some(move || mz_txn_wal::operator::txns_data_shard_retry_params(&cfg)),
237 None => None,
238 };
239
240 let filter_plan = map_filter_project.as_ref().map(|p| (*p).clone());
241 let (cfg, fetched, source_tokens) = fetch_parts(
242 outer,
243 scope,
244 source_id,
245 Arc::clone(&persist_clients),
246 metadata.clone(),
247 read_schema,
248 as_of.clone(),
249 snapshot_mode,
250 until.clone(),
251 filter_plan,
252 flow_control,
253 subscribe_sleep,
254 start_signal,
255 error_handler,
256 );
257 tokens.extend(source_tokens);
258
259 let (ok_stream, err_stream) = decode_and_mfp::<E, mz_repr::Timestamp, CB>(
260 cfg,
261 fetched,
262 &name,
263 until.clone(),
264 map_filter_project,
265 |time| time.0,
266 );
267
268 let (ok_stream, err_stream) = match flow_control_probe {
271 Some(probe) => (
272 ok_stream.probe_notify_with(vec![probe.clone()]),
273 err_stream.probe_notify_with(vec![probe]),
274 ),
275 None => (ok_stream, err_stream),
276 };
277
278 (ok_stream.leave(outer), err_stream.leave(outer))
279 });
280
281 let (ok_stream, err_stream) = match metadata.txns_shard {
286 Some(txns_shard) => {
287 let (progress, remap_token) = TxnsProgress::new::<SourceData, (), i64, TxnsCodecRow, _>(
288 outer,
289 &name,
290 txns_ctx,
291 move || {
292 let (c, l) = (
293 Arc::clone(&persist_clients),
294 metadata.persist_location.clone(),
295 );
296 async move { c.open(l).await.expect("location is valid") }
297 },
298 txns_shard,
299 metadata.data_shard,
300 as_of
301 .expect("as_of is provided for table sources")
302 .into_option()
303 .expect("shard is not closed"),
304 Arc::new(metadata.relation_desc),
305 Arc::new(UnitSchema),
306 );
307 let (ok_stream, ok_token) = progress.translate(ok_stream, until.clone());
308 let (err_stream, err_token) = progress.translate(err_stream, until);
309 tokens.extend([remap_token, ok_token, err_token]);
310 (ok_stream, err_stream)
311 }
312 None => (ok_stream, err_stream),
313 };
314
315 (ok_stream, err_stream, tokens)
316}
317
318type RefinedScope<'scope, T> = Scope<'scope, (T, Subtime)>;
319
320pub fn persist_source_core<'g, 'outer, E>(
327 outer: Scope<'outer, mz_repr::Timestamp>,
328 scope: RefinedScope<'g, mz_repr::Timestamp>,
329 source_id: GlobalId,
330 persist_clients: Arc<PersistClientCache>,
331 metadata: CollectionMetadata,
332 read_schema: Option<RelationDesc>,
333 as_of: Option<Antichain<Timestamp>>,
334 snapshot_mode: SnapshotMode,
335 until: Antichain<Timestamp>,
336 map_filter_project: Option<&mut MfpPlan>,
337 flow_control: Option<FlowControl<'g, RefinedTime>>,
338 listen_sleep: Option<impl Fn() -> RetryParameters + Send + 'static>,
340 start_signal: impl Future<Output = ()> + Send + 'static,
341 error_handler: ErrorHandler,
342) -> (
343 StreamVec<'g, RefinedTime, (Result<Row, E>, RefinedTime, Diff)>,
344 Vec<PressOnDropButton>,
345)
346where
347 E: timely::ExchangeData + Ord + Clone + Debug + From<DataflowError> + From<EvalError>,
348{
349 let name = source_id.to_string();
350 let filter_plan = map_filter_project.as_ref().map(|p| (*p).clone());
351 let (cfg, fetched, token) = fetch_parts(
352 outer,
353 scope,
354 source_id,
355 persist_clients,
356 metadata,
357 read_schema,
358 as_of,
359 snapshot_mode,
360 until.clone(),
361 filter_plan,
362 flow_control,
363 listen_sleep,
364 start_signal,
365 error_handler,
366 );
367 let (oks, errs) = decode_and_mfp::<E, RefinedTime, RowVecBuilder<RefinedTime>>(
368 cfg,
369 fetched,
370 &name,
371 until,
372 map_filter_project,
373 |time| time,
374 );
375 let rows = oks
378 .as_collection()
379 .map(Ok)
380 .concat(errs.as_collection().map(Err))
381 .inner;
382 (rows, token)
383}
384
385#[allow(clippy::needless_borrow)]
388fn fetch_parts<'g, 'outer>(
389 outer: Scope<'outer, mz_repr::Timestamp>,
390 scope: RefinedScope<'g, mz_repr::Timestamp>,
391 source_id: GlobalId,
392 persist_clients: Arc<PersistClientCache>,
393 metadata: CollectionMetadata,
394 read_schema: Option<RelationDesc>,
395 as_of: Option<Antichain<Timestamp>>,
396 snapshot_mode: SnapshotMode,
397 until: Antichain<Timestamp>,
398 filter_plan: Option<MfpPlan>,
400 flow_control: Option<FlowControl<'g, RefinedTime>>,
401 listen_sleep: Option<impl Fn() -> RetryParameters + Send + 'static>,
403 start_signal: impl Future<Output = ()> + Send + 'static,
404 error_handler: ErrorHandler,
405) -> (
406 PersistConfig,
407 StreamVec<'g, RefinedTime, FetchedBlob<SourceData, (), Timestamp, StorageDiff>>,
408 Vec<PressOnDropButton>,
409) {
410 let cfg = persist_clients.cfg().clone();
411 let name = source_id.to_string();
412
413 let read_desc = match read_schema {
415 Some(desc) => desc,
416 None => metadata.relation_desc,
417 };
418
419 let desc_transformer = match flow_control {
420 Some(flow_control) => Some(move |scope, descs, chosen_worker| {
421 let (stream, token) = backpressure(
422 scope,
423 &format!("backpressure({source_id})"),
424 descs,
425 flow_control,
426 chosen_worker,
427 None,
428 );
429 (stream, vec![token])
430 }),
431 None => None,
432 };
433
434 let metrics = Arc::clone(persist_clients.metrics());
435 let filter_name = name.clone();
436 let upper = until.as_option().cloned().unwrap_or(Timestamp::MAX);
440 let (fetched, token) = shard_source(
441 outer,
442 scope,
443 &name,
444 move || {
445 let (c, l) = (
446 Arc::clone(&persist_clients),
447 metadata.persist_location.clone(),
448 );
449 async move { c.open(l).await.unwrap() }
450 },
451 metadata.data_shard,
452 as_of,
453 snapshot_mode,
454 until.clone(),
455 desc_transformer,
456 Arc::new(read_desc.clone()),
457 Arc::new(UnitSchema),
458 move |stats, frontier| {
459 let Some(lower) = frontier.as_option().copied() else {
460 return FilterResult::Discard;
463 };
464
465 if lower > upper {
466 return FilterResult::Discard;
469 }
470
471 let time_range =
472 ResultSpec::value_between(Datum::MzTimestamp(lower), Datum::MzTimestamp(upper));
473 if let Some(plan) = &filter_plan {
474 let metrics = &metrics.pushdown.part_stats;
475 let stats = RelationPartStats::new(&filter_name, metrics, &read_desc, stats);
476 filter_result(&read_desc, time_range, stats, plan)
477 } else {
478 FilterResult::Keep
479 }
480 },
481 listen_sleep,
482 start_signal,
483 error_handler,
484 );
485 (cfg, fetched, token)
486}
487
488fn filter_result(
489 relation_desc: &RelationDesc,
490 time_range: ResultSpec,
491 stats: RelationPartStats,
492 plan: &MfpPlan,
493) -> FilterResult {
494 let arena = RowArena::new();
495 let relation = ReprRelationType::from(relation_desc.typ());
496 let mut ranges = ColumnSpecs::new(&relation, &arena);
497 ranges.push_unmaterializable(UnmaterializableFunc::MzNow, time_range);
498
499 let may_error = stats.err_count().map_or(true, |count| count > 0);
500
501 for (pos, (idx, _, _)) in relation_desc.iter_all().enumerate() {
504 let result_spec = stats.col_stats(idx, &arena);
505 ranges.push_column(pos, result_spec);
506 }
507 let result = ranges.mfp_plan_filter(plan).range;
508 let may_error = may_error || result.may_fail();
509 let may_keep = result.may_contain(Datum::True);
510 let may_skip = result.may_contain(Datum::False) || result.may_contain(Datum::Null);
511 if relation_desc.len() == 0 && !may_error && !may_skip {
512 let Ok(mut key) = <RelationDesc as Schema<SourceData>>::encoder(relation_desc) else {
513 return FilterResult::Keep;
514 };
515 key.append(&SourceData(Ok(Row::default())));
516 let key = key.finish();
517 let Ok(mut val) = <UnitSchema as Schema<()>>::encoder(&UnitSchema) else {
518 return FilterResult::Keep;
519 };
520 val.append(&());
521 let val = val.finish();
522
523 FilterResult::ReplaceWith {
524 key: Arc::new(key),
525 val: Arc::new(val),
526 }
527 } else if may_error || may_keep {
528 FilterResult::Keep
529 } else {
530 FilterResult::Discard
531 }
532}
533
534type RefinedTime = (mz_repr::Timestamp, Subtime);
537
538pub type RowVecBuilder<T> = ConsolidatingContainerBuilder<Vec<(Row, T, Diff)>>;
540
541type ErrBuilder<E, RT> = ConsolidatingContainerBuilder<Vec<(E, RT, Diff)>>;
543
544fn decode_and_mfp<'scope, E, RT, CB>(
553 cfg: PersistConfig,
554 fetched: StreamVec<'scope, RefinedTime, FetchedBlob<SourceData, (), Timestamp, StorageDiff>>,
555 name: &str,
556 until: Antichain<Timestamp>,
557 mut map_filter_project: Option<&mut MfpPlan>,
558 record_time: fn(RefinedTime) -> RT,
559) -> (
560 Stream<'scope, RefinedTime, CB::Container>,
561 StreamVec<'scope, RefinedTime, (E, RT, Diff)>,
562)
563where
564 E: timely::ExchangeData + Ord + Clone + Debug + From<DataflowError> + From<EvalError>,
565 RT: Ord + Clone + Debug + 'static,
566 CB: ContainerBuilder + PushInto<(Row, RT, Diff)>,
567{
568 let scope = fetched.scope();
569 let mut builder = OperatorBuilder::new(
570 format!("persist_source::decode_and_mfp({})", name),
571 scope.clone(),
572 );
573 let operator_info = builder.operator_info();
574
575 let mut fetched_input = builder.new_input(fetched, Pipeline);
576 let (ok_output, ok_stream) = builder.new_output::<CB::Container>();
577 let mut ok_output: OutputBuilder<_, CB> = OutputBuilder::from(ok_output);
578 let (err_output, err_stream) = builder.new_output();
579 let mut err_output: OutputBuilder<_, ErrBuilder<E, RT>> = OutputBuilder::from(err_output);
580
581 let name = name.to_owned();
582 let map_filter_project = map_filter_project.as_mut().map(|mfp| mfp.take());
584
585 builder.build(move |_caps| {
586 let activator = Activator::new(operator_info.address, scope.activations());
588 let panic_on_audit_failure = STATS_AUDIT_PANIC.handle(&cfg);
589 let mut pending_work = VecDeque::new();
590 let mut datum_vec = DatumVec::new();
591 let mut row_builder = Row::default();
592
593 move |_frontier| {
594 fetched_input.for_each(|time, data| {
595 let capabilities = [time.retain(0), time.retain(1)];
596 let panic_on_audit_failure = panic_on_audit_failure.get();
597 for blob in data.drain(..) {
598 pending_work.push_back(PendingWork {
599 panic_on_audit_failure,
600 capabilities: capabilities.clone(),
601 part: PendingPart::Unparsed(blob),
602 });
603 }
604 });
605
606 let yield_fuel = cfg.storage_source_decode_fuel();
608 let mut work = 0;
609 let mut ok_output = ok_output.activate();
610 let mut err_output = err_output.activate();
611 while let Some(front) = pending_work.front_mut() {
612 if work >= yield_fuel {
613 break;
614 }
615 let cap_time = *front.capabilities[0].time();
616 let mut ok_session = ok_output.session_with_builder(&front.capabilities[0]);
619 let mut err_session = err_output.session_with_builder(&front.capabilities[1]);
620 let done = decode_part(
621 &mut front.part,
622 front.panic_on_audit_failure,
623 cap_time,
624 &name,
625 &until,
626 map_filter_project.as_ref(),
627 &mut datum_vec,
628 &mut row_builder,
629 &mut work,
630 yield_fuel,
631 |record, time, diff| match record {
632 Ok(row) => ok_session.give((row.into_owned(), record_time(time), diff)),
633 Err(err) => err_session.give((err, record_time(time), diff)),
634 },
635 );
636 drop(ok_session);
637 drop(err_session);
638 if done {
639 pending_work.pop_front();
640 }
641 }
642 if !pending_work.is_empty() {
643 activator.activate();
644 }
645 }
646 });
647
648 (ok_stream, err_stream)
649}
650
651struct PendingWork {
653 panic_on_audit_failure: bool,
655 capabilities: [Capability<RefinedTime>; 2],
657 part: PendingPart,
659}
660
661enum PendingPart {
662 Unparsed(FetchedBlob<SourceData, (), Timestamp, StorageDiff>),
663 Parsed {
664 part: ShardSourcePart<SourceData, (), Timestamp, StorageDiff>,
665 },
666}
667
668impl PendingPart {
669 fn part_mut(&mut self) -> &mut FetchedPart<SourceData, (), Timestamp, StorageDiff> {
676 match self {
677 PendingPart::Unparsed(x) => {
678 *self = PendingPart::Parsed { part: x.parse() };
679 self.part_mut()
681 }
682 PendingPart::Parsed { part } => &mut part.part,
683 }
684 }
685}
686
687fn decode_part<E, F>(
693 part: &mut PendingPart,
694 panic_on_audit_failure: bool,
695 cap_time: RefinedTime,
696 name: &str,
697 until: &Antichain<Timestamp>,
698 map_filter_project: Option<&MfpPlan>,
699 datum_vec: &mut DatumVec,
700 row_builder: &mut Row,
701 work: &mut usize,
702 yield_fuel: usize,
703 mut give: F,
704) -> bool
705where
706 E: timely::ExchangeData + Ord + Clone + Debug + From<DataflowError> + From<EvalError>,
707 F: FnMut(Result<Cow<'_, Row>, E>, RefinedTime, Diff),
708{
709 let fetched_part = part.part_mut();
710 let is_filter_pushdown_audit = fetched_part.is_filter_pushdown_audit();
711 let mut row_buf = None;
712 while let Some(((key, val), time, diff)) =
713 fetched_part.next_with_storage(&mut row_buf, &mut None)
714 {
715 if until.less_equal(&time) {
716 continue;
717 }
718 match (key, val) {
719 (SourceData(Ok(row)), ()) => {
720 if let Some(mfp) = map_filter_project {
721 *work += 1;
728 let arena = mz_repr::RowArena::new();
729 let mut datums_local = datum_vec.borrow_with(&row);
730 for result in mfp.evaluate(
731 &mut datums_local,
732 &arena,
733 time,
734 diff.into(),
735 |time| !until.less_equal(time),
736 row_builder,
737 ) {
738 if let Some(stats) = &is_filter_pushdown_audit {
742 sentry::with_scope(
746 |scope| {
747 scope.set_tag("alert_id", "persist_pushdown_audit_violation")
748 },
749 || {
750 error!(
751 ?stats,
752 name,
753 mfp = ?redact(&mfp),
754 result = ?redact(&result),
755 "persist filter pushdown correctness violation!"
756 );
757 if panic_on_audit_failure {
758 panic!(
759 "persist filter pushdown correctness violation! {}",
760 name
761 );
762 }
763 },
764 );
765 }
766 match result {
767 Ok((row, time, diff)) => {
768 if !until.less_equal(&time) {
770 let mut emit_time = cap_time;
771 emit_time.0 = time;
772 give(Ok(Cow::Owned(row)), emit_time, diff);
773 *work += 1;
774 }
775 }
776 Err((err, time, diff)) => {
777 if !until.less_equal(&time) {
779 let mut emit_time = cap_time;
780 emit_time.0 = time;
781 give(Err(err), emit_time, diff);
782 *work += 1;
783 }
784 }
785 }
786 }
787 drop(datums_local);
790 row_buf.replace(SourceData(Ok(row)));
791 } else {
792 let mut emit_time = cap_time;
793 emit_time.0 = time;
794 give(Ok(Cow::Borrowed(&row)), emit_time, diff.into());
796 row_buf.replace(SourceData(Ok(row)));
797 *work += 1;
798 }
799 }
800 (SourceData(Err(err)), ()) => {
801 if let Some(stats) = &is_filter_pushdown_audit {
807 sentry::with_scope(
808 |scope| scope.set_tag("alert_id", "persist_pushdown_audit_violation"),
809 || {
810 error!(
816 ?stats,
817 name,
818 err = ?redact(&err),
819 "persist filter pushdown correctness violation!"
820 );
821 if panic_on_audit_failure {
822 panic!("persist filter pushdown correctness violation! {}", name);
823 }
824 },
825 );
826 }
827 let mut emit_time = cap_time;
828 emit_time.0 = time;
829 give(Err(E::from(err)), emit_time, diff.into());
830 *work += 1;
831 }
832 }
833 if *work >= yield_fuel {
834 return false;
835 }
836 }
837 true
838}
839
840pub trait Backpressureable: Clone + 'static {
842 fn byte_size(&self) -> usize;
844}
845
846impl<T: Clone + 'static> Backpressureable for (usize, ExchangeableBatchPart<T>) {
847 fn byte_size(&self) -> usize {
848 self.1.encoded_size_bytes()
849 }
850}
851
852#[derive(Debug)]
854pub struct FlowControl<'scope, T: timely::progress::Timestamp> {
855 pub progress_stream: StreamVec<'scope, T, Infallible>,
861 pub max_inflight_bytes: usize,
863 pub summary: T::Summary,
866
867 pub metrics: Option<BackpressureOperatorMetrics>,
869}
870
871pub fn backpressure<'scope, T, O>(
884 scope: Scope<'scope, (T, Subtime)>,
885 name: &str,
886 data: StreamVec<'scope, (T, Subtime), O>,
887 flow_control: FlowControl<'scope, (T, Subtime)>,
888 chosen_worker: usize,
889 probe: Option<UnboundedSender<(Antichain<(T, Subtime)>, usize, usize)>>,
891) -> (StreamVec<'scope, (T, Subtime), O>, PressOnDropButton)
892where
893 T: TimelyTimestamp + Lattice + Codec64 + TotalOrder,
894 O: Backpressureable + std::fmt::Debug,
895{
896 let worker_index = scope.index();
897
898 let (flow_control_stream, flow_control_max_bytes, metrics) = (
899 flow_control.progress_stream,
900 flow_control.max_inflight_bytes,
901 flow_control.metrics,
902 );
903
904 let (handle, summaried_flow) = scope.feedback(flow_control.summary.clone());
909 flow_control_stream.connect_loop(handle);
910
911 let mut builder = AsyncOperatorBuilder::new(
912 format!("persist_source_backpressure({})", name),
913 scope.clone(),
914 );
915 let (data_output, data_stream) = builder.new_output::<CapacityContainerBuilder<Vec<_>>>();
916
917 let mut data_input = builder.new_disconnected_input(data, Pipeline);
918 let mut flow_control_input = builder.new_disconnected_input(summaried_flow, Pipeline);
919
920 fn synthesize_frontiers<T: PartialOrder + Clone>(
922 mut frontier: Antichain<(T, Subtime)>,
923 mut time: (T, Subtime),
924 part_number: &mut u64,
925 ) -> (
926 (T, Subtime),
927 Antichain<(T, Subtime)>,
928 Antichain<(T, Subtime)>,
929 ) {
930 let mut next_frontier = frontier.clone();
931 time.1 = Subtime(*part_number);
932 frontier.insert(time.clone());
933 *part_number += 1;
934 let mut next_time = time.clone();
935 next_time.1 = Subtime(*part_number);
936 next_frontier.insert(next_time);
937 (time, frontier, next_frontier)
938 }
939
940 let data_input = async_stream::stream!({
943 let mut part_number = 0;
944 let mut parts: Vec<((T, Subtime), O)> = Vec::new();
945 loop {
946 match data_input.next().await {
947 None => {
948 let empty = Antichain::new();
949 parts.sort_by_key(|val| val.0.clone());
950 for (part_time, d) in parts.drain(..) {
951 let (part_time, frontier, next_frontier) = synthesize_frontiers(
952 empty.clone(),
953 part_time.clone(),
954 &mut part_number,
955 );
956 yield Either::Right((part_time, d, frontier, next_frontier))
957 }
958 break;
959 }
960 Some(Event::Data(time, data)) => {
961 for d in data {
962 parts.push((time.clone(), d));
963 }
964 }
965 Some(Event::Progress(prog)) => {
966 parts.sort_by_key(|val| val.0.clone());
967 for (part_time, d) in parts.extract_if(.., |p| !prog.less_equal(&p.0)) {
968 let (part_time, frontier, next_frontier) =
969 synthesize_frontiers(prog.clone(), part_time.clone(), &mut part_number);
970 yield Either::Right((part_time, d, frontier, next_frontier))
971 }
972 yield Either::Left(prog)
973 }
974 }
975 }
976 });
977 let shutdown_button = builder.build(move |caps| async move {
978 let mut cap_set = CapabilitySet::from_elem(caps.into_element());
980
981 let mut output_frontier = Antichain::from_elem(TimelyTimestamp::minimum());
983 let mut flow_control_frontier = Antichain::from_elem(TimelyTimestamp::minimum());
985
986 let mut inflight_parts = Vec::new();
988 let mut pending_parts = std::collections::VecDeque::new();
990
991 if worker_index != chosen_worker {
993 trace!(
994 "We are not the chosen worker ({}), exiting...",
995 chosen_worker
996 );
997 return;
998 }
999 tokio::pin!(data_input);
1000 'emitting_parts: loop {
1001 let inflight_bytes: usize = inflight_parts.iter().map(|(_, size)| size).sum();
1004
1005 if inflight_bytes < flow_control_max_bytes
1013 || !PartialOrder::less_equal(&flow_control_frontier, &output_frontier)
1014 {
1015 let (time, part, next_frontier) =
1016 if let Some((time, part, next_frontier)) = pending_parts.pop_front() {
1017 (time, part, next_frontier)
1018 } else {
1019 match data_input.next().await {
1020 Some(Either::Right((time, part, frontier, next_frontier))) => {
1021 output_frontier = frontier;
1026 cap_set.downgrade(output_frontier.iter());
1027
1028 if inflight_bytes >= flow_control_max_bytes
1033 && !PartialOrder::less_than(
1034 &output_frontier,
1035 &flow_control_frontier,
1036 )
1037 {
1038 pending_parts.push_back((time, part, next_frontier));
1039 continue 'emitting_parts;
1040 }
1041 (time, part, next_frontier)
1042 }
1043 Some(Either::Left(prog)) => {
1044 output_frontier = prog;
1045 cap_set.downgrade(output_frontier.iter());
1046 continue 'emitting_parts;
1047 }
1048 None => {
1049 if pending_parts.is_empty() {
1050 break 'emitting_parts;
1051 } else {
1052 continue 'emitting_parts;
1053 }
1054 }
1055 }
1056 };
1057
1058 let byte_size = part.byte_size();
1059 if let Some(emission_ts) = flow_control.summary.results_in(&time) {
1069 inflight_parts.push((emission_ts, byte_size));
1070 }
1071
1072 data_output.give(&cap_set.delayed(&time), part);
1075
1076 if let Some(metrics) = &metrics {
1077 metrics.emitted_bytes.inc_by(u64::cast_from(byte_size))
1078 }
1079
1080 output_frontier = next_frontier;
1081 cap_set.downgrade(output_frontier.iter())
1082 } else {
1083 if let Some(metrics) = &metrics {
1084 metrics
1085 .last_backpressured_bytes
1086 .set(u64::cast_from(inflight_bytes))
1087 }
1088 let parts_count = inflight_parts.len();
1089 let new_flow_control_frontier = match flow_control_input.next().await {
1094 Some(Event::Progress(frontier)) => frontier,
1095 Some(Event::Data(_, _)) => {
1096 unreachable!("flow_control_input should not contain data")
1097 }
1098 None => Antichain::new(),
1099 };
1100
1101 flow_control_frontier.clone_from(&new_flow_control_frontier);
1103
1104 let retired_parts = inflight_parts
1106 .extract_if(.., |(ts, _size)| !flow_control_frontier.less_equal(ts));
1107 let (retired_size, retired_count): (usize, usize) = retired_parts
1108 .fold((0, 0), |(accum_size, accum_count), (_ts, size)| {
1109 (accum_size + size, accum_count + 1)
1110 });
1111 trace!(
1112 "returning {} parts with {} bytes, frontier: {:?}",
1113 retired_count, retired_size, flow_control_frontier,
1114 );
1115
1116 if let Some(metrics) = &metrics {
1117 metrics.retired_bytes.inc_by(u64::cast_from(retired_size))
1118 }
1119
1120 if let Some(probe) = probe.as_ref() {
1122 let _ = probe.send((new_flow_control_frontier, parts_count, retired_count));
1123 }
1124 }
1125 }
1126 });
1127 (data_stream, shutdown_button.press_on_drop())
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132 use timely::container::CapacityContainerBuilder;
1133 use timely::dataflow::operators::{Enter, Probe};
1134 use tokio::sync::mpsc::unbounded_channel;
1135 use tokio::sync::oneshot;
1136
1137 use super::*;
1138
1139 #[mz_ore::test]
1140 fn test_backpressure_non_granular() {
1141 use Step::*;
1142 backpressure_runner(
1143 vec![(50, Part(101)), (50, Part(102)), (100, Part(1))],
1144 100,
1145 (1, Subtime(0)),
1146 vec![
1147 AssertOutputFrontier((50, Subtime(2))),
1150 AssertBackpressured {
1151 frontier: (1, Subtime(0)),
1152 inflight_parts: 1,
1153 retired_parts: 0,
1154 },
1155 AssertBackpressured {
1156 frontier: (51, Subtime(0)),
1157 inflight_parts: 1,
1158 retired_parts: 0,
1159 },
1160 ProcessXParts(2),
1161 AssertBackpressured {
1162 frontier: (101, Subtime(0)),
1163 inflight_parts: 2,
1164 retired_parts: 2,
1165 },
1166 AssertOutputFrontier((100, Subtime(3))),
1169 ],
1170 true,
1171 );
1172
1173 backpressure_runner(
1174 vec![
1175 (50, Part(10)),
1176 (50, Part(10)),
1177 (51, Part(100)),
1178 (52, Part(1000)),
1179 ],
1180 50,
1181 (1, Subtime(0)),
1182 vec![
1183 AssertOutputFrontier((51, Subtime(3))),
1185 AssertBackpressured {
1186 frontier: (1, Subtime(0)),
1187 inflight_parts: 3,
1188 retired_parts: 0,
1189 },
1190 ProcessXParts(3),
1191 AssertBackpressured {
1192 frontier: (52, Subtime(0)),
1193 inflight_parts: 3,
1194 retired_parts: 2,
1195 },
1196 AssertBackpressured {
1197 frontier: (53, Subtime(0)),
1198 inflight_parts: 1,
1199 retired_parts: 1,
1200 },
1201 AssertOutputFrontier((52, Subtime(4))),
1204 ],
1205 true,
1206 );
1207
1208 backpressure_runner(
1209 vec![
1210 (50, Part(98)),
1211 (50, Part(1)),
1212 (51, Part(10)),
1213 (52, Part(100)),
1214 (52, Part(10)),
1216 (52, Part(10)),
1217 (52, Part(10)),
1218 (52, Part(100)),
1219 (100, Part(100)),
1221 ],
1222 100,
1223 (1, Subtime(0)),
1224 vec![
1225 AssertOutputFrontier((51, Subtime(3))),
1226 AssertBackpressured {
1230 frontier: (1, Subtime(0)),
1231 inflight_parts: 3,
1232 retired_parts: 0,
1233 },
1234 AssertBackpressured {
1235 frontier: (51, Subtime(0)),
1236 inflight_parts: 3,
1237 retired_parts: 0,
1238 },
1239 ProcessXParts(1),
1240 AssertOutputFrontier((51, Subtime(3))),
1243 ProcessXParts(1),
1247 AssertOutputFrontier((52, Subtime(4))),
1248 AssertBackpressured {
1249 frontier: (52, Subtime(0)),
1250 inflight_parts: 3,
1251 retired_parts: 2,
1252 },
1253 ProcessXParts(1),
1257 AssertBackpressured {
1261 frontier: (53, Subtime(0)),
1262 inflight_parts: 2,
1263 retired_parts: 1,
1264 },
1265 ProcessXParts(5),
1267 AssertBackpressured {
1268 frontier: (101, Subtime(0)),
1269 inflight_parts: 5,
1270 retired_parts: 5,
1271 },
1272 AssertOutputFrontier((100, Subtime(9))),
1273 ],
1274 true,
1275 );
1276 }
1277
1278 #[mz_ore::test]
1279 fn test_backpressure_granular() {
1280 use Step::*;
1281 backpressure_runner(
1282 vec![(50, Part(101)), (50, Part(101))],
1283 100,
1284 (0, Subtime(1)),
1285 vec![
1286 AssertOutputFrontier((50, Subtime(1))),
1288 AssertBackpressured {
1291 frontier: (0, Subtime(1)),
1292 inflight_parts: 1,
1293 retired_parts: 0,
1294 },
1295 AssertBackpressured {
1296 frontier: (50, Subtime(1)),
1297 inflight_parts: 1,
1298 retired_parts: 0,
1299 },
1300 ProcessXParts(1),
1302 AssertBackpressured {
1304 frontier: (50, Subtime(2)),
1305 inflight_parts: 1,
1306 retired_parts: 1,
1307 },
1308 AssertOutputFrontier((50, Subtime(2))),
1310 ],
1311 false,
1312 );
1313
1314 backpressure_runner(
1315 vec![
1316 (50, Part(10)),
1317 (50, Part(10)),
1318 (51, Part(35)),
1319 (52, Part(100)),
1320 ],
1321 50,
1322 (0, Subtime(1)),
1323 vec![
1324 AssertOutputFrontier((51, Subtime(3))),
1326 AssertBackpressured {
1327 frontier: (0, Subtime(1)),
1328 inflight_parts: 3,
1329 retired_parts: 0,
1330 },
1331 AssertBackpressured {
1332 frontier: (50, Subtime(1)),
1333 inflight_parts: 3,
1334 retired_parts: 0,
1335 },
1336 ProcessXParts(1),
1338 AssertBackpressured {
1339 frontier: (50, Subtime(2)),
1340 inflight_parts: 3,
1341 retired_parts: 1,
1342 },
1343 AssertOutputFrontier((52, Subtime(4))),
1346 ProcessXParts(2),
1347 AssertBackpressured {
1348 frontier: (52, Subtime(4)),
1349 inflight_parts: 3,
1350 retired_parts: 2,
1351 },
1352 ],
1353 false,
1354 );
1355 }
1356
1357 type Time = (u64, Subtime);
1358 #[derive(Clone, Debug)]
1359 struct Part(usize);
1360 impl Backpressureable for Part {
1361 fn byte_size(&self) -> usize {
1362 self.0
1363 }
1364 }
1365
1366 enum Step {
1368 AssertOutputFrontier(Time),
1371 AssertBackpressured {
1375 frontier: Time,
1376 inflight_parts: usize,
1377 retired_parts: usize,
1378 },
1379 ProcessXParts(usize),
1381 }
1382
1383 fn backpressure_runner(
1385 input: Vec<(u64, Part)>,
1387 max_inflight_bytes: usize,
1389 summary: Time,
1391 steps: Vec<Step>,
1393 non_granular_consumer: bool,
1396 ) {
1397 timely::execute::execute_directly(move |worker| {
1398 let (
1399 backpressure_probe,
1400 consumer_tx,
1401 mut backpressure_status_rx,
1402 finalizer_tx,
1403 _token,
1404 ) =
1405 worker.dataflow::<u64, _, _>(|outer_scope| {
1407 let (non_granular_feedback_handle, non_granular_feedback) =
1408 if non_granular_consumer {
1409 let (h, f) = outer_scope.feedback(Default::default());
1410 (Some(h), Some(f))
1411 } else {
1412 (None, None)
1413 };
1414 let (
1415 backpressure_probe,
1416 consumer_tx,
1417 backpressure_status_rx,
1418 token,
1419 backpressured,
1420 finalizer_tx,
1421 ) = outer_scope.scoped::<(u64, Subtime), _, _>("hybrid", |scope| {
1422 let (input, finalizer_tx) =
1423 iterator_operator(scope.clone(), input.into_iter());
1424
1425 let (flow_control, granular_feedback_handle) = if non_granular_consumer {
1426 (
1427 FlowControl {
1428 progress_stream: non_granular_feedback.unwrap().enter(scope),
1429 max_inflight_bytes,
1430 summary,
1431 metrics: None
1432 },
1433 None,
1434 )
1435 } else {
1436 let (granular_feedback_handle, granular_feedback) =
1437 scope.feedback(Default::default());
1438 (
1439 FlowControl {
1440 progress_stream: granular_feedback,
1441 max_inflight_bytes,
1442 summary,
1443 metrics: None,
1444 },
1445 Some(granular_feedback_handle),
1446 )
1447 };
1448
1449 let (backpressure_status_tx, backpressure_status_rx) = unbounded_channel();
1450
1451 let (backpressured, token) = backpressure(
1452 scope,
1453 "test",
1454 input,
1455 flow_control,
1456 0,
1457 Some(backpressure_status_tx),
1458 );
1459
1460 let tx = if !non_granular_consumer {
1462 Some(consumer_operator(
1463 scope.clone(),
1464 backpressured.clone(),
1465 granular_feedback_handle.unwrap(),
1466 ))
1467 } else {
1468 None
1469 };
1470
1471 let (probe_handle, backpressured) = backpressured.probe();
1472 (
1473 probe_handle,
1474 tx,
1475 backpressure_status_rx,
1476 token,
1477 backpressured.leave(outer_scope),
1478 finalizer_tx,
1479 )
1480 });
1481
1482 let consumer_tx = if non_granular_consumer {
1484 consumer_operator(
1485 outer_scope.clone(),
1486 backpressured,
1487 non_granular_feedback_handle.unwrap(),
1488 )
1489 } else {
1490 consumer_tx.unwrap()
1491 };
1492
1493 (
1494 backpressure_probe,
1495 consumer_tx,
1496 backpressure_status_rx,
1497 finalizer_tx,
1498 token,
1499 )
1500 });
1501
1502 use Step::*;
1503 for step in steps {
1504 match step {
1505 AssertOutputFrontier(time) => {
1506 eprintln!("checking advance to {time:?}");
1507 backpressure_probe.with_frontier(|front| {
1508 eprintln!("current backpressure output frontier: {front:?}");
1509 });
1510 while backpressure_probe.less_than(&time) {
1511 worker.step();
1512 backpressure_probe.with_frontier(|front| {
1513 eprintln!("current backpressure output frontier: {front:?}");
1514 });
1515 std::thread::sleep(std::time::Duration::from_millis(25));
1516 }
1517 }
1518 ProcessXParts(parts) => {
1519 eprintln!("processing {parts:?} parts");
1520 for _ in 0..parts {
1521 consumer_tx.send(()).unwrap();
1522 }
1523 }
1524 AssertBackpressured {
1525 frontier,
1526 inflight_parts,
1527 retired_parts,
1528 } => {
1529 let frontier = Antichain::from_elem(frontier);
1530 eprintln!(
1531 "asserting backpressured at {frontier:?}, with {inflight_parts:?} inflight parts \
1532 and {retired_parts:?} retired"
1533 );
1534 let (new_frontier, new_count, new_retired_count) = loop {
1535 if let Ok(val) = backpressure_status_rx.try_recv() {
1536 break val;
1537 }
1538 worker.step();
1539 std::thread::sleep(std::time::Duration::from_millis(25));
1540 };
1541 assert_eq!(
1542 (frontier, inflight_parts, retired_parts),
1543 (new_frontier, new_count, new_retired_count)
1544 );
1545 }
1546 }
1547 }
1548 let _ = finalizer_tx.send(());
1550 });
1551 }
1552
1553 fn iterator_operator<'scope, I: Iterator<Item = (u64, Part)> + 'static>(
1556 scope: Scope<'scope, (u64, Subtime)>,
1557 mut input: I,
1558 ) -> (StreamVec<'scope, (u64, Subtime), Part>, oneshot::Sender<()>) {
1559 let (finalizer_tx, finalizer_rx) = oneshot::channel();
1560 let mut iterator = AsyncOperatorBuilder::new("iterator".to_string(), scope);
1561 let (output_handle, output) = iterator.new_output::<CapacityContainerBuilder<Vec<Part>>>();
1562
1563 iterator.build(|mut caps| async move {
1564 let mut capability = Some(caps.pop().unwrap());
1565 let mut last = None;
1566 while let Some(element) = input.next() {
1567 let time = element.0.clone();
1568 let part = element.1;
1569 last = Some((time, Subtime(0)));
1570 output_handle.give(&capability.as_ref().unwrap().delayed(&last.unwrap()), part);
1571 }
1572 if let Some(last) = last {
1573 capability
1574 .as_mut()
1575 .unwrap()
1576 .downgrade(&(last.0 + 1, last.1));
1577 }
1578
1579 let _ = finalizer_rx.await;
1580 capability.take();
1581 });
1582
1583 (output, finalizer_tx)
1584 }
1585
1586 fn consumer_operator<
1590 'scope,
1591 T: timely::progress::Timestamp,
1592 O: Backpressureable + std::fmt::Debug,
1593 >(
1594 scope: Scope<'scope, T>,
1595 input: StreamVec<'scope, T, O>,
1596 feedback: timely::dataflow::operators::feedback::Handle<
1597 'scope,
1598 T,
1599 Vec<std::convert::Infallible>,
1600 >,
1601 ) -> UnboundedSender<()> {
1602 let (tx, mut rx) = unbounded_channel::<()>();
1603 let mut consumer = AsyncOperatorBuilder::new("consumer".to_string(), scope);
1604 let (output_handle, output) =
1605 consumer.new_output::<CapacityContainerBuilder<Vec<std::convert::Infallible>>>();
1606 let mut input = consumer.new_input_for(input, Pipeline, &output_handle);
1607
1608 consumer.build(|_caps| async move {
1609 while let Some(()) = rx.recv().await {
1610 while let Some(Event::Progress(_)) = input.next().await {}
1612 }
1613 });
1614 output.connect_loop(feedback);
1615
1616 tx
1617 }
1618
1619 mod filter_pushdown_audit {
1634 use itertools::Itertools;
1635 use mz_expr::func::variadic::{And, Or};
1636 use mz_expr::func::{
1637 AddFloat32, AddTimestampInterval, CastNumericToFloat32, CastNumericToMzTimestamp, Eq,
1638 Gt, Gte, IsNull, JsonbGetString, JsonbGetStringStringify, Lt, Lte, MulFloat32,
1639 MulFloat64, Not, RoundNumericBinary, TryParseMonotonicIso8601Timestamp,
1640 };
1641 use mz_expr::{BinaryFunc, MapFilterProject, MirScalarExpr, UnaryFunc};
1642 use mz_ore::metrics::MetricsRegistry;
1643 use mz_persist_types::part::PartBuilder;
1644 use mz_persist_types::stats::{PartStats, PartStatsMetrics};
1645 use mz_repr::adt::interval::Interval;
1646 use mz_repr::adt::numeric::Numeric;
1647 use mz_repr::{Diff, ReprScalarType, SqlScalarType};
1648 use proptest::prelude::*;
1649 use proptest::sample::{Index, select};
1650 use proptest::strategy::Union;
1651
1652 use super::*;
1653
1654 fn f32_lit(x: f32) -> MirScalarExpr {
1655 MirScalarExpr::literal_ok(Datum::from(x), ReprScalarType::Float32)
1656 }
1657
1658 fn f64_lit(x: f64) -> MirScalarExpr {
1659 MirScalarExpr::literal_ok(Datum::from(x), ReprScalarType::Float64)
1660 }
1661
1662 fn numeric_datum(x: f64) -> Datum<'static> {
1663 Datum::from(Numeric::from(x))
1664 }
1665
1666 fn numeric_desc() -> RelationDesc {
1667 RelationDesc::builder()
1668 .with_column(
1669 "c0",
1670 SqlScalarType::Numeric { max_scale: None }.nullable(false),
1671 )
1672 .finish()
1673 }
1674
1675 fn build_part_stats(desc: &RelationDesc, rows: &[SourceData]) -> PartStats {
1678 let mut builder = PartBuilder::new(desc, &UnitSchema);
1679 for row in rows {
1680 builder.push(row, &(), 1u64, 1i64);
1681 }
1682 let part = builder.finish();
1683 PartStats::new::<SourceData, RelationDesc>(&part, desc).expect("stats")
1684 }
1685
1686 fn mfp_yields_output(plan: &MfpPlan, rows: &[Row]) -> bool {
1690 let arena = RowArena::new();
1691 let mut row_builder = Row::default();
1692 for row in rows {
1693 let mut datums: Vec<Datum> = row.iter().collect();
1694 let mut results = plan.evaluate::<DataflowError, _>(
1695 &mut datums,
1696 &arena,
1697 Timestamp::MIN,
1698 Diff::from(1),
1699 |_| true,
1700 &mut row_builder,
1701 );
1702 if results.next().is_some() {
1703 return true;
1704 }
1705 }
1706 false
1707 }
1708
1709 fn comparison_funcs() -> impl Strategy<Value = BinaryFunc> {
1710 select(vec![
1711 BinaryFunc::Lte(Lte),
1712 BinaryFunc::Lt(Lt),
1713 BinaryFunc::Gte(Gte),
1714 BinaryFunc::Gt(Gt),
1715 BinaryFunc::Eq(Eq),
1716 ])
1717 }
1718
1719 fn f32_consts() -> impl Strategy<Value = f32> {
1720 select(vec![
1721 0.0f32,
1722 1.0,
1723 -1.0,
1724 0.0087531805,
1725 745213.56,
1726 76700000000.0,
1727 f32::MAX,
1728 1e30,
1729 -1e30,
1730 ])
1731 }
1732
1733 fn float_arith_predicate(
1737 scale: i32,
1738 a: f32,
1739 b: f32,
1740 c: f32,
1741 cmp: BinaryFunc,
1742 ) -> MirScalarExpr {
1743 let round = MirScalarExpr::CallBinary {
1744 func: BinaryFunc::RoundNumeric(RoundNumericBinary),
1745 expr1: Box::new(MirScalarExpr::column(0)),
1746 expr2: Box::new(MirScalarExpr::literal_ok(
1747 Datum::from(scale),
1748 ReprScalarType::Int32,
1749 )),
1750 };
1751 let cast = MirScalarExpr::CallUnary {
1752 func: UnaryFunc::CastNumericToFloat32(CastNumericToFloat32),
1753 expr: Box::new(round),
1754 };
1755 let add = MirScalarExpr::CallBinary {
1756 func: BinaryFunc::AddFloat32(AddFloat32),
1757 expr1: Box::new(f32_lit(a)),
1758 expr2: Box::new(cast),
1759 };
1760 let mul = MirScalarExpr::CallBinary {
1761 func: BinaryFunc::MulFloat32(MulFloat32),
1762 expr1: Box::new(add),
1763 expr2: Box::new(f32_lit(b)),
1764 };
1765 MirScalarExpr::CallBinary {
1766 func: cmp,
1767 expr1: Box::new(mul),
1768 expr2: Box::new(f32_lit(c)),
1769 }
1770 }
1771
1772 fn cast_mz_timestamp_predicate(ts: u64, cmp: BinaryFunc) -> MirScalarExpr {
1776 let cast = MirScalarExpr::CallUnary {
1777 func: UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
1778 expr: Box::new(MirScalarExpr::column(0)),
1779 };
1780 MirScalarExpr::CallBinary {
1781 func: cmp,
1782 expr1: Box::new(cast),
1783 expr2: Box::new(MirScalarExpr::literal_ok(
1784 Datum::MzTimestamp(Timestamp::from(ts)),
1785 ReprScalarType::MzTimestamp,
1786 )),
1787 }
1788 }
1789
1790 fn arb_numeric_rows() -> impl Strategy<Value = Vec<Row>> {
1791 let magnitudes = vec![
1792 0.0f64, 1.0, 2.0, 1.5, 2.5, 0.25, -1.0, 10.0, 100.0, 1e10, -1e10, 3.0e38, 3.4e38,
1793 3.5e38, 1e40, -1e40, 1e300,
1794 ];
1795 prop::collection::vec(
1796 select(magnitudes).prop_map(|x| Row::pack_slice(&[numeric_datum(x)])),
1797 1..8,
1798 )
1799 }
1800
1801 fn arb_predicate() -> impl Strategy<Value = MirScalarExpr> {
1802 let float_arith = (
1803 select(vec![0i32, 2, -5, 24699]),
1804 f32_consts(),
1805 f32_consts(),
1806 f32_consts(),
1807 comparison_funcs(),
1808 )
1809 .prop_map(|(scale, a, b, c, cmp)| float_arith_predicate(scale, a, b, c, cmp));
1810 let cast_ts = (select(vec![0u64, 1, 2, 100, u64::MAX]), comparison_funcs())
1811 .prop_map(|(ts, cmp)| cast_mz_timestamp_predicate(ts, cmp));
1812 proptest::strategy::Union::new(vec![float_arith.boxed(), cast_ts.boxed()])
1813 }
1814
1815 #[mz_ore::test]
1816 #[cfg_attr(miri, ignore)] fn filter_result_never_discards_matching_part() {
1818 fn check(rows: Vec<Row>, predicate: MirScalarExpr) -> Result<(), TestCaseError> {
1819 let desc = numeric_desc();
1820 let plan = MapFilterProject::new(1)
1821 .filter(std::iter::once(predicate))
1822 .into_plan()
1823 .expect("into_plan");
1824
1825 let source_rows: Vec<_> = rows.iter().map(|r| SourceData(Ok(r.clone()))).collect();
1826 let part_stats = build_part_stats(&desc, &source_rows);
1827 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
1828 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
1829
1830 let decision = filter_result(&desc, ResultSpec::anything(), stats, &plan);
1831
1832 if mfp_yields_output(&plan, &rows) {
1833 prop_assert!(
1834 !matches!(decision, FilterResult::Discard),
1835 "filter pushdown discarded a part whose MFP yields output on a real \
1836 row (wrongly-skipped part; the runtime audit would panic).\n\
1837 rows={rows:?}\nplan={plan:?}",
1838 );
1839 }
1840 Ok(())
1841 }
1842
1843 proptest!(|(rows in arb_numeric_rows(), predicate in arb_predicate())| {
1844 check(rows, predicate)?;
1845 });
1846 }
1847
1848 fn assert_part_kept(desc: &RelationDesc, rows: &[Row], predicate: MirScalarExpr) {
1852 let plan = MapFilterProject::new(1)
1853 .filter(std::iter::once(predicate))
1854 .into_plan()
1855 .expect("into_plan");
1856 assert!(
1857 mfp_yields_output(&plan, rows),
1858 "nothing to keep: the MFP yields no output on any of these rows.\n\
1859 rows={rows:?}\nplan={plan:?}",
1860 );
1861
1862 let source_rows: Vec<_> = rows.iter().map(|r| SourceData(Ok(r.clone()))).collect();
1863 let part_stats = build_part_stats(desc, &source_rows);
1864 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
1865 let stats = RelationPartStats::new("test", &metrics, desc, &part_stats);
1866 let decision = filter_result(desc, ResultSpec::anything(), stats, &plan);
1867 assert!(
1868 !matches!(decision, FilterResult::Discard),
1869 "filter pushdown discarded a part whose MFP yields output on a real row.\n\
1870 rows={rows:?}\nplan={plan:?}",
1871 );
1872 }
1873
1874 #[mz_ore::test]
1885 #[cfg_attr(miri, ignore)] fn negative_nan_does_not_discard_matching_part() {
1887 let desc = RelationDesc::builder()
1890 .with_column("c0", SqlScalarType::Float32.nullable(false))
1891 .finish();
1892 let rows = [
1893 Row::pack_slice(&[Datum::from(-f32::NAN)]),
1894 Row::pack_slice(&[Datum::from(0.0f32)]),
1895 ];
1896 assert_part_kept(
1897 &desc,
1898 &rows,
1899 MirScalarExpr::CallBinary {
1900 func: BinaryFunc::Lt(Lt),
1901 expr1: Box::new(MirScalarExpr::column(0)),
1902 expr2: Box::new(f32_lit(1.0)),
1903 },
1904 );
1905
1906 let desc = RelationDesc::builder()
1907 .with_column("c0", SqlScalarType::Float64.nullable(false))
1908 .finish();
1909 let rows = [
1910 Row::pack_slice(&[Datum::from(-f64::NAN)]),
1911 Row::pack_slice(&[Datum::from(0.0f64)]),
1912 ];
1913 assert_part_kept(
1914 &desc,
1915 &rows,
1916 MirScalarExpr::CallBinary {
1917 func: BinaryFunc::Lt(Lt),
1918 expr1: Box::new(MirScalarExpr::column(0)),
1919 expr2: Box::new(f64_lit(1.0)),
1920 },
1921 );
1922 }
1923
1924 #[mz_ore::test]
1935 #[cfg_attr(miri, ignore)] fn mixed_sign_nans_do_not_discard_matching_part() {
1937 let desc = RelationDesc::builder()
1938 .with_column("c0", SqlScalarType::Float32.nullable(false))
1939 .finish();
1940 let rows = [
1941 Row::pack_slice(&[Datum::from(-f32::NAN)]),
1942 Row::pack_slice(&[Datum::from(f32::NAN)]),
1943 Row::pack_slice(&[Datum::from(0.0f32)]),
1944 ];
1945 assert_part_kept(
1946 &desc,
1947 &rows,
1948 MirScalarExpr::CallBinary {
1949 func: BinaryFunc::Eq(Eq),
1950 expr1: Box::new(MirScalarExpr::column(0)),
1951 expr2: Box::new(f32_lit(0.0)),
1952 },
1953 );
1954
1955 let desc = RelationDesc::builder()
1956 .with_column("c0", SqlScalarType::Float64.nullable(false))
1957 .finish();
1958 let rows = [
1959 Row::pack_slice(&[Datum::from(-f64::NAN)]),
1960 Row::pack_slice(&[Datum::from(f64::NAN)]),
1961 Row::pack_slice(&[Datum::from(0.0f64)]),
1962 ];
1963 assert_part_kept(
1964 &desc,
1965 &rows,
1966 MirScalarExpr::CallBinary {
1967 func: BinaryFunc::Eq(Eq),
1968 expr1: Box::new(MirScalarExpr::column(0)),
1969 expr2: Box::new(f64_lit(0.0)),
1970 },
1971 );
1972 }
1973
1974 const NUM: usize = 0;
1983 const F32: usize = 1;
1984 const F64: usize = 2;
1985 const STR: usize = 3;
1986 const J1: usize = 4;
1987 const J2: usize = 5;
1988 const BOOL: usize = 6;
1989 const TS: usize = 7;
1990 const MZTS: usize = 8;
1991 const WIDE_ARITY: usize = 9;
1992
1993 fn wide_scalar_type(col: usize) -> SqlScalarType {
1994 match col {
1995 NUM => SqlScalarType::Numeric { max_scale: None },
1996 F32 => SqlScalarType::Float32,
1997 F64 => SqlScalarType::Float64,
1998 STR => SqlScalarType::String,
1999 J1 | J2 => SqlScalarType::Jsonb,
2000 BOOL => SqlScalarType::Bool,
2001 TS => SqlScalarType::Timestamp { precision: None },
2002 MZTS => SqlScalarType::MzTimestamp,
2003 _ => unreachable!("no such column"),
2004 }
2005 }
2006
2007 fn wide_repr_type(col: usize) -> ReprScalarType {
2008 match col {
2009 NUM => ReprScalarType::Numeric,
2010 F32 => ReprScalarType::Float32,
2011 F64 => ReprScalarType::Float64,
2012 STR => ReprScalarType::String,
2013 J1 | J2 => ReprScalarType::Jsonb,
2014 BOOL => ReprScalarType::Bool,
2015 TS => ReprScalarType::Timestamp,
2016 MZTS => ReprScalarType::MzTimestamp,
2017 _ => unreachable!("no such column"),
2018 }
2019 }
2020
2021 fn wide_desc() -> RelationDesc {
2022 let mut builder = RelationDesc::builder();
2023 for col in 0..WIDE_ARITY {
2024 let nullable = col != BOOL;
2028 builder = builder
2029 .with_column(format!("c{col}"), wide_scalar_type(col).nullable(nullable));
2030 }
2031 builder.finish()
2032 }
2033
2034 fn wide_pool(col: usize) -> Vec<Datum<'static>> {
2035 let mut pool: Vec<_> = wide_scalar_type(col).interesting_datums().collect();
2036 if col != BOOL {
2037 pool.push(Datum::Null);
2038 }
2039 pool
2040 }
2041
2042 fn arb_wide_rows() -> impl Strategy<Value = Vec<SourceData>> {
2043 let pools: Vec<Vec<Datum<'static>>> = (0..WIDE_ARITY).map(wide_pool).collect();
2044 let ok_row = prop::collection::vec(any::<Index>(), WIDE_ARITY).prop_map(move |picks| {
2045 let datums = picks
2046 .iter()
2047 .zip_eq(&pools)
2048 .map(|(pick, pool)| pool[pick.index(pool.len())]);
2049 SourceData(Ok(Row::pack(datums)))
2050 });
2051 let err_row = Just(SourceData(Err(DataflowError::from(
2052 EvalError::DivisionByZero,
2053 ))));
2054 let row = Union::new_weighted(vec![(9, ok_row.boxed()), (1, err_row.boxed())]);
2055 prop::collection::vec(row, 2..8)
2056 }
2057
2058 fn lit(datum: Datum<'static>, typ: ReprScalarType) -> MirScalarExpr {
2059 if datum.is_null() {
2060 MirScalarExpr::literal_null(typ)
2061 } else {
2062 MirScalarExpr::literal_ok(datum, typ)
2063 }
2064 }
2065
2066 fn is_null(expr: MirScalarExpr) -> MirScalarExpr {
2067 MirScalarExpr::CallUnary {
2068 func: UnaryFunc::IsNull(IsNull),
2069 expr: Box::new(expr),
2070 }
2071 }
2072
2073 fn not(expr: MirScalarExpr) -> MirScalarExpr {
2074 MirScalarExpr::CallUnary {
2075 func: UnaryFunc::Not(Not),
2076 expr: Box::new(expr),
2077 }
2078 }
2079
2080 fn binary(func: BinaryFunc, a: MirScalarExpr, b: MirScalarExpr) -> MirScalarExpr {
2081 MirScalarExpr::CallBinary {
2082 func,
2083 expr1: Box::new(a),
2084 expr2: Box::new(b),
2085 }
2086 }
2087
2088 fn arb_cmp_col_lit() -> impl Strategy<Value = MirScalarExpr> {
2091 (0..WIDE_ARITY, any::<Index>(), comparison_funcs()).prop_map(|(col, pick, cmp)| {
2092 let pool = wide_pool(col);
2093 let datum = pool[pick.index(pool.len())];
2094 binary(
2095 cmp,
2096 MirScalarExpr::column(col),
2097 lit(datum, wide_repr_type(col)),
2098 )
2099 })
2100 }
2101
2102 fn arb_is_null_pred() -> impl Strategy<Value = MirScalarExpr> {
2103 (0..WIDE_ARITY, any::<bool>()).prop_map(|(col, negate)| {
2104 let expr = is_null(MirScalarExpr::column(col));
2105 if negate { not(expr) } else { expr }
2106 })
2107 }
2108
2109 fn jsonb_keys() -> impl Strategy<Value = &'static str> {
2110 select(vec!["x", "y", "nested", "absent"])
2111 }
2112
2113 fn jsonb_get(expr: MirScalarExpr, key: &'static str, stringify: bool) -> MirScalarExpr {
2114 let func = if stringify {
2115 BinaryFunc::JsonbGetStringStringify(JsonbGetStringStringify)
2116 } else {
2117 BinaryFunc::JsonbGetString(JsonbGetString)
2118 };
2119 binary(
2120 func,
2121 expr,
2122 MirScalarExpr::literal_ok(Datum::String(key), ReprScalarType::String),
2123 )
2124 }
2125
2126 fn arb_jsonb_pred() -> impl Strategy<Value = MirScalarExpr> {
2129 (
2130 select(vec![J1, J2]),
2131 jsonb_keys(),
2132 any::<bool>(),
2133 any::<bool>(),
2134 )
2135 .prop_map(|(col, key, stringify, wrap_eq)| {
2136 let get = jsonb_get(MirScalarExpr::column(col), key, stringify);
2137 if wrap_eq {
2138 let typ = if stringify {
2139 ReprScalarType::String
2140 } else {
2141 ReprScalarType::Jsonb
2142 };
2143 binary(BinaryFunc::Eq(Eq), get, lit(Datum::String("a"), typ))
2144 } else {
2145 is_null(get)
2146 }
2147 })
2148 }
2149
2150 fn arb_case_jsonb_pred() -> impl Strategy<Value = MirScalarExpr> {
2153 (any::<bool>(), jsonb_keys(), any::<bool>()).prop_map(
2154 |(cond_is_col, key, stringify)| {
2155 let cond = if cond_is_col {
2156 MirScalarExpr::column(BOOL)
2157 } else {
2158 is_null(MirScalarExpr::column(STR))
2159 };
2160 let case = MirScalarExpr::If {
2161 cond: Box::new(cond),
2162 then: Box::new(MirScalarExpr::column(J1)),
2163 els: Box::new(MirScalarExpr::column(J2)),
2164 };
2165 is_null(jsonb_get(case, key, stringify))
2166 },
2167 )
2168 }
2169
2170 fn arb_iso_parse_pred() -> impl Strategy<Value = MirScalarExpr> {
2173 (comparison_funcs(), any::<Index>(), any::<bool>()).prop_map(
2174 |(cmp, pick, wrap_null)| {
2175 let parse = MirScalarExpr::CallUnary {
2176 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(
2177 TryParseMonotonicIso8601Timestamp,
2178 ),
2179 expr: Box::new(MirScalarExpr::column(STR)),
2180 };
2181 if wrap_null {
2182 is_null(parse)
2183 } else {
2184 let pool: Vec<_> = SqlScalarType::Timestamp { precision: None }
2185 .interesting_datums()
2186 .collect();
2187 let datum = pool[pick.index(pool.len())];
2188 binary(cmp, parse, lit(datum, ReprScalarType::Timestamp))
2189 }
2190 },
2191 )
2192 }
2193
2194 fn arb_ts_interval_pred() -> impl Strategy<Value = MirScalarExpr> {
2198 let intervals = select(vec![
2199 Interval::new(0, 2, 0),
2200 Interval::new(0, 0, 3_600_000_000),
2201 Interval::new(1, 0, 0),
2202 Interval::new(-1, 0, 0),
2203 ]);
2204 (comparison_funcs(), intervals, any::<Index>()).prop_map(|(cmp, iv, pick)| {
2205 let add = binary(
2206 BinaryFunc::AddTimestampInterval(AddTimestampInterval),
2207 MirScalarExpr::column(TS),
2208 lit(Datum::Interval(iv), ReprScalarType::Interval),
2209 );
2210 let pool: Vec<_> = SqlScalarType::Timestamp { precision: None }
2211 .interesting_datums()
2212 .collect();
2213 let datum = pool[pick.index(pool.len())];
2214 binary(cmp, add, lit(datum, ReprScalarType::Timestamp))
2215 })
2216 }
2217
2218 fn arb_float_mul_pred() -> impl Strategy<Value = MirScalarExpr> {
2222 let consts = || select(vec![0.0f64, 1.0, -1.0, 1e300, -1e300, f64::INFINITY]);
2223 (comparison_funcs(), consts(), consts()).prop_map(|(cmp, a, c)| {
2224 let mul = binary(
2225 BinaryFunc::MulFloat64(MulFloat64),
2226 MirScalarExpr::column(F64),
2227 lit(Datum::from(a), ReprScalarType::Float64),
2228 );
2229 binary(cmp, mul, lit(Datum::from(c), ReprScalarType::Float64))
2230 })
2231 }
2232
2233 fn arb_temporal_pred() -> impl Strategy<Value = MirScalarExpr> {
2237 let cmps = select(vec![
2238 BinaryFunc::Lte(Lte),
2239 BinaryFunc::Lt(Lt),
2240 BinaryFunc::Gte(Gte),
2241 BinaryFunc::Gt(Gt),
2242 ]);
2243 (cmps, any::<bool>(), any::<Index>()).prop_map(|(cmp, use_col, pick)| {
2244 let mz_now = MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow);
2245 let rhs = if use_col {
2246 MirScalarExpr::column(MZTS)
2247 } else {
2248 let pool = wide_pool(MZTS);
2249 lit(pool[pick.index(pool.len())], ReprScalarType::MzTimestamp)
2250 };
2251 binary(cmp, mz_now, rhs)
2252 })
2253 }
2254
2255 fn arb_wide_predicate() -> impl Strategy<Value = MirScalarExpr> {
2256 let leaf = Union::new(vec![
2257 arb_cmp_col_lit().boxed(),
2258 arb_is_null_pred().boxed(),
2259 arb_jsonb_pred().boxed(),
2260 arb_case_jsonb_pred().boxed(),
2261 arb_iso_parse_pred().boxed(),
2262 arb_ts_interval_pred().boxed(),
2263 arb_float_mul_pred().boxed(),
2264 arb_temporal_pred().boxed(),
2265 (
2268 select(vec![0i32, 2, -5, 24699]),
2269 f32_consts(),
2270 f32_consts(),
2271 f32_consts(),
2272 comparison_funcs(),
2273 )
2274 .prop_map(|(s, a, b, c, cmp)| float_arith_predicate(s, a, b, c, cmp))
2275 .boxed(),
2276 (select(vec![0u64, 1, 2, 100, u64::MAX]), comparison_funcs())
2277 .prop_map(|(ts, cmp)| cast_mz_timestamp_predicate(ts, cmp))
2278 .boxed(),
2279 ])
2280 .boxed();
2281 Union::new_weighted(vec![
2282 (3, leaf.clone()),
2283 (
2284 1,
2285 (leaf.clone(), leaf.clone(), any::<bool>())
2286 .prop_map(|(a, b, is_and)| {
2287 let func = if is_and { And.into() } else { Or.into() };
2288 MirScalarExpr::CallVariadic {
2289 func,
2290 exprs: vec![a, b],
2291 }
2292 })
2293 .boxed(),
2294 ),
2295 (1, leaf.prop_map(not).boxed()),
2296 ])
2297 }
2298
2299 #[mz_ore::test]
2305 #[cfg_attr(miri, ignore)] fn zero_column_relation_replace_with() {
2307 let desc = RelationDesc::empty();
2308 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
2309 let ok_rows = vec![
2310 SourceData(Ok(Row::default())),
2311 SourceData(Ok(Row::default())),
2312 ];
2313
2314 let plan = MapFilterProject::new(0).into_plan().expect("into_plan");
2317 let part_stats = build_part_stats(&desc, &ok_rows);
2318 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2319 let decision = filter_result(&desc, ResultSpec::anything(), stats, &plan);
2320 assert!(
2321 matches!(decision, FilterResult::ReplaceWith { .. }),
2322 "expected ReplaceWith, got {decision:?}",
2323 );
2324
2325 let mixed_rows = vec![
2328 SourceData(Ok(Row::default())),
2329 SourceData(Err(DataflowError::from(EvalError::DivisionByZero))),
2330 ];
2331 let part_stats = build_part_stats(&desc, &mixed_rows);
2332 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2333 let decision = filter_result(&desc, ResultSpec::anything(), stats, &plan);
2334 assert!(
2335 matches!(decision, FilterResult::Keep),
2336 "expected Keep, got {decision:?}",
2337 );
2338
2339 let plan = MapFilterProject::new(0)
2341 .filter(std::iter::once(MirScalarExpr::literal_ok(
2342 Datum::False,
2343 ReprScalarType::Bool,
2344 )))
2345 .into_plan()
2346 .expect("into_plan");
2347 let part_stats = build_part_stats(&desc, &ok_rows);
2348 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2349 let decision = filter_result(&desc, ResultSpec::anything(), stats, &plan);
2350 assert!(
2351 matches!(decision, FilterResult::Discard),
2352 "expected Discard, got {decision:?}",
2353 );
2354 }
2355
2356 #[mz_ore::test]
2364 #[cfg_attr(miri, ignore)] fn schema_drift_degrades_to_no_stats() {
2366 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
2367
2368 let write_desc = RelationDesc::builder()
2370 .with_column("a", SqlScalarType::Int32.nullable(false))
2371 .finish();
2372 let rows = vec![SourceData(Ok(Row::pack_slice(&[Datum::Int32(1)])))];
2373 let part_stats = build_part_stats(&write_desc, &rows);
2374
2375 let read_desc = RelationDesc::builder()
2376 .with_column("a", SqlScalarType::Int32.nullable(false))
2377 .with_column("b", SqlScalarType::Float64.nullable(true))
2378 .finish();
2379 let stats = RelationPartStats::new("test", &metrics, &read_desc, &part_stats);
2380 let plan = MapFilterProject::new(2)
2383 .filter(std::iter::once(is_null(MirScalarExpr::column(1))))
2384 .into_plan()
2385 .expect("into_plan");
2386 let decision = filter_result(&read_desc, ResultSpec::anything(), stats, &plan);
2387 assert!(
2388 !matches!(decision, FilterResult::Discard),
2389 "part written before ADD COLUMN was discarded: {decision:?}",
2390 );
2391
2392 let write_desc = RelationDesc::builder()
2396 .with_column("a", SqlScalarType::Int32.nullable(false))
2397 .with_column("b", SqlScalarType::Float64.nullable(true))
2398 .finish();
2399 let rows = vec![SourceData(Ok(Row::pack_slice(&[
2400 Datum::Int32(1),
2401 Datum::from(5.0f64),
2402 ])))];
2403 let part_stats = build_part_stats(&write_desc, &rows);
2404
2405 let read_desc = RelationDesc::builder()
2406 .with_column("b", SqlScalarType::Float64.nullable(true))
2407 .finish();
2408 let stats = RelationPartStats::new("test", &metrics, &read_desc, &part_stats);
2409 let plan = MapFilterProject::new(1)
2410 .filter(std::iter::once(binary(
2411 BinaryFunc::Eq(Eq),
2412 MirScalarExpr::column(0),
2413 lit(Datum::from(5.0f64), ReprScalarType::Float64),
2414 )))
2415 .into_plan()
2416 .expect("into_plan");
2417 let decision = filter_result(&read_desc, ResultSpec::anything(), stats, &plan);
2418 assert!(
2419 !matches!(decision, FilterResult::Discard),
2420 "projected read desc discarded a matching part: {decision:?}",
2421 );
2422 }
2423
2424 fn part_yields_output(
2431 plan: &MfpPlan,
2432 rows: &[SourceData],
2433 eval_time: Timestamp,
2434 until: &Antichain<Timestamp>,
2435 ) -> bool {
2436 if until.less_equal(&eval_time) {
2437 return false;
2438 }
2439 let arena = RowArena::new();
2440 let mut row_builder = Row::default();
2441 for source_data in rows {
2442 match &source_data.0 {
2443 Err(_) => return true,
2444 Ok(row) => {
2445 let mut datums: Vec<Datum> = row.iter().collect();
2446 let mut results = plan.evaluate::<DataflowError, _>(
2447 &mut datums,
2448 &arena,
2449 eval_time,
2450 Diff::from(1),
2451 |time| !until.less_equal(time),
2452 &mut row_builder,
2453 );
2454 if results.next().is_some() {
2455 return true;
2456 }
2457 }
2458 }
2459 }
2460 false
2461 }
2462
2463 #[mz_ore::test]
2464 #[cfg_attr(miri, ignore)] fn wide_filter_result_never_discards_matching_part() {
2466 fn check(
2467 rows: Vec<SourceData>,
2468 predicate: MirScalarExpr,
2469 eval_time: u64,
2470 until: Option<u64>,
2471 ) -> Result<(), TestCaseError> {
2472 let desc = wide_desc();
2473 let Ok(plan) = MapFilterProject::new(desc.arity())
2477 .filter(std::iter::once(predicate))
2478 .into_plan()
2479 else {
2480 return Ok(());
2481 };
2482 let eval_time = Timestamp::from(eval_time);
2483 let until =
2484 until.map_or_else(Antichain::new, |t| Antichain::from_elem(Timestamp::from(t)));
2485
2486 let part_stats = build_part_stats(&desc, &rows);
2487 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
2488 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2489
2490 let upper = until.as_option().copied().unwrap_or(Timestamp::MAX);
2495 if eval_time > upper {
2496 return Ok(());
2497 }
2498 let time_range = ResultSpec::value_between(
2499 Datum::MzTimestamp(eval_time),
2500 Datum::MzTimestamp(upper),
2501 );
2502 let decision = filter_result(&desc, time_range, stats, &plan);
2503
2504 if part_yields_output(&plan, &rows, eval_time, &until) {
2505 prop_assert!(
2506 !matches!(decision, FilterResult::Discard),
2507 "filter pushdown discarded a part whose MFP yields output on a real \
2508 row (wrongly-skipped part; the runtime audit would panic).\n\
2509 rows={rows:?}\nplan={plan:?}\neval_time={eval_time}\nuntil={until:?}",
2510 );
2511 }
2512 Ok(())
2513 }
2514
2515 let default = ProptestConfig::default();
2522 let cases = if std::env::var_os("PROPTEST_CASES").is_some() {
2523 default.cases
2524 } else {
2525 4096
2526 };
2527 let config = ProptestConfig { cases, ..default };
2528 proptest!(config, |(
2529 rows in arb_wide_rows(),
2530 predicate in arb_wide_predicate(),
2531 eval_time in select(vec![1u64, 5]),
2532 until in select(vec![None, Some(1u64), Some(5), Some(8), Some(100)]),
2533 )| {
2534 check(rows, predicate, eval_time, until)?;
2535 });
2536 }
2537
2538 #[mz_ore::test]
2544 #[cfg_attr(miri, ignore)] fn multi_part_decisions_are_independent() {
2546 fn check(
2547 parts: Vec<Vec<SourceData>>,
2548 predicate: MirScalarExpr,
2549 eval_time: u64,
2550 until: Option<u64>,
2551 ) -> Result<(), TestCaseError> {
2552 let desc = wide_desc();
2553 let Ok(plan) = MapFilterProject::new(desc.arity())
2554 .filter(std::iter::once(predicate))
2555 .into_plan()
2556 else {
2557 return Ok(());
2558 };
2559 let eval_time = Timestamp::from(eval_time);
2560 let until =
2561 until.map_or_else(Antichain::new, |t| Antichain::from_elem(Timestamp::from(t)));
2562 let upper = until.as_option().copied().unwrap_or(Timestamp::MAX);
2563 if eval_time > upper {
2564 return Ok(());
2565 }
2566 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
2567
2568 for rows in &parts {
2569 let part_stats = build_part_stats(&desc, rows);
2570 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2571 let time_range = ResultSpec::value_between(
2572 Datum::MzTimestamp(eval_time),
2573 Datum::MzTimestamp(upper),
2574 );
2575 let decision = filter_result(&desc, time_range, stats, &plan);
2576 if part_yields_output(&plan, rows, eval_time, &until) {
2577 prop_assert!(
2578 !matches!(decision, FilterResult::Discard),
2579 "filter pushdown discarded a part whose MFP yields output on a \
2580 real row.\nrows={rows:?}\nplan={plan:?}\neval_time={eval_time}\n\
2581 until={until:?}",
2582 );
2583 }
2584 }
2585 Ok(())
2586 }
2587
2588 let default = ProptestConfig::default();
2589 let cases = if std::env::var_os("PROPTEST_CASES").is_some() {
2590 default.cases
2591 } else {
2592 1024
2593 };
2594 let config = ProptestConfig { cases, ..default };
2595 proptest!(config, |(
2596 parts in prop::collection::vec(arb_wide_rows(), 2..4),
2597 predicate in arb_wide_predicate(),
2598 eval_time in select(vec![1u64, 5]),
2599 until in select(vec![None, Some(5u64), Some(100)]),
2600 )| {
2601 check(parts, predicate, eval_time, until)?;
2602 });
2603 }
2604 }
2605}