1mod cache;
21mod io;
22mod split;
23#[cfg(test)]
24mod tests;
25
26use std::ops::Range;
27use std::sync::Arc;
28
29use arrow_array::RecordBatch;
30use arrow_schema::ArrowError;
31use arrow_schema::Field;
32use arrow_schema::SchemaRef;
33use futures::Stream;
34use futures::StreamExt;
35use futures::TryStreamExt;
36use futures::stream;
37use futures::stream::BoxStream;
38use vortex_array::ArrayRef;
39use vortex_array::VortexSessionExecute;
40use vortex_array::dtype::DType;
41use vortex_array::expr::BoundExpression;
42use vortex_error::VortexResult;
43use vortex_io::request::IoService;
44use vortex_io::session::RuntimeSessionExt;
45use vortex_scan::planning::planner::WorkScope;
46use vortex_scan::selection::Selection;
47use vortex_scan::strict_sorted_buffer::StrictSortedBuffer;
48use vortex_session::VortexSession;
49use vortex_utils::parallelism::get_available_parallelism;
50
51pub use self::cache::ScanCache;
52use crate::LayoutRef;
53use crate::plan::physical::lower;
54use crate::plan::physical::optimize;
55use crate::plan::physical::plan_row_idx_expression;
56use crate::scan::arrow::to_record_batch;
57use crate::scan::execution::ScanPlans;
58pub use crate::scan::execution::SegmentLocation;
59use crate::scan::execution::plan_selection;
60use crate::scan::v2::io::SegmentScanIo;
61use crate::scan::v2::io::segment_ranges;
62use crate::scan::v2::split::SplitPlan;
63use crate::segments::SegmentSource;
64
65const ROW_BLOCK_SIZE: usize = 8192;
66const MAX_ROWS_PER_CHUNK: usize = 8 * ROW_BLOCK_SIZE;
67const TARGET_BYTES_PER_CHUNK: u64 = 8 * 1024 * 1024;
68
69fn rows_per_chunk(row_count: u64, locations: &[SegmentLocation]) -> usize {
71 if row_count <= MAX_ROWS_PER_CHUNK as u64 {
72 return MAX_ROWS_PER_CHUNK;
73 }
74 let bytes: u64 = locations
75 .iter()
76 .map(|location| u64::from(location.length))
77 .sum();
78 let bytes_per_row = bytes.div_ceil(row_count).max(1);
79 let blocks = TARGET_BYTES_PER_CHUNK / bytes_per_row / ROW_BLOCK_SIZE as u64;
80 blocks.clamp(1, 8) as usize * ROW_BLOCK_SIZE
81}
82
83fn row_chunks(rows: Range<u64>, rows_per_chunk: usize) -> Vec<Range<u64>> {
85 if rows.is_empty() {
86 return Vec::new();
87 }
88 let first = rows.start / rows_per_chunk as u64 * rows_per_chunk as u64;
89 (first..rows.end)
90 .step_by(rows_per_chunk)
91 .map(|start| {
92 start.max(rows.start)..start.saturating_add(rows_per_chunk as u64).min(rows.end)
93 })
94 .collect()
95}
96
97#[derive(Clone)]
99pub struct ScanFile {
100 pub layout: LayoutRef,
102 pub locations: Arc<[SegmentLocation]>,
104 pub segments: Arc<dyn SegmentSource>,
106 pub cache: Option<ScanCache>,
108}
109
110pub struct ScanBuilder<A = ArrayRef> {
115 session: VortexSession,
116 file: ScanFile,
117 projection: BoundExpression,
118 ordered: bool,
119 row_range: Option<Range<u64>>,
120 selection: Selection,
121 concurrency: usize,
122 map_fn: Arc<dyn Fn(ArrayRef) -> VortexResult<A> + Send + Sync>,
123 row_offset: u64,
124}
125
126impl ScanBuilder<ArrayRef> {
127 pub fn new(session: VortexSession, file: ScanFile) -> Self {
129 Self {
130 projection: BoundExpression::new_root(file.layout.dtype().clone()),
131 session,
132 file,
133 ordered: true,
134 row_range: None,
135 selection: Selection::default(),
136 concurrency: 4,
137 map_fn: Arc::new(Ok),
138 row_offset: 0,
139 }
140 }
141
142 pub fn into_record_batch_stream(
144 self,
145 schema: SchemaRef,
146 ) -> VortexResult<impl Stream<Item = Result<RecordBatch, ArrowError>> + Send + 'static> {
147 let struct_field = Field::new_struct("", schema.fields().clone(), false);
148 let session = self.session.clone();
149 Ok(self
150 .map(move |chunk| {
151 let mut ctx = session.create_execution_ctx();
152 to_record_batch(chunk, &struct_field, &mut ctx)
153 })
154 .into_stream()?
155 .map_err(|error| ArrowError::ExternalError(Box::new(error))))
156 }
157}
158
159impl<A: 'static + Send> ScanBuilder<A> {
160 pub fn dtype(&self) -> &DType {
162 self.projection.dtype()
163 }
164
165 pub fn with_projection(mut self, projection: BoundExpression) -> Self {
167 self.projection = projection;
168 self
169 }
170
171 pub fn with_ordered(mut self, ordered: bool) -> Self {
173 self.ordered = ordered;
174 self
175 }
176
177 pub fn with_row_range(mut self, row_range: Range<u64>) -> Self {
179 self.row_range = Some(row_range);
180 self
181 }
182
183 pub fn with_selection(mut self, selection: Selection) -> Self {
185 self.selection = selection;
186 self
187 }
188
189 pub fn with_row_indices(self, row_indices: StrictSortedBuffer<u64>) -> Self {
191 self.with_selection(Selection::IncludeByIndex(row_indices))
192 }
193
194 pub fn with_row_offset(mut self, row_offset: u64) -> Self {
196 self.row_offset = row_offset;
197 self
198 }
199
200 pub fn with_concurrency(mut self, concurrency: usize) -> Self {
202 assert!(concurrency > 0);
203 self.concurrency = concurrency;
204 self
205 }
206
207 fn map<B: 'static>(
209 self,
210 map_fn: impl Fn(A) -> VortexResult<B> + 'static + Send + Sync,
211 ) -> ScanBuilder<B> {
212 let old_map_fn = self.map_fn;
213 ScanBuilder {
214 session: self.session,
215 file: self.file,
216 projection: self.projection,
217 ordered: self.ordered,
218 row_range: self.row_range,
219 selection: self.selection,
220 concurrency: self.concurrency,
221 row_offset: self.row_offset,
222 map_fn: Arc::new(move |array| old_map_fn(array).and_then(&map_fn)),
223 }
224 }
225
226 pub fn into_stream(self) -> VortexResult<BoxStream<'static, VortexResult<A>>> {
229 let handle = self.session.handle();
230 Ok(stream::once(async move {
231 handle
232 .spawn_cpu(move || PreparedScan::try_new(self)?.execute_stream())
233 .await
234 })
235 .try_flatten()
236 .boxed())
237 }
238}
239
240struct PreparedScan<A: 'static + Send> {
242 session: VortexSession,
243 plans: ScanPlans,
244 io: Arc<dyn IoService>,
246 splits: Vec<Range<u64>>,
248 selection: Selection,
249 map_fn: Arc<dyn Fn(ArrayRef) -> VortexResult<A> + Send + Sync>,
250 ordered: bool,
251 concurrency: usize,
252}
253
254impl<A: 'static + Send> PreparedScan<A> {
255 fn try_new(builder: ScanBuilder<A>) -> VortexResult<Self> {
258 let file = builder.file;
259 let cache = file.cache.unwrap_or_default();
260 let root = match cache.root() {
261 Some(root) => root,
262 None => cache.insert_root(lower(&file.layout)?),
263 };
264 let row_count = root.row_count();
265 let identity = builder.projection.is_root() && builder.projection.dtype() == root.dtype();
268 let projection = if identity {
269 root
270 } else {
271 match cache.projection(&builder.projection) {
272 Some(plan) => plan,
273 None => {
274 let plan =
275 optimize(plan_row_idx_expression(builder.projection.clone(), root)?)?;
276 cache.insert_projection(builder.projection, plan)
277 }
278 }
279 };
280 let chunk_rows = if identity {
281 rows_per_chunk(row_count, &file.locations)
282 } else {
283 MAX_ROWS_PER_CHUNK
284 };
285 let splits = row_chunks(builder.row_range.unwrap_or(0..row_count), chunk_rows);
286 let ranges = match cache.ranges() {
287 Some(ranges) => ranges,
288 None => cache.insert_ranges(segment_ranges(&file.locations)),
289 };
290 let io = Arc::new(SegmentScanIo::new(file.segments, ranges));
291 Ok(Self {
292 session: builder.session.clone(),
293 plans: ScanPlans {
294 session: builder.session,
295 locations: file.locations,
296 projection,
297 row_offset: builder.row_offset,
298 values: cache.values(),
299 },
300 io,
301 splits,
302 selection: builder.selection,
303 map_fn: builder.map_fn,
304 ordered: builder.ordered,
305 concurrency: builder.concurrency,
306 })
307 }
308
309 fn split_plans(&self) -> Vec<SplitPlan> {
311 self.splits
312 .iter()
313 .filter_map(|rows| {
314 let (scope, root) = plan_selection(
315 &self.plans,
316 WorkScope {
317 file_ordinal: 0,
318 rows: rows.clone(),
319 },
320 &self.selection,
321 )?;
322 Some(SplitPlan { scope, root })
323 })
324 .collect()
325 }
326
327 fn execute_stream(self) -> VortexResult<BoxStream<'static, VortexResult<A>>> {
332 let concurrency = self.concurrency * get_available_parallelism().unwrap_or(1);
333 let handle = self.session.handle();
334 let split_plans = self.split_plans();
335 let io = self.io;
336 let map_fn = self.map_fn;
337 let tasks = stream::iter(split_plans).map(move |split| {
339 let io = Arc::clone(&io);
340 let map_fn = Arc::clone(&map_fn);
341 handle.spawn(async move {
342 split
343 .run(io)
344 .await?
345 .into_iter()
346 .map(|array| map_fn(array))
347 .collect::<VortexResult<Vec<_>>>()
348 })
349 });
350 let batches = if self.ordered {
351 tasks.buffered(concurrency).boxed()
352 } else {
353 tasks.buffer_unordered(concurrency).boxed()
354 };
355 Ok(batches
356 .map_ok(|batches| stream::iter(batches.into_iter().map(Ok)))
357 .try_flatten()
358 .boxed())
359 }
360}