1mod cache;
21use cache::*;
22mod context;
23use context::*;
24mod task;
25
26use std::sync::Arc;
27
28use arrow_array::RecordBatch;
29use futures::channel::mpsc::{Sender, channel};
30use futures::stream::BoxStream;
31use futures::{SinkExt, StreamExt, TryStreamExt};
32pub use task::*;
33
34use crate::arrow::ArrowReaderBuilder;
35pub use crate::arrow::{ScanMetrics, ScanResult};
36use crate::delete_file_index::DeleteFileIndex;
37use crate::expr::visitors::inclusive_metrics_evaluator::InclusiveMetricsEvaluator;
38use crate::expr::{Bind, BoundPredicate, Predicate};
39use crate::io::FileIO;
40use crate::metadata_columns::{get_metadata_field_id, is_metadata_column_name};
41use crate::runtime::Runtime;
42use crate::spec::{DEFAULT_SCHEMA_NAME_MAPPING, DataContentType, NameMapping, SnapshotRef};
43use crate::table::Table;
44use crate::util::available_parallelism;
45use crate::{Error, ErrorKind, Result};
46
47pub type ArrowRecordBatchStream = BoxStream<'static, Result<RecordBatch>>;
49
50pub struct TableScanBuilder<'a> {
52 table: &'a Table,
53 column_names: Option<Vec<String>>,
55 snapshot_id: Option<i64>,
56 batch_size: Option<usize>,
57 case_sensitive: bool,
58 filter: Option<Predicate>,
59 concurrency_limit_data_files: usize,
60 concurrency_limit_manifest_entries: usize,
61 concurrency_limit_manifest_files: usize,
62 row_group_filtering_enabled: bool,
63 row_selection_enabled: bool,
64}
65
66impl<'a> TableScanBuilder<'a> {
67 pub(crate) fn new(table: &'a Table) -> Self {
68 let num_cpus = available_parallelism().get();
69
70 Self {
71 table,
72 column_names: None,
73 snapshot_id: None,
74 batch_size: None,
75 case_sensitive: true,
76 filter: None,
77 concurrency_limit_data_files: num_cpus,
78 concurrency_limit_manifest_entries: num_cpus,
79 concurrency_limit_manifest_files: num_cpus,
80 row_group_filtering_enabled: true,
81 row_selection_enabled: false,
82 }
83 }
84
85 pub fn with_batch_size(mut self, batch_size: Option<usize>) -> Self {
88 self.batch_size = batch_size;
89 self
90 }
91
92 pub fn with_case_sensitive(mut self, case_sensitive: bool) -> Self {
94 self.case_sensitive = case_sensitive;
95 self
96 }
97
98 pub fn with_filter(mut self, predicate: Predicate) -> Self {
100 self.filter = Some(predicate.rewrite_not());
103 self
104 }
105
106 pub fn select_all(mut self) -> Self {
108 self.column_names = None;
109 self
110 }
111
112 pub fn select_empty(mut self) -> Self {
114 self.column_names = Some(vec![]);
115 self
116 }
117
118 pub fn select(mut self, column_names: impl IntoIterator<Item = impl ToString>) -> Self {
120 self.column_names = Some(
121 column_names
122 .into_iter()
123 .map(|item| item.to_string())
124 .collect(),
125 );
126 self
127 }
128
129 pub fn snapshot_id(mut self, snapshot_id: i64) -> Self {
131 self.snapshot_id = Some(snapshot_id);
132 self
133 }
134
135 pub fn with_concurrency_limit(mut self, limit: usize) -> Self {
138 self.concurrency_limit_manifest_files = limit;
139 self.concurrency_limit_manifest_entries = limit;
140 self.concurrency_limit_data_files = limit;
141 self
142 }
143
144 pub fn with_data_file_concurrency_limit(mut self, limit: usize) -> Self {
146 self.concurrency_limit_data_files = limit;
147 self
148 }
149
150 pub fn with_manifest_entry_concurrency_limit(mut self, limit: usize) -> Self {
152 self.concurrency_limit_manifest_entries = limit;
153 self
154 }
155
156 pub fn with_row_group_filtering_enabled(mut self, row_group_filtering_enabled: bool) -> Self {
165 self.row_group_filtering_enabled = row_group_filtering_enabled;
166 self
167 }
168
169 pub fn with_row_selection_enabled(mut self, row_selection_enabled: bool) -> Self {
184 self.row_selection_enabled = row_selection_enabled;
185 self
186 }
187
188 pub fn build(self) -> Result<TableScan> {
190 let snapshot = match self.snapshot_id {
191 Some(snapshot_id) => self
192 .table
193 .metadata()
194 .snapshot_by_id(snapshot_id)
195 .ok_or_else(|| {
196 Error::new(
197 ErrorKind::DataInvalid,
198 format!("Snapshot with id {snapshot_id} not found"),
199 )
200 })?
201 .clone(),
202 None => {
203 let Some(current_snapshot_id) = self.table.metadata().current_snapshot() else {
204 return Ok(TableScan {
205 batch_size: self.batch_size,
206 column_names: self.column_names,
207 file_io: self.table.file_io().clone(),
208 plan_context: None,
209 concurrency_limit_data_files: self.concurrency_limit_data_files,
210 concurrency_limit_manifest_entries: self.concurrency_limit_manifest_entries,
211 concurrency_limit_manifest_files: self.concurrency_limit_manifest_files,
212 row_group_filtering_enabled: self.row_group_filtering_enabled,
213 row_selection_enabled: self.row_selection_enabled,
214 runtime: self.table.runtime().clone(),
215 });
216 };
217 current_snapshot_id.clone()
218 }
219 };
220
221 let schema = snapshot.schema(self.table.metadata())?;
222
223 if let Some(column_names) = self.column_names.as_ref() {
225 for column_name in column_names {
226 if is_metadata_column_name(column_name) {
228 continue;
229 }
230 if schema.field_by_name(column_name).is_none() {
231 return Err(Error::new(
232 ErrorKind::DataInvalid,
233 format!("Column {column_name} not found in table. Schema: {schema}"),
234 ));
235 }
236 }
237 }
238
239 let mut field_ids = vec![];
240 let column_names = self.column_names.clone().unwrap_or_else(|| {
241 schema
242 .as_struct()
243 .fields()
244 .iter()
245 .map(|f| f.name.clone())
246 .collect()
247 });
248
249 for column_name in column_names.iter() {
250 if is_metadata_column_name(column_name) {
252 field_ids.push(get_metadata_field_id(column_name)?);
253 continue;
254 }
255
256 let field_id = schema.field_id_by_name(column_name).ok_or_else(|| {
257 Error::new(
258 ErrorKind::DataInvalid,
259 format!("Column {column_name} not found in table. Schema: {schema}"),
260 )
261 })?;
262
263 schema
264 .as_struct()
265 .field_by_id(field_id)
266 .ok_or_else(|| {
267 Error::new(
268 ErrorKind::FeatureUnsupported,
269 format!(
270 "Column {column_name} is not a direct child of schema but a nested field, which is not supported now. Schema: {schema}"
271 ),
272 )
273 })?;
274
275 field_ids.push(field_id);
276 }
277
278 let snapshot_bound_predicate = if let Some(ref predicates) = self.filter {
279 Some(predicates.bind(schema.clone(), true)?)
280 } else {
281 None
282 };
283
284 let name_mapping = self
285 .table
286 .metadata()
287 .properties()
288 .get(DEFAULT_SCHEMA_NAME_MAPPING)
289 .map(|raw| {
290 serde_json::from_str::<NameMapping>(raw).map_err(|e| {
291 Error::new(
292 ErrorKind::DataInvalid,
293 format!(
294 "Failed to parse table property {DEFAULT_SCHEMA_NAME_MAPPING} as a NameMapping"
295 ),
296 )
297 .with_source(e)
298 })
299 })
300 .transpose()?
301 .map(Arc::new);
302
303 let plan_context = PlanContext {
304 snapshot,
305 table_metadata: self.table.metadata_ref(),
306 snapshot_schema: schema,
307 case_sensitive: self.case_sensitive,
308 predicate: self.filter.map(Arc::new),
309 snapshot_bound_predicate: snapshot_bound_predicate.map(Arc::new),
310 object_cache: self.table.object_cache(),
311 field_ids: Arc::new(field_ids),
312 name_mapping,
313 partition_filter_cache: Arc::new(PartitionFilterCache::new()),
314 manifest_evaluator_cache: Arc::new(ManifestEvaluatorCache::new()),
315 expression_evaluator_cache: Arc::new(ExpressionEvaluatorCache::new()),
316 };
317
318 Ok(TableScan {
319 batch_size: self.batch_size,
320 column_names: self.column_names,
321 file_io: self.table.file_io().clone(),
322 plan_context: Some(plan_context),
323 concurrency_limit_data_files: self.concurrency_limit_data_files,
324 concurrency_limit_manifest_entries: self.concurrency_limit_manifest_entries,
325 concurrency_limit_manifest_files: self.concurrency_limit_manifest_files,
326 row_group_filtering_enabled: self.row_group_filtering_enabled,
327 row_selection_enabled: self.row_selection_enabled,
328 runtime: self.table.runtime().clone(),
329 })
330 }
331}
332
333#[derive(Debug)]
335pub struct TableScan {
336 plan_context: Option<PlanContext>,
340 batch_size: Option<usize>,
341 file_io: FileIO,
342 column_names: Option<Vec<String>>,
343 concurrency_limit_manifest_files: usize,
346
347 concurrency_limit_manifest_entries: usize,
350
351 concurrency_limit_data_files: usize,
354
355 row_group_filtering_enabled: bool,
356 row_selection_enabled: bool,
357
358 runtime: Runtime,
359}
360
361impl TableScan {
362 pub async fn plan_files(&self) -> Result<FileScanTaskStream> {
364 let Some(plan_context) = self.plan_context.as_ref() else {
365 return Ok(Box::pin(futures::stream::empty()));
366 };
367
368 let concurrency_limit_manifest_files = self.concurrency_limit_manifest_files;
369 let concurrency_limit_manifest_entries = self.concurrency_limit_manifest_entries;
370
371 let (manifest_entry_data_ctx_tx, manifest_entry_data_ctx_rx) =
373 channel(concurrency_limit_manifest_files);
374 let (manifest_entry_delete_ctx_tx, manifest_entry_delete_ctx_rx) =
375 channel(concurrency_limit_manifest_files);
376
377 let (file_scan_task_tx, file_scan_task_rx) = channel(concurrency_limit_manifest_entries);
379
380 let (delete_file_idx, delete_file_tx) = DeleteFileIndex::new(self.runtime.clone());
381
382 let manifest_list = plan_context.get_manifest_list().await?;
383
384 let manifest_file_contexts = plan_context.build_manifest_file_contexts(
388 manifest_list,
389 manifest_entry_data_ctx_tx,
390 delete_file_idx.clone(),
391 manifest_entry_delete_ctx_tx,
392 )?;
393
394 let mut channel_for_manifest_error = file_scan_task_tx.clone();
395 let mut channel_for_data_manifest_entry_error = file_scan_task_tx.clone();
396 let mut channel_for_delete_manifest_entry_error = file_scan_task_tx.clone();
397
398 let rt = self.runtime.clone();
399
400 rt.io().spawn(async move {
402 let result = futures::stream::iter(manifest_file_contexts)
403 .try_for_each_concurrent(concurrency_limit_manifest_files, |ctx| async move {
404 ctx.fetch_manifest_and_stream_manifest_entries().await
405 })
406 .await;
407
408 if let Err(error) = result {
409 let _ = channel_for_manifest_error.send(Err(error)).await;
410 }
411 });
412
413 {
415 let rt = rt.clone();
416 let rt_inner = rt.clone();
417 rt.cpu().spawn(async move {
418 let result = manifest_entry_delete_ctx_rx
419 .map(|me_ctx| Ok((me_ctx, delete_file_tx.clone())))
420 .try_for_each_concurrent(
421 concurrency_limit_manifest_entries,
422 |(manifest_entry_context, tx)| {
423 let rt_inner = rt_inner.clone();
424 async move {
425 rt_inner
426 .cpu()
427 .spawn(async move {
428 Self::process_delete_manifest_entry(
429 manifest_entry_context,
430 tx,
431 )
432 .await
433 })
434 .await?
435 }
436 },
437 )
438 .await;
439
440 if let Err(error) = result {
441 let _ = channel_for_delete_manifest_entry_error
442 .send(Err(error))
443 .await;
444 }
445 });
446 }
447
448 {
450 let rt_inner = rt.clone();
451 rt.cpu().spawn(async move {
452 let result = manifest_entry_data_ctx_rx
453 .map(|me_ctx| Ok((me_ctx, file_scan_task_tx.clone())))
454 .try_for_each_concurrent(
455 concurrency_limit_manifest_entries,
456 |(manifest_entry_context, tx)| {
457 let rt_inner = rt_inner.clone();
458 async move {
459 rt_inner
460 .cpu()
461 .spawn(async move {
462 Self::process_data_manifest_entry(
463 manifest_entry_context,
464 tx,
465 )
466 .await
467 })
468 .await?
469 }
470 },
471 )
472 .await;
473
474 if let Err(error) = result {
475 let _ = channel_for_data_manifest_entry_error.send(Err(error)).await;
476 }
477 });
478 }
479
480 Ok(file_scan_task_rx.boxed())
481 }
482
483 pub async fn to_arrow(&self) -> Result<ArrowRecordBatchStream> {
485 let mut arrow_reader_builder =
486 ArrowReaderBuilder::new(self.file_io.clone(), self.runtime.clone())
487 .with_data_file_concurrency_limit(self.concurrency_limit_data_files)
488 .with_row_group_filtering_enabled(self.row_group_filtering_enabled)
489 .with_row_selection_enabled(self.row_selection_enabled);
490
491 if let Some(batch_size) = self.batch_size {
492 arrow_reader_builder = arrow_reader_builder.with_batch_size(batch_size);
493 }
494
495 arrow_reader_builder
496 .build()
497 .read(self.plan_files().await?)
498 .map(|result| result.stream())
499 }
500
501 pub fn column_names(&self) -> Option<&[String]> {
503 self.column_names.as_deref()
504 }
505
506 pub fn snapshot(&self) -> Option<&SnapshotRef> {
508 self.plan_context.as_ref().map(|x| &x.snapshot)
509 }
510
511 async fn process_data_manifest_entry(
512 manifest_entry_context: ManifestEntryContext,
513 mut file_scan_task_tx: Sender<Result<FileScanTask>>,
514 ) -> Result<()> {
515 if !manifest_entry_context.manifest_entry.is_alive() {
517 return Ok(());
518 }
519
520 if manifest_entry_context.manifest_entry.content_type() != DataContentType::Data {
522 return Err(Error::new(
523 ErrorKind::FeatureUnsupported,
524 "Encountered an entry for a delete file in a data file manifest",
525 ));
526 }
527
528 if let Some(ref bound_predicates) = manifest_entry_context.bound_predicates {
529 let BoundPredicates {
530 snapshot_bound_predicate,
531 partition_bound_predicate,
532 } = bound_predicates.as_ref();
533
534 let expression_evaluator_cache =
535 manifest_entry_context.expression_evaluator_cache.as_ref();
536
537 let expression_evaluator = expression_evaluator_cache.get(
538 manifest_entry_context.partition_spec_id,
539 partition_bound_predicate,
540 )?;
541
542 if !expression_evaluator.eval(manifest_entry_context.manifest_entry.data_file())? {
545 return Ok(());
546 }
547
548 if !InclusiveMetricsEvaluator::eval(
550 snapshot_bound_predicate,
551 manifest_entry_context.manifest_entry.data_file(),
552 false,
553 )? {
554 return Ok(());
555 }
556 }
557
558 file_scan_task_tx
562 .send(Ok(manifest_entry_context.into_file_scan_task().await?))
563 .await?;
564
565 Ok(())
566 }
567
568 async fn process_delete_manifest_entry(
569 manifest_entry_context: ManifestEntryContext,
570 mut delete_file_ctx_tx: Sender<DeleteFileContext>,
571 ) -> Result<()> {
572 if !manifest_entry_context.manifest_entry.is_alive() {
574 return Ok(());
575 }
576
577 if manifest_entry_context.manifest_entry.content_type() == DataContentType::Data {
579 return Err(Error::new(
580 ErrorKind::FeatureUnsupported,
581 "Encountered an entry for a data file in a delete manifest",
582 ));
583 }
584
585 if let Some(ref bound_predicates) = manifest_entry_context.bound_predicates {
586 let expression_evaluator_cache =
587 manifest_entry_context.expression_evaluator_cache.as_ref();
588
589 let expression_evaluator = expression_evaluator_cache.get(
590 manifest_entry_context.partition_spec_id,
591 &bound_predicates.partition_bound_predicate,
592 )?;
593
594 if !expression_evaluator.eval(manifest_entry_context.manifest_entry.data_file())? {
597 return Ok(());
598 }
599 }
600
601 delete_file_ctx_tx
602 .send(DeleteFileContext {
603 manifest_entry: manifest_entry_context.manifest_entry.clone(),
604 partition_spec_id: manifest_entry_context.partition_spec_id,
605 })
606 .await?;
607
608 Ok(())
609 }
610}
611
612pub(crate) struct BoundPredicates {
613 partition_bound_predicate: BoundPredicate,
614 snapshot_bound_predicate: BoundPredicate,
615}
616
617#[cfg(test)]
618pub mod tests {
619 #![allow(missing_docs)]
621
622 use std::collections::HashMap;
623 use std::fs;
624 use std::fs::File;
625 use std::sync::Arc;
626
627 use arrow_array::cast::AsArray;
628 use arrow_array::types::Int32Type;
629 use arrow_array::{
630 Array, ArrayRef, BooleanArray, Float64Array, Int32Array, Int64Array, RecordBatch, RunArray,
631 StringArray,
632 };
633 use futures::{TryStreamExt, stream};
634 use minijinja::value::Value;
635 use minijinja::{AutoEscape, Environment, context};
636 use parquet::arrow::{ArrowWriter, PARQUET_FIELD_ID_META_KEY};
637 use parquet::basic::Compression;
638 use parquet::file::properties::WriterProperties;
639 use tempfile::TempDir;
640 use uuid::Uuid;
641
642 use crate::arrow::ArrowReaderBuilder;
643 use crate::expr::{BoundPredicate, Reference};
644 use crate::io::{FileIO, OutputFile};
645 use crate::metadata_columns::{
646 RESERVED_COL_NAME_DELETE_FILE_PATH, RESERVED_COL_NAME_DELETE_FILE_POS,
647 RESERVED_COL_NAME_FILE, RESERVED_COL_NAME_POS, RESERVED_COL_NAME_SPEC_ID,
648 RESERVED_FIELD_ID_DELETE_FILE_PATH, RESERVED_FIELD_ID_DELETE_FILE_POS,
649 RESERVED_FIELD_ID_POS,
650 };
651 use crate::scan::FileScanTask;
652 use crate::spec::{
653 DEFAULT_SCHEMA_NAME_MAPPING, DataContentType, DataFileBuilder, DataFileFormat, Datum,
654 Literal, ManifestEntry, ManifestListWriter, ManifestStatus, ManifestWriterBuilder,
655 NestedField, PartitionSpec, PrimitiveType, Schema, Struct, StructType, TableMetadata, Type,
656 };
657 use crate::table::Table;
658 use crate::test_utils::test_runtime;
659 use crate::{ErrorKind, TableIdent};
660
661 fn render_template(template: &str, ctx: Value) -> String {
662 let mut env = Environment::new();
663 env.set_auto_escape_callback(|_| AutoEscape::None);
664 env.render_str(template, ctx).unwrap()
665 }
666
667 pub struct TableTestFixture {
668 pub table_location: String,
669 pub table: Table,
670 }
671
672 impl TableTestFixture {
673 #[allow(clippy::new_without_default)]
674 pub fn new() -> Self {
675 let tmp_dir = TempDir::new().unwrap();
676 let table_location = tmp_dir.path().join("table1");
677 let manifest_list1_location = table_location.join("metadata/manifests_list_1.avro");
678 let manifest_list2_location = table_location.join("metadata/manifests_list_2.avro");
679 let table_metadata1_location = table_location.join("metadata/v1.json");
680
681 let file_io = FileIO::new_with_fs();
682
683 let table_metadata = {
684 let template_json_str = fs::read_to_string(format!(
685 "{}/testdata/example_table_metadata_v2.json",
686 env!("CARGO_MANIFEST_DIR")
687 ))
688 .unwrap();
689 let metadata_json = render_template(&template_json_str, context! {
690 table_location => &table_location,
691 manifest_list_1_location => &manifest_list1_location,
692 manifest_list_2_location => &manifest_list2_location,
693 table_metadata_1_location => &table_metadata1_location,
694 });
695 serde_json::from_str::<TableMetadata>(&metadata_json).unwrap()
696 };
697
698 let table = Table::builder()
699 .metadata(table_metadata)
700 .identifier(TableIdent::from_strs(["db", "table1"]).unwrap())
701 .file_io(file_io.clone())
702 .metadata_location(table_metadata1_location.as_os_str().to_str().unwrap())
703 .runtime(test_runtime())
704 .build()
705 .unwrap();
706
707 Self {
708 table_location: table_location.to_str().unwrap().to_string(),
709 table,
710 }
711 }
712
713 #[allow(clippy::new_without_default)]
714 pub fn new_empty() -> Self {
715 let tmp_dir = TempDir::new().unwrap();
716 let table_location = tmp_dir.path().join("table1");
717 let table_metadata1_location = table_location.join("metadata/v1.json");
718
719 let file_io = FileIO::new_with_fs();
720
721 let table_metadata = {
722 let template_json_str = fs::read_to_string(format!(
723 "{}/testdata/example_empty_table_metadata_v2.json",
724 env!("CARGO_MANIFEST_DIR")
725 ))
726 .unwrap();
727 let metadata_json = render_template(&template_json_str, context! {
728 table_location => &table_location,
729 table_metadata_1_location => &table_metadata1_location,
730 });
731 serde_json::from_str::<TableMetadata>(&metadata_json).unwrap()
732 };
733
734 let table = Table::builder()
735 .metadata(table_metadata)
736 .identifier(TableIdent::from_strs(["db", "table1"]).unwrap())
737 .file_io(file_io.clone())
738 .metadata_location(table_metadata1_location.as_os_str().to_str().unwrap())
739 .runtime(test_runtime())
740 .build()
741 .unwrap();
742
743 Self {
744 table_location: table_location.to_str().unwrap().to_string(),
745 table,
746 }
747 }
748
749 pub fn new_with_deep_history() -> Self {
753 let tmp_dir = TempDir::new().unwrap();
754 let table_location = tmp_dir.path().join("table1");
755 let table_metadata1_location = table_location.join("metadata/v1.json");
756
757 let file_io = FileIO::new_with_fs();
758
759 let table_metadata = {
760 let json_str = fs::read_to_string(format!(
761 "{}/testdata/example_table_metadata_v2_deep_history.json",
762 env!("CARGO_MANIFEST_DIR")
763 ))
764 .unwrap();
765 serde_json::from_str::<TableMetadata>(&json_str).unwrap()
766 };
767
768 let table = Table::builder()
769 .metadata(table_metadata)
770 .identifier(TableIdent::from_strs(["db", "table1"]).unwrap())
771 .file_io(file_io.clone())
772 .metadata_location(table_metadata1_location.as_os_str().to_str().unwrap())
773 .runtime(test_runtime())
774 .build()
775 .unwrap();
776
777 Self {
778 table_location: table_location.to_str().unwrap().to_string(),
779 table,
780 }
781 }
782
783 pub fn new_unpartitioned() -> Self {
784 let tmp_dir = TempDir::new().unwrap();
785 let table_location = tmp_dir.path().join("table1");
786 let manifest_list1_location = table_location.join("metadata/manifests_list_1.avro");
787 let manifest_list2_location = table_location.join("metadata/manifests_list_2.avro");
788 let table_metadata1_location = table_location.join("metadata/v1.json");
789
790 let file_io = FileIO::new_with_fs();
791
792 let mut table_metadata = {
793 let template_json_str = fs::read_to_string(format!(
794 "{}/testdata/example_table_metadata_v2.json",
795 env!("CARGO_MANIFEST_DIR")
796 ))
797 .unwrap();
798 let metadata_json = render_template(&template_json_str, context! {
799 table_location => &table_location,
800 manifest_list_1_location => &manifest_list1_location,
801 manifest_list_2_location => &manifest_list2_location,
802 table_metadata_1_location => &table_metadata1_location,
803 });
804 serde_json::from_str::<TableMetadata>(&metadata_json).unwrap()
805 };
806
807 table_metadata.default_spec = Arc::new(PartitionSpec::unpartition_spec());
808 table_metadata.partition_specs.clear();
809 table_metadata.default_partition_type = StructType::new(vec![]);
810 table_metadata
811 .partition_specs
812 .insert(0, table_metadata.default_spec.clone());
813
814 let table = Table::builder()
815 .metadata(table_metadata)
816 .identifier(TableIdent::from_strs(["db", "table1"]).unwrap())
817 .file_io(file_io.clone())
818 .metadata_location(table_metadata1_location.to_str().unwrap())
819 .runtime(test_runtime())
820 .build()
821 .unwrap();
822
823 Self {
824 table_location: table_location.to_str().unwrap().to_string(),
825 table,
826 }
827 }
828
829 pub fn new_with_partition_evolution() -> Self {
830 let table = Self::new().table;
831 let table_location = table.metadata().location.clone();
832
833 let manifest_list1_location =
834 format!("{}/metadata/manifests_list_1.avro", table_location);
835 let manifest_list2_location =
836 format!("{}/metadata/manifests_list_2.avro", table_location);
837 let manifest_list3_location =
838 format!("{}/metadata/manifests_list_3.avro", table_location);
839 let table_metadata1_location = format!("{}/metadata/v1.json", table_location);
840
841 let new_table_metadata = {
842 let template_json_str = fs::read_to_string(format!(
843 "{}/testdata/example_table_metadata_v2_partition_evolution.json",
844 env!("CARGO_MANIFEST_DIR")
845 ))
846 .unwrap();
847 let metadata_json = render_template(&template_json_str, context! {
848 table_location => &table_location,
849 manifest_list_1_location => &manifest_list1_location,
850 manifest_list_2_location => &manifest_list2_location,
851 manifest_list_3_location => &manifest_list3_location,
852 table_metadata_1_location => &table_metadata1_location,
853 });
854 Arc::new(serde_json::from_str::<TableMetadata>(&metadata_json).unwrap())
855 };
856
857 Self {
858 table_location,
859 table: table.with_metadata(new_table_metadata),
860 }
861 }
862
863 fn next_manifest_file(&self) -> OutputFile {
864 self.table
865 .file_io()
866 .new_output(format!(
867 "{}/metadata/manifest_{}.avro",
868 self.table_location,
869 Uuid::new_v4()
870 ))
871 .unwrap()
872 }
873
874 pub async fn setup_manifest_files(&mut self) {
875 let current_snapshot = self.table.metadata().current_snapshot().unwrap();
876 let parent_snapshot = current_snapshot
877 .parent_snapshot(self.table.metadata())
878 .unwrap();
879 let current_schema = current_snapshot.schema(self.table.metadata()).unwrap();
880 let current_partition_spec = self.table.metadata().default_partition_spec();
881
882 let parquet_file_size = self.write_parquet_data_files();
884
885 let mut writer = ManifestWriterBuilder::new(
886 self.next_manifest_file(),
887 Some(current_snapshot.snapshot_id()),
888 current_schema.clone(),
889 current_partition_spec.as_ref().clone(),
890 )
891 .build_v2_data();
892 writer
893 .add_entry(
894 ManifestEntry::builder()
895 .status(ManifestStatus::Added)
896 .data_file(
897 DataFileBuilder::default()
898 .partition_spec_id(0)
899 .content(DataContentType::Data)
900 .file_path(format!("{}/1.parquet", &self.table_location))
901 .file_format(DataFileFormat::Parquet)
902 .file_size_in_bytes(parquet_file_size)
903 .record_count(1)
904 .partition(Struct::from_iter([Some(Literal::long(100))]))
905 .key_metadata(None)
906 .build()
907 .unwrap(),
908 )
909 .build(),
910 )
911 .unwrap();
912 writer
913 .add_delete_entry(
914 ManifestEntry::builder()
915 .status(ManifestStatus::Deleted)
916 .snapshot_id(parent_snapshot.snapshot_id())
917 .sequence_number(parent_snapshot.sequence_number())
918 .file_sequence_number(parent_snapshot.sequence_number())
919 .data_file(
920 DataFileBuilder::default()
921 .partition_spec_id(0)
922 .content(DataContentType::Data)
923 .file_path(format!("{}/2.parquet", &self.table_location))
924 .file_format(DataFileFormat::Parquet)
925 .file_size_in_bytes(parquet_file_size)
926 .record_count(1)
927 .partition(Struct::from_iter([Some(Literal::long(200))]))
928 .build()
929 .unwrap(),
930 )
931 .build(),
932 )
933 .unwrap();
934 writer
935 .add_existing_entry(
936 ManifestEntry::builder()
937 .status(ManifestStatus::Existing)
938 .snapshot_id(parent_snapshot.snapshot_id())
939 .sequence_number(parent_snapshot.sequence_number())
940 .file_sequence_number(parent_snapshot.sequence_number())
941 .data_file(
942 DataFileBuilder::default()
943 .partition_spec_id(0)
944 .content(DataContentType::Data)
945 .file_path(format!("{}/3.parquet", &self.table_location))
946 .file_format(DataFileFormat::Parquet)
947 .file_size_in_bytes(parquet_file_size)
948 .record_count(1)
949 .partition(Struct::from_iter([Some(Literal::long(300))]))
950 .build()
951 .unwrap(),
952 )
953 .build(),
954 )
955 .unwrap();
956 let data_file_manifest = writer.write_manifest_file().await.unwrap();
957
958 let manifest_list_writer = self
960 .table
961 .file_io()
962 .new_output(current_snapshot.manifest_list())
963 .unwrap()
964 .writer()
965 .await
966 .unwrap();
967 let mut manifest_list_write = ManifestListWriter::v2(
968 manifest_list_writer,
969 current_snapshot.snapshot_id(),
970 current_snapshot.parent_snapshot_id(),
971 current_snapshot.sequence_number(),
972 );
973 manifest_list_write
974 .add_manifests(vec![data_file_manifest].into_iter())
975 .unwrap();
976 manifest_list_write.close().await.unwrap();
977 }
978
979 pub async fn setup_manifest_files_with_partition_evolution(&mut self) {
980 let current_snapshot = self.table.metadata().current_snapshot().unwrap();
981 let parent_snapshot = current_snapshot
982 .parent_snapshot(self.table.metadata())
983 .unwrap();
984 let current_schema = current_snapshot.schema(self.table.metadata()).unwrap();
985 let current_partition_spec = self.table.metadata().default_partition_spec();
986
987 let parquet_file_size = self.write_parquet_data_files();
989
990 let mut writer = ManifestWriterBuilder::new(
991 self.next_manifest_file(),
992 Some(current_snapshot.snapshot_id()),
993 current_schema.clone(),
994 current_partition_spec.as_ref().clone(),
995 )
996 .build_v2_data();
997 writer
998 .add_entry(
999 ManifestEntry::builder()
1000 .status(ManifestStatus::Added)
1001 .data_file(
1002 DataFileBuilder::default()
1003 .partition_spec_id(1)
1004 .content(DataContentType::Data)
1005 .file_path(format!("{}/1.parquet", &self.table_location))
1006 .file_format(DataFileFormat::Parquet)
1007 .file_size_in_bytes(parquet_file_size)
1008 .record_count(1)
1009 .partition(Struct::from_iter([
1010 Some(Literal::long(100)),
1011 Some(Literal::string("apa")),
1012 Some(Literal::int(27)),
1013 ]))
1014 .key_metadata(None)
1015 .build()
1016 .unwrap(),
1017 )
1018 .build(),
1019 )
1020 .unwrap();
1021 writer
1022 .add_delete_entry(
1023 ManifestEntry::builder()
1024 .status(ManifestStatus::Deleted)
1025 .snapshot_id(parent_snapshot.snapshot_id())
1026 .sequence_number(parent_snapshot.sequence_number())
1027 .file_sequence_number(parent_snapshot.sequence_number())
1028 .data_file(
1029 DataFileBuilder::default()
1030 .partition_spec_id(1)
1031 .content(DataContentType::Data)
1032 .file_path(format!("{}/2.parquet", &self.table_location))
1033 .file_format(DataFileFormat::Parquet)
1034 .file_size_in_bytes(parquet_file_size)
1035 .record_count(1)
1036 .partition(Struct::from_iter([
1037 Some(Literal::long(200)),
1038 Some(Literal::string("ice")),
1039 Some(Literal::int(5)),
1040 ]))
1041 .build()
1042 .unwrap(),
1043 )
1044 .build(),
1045 )
1046 .unwrap();
1047 writer
1048 .add_existing_entry(
1049 ManifestEntry::builder()
1050 .status(ManifestStatus::Existing)
1051 .snapshot_id(parent_snapshot.snapshot_id())
1052 .sequence_number(parent_snapshot.sequence_number())
1053 .file_sequence_number(parent_snapshot.sequence_number())
1054 .data_file(
1055 DataFileBuilder::default()
1056 .partition_spec_id(1)
1057 .content(DataContentType::Data)
1058 .file_path(format!("{}/3.parquet", &self.table_location))
1059 .file_format(DataFileFormat::Parquet)
1060 .file_size_in_bytes(parquet_file_size)
1061 .record_count(1)
1062 .partition(Struct::from_iter([
1063 Some(Literal::long(300)),
1064 Some(Literal::string("apa")),
1065 Some(Literal::int(19)),
1066 ]))
1067 .build()
1068 .unwrap(),
1069 )
1070 .build(),
1071 )
1072 .unwrap();
1073 let data_file_manifest = writer.write_manifest_file().await.unwrap();
1074
1075 let manifest_list_writer = self
1077 .table
1078 .file_io()
1079 .new_output(current_snapshot.manifest_list())
1080 .unwrap()
1081 .writer()
1082 .await
1083 .unwrap();
1084 let mut manifest_list_write = ManifestListWriter::v2(
1085 manifest_list_writer,
1086 current_snapshot.snapshot_id(),
1087 current_snapshot.parent_snapshot_id(),
1088 current_snapshot.sequence_number(),
1089 );
1090 manifest_list_write
1091 .add_manifests(vec![data_file_manifest].into_iter())
1092 .unwrap();
1093 manifest_list_write.close().await.unwrap();
1094 }
1095
1096 fn write_parquet_data_files(&self) -> u64 {
1099 fs::create_dir_all(&self.table_location).unwrap();
1100
1101 let schema = {
1102 let fields = vec![
1103 arrow_schema::Field::new("x", arrow_schema::DataType::Int64, false)
1104 .with_metadata(HashMap::from([(
1105 PARQUET_FIELD_ID_META_KEY.to_string(),
1106 "1".to_string(),
1107 )])),
1108 arrow_schema::Field::new("y", arrow_schema::DataType::Int64, false)
1109 .with_metadata(HashMap::from([(
1110 PARQUET_FIELD_ID_META_KEY.to_string(),
1111 "2".to_string(),
1112 )])),
1113 arrow_schema::Field::new("z", arrow_schema::DataType::Int64, false)
1114 .with_metadata(HashMap::from([(
1115 PARQUET_FIELD_ID_META_KEY.to_string(),
1116 "3".to_string(),
1117 )])),
1118 arrow_schema::Field::new("a", arrow_schema::DataType::Utf8, false)
1119 .with_metadata(HashMap::from([(
1120 PARQUET_FIELD_ID_META_KEY.to_string(),
1121 "4".to_string(),
1122 )])),
1123 arrow_schema::Field::new("dbl", arrow_schema::DataType::Float64, false)
1124 .with_metadata(HashMap::from([(
1125 PARQUET_FIELD_ID_META_KEY.to_string(),
1126 "5".to_string(),
1127 )])),
1128 arrow_schema::Field::new("i32", arrow_schema::DataType::Int32, false)
1129 .with_metadata(HashMap::from([(
1130 PARQUET_FIELD_ID_META_KEY.to_string(),
1131 "6".to_string(),
1132 )])),
1133 arrow_schema::Field::new("i64", arrow_schema::DataType::Int64, false)
1134 .with_metadata(HashMap::from([(
1135 PARQUET_FIELD_ID_META_KEY.to_string(),
1136 "7".to_string(),
1137 )])),
1138 arrow_schema::Field::new("bool", arrow_schema::DataType::Boolean, false)
1139 .with_metadata(HashMap::from([(
1140 PARQUET_FIELD_ID_META_KEY.to_string(),
1141 "8".to_string(),
1142 )])),
1143 ];
1144 Arc::new(arrow_schema::Schema::new(fields))
1145 };
1146 let col1 = Arc::new(Int64Array::from_iter_values(vec![1; 1024])) as ArrayRef;
1148
1149 let mut values = vec![2; 512];
1150 values.append(vec![3; 200].as_mut());
1151 values.append(vec![4; 300].as_mut());
1152 values.append(vec![5; 12].as_mut());
1153
1154 let col2 = Arc::new(Int64Array::from_iter_values(values)) as ArrayRef;
1156
1157 let mut values = vec![3; 512];
1158 values.append(vec![4; 512].as_mut());
1159
1160 let col3 = Arc::new(Int64Array::from_iter_values(values)) as ArrayRef;
1162
1163 let mut values = vec!["Apache"; 512];
1165 values.append(vec!["Iceberg"; 512].as_mut());
1166 let col4 = Arc::new(StringArray::from_iter_values(values)) as ArrayRef;
1167
1168 let mut values = vec![100.0f64; 512];
1170 values.append(vec![150.0f64; 12].as_mut());
1171 values.append(vec![200.0f64; 500].as_mut());
1172 let col5 = Arc::new(Float64Array::from_iter_values(values)) as ArrayRef;
1173
1174 let mut values = vec![100i32; 512];
1176 values.append(vec![150i32; 12].as_mut());
1177 values.append(vec![200i32; 500].as_mut());
1178 let col6 = Arc::new(Int32Array::from_iter_values(values)) as ArrayRef;
1179
1180 let mut values = vec![100i64; 512];
1182 values.append(vec![150i64; 12].as_mut());
1183 values.append(vec![200i64; 500].as_mut());
1184 let col7 = Arc::new(Int64Array::from_iter_values(values)) as ArrayRef;
1185
1186 let mut values = vec![false; 512];
1188 values.append(vec![true; 512].as_mut());
1189 let values: BooleanArray = values.into();
1190 let col8 = Arc::new(values) as ArrayRef;
1191
1192 let to_write = RecordBatch::try_new(schema.clone(), vec![
1193 col1, col2, col3, col4, col5, col6, col7, col8,
1194 ])
1195 .unwrap();
1196
1197 let props = WriterProperties::builder()
1199 .set_compression(Compression::SNAPPY)
1200 .build();
1201
1202 for n in 1..=3 {
1203 let file = File::create(format!("{}/{}.parquet", &self.table_location, n)).unwrap();
1204 let mut writer =
1205 ArrowWriter::try_new(file, to_write.schema(), Some(props.clone())).unwrap();
1206
1207 writer.write(&to_write).expect("Writing batch");
1208
1209 writer.close().unwrap();
1211 }
1212
1213 fs::metadata(format!("{}/1.parquet", &self.table_location))
1214 .unwrap()
1215 .len()
1216 }
1217
1218 pub async fn setup_unpartitioned_manifest_files(&mut self) {
1219 let current_snapshot = self.table.metadata().current_snapshot().unwrap();
1220 let parent_snapshot = current_snapshot
1221 .parent_snapshot(self.table.metadata())
1222 .unwrap();
1223 let current_schema = current_snapshot.schema(self.table.metadata()).unwrap();
1224 let current_partition_spec = Arc::new(PartitionSpec::unpartition_spec());
1225
1226 let parquet_file_size = self.write_parquet_data_files();
1228
1229 let mut writer = ManifestWriterBuilder::new(
1231 self.next_manifest_file(),
1232 Some(current_snapshot.snapshot_id()),
1233 current_schema.clone(),
1234 current_partition_spec.as_ref().clone(),
1235 )
1236 .build_v2_data();
1237
1238 let empty_partition = Struct::empty();
1240
1241 writer
1242 .add_entry(
1243 ManifestEntry::builder()
1244 .status(ManifestStatus::Added)
1245 .data_file(
1246 DataFileBuilder::default()
1247 .partition_spec_id(0)
1248 .content(DataContentType::Data)
1249 .file_path(format!("{}/1.parquet", &self.table_location))
1250 .file_format(DataFileFormat::Parquet)
1251 .file_size_in_bytes(parquet_file_size)
1252 .record_count(1)
1253 .partition(empty_partition.clone())
1254 .key_metadata(None)
1255 .build()
1256 .unwrap(),
1257 )
1258 .build(),
1259 )
1260 .unwrap();
1261
1262 writer
1263 .add_delete_entry(
1264 ManifestEntry::builder()
1265 .status(ManifestStatus::Deleted)
1266 .snapshot_id(parent_snapshot.snapshot_id())
1267 .sequence_number(parent_snapshot.sequence_number())
1268 .file_sequence_number(parent_snapshot.sequence_number())
1269 .data_file(
1270 DataFileBuilder::default()
1271 .partition_spec_id(0)
1272 .content(DataContentType::Data)
1273 .file_path(format!("{}/2.parquet", &self.table_location))
1274 .file_format(DataFileFormat::Parquet)
1275 .file_size_in_bytes(parquet_file_size)
1276 .record_count(1)
1277 .partition(empty_partition.clone())
1278 .build()
1279 .unwrap(),
1280 )
1281 .build(),
1282 )
1283 .unwrap();
1284
1285 writer
1286 .add_existing_entry(
1287 ManifestEntry::builder()
1288 .status(ManifestStatus::Existing)
1289 .snapshot_id(parent_snapshot.snapshot_id())
1290 .sequence_number(parent_snapshot.sequence_number())
1291 .file_sequence_number(parent_snapshot.sequence_number())
1292 .data_file(
1293 DataFileBuilder::default()
1294 .partition_spec_id(0)
1295 .content(DataContentType::Data)
1296 .file_path(format!("{}/3.parquet", &self.table_location))
1297 .file_format(DataFileFormat::Parquet)
1298 .file_size_in_bytes(parquet_file_size)
1299 .record_count(1)
1300 .partition(empty_partition.clone())
1301 .build()
1302 .unwrap(),
1303 )
1304 .build(),
1305 )
1306 .unwrap();
1307
1308 let data_file_manifest = writer.write_manifest_file().await.unwrap();
1309
1310 let manifest_list_writer = self
1312 .table
1313 .file_io()
1314 .new_output(current_snapshot.manifest_list())
1315 .unwrap()
1316 .writer()
1317 .await
1318 .unwrap();
1319 let mut manifest_list_write = ManifestListWriter::v2(
1320 manifest_list_writer,
1321 current_snapshot.snapshot_id(),
1322 current_snapshot.parent_snapshot_id(),
1323 current_snapshot.sequence_number(),
1324 );
1325 manifest_list_write
1326 .add_manifests(vec![data_file_manifest].into_iter())
1327 .unwrap();
1328 manifest_list_write.close().await.unwrap();
1329 }
1330
1331 pub async fn setup_deadlock_manifests(&mut self) {
1332 let current_snapshot = self.table.metadata().current_snapshot().unwrap();
1333 let _parent_snapshot = current_snapshot
1334 .parent_snapshot(self.table.metadata())
1335 .unwrap();
1336 let current_schema = current_snapshot.schema(self.table.metadata()).unwrap();
1337 let current_partition_spec = self.table.metadata().default_partition_spec();
1338
1339 let mut writer = ManifestWriterBuilder::new(
1341 self.next_manifest_file(),
1342 Some(current_snapshot.snapshot_id()),
1343 current_schema.clone(),
1344 current_partition_spec.as_ref().clone(),
1345 )
1346 .build_v2_data();
1347
1348 for i in 0..10 {
1350 writer
1351 .add_entry(
1352 ManifestEntry::builder()
1353 .status(ManifestStatus::Added)
1354 .data_file(
1355 DataFileBuilder::default()
1356 .partition_spec_id(0)
1357 .content(DataContentType::Data)
1358 .file_path(format!("{}/{}.parquet", &self.table_location, i))
1359 .file_format(DataFileFormat::Parquet)
1360 .file_size_in_bytes(100)
1361 .record_count(1)
1362 .partition(Struct::from_iter([Some(Literal::long(100))]))
1363 .key_metadata(None)
1364 .build()
1365 .unwrap(),
1366 )
1367 .build(),
1368 )
1369 .unwrap();
1370 }
1371 let data_manifest = writer.write_manifest_file().await.unwrap();
1372
1373 let mut writer = ManifestWriterBuilder::new(
1375 self.next_manifest_file(),
1376 Some(current_snapshot.snapshot_id()),
1377 current_schema.clone(),
1378 current_partition_spec.as_ref().clone(),
1379 )
1380 .build_v2_deletes();
1381
1382 writer
1383 .add_entry(
1384 ManifestEntry::builder()
1385 .status(ManifestStatus::Added)
1386 .data_file(
1387 DataFileBuilder::default()
1388 .partition_spec_id(0)
1389 .content(DataContentType::PositionDeletes)
1390 .file_path(format!("{}/del.parquet", &self.table_location))
1391 .file_format(DataFileFormat::Parquet)
1392 .file_size_in_bytes(100)
1393 .record_count(1)
1394 .partition(Struct::from_iter([Some(Literal::long(100))]))
1395 .build()
1396 .unwrap(),
1397 )
1398 .build(),
1399 )
1400 .unwrap();
1401 let delete_manifest = writer.write_manifest_file().await.unwrap();
1402
1403 let manifest_list_writer = self
1406 .table
1407 .file_io()
1408 .new_output(current_snapshot.manifest_list())
1409 .unwrap()
1410 .writer()
1411 .await
1412 .unwrap();
1413 let mut manifest_list_write = ManifestListWriter::v2(
1414 manifest_list_writer,
1415 current_snapshot.snapshot_id(),
1416 current_snapshot.parent_snapshot_id(),
1417 current_snapshot.sequence_number(),
1418 );
1419 manifest_list_write
1420 .add_manifests(vec![data_manifest, delete_manifest].into_iter())
1421 .unwrap();
1422 manifest_list_write.close().await.unwrap();
1423 }
1424
1425 pub async fn setup_multi_row_group_manifest(&mut self, delete_positions: &[i64]) {
1434 let current_snapshot = self.table.metadata().current_snapshot().unwrap();
1435 let current_schema = current_snapshot.schema(self.table.metadata()).unwrap();
1436 let current_partition_spec = self.table.metadata().default_partition_spec();
1437
1438 let partition = Struct::from_iter([Some(Literal::long(1000))]);
1443
1444 let (data_file_path, data_file_size) = self.write_multi_row_group_data_file();
1445
1446 let mut data_writer = ManifestWriterBuilder::new(
1447 self.next_manifest_file(),
1448 Some(current_snapshot.snapshot_id()),
1449 current_schema.clone(),
1450 current_partition_spec.as_ref().clone(),
1451 )
1452 .build_v2_data();
1453 data_writer
1454 .add_entry(
1455 ManifestEntry::builder()
1456 .status(ManifestStatus::Added)
1457 .data_file(
1458 DataFileBuilder::default()
1459 .partition_spec_id(0)
1460 .content(DataContentType::Data)
1461 .file_path(data_file_path.clone())
1462 .file_format(DataFileFormat::Parquet)
1463 .file_size_in_bytes(data_file_size)
1464 .record_count(300)
1465 .partition(partition.clone())
1466 .key_metadata(None)
1467 .build()
1468 .unwrap(),
1469 )
1470 .build(),
1471 )
1472 .unwrap();
1473 let data_manifest = data_writer.write_manifest_file().await.unwrap();
1474
1475 let mut manifests = vec![data_manifest];
1476
1477 if !delete_positions.is_empty() {
1478 let (del_path, del_size) =
1479 self.write_positional_delete_file(&data_file_path, delete_positions);
1480
1481 let mut delete_writer = ManifestWriterBuilder::new(
1482 self.next_manifest_file(),
1483 Some(current_snapshot.snapshot_id()),
1484 current_schema.clone(),
1485 current_partition_spec.as_ref().clone(),
1486 )
1487 .build_v2_deletes();
1488 delete_writer
1489 .add_entry(
1490 ManifestEntry::builder()
1491 .status(ManifestStatus::Added)
1492 .data_file(
1493 DataFileBuilder::default()
1494 .partition_spec_id(0)
1495 .content(DataContentType::PositionDeletes)
1496 .file_path(del_path)
1497 .file_format(DataFileFormat::Parquet)
1498 .file_size_in_bytes(del_size)
1499 .record_count(delete_positions.len() as u64)
1500 .partition(partition.clone())
1501 .build()
1502 .unwrap(),
1503 )
1504 .build(),
1505 )
1506 .unwrap();
1507 manifests.push(delete_writer.write_manifest_file().await.unwrap());
1508 }
1509
1510 let manifest_list_writer = self
1511 .table
1512 .file_io()
1513 .new_output(current_snapshot.manifest_list())
1514 .unwrap()
1515 .writer()
1516 .await
1517 .unwrap();
1518 let mut manifest_list_write = ManifestListWriter::v2(
1519 manifest_list_writer,
1520 current_snapshot.snapshot_id(),
1521 current_snapshot.parent_snapshot_id(),
1522 current_snapshot.sequence_number(),
1523 );
1524 manifest_list_write
1525 .add_manifests(manifests.into_iter())
1526 .unwrap();
1527 manifest_list_write.close().await.unwrap();
1528 }
1529
1530 fn write_multi_row_group_data_file(&self) -> (String, u64) {
1534 fs::create_dir_all(&self.table_location).unwrap();
1535
1536 let arrow_schema = Arc::new(arrow_schema::Schema::new(vec![
1537 arrow_schema::Field::new("x", arrow_schema::DataType::Int64, false).with_metadata(
1538 HashMap::from([(PARQUET_FIELD_ID_META_KEY.to_string(), "1".to_string())]),
1539 ),
1540 arrow_schema::Field::new("y", arrow_schema::DataType::Int64, false).with_metadata(
1541 HashMap::from([(PARQUET_FIELD_ID_META_KEY.to_string(), "2".to_string())]),
1542 ),
1543 ]));
1544
1545 let path = format!("{}/mrg.parquet", &self.table_location);
1546 let max_row_group_row_count = 100;
1547 let props = WriterProperties::builder()
1548 .set_compression(Compression::SNAPPY)
1549 .set_max_row_group_row_count(Some(max_row_group_row_count))
1550 .build();
1551
1552 let file = File::create(&path).unwrap();
1553 let mut writer = ArrowWriter::try_new(file, arrow_schema.clone(), Some(props)).unwrap();
1554 for group in 0..3i64 {
1555 let base = 1000 + group * max_row_group_row_count as i64;
1556 let col = Arc::new(Int64Array::from_iter_values(
1557 base..base + max_row_group_row_count as i64,
1558 )) as ArrayRef;
1559 let batch =
1560 RecordBatch::try_new(arrow_schema.clone(), vec![col.clone(), col]).unwrap();
1561 writer.write(&batch).unwrap();
1562 }
1563 writer.close().unwrap();
1564
1565 let size = fs::metadata(&path).unwrap().len();
1566 (path, size)
1567 }
1568
1569 fn write_positional_delete_file(
1572 &self,
1573 data_path: &str,
1574 positions: &[i64],
1575 ) -> (String, u64) {
1576 let del_schema = Arc::new(arrow_schema::Schema::new(vec![
1577 arrow_schema::Field::new(
1578 RESERVED_COL_NAME_DELETE_FILE_PATH,
1579 arrow_schema::DataType::Utf8,
1580 false,
1581 )
1582 .with_metadata(HashMap::from([(
1583 PARQUET_FIELD_ID_META_KEY.to_string(),
1584 RESERVED_FIELD_ID_DELETE_FILE_PATH.to_string(), )])),
1586 arrow_schema::Field::new(
1587 RESERVED_COL_NAME_DELETE_FILE_POS,
1588 arrow_schema::DataType::Int64,
1589 false,
1590 )
1591 .with_metadata(HashMap::from([(
1592 PARQUET_FIELD_ID_META_KEY.to_string(),
1593 RESERVED_FIELD_ID_DELETE_FILE_POS.to_string(), )])),
1595 ]));
1596
1597 let batch = RecordBatch::try_new(del_schema.clone(), vec![
1598 Arc::new(StringArray::from_iter_values(std::iter::repeat_n(
1599 data_path.to_string(),
1600 positions.len(),
1601 ))) as ArrayRef,
1602 Arc::new(Int64Array::from_iter_values(positions.iter().copied())) as ArrayRef,
1603 ])
1604 .unwrap();
1605
1606 let path = format!("{}/pos-del.parquet", &self.table_location);
1607 let props = WriterProperties::builder()
1608 .set_compression(Compression::SNAPPY)
1609 .build();
1610 let file = File::create(&path).unwrap();
1611 let mut writer = ArrowWriter::try_new(file, del_schema, Some(props)).unwrap();
1612 writer.write(&batch).unwrap();
1613 writer.close().unwrap();
1614
1615 let size = fs::metadata(&path).unwrap().len();
1616 (path, size)
1617 }
1618 }
1619
1620 #[tokio::test]
1621 async fn test_table_scan_columns() {
1622 let table = TableTestFixture::new().table;
1623
1624 let table_scan = table.scan().select(["x", "y"]).build().unwrap();
1625 assert_eq!(
1626 Some(vec!["x".to_string(), "y".to_string()]),
1627 table_scan.column_names
1628 );
1629
1630 let table_scan = table
1631 .scan()
1632 .select(["x", "y"])
1633 .select(["z"])
1634 .build()
1635 .unwrap();
1636 assert_eq!(Some(vec!["z".to_string()]), table_scan.column_names);
1637 }
1638
1639 #[tokio::test]
1640 async fn test_select_all() {
1641 let table = TableTestFixture::new().table;
1642
1643 let table_scan = table.scan().select_all().build().unwrap();
1644 assert!(table_scan.column_names.is_none());
1645 }
1646
1647 #[test]
1648 fn test_select_no_exist_column() {
1649 let table = TableTestFixture::new().table;
1650
1651 let table_scan = table.scan().select(["x", "y", "z", "a", "b"]).build();
1652 assert!(table_scan.is_err());
1653 }
1654
1655 #[tokio::test]
1656 async fn test_table_scan_default_snapshot_id() {
1657 let table = TableTestFixture::new().table;
1658
1659 let table_scan = table.scan().build().unwrap();
1660 assert_eq!(
1661 table.metadata().current_snapshot().unwrap().snapshot_id(),
1662 table_scan.snapshot().unwrap().snapshot_id()
1663 );
1664 }
1665
1666 #[test]
1667 fn test_table_scan_non_exist_snapshot_id() {
1668 let table = TableTestFixture::new().table;
1669
1670 let table_scan = table.scan().snapshot_id(1024).build();
1671 assert!(table_scan.is_err());
1672 }
1673
1674 #[tokio::test]
1675 async fn test_table_scan_with_snapshot_id() {
1676 let table = TableTestFixture::new().table;
1677
1678 let table_scan = table
1679 .scan()
1680 .snapshot_id(3051729675574597004)
1681 .with_row_selection_enabled(true)
1682 .build()
1683 .unwrap();
1684 assert_eq!(
1685 table_scan.snapshot().unwrap().snapshot_id(),
1686 3051729675574597004
1687 );
1688 }
1689
1690 fn table_with_property(key: &str, value: &str) -> Table {
1691 let fixture = TableTestFixture::new();
1692 let mut metadata = fixture.table.metadata().clone();
1693 metadata
1694 .properties
1695 .insert(key.to_string(), value.to_string());
1696 Table::builder()
1697 .metadata(metadata)
1698 .identifier(fixture.table.identifier().clone())
1699 .file_io(fixture.table.file_io().clone())
1700 .metadata_location(fixture.table.metadata_location().unwrap().to_string())
1701 .runtime(test_runtime())
1702 .build()
1703 .unwrap()
1704 }
1705
1706 #[test]
1707 fn test_table_scan_without_name_mapping_property() {
1708 let table = TableTestFixture::new().table;
1709
1710 let table_scan = table.scan().build().unwrap();
1711 assert!(
1712 table_scan
1713 .plan_context
1714 .as_ref()
1715 .unwrap()
1716 .name_mapping
1717 .is_none()
1718 );
1719 }
1720
1721 #[test]
1722 fn test_table_scan_with_name_mapping_property() {
1723 let mapping_json = r#"[{"field-id":1,"names":["id","record_id"]}]"#;
1724 let table = table_with_property(DEFAULT_SCHEMA_NAME_MAPPING, mapping_json);
1725
1726 let table_scan = table.scan().build().unwrap();
1727 let mapping = table_scan
1728 .plan_context
1729 .as_ref()
1730 .unwrap()
1731 .name_mapping
1732 .as_ref()
1733 .expect("name_mapping should be parsed from the table property");
1734 let fields = mapping.fields();
1735 assert_eq!(fields.len(), 1);
1736 assert_eq!(fields[0].field_id(), Some(1));
1737 assert_eq!(fields[0].names(), &[
1738 "id".to_string(),
1739 "record_id".to_string()
1740 ]);
1741 }
1742
1743 #[test]
1744 fn test_table_scan_with_malformed_name_mapping_property() {
1745 let table = table_with_property(DEFAULT_SCHEMA_NAME_MAPPING, "{ not valid json");
1746
1747 let err = table
1748 .scan()
1749 .build()
1750 .expect_err("malformed name mapping should fail to parse");
1751 assert_eq!(err.kind(), ErrorKind::DataInvalid);
1752 }
1753
1754 #[tokio::test]
1755 async fn test_plan_files_carries_name_mapping_into_file_scan_task() {
1756 let mut fixture = TableTestFixture::new();
1757 fixture.setup_manifest_files().await;
1758
1759 let mapping_json = r#"[{"field-id":1,"names":["id","record_id"]}]"#;
1760 let mut metadata = fixture.table.metadata().clone();
1761 metadata.properties.insert(
1762 DEFAULT_SCHEMA_NAME_MAPPING.to_string(),
1763 mapping_json.to_string(),
1764 );
1765 let table = Table::builder()
1766 .metadata(metadata)
1767 .identifier(fixture.table.identifier().clone())
1768 .file_io(fixture.table.file_io().clone())
1769 .metadata_location(fixture.table.metadata_location().unwrap().to_string())
1770 .runtime(test_runtime())
1771 .build()
1772 .unwrap();
1773
1774 let tasks: Vec<_> = table
1775 .scan()
1776 .build()
1777 .unwrap()
1778 .plan_files()
1779 .await
1780 .unwrap()
1781 .try_collect()
1782 .await
1783 .unwrap();
1784
1785 assert!(!tasks.is_empty(), "expected at least one FileScanTask");
1786 for task in &tasks {
1787 let mapping = task
1788 .name_mapping
1789 .as_ref()
1790 .expect("name_mapping should reach the FileScanTask");
1791 assert_eq!(mapping.fields().len(), 1);
1792 assert_eq!(mapping.fields()[0].field_id(), Some(1));
1793 }
1794 }
1795
1796 #[tokio::test]
1797 async fn test_plan_files_on_table_without_any_snapshots() {
1798 let table = TableTestFixture::new_empty().table;
1799 let batch_stream = table.scan().build().unwrap().to_arrow().await.unwrap();
1800 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
1801 assert!(batches.is_empty());
1802 }
1803
1804 #[tokio::test]
1805 async fn test_plan_files_no_deletions() {
1806 let mut fixture = TableTestFixture::new();
1807 fixture.setup_manifest_files().await;
1808
1809 let table_scan = fixture
1811 .table
1812 .scan()
1813 .with_row_selection_enabled(true)
1814 .build()
1815 .unwrap();
1816
1817 let mut tasks = table_scan
1818 .plan_files()
1819 .await
1820 .unwrap()
1821 .try_fold(vec![], |mut acc, task| async move {
1822 acc.push(task);
1823 Ok(acc)
1824 })
1825 .await
1826 .unwrap();
1827
1828 assert_eq!(tasks.len(), 2);
1829
1830 tasks.sort_by_key(|t| t.data_file_path.to_string());
1831
1832 assert_eq!(
1834 tasks[0].data_file_path,
1835 format!("{}/1.parquet", &fixture.table_location)
1836 );
1837
1838 assert_eq!(
1840 tasks[1].data_file_path,
1841 format!("{}/3.parquet", &fixture.table_location)
1842 );
1843 }
1844
1845 #[tokio::test]
1846 async fn test_open_parquet_no_deletions() {
1847 let mut fixture = TableTestFixture::new();
1848 fixture.setup_manifest_files().await;
1849
1850 let table_scan = fixture
1852 .table
1853 .scan()
1854 .with_row_selection_enabled(true)
1855 .build()
1856 .unwrap();
1857
1858 let batch_stream = table_scan.to_arrow().await.unwrap();
1859
1860 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
1861
1862 let col = batches[0].column_by_name("x").unwrap();
1863
1864 let int64_arr = col.as_any().downcast_ref::<Int64Array>().unwrap();
1865 assert_eq!(int64_arr.value(0), 1);
1866 }
1867
1868 #[tokio::test]
1869 async fn test_open_parquet_no_deletions_by_separate_reader() {
1870 let mut fixture = TableTestFixture::new();
1871 fixture.setup_manifest_files().await;
1872
1873 let table_scan = fixture
1875 .table
1876 .scan()
1877 .with_row_selection_enabled(true)
1878 .build()
1879 .unwrap();
1880
1881 let mut plan_task: Vec<_> = table_scan
1882 .plan_files()
1883 .await
1884 .unwrap()
1885 .try_collect()
1886 .await
1887 .unwrap();
1888 assert_eq!(plan_task.len(), 2);
1889
1890 let reader = ArrowReaderBuilder::new(
1891 fixture.table.file_io().clone(),
1892 fixture.table.runtime().clone(),
1893 )
1894 .build();
1895 let batch_stream = reader
1896 .clone()
1897 .read(Box::pin(stream::iter(vec![Ok(plan_task.remove(0))])))
1898 .unwrap()
1899 .stream();
1900 let batch_1: Vec<_> = batch_stream.try_collect().await.unwrap();
1901
1902 let reader = ArrowReaderBuilder::new(
1903 fixture.table.file_io().clone(),
1904 fixture.table.runtime().clone(),
1905 )
1906 .build();
1907 let batch_stream = reader
1908 .read(Box::pin(stream::iter(vec![Ok(plan_task.remove(0))])))
1909 .unwrap()
1910 .stream();
1911 let batch_2: Vec<_> = batch_stream.try_collect().await.unwrap();
1912
1913 assert_eq!(batch_1, batch_2);
1914 }
1915
1916 #[tokio::test]
1917 async fn test_open_parquet_with_projection() {
1918 let mut fixture = TableTestFixture::new();
1919 fixture.setup_manifest_files().await;
1920
1921 let table_scan = fixture
1923 .table
1924 .scan()
1925 .select(["x", "z"])
1926 .with_row_selection_enabled(true)
1927 .build()
1928 .unwrap();
1929
1930 let batch_stream = table_scan.to_arrow().await.unwrap();
1931
1932 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
1933
1934 assert_eq!(batches[0].num_columns(), 2);
1935
1936 let col1 = batches[0].column_by_name("x").unwrap();
1937 let int64_arr = col1.as_any().downcast_ref::<Int64Array>().unwrap();
1938 assert_eq!(int64_arr.value(0), 1);
1939
1940 let col2 = batches[0].column_by_name("z").unwrap();
1941 let int64_arr = col2.as_any().downcast_ref::<Int64Array>().unwrap();
1942 assert_eq!(int64_arr.value(0), 3);
1943
1944 let table_scan = fixture.table.scan().select_empty().build().unwrap();
1946 let batch_stream = table_scan.to_arrow().await.unwrap();
1947 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
1948
1949 assert_eq!(batches[0].num_columns(), 0);
1950 assert_eq!(batches[0].num_rows(), 1024);
1951 }
1952
1953 #[tokio::test]
1954 async fn test_filter_on_arrow_lt() {
1955 let mut fixture = TableTestFixture::new();
1956 fixture.setup_manifest_files().await;
1957
1958 let mut builder = fixture.table.scan();
1960 let predicate = Reference::new("y").less_than(Datum::long(3));
1961 builder = builder
1962 .with_filter(predicate)
1963 .with_row_selection_enabled(true);
1964 let table_scan = builder.build().unwrap();
1965
1966 let batch_stream = table_scan.to_arrow().await.unwrap();
1967
1968 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
1969
1970 assert_eq!(batches[0].num_rows(), 512);
1971
1972 let col = batches[0].column_by_name("x").unwrap();
1973 let int64_arr = col.as_any().downcast_ref::<Int64Array>().unwrap();
1974 assert_eq!(int64_arr.value(0), 1);
1975
1976 let col = batches[0].column_by_name("y").unwrap();
1977 let int64_arr = col.as_any().downcast_ref::<Int64Array>().unwrap();
1978 assert_eq!(int64_arr.value(0), 2);
1979 }
1980
1981 #[tokio::test]
1982 async fn test_filter_on_arrow_gt_eq() {
1983 let mut fixture = TableTestFixture::new();
1984 fixture.setup_manifest_files().await;
1985
1986 let mut builder = fixture.table.scan();
1988 let predicate = Reference::new("y").greater_than_or_equal_to(Datum::long(5));
1989 builder = builder
1990 .with_filter(predicate)
1991 .with_row_selection_enabled(true);
1992 let table_scan = builder.build().unwrap();
1993
1994 let batch_stream = table_scan.to_arrow().await.unwrap();
1995
1996 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
1997
1998 assert_eq!(batches[0].num_rows(), 12);
1999
2000 let col = batches[0].column_by_name("x").unwrap();
2001 let int64_arr = col.as_any().downcast_ref::<Int64Array>().unwrap();
2002 assert_eq!(int64_arr.value(0), 1);
2003
2004 let col = batches[0].column_by_name("y").unwrap();
2005 let int64_arr = col.as_any().downcast_ref::<Int64Array>().unwrap();
2006 assert_eq!(int64_arr.value(0), 5);
2007 }
2008
2009 #[tokio::test]
2010 async fn test_filter_double_eq() {
2011 let mut fixture = TableTestFixture::new();
2012 fixture.setup_manifest_files().await;
2013
2014 let mut builder = fixture.table.scan();
2016 let predicate = Reference::new("dbl").equal_to(Datum::double(150.0f64));
2017 builder = builder
2018 .with_filter(predicate)
2019 .with_row_selection_enabled(true);
2020 let table_scan = builder.build().unwrap();
2021
2022 let batch_stream = table_scan.to_arrow().await.unwrap();
2023
2024 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2025
2026 assert_eq!(batches.len(), 2);
2027 assert_eq!(batches[0].num_rows(), 12);
2028
2029 let col = batches[0].column_by_name("dbl").unwrap();
2030 let f64_arr = col.as_any().downcast_ref::<Float64Array>().unwrap();
2031 assert_eq!(f64_arr.value(1), 150.0f64);
2032 }
2033
2034 #[tokio::test]
2035 async fn test_filter_int_eq() {
2036 let mut fixture = TableTestFixture::new();
2037 fixture.setup_manifest_files().await;
2038
2039 let mut builder = fixture.table.scan();
2041 let predicate = Reference::new("i32").equal_to(Datum::int(150i32));
2042 builder = builder
2043 .with_filter(predicate)
2044 .with_row_selection_enabled(true);
2045 let table_scan = builder.build().unwrap();
2046
2047 let batch_stream = table_scan.to_arrow().await.unwrap();
2048
2049 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2050
2051 assert_eq!(batches.len(), 2);
2052 assert_eq!(batches[0].num_rows(), 12);
2053
2054 let col = batches[0].column_by_name("i32").unwrap();
2055 let i32_arr = col.as_any().downcast_ref::<Int32Array>().unwrap();
2056 assert_eq!(i32_arr.value(1), 150i32);
2057 }
2058
2059 #[tokio::test]
2060 async fn test_filter_long_eq() {
2061 let mut fixture = TableTestFixture::new();
2062 fixture.setup_manifest_files().await;
2063
2064 let mut builder = fixture.table.scan();
2066 let predicate = Reference::new("i64").equal_to(Datum::long(150i64));
2067 builder = builder
2068 .with_filter(predicate)
2069 .with_row_selection_enabled(true);
2070 let table_scan = builder.build().unwrap();
2071
2072 let batch_stream = table_scan.to_arrow().await.unwrap();
2073
2074 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2075
2076 assert_eq!(batches.len(), 2);
2077 assert_eq!(batches[0].num_rows(), 12);
2078
2079 let col = batches[0].column_by_name("i64").unwrap();
2080 let i64_arr = col.as_any().downcast_ref::<Int64Array>().unwrap();
2081 assert_eq!(i64_arr.value(1), 150i64);
2082 }
2083
2084 #[tokio::test]
2085 async fn test_filter_bool_eq() {
2086 let mut fixture = TableTestFixture::new();
2087 fixture.setup_manifest_files().await;
2088
2089 let mut builder = fixture.table.scan();
2091 let predicate = Reference::new("bool").equal_to(Datum::bool(true));
2092 builder = builder
2093 .with_filter(predicate)
2094 .with_row_selection_enabled(true);
2095 let table_scan = builder.build().unwrap();
2096
2097 let batch_stream = table_scan.to_arrow().await.unwrap();
2098
2099 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2100
2101 assert_eq!(batches.len(), 2);
2102 assert_eq!(batches[0].num_rows(), 512);
2103
2104 let col = batches[0].column_by_name("bool").unwrap();
2105 let bool_arr = col.as_any().downcast_ref::<BooleanArray>().unwrap();
2106 assert!(bool_arr.value(1));
2107 }
2108
2109 #[tokio::test]
2110 async fn test_filter_on_arrow_is_null() {
2111 let mut fixture = TableTestFixture::new();
2112 fixture.setup_manifest_files().await;
2113
2114 let mut builder = fixture.table.scan();
2116 let predicate = Reference::new("y").is_null();
2117 builder = builder
2118 .with_filter(predicate)
2119 .with_row_selection_enabled(true);
2120 let table_scan = builder.build().unwrap();
2121
2122 let batch_stream = table_scan.to_arrow().await.unwrap();
2123
2124 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2125 assert_eq!(batches.len(), 0);
2126 }
2127
2128 #[tokio::test]
2129 async fn test_filter_on_arrow_is_not_null() {
2130 let mut fixture = TableTestFixture::new();
2131 fixture.setup_manifest_files().await;
2132
2133 let mut builder = fixture.table.scan();
2135 let predicate = Reference::new("y").is_not_null();
2136 builder = builder
2137 .with_filter(predicate)
2138 .with_row_selection_enabled(true);
2139 let table_scan = builder.build().unwrap();
2140
2141 let batch_stream = table_scan.to_arrow().await.unwrap();
2142
2143 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2144 assert_eq!(batches[0].num_rows(), 1024);
2145 }
2146
2147 #[tokio::test]
2148 async fn test_filter_on_arrow_lt_and_gt() {
2149 let mut fixture = TableTestFixture::new();
2150 fixture.setup_manifest_files().await;
2151
2152 let mut builder = fixture.table.scan();
2154 let predicate = Reference::new("y")
2155 .less_than(Datum::long(5))
2156 .and(Reference::new("z").greater_than_or_equal_to(Datum::long(4)));
2157 builder = builder
2158 .with_filter(predicate)
2159 .with_row_selection_enabled(true);
2160 let table_scan = builder.build().unwrap();
2161
2162 let batch_stream = table_scan.to_arrow().await.unwrap();
2163
2164 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2165 assert_eq!(batches[0].num_rows(), 500);
2166
2167 let col = batches[0].column_by_name("x").unwrap();
2168 let expected_x = Arc::new(Int64Array::from_iter_values(vec![1; 500])) as ArrayRef;
2169 assert_eq!(col, &expected_x);
2170
2171 let col = batches[0].column_by_name("y").unwrap();
2172 let mut values = vec![];
2173 values.append(vec![3; 200].as_mut());
2174 values.append(vec![4; 300].as_mut());
2175 let expected_y = Arc::new(Int64Array::from_iter_values(values)) as ArrayRef;
2176 assert_eq!(col, &expected_y);
2177
2178 let col = batches[0].column_by_name("z").unwrap();
2179 let expected_z = Arc::new(Int64Array::from_iter_values(vec![4; 500])) as ArrayRef;
2180 assert_eq!(col, &expected_z);
2181 }
2182
2183 #[tokio::test]
2184 async fn test_filter_on_arrow_lt_or_gt() {
2185 let mut fixture = TableTestFixture::new();
2186 fixture.setup_manifest_files().await;
2187
2188 let mut builder = fixture.table.scan();
2190 let predicate = Reference::new("y")
2191 .less_than(Datum::long(5))
2192 .or(Reference::new("z").greater_than_or_equal_to(Datum::long(4)));
2193 builder = builder
2194 .with_filter(predicate)
2195 .with_row_selection_enabled(true);
2196 let table_scan = builder.build().unwrap();
2197
2198 let batch_stream = table_scan.to_arrow().await.unwrap();
2199
2200 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2201 assert_eq!(batches[0].num_rows(), 1024);
2202
2203 let col = batches[0].column_by_name("x").unwrap();
2204 let expected_x = Arc::new(Int64Array::from_iter_values(vec![1; 1024])) as ArrayRef;
2205 assert_eq!(col, &expected_x);
2206
2207 let col = batches[0].column_by_name("y").unwrap();
2208 let mut values = vec![2; 512];
2209 values.append(vec![3; 200].as_mut());
2210 values.append(vec![4; 300].as_mut());
2211 values.append(vec![5; 12].as_mut());
2212 let expected_y = Arc::new(Int64Array::from_iter_values(values)) as ArrayRef;
2213 assert_eq!(col, &expected_y);
2214
2215 let col = batches[0].column_by_name("z").unwrap();
2216 let mut values = vec![3; 512];
2217 values.append(vec![4; 512].as_mut());
2218 let expected_z = Arc::new(Int64Array::from_iter_values(values)) as ArrayRef;
2219 assert_eq!(col, &expected_z);
2220 }
2221
2222 #[tokio::test]
2223 async fn test_filter_on_arrow_startswith() {
2224 let mut fixture = TableTestFixture::new();
2225 fixture.setup_manifest_files().await;
2226
2227 let mut builder = fixture.table.scan();
2229 let predicate = Reference::new("a").starts_with(Datum::string("Ice"));
2230 builder = builder
2231 .with_filter(predicate)
2232 .with_row_selection_enabled(true);
2233 let table_scan = builder.build().unwrap();
2234
2235 let batch_stream = table_scan.to_arrow().await.unwrap();
2236
2237 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2238
2239 assert_eq!(batches[0].num_rows(), 512);
2240
2241 let col = batches[0].column_by_name("a").unwrap();
2242 let string_arr = col.as_any().downcast_ref::<StringArray>().unwrap();
2243 assert_eq!(string_arr.value(0), "Iceberg");
2244 }
2245
2246 #[tokio::test]
2247 async fn test_filter_on_arrow_not_startswith() {
2248 let mut fixture = TableTestFixture::new();
2249 fixture.setup_manifest_files().await;
2250
2251 let mut builder = fixture.table.scan();
2253 let predicate = Reference::new("a").not_starts_with(Datum::string("Ice"));
2254 builder = builder
2255 .with_filter(predicate)
2256 .with_row_selection_enabled(true);
2257 let table_scan = builder.build().unwrap();
2258
2259 let batch_stream = table_scan.to_arrow().await.unwrap();
2260
2261 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2262
2263 assert_eq!(batches[0].num_rows(), 512);
2264
2265 let col = batches[0].column_by_name("a").unwrap();
2266 let string_arr = col.as_any().downcast_ref::<StringArray>().unwrap();
2267 assert_eq!(string_arr.value(0), "Apache");
2268 }
2269
2270 #[tokio::test]
2271 async fn test_filter_on_arrow_in() {
2272 let mut fixture = TableTestFixture::new();
2273 fixture.setup_manifest_files().await;
2274
2275 let mut builder = fixture.table.scan();
2277 let predicate =
2278 Reference::new("a").is_in([Datum::string("Sioux"), Datum::string("Iceberg")]);
2279 builder = builder
2280 .with_filter(predicate)
2281 .with_row_selection_enabled(true);
2282 let table_scan = builder.build().unwrap();
2283
2284 let batch_stream = table_scan.to_arrow().await.unwrap();
2285
2286 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2287
2288 assert_eq!(batches[0].num_rows(), 512);
2289
2290 let col = batches[0].column_by_name("a").unwrap();
2291 let string_arr = col.as_any().downcast_ref::<StringArray>().unwrap();
2292 assert_eq!(string_arr.value(0), "Iceberg");
2293 }
2294
2295 #[tokio::test]
2296 async fn test_filter_on_arrow_not_in() {
2297 let mut fixture = TableTestFixture::new();
2298 fixture.setup_manifest_files().await;
2299
2300 let mut builder = fixture.table.scan();
2302 let predicate =
2303 Reference::new("a").is_not_in([Datum::string("Sioux"), Datum::string("Iceberg")]);
2304 builder = builder
2305 .with_filter(predicate)
2306 .with_row_selection_enabled(true);
2307 let table_scan = builder.build().unwrap();
2308
2309 let batch_stream = table_scan.to_arrow().await.unwrap();
2310
2311 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2312
2313 assert_eq!(batches[0].num_rows(), 512);
2314
2315 let col = batches[0].column_by_name("a").unwrap();
2316 let string_arr = col.as_any().downcast_ref::<StringArray>().unwrap();
2317 assert_eq!(string_arr.value(0), "Apache");
2318 }
2319
2320 #[test]
2321 fn test_file_scan_task_serialize_deserialize() {
2322 let test_fn = |task: FileScanTask| {
2323 let serialized = serde_json::to_string(&task).unwrap();
2324 let deserialized: FileScanTask = serde_json::from_str(&serialized).unwrap();
2325
2326 assert_eq!(task.data_file_path, deserialized.data_file_path);
2327 assert_eq!(task.start, deserialized.start);
2328 assert_eq!(task.length, deserialized.length);
2329 assert_eq!(task.project_field_ids, deserialized.project_field_ids);
2330 assert_eq!(task.predicate, deserialized.predicate);
2331 assert_eq!(task.schema, deserialized.schema);
2332 };
2333
2334 let schema = Arc::new(
2336 Schema::builder()
2337 .with_fields(vec![Arc::new(NestedField::required(
2338 1,
2339 "x",
2340 Type::Primitive(PrimitiveType::Binary),
2341 ))])
2342 .build()
2343 .unwrap(),
2344 );
2345 let task = FileScanTask::builder()
2346 .with_data_file_path("data_file_path".to_string())
2347 .with_file_size_in_bytes(0)
2348 .with_start(0)
2349 .with_length(100)
2350 .with_project_field_ids(vec![1, 2, 3])
2351 .with_schema(schema.clone())
2352 .with_record_count(Some(100))
2353 .with_data_file_format(DataFileFormat::Parquet)
2354 .with_case_sensitive(false)
2355 .build();
2356 test_fn(task);
2357
2358 let task = FileScanTask::builder()
2360 .with_data_file_path("data_file_path".to_string())
2361 .with_file_size_in_bytes(0)
2362 .with_start(0)
2363 .with_length(100)
2364 .with_project_field_ids(vec![1, 2, 3])
2365 .with_predicate(Some(BoundPredicate::AlwaysTrue))
2366 .with_schema(schema)
2367 .with_data_file_format(DataFileFormat::Avro)
2368 .with_case_sensitive(false)
2369 .build();
2370 test_fn(task);
2371 }
2372
2373 #[tokio::test]
2374 async fn test_select_with_file_column() {
2375 let mut fixture = TableTestFixture::new();
2376 fixture.setup_manifest_files().await;
2377
2378 let table_scan = fixture
2380 .table
2381 .scan()
2382 .select(["x", RESERVED_COL_NAME_FILE])
2383 .with_row_selection_enabled(true)
2384 .build()
2385 .unwrap();
2386
2387 let batch_stream = table_scan.to_arrow().await.unwrap();
2388 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2389
2390 assert_eq!(batches[0].num_columns(), 2);
2392
2393 let x_col = batches[0].column_by_name("x").unwrap();
2395 let x_arr = x_col.as_primitive::<arrow_array::types::Int64Type>();
2396 assert_eq!(x_arr.value(0), 1);
2397
2398 let file_col = batches[0].column_by_name(RESERVED_COL_NAME_FILE);
2400 assert!(
2401 file_col.is_some(),
2402 "_file column should be present in the batch"
2403 );
2404
2405 let file_col = file_col.unwrap();
2407 assert!(
2408 matches!(
2409 file_col.data_type(),
2410 arrow_schema::DataType::RunEndEncoded(_, _)
2411 ),
2412 "_file column should use RunEndEncoded type"
2413 );
2414
2415 let run_array = file_col
2417 .as_any()
2418 .downcast_ref::<RunArray<Int32Type>>()
2419 .expect("_file column should be a RunArray");
2420
2421 let values = run_array.values();
2422 let string_values = values.as_string::<i32>();
2423 assert_eq!(string_values.len(), 1, "Should have a single file path");
2424
2425 let file_path = string_values.value(0);
2426 assert!(
2427 file_path.ends_with(".parquet"),
2428 "File path should end with .parquet, got: {file_path}"
2429 );
2430 }
2431
2432 #[tokio::test]
2433 async fn test_select_file_column_position() {
2434 let mut fixture = TableTestFixture::new();
2435 fixture.setup_manifest_files().await;
2436
2437 let table_scan = fixture
2439 .table
2440 .scan()
2441 .select(["x", RESERVED_COL_NAME_FILE, "z"])
2442 .with_row_selection_enabled(true)
2443 .build()
2444 .unwrap();
2445
2446 let batch_stream = table_scan.to_arrow().await.unwrap();
2447 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2448
2449 assert_eq!(batches[0].num_columns(), 3);
2450
2451 let schema = batches[0].schema();
2453 assert_eq!(schema.field(0).name(), "x");
2454 assert_eq!(schema.field(1).name(), RESERVED_COL_NAME_FILE);
2455 assert_eq!(schema.field(2).name(), "z");
2456
2457 assert!(batches[0].column_by_name("x").is_some());
2459 assert!(batches[0].column_by_name(RESERVED_COL_NAME_FILE).is_some());
2460 assert!(batches[0].column_by_name("z").is_some());
2461 }
2462
2463 #[tokio::test]
2464 async fn test_select_file_column_only() {
2465 let mut fixture = TableTestFixture::new();
2466 fixture.setup_manifest_files().await;
2467
2468 let table_scan = fixture
2470 .table
2471 .scan()
2472 .select([RESERVED_COL_NAME_FILE])
2473 .with_row_selection_enabled(true)
2474 .build()
2475 .unwrap();
2476
2477 let batch_stream = table_scan.to_arrow().await.unwrap();
2478 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2479
2480 assert_eq!(batches[0].num_columns(), 1);
2482
2483 let schema = batches[0].schema();
2485 assert_eq!(schema.field(0).name(), RESERVED_COL_NAME_FILE);
2486
2487 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
2491 assert_eq!(total_rows, 2048);
2492 }
2493
2494 #[tokio::test]
2495 async fn test_file_column_with_multiple_files() {
2496 use std::collections::HashSet;
2497
2498 let mut fixture = TableTestFixture::new();
2499 fixture.setup_manifest_files().await;
2500
2501 let table_scan = fixture
2503 .table
2504 .scan()
2505 .select(["x", RESERVED_COL_NAME_FILE])
2506 .with_row_selection_enabled(true)
2507 .build()
2508 .unwrap();
2509
2510 let batch_stream = table_scan.to_arrow().await.unwrap();
2511 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2512
2513 let mut file_paths = HashSet::new();
2515 for batch in &batches {
2516 let file_col = batch.column_by_name(RESERVED_COL_NAME_FILE).unwrap();
2517 let run_array = file_col
2518 .as_any()
2519 .downcast_ref::<RunArray<Int32Type>>()
2520 .expect("_file column should be a RunArray");
2521
2522 let values = run_array.values();
2523 let string_values = values.as_string::<i32>();
2524 for i in 0..string_values.len() {
2525 file_paths.insert(string_values.value(i).to_string());
2526 }
2527 }
2528
2529 assert!(!file_paths.is_empty(), "Should have at least one file path");
2531
2532 for path in &file_paths {
2534 assert!(
2535 path.ends_with(".parquet"),
2536 "All file paths should end with .parquet, got: {path}"
2537 );
2538 }
2539 }
2540
2541 #[tokio::test]
2542 async fn test_file_column_at_start() {
2543 let mut fixture = TableTestFixture::new();
2544 fixture.setup_manifest_files().await;
2545
2546 let table_scan = fixture
2548 .table
2549 .scan()
2550 .select([RESERVED_COL_NAME_FILE, "x", "y"])
2551 .with_row_selection_enabled(true)
2552 .build()
2553 .unwrap();
2554
2555 let batch_stream = table_scan.to_arrow().await.unwrap();
2556 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2557
2558 assert_eq!(batches[0].num_columns(), 3);
2559
2560 let schema = batches[0].schema();
2562 assert_eq!(schema.field(0).name(), RESERVED_COL_NAME_FILE);
2563 assert_eq!(schema.field(1).name(), "x");
2564 assert_eq!(schema.field(2).name(), "y");
2565 }
2566
2567 #[tokio::test]
2568 async fn test_file_column_at_end() {
2569 let mut fixture = TableTestFixture::new();
2570 fixture.setup_manifest_files().await;
2571
2572 let table_scan = fixture
2574 .table
2575 .scan()
2576 .select(["x", "y", RESERVED_COL_NAME_FILE])
2577 .with_row_selection_enabled(true)
2578 .build()
2579 .unwrap();
2580
2581 let batch_stream = table_scan.to_arrow().await.unwrap();
2582 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2583
2584 assert_eq!(batches[0].num_columns(), 3);
2585
2586 let schema = batches[0].schema();
2588 assert_eq!(schema.field(0).name(), "x");
2589 assert_eq!(schema.field(1).name(), "y");
2590 assert_eq!(schema.field(2).name(), RESERVED_COL_NAME_FILE);
2591 }
2592
2593 #[tokio::test]
2594 async fn test_select_with_repeated_column_names() {
2595 let mut fixture = TableTestFixture::new();
2596 fixture.setup_manifest_files().await;
2597
2598 let table_scan = fixture
2601 .table
2602 .scan()
2603 .select([
2604 "x",
2605 RESERVED_COL_NAME_FILE,
2606 "x", "y",
2608 RESERVED_COL_NAME_FILE, "y", ])
2611 .with_row_selection_enabled(true)
2612 .build()
2613 .unwrap();
2614
2615 let batch_stream = table_scan.to_arrow().await.unwrap();
2616 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2617
2618 assert_eq!(
2620 batches[0].num_columns(),
2621 6,
2622 "Should have exactly 6 columns with duplicates"
2623 );
2624
2625 let schema = batches[0].schema();
2626
2627 assert_eq!(schema.field(0).name(), "x", "Column 0 should be x");
2629 assert_eq!(
2630 schema.field(1).name(),
2631 RESERVED_COL_NAME_FILE,
2632 "Column 1 should be _file"
2633 );
2634 assert_eq!(
2635 schema.field(2).name(),
2636 "x",
2637 "Column 2 should be x (duplicate)"
2638 );
2639 assert_eq!(schema.field(3).name(), "y", "Column 3 should be y");
2640 assert_eq!(
2641 schema.field(4).name(),
2642 RESERVED_COL_NAME_FILE,
2643 "Column 4 should be _file (duplicate)"
2644 );
2645 assert_eq!(
2646 schema.field(5).name(),
2647 "y",
2648 "Column 5 should be y (duplicate)"
2649 );
2650
2651 assert!(
2653 matches!(schema.field(0).data_type(), arrow_schema::DataType::Int64),
2654 "Column x should be Int64"
2655 );
2656 assert!(
2657 matches!(schema.field(2).data_type(), arrow_schema::DataType::Int64),
2658 "Column x (duplicate) should be Int64"
2659 );
2660 assert!(
2661 matches!(schema.field(3).data_type(), arrow_schema::DataType::Int64),
2662 "Column y should be Int64"
2663 );
2664 assert!(
2665 matches!(schema.field(5).data_type(), arrow_schema::DataType::Int64),
2666 "Column y (duplicate) should be Int64"
2667 );
2668 assert!(
2669 matches!(
2670 schema.field(1).data_type(),
2671 arrow_schema::DataType::RunEndEncoded(_, _)
2672 ),
2673 "_file column should use RunEndEncoded type"
2674 );
2675 assert!(
2676 matches!(
2677 schema.field(4).data_type(),
2678 arrow_schema::DataType::RunEndEncoded(_, _)
2679 ),
2680 "_file column (duplicate) should use RunEndEncoded type"
2681 );
2682 }
2683
2684 #[tokio::test]
2685 async fn test_scan_deadlock() {
2686 let mut fixture = TableTestFixture::new();
2687 fixture.setup_deadlock_manifests().await;
2688
2689 let table_scan = fixture
2696 .table
2697 .scan()
2698 .with_concurrency_limit(1)
2699 .build()
2700 .unwrap();
2701
2702 let result = tokio::time::timeout(std::time::Duration::from_secs(5), async {
2705 table_scan
2706 .plan_files()
2707 .await
2708 .unwrap()
2709 .try_collect::<Vec<_>>()
2710 .await
2711 })
2712 .await;
2713
2714 assert!(result.is_ok(), "Scan timed out - deadlock detected");
2716 }
2717
2718 #[tokio::test]
2719 async fn test_select_with_spec_id_column() {
2720 let mut fixture = TableTestFixture::new();
2721 fixture.setup_manifest_files().await;
2722
2723 let table_scan = fixture
2725 .table
2726 .scan()
2727 .select(["x", RESERVED_COL_NAME_SPEC_ID, "z"])
2728 .with_row_selection_enabled(true)
2729 .build()
2730 .unwrap();
2731
2732 let batch_stream = table_scan.to_arrow().await.unwrap();
2733 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2734
2735 assert_eq!(batches[0].num_columns(), 3);
2737
2738 let col1 = batches[0].column_by_name("x").unwrap();
2740 let int64_arr = col1.as_any().downcast_ref::<Int64Array>().unwrap();
2741 assert_eq!(int64_arr.value(0), 1);
2742
2743 let spec_id_col = batches[0].column_by_name(RESERVED_COL_NAME_SPEC_ID);
2745 assert!(
2746 spec_id_col.is_some(),
2747 "_spec_id column should be present in the batch"
2748 );
2749
2750 let spec_id_col = spec_id_col.unwrap();
2752 assert!(
2753 matches!(
2754 spec_id_col.data_type(),
2755 arrow_schema::DataType::RunEndEncoded(_, _)
2756 ),
2757 "_spec_id column should use RunEndEncoded type"
2758 );
2759
2760 let run_array = spec_id_col
2762 .as_any()
2763 .downcast_ref::<RunArray<Int32Type>>()
2764 .expect("_spec_id column should be a RunArray");
2765
2766 let values = run_array.values();
2767 let int_values = values.as_primitive::<Int32Type>();
2768 assert_eq!(int_values.len(), 1, "Should have a single _spec_id");
2769
2770 let spec_id = int_values.value(0);
2771 assert_eq!(spec_id, 0, "_spec_id should be 0, got: {spec_id}");
2772
2773 assert!(batches[0].column_by_name("z").is_some());
2775 }
2776
2777 #[tokio::test]
2778 async fn test_select_with_spec_id_column_from_unpartitioned_table() {
2779 let mut fixture = TableTestFixture::new_unpartitioned();
2780 fixture.setup_unpartitioned_manifest_files().await;
2781
2782 let table_scan = fixture
2784 .table
2785 .scan()
2786 .select(["x", RESERVED_COL_NAME_SPEC_ID])
2787 .with_row_selection_enabled(true)
2788 .build()
2789 .unwrap();
2790
2791 let batch_stream = table_scan.to_arrow().await.unwrap();
2792 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2793
2794 assert_eq!(batches[0].num_columns(), 2);
2796
2797 let spec_id_col = batches[0].column_by_name(RESERVED_COL_NAME_SPEC_ID);
2799 assert!(
2800 spec_id_col.is_some(),
2801 "_spec_id column should be present in the batch"
2802 );
2803
2804 let spec_id_col = spec_id_col.unwrap();
2806 assert!(
2807 matches!(
2808 spec_id_col.data_type(),
2809 arrow_schema::DataType::RunEndEncoded(_, _)
2810 ),
2811 "_spec_id column should use RunEndEncoded type"
2812 );
2813
2814 let run_array = spec_id_col
2816 .as_any()
2817 .downcast_ref::<RunArray<Int32Type>>()
2818 .expect("_spec_id column should be a RunArray");
2819
2820 let values = run_array.values();
2821 let int_values = values.as_primitive::<Int32Type>();
2822 assert_eq!(int_values.len(), 1, "Should have a single _spec_id");
2823
2824 let spec_id = int_values.value(0);
2825 assert_eq!(spec_id, 0, "_spec_id should be 0, got: {spec_id}");
2826 }
2827
2828 #[tokio::test]
2829 async fn test_select_with_spec_id_column_with_partition_evolution() {
2830 let mut fixture = TableTestFixture::new_with_partition_evolution();
2831 fixture
2832 .setup_manifest_files_with_partition_evolution()
2833 .await;
2834
2835 let table_scan = fixture
2837 .table
2838 .scan()
2839 .select(["x", RESERVED_COL_NAME_SPEC_ID, "z"])
2840 .with_row_selection_enabled(true)
2841 .build()
2842 .unwrap();
2843
2844 let batch_stream = table_scan.to_arrow().await.unwrap();
2845 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2846
2847 let col1 = batches[0].column_by_name("x").unwrap();
2849 let int64_arr = col1.as_any().downcast_ref::<Int64Array>().unwrap();
2850 assert_eq!(int64_arr.value(0), 1);
2851
2852 let spec_id_col = batches[0].column_by_name(RESERVED_COL_NAME_SPEC_ID);
2854 assert!(
2855 spec_id_col.is_some(),
2856 "_spec_id column should be present in the batch"
2857 );
2858
2859 let spec_id_col = spec_id_col.unwrap();
2861 assert!(
2862 matches!(
2863 spec_id_col.data_type(),
2864 arrow_schema::DataType::RunEndEncoded(_, _)
2865 ),
2866 "_spec_id column should use RunEndEncoded type"
2867 );
2868
2869 let run_array = spec_id_col
2871 .as_any()
2872 .downcast_ref::<RunArray<Int32Type>>()
2873 .expect("_spec_id column should be a RunArray");
2874
2875 let values = run_array.values();
2876 let int_values = values.as_primitive::<Int32Type>();
2877 assert_eq!(int_values.len(), 1, "Should have a single _spec_id");
2878
2879 let spec_id = int_values.value(0);
2880 assert_eq!(spec_id, 2, "_spec_id should be 2, got: {spec_id}");
2881 }
2882
2883 #[tokio::test]
2884 async fn test_select_with_pos_and_file_columns() {
2885 use arrow_array::cast::AsArray;
2886
2887 let mut fixture = TableTestFixture::new();
2888 fixture.setup_manifest_files().await;
2889
2890 let table_scan = fixture
2892 .table
2893 .scan()
2894 .select(["x", RESERVED_COL_NAME_POS, RESERVED_COL_NAME_FILE])
2895 .with_row_selection_enabled(true)
2896 .build()
2897 .unwrap();
2898
2899 let batch_stream = table_scan.to_arrow().await.unwrap();
2900 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2901 assert_eq!(batches.len(), 2);
2902
2903 for batch in batches.iter() {
2905 assert_eq!(batch.num_columns(), 3);
2907
2908 let x_col = batch.column_by_name("x").unwrap();
2910 let x_arr = x_col.as_primitive::<arrow_array::types::Int64Type>();
2911 assert_eq!(x_arr.value(0), 1);
2912
2913 let pos_col = batch.column(1);
2915 let pos_array: &Int64Array = pos_col
2916 .as_any()
2917 .downcast_ref::<Int64Array>()
2918 .expect("_pos column should be a Int64Array");
2919 assert_eq!(*pos_array, Int64Array::from_iter_values(0i64..1024));
2920
2921 let file_col = batch.column_by_name(RESERVED_COL_NAME_FILE);
2923 assert!(
2924 file_col.is_some(),
2925 "_file column should be present in the batch"
2926 );
2927 }
2928 }
2929
2930 #[tokio::test]
2931 async fn test_pos_column_at_start_with_filters() {
2932 let mut fixture = TableTestFixture::new();
2933 fixture.setup_manifest_files().await;
2934
2935 let predicate = Reference::new("y")
2937 .greater_than(Datum::long(4i64))
2938 .and(Reference::new("y").less_than_or_equal_to(Datum::long(5i64)));
2939 let table_scan = fixture
2941 .table
2942 .scan()
2943 .select([RESERVED_COL_NAME_POS, "x", "y"])
2944 .with_filter(predicate)
2945 .with_row_selection_enabled(true)
2946 .build()
2947 .unwrap();
2948
2949 let batch_stream = table_scan.to_arrow().await.unwrap();
2950 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2951 assert_eq!(batches.len(), 2);
2952
2953 for batch in batches.iter() {
2955 assert_eq!(batch.num_columns(), 3);
2956 assert_eq!(batch.num_rows(), 12);
2957
2958 let schema = batch.schema();
2960 assert_eq!(schema.field(0).name(), RESERVED_COL_NAME_POS);
2961 assert_eq!(schema.field(1).name(), "x");
2962 assert_eq!(schema.field(2).name(), "y");
2963
2964 let pos_col = batch.column(0);
2965 let pos_array: &Int64Array = pos_col
2966 .as_any()
2967 .downcast_ref::<Int64Array>()
2968 .expect("_pos column should be a Int64Array");
2969 assert_eq!(*pos_array, Int64Array::from_iter_values(1012i64..1024));
2970 }
2971 }
2972
2973 #[tokio::test]
2974 async fn test_repeated_pos_column_with_filter() {
2975 let mut fixture = TableTestFixture::new();
2976 fixture.setup_manifest_files().await;
2977
2978 let predicate = Reference::new("a").not_starts_with(Datum::string("Apa"));
2980 let table_scan = fixture
2982 .table
2983 .scan()
2984 .select([RESERVED_COL_NAME_POS, "a", RESERVED_COL_NAME_POS, "x"])
2985 .with_row_selection_enabled(true)
2986 .with_filter(predicate)
2987 .build()
2988 .unwrap();
2989
2990 let batch_stream = table_scan.to_arrow().await.unwrap();
2991
2992 let batches: Vec<_> = batch_stream.try_collect().await.unwrap();
2993 assert_eq!(batches.len(), 2);
2994
2995 for batch in batches.iter() {
2997 assert_eq!(batch.num_rows(), 512);
2998
2999 let pos_col = batch
3001 .column_by_name("_pos")
3002 .expect("_pos column should be present in the batch");
3003 let pos_array: &Int64Array = pos_col
3004 .as_any()
3005 .downcast_ref::<Int64Array>()
3006 .expect("_pos column should be a Int64Array");
3007 assert_eq!(*pos_array, Int64Array::from_iter_values(512i64..1024));
3008
3009 let pos_col = batch.column(2);
3011 let pos_array: &Int64Array = pos_col
3012 .as_any()
3013 .downcast_ref::<Int64Array>()
3014 .expect("_pos column should be a Int64Array");
3015 assert_eq!(*pos_array, Int64Array::from_iter_values(512i64..1024));
3016 }
3017 }
3018
3019 #[tokio::test]
3023 async fn test_pos_across_row_groups_via_table_scan() {
3024 let mut fixture = TableTestFixture::new();
3025 fixture.setup_multi_row_group_manifest(&[]).await;
3026
3027 let tasks: Vec<_> = fixture
3030 .table
3031 .scan()
3032 .select(["x", RESERVED_COL_NAME_POS])
3033 .build()
3034 .unwrap()
3035 .plan_files()
3036 .await
3037 .unwrap()
3038 .try_collect()
3039 .await
3040 .unwrap();
3041 assert_eq!(tasks.len(), 1, "expected a single FileScanTask");
3042 let task = &tasks[0];
3043 assert!(
3044 task.project_field_ids.contains(&RESERVED_FIELD_ID_POS),
3045 "_pos field id must be projected into the FileScanTask"
3046 );
3047 assert_eq!(task.start, 0, "TableScan should plan whole-file tasks");
3048 assert_eq!(task.length, task.file_size_in_bytes);
3049 assert!(task.deletes.is_empty());
3050
3051 let batches: Vec<_> = fixture
3053 .table
3054 .scan()
3055 .select(["x", RESERVED_COL_NAME_POS])
3056 .build()
3057 .unwrap()
3058 .to_arrow()
3059 .await
3060 .unwrap()
3061 .try_collect()
3062 .await
3063 .unwrap();
3064
3065 let pos: Vec<i64> = batches
3066 .iter()
3067 .flat_map(|b| {
3068 b.column_by_name(RESERVED_COL_NAME_POS)
3069 .expect("_pos column should be present")
3070 .as_any()
3071 .downcast_ref::<Int64Array>()
3072 .expect("_pos column should be a Int64Array")
3073 .values()
3074 .to_vec()
3075 })
3076 .collect();
3077 assert_eq!(pos, (0..300).collect::<Vec<i64>>());
3078
3079 let x: Vec<i64> = batches
3081 .iter()
3082 .flat_map(|b| {
3083 b.column_by_name("x")
3084 .unwrap()
3085 .as_primitive::<arrow_array::types::Int64Type>()
3086 .values()
3087 .to_vec()
3088 })
3089 .collect();
3090 assert_eq!(x, (1000..1300).collect::<Vec<i64>>());
3091 }
3092
3093 #[tokio::test]
3096 async fn test_pos_with_positional_deletes_via_table_scan() {
3097 let mut fixture = TableTestFixture::new();
3098 fixture.setup_multi_row_group_manifest(&[150, 299]).await;
3100
3101 let tasks: Vec<_> = fixture
3103 .table
3104 .scan()
3105 .select(["x", RESERVED_COL_NAME_POS])
3106 .build()
3107 .unwrap()
3108 .plan_files()
3109 .await
3110 .unwrap()
3111 .try_collect()
3112 .await
3113 .unwrap();
3114 assert_eq!(tasks.len(), 1);
3115 assert_eq!(
3116 tasks[0].deletes.len(),
3117 1,
3118 "positional delete file should be planned into the task"
3119 );
3120 assert_eq!(
3121 tasks[0].deletes[0].file_type,
3122 DataContentType::PositionDeletes
3123 );
3124
3125 let batches: Vec<_> = fixture
3127 .table
3128 .scan()
3129 .select(["x", RESERVED_COL_NAME_POS])
3130 .build()
3131 .unwrap()
3132 .to_arrow()
3133 .await
3134 .unwrap()
3135 .try_collect()
3136 .await
3137 .unwrap();
3138
3139 let pos: Vec<i64> = batches
3140 .iter()
3141 .flat_map(|b| {
3142 b.column_by_name(RESERVED_COL_NAME_POS)
3143 .expect("_pos column should be present")
3144 .as_any()
3145 .downcast_ref::<Int64Array>()
3146 .expect("_pos column should be a Int64Array")
3147 .values()
3148 .to_vec()
3149 })
3150 .collect();
3151
3152 let total: usize = batches.iter().map(|b| b.num_rows()).sum();
3153 assert_eq!(
3154 total, 298,
3155 "two rows should be removed by positional deletes"
3156 );
3157 assert!(!pos.contains(&150) && !pos.contains(&299), "got {pos:?}");
3158 let expected: Vec<i64> = (0..150).chain(151..299).collect();
3159 assert_eq!(pos, expected);
3160 }
3161
3162 #[tokio::test]
3170 async fn test_pos_reads_only_middle_row_group_via_filter() {
3171 let mut fixture = TableTestFixture::new();
3172 fixture.setup_multi_row_group_manifest(&[]).await;
3173
3174 let predicate = Reference::new("y")
3176 .greater_than_or_equal_to(Datum::long(1100))
3177 .and(Reference::new("y").less_than(Datum::long(1200)));
3178
3179 let batches: Vec<_> = fixture
3180 .table
3181 .scan()
3182 .select(["y", RESERVED_COL_NAME_POS])
3183 .with_filter(predicate)
3184 .with_row_group_filtering_enabled(true)
3185 .build()
3186 .unwrap()
3187 .to_arrow()
3188 .await
3189 .unwrap()
3190 .try_collect()
3191 .await
3192 .unwrap();
3193
3194 let total: usize = batches.iter().map(|b| b.num_rows()).sum();
3195 assert_eq!(total, 100, "only the middle row group should be read");
3196
3197 let pos: Vec<i64> = batches
3198 .iter()
3199 .flat_map(|b| {
3200 b.column_by_name(RESERVED_COL_NAME_POS)
3201 .expect("_pos column should be present")
3202 .as_any()
3203 .downcast_ref::<Int64Array>()
3204 .expect("_pos column should be a Int64Array")
3205 .values()
3206 .to_vec()
3207 })
3208 .collect();
3209 assert_eq!(
3210 pos,
3211 (100..200).collect::<Vec<i64>>(),
3212 "_pos must be file-absolute for the middle row group"
3213 );
3214
3215 let y: Vec<i64> = batches
3217 .iter()
3218 .flat_map(|b| {
3219 b.column_by_name("y")
3220 .unwrap()
3221 .as_primitive::<arrow_array::types::Int64Type>()
3222 .values()
3223 .to_vec()
3224 })
3225 .collect();
3226 assert_eq!(y, (1100..1200).collect::<Vec<i64>>());
3227 }
3228}