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 const EMPTY_STAMP: &str = "message stamped with no capabilities";
591 let capabilities = [
594 time.retain_least(0).expect(EMPTY_STAMP),
595 time.retain_least(1).expect(EMPTY_STAMP),
596 ];
597 let panic_on_audit_failure = panic_on_audit_failure.get();
598 for blob in data.drain(..) {
599 pending_work.push_back(PendingWork {
600 panic_on_audit_failure,
601 capabilities: capabilities.clone(),
602 part: PendingPart::Unparsed(blob),
603 });
604 }
605 });
606
607 let yield_fuel = cfg.storage_source_decode_fuel();
609 let mut work = 0;
610 let mut ok_output = ok_output.activate();
611 let mut err_output = err_output.activate();
612 while let Some(front) = pending_work.front_mut() {
613 if work >= yield_fuel {
614 break;
615 }
616 let cap_time = *front.capabilities[0].time();
617 let mut ok_session = ok_output.session_with_builder(&front.capabilities[0]);
620 let mut err_session = err_output.session_with_builder(&front.capabilities[1]);
621 let done = decode_part(
622 &mut front.part,
623 front.panic_on_audit_failure,
624 cap_time,
625 &name,
626 &until,
627 map_filter_project.as_ref(),
628 &mut datum_vec,
629 &mut row_builder,
630 &mut work,
631 yield_fuel,
632 |record, time, diff| match record {
633 Ok(row) => ok_session.give((row.into_owned(), record_time(time), diff)),
634 Err(err) => err_session.give((err, record_time(time), diff)),
635 },
636 );
637 drop(ok_session);
638 drop(err_session);
639 if done {
640 pending_work.pop_front();
641 }
642 }
643 if !pending_work.is_empty() {
644 activator.activate();
645 }
646 }
647 });
648
649 (ok_stream, err_stream)
650}
651
652struct PendingWork {
654 panic_on_audit_failure: bool,
656 capabilities: [Capability<RefinedTime>; 2],
658 part: PendingPart,
660}
661
662enum PendingPart {
663 Unparsed(FetchedBlob<SourceData, (), Timestamp, StorageDiff>),
664 Parsed {
665 part: ShardSourcePart<SourceData, (), Timestamp, StorageDiff>,
666 },
667}
668
669impl PendingPart {
670 fn part_mut(&mut self) -> &mut FetchedPart<SourceData, (), Timestamp, StorageDiff> {
677 match self {
678 PendingPart::Unparsed(x) => {
679 *self = PendingPart::Parsed { part: x.parse() };
680 self.part_mut()
682 }
683 PendingPart::Parsed { part } => &mut part.part,
684 }
685 }
686}
687
688fn decode_part<E, F>(
694 part: &mut PendingPart,
695 panic_on_audit_failure: bool,
696 cap_time: RefinedTime,
697 name: &str,
698 until: &Antichain<Timestamp>,
699 map_filter_project: Option<&MfpPlan>,
700 datum_vec: &mut DatumVec,
701 row_builder: &mut Row,
702 work: &mut usize,
703 yield_fuel: usize,
704 mut give: F,
705) -> bool
706where
707 E: timely::ExchangeData + Ord + Clone + Debug + From<DataflowError> + From<EvalError>,
708 F: FnMut(Result<Cow<'_, Row>, E>, RefinedTime, Diff),
709{
710 let fetched_part = part.part_mut();
711 let is_filter_pushdown_audit = fetched_part.is_filter_pushdown_audit();
712 let mut row_buf = None;
713 while let Some(((key, val), time, diff)) =
714 fetched_part.next_with_storage(&mut row_buf, &mut None)
715 {
716 if until.less_equal(&time) {
717 continue;
718 }
719 match (key, val) {
720 (SourceData(Ok(row)), ()) => {
721 if let Some(mfp) = map_filter_project {
722 *work += 1;
729 let arena = mz_repr::RowArena::new();
730 let mut datums_local = datum_vec.borrow_with(&row);
731 for result in mfp.evaluate(
732 &mut datums_local,
733 &arena,
734 time,
735 diff.into(),
736 |time| !until.less_equal(time),
737 row_builder,
738 ) {
739 if let Some(stats) = &is_filter_pushdown_audit {
743 sentry::with_scope(
747 |scope| {
748 scope.set_tag("alert_id", "persist_pushdown_audit_violation")
749 },
750 || {
751 error!(
752 ?stats,
753 name,
754 mfp = ?redact(&mfp),
755 result = ?redact(&result),
756 "persist filter pushdown correctness violation!"
757 );
758 if panic_on_audit_failure {
759 panic!(
760 "persist filter pushdown correctness violation! {}",
761 name
762 );
763 }
764 },
765 );
766 }
767 match result {
768 Ok((row, time, diff)) => {
769 if !until.less_equal(&time) {
771 let mut emit_time = cap_time;
772 emit_time.0 = time;
773 give(Ok(Cow::Owned(row)), emit_time, diff);
774 *work += 1;
775 }
776 }
777 Err((err, time, diff)) => {
778 if !until.less_equal(&time) {
780 let mut emit_time = cap_time;
781 emit_time.0 = time;
782 give(Err(err), emit_time, diff);
783 *work += 1;
784 }
785 }
786 }
787 }
788 drop(datums_local);
791 row_buf.replace(SourceData(Ok(row)));
792 } else {
793 let mut emit_time = cap_time;
794 emit_time.0 = time;
795 give(Ok(Cow::Borrowed(&row)), emit_time, diff.into());
797 row_buf.replace(SourceData(Ok(row)));
798 *work += 1;
799 }
800 }
801 (SourceData(Err(err)), ()) => {
802 if let Some(stats) = &is_filter_pushdown_audit {
808 sentry::with_scope(
809 |scope| scope.set_tag("alert_id", "persist_pushdown_audit_violation"),
810 || {
811 error!(
817 ?stats,
818 name,
819 err = ?redact(&err),
820 "persist filter pushdown correctness violation!"
821 );
822 if panic_on_audit_failure {
823 panic!("persist filter pushdown correctness violation! {}", name);
824 }
825 },
826 );
827 }
828 let mut emit_time = cap_time;
829 emit_time.0 = time;
830 give(Err(E::from(err)), emit_time, diff.into());
831 *work += 1;
832 }
833 }
834 if *work >= yield_fuel {
835 return false;
836 }
837 }
838 true
839}
840
841pub trait Backpressureable: Clone + 'static {
843 fn byte_size(&self) -> usize;
845}
846
847impl<T: Clone + 'static> Backpressureable for (usize, ExchangeableBatchPart<T>) {
848 fn byte_size(&self) -> usize {
849 self.1.encoded_size_bytes()
850 }
851}
852
853#[derive(Debug)]
855pub struct FlowControl<'scope, T: timely::progress::Timestamp> {
856 pub progress_stream: StreamVec<'scope, T, Infallible>,
862 pub max_inflight_bytes: usize,
864 pub summary: T::Summary,
867
868 pub metrics: Option<BackpressureOperatorMetrics>,
870}
871
872pub fn backpressure<'scope, T, O>(
885 scope: Scope<'scope, (T, Subtime)>,
886 name: &str,
887 data: StreamVec<'scope, (T, Subtime), O>,
888 flow_control: FlowControl<'scope, (T, Subtime)>,
889 chosen_worker: usize,
890 probe: Option<UnboundedSender<(Antichain<(T, Subtime)>, usize, usize)>>,
892) -> (StreamVec<'scope, (T, Subtime), O>, PressOnDropButton)
893where
894 T: TimelyTimestamp + Lattice + Codec64 + TotalOrder,
895 O: Backpressureable + std::fmt::Debug,
896{
897 let worker_index = scope.index();
898
899 let (flow_control_stream, flow_control_max_bytes, metrics) = (
900 flow_control.progress_stream,
901 flow_control.max_inflight_bytes,
902 flow_control.metrics,
903 );
904
905 let (handle, summaried_flow) = scope.feedback(flow_control.summary.clone());
910 flow_control_stream.connect_loop(handle);
911
912 let mut builder = AsyncOperatorBuilder::new(
913 format!("persist_source_backpressure({})", name),
914 scope.clone(),
915 );
916 let (data_output, data_stream) = builder.new_output::<CapacityContainerBuilder<Vec<_>>>();
917
918 let mut data_input = builder.new_disconnected_input(data, Pipeline);
919 let mut flow_control_input = builder.new_disconnected_input(summaried_flow, Pipeline);
920
921 fn synthesize_frontiers<T: PartialOrder + Clone>(
923 mut frontier: Antichain<(T, Subtime)>,
924 mut time: (T, Subtime),
925 part_number: &mut u64,
926 ) -> (
927 (T, Subtime),
928 Antichain<(T, Subtime)>,
929 Antichain<(T, Subtime)>,
930 ) {
931 let mut next_frontier = frontier.clone();
932 time.1 = Subtime(*part_number);
933 frontier.insert(time.clone());
934 *part_number += 1;
935 let mut next_time = time.clone();
936 next_time.1 = Subtime(*part_number);
937 next_frontier.insert(next_time);
938 (time, frontier, next_frontier)
939 }
940
941 let data_input = async_stream::stream!({
944 let mut part_number = 0;
945 let mut parts: Vec<((T, Subtime), O)> = Vec::new();
946 loop {
947 match data_input.next().await {
948 None => {
949 let empty = Antichain::new();
950 parts.sort_by_key(|val| val.0.clone());
951 for (part_time, d) in parts.drain(..) {
952 let (part_time, frontier, next_frontier) = synthesize_frontiers(
953 empty.clone(),
954 part_time.clone(),
955 &mut part_number,
956 );
957 yield Either::Right((part_time, d, frontier, next_frontier))
958 }
959 break;
960 }
961 Some(Event::Data(stamp, data)) => {
962 let time = stamp.least().expect("message stamped with no capabilities");
965 for d in data {
966 parts.push((time.clone(), d));
967 }
968 }
969 Some(Event::Progress(prog)) => {
970 parts.sort_by_key(|val| val.0.clone());
971 for (part_time, d) in parts.extract_if(.., |p| !prog.less_equal(&p.0)) {
972 let (part_time, frontier, next_frontier) =
973 synthesize_frontiers(prog.clone(), part_time.clone(), &mut part_number);
974 yield Either::Right((part_time, d, frontier, next_frontier))
975 }
976 yield Either::Left(prog)
977 }
978 }
979 }
980 });
981 let shutdown_button = builder.build(move |caps| async move {
982 let mut cap_set = CapabilitySet::from_elem(caps.into_element());
984
985 let mut output_frontier = Antichain::from_elem(TimelyTimestamp::minimum());
987 let mut flow_control_frontier = Antichain::from_elem(TimelyTimestamp::minimum());
989
990 let mut inflight_parts = Vec::new();
992 let mut pending_parts = std::collections::VecDeque::new();
994
995 if worker_index != chosen_worker {
997 trace!(
998 "We are not the chosen worker ({}), exiting...",
999 chosen_worker
1000 );
1001 return;
1002 }
1003 tokio::pin!(data_input);
1004 'emitting_parts: loop {
1005 let inflight_bytes: usize = inflight_parts.iter().map(|(_, size)| size).sum();
1008
1009 if inflight_bytes < flow_control_max_bytes
1017 || !PartialOrder::less_equal(&flow_control_frontier, &output_frontier)
1018 {
1019 let (time, part, next_frontier) =
1020 if let Some((time, part, next_frontier)) = pending_parts.pop_front() {
1021 (time, part, next_frontier)
1022 } else {
1023 match data_input.next().await {
1024 Some(Either::Right((time, part, frontier, next_frontier))) => {
1025 output_frontier = frontier;
1030 cap_set.downgrade(output_frontier.iter());
1031
1032 if inflight_bytes >= flow_control_max_bytes
1037 && !PartialOrder::less_than(
1038 &output_frontier,
1039 &flow_control_frontier,
1040 )
1041 {
1042 pending_parts.push_back((time, part, next_frontier));
1043 continue 'emitting_parts;
1044 }
1045 (time, part, next_frontier)
1046 }
1047 Some(Either::Left(prog)) => {
1048 output_frontier = prog;
1049 cap_set.downgrade(output_frontier.iter());
1050 continue 'emitting_parts;
1051 }
1052 None => {
1053 if pending_parts.is_empty() {
1054 break 'emitting_parts;
1055 } else {
1056 continue 'emitting_parts;
1057 }
1058 }
1059 }
1060 };
1061
1062 let byte_size = part.byte_size();
1063 if let Some(emission_ts) = flow_control.summary.results_in(&time) {
1073 inflight_parts.push((emission_ts, byte_size));
1074 }
1075
1076 data_output.give(&cap_set.delayed(&time), part);
1079
1080 if let Some(metrics) = &metrics {
1081 metrics.emitted_bytes.inc_by(u64::cast_from(byte_size))
1082 }
1083
1084 output_frontier = next_frontier;
1085 cap_set.downgrade(output_frontier.iter())
1086 } else {
1087 if let Some(metrics) = &metrics {
1088 metrics
1089 .last_backpressured_bytes
1090 .set(u64::cast_from(inflight_bytes))
1091 }
1092 let parts_count = inflight_parts.len();
1093 let new_flow_control_frontier = match flow_control_input.next().await {
1098 Some(Event::Progress(frontier)) => frontier,
1099 Some(Event::Data(_, _)) => {
1100 unreachable!("flow_control_input should not contain data")
1101 }
1102 None => Antichain::new(),
1103 };
1104
1105 flow_control_frontier.clone_from(&new_flow_control_frontier);
1107
1108 let retired_parts = inflight_parts
1110 .extract_if(.., |(ts, _size)| !flow_control_frontier.less_equal(ts));
1111 let (retired_size, retired_count): (usize, usize) = retired_parts
1112 .fold((0, 0), |(accum_size, accum_count), (_ts, size)| {
1113 (accum_size + size, accum_count + 1)
1114 });
1115 trace!(
1116 "returning {} parts with {} bytes, frontier: {:?}",
1117 retired_count, retired_size, flow_control_frontier,
1118 );
1119
1120 if let Some(metrics) = &metrics {
1121 metrics.retired_bytes.inc_by(u64::cast_from(retired_size))
1122 }
1123
1124 if let Some(probe) = probe.as_ref() {
1126 let _ = probe.send((new_flow_control_frontier, parts_count, retired_count));
1127 }
1128 }
1129 }
1130 });
1131 (data_stream, shutdown_button.press_on_drop())
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136 use timely::container::CapacityContainerBuilder;
1137 use timely::dataflow::operators::{Enter, Probe};
1138 use tokio::sync::mpsc::unbounded_channel;
1139 use tokio::sync::oneshot;
1140
1141 use super::*;
1142
1143 #[mz_ore::test]
1144 fn test_backpressure_non_granular() {
1145 use Step::*;
1146 backpressure_runner(
1147 vec![(50, Part(101)), (50, Part(102)), (100, Part(1))],
1148 100,
1149 (1, Subtime(0)),
1150 vec![
1151 AssertOutputFrontier((50, Subtime(2))),
1154 AssertBackpressured {
1155 frontier: (1, Subtime(0)),
1156 inflight_parts: 1,
1157 retired_parts: 0,
1158 },
1159 AssertBackpressured {
1160 frontier: (51, Subtime(0)),
1161 inflight_parts: 1,
1162 retired_parts: 0,
1163 },
1164 ProcessXParts(2),
1165 AssertBackpressured {
1166 frontier: (101, Subtime(0)),
1167 inflight_parts: 2,
1168 retired_parts: 2,
1169 },
1170 AssertOutputFrontier((100, Subtime(3))),
1173 ],
1174 true,
1175 );
1176
1177 backpressure_runner(
1178 vec![
1179 (50, Part(10)),
1180 (50, Part(10)),
1181 (51, Part(100)),
1182 (52, Part(1000)),
1183 ],
1184 50,
1185 (1, Subtime(0)),
1186 vec![
1187 AssertOutputFrontier((51, Subtime(3))),
1189 AssertBackpressured {
1190 frontier: (1, Subtime(0)),
1191 inflight_parts: 3,
1192 retired_parts: 0,
1193 },
1194 ProcessXParts(3),
1195 AssertBackpressured {
1196 frontier: (52, Subtime(0)),
1197 inflight_parts: 3,
1198 retired_parts: 2,
1199 },
1200 AssertBackpressured {
1201 frontier: (53, Subtime(0)),
1202 inflight_parts: 1,
1203 retired_parts: 1,
1204 },
1205 AssertOutputFrontier((52, Subtime(4))),
1208 ],
1209 true,
1210 );
1211
1212 backpressure_runner(
1213 vec![
1214 (50, Part(98)),
1215 (50, Part(1)),
1216 (51, Part(10)),
1217 (52, Part(100)),
1218 (52, Part(10)),
1220 (52, Part(10)),
1221 (52, Part(10)),
1222 (52, Part(100)),
1223 (100, Part(100)),
1225 ],
1226 100,
1227 (1, Subtime(0)),
1228 vec![
1229 AssertOutputFrontier((51, Subtime(3))),
1230 AssertBackpressured {
1234 frontier: (1, Subtime(0)),
1235 inflight_parts: 3,
1236 retired_parts: 0,
1237 },
1238 AssertBackpressured {
1239 frontier: (51, Subtime(0)),
1240 inflight_parts: 3,
1241 retired_parts: 0,
1242 },
1243 ProcessXParts(1),
1244 AssertOutputFrontier((51, Subtime(3))),
1247 ProcessXParts(1),
1251 AssertOutputFrontier((52, Subtime(4))),
1252 AssertBackpressured {
1253 frontier: (52, Subtime(0)),
1254 inflight_parts: 3,
1255 retired_parts: 2,
1256 },
1257 ProcessXParts(1),
1261 AssertBackpressured {
1265 frontier: (53, Subtime(0)),
1266 inflight_parts: 2,
1267 retired_parts: 1,
1268 },
1269 ProcessXParts(5),
1271 AssertBackpressured {
1272 frontier: (101, Subtime(0)),
1273 inflight_parts: 5,
1274 retired_parts: 5,
1275 },
1276 AssertOutputFrontier((100, Subtime(9))),
1277 ],
1278 true,
1279 );
1280 }
1281
1282 #[mz_ore::test]
1283 fn test_backpressure_granular() {
1284 use Step::*;
1285 backpressure_runner(
1286 vec![(50, Part(101)), (50, Part(101))],
1287 100,
1288 (0, Subtime(1)),
1289 vec![
1290 AssertOutputFrontier((50, Subtime(1))),
1292 AssertBackpressured {
1295 frontier: (0, Subtime(1)),
1296 inflight_parts: 1,
1297 retired_parts: 0,
1298 },
1299 AssertBackpressured {
1300 frontier: (50, Subtime(1)),
1301 inflight_parts: 1,
1302 retired_parts: 0,
1303 },
1304 ProcessXParts(1),
1306 AssertBackpressured {
1308 frontier: (50, Subtime(2)),
1309 inflight_parts: 1,
1310 retired_parts: 1,
1311 },
1312 AssertOutputFrontier((50, Subtime(2))),
1314 ],
1315 false,
1316 );
1317
1318 backpressure_runner(
1319 vec![
1320 (50, Part(10)),
1321 (50, Part(10)),
1322 (51, Part(35)),
1323 (52, Part(100)),
1324 ],
1325 50,
1326 (0, Subtime(1)),
1327 vec![
1328 AssertOutputFrontier((51, Subtime(3))),
1330 AssertBackpressured {
1331 frontier: (0, Subtime(1)),
1332 inflight_parts: 3,
1333 retired_parts: 0,
1334 },
1335 AssertBackpressured {
1336 frontier: (50, Subtime(1)),
1337 inflight_parts: 3,
1338 retired_parts: 0,
1339 },
1340 ProcessXParts(1),
1342 AssertBackpressured {
1343 frontier: (50, Subtime(2)),
1344 inflight_parts: 3,
1345 retired_parts: 1,
1346 },
1347 AssertOutputFrontier((52, Subtime(4))),
1350 ProcessXParts(2),
1351 AssertBackpressured {
1352 frontier: (52, Subtime(4)),
1353 inflight_parts: 3,
1354 retired_parts: 2,
1355 },
1356 ],
1357 false,
1358 );
1359 }
1360
1361 type Time = (u64, Subtime);
1362 #[derive(Clone, Debug)]
1363 struct Part(usize);
1364 impl Backpressureable for Part {
1365 fn byte_size(&self) -> usize {
1366 self.0
1367 }
1368 }
1369
1370 enum Step {
1372 AssertOutputFrontier(Time),
1375 AssertBackpressured {
1379 frontier: Time,
1380 inflight_parts: usize,
1381 retired_parts: usize,
1382 },
1383 ProcessXParts(usize),
1385 }
1386
1387 fn backpressure_runner(
1389 input: Vec<(u64, Part)>,
1391 max_inflight_bytes: usize,
1393 summary: Time,
1395 steps: Vec<Step>,
1397 non_granular_consumer: bool,
1400 ) {
1401 timely::execute::execute_directly(move |worker| {
1402 let (
1403 backpressure_probe,
1404 consumer_tx,
1405 mut backpressure_status_rx,
1406 finalizer_tx,
1407 _token,
1408 ) =
1409 worker.dataflow::<u64, _, _>(|outer_scope| {
1411 let (non_granular_feedback_handle, non_granular_feedback) =
1412 if non_granular_consumer {
1413 let (h, f) = outer_scope.feedback(Default::default());
1414 (Some(h), Some(f))
1415 } else {
1416 (None, None)
1417 };
1418 let (
1419 backpressure_probe,
1420 consumer_tx,
1421 backpressure_status_rx,
1422 token,
1423 backpressured,
1424 finalizer_tx,
1425 ) = outer_scope.scoped::<(u64, Subtime), _, _>("hybrid", |scope| {
1426 let (input, finalizer_tx) =
1427 iterator_operator(scope.clone(), input.into_iter());
1428
1429 let (flow_control, granular_feedback_handle) = if non_granular_consumer {
1430 (
1431 FlowControl {
1432 progress_stream: non_granular_feedback.unwrap().enter(scope),
1433 max_inflight_bytes,
1434 summary,
1435 metrics: None
1436 },
1437 None,
1438 )
1439 } else {
1440 let (granular_feedback_handle, granular_feedback) =
1441 scope.feedback(Default::default());
1442 (
1443 FlowControl {
1444 progress_stream: granular_feedback,
1445 max_inflight_bytes,
1446 summary,
1447 metrics: None,
1448 },
1449 Some(granular_feedback_handle),
1450 )
1451 };
1452
1453 let (backpressure_status_tx, backpressure_status_rx) = unbounded_channel();
1454
1455 let (backpressured, token) = backpressure(
1456 scope,
1457 "test",
1458 input,
1459 flow_control,
1460 0,
1461 Some(backpressure_status_tx),
1462 );
1463
1464 let tx = if !non_granular_consumer {
1466 Some(consumer_operator(
1467 scope.clone(),
1468 backpressured.clone(),
1469 granular_feedback_handle.unwrap(),
1470 ))
1471 } else {
1472 None
1473 };
1474
1475 let (probe_handle, backpressured) = backpressured.probe();
1476 (
1477 probe_handle,
1478 tx,
1479 backpressure_status_rx,
1480 token,
1481 backpressured.leave(outer_scope),
1482 finalizer_tx,
1483 )
1484 });
1485
1486 let consumer_tx = if non_granular_consumer {
1488 consumer_operator(
1489 outer_scope.clone(),
1490 backpressured,
1491 non_granular_feedback_handle.unwrap(),
1492 )
1493 } else {
1494 consumer_tx.unwrap()
1495 };
1496
1497 (
1498 backpressure_probe,
1499 consumer_tx,
1500 backpressure_status_rx,
1501 finalizer_tx,
1502 token,
1503 )
1504 });
1505
1506 use Step::*;
1507 for step in steps {
1508 match step {
1509 AssertOutputFrontier(time) => {
1510 eprintln!("checking advance to {time:?}");
1511 backpressure_probe.with_frontier(|front| {
1512 eprintln!("current backpressure output frontier: {front:?}");
1513 });
1514 while backpressure_probe.less_than(&time) {
1515 worker.step();
1516 backpressure_probe.with_frontier(|front| {
1517 eprintln!("current backpressure output frontier: {front:?}");
1518 });
1519 std::thread::sleep(std::time::Duration::from_millis(25));
1520 }
1521 }
1522 ProcessXParts(parts) => {
1523 eprintln!("processing {parts:?} parts");
1524 for _ in 0..parts {
1525 consumer_tx.send(()).unwrap();
1526 }
1527 }
1528 AssertBackpressured {
1529 frontier,
1530 inflight_parts,
1531 retired_parts,
1532 } => {
1533 let frontier = Antichain::from_elem(frontier);
1534 eprintln!(
1535 "asserting backpressured at {frontier:?}, with {inflight_parts:?} inflight parts \
1536 and {retired_parts:?} retired"
1537 );
1538 let (new_frontier, new_count, new_retired_count) = loop {
1539 if let Ok(val) = backpressure_status_rx.try_recv() {
1540 break val;
1541 }
1542 worker.step();
1543 std::thread::sleep(std::time::Duration::from_millis(25));
1544 };
1545 assert_eq!(
1546 (frontier, inflight_parts, retired_parts),
1547 (new_frontier, new_count, new_retired_count)
1548 );
1549 }
1550 }
1551 }
1552 let _ = finalizer_tx.send(());
1554 });
1555 }
1556
1557 fn iterator_operator<'scope, I: Iterator<Item = (u64, Part)> + 'static>(
1560 scope: Scope<'scope, (u64, Subtime)>,
1561 mut input: I,
1562 ) -> (StreamVec<'scope, (u64, Subtime), Part>, oneshot::Sender<()>) {
1563 let (finalizer_tx, finalizer_rx) = oneshot::channel();
1564 let mut iterator = AsyncOperatorBuilder::new("iterator".to_string(), scope);
1565 let (output_handle, output) = iterator.new_output::<CapacityContainerBuilder<Vec<Part>>>();
1566
1567 iterator.build(|mut caps| async move {
1568 let mut capability = Some(caps.pop().unwrap());
1569 let mut last = None;
1570 while let Some(element) = input.next() {
1571 let time = element.0.clone();
1572 let part = element.1;
1573 last = Some((time, Subtime(0)));
1574 output_handle.give(&capability.as_ref().unwrap().delayed(&last.unwrap()), part);
1575 }
1576 if let Some(last) = last {
1577 capability
1578 .as_mut()
1579 .unwrap()
1580 .downgrade(&(last.0 + 1, last.1));
1581 }
1582
1583 let _ = finalizer_rx.await;
1584 capability.take();
1585 });
1586
1587 (output, finalizer_tx)
1588 }
1589
1590 fn consumer_operator<
1594 'scope,
1595 T: timely::progress::Timestamp,
1596 O: Backpressureable + std::fmt::Debug,
1597 >(
1598 scope: Scope<'scope, T>,
1599 input: StreamVec<'scope, T, O>,
1600 feedback: timely::dataflow::operators::feedback::Handle<
1601 'scope,
1602 T,
1603 Vec<std::convert::Infallible>,
1604 >,
1605 ) -> UnboundedSender<()> {
1606 let (tx, mut rx) = unbounded_channel::<()>();
1607 let mut consumer = AsyncOperatorBuilder::new("consumer".to_string(), scope);
1608 let (output_handle, output) =
1609 consumer.new_output::<CapacityContainerBuilder<Vec<std::convert::Infallible>>>();
1610 let mut input = consumer.new_input_for(input, Pipeline, &output_handle);
1611
1612 consumer.build(|_caps| async move {
1613 while let Some(()) = rx.recv().await {
1614 while let Some(Event::Progress(_)) = input.next().await {}
1616 }
1617 });
1618 output.connect_loop(feedback);
1619
1620 tx
1621 }
1622
1623 mod filter_pushdown_audit {
1638 use itertools::Itertools;
1639 use mz_expr::func::variadic::{And, Or};
1640 use mz_expr::func::{
1641 AddFloat32, AddTimestampInterval, CastNumericToFloat32, CastNumericToMzTimestamp, Eq,
1642 Gt, Gte, IsNull, JsonbGetString, JsonbGetStringStringify, Lt, Lte, MulFloat32,
1643 MulFloat64, Not, RoundNumericBinary, TryParseMonotonicIso8601Timestamp,
1644 };
1645 use mz_expr::{BinaryFunc, MapFilterProject, MirScalarExpr, UnaryFunc};
1646 use mz_ore::metrics::MetricsRegistry;
1647 use mz_persist_types::part::PartBuilder;
1648 use mz_persist_types::stats::{PartStats, PartStatsMetrics};
1649 use mz_repr::adt::interval::Interval;
1650 use mz_repr::adt::numeric::Numeric;
1651 use mz_repr::{Diff, ReprScalarType, SqlScalarType};
1652 use proptest::prelude::*;
1653 use proptest::sample::{Index, select};
1654 use proptest::strategy::Union;
1655
1656 use super::*;
1657
1658 fn f32_lit(x: f32) -> MirScalarExpr {
1659 MirScalarExpr::literal_ok(Datum::from(x), ReprScalarType::Float32)
1660 }
1661
1662 fn f64_lit(x: f64) -> MirScalarExpr {
1663 MirScalarExpr::literal_ok(Datum::from(x), ReprScalarType::Float64)
1664 }
1665
1666 fn numeric_datum(x: f64) -> Datum<'static> {
1667 Datum::from(Numeric::from(x))
1668 }
1669
1670 fn numeric_desc() -> RelationDesc {
1671 RelationDesc::builder()
1672 .with_column(
1673 "c0",
1674 SqlScalarType::Numeric { max_scale: None }.nullable(false),
1675 )
1676 .finish()
1677 }
1678
1679 fn build_part_stats(desc: &RelationDesc, rows: &[SourceData]) -> PartStats {
1682 let mut builder = PartBuilder::new(desc, &UnitSchema);
1683 for row in rows {
1684 builder.push(row, &(), 1u64, 1i64);
1685 }
1686 let part = builder.finish();
1687 PartStats::new::<SourceData, RelationDesc>(&part, desc).expect("stats")
1688 }
1689
1690 fn mfp_yields_output(plan: &MfpPlan, rows: &[Row]) -> bool {
1694 let arena = RowArena::new();
1695 let mut row_builder = Row::default();
1696 for row in rows {
1697 let mut datums: Vec<Datum> = row.iter().collect();
1698 let mut results = plan.evaluate::<DataflowError, _>(
1699 &mut datums,
1700 &arena,
1701 Timestamp::MIN,
1702 Diff::from(1),
1703 |_| true,
1704 &mut row_builder,
1705 );
1706 if results.next().is_some() {
1707 return true;
1708 }
1709 }
1710 false
1711 }
1712
1713 fn comparison_funcs() -> impl Strategy<Value = BinaryFunc> {
1714 select(vec![
1715 BinaryFunc::Lte(Lte),
1716 BinaryFunc::Lt(Lt),
1717 BinaryFunc::Gte(Gte),
1718 BinaryFunc::Gt(Gt),
1719 BinaryFunc::Eq(Eq),
1720 ])
1721 }
1722
1723 fn f32_consts() -> impl Strategy<Value = f32> {
1724 select(vec![
1725 0.0f32,
1726 1.0,
1727 -1.0,
1728 0.0087531805,
1729 745213.56,
1730 76700000000.0,
1731 f32::MAX,
1732 1e30,
1733 -1e30,
1734 ])
1735 }
1736
1737 fn float_arith_predicate(
1741 scale: i32,
1742 a: f32,
1743 b: f32,
1744 c: f32,
1745 cmp: BinaryFunc,
1746 ) -> MirScalarExpr {
1747 let round = MirScalarExpr::CallBinary {
1748 func: BinaryFunc::RoundNumeric(RoundNumericBinary),
1749 expr1: Box::new(MirScalarExpr::column(0)),
1750 expr2: Box::new(MirScalarExpr::literal_ok(
1751 Datum::from(scale),
1752 ReprScalarType::Int32,
1753 )),
1754 };
1755 let cast = MirScalarExpr::CallUnary {
1756 func: UnaryFunc::CastNumericToFloat32(CastNumericToFloat32),
1757 expr: Box::new(round),
1758 };
1759 let add = MirScalarExpr::CallBinary {
1760 func: BinaryFunc::AddFloat32(AddFloat32),
1761 expr1: Box::new(f32_lit(a)),
1762 expr2: Box::new(cast),
1763 };
1764 let mul = MirScalarExpr::CallBinary {
1765 func: BinaryFunc::MulFloat32(MulFloat32),
1766 expr1: Box::new(add),
1767 expr2: Box::new(f32_lit(b)),
1768 };
1769 MirScalarExpr::CallBinary {
1770 func: cmp,
1771 expr1: Box::new(mul),
1772 expr2: Box::new(f32_lit(c)),
1773 }
1774 }
1775
1776 fn cast_mz_timestamp_predicate(ts: u64, cmp: BinaryFunc) -> MirScalarExpr {
1780 let cast = MirScalarExpr::CallUnary {
1781 func: UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
1782 expr: Box::new(MirScalarExpr::column(0)),
1783 };
1784 MirScalarExpr::CallBinary {
1785 func: cmp,
1786 expr1: Box::new(cast),
1787 expr2: Box::new(MirScalarExpr::literal_ok(
1788 Datum::MzTimestamp(Timestamp::from(ts)),
1789 ReprScalarType::MzTimestamp,
1790 )),
1791 }
1792 }
1793
1794 fn arb_numeric_rows() -> impl Strategy<Value = Vec<Row>> {
1795 let magnitudes = vec![
1796 0.0f64, 1.0, 2.0, 1.5, 2.5, 0.25, -1.0, 10.0, 100.0, 1e10, -1e10, 3.0e38, 3.4e38,
1797 3.5e38, 1e40, -1e40, 1e300,
1798 ];
1799 prop::collection::vec(
1800 select(magnitudes).prop_map(|x| Row::pack_slice(&[numeric_datum(x)])),
1801 1..8,
1802 )
1803 }
1804
1805 fn arb_predicate() -> impl Strategy<Value = MirScalarExpr> {
1806 let float_arith = (
1807 select(vec![0i32, 2, -5, 24699]),
1808 f32_consts(),
1809 f32_consts(),
1810 f32_consts(),
1811 comparison_funcs(),
1812 )
1813 .prop_map(|(scale, a, b, c, cmp)| float_arith_predicate(scale, a, b, c, cmp));
1814 let cast_ts = (select(vec![0u64, 1, 2, 100, u64::MAX]), comparison_funcs())
1815 .prop_map(|(ts, cmp)| cast_mz_timestamp_predicate(ts, cmp));
1816 proptest::strategy::Union::new(vec![float_arith.boxed(), cast_ts.boxed()])
1817 }
1818
1819 #[mz_ore::test]
1820 #[cfg_attr(miri, ignore)] fn filter_result_never_discards_matching_part() {
1822 fn check(rows: Vec<Row>, predicate: MirScalarExpr) -> Result<(), TestCaseError> {
1823 let desc = numeric_desc();
1824 let plan = MapFilterProject::new(1)
1825 .filter(std::iter::once(predicate))
1826 .into_plan()
1827 .expect("into_plan");
1828
1829 let source_rows: Vec<_> = rows.iter().map(|r| SourceData(Ok(r.clone()))).collect();
1830 let part_stats = build_part_stats(&desc, &source_rows);
1831 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
1832 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
1833
1834 let decision = filter_result(&desc, ResultSpec::anything(), stats, &plan);
1835
1836 if mfp_yields_output(&plan, &rows) {
1837 prop_assert!(
1838 !matches!(decision, FilterResult::Discard),
1839 "filter pushdown discarded a part whose MFP yields output on a real \
1840 row (wrongly-skipped part; the runtime audit would panic).\n\
1841 rows={rows:?}\nplan={plan:?}",
1842 );
1843 }
1844 Ok(())
1845 }
1846
1847 proptest!(|(rows in arb_numeric_rows(), predicate in arb_predicate())| {
1848 check(rows, predicate)?;
1849 });
1850 }
1851
1852 fn assert_part_kept(desc: &RelationDesc, rows: &[Row], predicate: MirScalarExpr) {
1856 let plan = MapFilterProject::new(1)
1857 .filter(std::iter::once(predicate))
1858 .into_plan()
1859 .expect("into_plan");
1860 assert!(
1861 mfp_yields_output(&plan, rows),
1862 "nothing to keep: the MFP yields no output on any of these rows.\n\
1863 rows={rows:?}\nplan={plan:?}",
1864 );
1865
1866 let source_rows: Vec<_> = rows.iter().map(|r| SourceData(Ok(r.clone()))).collect();
1867 let part_stats = build_part_stats(desc, &source_rows);
1868 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
1869 let stats = RelationPartStats::new("test", &metrics, desc, &part_stats);
1870 let decision = filter_result(desc, ResultSpec::anything(), stats, &plan);
1871 assert!(
1872 !matches!(decision, FilterResult::Discard),
1873 "filter pushdown discarded a part whose MFP yields output on a real row.\n\
1874 rows={rows:?}\nplan={plan:?}",
1875 );
1876 }
1877
1878 #[mz_ore::test]
1889 #[cfg_attr(miri, ignore)] fn negative_nan_does_not_discard_matching_part() {
1891 let desc = RelationDesc::builder()
1894 .with_column("c0", SqlScalarType::Float32.nullable(false))
1895 .finish();
1896 let rows = [
1897 Row::pack_slice(&[Datum::from(-f32::NAN)]),
1898 Row::pack_slice(&[Datum::from(0.0f32)]),
1899 ];
1900 assert_part_kept(
1901 &desc,
1902 &rows,
1903 MirScalarExpr::CallBinary {
1904 func: BinaryFunc::Lt(Lt),
1905 expr1: Box::new(MirScalarExpr::column(0)),
1906 expr2: Box::new(f32_lit(1.0)),
1907 },
1908 );
1909
1910 let desc = RelationDesc::builder()
1911 .with_column("c0", SqlScalarType::Float64.nullable(false))
1912 .finish();
1913 let rows = [
1914 Row::pack_slice(&[Datum::from(-f64::NAN)]),
1915 Row::pack_slice(&[Datum::from(0.0f64)]),
1916 ];
1917 assert_part_kept(
1918 &desc,
1919 &rows,
1920 MirScalarExpr::CallBinary {
1921 func: BinaryFunc::Lt(Lt),
1922 expr1: Box::new(MirScalarExpr::column(0)),
1923 expr2: Box::new(f64_lit(1.0)),
1924 },
1925 );
1926 }
1927
1928 #[mz_ore::test]
1939 #[cfg_attr(miri, ignore)] fn mixed_sign_nans_do_not_discard_matching_part() {
1941 let desc = RelationDesc::builder()
1942 .with_column("c0", SqlScalarType::Float32.nullable(false))
1943 .finish();
1944 let rows = [
1945 Row::pack_slice(&[Datum::from(-f32::NAN)]),
1946 Row::pack_slice(&[Datum::from(f32::NAN)]),
1947 Row::pack_slice(&[Datum::from(0.0f32)]),
1948 ];
1949 assert_part_kept(
1950 &desc,
1951 &rows,
1952 MirScalarExpr::CallBinary {
1953 func: BinaryFunc::Eq(Eq),
1954 expr1: Box::new(MirScalarExpr::column(0)),
1955 expr2: Box::new(f32_lit(0.0)),
1956 },
1957 );
1958
1959 let desc = RelationDesc::builder()
1960 .with_column("c0", SqlScalarType::Float64.nullable(false))
1961 .finish();
1962 let rows = [
1963 Row::pack_slice(&[Datum::from(-f64::NAN)]),
1964 Row::pack_slice(&[Datum::from(f64::NAN)]),
1965 Row::pack_slice(&[Datum::from(0.0f64)]),
1966 ];
1967 assert_part_kept(
1968 &desc,
1969 &rows,
1970 MirScalarExpr::CallBinary {
1971 func: BinaryFunc::Eq(Eq),
1972 expr1: Box::new(MirScalarExpr::column(0)),
1973 expr2: Box::new(f64_lit(0.0)),
1974 },
1975 );
1976 }
1977
1978 const NUM: usize = 0;
1987 const F32: usize = 1;
1988 const F64: usize = 2;
1989 const STR: usize = 3;
1990 const J1: usize = 4;
1991 const J2: usize = 5;
1992 const BOOL: usize = 6;
1993 const TS: usize = 7;
1994 const MZTS: usize = 8;
1995 const WIDE_ARITY: usize = 9;
1996
1997 fn wide_scalar_type(col: usize) -> SqlScalarType {
1998 match col {
1999 NUM => SqlScalarType::Numeric { max_scale: None },
2000 F32 => SqlScalarType::Float32,
2001 F64 => SqlScalarType::Float64,
2002 STR => SqlScalarType::String,
2003 J1 | J2 => SqlScalarType::Jsonb,
2004 BOOL => SqlScalarType::Bool,
2005 TS => SqlScalarType::Timestamp { precision: None },
2006 MZTS => SqlScalarType::MzTimestamp,
2007 _ => unreachable!("no such column"),
2008 }
2009 }
2010
2011 fn wide_repr_type(col: usize) -> ReprScalarType {
2012 match col {
2013 NUM => ReprScalarType::Numeric,
2014 F32 => ReprScalarType::Float32,
2015 F64 => ReprScalarType::Float64,
2016 STR => ReprScalarType::String,
2017 J1 | J2 => ReprScalarType::Jsonb,
2018 BOOL => ReprScalarType::Bool,
2019 TS => ReprScalarType::Timestamp,
2020 MZTS => ReprScalarType::MzTimestamp,
2021 _ => unreachable!("no such column"),
2022 }
2023 }
2024
2025 fn wide_desc() -> RelationDesc {
2026 let mut builder = RelationDesc::builder();
2027 for col in 0..WIDE_ARITY {
2028 let nullable = col != BOOL;
2032 builder = builder
2033 .with_column(format!("c{col}"), wide_scalar_type(col).nullable(nullable));
2034 }
2035 builder.finish()
2036 }
2037
2038 fn wide_pool(col: usize) -> Vec<Datum<'static>> {
2039 let mut pool: Vec<_> = wide_scalar_type(col).interesting_datums().collect();
2040 if col != BOOL {
2041 pool.push(Datum::Null);
2042 }
2043 pool
2044 }
2045
2046 fn arb_wide_rows() -> impl Strategy<Value = Vec<SourceData>> {
2047 let pools: Vec<Vec<Datum<'static>>> = (0..WIDE_ARITY).map(wide_pool).collect();
2048 let ok_row = prop::collection::vec(any::<Index>(), WIDE_ARITY).prop_map(move |picks| {
2049 let datums = picks
2050 .iter()
2051 .zip_eq(&pools)
2052 .map(|(pick, pool)| pool[pick.index(pool.len())]);
2053 SourceData(Ok(Row::pack(datums)))
2054 });
2055 let err_row = Just(SourceData(Err(DataflowError::from(
2056 EvalError::DivisionByZero,
2057 ))));
2058 let row = Union::new_weighted(vec![(9, ok_row.boxed()), (1, err_row.boxed())]);
2059 prop::collection::vec(row, 2..8)
2060 }
2061
2062 fn lit(datum: Datum<'static>, typ: ReprScalarType) -> MirScalarExpr {
2063 if datum.is_null() {
2064 MirScalarExpr::literal_null(typ)
2065 } else {
2066 MirScalarExpr::literal_ok(datum, typ)
2067 }
2068 }
2069
2070 fn is_null(expr: MirScalarExpr) -> MirScalarExpr {
2071 MirScalarExpr::CallUnary {
2072 func: UnaryFunc::IsNull(IsNull),
2073 expr: Box::new(expr),
2074 }
2075 }
2076
2077 fn not(expr: MirScalarExpr) -> MirScalarExpr {
2078 MirScalarExpr::CallUnary {
2079 func: UnaryFunc::Not(Not),
2080 expr: Box::new(expr),
2081 }
2082 }
2083
2084 fn binary(func: BinaryFunc, a: MirScalarExpr, b: MirScalarExpr) -> MirScalarExpr {
2085 MirScalarExpr::CallBinary {
2086 func,
2087 expr1: Box::new(a),
2088 expr2: Box::new(b),
2089 }
2090 }
2091
2092 fn arb_cmp_col_lit() -> impl Strategy<Value = MirScalarExpr> {
2095 (0..WIDE_ARITY, any::<Index>(), comparison_funcs()).prop_map(|(col, pick, cmp)| {
2096 let pool = wide_pool(col);
2097 let datum = pool[pick.index(pool.len())];
2098 binary(
2099 cmp,
2100 MirScalarExpr::column(col),
2101 lit(datum, wide_repr_type(col)),
2102 )
2103 })
2104 }
2105
2106 fn arb_is_null_pred() -> impl Strategy<Value = MirScalarExpr> {
2107 (0..WIDE_ARITY, any::<bool>()).prop_map(|(col, negate)| {
2108 let expr = is_null(MirScalarExpr::column(col));
2109 if negate { not(expr) } else { expr }
2110 })
2111 }
2112
2113 fn jsonb_keys() -> impl Strategy<Value = &'static str> {
2114 select(vec!["x", "y", "nested", "absent"])
2115 }
2116
2117 fn jsonb_get(expr: MirScalarExpr, key: &'static str, stringify: bool) -> MirScalarExpr {
2118 let func = if stringify {
2119 BinaryFunc::JsonbGetStringStringify(JsonbGetStringStringify)
2120 } else {
2121 BinaryFunc::JsonbGetString(JsonbGetString)
2122 };
2123 binary(
2124 func,
2125 expr,
2126 MirScalarExpr::literal_ok(Datum::String(key), ReprScalarType::String),
2127 )
2128 }
2129
2130 fn arb_jsonb_pred() -> impl Strategy<Value = MirScalarExpr> {
2133 (
2134 select(vec![J1, J2]),
2135 jsonb_keys(),
2136 any::<bool>(),
2137 any::<bool>(),
2138 )
2139 .prop_map(|(col, key, stringify, wrap_eq)| {
2140 let get = jsonb_get(MirScalarExpr::column(col), key, stringify);
2141 if wrap_eq {
2142 let typ = if stringify {
2143 ReprScalarType::String
2144 } else {
2145 ReprScalarType::Jsonb
2146 };
2147 binary(BinaryFunc::Eq(Eq), get, lit(Datum::String("a"), typ))
2148 } else {
2149 is_null(get)
2150 }
2151 })
2152 }
2153
2154 fn arb_case_jsonb_pred() -> impl Strategy<Value = MirScalarExpr> {
2157 (any::<bool>(), jsonb_keys(), any::<bool>()).prop_map(
2158 |(cond_is_col, key, stringify)| {
2159 let cond = if cond_is_col {
2160 MirScalarExpr::column(BOOL)
2161 } else {
2162 is_null(MirScalarExpr::column(STR))
2163 };
2164 let case = MirScalarExpr::If {
2165 cond: Box::new(cond),
2166 then: Box::new(MirScalarExpr::column(J1)),
2167 els: Box::new(MirScalarExpr::column(J2)),
2168 };
2169 is_null(jsonb_get(case, key, stringify))
2170 },
2171 )
2172 }
2173
2174 fn arb_iso_parse_pred() -> impl Strategy<Value = MirScalarExpr> {
2177 (comparison_funcs(), any::<Index>(), any::<bool>()).prop_map(
2178 |(cmp, pick, wrap_null)| {
2179 let parse = MirScalarExpr::CallUnary {
2180 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(
2181 TryParseMonotonicIso8601Timestamp,
2182 ),
2183 expr: Box::new(MirScalarExpr::column(STR)),
2184 };
2185 if wrap_null {
2186 is_null(parse)
2187 } else {
2188 let pool: Vec<_> = SqlScalarType::Timestamp { precision: None }
2189 .interesting_datums()
2190 .collect();
2191 let datum = pool[pick.index(pool.len())];
2192 binary(cmp, parse, lit(datum, ReprScalarType::Timestamp))
2193 }
2194 },
2195 )
2196 }
2197
2198 fn arb_ts_interval_pred() -> impl Strategy<Value = MirScalarExpr> {
2202 let intervals = select(vec![
2203 Interval::new(0, 2, 0),
2204 Interval::new(0, 0, 3_600_000_000),
2205 Interval::new(1, 0, 0),
2206 Interval::new(-1, 0, 0),
2207 ]);
2208 (comparison_funcs(), intervals, any::<Index>()).prop_map(|(cmp, iv, pick)| {
2209 let add = binary(
2210 BinaryFunc::AddTimestampInterval(AddTimestampInterval),
2211 MirScalarExpr::column(TS),
2212 lit(Datum::Interval(iv), ReprScalarType::Interval),
2213 );
2214 let pool: Vec<_> = SqlScalarType::Timestamp { precision: None }
2215 .interesting_datums()
2216 .collect();
2217 let datum = pool[pick.index(pool.len())];
2218 binary(cmp, add, lit(datum, ReprScalarType::Timestamp))
2219 })
2220 }
2221
2222 fn arb_float_mul_pred() -> impl Strategy<Value = MirScalarExpr> {
2226 let consts = || select(vec![0.0f64, 1.0, -1.0, 1e300, -1e300, f64::INFINITY]);
2227 (comparison_funcs(), consts(), consts()).prop_map(|(cmp, a, c)| {
2228 let mul = binary(
2229 BinaryFunc::MulFloat64(MulFloat64),
2230 MirScalarExpr::column(F64),
2231 lit(Datum::from(a), ReprScalarType::Float64),
2232 );
2233 binary(cmp, mul, lit(Datum::from(c), ReprScalarType::Float64))
2234 })
2235 }
2236
2237 fn arb_temporal_pred() -> impl Strategy<Value = MirScalarExpr> {
2241 let cmps = select(vec![
2242 BinaryFunc::Lte(Lte),
2243 BinaryFunc::Lt(Lt),
2244 BinaryFunc::Gte(Gte),
2245 BinaryFunc::Gt(Gt),
2246 ]);
2247 (cmps, any::<bool>(), any::<Index>()).prop_map(|(cmp, use_col, pick)| {
2248 let mz_now = MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow);
2249 let rhs = if use_col {
2250 MirScalarExpr::column(MZTS)
2251 } else {
2252 let pool = wide_pool(MZTS);
2253 lit(pool[pick.index(pool.len())], ReprScalarType::MzTimestamp)
2254 };
2255 binary(cmp, mz_now, rhs)
2256 })
2257 }
2258
2259 fn arb_wide_predicate() -> impl Strategy<Value = MirScalarExpr> {
2260 let leaf = Union::new(vec![
2261 arb_cmp_col_lit().boxed(),
2262 arb_is_null_pred().boxed(),
2263 arb_jsonb_pred().boxed(),
2264 arb_case_jsonb_pred().boxed(),
2265 arb_iso_parse_pred().boxed(),
2266 arb_ts_interval_pred().boxed(),
2267 arb_float_mul_pred().boxed(),
2268 arb_temporal_pred().boxed(),
2269 (
2272 select(vec![0i32, 2, -5, 24699]),
2273 f32_consts(),
2274 f32_consts(),
2275 f32_consts(),
2276 comparison_funcs(),
2277 )
2278 .prop_map(|(s, a, b, c, cmp)| float_arith_predicate(s, a, b, c, cmp))
2279 .boxed(),
2280 (select(vec![0u64, 1, 2, 100, u64::MAX]), comparison_funcs())
2281 .prop_map(|(ts, cmp)| cast_mz_timestamp_predicate(ts, cmp))
2282 .boxed(),
2283 ])
2284 .boxed();
2285 Union::new_weighted(vec![
2286 (3, leaf.clone()),
2287 (
2288 1,
2289 (leaf.clone(), leaf.clone(), any::<bool>())
2290 .prop_map(|(a, b, is_and)| {
2291 let func = if is_and { And.into() } else { Or.into() };
2292 MirScalarExpr::CallVariadic {
2293 func,
2294 exprs: vec![a, b],
2295 }
2296 })
2297 .boxed(),
2298 ),
2299 (1, leaf.prop_map(not).boxed()),
2300 ])
2301 }
2302
2303 #[mz_ore::test]
2309 #[cfg_attr(miri, ignore)] fn zero_column_relation_replace_with() {
2311 let desc = RelationDesc::empty();
2312 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
2313 let ok_rows = vec![
2314 SourceData(Ok(Row::default())),
2315 SourceData(Ok(Row::default())),
2316 ];
2317
2318 let plan = MapFilterProject::new(0).into_plan().expect("into_plan");
2321 let part_stats = build_part_stats(&desc, &ok_rows);
2322 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2323 let decision = filter_result(&desc, ResultSpec::anything(), stats, &plan);
2324 assert!(
2325 matches!(decision, FilterResult::ReplaceWith { .. }),
2326 "expected ReplaceWith, got {decision:?}",
2327 );
2328
2329 let mixed_rows = vec![
2332 SourceData(Ok(Row::default())),
2333 SourceData(Err(DataflowError::from(EvalError::DivisionByZero))),
2334 ];
2335 let part_stats = build_part_stats(&desc, &mixed_rows);
2336 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2337 let decision = filter_result(&desc, ResultSpec::anything(), stats, &plan);
2338 assert!(
2339 matches!(decision, FilterResult::Keep),
2340 "expected Keep, got {decision:?}",
2341 );
2342
2343 let plan = MapFilterProject::new(0)
2345 .filter(std::iter::once(MirScalarExpr::literal_ok(
2346 Datum::False,
2347 ReprScalarType::Bool,
2348 )))
2349 .into_plan()
2350 .expect("into_plan");
2351 let part_stats = build_part_stats(&desc, &ok_rows);
2352 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2353 let decision = filter_result(&desc, ResultSpec::anything(), stats, &plan);
2354 assert!(
2355 matches!(decision, FilterResult::Discard),
2356 "expected Discard, got {decision:?}",
2357 );
2358 }
2359
2360 #[mz_ore::test]
2368 #[cfg_attr(miri, ignore)] fn schema_drift_degrades_to_no_stats() {
2370 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
2371
2372 let write_desc = RelationDesc::builder()
2374 .with_column("a", SqlScalarType::Int32.nullable(false))
2375 .finish();
2376 let rows = vec![SourceData(Ok(Row::pack_slice(&[Datum::Int32(1)])))];
2377 let part_stats = build_part_stats(&write_desc, &rows);
2378
2379 let read_desc = RelationDesc::builder()
2380 .with_column("a", SqlScalarType::Int32.nullable(false))
2381 .with_column("b", SqlScalarType::Float64.nullable(true))
2382 .finish();
2383 let stats = RelationPartStats::new("test", &metrics, &read_desc, &part_stats);
2384 let plan = MapFilterProject::new(2)
2387 .filter(std::iter::once(is_null(MirScalarExpr::column(1))))
2388 .into_plan()
2389 .expect("into_plan");
2390 let decision = filter_result(&read_desc, ResultSpec::anything(), stats, &plan);
2391 assert!(
2392 !matches!(decision, FilterResult::Discard),
2393 "part written before ADD COLUMN was discarded: {decision:?}",
2394 );
2395
2396 let write_desc = RelationDesc::builder()
2400 .with_column("a", SqlScalarType::Int32.nullable(false))
2401 .with_column("b", SqlScalarType::Float64.nullable(true))
2402 .finish();
2403 let rows = vec![SourceData(Ok(Row::pack_slice(&[
2404 Datum::Int32(1),
2405 Datum::from(5.0f64),
2406 ])))];
2407 let part_stats = build_part_stats(&write_desc, &rows);
2408
2409 let read_desc = RelationDesc::builder()
2410 .with_column("b", SqlScalarType::Float64.nullable(true))
2411 .finish();
2412 let stats = RelationPartStats::new("test", &metrics, &read_desc, &part_stats);
2413 let plan = MapFilterProject::new(1)
2414 .filter(std::iter::once(binary(
2415 BinaryFunc::Eq(Eq),
2416 MirScalarExpr::column(0),
2417 lit(Datum::from(5.0f64), ReprScalarType::Float64),
2418 )))
2419 .into_plan()
2420 .expect("into_plan");
2421 let decision = filter_result(&read_desc, ResultSpec::anything(), stats, &plan);
2422 assert!(
2423 !matches!(decision, FilterResult::Discard),
2424 "projected read desc discarded a matching part: {decision:?}",
2425 );
2426 }
2427
2428 fn part_yields_output(
2435 plan: &MfpPlan,
2436 rows: &[SourceData],
2437 eval_time: Timestamp,
2438 until: &Antichain<Timestamp>,
2439 ) -> bool {
2440 if until.less_equal(&eval_time) {
2441 return false;
2442 }
2443 let arena = RowArena::new();
2444 let mut row_builder = Row::default();
2445 for source_data in rows {
2446 match &source_data.0 {
2447 Err(_) => return true,
2448 Ok(row) => {
2449 let mut datums: Vec<Datum> = row.iter().collect();
2450 let mut results = plan.evaluate::<DataflowError, _>(
2451 &mut datums,
2452 &arena,
2453 eval_time,
2454 Diff::from(1),
2455 |time| !until.less_equal(time),
2456 &mut row_builder,
2457 );
2458 if results.next().is_some() {
2459 return true;
2460 }
2461 }
2462 }
2463 }
2464 false
2465 }
2466
2467 #[mz_ore::test]
2468 #[cfg_attr(miri, ignore)] fn wide_filter_result_never_discards_matching_part() {
2470 fn check(
2471 rows: Vec<SourceData>,
2472 predicate: MirScalarExpr,
2473 eval_time: u64,
2474 until: Option<u64>,
2475 ) -> Result<(), TestCaseError> {
2476 let desc = wide_desc();
2477 let Ok(plan) = MapFilterProject::new(desc.arity())
2481 .filter(std::iter::once(predicate))
2482 .into_plan()
2483 else {
2484 return Ok(());
2485 };
2486 let eval_time = Timestamp::from(eval_time);
2487 let until =
2488 until.map_or_else(Antichain::new, |t| Antichain::from_elem(Timestamp::from(t)));
2489
2490 let part_stats = build_part_stats(&desc, &rows);
2491 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
2492 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2493
2494 let upper = until.as_option().copied().unwrap_or(Timestamp::MAX);
2499 if eval_time > upper {
2500 return Ok(());
2501 }
2502 let time_range = ResultSpec::value_between(
2503 Datum::MzTimestamp(eval_time),
2504 Datum::MzTimestamp(upper),
2505 );
2506 let decision = filter_result(&desc, time_range, stats, &plan);
2507
2508 if part_yields_output(&plan, &rows, eval_time, &until) {
2509 prop_assert!(
2510 !matches!(decision, FilterResult::Discard),
2511 "filter pushdown discarded a part whose MFP yields output on a real \
2512 row (wrongly-skipped part; the runtime audit would panic).\n\
2513 rows={rows:?}\nplan={plan:?}\neval_time={eval_time}\nuntil={until:?}",
2514 );
2515 }
2516 Ok(())
2517 }
2518
2519 let default = ProptestConfig::default();
2526 let cases = if std::env::var_os("PROPTEST_CASES").is_some() {
2527 default.cases
2528 } else {
2529 4096
2530 };
2531 let config = ProptestConfig { cases, ..default };
2532 proptest!(config, |(
2533 rows in arb_wide_rows(),
2534 predicate in arb_wide_predicate(),
2535 eval_time in select(vec![1u64, 5]),
2536 until in select(vec![None, Some(1u64), Some(5), Some(8), Some(100)]),
2537 )| {
2538 check(rows, predicate, eval_time, until)?;
2539 });
2540 }
2541
2542 #[mz_ore::test]
2548 #[cfg_attr(miri, ignore)] fn multi_part_decisions_are_independent() {
2550 fn check(
2551 parts: Vec<Vec<SourceData>>,
2552 predicate: MirScalarExpr,
2553 eval_time: u64,
2554 until: Option<u64>,
2555 ) -> Result<(), TestCaseError> {
2556 let desc = wide_desc();
2557 let Ok(plan) = MapFilterProject::new(desc.arity())
2558 .filter(std::iter::once(predicate))
2559 .into_plan()
2560 else {
2561 return Ok(());
2562 };
2563 let eval_time = Timestamp::from(eval_time);
2564 let until =
2565 until.map_or_else(Antichain::new, |t| Antichain::from_elem(Timestamp::from(t)));
2566 let upper = until.as_option().copied().unwrap_or(Timestamp::MAX);
2567 if eval_time > upper {
2568 return Ok(());
2569 }
2570 let metrics = PartStatsMetrics::new(&MetricsRegistry::new());
2571
2572 for rows in &parts {
2573 let part_stats = build_part_stats(&desc, rows);
2574 let stats = RelationPartStats::new("test", &metrics, &desc, &part_stats);
2575 let time_range = ResultSpec::value_between(
2576 Datum::MzTimestamp(eval_time),
2577 Datum::MzTimestamp(upper),
2578 );
2579 let decision = filter_result(&desc, time_range, stats, &plan);
2580 if part_yields_output(&plan, rows, eval_time, &until) {
2581 prop_assert!(
2582 !matches!(decision, FilterResult::Discard),
2583 "filter pushdown discarded a part whose MFP yields output on a \
2584 real row.\nrows={rows:?}\nplan={plan:?}\neval_time={eval_time}\n\
2585 until={until:?}",
2586 );
2587 }
2588 }
2589 Ok(())
2590 }
2591
2592 let default = ProptestConfig::default();
2593 let cases = if std::env::var_os("PROPTEST_CASES").is_some() {
2594 default.cases
2595 } else {
2596 1024
2597 };
2598 let config = ProptestConfig { cases, ..default };
2599 proptest!(config, |(
2600 parts in prop::collection::vec(arb_wide_rows(), 2..4),
2601 predicate in arb_wide_predicate(),
2602 eval_time in select(vec![1u64, 5]),
2603 until in select(vec![None, Some(5u64), Some(100)]),
2604 )| {
2605 check(parts, predicate, eval_time, until)?;
2606 });
2607 }
2608 }
2609}