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