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