Skip to main content

file_engine/
query.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub(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    // Collects filters that can be pushed down to the file, specifically filters where Expr
120    // only contains columns from the file.
121    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    /// Maps columns in `scan_schema` to their index in the projected file schema.
166    /// `None` means the column doesn't exist in the file and should be filled with default values.
167    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}