1pub(crate) mod backend;
4#[cfg(feature = "datafusion")]
5pub mod datafusion;
6pub(crate) mod deletion_vector;
7pub(crate) mod metrics;
8mod options;
9pub(crate) mod partition_target;
10pub(crate) mod planning;
11pub(crate) mod predicate;
12#[allow(dead_code)]
13pub(crate) mod scheduling;
14pub(crate) mod transform;
15
16#[doc(hidden)]
17pub use metrics::ParquetRangePlanningDiagnosticSnapshot;
18pub use metrics::{DeltaScanMetrics, DeltaScanMetricsSnapshot};
19#[doc(hidden)]
20pub use options::ParquetRangeReadPolicy;
21pub use options::{
22 DeltaScanExecutionOptions, DeltaSnapshotSelection, DeltaStorageOptions, ParquetReaderBackend,
23};
24pub use predicate::{DeltaComparison, DeltaPredicate, DeltaScalar};
25
26use std::{
27 fmt,
28 pin::Pin,
29 sync::Arc,
30 task::{Context, Poll},
31};
32
33use arrow::{datatypes::SchemaRef, record_batch::RecordBatch};
34use futures_util::Stream;
35use snafu::ResultExt;
36
37use self::{
38 planning::{
39 DeltaScanPartitionTargetOptions, DeltaScanPlan, build_physical_row_predicate, plan_scan,
40 },
41 predicate::{evaluate_predicate, referenced_columns, validate_predicate},
42 scheduling::{
43 DeltaScanScheduler, FileAdmissionDecision, FileAdmissionPolicy, FileBatchStream,
44 FileExecutor, OrderedPartitionStream,
45 },
46};
47
48use crate::{
49 DeltaProtocol, DeltaReaderError,
50 delta::{
51 kernel::{DeltaKernelPredicate, kernel_pruning_predicate, kernel_row_predicate},
52 protocol::validate_protocol,
53 snapshot::{
54 ArrowTableSnapshot, KernelTableSnapshot, load_delta_table_snapshot,
55 load_kernel_table_snapshot,
56 },
57 },
58 error::{DataFileReadSnafu, InvalidConfigurationSnafu, ScanPlanningSnafu, SnapshotLoadSnafu},
59};
60
61const TRACING_TARGET: &str = "delta_arrow_reader";
62const DELTA_LOG_SCAN_METADATA_SOURCE: &str = "delta_log";
63const QUERY_PLANNING_CACHE_SCAN_METADATA_SOURCE: &str = "query_planning_cache";
64
65#[non_exhaustive]
70#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
71pub enum WarmupMode {
72 #[default]
74 None,
75 QueryPlanning,
77}
78
79#[must_use = "table builder settings do nothing unless the builder is loaded"]
113pub struct DeltaTableBuilder {
114 table_location: String,
115 storage_options: DeltaStorageOptions,
116 snapshot_selection: DeltaSnapshotSelection,
117 execution_options: DeltaScanExecutionOptions,
118 warmup: WarmupMode,
119}
120
121impl DeltaTableBuilder {
122 pub fn new(table_location: impl Into<String>) -> Self {
124 Self {
125 table_location: table_location.into(),
126 storage_options: DeltaStorageOptions::new(),
127 snapshot_selection: DeltaSnapshotSelection::Latest,
128 execution_options: DeltaScanExecutionOptions::new(),
129 warmup: WarmupMode::None,
130 }
131 }
132
133 pub fn with_storage_options(mut self, storage_options: DeltaStorageOptions) -> Self {
135 self.storage_options = storage_options;
136 self
137 }
138
139 pub const fn with_snapshot_selection(
141 mut self,
142 snapshot_selection: DeltaSnapshotSelection,
143 ) -> Self {
144 self.snapshot_selection = snapshot_selection;
145 self
146 }
147
148 pub const fn with_execution_options(
150 mut self,
151 execution_options: DeltaScanExecutionOptions,
152 ) -> Self {
153 self.execution_options = execution_options;
154 self
155 }
156
157 pub const fn with_warmup(mut self, warmup: WarmupMode) -> Self {
162 self.warmup = warmup;
163 self
164 }
165
166 pub async fn load_table(self) -> Result<DeltaTable, DeltaReaderError> {
182 let snapshot = load_delta_table_snapshot(
183 self.table_location,
184 self.storage_options,
185 self.snapshot_selection,
186 )
187 .await?;
188 let snapshot = if self.warmup == WarmupMode::None {
189 snapshot
190 } else {
191 validate_protocol(snapshot.protocol())?;
192 materialize_eager_scan_metadata(snapshot).await?
193 };
194 Ok(DeltaTable::new(snapshot, self.execution_options))
195 }
196
197 pub async fn load_snapshot(self) -> Result<DeltaTableSnapshot, DeltaReaderError> {
208 if self.warmup != WarmupMode::None {
209 return InvalidConfigurationSnafu {
210 reason: "snapshot_load_does_not_support_table_warmup",
211 }
212 .fail();
213 }
214 let snapshot = load_kernel_table_snapshot(
215 self.table_location,
216 self.storage_options,
217 self.snapshot_selection,
218 )
219 .await?;
220 Ok(DeltaTableSnapshot::new(snapshot, self.execution_options))
221 }
222}
223
224async fn materialize_eager_scan_metadata(
225 snapshot: ArrowTableSnapshot,
226) -> Result<ArrowTableSnapshot, DeltaReaderError> {
227 tokio::task::spawn_blocking(move || snapshot.materialize_eager_scan_metadata())
228 .await
229 .boxed()
230 .context(ScanPlanningSnafu {
231 reason: "query_planning_warmup_task_failed",
232 })?
233}
234
235impl fmt::Debug for DeltaTableBuilder {
236 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
237 formatter
238 .debug_struct("DeltaTableBuilder")
239 .field("table_location", &"<redacted>")
240 .field("storage_options", &"<redacted>")
241 .field("snapshot_selection", &self.snapshot_selection)
242 .field("execution_options", &self.execution_options)
243 .field("warmup", &self.warmup)
244 .finish()
245 }
246}
247
248pub struct DeltaTableSnapshot {
250 snapshot: KernelTableSnapshot,
251 execution_options: DeltaScanExecutionOptions,
252}
253
254impl DeltaTableSnapshot {
255 fn new(snapshot: KernelTableSnapshot, execution_options: DeltaScanExecutionOptions) -> Self {
256 Self {
257 snapshot,
258 execution_options,
259 }
260 }
261
262 pub fn version(&self) -> u64 {
264 self.snapshot.version()
265 }
266
267 pub fn protocol(&self) -> &DeltaProtocol {
269 self.snapshot.protocol()
270 }
271
272 pub fn table_url(&self) -> &str {
276 self.snapshot.table_url()
277 }
278
279 pub fn validate_protocol(&self) -> Result<(), DeltaReaderError> {
281 validate_protocol(self.protocol())
282 }
283
284 pub fn into_table(self) -> Result<DeltaTable, DeltaReaderError> {
286 Ok(DeltaTable::new(
287 self.snapshot.into_arrow_snapshot()?,
288 self.execution_options,
289 ))
290 }
291}
292
293impl fmt::Debug for DeltaTableSnapshot {
294 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
295 formatter
296 .debug_struct("DeltaTableSnapshot")
297 .field("version", &self.version())
298 .finish_non_exhaustive()
299 }
300}
301
302#[derive(Clone)]
304pub struct DeltaTable {
305 snapshot: Arc<ArrowTableSnapshot>,
306 execution_options: DeltaScanExecutionOptions,
307}
308
309impl DeltaTable {
310 fn new(snapshot: ArrowTableSnapshot, execution_options: DeltaScanExecutionOptions) -> Self {
311 Self {
312 snapshot: Arc::new(snapshot),
313 execution_options,
314 }
315 }
316
317 pub fn version(&self) -> u64 {
319 self.snapshot.version()
320 }
321
322 pub fn schema(&self) -> SchemaRef {
324 self.snapshot.schema()
325 }
326
327 pub fn protocol(&self) -> &DeltaProtocol {
329 self.snapshot.protocol()
330 }
331
332 pub fn table_url(&self) -> &str {
336 self.snapshot.table_url()
337 }
338
339 #[allow(dead_code)]
340 pub(crate) fn partition_columns(&self) -> &[String] {
341 self.snapshot.partition_columns()
342 }
343
344 #[allow(dead_code)]
345 pub(crate) fn snapshot(&self) -> &ArrowTableSnapshot {
346 self.snapshot.as_ref()
347 }
348
349 pub fn validate_protocol(&self) -> Result<(), DeltaReaderError> {
351 validate_protocol(self.protocol())
352 }
353
354 pub async fn refresh(&self) -> Result<Self, DeltaReaderError> {
365 let snapshot = Arc::clone(&self.snapshot);
366 let snapshot = tokio::task::spawn_blocking(move || snapshot.refresh())
367 .await
368 .boxed()
369 .context(SnapshotLoadSnafu {
370 reason: "snapshot_refresh_task_failed",
371 })
372 .and_then(|result| result)?;
373 Ok(Self::new(snapshot, self.execution_options))
374 }
375
376 pub fn scan(&self) -> DeltaScanBuilder<'_> {
378 DeltaScanBuilder {
379 table: self,
380 projection: None,
381 predicate: None,
382 limit: None,
383 target_partitions: None,
384 execution_options: self.execution_options,
385 }
386 }
387}
388
389impl fmt::Debug for DeltaTable {
390 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
391 formatter
392 .debug_struct("DeltaTable")
393 .field("version", &self.version())
394 .finish_non_exhaustive()
395 }
396}
397
398#[must_use = "scan builder settings do nothing unless the scan is built"]
400pub struct DeltaScanBuilder<'table> {
401 table: &'table DeltaTable,
402 projection: Option<Vec<String>>,
403 predicate: Option<DeltaPredicate>,
404 limit: Option<usize>,
405 target_partitions: Option<usize>,
406 execution_options: DeltaScanExecutionOptions,
407}
408
409impl<'table> DeltaScanBuilder<'table> {
410 pub fn with_projection(
412 mut self,
413 logical_columns: impl IntoIterator<Item = impl Into<String>>,
414 ) -> Self {
415 self.projection = Some(logical_columns.into_iter().map(Into::into).collect());
416 self
417 }
418
419 pub fn with_predicate(mut self, predicate: DeltaPredicate) -> Self {
421 self.predicate = Some(predicate);
422 self
423 }
424
425 pub const fn with_limit(mut self, limit: usize) -> Self {
427 self.limit = Some(limit);
428 self
429 }
430
431 pub fn with_target_partitions(
433 mut self,
434 target_partitions: usize,
435 ) -> Result<Self, DeltaReaderError> {
436 if target_partitions == 0 {
437 return InvalidConfigurationSnafu {
438 reason: "scan_partition_target_must_be_positive",
439 }
440 .fail();
441 }
442 self.target_partitions = Some(target_partitions);
443 Ok(self)
444 }
445
446 pub const fn with_execution_options(
448 mut self,
449 execution_options: DeltaScanExecutionOptions,
450 ) -> Self {
451 self.execution_options = execution_options;
452 self
453 }
454
455 pub async fn build(self) -> Result<DeltaScan, DeltaReaderError> {
457 self.table.validate_protocol()?;
458 if let Some(predicate) = self.predicate.as_ref() {
459 validate_predicate(predicate, self.table.schema().as_ref())?;
460 }
461
462 let snapshot_version = self.table.version();
463 let backend = self.execution_options.parquet_backend();
464 let scan_metadata_source = if self.table.snapshot.eager_scan_metadata().is_some() {
465 QUERY_PLANNING_CACHE_SCAN_METADATA_SOURCE
466 } else {
467 DELTA_LOG_SCAN_METADATA_SOURCE
468 };
469 trace_planning_started(snapshot_version, backend, scan_metadata_source);
470 let snapshot = Arc::clone(&self.table.snapshot);
471 let projection = self.projection;
472 let predicate = self.predicate;
473 let hidden_columns = predicate
474 .as_ref()
475 .map(referenced_columns)
476 .unwrap_or_default();
477 let row_predicate = predicate
478 .as_ref()
479 .filter(|_| backend == ParquetReaderBackend::Direct)
480 .and_then(|predicate| kernel_row_predicate(predicate, snapshot.partition_columns()));
481 let kernel_predicate = predicate.as_ref().and_then(kernel_pruning_predicate);
482 let include_stats = kernel_predicate.is_some();
483 let execution_options = self.execution_options;
484 let target_partitions = self.target_partitions;
485 let result = tokio::task::spawn_blocking(move || {
486 let same_predicate = row_predicate.is_some() && row_predicate == kernel_predicate;
488 let plan = plan_scan(
489 snapshot.as_ref(),
490 projection.as_deref(),
491 &hidden_columns,
492 kernel_predicate,
493 include_stats,
494 execution_options,
495 DeltaScanPartitionTargetOptions {
496 explicit_target_partitions: target_partitions,
497 datafusion_target_partitions: None,
498 },
499 )?;
500 let physical_row_predicate = if plan.partitions.is_empty() {
501 None
502 } else if same_predicate {
503 plan.physical_predicate.clone()
504 } else {
505 build_physical_row_predicate(
506 snapshot.as_ref(),
507 projection.as_deref(),
508 &hidden_columns,
509 row_predicate,
510 )?
511 };
512 Ok((plan, physical_row_predicate))
513 })
514 .await
515 .boxed()
516 .context(ScanPlanningSnafu {
517 reason: "scan_planning_task_failed",
518 })
519 .and_then(|result| result);
520
521 match result {
522 Ok((plan, physical_row_predicate)) => {
523 trace_planning_completed(
524 snapshot_version,
525 backend,
526 plan.partitions.len(),
527 scan_metadata_source,
528 );
529 Ok(DeltaScan {
530 plan: Arc::new(plan),
531 predicate,
532 limit: self.limit,
533 physical_row_predicate,
534 })
535 }
536 Err(error) => {
537 trace_planning_failed(snapshot_version, backend, scan_metadata_source, &error);
538 Err(error)
539 }
540 }
541 }
542}
543
544#[must_use = "scans do nothing unless converted into a stream"]
565pub struct DeltaScan {
566 plan: Arc<DeltaScanPlan>,
567 predicate: Option<DeltaPredicate>,
568 limit: Option<usize>,
569 physical_row_predicate: Option<DeltaKernelPredicate>,
570}
571
572impl DeltaScan {
573 pub fn schema(&self) -> SchemaRef {
575 Arc::clone(&self.plan.projected_schema)
576 }
577
578 pub fn partition_count(&self) -> usize {
580 self.plan.partitions.len()
581 }
582
583 pub fn into_stream(self) -> DeltaBatchStream {
587 let metrics = self.plan.metrics.clone();
588 let schema = Arc::clone(&self.plan.projected_schema);
589 let partition_count = self.plan.partitions.len();
590 let snapshot_version = self.plan.snapshot_version;
591 let backend = self.plan.execution_options.parquet_backend();
592 let projection = (self.plan.logical_schema.as_ref() != schema.as_ref())
593 .then(|| (0..schema.fields().len()).collect::<Vec<_>>());
594 let partitions = if self.limit == Some(0) {
595 OrderedPartitionStream::default()
596 } else {
597 let scheduler = DeltaScanScheduler::new(Arc::clone(&self.plan));
598 let admission: FileAdmissionPolicy<_> = Arc::new(|_| Ok(FileAdmissionDecision::Admit));
599 let executor = match backend {
600 ParquetReaderBackend::Direct => {
601 direct_parquet_executor(&self.plan, None, self.physical_row_predicate)
602 }
603 ParquetReaderBackend::DeltaKernel => delta_kernel_executor(&self.plan),
604 };
605 scheduler.partition_streams(admission, executor)
606 };
607
608 DeltaBatchStream {
609 schema,
610 metrics,
611 partitions,
612 predicate: self.predicate,
613 projection,
614 remaining: self.limit,
615 snapshot_version,
616 backend,
617 partition_count,
618 started: false,
619 done: false,
620 }
621 }
622}
623
624#[must_use = "streams do nothing unless polled"]
637pub struct DeltaBatchStream {
638 schema: SchemaRef,
639 metrics: DeltaScanMetrics,
640 partitions: OrderedPartitionStream,
641 predicate: Option<DeltaPredicate>,
642 projection: Option<Vec<usize>>,
643 remaining: Option<usize>,
644 snapshot_version: u64,
645 backend: ParquetReaderBackend,
646 partition_count: usize,
647 started: bool,
648 done: bool,
649}
650
651impl DeltaBatchStream {
652 pub async fn next_batch(&mut self) -> Result<Option<RecordBatch>, DeltaReaderError> {
658 futures_util::TryStreamExt::try_next(self).await
659 }
660
661 pub fn schema(&self) -> SchemaRef {
663 Arc::clone(&self.schema)
664 }
665
666 pub fn metrics(&self) -> DeltaScanMetrics {
668 self.metrics.clone()
669 }
670
671 fn start(&mut self) {
672 if self.started {
673 return;
674 }
675 self.started = true;
676 trace_execution_started(self.snapshot_version, self.backend, self.partition_count);
677 }
678
679 fn complete(&mut self) {
680 if self.done {
681 return;
682 }
683 self.partitions.clear();
684 self.done = true;
685 trace_execution_completed(self.snapshot_version, self.backend, self.partition_count);
686 }
687
688 fn fail(&mut self, error: &DeltaReaderError) {
689 self.partitions.clear();
690 self.done = true;
691 trace_execution_failed(
692 self.snapshot_version,
693 self.backend,
694 self.partition_count,
695 error,
696 );
697 }
698
699 fn finalize_batch(&self, mut batch: RecordBatch) -> Result<RecordBatch, DeltaReaderError> {
700 if let Some(predicate) = self.predicate.as_ref() {
701 batch = evaluate_predicate(&batch, predicate)?;
702 }
703 if let Some(projection) = self.projection.as_ref() {
704 batch = batch
705 .project(projection)
706 .boxed()
707 .context(DataFileReadSnafu {
708 reason: "direct_projection_failed",
709 })?;
710 }
711 Ok(batch)
712 }
713}
714
715impl Stream for DeltaBatchStream {
716 type Item = Result<RecordBatch, DeltaReaderError>;
717
718 fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
719 let this = self.get_mut();
720 if this.done {
721 return Poll::Ready(None);
722 }
723 this.start();
724
725 match Pin::new(&mut this.partitions).poll_next(context) {
726 Poll::Ready(Some(Ok(batch))) => {
727 let mut batch = match this.finalize_batch(batch) {
728 Ok(batch) => batch,
729 Err(error) => {
730 this.fail(&error);
731 return Poll::Ready(Some(Err(error)));
732 }
733 };
734 if let Some(remaining) = this.remaining.as_mut() {
735 if batch.num_rows() >= *remaining {
736 batch = batch.slice(0, *remaining);
737 *remaining = 0;
738 this.complete();
739 } else {
740 *remaining -= batch.num_rows();
741 }
742 }
743 Poll::Ready(Some(Ok(batch)))
744 }
745 Poll::Ready(Some(Err(error))) => {
746 this.fail(&error);
747 Poll::Ready(Some(Err(error)))
748 }
749 Poll::Ready(None) => {
750 this.complete();
751 Poll::Ready(None)
752 }
753 Poll::Pending => Poll::Pending,
754 }
755 }
756}
757
758impl Drop for DeltaBatchStream {
759 fn drop(&mut self) {
760 if self.done {
761 return;
762 }
763 self.partitions.clear();
764 self.done = true;
765 trace_execution_dropped(self.snapshot_version, self.backend, self.partition_count);
766 }
767}
768
769pub(crate) fn direct_parquet_executor(
770 plan: &Arc<DeltaScanPlan>,
771 output_batch_size_rows: Option<usize>,
772 row_predicate: Option<crate::delta::kernel::DeltaKernelPredicate>,
773) -> FileExecutor<planning::DeltaScanFileTask, FileBatchStream> {
774 backend::direct_parquet::direct_parquet_file_executor(
775 plan,
776 output_batch_size_rows,
777 row_predicate,
778 Arc::default(),
779 None,
780 )
781}
782
783pub(crate) fn delta_kernel_executor(
784 plan: &Arc<DeltaScanPlan>,
785) -> FileExecutor<planning::DeltaScanFileTask, FileBatchStream> {
786 backend::kernel_reader::delta_kernel_file_executor(plan)
787}
788
789fn trace_planning_started(
790 snapshot_version: u64,
791 backend: ParquetReaderBackend,
792 scan_metadata_source: &'static str,
793) {
794 tracing::debug!(
795 target: TRACING_TARGET,
796 event = "scan_planning.started",
797 snapshot_version,
798 backend = ?backend,
799 partition_count = tracing::field::Empty,
800 scan_metadata_source,
801 outcome = "started"
802 );
803}
804
805fn trace_planning_completed(
806 snapshot_version: u64,
807 backend: ParquetReaderBackend,
808 partition_count: usize,
809 scan_metadata_source: &'static str,
810) {
811 tracing::debug!(
812 target: TRACING_TARGET,
813 event = "scan_planning.completed",
814 snapshot_version,
815 backend = ?backend,
816 partition_count,
817 scan_metadata_source,
818 outcome = "completed"
819 );
820}
821
822fn trace_planning_failed(
823 snapshot_version: u64,
824 backend: ParquetReaderBackend,
825 scan_metadata_source: &'static str,
826 error: &DeltaReaderError,
827) {
828 tracing::debug!(
829 target: TRACING_TARGET,
830 event = "scan_planning.failed",
831 snapshot_version,
832 backend = ?backend,
833 partition_count = tracing::field::Empty,
834 scan_metadata_source,
835 outcome = "failed",
836 error_code = error.code(),
837 error_phase = error.phase().as_str()
838 );
839}
840
841fn trace_execution_started(
842 snapshot_version: u64,
843 backend: ParquetReaderBackend,
844 partition_count: usize,
845) {
846 tracing::debug!(
847 target: TRACING_TARGET,
848 event = "scan_execution.started",
849 snapshot_version,
850 backend = ?backend,
851 partition_count,
852 outcome = "started"
853 );
854}
855
856fn trace_execution_completed(
857 snapshot_version: u64,
858 backend: ParquetReaderBackend,
859 partition_count: usize,
860) {
861 tracing::debug!(
862 target: TRACING_TARGET,
863 event = "scan_execution.completed",
864 snapshot_version,
865 backend = ?backend,
866 partition_count,
867 outcome = "completed"
868 );
869}
870
871fn trace_execution_failed(
872 snapshot_version: u64,
873 backend: ParquetReaderBackend,
874 partition_count: usize,
875 error: &DeltaReaderError,
876) {
877 tracing::debug!(
878 target: TRACING_TARGET,
879 event = "scan_execution.failed",
880 snapshot_version,
881 backend = ?backend,
882 partition_count,
883 outcome = "failed",
884 error_code = error.code(),
885 error_phase = error.phase().as_str()
886 );
887}
888
889fn trace_execution_dropped(
890 snapshot_version: u64,
891 backend: ParquetReaderBackend,
892 partition_count: usize,
893) {
894 tracing::debug!(
895 target: TRACING_TARGET,
896 event = "scan_execution.dropped",
897 snapshot_version,
898 backend = ?backend,
899 partition_count,
900 outcome = "dropped"
901 );
902}
903
904#[cfg(test)]
905mod tests {
906 use std::{
907 collections::{BTreeMap, VecDeque},
908 fmt, fs,
909 future::pending,
910 path::{Path, PathBuf},
911 sync::{Arc, Mutex, Once},
912 time::{Duration, SystemTime, UNIX_EPOCH},
913 };
914
915 use arrow::{
916 array::Int32Array,
917 datatypes::{DataType, Field, Schema, SchemaRef},
918 record_batch::RecordBatch,
919 };
920 use futures_util::{FutureExt, StreamExt, stream};
921 use tokio::{sync::Notify, time::timeout};
922 use tracing::{
923 Event, Level, Metadata, Subscriber,
924 field::{Field as TracingField, Visit},
925 span::{Attributes, Id, Record},
926 subscriber::{Interest, with_default},
927 };
928
929 use super::{
930 DeltaBatchStream, DeltaTable, trace_execution_completed, trace_execution_dropped,
931 trace_execution_failed, trace_execution_started, trace_planning_completed,
932 trace_planning_failed, trace_planning_started,
933 };
934 use crate::{
935 DeltaScanExecutionOptions, DeltaScanMetrics, DeltaSnapshotSelection, DeltaStorageOptions,
936 ParquetReaderBackend,
937 delta::snapshot::load_delta_table_snapshot_blocking,
938 error::InvalidConfigurationSnafu,
939 reader::{
940 metrics::DeltaScanMetricsConfig,
941 scheduling::{
942 FileAdmissionDecision, FileAdmissionPolicy, FileBatchStream, FileExecutor,
943 FileReadPermit, OrderedPartitionStream, PartitionStream, ScanCancellation,
944 ScanReadLimiter,
945 },
946 },
947 };
948
949 static TRACING_TEST_LOCK: Mutex<()> = Mutex::new(());
950 static TRACING_TEST_GLOBAL_SUBSCRIBER: Once = Once::new();
951
952 #[derive(Clone, Default)]
953 struct EventFields(Arc<Mutex<Vec<BTreeMap<String, String>>>>);
954
955 impl Subscriber for EventFields {
956 fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
957 if metadata.target() == "delta_arrow_reader" && *metadata.level() == Level::DEBUG {
958 Interest::always()
959 } else {
960 Interest::sometimes()
961 }
962 }
963
964 fn enabled(&self, metadata: &Metadata<'_>) -> bool {
965 metadata.target() == "delta_arrow_reader" && *metadata.level() == Level::DEBUG
966 }
967
968 fn new_span(&self, _attributes: &Attributes<'_>) -> Id {
969 Id::from_u64(1)
970 }
971
972 fn record(&self, _span: &Id, _values: &Record<'_>) {}
973
974 fn record_follows_from(&self, _span: &Id, _follows: &Id) {}
975
976 fn event(&self, event: &Event<'_>) {
977 let metadata = event.metadata();
978 assert_eq!(metadata.target(), "delta_arrow_reader");
979 let mut fields = metadata
980 .fields()
981 .iter()
982 .map(|field| (field.name().to_owned(), "<empty>".to_owned()))
983 .collect();
984 event.record(&mut FieldVisitor(&mut fields));
985 self.0.lock().expect("event lock").push(fields);
986 }
987
988 fn enter(&self, _span: &Id) {}
989
990 fn exit(&self, _span: &Id) {}
991 }
992
993 struct FieldVisitor<'fields>(&'fields mut BTreeMap<String, String>);
994
995 impl Visit for FieldVisitor<'_> {
996 fn record_debug(&mut self, field: &TracingField, value: &dyn fmt::Debug) {
997 self.0.insert(field.name().to_owned(), format!("{value:?}"));
998 }
999
1000 fn record_str(&mut self, field: &TracingField, value: &str) {
1001 self.0.insert(field.name().to_owned(), value.to_owned());
1002 }
1003
1004 fn record_u64(&mut self, field: &TracingField, value: u64) {
1005 self.0.insert(field.name().to_owned(), value.to_string());
1006 }
1007
1008 fn record_i64(&mut self, field: &TracingField, value: i64) {
1009 self.0.insert(field.name().to_owned(), value.to_string());
1010 }
1011
1012 fn record_u128(&mut self, field: &TracingField, value: u128) {
1013 self.0.insert(field.name().to_owned(), value.to_string());
1014 }
1015 }
1016
1017 struct DeltaLogTable(PathBuf);
1018
1019 impl DeltaLogTable {
1020 fn new(name: &str) -> Result<Self, Box<dyn std::error::Error>> {
1021 let nanos = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
1022 let path = Path::new("target")
1023 .join("delta-arrow-reader-tracing-tests")
1024 .join(format!("{}-{name}-{nanos}", std::process::id()));
1025 fs::create_dir_all(path.join("_delta_log"))?;
1026 fs::write(
1027 path.join("_delta_log/00000000000000000000.json"),
1028 r#"{"protocol":{"minReaderVersion":1,"minWriterVersion":2}}
1029{"metaData":{"id":"tracing-test","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1587968585495}}
1030{"add":{"path":"secret-planning-object.parquet","partitionValues":{},"size":10,"modificationTime":1587968586000,"dataChange":true}}
1031"#,
1032 )?;
1033 Ok(Self(path))
1034 }
1035 }
1036
1037 impl Drop for DeltaLogTable {
1038 fn drop(&mut self) {
1039 let _ = fs::remove_dir_all(&self.0);
1040 }
1041 }
1042
1043 fn capture_events<T>(run: impl FnOnce() -> T) -> (T, Vec<BTreeMap<String, String>>) {
1044 let _lock = TRACING_TEST_LOCK
1045 .lock()
1046 .unwrap_or_else(std::sync::PoisonError::into_inner);
1047 let events = Arc::new(Mutex::new(Vec::new()));
1048 let subscriber = EventFields(Arc::clone(&events));
1049 TRACING_TEST_GLOBAL_SUBSCRIBER.call_once(|| {
1050 let _ = tracing::subscriber::set_global_default(EventFields::default());
1051 });
1052 let result = with_default(subscriber, || {
1053 tracing::callsite::rebuild_interest_cache();
1054 run()
1055 });
1056 tracing::callsite::rebuild_interest_cache();
1057 let captured = events
1058 .lock()
1059 .map(|events| events.clone())
1060 .unwrap_or_default();
1061 (result, captured)
1062 }
1063
1064 struct ControlledMerge {
1065 stream: DeltaBatchStream,
1066 limiter: Arc<ScanReadLimiter>,
1067 cancellation: ScanCancellation,
1068 metrics: DeltaScanMetrics,
1069 first_partition_gate: Arc<Notify>,
1070 }
1071
1072 fn schema() -> SchemaRef {
1073 Arc::new(Schema::new(vec![Field::new("id", DataType::Int32, false)]))
1074 }
1075
1076 fn batch(id: i32) -> RecordBatch {
1077 RecordBatch::try_new(schema(), vec![Arc::new(Int32Array::from(vec![id]))])
1078 .expect("valid test batch")
1079 }
1080
1081 fn batch_id(batch: &RecordBatch) -> i32 {
1082 batch
1083 .column(0)
1084 .as_any()
1085 .downcast_ref::<Int32Array>()
1086 .expect("Int32 id")
1087 .value(0)
1088 }
1089
1090 fn execution_options() -> Result<DeltaScanExecutionOptions, crate::DeltaReaderError> {
1091 DeltaScanExecutionOptions::new()
1092 .with_prefetch_files_per_partition(0)
1093 .with_max_concurrent_file_reads_per_partition(1)?
1094 .with_max_concurrent_file_reads_per_scan(Some(2))?
1095 .with_output_buffer_batches_per_partition(1)
1096 }
1097
1098 fn metrics() -> DeltaScanMetrics {
1099 DeltaScanMetrics::new(DeltaScanMetricsConfig {
1100 snapshot_version: 7,
1101 parquet_backend: ParquetReaderBackend::Direct,
1102 scan_partitions_planned: 2,
1103 files_planned: 2,
1104 add_actions_excluded_during_planning: Some(0),
1105 estimated_input_rows: Some(4),
1106 estimated_input_bytes: Some(4),
1107 })
1108 }
1109
1110 fn file_stream(permit: FileReadPermit, batches: Vec<RecordBatch>) -> FileBatchStream {
1111 Box::pin(stream::unfold(
1112 (VecDeque::from(batches), permit),
1113 |(mut batches, permit)| async move {
1114 batches
1115 .pop_front()
1116 .map(|batch| (Ok(batch), (batches, permit)))
1117 },
1118 ))
1119 }
1120
1121 fn gated_file_stream(
1122 permit: FileReadPermit,
1123 batches: Vec<RecordBatch>,
1124 gate: Arc<Notify>,
1125 ) -> FileBatchStream {
1126 Box::pin(stream::unfold(
1127 (false, VecDeque::from(batches), permit, gate),
1128 |(wait, mut batches, permit, gate)| async move {
1129 let batch = batches.pop_front()?;
1130 if wait {
1131 gate.notified().await;
1132 }
1133 Some((Ok(batch), (true, batches, permit, gate)))
1134 },
1135 ))
1136 }
1137
1138 fn direct_stream(
1139 partitions: VecDeque<PartitionStream>,
1140 metrics: DeltaScanMetrics,
1141 scan_capacity: usize,
1142 ) -> DeltaBatchStream {
1143 DeltaBatchStream {
1144 schema: schema(),
1145 metrics,
1146 partitions: OrderedPartitionStream::new(partitions, scan_capacity),
1147 predicate: None,
1148 projection: None,
1149 remaining: None,
1150 snapshot_version: 7,
1151 backend: ParquetReaderBackend::Direct,
1152 partition_count: 2,
1153 started: false,
1154 done: false,
1155 }
1156 }
1157
1158 fn controlled_merge() -> Result<ControlledMerge, Box<dyn std::error::Error>> {
1159 let options = execution_options()?;
1160 let limiter = ScanReadLimiter::new(options, 2, 2);
1161 let cancellation = ScanCancellation::new();
1162 let metrics = metrics();
1163 let first_partition_gate = Arc::new(Notify::new());
1164 let executor: FileExecutor<i32, FileBatchStream> = {
1165 let gate = Arc::clone(&first_partition_gate);
1166 Arc::new(move |task, permit, _| {
1167 let gate = Arc::clone(&gate);
1168 async move {
1169 let batches = vec![batch(task), batch(task * 2)];
1170 Ok(if task == 1 {
1171 gated_file_stream(permit, batches, gate)
1172 } else {
1173 file_stream(permit, batches)
1174 })
1175 }
1176 .boxed()
1177 })
1178 };
1179 let admission: FileAdmissionPolicy<i32> =
1180 Arc::new(|_: &i32| Ok(FileAdmissionDecision::Admit));
1181 let first = PartitionStream::new(
1182 vec![1],
1183 limiter.partition(0)?,
1184 options,
1185 admission.clone(),
1186 Arc::clone(&executor),
1187 metrics.clone(),
1188 cancellation.clone(),
1189 );
1190 let second = PartitionStream::new(
1191 vec![10],
1192 limiter.partition(1)?,
1193 options,
1194 admission,
1195 executor,
1196 metrics.clone(),
1197 cancellation.clone(),
1198 );
1199
1200 Ok(ControlledMerge {
1201 stream: direct_stream(VecDeque::from([first, second]), metrics.clone(), 2),
1202 limiter,
1203 cancellation,
1204 metrics,
1205 first_partition_gate,
1206 })
1207 }
1208
1209 async fn wait_for_batches(metrics: &DeltaScanMetrics, expected: u64) {
1210 timeout(Duration::from_secs(5), async {
1211 while metrics.snapshot().scheduler_batches_emitted < expected {
1212 tokio::task::yield_now().await;
1213 }
1214 })
1215 .await
1216 .expect("batch production reached expected bound");
1217 }
1218
1219 #[test]
1220 fn lifecycle_tracing_has_only_bounded_fields() {
1221 let error = InvalidConfigurationSnafu { reason: "test" }.build();
1222
1223 let (_, events) = capture_events(|| {
1224 trace_planning_started(7, ParquetReaderBackend::Direct, "query_planning_cache");
1225 trace_planning_completed(7, ParquetReaderBackend::Direct, 2, "query_planning_cache");
1226 trace_planning_failed(
1227 7,
1228 ParquetReaderBackend::Direct,
1229 "query_planning_cache",
1230 &error,
1231 );
1232 trace_execution_started(7, ParquetReaderBackend::Direct, 2);
1233 trace_execution_completed(7, ParquetReaderBackend::Direct, 2);
1234 trace_execution_failed(7, ParquetReaderBackend::Direct, 2, &error);
1235 trace_execution_dropped(7, ParquetReaderBackend::Direct, 2);
1236 });
1237
1238 assert_eq!(events.len(), 7);
1239 let allowed = [
1240 "backend",
1241 "error_phase",
1242 "error_code",
1243 "event",
1244 "outcome",
1245 "partition_count",
1246 "scan_metadata_source",
1247 "snapshot_version",
1248 ];
1249 for fields in events.iter() {
1250 assert!(fields.keys().all(|field| allowed.contains(&field.as_str())));
1251 assert!(fields.contains_key("event"));
1252 assert!(fields.contains_key("snapshot_version"));
1253 assert!(fields.contains_key("backend"));
1254 assert!(fields.contains_key("partition_count"));
1255 assert!(fields.contains_key("outcome"));
1256 if fields
1257 .get("event")
1258 .is_some_and(|event| event.starts_with("scan_planning."))
1259 {
1260 assert_eq!(
1261 fields.get("scan_metadata_source").map(String::as_str),
1262 Some("query_planning_cache")
1263 );
1264 }
1265 }
1266 }
1267
1268 #[test]
1269 fn planning_tracing_reports_the_table_metadata_source_on_success_and_failure()
1270 -> Result<(), Box<dyn std::error::Error>> {
1271 const OBJECT_KEY: &str = "secret-planning-object.parquet";
1272 const STORAGE_VALUE: &str = "secret-planning-storage-value";
1273 let fixture = DeltaLogTable::new("planning-source")?;
1274 let mut storage_options = DeltaStorageOptions::new();
1275 storage_options.insert("secret-option".to_owned(), STORAGE_VALUE.to_owned());
1276 let snapshot = load_delta_table_snapshot_blocking(
1277 &fixture.0.to_string_lossy(),
1278 &storage_options,
1279 DeltaSnapshotSelection::Latest,
1280 )?;
1281 let eager_snapshot = snapshot.clone().materialize_eager_scan_metadata()?;
1282 let lazy = DeltaTable::new(snapshot, DeltaScanExecutionOptions::new());
1283 let eager = DeltaTable::new(eager_snapshot, DeltaScanExecutionOptions::new());
1284 let runtime = tokio::runtime::Builder::new_current_thread()
1285 .enable_all()
1286 .build()?;
1287
1288 let (eager_result, eager_events) =
1289 capture_events(|| runtime.block_on(eager.scan().build()));
1290 let _ = eager_result?;
1291 assert_eq!(eager_events.len(), 2);
1292 assert_eq!(
1293 eager_events[0].get("event").map(String::as_str),
1294 Some("scan_planning.started")
1295 );
1296 assert_eq!(
1297 eager_events[1].get("event").map(String::as_str),
1298 Some("scan_planning.completed")
1299 );
1300 assert!(eager_events.iter().all(|event| {
1301 event.get("scan_metadata_source").map(String::as_str) == Some("query_planning_cache")
1302 }));
1303
1304 let (failed_result, failed_events) =
1305 capture_events(|| runtime.block_on(eager.scan().with_projection(["missing"]).build()));
1306 assert!(failed_result.is_err());
1307 assert_eq!(failed_events.len(), 2);
1308 assert_eq!(
1309 failed_events[0].get("event").map(String::as_str),
1310 Some("scan_planning.started")
1311 );
1312 assert_eq!(
1313 failed_events[1].get("event").map(String::as_str),
1314 Some("scan_planning.failed")
1315 );
1316 assert!(failed_events.iter().all(|event| {
1317 event.get("scan_metadata_source").map(String::as_str) == Some("query_planning_cache")
1318 }));
1319
1320 let (lazy_result, lazy_events) = capture_events(|| runtime.block_on(lazy.scan().build()));
1321 let _ = lazy_result?;
1322 assert_eq!(lazy_events.len(), 2);
1323 assert!(lazy_events.iter().all(|event| {
1324 event.get("scan_metadata_source").map(String::as_str) == Some("delta_log")
1325 }));
1326
1327 let captured = format!("{eager_events:?}{failed_events:?}{lazy_events:?}");
1328 assert!(!captured.contains(&fixture.0.to_string_lossy().into_owned()));
1329 assert!(!captured.contains(OBJECT_KEY));
1330 assert!(!captured.contains(STORAGE_VALUE));
1331 Ok(())
1332 }
1333
1334 #[tokio::test]
1335 async fn merged_stream_stops_without_admitting_waiting_partitions()
1336 -> Result<(), Box<dyn std::error::Error>> {
1337 for stop in ["limit", "drop", "error"] {
1338 let options = execution_options()?.with_max_concurrent_file_reads_per_scan(Some(1))?;
1339 let limiter = ScanReadLimiter::new(options, 3, 3);
1340 let cancellation = ScanCancellation::new();
1341 let metrics = metrics();
1342 let executor: FileExecutor<i32, FileBatchStream> = Arc::new(move |task, permit, _| {
1343 async move {
1344 Ok(if stop == "error" {
1345 Box::pin(stream::once(async move {
1346 let _permit = permit;
1347 Err(InvalidConfigurationSnafu {
1348 reason: "controlled_partition_failure",
1349 }
1350 .build())
1351 })) as FileBatchStream
1352 } else {
1353 gated_file_stream(permit, vec![batch(task); 3], Arc::new(Notify::new()))
1354 })
1355 }
1356 .boxed()
1357 });
1358 let partitions = (0..3)
1359 .map(|index| {
1360 Ok(PartitionStream::new(
1361 vec![index as i32; 2],
1362 limiter.partition(index)?,
1363 options,
1364 Arc::new(|_| Ok(FileAdmissionDecision::Admit)),
1365 Arc::clone(&executor),
1366 metrics.clone(),
1367 cancellation.clone(),
1368 ))
1369 })
1370 .collect::<Result<VecDeque<_>, crate::DeltaReaderError>>()?;
1371 let mut stream = direct_stream(partitions, metrics.clone(), 1);
1372 if stop == "limit" {
1373 stream.remaining = Some(1);
1374 }
1375 let first = timeout(Duration::from_secs(5), stream.next())
1376 .await?
1377 .ok_or("missing result")?;
1378 if stop == "error" {
1379 assert_eq!(
1380 first.expect_err("controlled failure").code(),
1381 "invalid_configuration"
1382 );
1383 } else {
1384 assert_eq!(batch_id(&first?), 0);
1385 }
1386 if stop != "drop" {
1387 assert!(stream.next().await.is_none());
1388 }
1389 drop(stream);
1390 timeout(Duration::from_secs(5), async {
1391 while limiter.active_file_reads() != 0 {
1392 tokio::task::yield_now().await;
1393 }
1394 })
1395 .await?;
1396 assert!(cancellation.is_cancelled());
1397 assert_eq!(metrics.snapshot().file_tasks_started, 1);
1398 assert_eq!(metrics.snapshot().scan_partitions_started, 1);
1399 }
1400 Ok(())
1401 }
1402
1403 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1404 async fn merged_stream_progresses_with_small_scan_read_limits()
1405 -> Result<(), Box<dyn std::error::Error>> {
1406 for backend in [
1407 ParquetReaderBackend::Direct,
1408 ParquetReaderBackend::DeltaKernel,
1409 ] {
1410 for cap in [1, 2, 4, 8] {
1411 for prefetch in [0, 2, usize::MAX] {
1412 let options = DeltaScanExecutionOptions::new()
1413 .with_parquet_backend(backend)
1414 .with_max_concurrent_file_reads_per_scan(Some(cap))?
1415 .with_prefetch_files_per_partition(prefetch);
1416 let limiter = ScanReadLimiter::new(options, 5, 5);
1417 let cancellation = ScanCancellation::new();
1418 let metrics = metrics();
1419 let executor: FileExecutor<i32, FileBatchStream> =
1420 Arc::new(|task, permit, _| {
1421 async move {
1422 Ok(file_stream(permit, vec![batch(task); 3]))
1425 }
1426 .boxed()
1427 });
1428 let partitions = (0..5)
1429 .map(|partition| {
1430 Ok(PartitionStream::new(
1431 (partition * 4..partition * 4 + 4)
1432 .map(|id| id as i32)
1433 .collect(),
1434 limiter.partition(partition)?,
1435 options,
1436 Arc::new(|_| Ok(FileAdmissionDecision::Admit)),
1437 Arc::clone(&executor),
1438 metrics.clone(),
1439 cancellation.clone(),
1440 ))
1441 })
1442 .collect::<Result<VecDeque<_>, crate::DeltaReaderError>>()?;
1443 let mut stream = direct_stream(partitions, metrics.clone(), cap);
1444 let ids = timeout(Duration::from_secs(2), async {
1445 let mut ids = Vec::new();
1446 while let Some(batch) = stream.next().await {
1447 ids.push(batch_id(&batch?));
1448 assert!(limiter.active_file_reads() <= cap);
1449 tokio::task::yield_now().await;
1450 }
1451 Ok::<_, crate::DeltaReaderError>(ids)
1452 })
1453 .await
1454 .unwrap_or_else(|_| {
1455 panic!("stalled: {backend:?}, cap={cap}, prefetch={prefetch}")
1456 })?;
1457 assert_eq!(ids, (0..20).flat_map(|id| [id; 3]).collect::<Vec<_>>());
1458 assert_eq!(limiter.active_file_reads(), 0);
1459 assert_eq!(metrics.snapshot().file_tasks_completed, 20);
1460 assert_eq!(metrics.snapshot().scan_partitions_completed, 5);
1461 }
1462 }
1463 }
1464 Ok(())
1465 }
1466
1467 #[tokio::test]
1468 async fn merged_stream_is_ordered_and_bounds_later_partition_queues()
1469 -> Result<(), Box<dyn std::error::Error>> {
1470 let ControlledMerge {
1471 mut stream,
1472 limiter,
1473 metrics,
1474 first_partition_gate,
1475 ..
1476 } = controlled_merge()?;
1477
1478 let first = stream.next().await.ok_or("first batch missing")??;
1479 assert_eq!(batch_id(&first), 1);
1480 wait_for_batches(&metrics, 2).await;
1481 for _ in 0..32 {
1482 tokio::task::yield_now().await;
1483 }
1484 assert_eq!(metrics.snapshot().scheduler_batches_emitted, 2);
1485 assert_eq!(limiter.active_file_reads(), 2);
1486
1487 assert!(stream.next_batch().now_or_never().is_none());
1489 first_partition_gate.notify_one();
1490 let mut ids = vec![batch_id(
1491 &stream.next_batch().await?.ok_or("second batch missing")?,
1492 )];
1493 while let Some(batch) = stream.next_batch().await? {
1494 ids.push(batch_id(&batch));
1495 }
1496 assert_eq!(ids, [2, 10, 20]);
1497 assert_eq!(metrics.snapshot().scheduler_batches_emitted, 4);
1498 assert_eq!(metrics.snapshot().scan_partitions_completed, 2);
1499 assert_eq!(limiter.active_file_reads(), 0);
1500 Ok(())
1501 }
1502
1503 #[tokio::test]
1504 async fn merged_stream_drop_cancels_blocked_partitions_and_releases_permits()
1505 -> Result<(), Box<dyn std::error::Error>> {
1506 let ControlledMerge {
1507 mut stream,
1508 limiter,
1509 cancellation,
1510 metrics,
1511 ..
1512 } = controlled_merge()?;
1513
1514 let first = stream.next().await.ok_or("first batch missing")??;
1515 assert_eq!(batch_id(&first), 1);
1516 wait_for_batches(&metrics, 2).await;
1517 assert_eq!(limiter.active_file_reads(), 2);
1518 drop(stream);
1519
1520 assert!(cancellation.is_cancelled());
1521 timeout(Duration::from_secs(5), async {
1522 while limiter.active_file_reads() != 0 {
1523 tokio::task::yield_now().await;
1524 }
1525 })
1526 .await?;
1527 assert_eq!(metrics.snapshot().scheduler_batches_emitted, 2);
1528 assert_eq!(metrics.snapshot().scan_partitions_completed, 0);
1529 Ok(())
1530 }
1531
1532 #[tokio::test]
1533 async fn merged_stream_forwards_one_concurrent_error_and_releases_permits()
1534 -> Result<(), Box<dyn std::error::Error>> {
1535 let options = execution_options()?;
1536 let limiter = ScanReadLimiter::new(options, 3, 3);
1537 let cancellation = ScanCancellation::new();
1538 let metrics = metrics();
1539 let executor: FileExecutor<i32, FileBatchStream> = Arc::new(|task, permit, _| {
1540 async move {
1541 Ok(if task == 1 {
1542 Box::pin(stream::once(async move {
1543 let _permit = permit;
1544 pending::<Result<RecordBatch, crate::DeltaReaderError>>().await
1545 })) as FileBatchStream
1546 } else {
1547 Box::pin(stream::once(async move {
1548 let _permit = permit;
1549 Err(InvalidConfigurationSnafu {
1550 reason: "controlled_partition_failure",
1551 }
1552 .build())
1553 })) as FileBatchStream
1554 })
1555 }
1556 .boxed()
1557 });
1558 let admission = Arc::new(|_: &i32| Ok(FileAdmissionDecision::Admit));
1559 let first = PartitionStream::new(
1560 vec![1],
1561 limiter.partition(0)?,
1562 options,
1563 admission.clone(),
1564 Arc::clone(&executor),
1565 metrics.clone(),
1566 cancellation.clone(),
1567 );
1568 let second = PartitionStream::new(
1569 vec![2],
1570 limiter.partition(1)?,
1571 options,
1572 admission.clone(),
1573 Arc::clone(&executor),
1574 metrics.clone(),
1575 cancellation.clone(),
1576 );
1577 let third = PartitionStream::new(
1578 vec![3],
1579 limiter.partition(2)?,
1580 options,
1581 admission,
1582 executor,
1583 metrics.clone(),
1584 cancellation.clone(),
1585 );
1586 let mut stream = direct_stream(VecDeque::from([first, second, third]), metrics.clone(), 2);
1587
1588 let error = timeout(Duration::from_secs(5), stream.next())
1589 .await?
1590 .ok_or("error item missing")?
1591 .expect_err("controlled partition must fail");
1592 assert_eq!(error.code(), "invalid_configuration");
1593 assert!(stream.next().await.is_none());
1594 assert!(cancellation.is_cancelled());
1595 assert_eq!(metrics.snapshot().file_tasks_started, 2);
1596 timeout(Duration::from_secs(5), async {
1597 while limiter.active_file_reads() != 0 {
1598 tokio::task::yield_now().await;
1599 }
1600 })
1601 .await?;
1602 Ok(())
1603 }
1604}