1use std::{
7 path::Path,
8 sync::{Arc, Mutex},
9};
10
11#[cfg(not(target_arch = "wasm32"))]
12use std::path::PathBuf;
13
14use arrow_array::{RecordBatch, RecordBatchOptions};
15
16use crate::{
17 err::ReadStatError, progress::ProgressCallback, rs_data::ReadStatData,
18 rs_metadata::ReadStatMetadata, rs_path::ReadStatPath,
19};
20
21enum Source {
22 Path(ReadStatPath),
23 Bytes(Arc<[u8]>),
24 #[cfg(not(target_arch = "wasm32"))]
25 Mmap(PathBuf),
26}
27
28struct ReadPlan {
29 metadata: ReadStatMetadata,
30 mapping: Option<std::collections::BTreeMap<i32, i32>>,
31 count: u32,
32}
33
34pub struct ReadStatReader {
40 source: Source,
41 metadata: Mutex<Option<ReadStatMetadata>>,
42 offset: u32,
43 limit: Option<u32>,
44 columns: Option<Vec<String>>,
45 chunk_rows: u32,
46 progress: Option<Arc<dyn ProgressCallback>>,
47}
48
49impl ReadStatReader {
50 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, ReadStatError> {
52 Ok(Self::new(Source::Path(ReadStatPath::new(path)?)))
53 }
54
55 #[must_use]
57 pub fn from_bytes(bytes: impl Into<Arc<[u8]>>) -> Self {
58 Self::new(Source::Bytes(bytes.into()))
59 }
60
61 #[cfg(not(target_arch = "wasm32"))]
63 pub fn from_mmap(path: impl Into<PathBuf>) -> Result<Self, ReadStatError> {
64 let path = path.into();
65 let _ = ReadStatPath::new(&path)?;
66 Ok(Self::new(Source::Mmap(path)))
67 }
68
69 fn new(source: Source) -> Self {
70 Self {
71 source,
72 metadata: Mutex::new(None),
73 offset: 0,
74 limit: None,
75 columns: None,
76 chunk_rows: 10_000,
77 progress: None,
78 }
79 }
80
81 #[must_use]
84 pub fn rows(mut self, offset: u32, limit: Option<u32>) -> Self {
85 self.offset = offset;
86 self.limit = limit;
87 self
88 }
89
90 #[must_use]
95 pub fn columns(mut self, columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
96 self.columns = Some(columns.into_iter().map(Into::into).collect());
97 self
98 }
99
100 #[must_use]
102 pub fn chunk_rows(mut self, rows: u32) -> Self {
103 self.chunk_rows = rows;
104 self
105 }
106
107 #[must_use]
109 pub fn progress(mut self, callback: Arc<dyn ProgressCallback>) -> Self {
110 self.progress = Some(callback);
111 self
112 }
113
114 pub fn metadata(&self) -> Result<ReadStatMetadata, ReadStatError> {
121 let mut cached = self
122 .metadata
123 .lock()
124 .map_err(|_| ReadStatError::Other("reader metadata cache is poisoned".into()))?;
125 if let Some(md) = cached.as_ref() {
126 return Ok(md.clone());
127 }
128
129 let mut md = ReadStatMetadata::new();
130 match &self.source {
131 Source::Path(path) => md.read_metadata(path, false)?,
132 Source::Bytes(bytes) => md.read_metadata_from_bytes(bytes, false)?,
133 #[cfg(not(target_arch = "wasm32"))]
134 Source::Mmap(path) => md.read_metadata_from_mmap(path, false)?,
135 }
136 *cached = Some(md.clone());
137 Ok(md)
138 }
139
140 fn plan(&self) -> Result<ReadPlan, ReadStatError> {
141 if self.chunk_rows == 0 {
142 return Err(ReadStatError::InvalidChunkSize);
143 }
144 let md = self.metadata()?;
145 let total = u32::try_from(md.row_count.ok_or(ReadStatError::RowCountUnavailable)?)?;
146 if self.offset > total {
147 return Err(ReadStatError::InvalidRowRange {
148 offset: self.offset,
149 limit: self.limit,
150 row_count: total,
151 });
152 }
153 let available = total - self.offset;
154 let count = self.limit.unwrap_or(available);
155 if count > available {
156 return Err(ReadStatError::InvalidRowRange {
157 offset: self.offset,
158 limit: self.limit,
159 row_count: total,
160 });
161 }
162 let mapping = md.resolve_selected_columns(self.columns.clone())?;
163 Ok(ReadPlan {
164 metadata: md,
165 mapping,
166 count,
167 })
168 }
169
170 pub fn visit(
172 &self,
173 visitor: impl FnMut(RecordBatch) -> Result<(), ReadStatError>,
174 ) -> Result<(), ReadStatError> {
175 let plan = self.plan()?;
176 self.visit_with_plan(&plan, visitor)
177 }
178
179 fn visit_with_plan(
180 &self,
181 plan: &ReadPlan,
182 mut visitor: impl FnMut(RecordBatch) -> Result<(), ReadStatError>,
183 ) -> Result<(), ReadStatError> {
184 let ReadPlan {
185 metadata: md,
186 mapping,
187 count,
188 } = plan;
189 if let Some(progress) = &self.progress {
190 let label = match &self.source {
191 Source::Path(p) => p.path.to_string_lossy().into_owned(),
192 Source::Bytes(_) => "<bytes>".into(),
193 #[cfg(not(target_arch = "wasm32"))]
194 Source::Mmap(p) => p.to_string_lossy().into_owned(),
195 };
196 progress.parsing_started(&label);
197 }
198 if *count == 0 {
199 return Ok(());
200 }
201 let end = self
202 .offset
203 .checked_add(*count)
204 .ok_or_else(|| ReadStatError::Other("row offset overflow".into()))?;
205 let mut data = ReadStatData::new().init_for_visit(
206 md.clone(),
207 mapping.as_ref(),
208 self.offset,
209 end,
210 self.chunk_rows as usize,
211 );
212 if let Some(progress) = &self.progress {
213 data = data.set_progress(progress.clone());
214 }
215 match &self.source {
216 Source::Path(path) => data.visit_data(path, self.chunk_rows as usize, &mut visitor),
217 Source::Bytes(bytes) => {
218 data.visit_data_from_bytes(bytes, self.chunk_rows as usize, &mut visitor)
219 }
220 #[cfg(not(target_arch = "wasm32"))]
221 Source::Mmap(path) => {
222 data.visit_data_from_mmap(path, self.chunk_rows as usize, &mut visitor)
223 }
224 }
225 }
226
227 pub fn chunks(&self) -> Result<Vec<RecordBatch>, ReadStatError> {
229 let mut batches = Vec::new();
230 self.visit(|batch| {
231 batches.push(batch);
232 Ok(())
233 })?;
234 Ok(batches)
235 }
236
237 pub fn read(&self) -> Result<RecordBatch, ReadStatError> {
239 let plan = self.plan()?;
240 let ReadPlan {
241 metadata: md,
242 mapping,
243 ..
244 } = &plan;
245 let schema = mapping.as_ref().map_or_else(
246 || md.schema.clone(),
247 |m| md.filter_to_selected_columns(m).schema,
248 );
249 let mut batches = Vec::new();
250 self.visit_with_plan(&plan, |batch| {
251 batches.push(batch);
252 Ok(())
253 })?;
254 if batches.is_empty() {
255 return Ok(RecordBatch::new_empty(Arc::new(schema)));
256 }
257 if schema.fields().is_empty() {
258 return RecordBatch::try_new_with_options(
259 Arc::new(schema),
260 Vec::new(),
261 &RecordBatchOptions::new().with_row_count(Some(plan.count as usize)),
262 )
263 .map_err(Into::into);
264 }
265 arrow::compute::concat_batches(&Arc::new(schema), &batches).map_err(Into::into)
266 }
267}
268
269pub fn read_metadata<P: AsRef<Path>>(path: P) -> Result<ReadStatMetadata, ReadStatError> {
286 ReadStatReader::from_path(path)?.metadata()
287}
288
289pub fn read_to_batch<P: AsRef<Path>>(path: P) -> Result<RecordBatch, ReadStatError> {
307 ReadStatReader::from_path(path)?.read()
308}