1mod expr;
5
6use std::fmt::Display;
7use std::fmt::Formatter;
8use std::ops::BitAnd;
9use std::ops::Range;
10use std::sync::Arc;
11use std::sync::OnceLock;
12
13use Nullability::NonNullable;
14pub use expr::*;
15use futures::FutureExt;
16use futures::future::BoxFuture;
17use vortex_array::ArrayRef;
18use vortex_array::IntoArray;
19use vortex_array::MaskFuture;
20use vortex_array::VortexSessionExecute;
21use vortex_array::dtype::DType;
22use vortex_array::dtype::FieldMask;
23use vortex_array::dtype::FieldName;
24use vortex_array::dtype::Nullability;
25use vortex_array::dtype::PType;
26use vortex_array::expr::BoundExpression;
27use vortex_array::expr::ExactBoundExpr;
28use vortex_array::expr::transform::BoundPartitionedExpr;
29use vortex_array::expr::transform::partition_bound;
30use vortex_array::expr::traversal::NodeExt;
31use vortex_array::expr::traversal::Transformed;
32use vortex_array::expr::traversal::TraversalOrder;
33use vortex_array::scalar::PValue;
34use vortex_error::VortexExpect;
35use vortex_error::VortexResult;
36use vortex_mask::Mask;
37use vortex_sequence::Sequence;
38use vortex_sequence::SequenceArray;
39use vortex_session::VortexSession;
40use vortex_utils::aliases::dash_map::DashMap;
41
42use crate::ArrayFuture;
43use crate::LayoutReader;
44use crate::RowSplits;
45use crate::SplitRange;
46use crate::layouts::partitioned::BoundPartitionedExprEval;
47
48pub struct RowIdxLayoutReader {
49 name: Arc<str>,
50 row_offset: u64,
51 child: Arc<dyn LayoutReader>,
52 partition_cache: DashMap<ExactBoundExpr, Arc<OnceLock<Partitioning>>>,
53 session: VortexSession,
54}
55
56impl RowIdxLayoutReader {
57 pub fn new(row_offset: u64, child: Arc<dyn LayoutReader>, session: VortexSession) -> Self {
58 Self {
59 name: Arc::clone(child.name()),
60 row_offset,
61 child,
62 partition_cache: DashMap::with_hasher(Default::default()),
63 session,
64 }
65 }
66
67 fn partition_expr(&self, expr: &BoundExpression) -> VortexResult<Partitioning> {
68 let key = ExactBoundExpr(expr.clone());
69
70 if let Some(entry) = self.partition_cache.get(&key)
72 && let Some(partitioning) = entry.value().get()
73 {
74 return Ok(partitioning.clone());
75 }
76
77 let result = self.compute_partitioning(expr)?;
78
79 self.partition_cache
80 .entry(key)
81 .or_insert_with(|| Arc::new(OnceLock::new()))
82 .get_or_init(|| result.clone());
83
84 Ok(result)
85 }
86
87 fn compute_partitioning(&self, expr: &BoundExpression) -> VortexResult<Partitioning> {
88 let mut partitioned = partition_bound(expr.clone(), |expr: &BoundExpression| {
90 if expr
91 .as_scalar()
92 .is_some_and(|scalar_fn| scalar_fn.is::<RowIdx>())
93 {
94 vec![Partition::RowIdx]
95 } else if expr.is_root() {
96 vec![Partition::Child]
97 } else {
98 vec![]
99 }
100 })?;
101
102 if partitioned.partitions.len() == 1 {
104 return Ok(match &partitioned.partition_annotations[0] {
105 Partition::RowIdx => Partitioning::RowIdx(replace_row_idx(expr.clone())?),
106 Partition::Child => Partitioning::Child(expr.clone()),
107 });
108 }
109
110 let partitions = partitioned
112 .partitions
113 .iter()
114 .cloned()
115 .map(replace_row_idx)
116 .collect::<VortexResult<Vec<_>>>()?
117 .into_boxed_slice();
118 partitioned.replace_partitions(partitions)?;
119
120 Ok(Partitioning::Partitioned(Arc::new(partitioned)))
121 }
122}
123
124#[derive(Clone)]
125enum Partitioning {
126 RowIdx(BoundExpression),
128 Child(BoundExpression),
130 Partitioned(Arc<BoundPartitionedExpr<Partition>>),
132}
133
134#[derive(Clone, PartialEq, Eq, Hash)]
135enum Partition {
136 RowIdx,
137 Child,
138}
139
140impl Partition {
141 pub fn name(&self) -> &str {
142 match self {
143 Partition::RowIdx => "row_idx",
144 Partition::Child => "child",
145 }
146 }
147}
148
149impl From<Partition> for FieldName {
150 fn from(value: Partition) -> Self {
151 FieldName::from(value.name())
152 }
153}
154
155impl Display for Partition {
156 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
157 write!(f, "{}", self.name())
158 }
159}
160
161impl LayoutReader for RowIdxLayoutReader {
162 fn name(&self) -> &Arc<str> {
163 &self.name
164 }
165
166 fn dtype(&self) -> &DType {
167 self.child.dtype()
168 }
169
170 fn row_count(&self) -> u64 {
171 self.child.row_count()
172 }
173
174 fn register_splits(
175 &self,
176 field_mask: &[FieldMask],
177 split_range: &SplitRange,
178 splits: &mut RowSplits,
179 ) -> VortexResult<()> {
180 self.child.register_splits(field_mask, split_range, splits)
181 }
182
183 fn pruning_evaluation(
184 &self,
185 row_range: &Range<u64>,
186 expr: &BoundExpression,
187 mask: Mask,
188 ) -> VortexResult<MaskFuture> {
189 Ok(match &self.partition_expr(expr)? {
190 Partitioning::RowIdx(expr) => row_idx_mask_future(
191 self.row_offset,
192 row_range,
193 expr.clone(),
194 MaskFuture::ready(mask),
195 self.session.clone(),
196 ),
197 Partitioning::Child(expr) => self.child.pruning_evaluation(row_range, expr, mask)?,
198 Partitioning::Partitioned(..) => MaskFuture::ready(mask),
199 })
200 }
201
202 fn filter_evaluation(
203 &self,
204 row_range: &Range<u64>,
205 expr: &BoundExpression,
206 mask: MaskFuture,
207 ) -> VortexResult<MaskFuture> {
208 match &self.partition_expr(expr)? {
209 Partitioning::RowIdx(_) => Ok(mask),
212 Partitioning::Child(expr) => self.child.filter_evaluation(row_range, expr, mask),
213 Partitioning::Partitioned(p) => Arc::clone(p).into_mask_future(
214 mask,
215 |annotation, expr, mask| match annotation {
216 Partition::RowIdx => Ok(row_idx_mask_future(
217 self.row_offset,
218 row_range,
219 expr.clone(),
220 mask,
221 self.session.clone(),
222 )),
223 Partition::Child => self.child.filter_evaluation(row_range, expr, mask),
224 },
225 |annotation, expr, mask| match annotation {
226 Partition::RowIdx => Ok(row_idx_array_future(
227 self.row_offset,
228 row_range,
229 expr.clone(),
230 mask,
231 )),
232 Partition::Child => self.child.projection_evaluation(row_range, expr, mask),
233 },
234 self.session.clone(),
235 ),
236 }
237 }
238
239 fn projection_evaluation(
240 &self,
241 row_range: &Range<u64>,
242 expr: &BoundExpression,
243 mask: MaskFuture,
244 ) -> VortexResult<BoxFuture<'static, VortexResult<ArrayRef>>> {
245 match &self.partition_expr(expr)? {
246 Partitioning::RowIdx(expr) => Ok(row_idx_array_future(
247 self.row_offset,
248 row_range,
249 expr.clone(),
250 mask,
251 )),
252 Partitioning::Child(expr) => self.child.projection_evaluation(row_range, expr, mask),
253 Partitioning::Partitioned(p) => {
254 Arc::clone(p).into_array_future(mask, |annotation, expr, mask| match annotation {
255 Partition::RowIdx => Ok(row_idx_array_future(
256 self.row_offset,
257 row_range,
258 expr.clone(),
259 mask,
260 )),
261 Partition::Child => self.child.projection_evaluation(row_range, expr, mask),
262 })
263 }
264 }
265 }
266
267 fn as_any(&self) -> &dyn std::any::Any {
268 self
269 }
270}
271
272fn replace_row_idx(expr: BoundExpression) -> VortexResult<BoundExpression> {
273 Ok(expr
274 .transform_down(|node| {
275 if node
276 .as_scalar()
277 .is_some_and(|scalar_fn| scalar_fn.is::<RowIdx>())
278 {
279 Ok(Transformed {
280 value: BoundExpression::new_root(row_idx_dtype()),
281 changed: true,
282 order: TraversalOrder::Skip,
283 })
284 } else {
285 Ok(Transformed::no(node))
286 }
287 })?
288 .into_inner())
289}
290
291fn row_idx_dtype() -> DType {
292 DType::Primitive(PType::U64, NonNullable)
293}
294
295fn idx_array(row_offset: u64, row_range: &Range<u64>) -> SequenceArray {
297 Sequence::try_new(
298 PValue::U64(row_offset + row_range.start),
299 PValue::U64(1),
300 PType::U64,
301 NonNullable,
302 usize::try_from(row_range.end - row_range.start)
303 .vortex_expect("Row range length must fit in usize"),
304 )
305 .vortex_expect("Failed to create row index array")
306}
307
308fn row_idx_mask_future(
309 row_offset: u64,
310 row_range: &Range<u64>,
311 expr: BoundExpression,
312 mask: MaskFuture,
313 session: VortexSession,
314) -> MaskFuture {
315 let row_range = row_range.clone();
316 MaskFuture::new(mask.len(), async move {
317 let array = idx_array(row_offset, &row_range).into_array();
318
319 let mut ctx = session.create_execution_ctx();
320 let result_mask = array
321 .apply_bound(&expr)?
322 .null_as_false()
323 .execute(&mut ctx)?;
324
325 Ok(result_mask.bitand(&mask.await?))
326 })
327}
328
329fn row_idx_array_future(
330 row_offset: u64,
331 row_range: &Range<u64>,
332 expr: BoundExpression,
333 mask: MaskFuture,
334) -> ArrayFuture {
335 let row_range = row_range.clone();
336 async move {
337 idx_array(row_offset, &row_range)
340 .into_array()
341 .filter(mask.await?)?
342 .apply_bound(&expr)
343 }
344 .boxed()
345}
346
347#[cfg(test)]
348mod tests {
349 use std::sync::Arc;
350
351 use vortex_array::ArrayContext;
352 use vortex_array::IntoArray as _;
353 use vortex_array::MaskFuture;
354 use vortex_array::VortexSessionExecute;
355 use vortex_array::arrays::BoolArray;
356 use vortex_array::assert_arrays_eq;
357 use vortex_array::dtype::DType;
358 use vortex_array::dtype::Nullability;
359 use vortex_array::dtype::PType;
360 use vortex_array::expr::eq;
361 use vortex_array::expr::gt;
362 use vortex_array::expr::lit;
363 use vortex_array::expr::or;
364 use vortex_array::expr::root;
365 use vortex_buffer::buffer;
366 use vortex_io::runtime::single::block_on;
367 use vortex_io::session::RuntimeSessionExt;
368 use vortex_mask::Mask;
369 use vortex_sequence::Sequence;
370
371 use crate::LayoutReader;
372 use crate::LayoutStrategy;
373 use crate::layouts::flat::writer::FlatLayoutStrategy;
374 use crate::layouts::row_idx::RowIdxLayoutReader;
375 use crate::layouts::row_idx::row_idx;
376 use crate::segments::TestSegments;
377 use crate::sequence::SequenceId;
378 use crate::sequence::SequentialArrayStreamExt;
379 use crate::test::new_session;
380
381 #[test]
382 fn flat_expr_no_row_id() {
383 block_on(|handle| async {
384 let session = new_session().with_handle(handle);
385 let mut ctx = session.create_execution_ctx();
386 let array_ctx = ArrayContext::empty();
387 let segments = Arc::new(TestSegments::default());
388 let (ptr, eof) = SequenceId::root().split();
389 let array = buffer![1..=5].into_array();
390 let layout = FlatLayoutStrategy::default()
391 .write_stream(
392 array_ctx.into(),
393 Arc::<TestSegments>::clone(&segments),
394 array.to_array_stream().sequenced(ptr),
395 eof,
396 &session,
397 )
398 .await
399 .unwrap();
400
401 let expr = eq(root(), lit(3i32));
402 let reader = RowIdxLayoutReader::new(
403 0,
404 layout
405 .new_reader("".into(), segments, &session, &Default::default())
406 .unwrap(),
407 session.clone(),
408 );
409 let expr = expr.bind(reader.dtype()).unwrap();
410 let result = reader
411 .projection_evaluation(
412 &(0..layout.row_count()),
413 &expr,
414 MaskFuture::new_true(layout.row_count().try_into().unwrap()),
415 )
416 .unwrap()
417 .await
418 .unwrap();
419
420 assert_arrays_eq!(
421 result,
422 BoolArray::from_iter([false, false, true, false, false]),
423 &mut ctx
424 );
425 })
426 }
427
428 #[test]
429 fn flat_expr_row_id() {
430 block_on(|handle| async {
431 let session = new_session().with_handle(handle);
432 let mut ctx = session.create_execution_ctx();
433 let array_ctx = ArrayContext::empty();
434 let segments = Arc::new(TestSegments::default());
435 let (ptr, eof) = SequenceId::root().split();
436 let array = buffer![1..=5].into_array();
437 let layout = FlatLayoutStrategy::default()
438 .write_stream(
439 array_ctx.into(),
440 Arc::<TestSegments>::clone(&segments),
441 array.to_array_stream().sequenced(ptr),
442 eof,
443 &session,
444 )
445 .await
446 .unwrap();
447
448 let expr = gt(row_idx(), lit(3u64));
449 let reader = RowIdxLayoutReader::new(
450 0,
451 layout
452 .new_reader("".into(), segments, &session, &Default::default())
453 .unwrap(),
454 session.clone(),
455 );
456 let expr = expr.bind(reader.dtype()).unwrap();
457 let result = reader
458 .projection_evaluation(
459 &(0..layout.row_count()),
460 &expr,
461 MaskFuture::new_true(layout.row_count().try_into().unwrap()),
462 )
463 .unwrap()
464 .await
465 .unwrap();
466
467 assert_arrays_eq!(
468 result,
469 BoolArray::from_iter([false, false, false, false, true]),
470 &mut ctx
471 );
472 })
473 }
474
475 #[test]
476 fn flat_expr_or() {
477 block_on(|handle| async {
478 let session = new_session().with_handle(handle);
479 let mut ctx = session.create_execution_ctx();
480 let array_ctx = ArrayContext::empty();
481 let segments = Arc::new(TestSegments::default());
482 let (ptr, eof) = SequenceId::root().split();
483 let array = buffer![1..=5].into_array();
484 let layout = FlatLayoutStrategy::default()
485 .write_stream(
486 array_ctx.into(),
487 Arc::<TestSegments>::clone(&segments),
488 array.to_array_stream().sequenced(ptr),
489 eof,
490 &session,
491 )
492 .await
493 .unwrap();
494
495 let expr = or(
496 eq(root(), lit(3i32)),
497 or(gt(row_idx(), lit(3u64)), eq(root(), lit(1i32))),
498 );
499
500 let reader = RowIdxLayoutReader::new(
501 0,
502 layout
503 .new_reader("".into(), segments, &session, &Default::default())
504 .unwrap(),
505 session.clone(),
506 );
507 let expr = expr.bind(reader.dtype()).unwrap();
508 let result = reader
509 .projection_evaluation(
510 &(0..layout.row_count()),
511 &expr,
512 MaskFuture::new_true(layout.row_count().try_into().unwrap()),
513 )
514 .unwrap()
515 .await
516 .unwrap();
517
518 assert_arrays_eq!(
519 result,
520 BoolArray::from_iter([true, false, true, false, true]),
521 &mut ctx
522 );
523 })
524 }
525
526 #[test]
527 fn row_idx_array_all_true_keeps_sequence() {
528 block_on(|_| async {
529 let expr = root()
530 .bind(&DType::Primitive(PType::U64, Nullability::NonNullable))
531 .unwrap();
532
533 let result = super::row_idx_array_future(
534 10,
535 &(2..7),
536 expr,
537 MaskFuture::ready(Mask::new_true(5)),
538 )
539 .await
540 .unwrap();
541
542 assert!(result.is::<Sequence>());
543 assert_eq!(result.len(), 5);
544 })
545 }
546
547 #[test]
548 fn row_idx_array_all_false_returns_empty() {
549 block_on(|_| async {
550 let expr = root()
551 .bind(&DType::Primitive(PType::U64, Nullability::NonNullable))
552 .unwrap();
553
554 let result = super::row_idx_array_future(
555 10,
556 &(2..7),
557 expr,
558 MaskFuture::ready(Mask::new_false(5)),
559 )
560 .await
561 .unwrap();
562
563 assert_eq!(
564 result.dtype(),
565 &DType::Primitive(PType::U64, Nullability::NonNullable)
566 );
567 assert_eq!(result.len(), 0);
568 })
569 }
570}