1pub(crate) mod file_stream;
16
17use std::collections::HashSet;
18use std::pin::Pin;
19use std::sync::Arc;
20use std::task::{Context, Poll};
21
22use common_datasource::object_store::{LocalFileAccess, build_backend};
23use common_recordbatch::adapter::RecordBatchMetrics;
24use common_recordbatch::error::{self as recordbatch_error, Result as RecordBatchResult};
25use common_recordbatch::{
26 DfSendableRecordBatchStream, OrderOption, RecordBatch, RecordBatchStream,
27 SendableRecordBatchStream,
28};
29use datafusion::logical_expr::utils as df_logical_expr_utils;
30use datafusion_expr::expr::Expr;
31use datatypes::arrow::compute as arrow_compute;
32use datatypes::data_type::DataType;
33use datatypes::schema::{Schema, SchemaRef};
34use datatypes::vectors::Helper;
35use futures::Stream;
36use snafu::{GenerateImplicitData, ResultExt, ensure};
37use store_api::storage::ScanRequest;
38
39use self::file_stream::ScanPlanConfig;
40use crate::error::{BuildBackendSnafu, ProjectSchemaSnafu, ProjectionOutOfBoundsSnafu, Result};
41use crate::region::FileRegion;
42
43impl FileRegion {
44 pub async fn query(
45 &self,
46 request: ScanRequest,
47 local_file_access: &LocalFileAccess,
48 ) -> Result<SendableRecordBatchStream> {
49 let store = build_backend(&self.url, &self.options, local_file_access)
50 .await
51 .context(BuildBackendSnafu)?;
52
53 let projection = request.projection.as_deref();
54 let file_projection = self.projection_pushdown_to_file(projection)?;
55 let file_filters = self.filters_pushdown_to_file(&request.filters)?;
56 let file_schema = Arc::new(Schema::new(self.file_options.file_column_schemas.clone()));
57
58 let projected_file_schema = if let Some(projection) = &file_projection {
59 Arc::new(
60 file_schema
61 .try_project(projection)
62 .context(ProjectSchemaSnafu)?,
63 )
64 } else {
65 file_schema.clone()
66 };
67
68 let file_stream = file_stream::create_stream(
69 &self.format,
70 &ScanPlanConfig {
71 file_schema,
72 files: &self.file_options.files,
73 projection: file_projection.as_ref(),
74 filters: &file_filters,
75 limit: request.limit,
76 store,
77 },
78 )?;
79
80 let scan_schema = self.scan_schema(projection)?;
81
82 Ok(Box::pin(FileToScanRegionStream::new(
83 scan_schema,
84 projected_file_schema,
85 file_stream,
86 )))
87 }
88
89 fn projection_pushdown_to_file(
90 &self,
91 req_projection: Option<&[usize]>,
92 ) -> Result<Option<Vec<usize>>> {
93 let Some(scan_projection) = req_projection else {
94 return Ok(None);
95 };
96
97 let file_column_schemas = &self.file_options.file_column_schemas;
98 let mut file_projection = Vec::with_capacity(scan_projection.len());
99 for column_index in scan_projection {
100 ensure!(
101 *column_index < self.metadata.schema.num_columns(),
102 ProjectionOutOfBoundsSnafu {
103 column_index: *column_index,
104 bounds: self.metadata.schema.num_columns()
105 }
106 );
107
108 let column_name = self.metadata.schema.column_name_by_index(*column_index);
109 let file_column_index = file_column_schemas
110 .iter()
111 .position(|c| c.name == column_name);
112 if let Some(file_column_index) = file_column_index {
113 file_projection.push(file_column_index);
114 }
115 }
116 Ok(Some(file_projection))
117 }
118
119 fn filters_pushdown_to_file(&self, scan_filters: &[Expr]) -> Result<Vec<Expr>> {
122 let mut file_filters = Vec::with_capacity(scan_filters.len());
123
124 let file_column_names = self
125 .file_options
126 .file_column_schemas
127 .iter()
128 .map(|c| &c.name)
129 .collect::<HashSet<_>>();
130
131 let mut aux_column_set = HashSet::new();
132 for scan_filter in scan_filters {
133 df_logical_expr_utils::expr_to_columns(scan_filter, &mut aux_column_set)?;
134
135 let all_file_columns = aux_column_set
136 .iter()
137 .all(|column_in_expr| file_column_names.contains(&column_in_expr.name));
138 if all_file_columns {
139 file_filters.push(scan_filter.clone());
140 }
141 aux_column_set.clear();
142 }
143 Ok(file_filters)
144 }
145
146 fn scan_schema(&self, req_projection: Option<&[usize]>) -> Result<SchemaRef> {
147 let schema = if let Some(indices) = req_projection {
148 Arc::new(
149 self.metadata
150 .schema
151 .try_project(indices)
152 .context(ProjectSchemaSnafu)?,
153 )
154 } else {
155 self.metadata.schema.clone()
156 };
157
158 Ok(schema)
159 }
160}
161
162struct FileToScanRegionStream {
163 scan_schema: SchemaRef,
164 file_stream: DfSendableRecordBatchStream,
165 scan_to_file_projection: Vec<Option<usize>>,
168}
169
170impl RecordBatchStream for FileToScanRegionStream {
171 fn schema(&self) -> SchemaRef {
172 self.scan_schema.clone()
173 }
174
175 fn output_ordering(&self) -> Option<&[OrderOption]> {
176 None
177 }
178
179 fn metrics(&self) -> Option<RecordBatchMetrics> {
180 None
181 }
182}
183
184impl Stream for FileToScanRegionStream {
185 type Item = RecordBatchResult<RecordBatch>;
186
187 fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
188 match Pin::new(&mut self.file_stream).poll_next(ctx) {
189 Poll::Pending => Poll::Pending,
190 Poll::Ready(Some(Ok(file_record_batch))) => {
191 let num_rows = file_record_batch.num_rows();
192 let mut columns = Vec::with_capacity(self.scan_schema.num_columns());
193
194 for (idx, column_schema) in self.scan_schema.column_schemas().iter().enumerate() {
195 if let Some(file_idx) = self.scan_to_file_projection[idx] {
196 let expected_arrow_type = column_schema.data_type.as_arrow_type();
197 let mut array = file_record_batch.column(file_idx).clone();
198
199 if array.data_type() != &expected_arrow_type {
200 array = arrow_compute::cast(array.as_ref(), &expected_arrow_type)
201 .context(recordbatch_error::ArrowComputeSnafu)?;
202 }
203
204 let vector = Helper::try_into_vector(array)
205 .context(recordbatch_error::DataTypesSnafu)?;
206 columns.push(vector);
207 } else {
208 let vector = column_schema
209 .create_default_vector(num_rows)
210 .context(recordbatch_error::DataTypesSnafu)?
211 .ok_or_else(|| {
212 recordbatch_error::CreateRecordBatchesSnafu {
213 reason: format!(
214 "column {} is missing from file source and has no default",
215 column_schema.name
216 ),
217 }
218 .build()
219 })?;
220 columns.push(vector);
221 }
222 }
223
224 let record_batch = RecordBatch::new(self.scan_schema.clone(), columns)?;
225
226 Poll::Ready(Some(Ok(record_batch)))
227 }
228 Poll::Ready(Some(Err(error))) => {
229 Poll::Ready(Some(Err(recordbatch_error::Error::PollStream {
230 error,
231 location: snafu::Location::generate(),
232 })))
233 }
234 Poll::Ready(None) => Poll::Ready(None),
235 }
236 }
237}
238
239impl FileToScanRegionStream {
240 fn new(
241 scan_schema: SchemaRef,
242 file_schema: SchemaRef,
243 file_stream: DfSendableRecordBatchStream,
244 ) -> Self {
245 let scan_to_file_projection = scan_schema
246 .column_schemas()
247 .iter()
248 .map(|column| file_schema.column_index_by_name(&column.name))
249 .collect();
250
251 Self {
252 scan_schema,
253 file_stream,
254 scan_to_file_projection,
255 }
256 }
257}