1use std::collections::BTreeMap;
4use std::ops::Range;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::task::{Context, Poll};
8
9use arrow_array::{RecordBatch, RecordBatchReader};
10use arrow_schema::{Schema, SchemaRef};
11use async_trait::async_trait;
12use futures::task::{ArcWake, AtomicWaker, waker_ref};
13use futures::{Stream, StreamExt as _, TryStreamExt as _, stream};
14use parking_lot::Mutex;
15use tokio::sync::mpsc;
16use tokio::task::{JoinHandle, spawn_blocking};
17use uuid::Uuid;
18
19use crate::scandal::TableBinding;
20pub use crate::scandal::api::{ColumnStatistics, ScanStatistics};
21use crate::scandal::expression::Expression;
22use crate::scandal::types::require_exact_filter;
23pub use crate::scandal::types::{
24 FilterClause, FilterSupport, ScanMetricsSnapshot, ScanOptions, ScanRowRange, StorageCounters,
25 StorageScope, TableSplit, WindowCounters,
26};
27use crate::{
28 Error, ExecutionDiagnostics, ExecutionOptions, PlanDescription, ResourceAddress, SortColumn,
29 TableIdentity, WriteLayout,
30};
31
32type MetricsProvider = Arc<dyn Fn() -> Result<Option<ScanMetricsSnapshot>, Error> + Send + Sync>;
33
34#[derive(Clone, Default)]
37pub struct ScanMetrics {
38 provider: Option<Arc<Mutex<Option<MetricsProvider>>>>,
39}
40
41impl ScanMetrics {
42 pub(crate) fn from_snapshot_fn(
44 provider: impl Fn() -> Result<ScanMetricsSnapshot, Error> + Send + Sync + 'static,
45 ) -> Self {
46 Self {
47 provider: Some(Arc::new(Mutex::new(Some(Arc::new(move || {
48 provider().map(Some)
49 }))))),
50 }
51 }
52
53 pub fn snapshot(&self) -> Result<Option<ScanMetricsSnapshot>, Error> {
56 let provider = self
57 .provider
58 .as_ref()
59 .and_then(|provider| provider.lock().clone());
60 provider.map_or(Ok(None), |provider| provider())
61 }
62
63 fn pending() -> Self {
64 Self {
65 provider: Some(Arc::new(Mutex::new(None))),
66 }
67 }
68
69 fn bind(&self, source: Self) {
70 if let (Some(provider), Some(_)) = (&self.provider, &source.provider) {
71 *provider.lock() = Some(Arc::new(move || source.snapshot()));
72 }
73 }
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct ScanSplit {
79 pub fragment: u64,
81 pub rows: Range<u64>,
83 pub estimated_rows: Option<u64>,
85 pub estimated_bytes: Option<u64>,
87 pub fragment_id: Option<Uuid>,
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
96pub struct WorkItem {
97 pub split: usize,
99 pub rows: Range<u64>,
101}
102
103#[derive(Clone, Debug, Eq, PartialEq)]
105pub struct Take {
106 pub split: usize,
108 pub positions: Vec<u64>,
112}
113
114#[derive(Clone, Debug)]
116pub(crate) struct ScanInfo {
117 pub schema: Schema,
119 pub source_fields: Option<BTreeMap<String, Uuid>>,
120 pub filter_clauses: Vec<FilterClause>,
122 pub write_layout: Option<WriteLayout>,
123 pub statistics: Option<ScanStatistics>,
124}
125
126pub trait SplitFilter: Send + Sync {
131 fn current(&self) -> Option<Expression>;
133}
134
135#[async_trait]
137pub(crate) trait ScanSource: Send + Sync {
138 async fn read(&self, narrowing: Option<Arc<dyn SplitFilter>>) -> Result<BatchStream, Error>;
143
144 fn ordered_partitions(&self) -> Option<(usize, Vec<SortColumn>)> {
146 None
147 }
148
149 fn read_partitions(
151 &self,
152 _narrowing: Option<Arc<dyn SplitFilter>>,
153 _lanes: usize,
154 _batch_rows: usize,
155 ) -> Option<Vec<BatchStream>> {
156 None
157 }
158
159 async fn table_binding(&self) -> Result<Option<TableBinding>, Error> {
162 Ok(None)
163 }
164
165 fn estimated_rows(&self) -> Option<u64> {
167 None
168 }
169
170 fn splits(&self) -> &[ScanSplit] {
172 &[]
173 }
174
175 async fn take(&self, _takes: Vec<Take>) -> Result<BatchStream, Error> {
177 Err(Error::invalid("this scan has no splits to take rows from"))
178 }
179
180 async fn read_work(&self, _work: Vec<WorkItem>) -> Result<WorkStream, Error> {
182 Err(Error::invalid("this scan has no splits to read"))
183 }
184
185 async fn count_rows(&self, _work: &[WorkItem]) -> Result<Vec<u64>, Error> {
187 Err(Error::invalid("this scan has no splits to count"))
188 }
189}
190
191#[derive(Clone)]
193pub struct Scan {
194 pub(crate) address: Option<ResourceAddress>,
195 identity: TableIdentity,
196 options: ScanOptions,
197 schema: SchemaRef,
198 source_fields: Option<BTreeMap<String, Uuid>>,
199 filter_clauses: Vec<FilterClause>,
200 write_layout: Option<WriteLayout>,
201 statistics: Option<ScanStatistics>,
202 stream_count: usize,
203 ordering: Vec<SortColumn>,
204 source: Arc<dyn ScanSource>,
205}
206
207impl Scan {
208 pub(crate) fn new(
213 identity: TableIdentity,
214 options: ScanOptions,
215 info: ScanInfo,
216 source: Arc<dyn ScanSource>,
217 ) -> Result<Self, Error> {
218 require_exact_filter(&info.filter_clauses)?;
219 let max_streams = options.stream_count()?;
220 let ordered = source.ordered_partitions().filter(|(count, _)| {
221 (*count == 1
222 || (options.partitioned && options.limit.is_none() && options.row_range.is_none()))
223 && options.emit.is_none()
224 });
225 let (stream_count, ordering) = ordered
226 .map(|(count, ordering)| (count.min(max_streams), ordering))
227 .unwrap_or((max_streams, Vec::new()));
228 Ok(Self {
229 address: None,
230 identity,
231 options,
232 schema: Arc::new(info.schema),
233 source_fields: info.source_fields,
234 filter_clauses: info.filter_clauses,
235 write_layout: info.write_layout,
236 statistics: info.statistics,
237 stream_count,
238 ordering,
239 source,
240 })
241 }
242
243 pub(crate) async fn table_binding(&self) -> Result<Option<TableBinding>, Error> {
245 self.source.table_binding().await
246 }
247
248 pub fn identity(&self) -> TableIdentity {
250 self.identity
251 }
252
253 pub fn resolved_address(&self) -> Option<&ResourceAddress> {
256 self.address.as_ref()
257 }
258
259 pub fn options(&self) -> &ScanOptions {
261 &self.options
262 }
263
264 pub fn schema(&self) -> SchemaRef {
266 Arc::clone(&self.schema)
267 }
268
269 pub fn source_fields(&self) -> Option<&BTreeMap<String, Uuid>> {
273 self.source_fields.as_ref()
274 }
275
276 pub fn write_layout(&self) -> Option<&WriteLayout> {
278 self.write_layout.as_ref()
279 }
280
281 pub fn statistics(&self) -> Option<&ScanStatistics> {
283 self.statistics.as_ref()
284 }
285
286 pub fn ordering(&self) -> &[SortColumn] {
288 &self.ordering
289 }
290
291 pub fn filter_clauses(&self) -> &[FilterClause] {
293 &self.filter_clauses
294 }
295
296 pub fn estimated_rows(&self) -> Option<u64> {
301 self.source.estimated_rows()
302 }
303
304 pub fn stream_count(&self) -> usize {
306 self.stream_count
307 }
308
309 pub fn splits(&self) -> &[ScanSplit] {
312 self.source.splits()
313 }
314
315 pub async fn read_work(&self, work: Vec<WorkItem>) -> Result<WorkStream, Error> {
322 self.validate_work(&work)?;
323 self.source.read_work(work).await
324 }
325
326 pub async fn count_rows(&self, work: &[WorkItem]) -> Result<Vec<u64>, Error> {
329 self.validate_work(work)?;
330 self.source.count_rows(work).await
331 }
332
333 pub async fn take(&self, takes: Vec<Take>) -> Result<BatchStream, Error> {
343 if self.options.filter.is_some()
344 || self.options.limit.is_some()
345 || self.options.emit.is_some()
346 {
347 return Err(Error::invalid(
348 "scans with a filter, a limit or an emit take no rows by position",
349 ));
350 }
351 let splits = self.splits().len();
352 if let Some(take) = takes.iter().find(|take| take.split >= splits) {
353 return Err(Error::invalid(format!(
354 "a take names split {} of a scan with {splits} splits",
355 take.split
356 )));
357 }
358 self.source.take(takes).await
359 }
360
361 fn validate_work(&self, work: &[WorkItem]) -> Result<(), Error> {
362 if self.options.limit.is_some() || self.options.emit.is_some() {
363 return Err(Error::invalid(
364 "scans with a limit or an emit read no work items",
365 ));
366 }
367 let splits = self.splits();
368 for item in work {
369 let split = splits.get(item.split).ok_or_else(|| {
370 Error::invalid(format!(
371 "work names split {} of a scan with {} splits",
372 item.split,
373 splits.len()
374 ))
375 })?;
376 if item.rows.start > item.rows.end
377 || item.rows.start < split.rows.start
378 || item.rows.end > split.rows.end
379 {
380 return Err(Error::invalid(format!(
381 "work rows {:?} are outside split {} rows {:?}",
382 item.rows, item.split, split.rows
383 )));
384 }
385 }
386 Ok(())
387 }
388
389 pub fn into_stream(self) -> BatchStream {
392 if !self.ordering.is_empty() {
393 return self
394 .source
395 .read_partitions(None, 1, self.options.batch_rows.unwrap_or(65_536))
396 .expect("ordered source supplies fixed partitions")
397 .pop()
398 .expect("one ordered lane");
399 }
400 self.open_stream(None)
401 }
402
403 pub fn into_streams(self) -> Vec<ScanStream> {
411 self.lanes(None)
412 }
413
414 pub fn into_narrowed_streams(self, filter: Arc<dyn SplitFilter>) -> Vec<ScanStream> {
417 self.lanes(Some(filter))
418 }
419
420 fn lanes(self, narrowing: Option<Arc<dyn SplitFilter>>) -> Vec<ScanStream> {
421 if !self.ordering.is_empty() {
422 return self
423 .source
424 .read_partitions(
425 narrowing,
426 self.stream_count,
427 self.options.batch_rows.unwrap_or(65_536),
428 )
429 .expect("ordered source supplies fixed partitions")
430 .into_iter()
431 .map(|stream| ScanStream {
432 schema: stream.schema(),
433 metrics: stream.metrics(),
434 diagnostics: stream.diagnostics(),
435 stream: Box::pin(stream),
436 })
437 .collect();
438 }
439 let count = self.stream_count;
440 let schema = self.schema();
441 let stream = self.open_stream(narrowing);
442 let metrics = stream.metrics();
443 let diagnostics = stream.diagnostics();
444 let shared = Arc::new(LaneSource {
445 state: Mutex::new(LaneState {
446 stream: Some(stream),
447 active: 0,
448 failure: None,
449 }),
450 waiters: (0..count).map(|_| AtomicWaker::new()).collect(),
451 });
452 (0..count)
453 .map(|index| ScanStream {
454 schema: Arc::clone(&schema),
455 metrics: metrics.clone(),
456 diagnostics: diagnostics.clone(),
457 stream: Box::pin(Lane {
458 shared: Arc::clone(&shared),
459 index,
460 active: false,
461 }),
462 })
463 .collect()
464 }
465
466 fn open_stream(self, narrowing: Option<Arc<dyn SplitFilter>>) -> BatchStream {
467 let schema = self.schema();
468 let metrics = if self.options.collect_metrics {
469 ScanMetrics::pending()
470 } else {
471 ScanMetrics::default()
472 };
473 let execution_metrics = metrics.clone();
474 let diagnostics = ExecutionDiagnostics::default();
475 let execution_diagnostics = diagnostics.clone();
476 let opened = stream::once(async move {
477 let batches = self.source.read(narrowing).await?;
478 execution_metrics.bind(batches.metrics());
479 execution_diagnostics.bind(batches.diagnostics());
480 if PlanDescription::row_schema(&batches.schema())
481 != PlanDescription::row_schema(&self.schema)
482 {
483 return Err(Error::arrow(
484 "opened stream schema differs from scan schema",
485 ));
486 }
487 Ok(batches)
488 });
489 BatchStream::from_stream(schema, opened.try_flatten())
490 .with_metrics(metrics)
491 .with_diagnostics(diagnostics)
492 }
493}
494
495pub struct ScanStream {
497 diagnostics: ExecutionDiagnostics,
498 schema: SchemaRef,
499 metrics: ScanMetrics,
500 stream: Pin<Box<dyn Stream<Item = Result<RecordBatch, Error>> + Send>>,
501}
502
503impl ScanStream {
504 pub fn metrics(&self) -> ScanMetrics {
506 self.metrics.clone()
507 }
508
509 pub fn schema(&self) -> SchemaRef {
511 Arc::clone(&self.schema)
512 }
513
514 pub fn into_stream(self) -> BatchStream {
516 BatchStream::from_stream(self.schema, self.stream)
517 .with_metrics(self.metrics)
518 .with_diagnostics(self.diagnostics)
519 }
520}
521
522struct LaneSource {
523 state: Mutex<LaneState>,
524 waiters: Vec<AtomicWaker>,
525}
526
527struct LaneState {
528 stream: Option<BatchStream>,
529 active: usize,
530 failure: Option<String>,
531}
532
533impl ArcWake for LaneSource {
534 fn wake_by_ref(shared: &Arc<Self>) {
535 for waiter in &shared.waiters {
536 waiter.wake();
537 }
538 }
539}
540
541struct Lane {
542 shared: Arc<LaneSource>,
543 index: usize,
544 active: bool,
545}
546
547impl Stream for Lane {
548 type Item = Result<RecordBatch, Error>;
549
550 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
551 if !self.active {
552 self.shared.state.lock().active += 1;
553 self.active = true;
554 }
555 self.shared.waiters[self.index].register(cx.waker());
556 let waker = waker_ref(&self.shared);
557 let mut source_context = Context::from_waker(&waker);
558 let result = {
559 let mut state = self.shared.state.lock();
560 if let Some(failure) = &state.failure {
561 return Poll::Ready(Some(Err(Error::arrow(failure))));
562 }
563 let Some(stream) = &mut state.stream else {
564 return Poll::Ready(None);
565 };
566 let result = Pin::new(stream).poll_next(&mut source_context);
567 match &result {
568 Poll::Ready(Some(Err(error))) => {
569 state.failure = Some(error.to_string());
570 state.stream = None;
571 }
572 Poll::Ready(None) => state.stream = None,
573 _ => {}
574 }
575 result
576 };
577 if result.is_ready() {
578 LaneSource::wake_by_ref(&self.shared);
579 }
580 result
581 }
582}
583
584impl Drop for Lane {
585 fn drop(&mut self) {
586 self.shared.waiters[self.index].take();
587 if self.active {
588 let mut state = self.shared.state.lock();
589 state.active -= 1;
590 if state.active == 0 && state.stream.take().is_some() {
591 state.failure = Some("scan execution cancelled".into());
592 }
593 }
594 }
595}
596
597#[derive(Clone, Debug)]
599pub struct WorkBatch {
600 pub batch: RecordBatch,
601 pub item: usize,
603 pub next_row: u64,
605}
606
607pub struct WorkStream {
609 schema: SchemaRef,
610 stream: Pin<Box<dyn Stream<Item = Result<WorkBatch, Error>> + Send>>,
611}
612
613impl WorkStream {
614 pub(crate) fn new(
615 schema: SchemaRef,
616 stream: impl Stream<Item = Result<WorkBatch, Error>> + Send + 'static,
617 ) -> Self {
618 Self {
619 schema,
620 stream: Box::pin(stream),
621 }
622 }
623
624 pub fn schema(&self) -> SchemaRef {
626 Arc::clone(&self.schema)
627 }
628
629 pub async fn next_batch(&mut self) -> Option<Result<WorkBatch, Error>> {
631 self.next().await
632 }
633}
634
635impl Stream for WorkStream {
636 type Item = Result<WorkBatch, Error>;
637 fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
638 self.stream.as_mut().poll_next(context)
639 }
640}
641
642pub struct BatchStream {
644 schema: SchemaRef,
645 diagnostics: ExecutionDiagnostics,
646 metrics: ScanMetrics,
647 stream: Pin<Box<dyn Stream<Item = Result<RecordBatch, Error>> + Send>>,
648}
649
650impl BatchStream {
651 pub fn from_stream(
653 schema: SchemaRef,
654 stream: impl Stream<Item = Result<RecordBatch, Error>> + Send + 'static,
655 ) -> Self {
656 let expected = Arc::clone(&schema);
657 let stream = stream::unfold(Some(stream.boxed()), move |source| {
658 let expected = Arc::clone(&expected);
659 async move {
660 let mut source = source?;
661 let batch = source.next().await?.and_then(|batch| {
662 if PlanDescription::row_schema(&batch.schema())
663 != PlanDescription::row_schema(&expected)
664 {
665 return Err(Error::arrow("batch schema differs from scan schema"));
666 }
667 Ok(batch)
668 });
669 let source = batch.is_ok().then_some(source);
670 Some((batch, source))
671 }
672 });
673 Self {
674 schema,
675 metrics: ScanMetrics::default(),
676 diagnostics: ExecutionDiagnostics::default(),
677 stream: Box::pin(stream),
678 }
679 }
680
681 pub fn with_execution_options(mut self, options: ExecutionOptions) -> Result<Self, Error> {
683 self.schema = Arc::new(options.with_schema(&self.schema)?);
684 Ok(self)
685 }
686
687 pub fn diagnostics(&self) -> ExecutionDiagnostics {
690 self.diagnostics.clone()
691 }
692
693 pub(crate) fn with_diagnostics(mut self, diagnostics: ExecutionDiagnostics) -> Self {
694 self.diagnostics = diagnostics;
695 self
696 }
697
698 pub fn with_metrics(mut self, metrics: ScanMetrics) -> Self {
700 self.metrics = metrics;
701 self
702 }
703
704 pub fn metrics(&self) -> ScanMetrics {
706 self.metrics.clone()
707 }
708
709 #[expect(
711 clippy::disallowed_methods,
712 reason = "The open SDK adapts blocking Arrow callbacks without a Vortex runtime"
713 )]
714 pub fn from_reader(mut reader: Box<dyn RecordBatchReader + Send>) -> Self {
715 let schema = reader.schema();
716 let (sender, receiver) = mpsc::channel(2);
717 let worker = spawn_blocking(move || {
718 while !sender.is_closed() {
719 let Some(batch) = reader.next() else { break };
720 let failed = batch.is_err();
721 if sender.blocking_send(batch.map_err(Error::arrow)).is_err() || failed {
722 break;
723 }
724 }
725 });
726 Self::from_stream(schema, ReaderStream { receiver, worker })
727 }
728
729 pub fn schema(&self) -> SchemaRef {
731 Arc::clone(&self.schema)
732 }
733
734 pub async fn next_batch(&mut self) -> Option<Result<RecordBatch, Error>> {
736 self.next().await
737 }
738}
739
740impl Stream for BatchStream {
741 type Item = Result<RecordBatch, Error>;
742 fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
743 for _ in 0..32 {
744 let next = self.stream.as_mut().poll_next(context);
745 if let Poll::Ready(Some(Ok(batch))) = &next {
746 match self.diagnostics.observe(batch.schema_ref()) {
747 Ok(true) if batch.num_rows() == 0 => continue,
748 Ok(_) => {}
749 Err(error) => return Poll::Ready(Some(Err(error.into()))),
750 }
751 }
752 return next;
753 }
754 context.waker().wake_by_ref();
755 Poll::Pending
756 }
757}
758
759impl Drop for BatchStream {
760 fn drop(&mut self) {
761 self.diagnostics.cancelled();
762 }
763}
764
765struct ReaderStream {
766 receiver: mpsc::Receiver<Result<RecordBatch, Error>>,
767 worker: JoinHandle<()>,
768}
769
770impl Stream for ReaderStream {
771 type Item = Result<RecordBatch, Error>;
772 fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
773 self.receiver.poll_recv(context)
774 }
775}
776
777impl Drop for ReaderStream {
778 fn drop(&mut self) {
779 self.receiver.close();
780 self.worker.abort();
781 }
782}
783
784#[cfg(test)]
785mod tests {
786 use std::sync::atomic::{AtomicUsize, Ordering};
787
788 use arrow_array::Int64Array;
789 use arrow_schema::{DataType, Field};
790 use uuid::Uuid;
791
792 use super::*;
793
794 struct WorkSource {
795 reads: AtomicUsize,
796 drops: Arc<AtomicUsize>,
797 schema: SchemaRef,
798 fail: bool,
799 }
800
801 struct WorkLifetime(Arc<AtomicUsize>);
802 impl Drop for WorkLifetime {
803 fn drop(&mut self) {
804 self.0.fetch_add(1, Ordering::SeqCst);
805 }
806 }
807
808 #[async_trait]
809 impl ScanSource for WorkSource {
810 async fn read(&self, _: Option<Arc<dyn SplitFilter>>) -> Result<BatchStream, Error> {
811 self.reads.fetch_add(1, Ordering::SeqCst);
812 let schema = Arc::clone(&self.schema);
813 let fail = self.fail;
814 let guard = WorkLifetime(Arc::clone(&self.drops));
815 let rows = Arc::new(AtomicUsize::new(0));
816 let counters = Arc::clone(&rows);
817 let metrics = ScanMetrics::from_snapshot_fn(move || {
818 Ok(ScanMetricsSnapshot {
819 output_rows: (counters.load(Ordering::Relaxed) as u64).into(),
820 ..Default::default()
821 })
822 });
823 Ok(BatchStream::from_stream(
824 Arc::clone(&schema),
825 stream::unfold((0, guard), move |(index, guard)| {
826 let schema = Arc::clone(&schema);
827 let rows = Arc::clone(&rows);
828 async move {
829 if index == 100 {
830 return None;
831 }
832 let batch = if fail && index == 1 {
833 Err(Error::arrow("work failed"))
834 } else {
835 Ok(RecordBatch::try_new(
836 schema,
837 vec![Arc::new(Int64Array::from(vec![index]))],
838 )
839 .unwrap())
840 };
841 if batch.is_ok() {
842 rows.fetch_add(1, Ordering::Relaxed);
843 }
844 Some((batch, (index + 1, guard)))
845 }
846 }),
847 )
848 .with_metrics(metrics))
849 }
850 }
851
852 fn work_scan(fail: bool) -> (Scan, Arc<WorkSource>) {
853 let schema = Arc::new(Schema::new(vec![Field::new(
854 "value",
855 DataType::Int64,
856 false,
857 )]));
858 let source = Arc::new(WorkSource {
859 reads: AtomicUsize::new(0),
860 drops: Arc::new(AtomicUsize::new(0)),
861 schema: Arc::clone(&schema),
862 fail,
863 });
864 let scan = Scan::new(
865 TableIdentity {
866 resource_id: Uuid::new_v4(),
867 version_id: Uuid::new_v4(),
868 },
869 ScanOptions {
870 collect_metrics: true,
871 partitioned: true,
872 max_streams: Some(2),
873 ..Default::default()
874 },
875 ScanInfo {
876 source_fields: None,
877 statistics: None,
878 write_layout: None,
879 schema: schema.as_ref().clone(),
880 filter_clauses: vec![],
881 },
882 source.clone(),
883 )
884 .unwrap();
885 (scan, source)
886 }
887
888 #[tokio::test]
889 async fn metrics_follow_execution_lifetime_and_replay() {
890 let (scan, _) = work_scan(false);
891 let mut lanes = scan.clone().into_streams().into_iter();
892 let first = lanes.next().unwrap();
893 let second = lanes.next().unwrap();
894 let first_metrics = first.metrics();
895 let second_metrics = second.metrics();
896 assert!(first_metrics.snapshot().unwrap().is_none());
897 let mut first = first.into_stream();
898 let mut second = second.into_stream();
899 first.next_batch().await.unwrap().unwrap();
900 second.next_batch().await.unwrap().unwrap();
901 assert_eq!(first_metrics.snapshot().unwrap().unwrap().output_rows.0, 2);
902 assert_eq!(
903 first_metrics.snapshot().unwrap(),
904 second_metrics.snapshot().unwrap()
905 );
906 drop(first);
907 drop(second);
908 assert_eq!(first_metrics.snapshot().unwrap().unwrap().output_rows.0, 2);
909 let mut replay = scan.into_stream();
910 let replay_metrics = replay.metrics();
911 assert!(replay_metrics.snapshot().unwrap().is_none());
912 replay.next_batch().await.unwrap().unwrap();
913 assert_eq!(replay_metrics.snapshot().unwrap().unwrap().output_rows.0, 1);
914 assert_eq!(first_metrics.snapshot().unwrap().unwrap().output_rows.0, 2);
915 }
916
917 #[tokio::test]
918 async fn lanes_share_work_and_replay_creates_a_fresh_execution() {
919 let (scan, source) = work_scan(false);
920 let mut lanes = scan
921 .clone()
922 .into_streams()
923 .into_iter()
924 .map(ScanStream::into_stream);
925 let mut slow = lanes.next().unwrap();
926 let mut fast = lanes.next().unwrap();
927 assert_eq!(source.reads.load(Ordering::SeqCst), 0);
928 let first = slow.next_batch().await.unwrap().unwrap();
929 assert_eq!(
930 first
931 .column(0)
932 .as_any()
933 .downcast_ref::<Int64Array>()
934 .unwrap()
935 .value(0),
936 0
937 );
938 for value in 1..100 {
939 let batch = fast.next_batch().await.unwrap().unwrap();
940 assert_eq!(
941 batch
942 .column(0)
943 .as_any()
944 .downcast_ref::<Int64Array>()
945 .unwrap()
946 .value(0),
947 value
948 );
949 }
950 assert!(fast.next_batch().await.is_none());
951 assert!(slow.next_batch().await.is_none());
952 assert_eq!(source.reads.load(Ordering::SeqCst), 1);
953 assert_eq!(source.drops.load(Ordering::SeqCst), 1);
954 let batches = scan.into_stream().collect::<Vec<_>>().await;
955 assert_eq!(batches.len(), 100);
956 assert_eq!(source.reads.load(Ordering::SeqCst), 2);
957 }
958
959 #[tokio::test]
960 async fn lane_failure_and_last_active_lane_cancellation_release_work() {
961 let (scan, source) = work_scan(true);
962 let mut lanes = scan.into_streams().into_iter().map(ScanStream::into_stream);
963 let mut first = lanes.next().unwrap();
964 let mut second = lanes.next().unwrap();
965 first.next_batch().await.unwrap().unwrap();
966 assert!(second.next_batch().await.unwrap().is_err());
967 assert!(first.next_batch().await.unwrap().is_err());
968 assert!(first.next_batch().await.is_none());
969 assert!(second.next_batch().await.is_none());
970 assert_eq!(source.drops.load(Ordering::SeqCst), 1);
971
972 let (scan, source) = work_scan(false);
973 let mut lanes = scan.into_streams().into_iter().map(ScanStream::into_stream);
974 let mut active = lanes.next().unwrap();
975 let mut idle = lanes.next().unwrap();
976 active.next_batch().await.unwrap().unwrap();
977 drop(active);
978 assert_eq!(
979 source.drops.load(Ordering::SeqCst),
980 1,
981 "idle lanes must not retain active I/O"
982 );
983 assert!(idle.next_batch().await.unwrap().is_err());
984 assert_eq!(source.reads.load(Ordering::SeqCst), 1);
985 }
986
987 #[tokio::test]
988 async fn batch_schema_mismatch_is_terminal() {
989 let expected = Arc::new(Schema::new(vec![Field::new(
990 "expected",
991 DataType::Int64,
992 false,
993 )]));
994 let wrong = RecordBatch::try_new(
995 Arc::new(Schema::new(vec![Field::new(
996 "wrong",
997 DataType::Int64,
998 false,
999 )])),
1000 vec![Arc::new(Int64Array::from(vec![1]))],
1001 )
1002 .unwrap();
1003 let valid = RecordBatch::try_new(
1004 Arc::clone(&expected),
1005 vec![Arc::new(Int64Array::from(vec![2]))],
1006 )
1007 .unwrap();
1008 let polled = Arc::new(AtomicUsize::new(0));
1009 let counter = Arc::clone(&polled);
1010 let source = stream::iter([Ok(wrong), Ok(valid)]).inspect(move |_| {
1011 counter.fetch_add(1, Ordering::SeqCst);
1012 });
1013 let mut batches = BatchStream::from_stream(expected, source);
1014 assert!(matches!(
1015 batches.next_batch().await,
1016 Some(Err(Error::Arrow(_)))
1017 ));
1018 assert!(batches.next_batch().await.is_none());
1019 assert_eq!(polled.load(Ordering::SeqCst), 1);
1020 }
1021}