-
Notifications
You must be signed in to change notification settings - Fork 180
Expand file tree
/
Copy pathtake.rs
More file actions
186 lines (164 loc) · 5.78 KB
/
Copy pathtake.rs
File metadata and controls
186 lines (164 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors
use std::iter::once;
use std::path::PathBuf;
use arrow_array::PrimitiveArray;
use arrow_array::types::Int64Type;
use arrow_select::concat::concat_batches;
use arrow_select::take::take_record_batch;
use async_trait::async_trait;
use futures::stream;
use itertools::Itertools;
use parquet::arrow::ParquetRecordBatchStreamBuilder;
use parquet::arrow::arrow_reader::ArrowReaderMetadata;
use parquet::arrow::arrow_reader::ArrowReaderOptions;
use parquet::file::metadata::PageIndexPolicy;
use stream::StreamExt;
use tokio::fs::File;
use vortex::array::Canonical;
use vortex::array::DynArray;
use vortex::array::IntoArray;
use vortex::array::VortexSessionExecute;
use vortex::array::stream::ArrayStreamExt;
use vortex::buffer::Buffer;
use vortex::file::OpenOptionsSessionExt;
use vortex::file::VortexFile;
use vortex::utils::aliases::hash_map::HashMap;
use crate::Format;
use crate::SESSION;
use crate::random_access::RandomAccessor;
/// Random accessor for Vortex format files.
///
/// The file handle is opened at construction time and reused across `take()` calls.
pub struct VortexRandomAccessor {
name: String,
format: Format,
file: VortexFile,
}
impl VortexRandomAccessor {
/// Open a Vortex file and return a ready-to-use accessor.
pub async fn open(
path: impl AsRef<std::path::Path>,
name: impl Into<String>,
format: Format,
) -> anyhow::Result<Self> {
let file = SESSION.open_options().open_path(path.as_ref()).await?;
Ok(Self {
name: name.into(),
format,
file,
})
}
}
#[async_trait]
impl RandomAccessor for VortexRandomAccessor {
fn format(&self) -> Format {
self.format
}
fn name(&self) -> &str {
&self.name
}
async fn take(&self, indices: &[u64]) -> anyhow::Result<usize> {
let indices_buf: Buffer<u64> = Buffer::from(indices.to_vec());
let array = self
.file
.scan()?
.with_row_indices(indices_buf)
.into_array_stream()?
.read_all()
.await?;
// We canonicalize / decompress for equivalence to Arrow's `RecordBatch`es.
let mut ctx = SESSION.create_execution_ctx();
let canonical = array.execute::<Canonical>(&mut ctx)?.into_array();
Ok(canonical.len())
}
}
/// Random accessor for Parquet format files.
///
/// Parquet footer and row group offsets are parsed at construction time and
/// reused to map indices to row groups in each `take()` call.
pub struct ParquetRandomAccessor {
name: String,
/// Cumulative row offsets per row group (length = num_row_groups + 1).
row_group_offsets: Vec<i64>,
/// Cached Arrow reader metadata (footer) to avoid re-parsing on each take.
arrow_metadata: ArrowReaderMetadata,
/// Path to the Parquet file (for re-opening on each take).
path: PathBuf,
}
impl ParquetRandomAccessor {
/// Open a Parquet file, parse the footer, and return a ready-to-use accessor.
pub async fn open(path: PathBuf, name: impl Into<String>) -> anyhow::Result<Self> {
let mut file = File::open(&path).await?;
let options = ArrowReaderOptions::new().with_page_index_policy(PageIndexPolicy::Required);
let arrow_metadata = ArrowReaderMetadata::load_async(&mut file, options).await?;
let row_group_offsets = once(0)
.chain(
arrow_metadata
.metadata()
.row_groups()
.iter()
.map(|rg| rg.num_rows()),
)
.scan(0i64, |acc, x| {
*acc += x;
Some(*acc)
})
.collect::<Vec<_>>();
Ok(Self {
name: name.into(),
row_group_offsets,
arrow_metadata,
path,
})
}
}
#[async_trait]
impl RandomAccessor for ParquetRandomAccessor {
fn format(&self) -> Format {
Format::Parquet
}
fn name(&self) -> &str {
&self.name
}
async fn take(&self, indices: &[u64]) -> anyhow::Result<usize> {
// Map indices to row groups.
let mut row_groups = HashMap::new();
for &idx in indices {
let row_group_idx = self
.row_group_offsets
.binary_search(&(idx as i64))
.unwrap_or_else(|e| e - 1);
row_groups
.entry(row_group_idx)
.or_insert_with(Vec::new)
.push((idx as i64) - self.row_group_offsets[row_group_idx]);
}
let sorted_row_group_keys = row_groups.keys().copied().sorted().collect_vec();
let row_group_indices = sorted_row_group_keys
.iter()
.map(|i| row_groups[i].clone())
.collect_vec();
// Re-open the file but reuse cached metadata (avoids re-parsing the footer).
let file = File::open(&self.path).await?;
let builder =
ParquetRecordBatchStreamBuilder::new_with_metadata(file, self.arrow_metadata.clone());
let reader = builder
.with_row_groups(sorted_row_group_keys)
// FIXME(ngates): our indices code assumes the batch size == the row group sizes
.with_batch_size(10_000_000)
.build()?;
let schema = reader.schema().clone();
let batches = reader
.enumerate()
.map(|(idx, batch)| {
let batch = batch.unwrap();
let indices = PrimitiveArray::<Int64Type>::from(row_group_indices[idx].clone());
take_record_batch(&batch, &indices).unwrap()
})
.collect::<Vec<_>>()
.await;
let result = concat_batches(&schema, &batches)?;
Ok(result.num_rows())
}
}