1use std::sync::Arc;
19
20use arrow::datatypes::{Schema, SchemaRef};
21use datafusion_common::{
22 Result, ScalarValue,
23 tree_node::{Transformed, TransformedResult, TreeNode},
24};
25use datafusion_physical_expr::{
26 expressions::{Column, Literal},
27 projection::{ProjectionExpr, ProjectionExprs},
28};
29use datafusion_physical_expr_adapter::rewrite::rewrite_input_file_name_in_projection;
30use futures::{FutureExt, StreamExt};
31use itertools::Itertools;
32
33use crate::{
34 PartitionedFile, TableSchema,
35 file_stream::{FileOpenFuture, FileOpener},
36};
37
38pub struct ProjectionOpener {
49 inner: Arc<dyn FileOpener>,
50 projection: ProjectionExprs,
51 input_schema: SchemaRef,
52 partition_columns: Vec<PartitionColumnIndex>,
53}
54
55impl ProjectionOpener {
56 pub fn try_new(
57 projection: SplitProjection,
58 inner: Arc<dyn FileOpener>,
59 file_schema: &Schema,
60 ) -> Result<Arc<dyn FileOpener>> {
61 Ok(Arc::new(ProjectionOpener {
62 inner,
63 projection: projection.remapped_projection,
64 input_schema: Arc::new(file_schema.project(&projection.file_indices)?),
65 partition_columns: projection.partition_columns,
66 }))
67 }
68}
69
70impl FileOpener for ProjectionOpener {
71 fn open(&self, partitioned_file: PartitionedFile) -> Result<FileOpenFuture> {
72 let partition_values = partitioned_file.partition_values.clone();
73
74 let projection = if self.partition_columns.is_empty() {
77 self.projection.clone()
78 } else {
79 inject_partition_columns_into_projection(
80 &self.projection,
81 &self.partition_columns,
82 partition_values,
83 )
84 };
85 let projection = rewrite_input_file_name_in_projection(
87 projection,
88 partitioned_file.object_meta.location.as_ref(),
89 )?;
90 let projector = projection.make_projector(&self.input_schema)?;
91
92 let inner = self.inner.open(partitioned_file)?;
93
94 Ok(async move {
95 let stream = inner.await?;
96 let stream = stream.map(move |batch| {
97 let batch = batch?;
98 let batch = projector.project_batch(&batch)?;
99 Ok(batch)
100 });
101 Ok(stream.boxed())
102 }
103 .boxed())
104 }
105}
106
107#[derive(Debug, Clone, Copy)]
108pub struct PartitionColumnIndex {
109 pub in_remainder_projection: usize,
111 pub in_partition_values: usize,
113}
114
115fn inject_partition_columns_into_projection(
116 projection: &ProjectionExprs,
117 partition_columns: &[PartitionColumnIndex],
118 partition_values: Vec<ScalarValue>,
119) -> ProjectionExprs {
120 let partition_literals: Vec<Arc<Literal>> = partition_values
122 .into_iter()
123 .map(|value| Arc::new(Literal::new(value)))
124 .collect();
125
126 let projections = projection
127 .iter()
128 .map(|projection| {
129 let expr = Arc::clone(&projection.expr)
130 .transform(|expr| {
131 let original_expr = Arc::clone(&expr);
132 if let Some(column) = expr.downcast_ref::<Column>() {
133 if let Some(pci) = partition_columns
135 .iter()
136 .find(|pci| pci.in_remainder_projection == column.index())
137 {
138 let literal =
139 Arc::clone(&partition_literals[pci.in_partition_values]);
140 return Ok(Transformed::yes(literal));
141 }
142 }
143 Ok(Transformed::no(original_expr))
144 })
145 .data()
146 .expect("infallible transform");
147 ProjectionExpr::new(expr, projection.alias.clone())
148 })
149 .collect_vec();
150 ProjectionExprs::new(projections)
151}
152
153#[derive(Debug, Clone)]
166pub struct SplitProjection {
167 pub source: ProjectionExprs,
169 pub file_indices: Vec<usize>,
171 pub(crate) partition_columns: Vec<PartitionColumnIndex>,
173 pub(crate) remapped_projection: ProjectionExprs,
175}
176
177impl SplitProjection {
178 pub fn unprojected(table_schema: &TableSchema) -> Self {
179 let projection = ProjectionExprs::from_indices(
180 &(0..table_schema.table_schema().fields().len()).collect_vec(),
181 table_schema.table_schema(),
182 );
183 Self::new(table_schema.file_schema(), &projection)
184 }
185
186 pub fn new(logical_file_schema: &Schema, projection: &ProjectionExprs) -> Self {
197 let num_file_schema_columns = logical_file_schema.fields().len();
198
199 let mut file_columns = Vec::new();
201 let mut partition_columns = Vec::new();
202 let mut all_columns = std::collections::HashMap::new();
203
204 for proj_expr in projection {
206 proj_expr
207 .expr
208 .apply(|expr| {
209 if let Some(column) = expr.downcast_ref::<Column>() {
210 all_columns
211 .entry(column.index())
212 .or_insert_with(|| column.name().to_string());
213 }
214 Ok(datafusion_common::tree_node::TreeNodeRecursion::Continue)
215 })
216 .expect("infallible apply");
217 }
218
219 let mut sorted_columns: Vec<_> = all_columns
221 .into_iter()
222 .map(|(idx, name)| (name, idx))
223 .collect();
224 sorted_columns.sort_by_key(|(_, idx)| *idx);
225
226 let mut column_mapping = std::collections::HashMap::new();
229 let mut file_idx = 0;
230 let mut partition_idx = 0;
231
232 for (name, original_index) in sorted_columns {
233 let new_index = if original_index < num_file_schema_columns {
234 file_columns.push(original_index);
236 let idx = file_idx;
237 file_idx += 1;
238 idx
239 } else {
240 partition_columns.push(original_index);
242 let idx = file_idx + partition_idx;
243 partition_idx += 1;
244 idx
245 };
246
247 let new_column: Arc<dyn datafusion_physical_plan::PhysicalExpr> =
249 Arc::new(Column::new(&name, new_index));
250 column_mapping.insert(original_index, new_column);
251 }
252
253 let remapped_projection = projection
255 .iter()
256 .map(|proj_expr| {
257 let expr = Arc::clone(&proj_expr.expr)
258 .transform(|expr| {
259 let original_expr = Arc::clone(&expr);
260 if let Some(column) = expr.downcast_ref::<Column>()
261 && let Some(new_column) = column_mapping.get(&column.index())
262 {
263 return Ok(Transformed::yes(Arc::clone(new_column)));
264 }
265 Ok(Transformed::no(original_expr))
266 })
267 .data()
268 .expect("infallible transform");
269 ProjectionExpr::new(expr, proj_expr.alias.clone())
270 })
271 .collect_vec();
272
273 let num_file_columns = file_columns.len();
275 let partition_column_mappings = partition_columns
276 .iter()
277 .enumerate()
278 .map(|(partition_idx, &table_index)| PartitionColumnIndex {
279 in_remainder_projection: num_file_columns + partition_idx,
280 in_partition_values: table_index - num_file_schema_columns,
281 })
282 .collect_vec();
283
284 Self {
285 source: projection.clone(),
286 file_indices: file_columns,
287 partition_columns: partition_column_mappings,
288 remapped_projection: ProjectionExprs::from(remapped_projection),
289 }
290 }
291}
292
293#[cfg(test)]
294mod test {
295 use std::sync::Arc;
296
297 use arrow::array::{AsArray, RecordBatch, record_batch};
298 use arrow::datatypes as arrow_schema;
299 use arrow::datatypes::{DataType, Field, SchemaRef};
300 use datafusion_common::{DFSchema, ScalarValue, config::ConfigOptions};
301 use datafusion_expr::{
302 Expr, ScalarUDF, col, execution_props::ExecutionProps,
303 physical_planning_context::PhysicalPlanningContext,
304 };
305 use datafusion_functions::core::input_file_name::InputFileNameFunc;
306 use datafusion_physical_expr::{
307 ScalarFunctionExpr, create_physical_exprs, projection::ProjectionExpr,
308 };
309 use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
310 use futures::{FutureExt, StreamExt};
311 use itertools::Itertools;
312
313 use super::*;
314
315 struct StaticBatchOpener {
316 batch: RecordBatch,
317 }
318
319 impl FileOpener for StaticBatchOpener {
320 fn open(&self, _partitioned_file: PartitionedFile) -> Result<FileOpenFuture> {
321 let batch = self.batch.clone();
322 Ok(async move { Ok(futures::stream::iter([Ok(batch)]).boxed()) }.boxed())
323 }
324 }
325
326 fn create_projection_exprs<'a>(
327 exprs: impl IntoIterator<Item = &'a Expr>,
328 schema: &SchemaRef,
329 ) -> ProjectionExprs {
330 let df_schema = DFSchema::try_from(Arc::clone(schema)).unwrap();
331 let physical_exprs = create_physical_exprs(
332 exprs,
333 &df_schema,
334 &ExecutionProps::default(),
335 &PhysicalPlanningContext::default(),
336 )
337 .unwrap();
338 let projection_exprs = physical_exprs
339 .into_iter()
340 .enumerate()
341 .map(|(i, e)| ProjectionExpr::new(Arc::clone(&e), format!("col{i}")))
342 .collect_vec();
343 ProjectionExprs::from(projection_exprs)
344 }
345
346 fn input_file_name_expr() -> Arc<dyn PhysicalExpr> {
347 Arc::new(ScalarFunctionExpr::new(
348 "input_file_name",
349 Arc::new(ScalarUDF::from(InputFileNameFunc::new())),
350 vec![],
351 Arc::new(Field::new("input_file_name", DataType::Utf8, true)),
352 Arc::new(ConfigOptions::default()),
353 ))
354 }
355
356 #[tokio::test]
357 async fn test_projection_opener_rewrites_input_file_name_with_partitions() {
358 let file_schema = Schema::new(vec![Field::new("value", DataType::Int32, false)]);
359 let projection = ProjectionExprs::new([
360 ProjectionExpr::new(Arc::new(Column::new("value", 0)), "value"),
361 ProjectionExpr::new(Arc::new(Column::new("part", 1)), "part"),
362 ProjectionExpr::new(input_file_name_expr(), "file_name"),
363 ]);
364 let split = SplitProjection::new(&file_schema, &projection);
365 let input_batch =
366 record_batch!(("value", Int32, vec![10, 20])).expect("input batch");
367
368 let opener = ProjectionOpener::try_new(
369 split,
370 Arc::new(StaticBatchOpener { batch: input_batch }),
371 &file_schema,
372 )
373 .expect("projection opener");
374
375 let mut file = PartitionedFile::new("part=west/data.csv", 100);
376 file.partition_values = vec![ScalarValue::from("west")];
377 let mut stream = opener
378 .open(file)
379 .expect("open projection")
380 .await
381 .expect("inner stream");
382 let batch = stream
383 .next()
384 .await
385 .expect("one projected batch")
386 .expect("projected batch");
387 assert!(stream.next().await.is_none());
388
389 assert_eq!(batch.schema().field(0).name(), "value");
390 assert_eq!(batch.schema().field(1).name(), "part");
391 assert_eq!(batch.schema().field(2).name(), "file_name");
392
393 let values = batch
394 .column(0)
395 .as_primitive::<arrow::datatypes::Int32Type>();
396 assert_eq!(values.value(0), 10);
397 assert_eq!(values.value(1), 20);
398
399 let parts = batch.column(1).as_string::<i32>();
400 assert_eq!(parts.value(0), "west");
401 assert_eq!(parts.value(1), "west");
402
403 let file_names = batch.column(2).as_string::<i32>();
404 assert_eq!(file_names.value(0), "part=west/data.csv");
405 assert_eq!(file_names.value(1), "part=west/data.csv");
406 }
407
408 #[test]
409 fn test_split_projection_with_partition_columns() {
410 use arrow::array::AsArray;
411 use arrow::datatypes::Field;
412 let file_schema = Arc::new(Schema::new(vec![
415 Field::new("id", DataType::Int32, false),
416 Field::new("bool_col", DataType::Boolean, false),
417 Field::new("tinyint_col", DataType::Int8, false),
418 ]));
419
420 let table_schema = Arc::new(Schema::new(vec![
422 Field::new("id", DataType::Int32, false),
423 Field::new("bool_col", DataType::Boolean, false),
424 Field::new("tinyint_col", DataType::Int8, false),
425 Field::new("date", DataType::Utf8, false), ]));
427
428 let projection_indices = vec![0, 1, 3, 2];
431
432 let projection =
434 ProjectionExprs::from_indices(&projection_indices, &table_schema);
435
436 let split = SplitProjection::new(&file_schema, &projection);
438
439 assert_eq!(split.file_indices, vec![0, 1, 2]);
441
442 assert_eq!(split.partition_columns.len(), 1);
444 assert_eq!(split.partition_columns[0].in_partition_values, 0);
445
446 let file_batch = record_batch!(
448 ("id", Int32, vec![4]),
449 ("bool_col", Boolean, vec![true]),
450 ("tinyint_col", Int8, vec![0])
451 )
452 .unwrap();
453
454 let partition_values = vec![ScalarValue::from("2021-10-26")];
461
462 let partition_columns = vec![PartitionColumnIndex {
464 in_remainder_projection: 3, in_partition_values: 0, }];
467
468 let injected_projection = inject_partition_columns_into_projection(
470 &split.remapped_projection,
471 &partition_columns,
472 partition_values,
473 );
474
475 let projector = injected_projection
477 .make_projector(&file_batch.schema())
478 .unwrap();
479 let result = projector.project_batch(&file_batch).unwrap();
480
481 assert_eq!(result.num_columns(), 4);
483 assert_eq!(
484 result
485 .column(0)
486 .as_primitive::<arrow::datatypes::Int32Type>()
487 .value(0),
488 4
489 );
490 assert!(result.column(1).as_boolean().value(0));
491 assert_eq!(result.column(2).as_string::<i32>().value(0), "2021-10-26");
492 assert_eq!(
493 result
494 .column(3)
495 .as_primitive::<arrow::datatypes::Int8Type>()
496 .value(0),
497 0
498 );
499 }
500
501 fn create_test_schemas(
507 file_cols: usize,
508 partition_cols: usize,
509 ) -> (SchemaRef, SchemaRef) {
510 use arrow::datatypes::Field;
511
512 let file_fields: Vec<_> = (0..file_cols)
513 .map(|i| Field::new(format!("col_{i}"), DataType::Int32, false))
514 .collect();
515
516 let mut table_fields = file_fields.clone();
517 table_fields.extend(
518 (0..partition_cols)
519 .map(|i| Field::new(format!("part_{i}"), DataType::Utf8, false)),
520 );
521
522 (
523 Arc::new(Schema::new(file_fields)),
524 Arc::new(Schema::new(table_fields)),
525 )
526 }
527
528 #[test]
533 fn test_split_projection_only_file_columns() {
534 let (file_schema, table_schema) = create_test_schemas(3, 2);
535 let projection = ProjectionExprs::from_indices(&[0, 1, 2], &table_schema);
537
538 let split = SplitProjection::new(&file_schema, &projection);
539
540 assert_eq!(split.file_indices, vec![0, 1, 2]);
541 assert_eq!(split.partition_columns.len(), 0);
542 }
543
544 #[test]
545 fn test_split_projection_only_partition_columns() {
546 let (file_schema, table_schema) = create_test_schemas(3, 2);
547 let projection = ProjectionExprs::from_indices(&[3, 4], &table_schema);
549
550 let split = SplitProjection::new(&file_schema, &projection);
551
552 assert_eq!(split.file_indices, Vec::<usize>::new());
553 assert_eq!(split.partition_columns.len(), 2);
554 assert_eq!(split.partition_columns[0].in_partition_values, 0);
555 assert_eq!(split.partition_columns[1].in_partition_values, 1);
556 }
557
558 #[test]
559 fn test_split_projection_multiple_partition_columns() {
560 let (file_schema, table_schema) = create_test_schemas(2, 3);
561 let projection = ProjectionExprs::from_indices(&[0, 2, 4, 1, 3], &table_schema);
564
565 let split = SplitProjection::new(&file_schema, &projection);
566
567 assert_eq!(split.file_indices, vec![0, 1]);
568 assert_eq!(split.partition_columns.len(), 3);
569 assert_eq!(split.partition_columns[0].in_partition_values, 0);
570 assert_eq!(split.partition_columns[1].in_partition_values, 1);
571 assert_eq!(split.partition_columns[2].in_partition_values, 2);
572
573 assert_eq!(split.remapped_projection.iter().count(), 5);
576 }
577
578 #[test]
579 fn test_split_projection_partition_columns_reverse_order() {
580 let (file_schema, table_schema) = create_test_schemas(2, 2);
581 let projection = ProjectionExprs::from_indices(&[3, 2], &table_schema);
584
585 let split = SplitProjection::new(&file_schema, &projection);
586
587 assert_eq!(split.file_indices, Vec::<usize>::new());
588 assert_eq!(split.partition_columns.len(), 2);
589 assert_eq!(split.partition_columns[0].in_partition_values, 0);
590 assert_eq!(split.partition_columns[1].in_partition_values, 1);
591 }
592
593 #[test]
594 fn test_split_projection_interleaved_file_and_partition() {
595 let (file_schema, table_schema) = create_test_schemas(3, 3);
596 let projection =
599 ProjectionExprs::from_indices(&[0, 3, 1, 4, 2, 5], &table_schema);
600
601 let split = SplitProjection::new(&file_schema, &projection);
602
603 assert_eq!(split.file_indices, vec![0, 1, 2]);
604 assert_eq!(split.partition_columns.len(), 3);
605 assert_eq!(split.partition_columns[0].in_partition_values, 0);
606 assert_eq!(split.partition_columns[1].in_partition_values, 1);
607 assert_eq!(split.partition_columns[2].in_partition_values, 2);
608 }
609
610 #[test]
611 fn test_split_projection_expression_with_file_and_partition_columns() {
612 use arrow::datatypes::Field;
613
614 let file_schema = Arc::new(Schema::new(vec![
616 Field::new("file_a", DataType::Int32, false),
617 Field::new("file_b", DataType::Int32, false),
618 ]));
619 let table_schema = Arc::new(Schema::new(vec![
620 Field::new("file_a", DataType::Int32, false),
621 Field::new("file_b", DataType::Int32, false),
622 Field::new("part_c", DataType::Int32, false),
623 ]));
624
625 let exprs = [col("file_a") + col("part_c")];
627 let projection = create_projection_exprs(exprs.iter(), &table_schema);
628
629 let split = SplitProjection::new(&file_schema, &projection);
630
631 assert_eq!(split.file_indices, vec![0]);
633 assert_eq!(split.partition_columns.len(), 1);
634 assert_eq!(split.partition_columns[0].in_partition_values, 0);
635 }
636
637 #[test]
642 fn test_split_projection_boundary_last_file_column() {
643 let (file_schema, table_schema) = create_test_schemas(3, 2);
644 let projection = ProjectionExprs::from_indices(&[2], &table_schema);
646
647 let split = SplitProjection::new(&file_schema, &projection);
648
649 assert_eq!(split.file_indices, vec![2]);
650 assert_eq!(split.partition_columns.len(), 0);
651 }
652
653 #[test]
654 fn test_split_projection_boundary_first_partition_column() {
655 let (file_schema, table_schema) = create_test_schemas(3, 2);
656 let projection = ProjectionExprs::from_indices(&[3], &table_schema);
658
659 let split = SplitProjection::new(&file_schema, &projection);
660
661 assert_eq!(split.file_indices, Vec::<usize>::new());
662 assert_eq!(split.partition_columns.len(), 1);
663 assert_eq!(split.partition_columns[0].in_partition_values, 0);
664 }
665
666 #[test]
671 fn test_inject_partition_columns_multiple_partitions() {
672 let data =
673 record_batch!(("col_0", Int32, vec![1]), ("col_1", Int32, vec![2])).unwrap();
674
675 let (file_schema, table_schema) = create_test_schemas(2, 2);
677 let projection = ProjectionExprs::from_indices(&[0, 2, 1, 3], &table_schema);
679 let split = SplitProjection::new(&file_schema, &projection);
680
681 let partition_columns = vec![
683 PartitionColumnIndex {
684 in_remainder_projection: 2, in_partition_values: 0,
686 },
687 PartitionColumnIndex {
688 in_remainder_projection: 3, in_partition_values: 1,
690 },
691 ];
692
693 let partition_values =
694 vec![ScalarValue::from("part_a"), ScalarValue::from("part_b")];
695
696 let injected = inject_partition_columns_into_projection(
697 &split.remapped_projection,
698 &partition_columns,
699 partition_values,
700 );
701
702 let projector = injected.make_projector(&data.schema()).unwrap();
704 let result = projector.project_batch(&data).unwrap();
705
706 assert_eq!(result.num_columns(), 4);
707 assert_eq!(
708 result
709 .column(0)
710 .as_primitive::<arrow::datatypes::Int32Type>()
711 .value(0),
712 1
713 );
714 assert_eq!(result.column(1).as_string::<i32>().value(0), "part_a");
715 assert_eq!(
716 result
717 .column(2)
718 .as_primitive::<arrow::datatypes::Int32Type>()
719 .value(0),
720 2
721 );
722 assert_eq!(result.column(3).as_string::<i32>().value(0), "part_b");
723 }
724}