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