Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #pragma once |
19 | | |
20 | | #include <cstddef> |
21 | | #include <cstdint> |
22 | | #include <memory> |
23 | | #include <optional> |
24 | | #include <string> |
25 | | #include <utility> |
26 | | #include <vector> |
27 | | |
28 | | #include "common/status.h" |
29 | | #include "core/data_type/data_type.h" |
30 | | #include "core/field.h" |
31 | | #include "exprs/vexpr_fwd.h" |
32 | | #include "format_v2/file_reader.h" |
33 | | |
34 | | namespace doris { |
35 | | class RuntimeState; |
36 | | } // namespace doris |
37 | | |
38 | | namespace doris::format { |
39 | | |
40 | | struct ColumnDefinition; |
41 | | struct TableFilter; |
42 | | |
43 | | enum class TableColumnMappingMode { |
44 | | // Match by ColumnDefinition::identifier TYPE_INT as field id. |
45 | | BY_FIELD_ID, |
46 | | // Match by ColumnDefinition::identifier TYPE_STRING, or logical name when identifier is null. |
47 | | BY_NAME, |
48 | | // Match top-level columns by file position. This mainly serves Hive1 ORC style files whose |
49 | | // column names are placeholder values such as `_col0` / `_col1`, where position is the only |
50 | | // reliable way to select the correct column. |
51 | | BY_INDEX, |
52 | | }; |
53 | | |
54 | | enum TableVirtualColumnType { |
55 | | INVALID = 0, // not a virtual column |
56 | | // Iceberg v3 row lineage metadata column `_row_id`. Physical non-null values |
57 | | // are preserved; NULL or missing values inherit first_row_id + row_position. |
58 | | ROW_ID = 1, |
59 | | // Iceberg v3 row lineage metadata column `_last_updated_sequence_number`. |
60 | | // Physical non-null values are preserved; NULL or missing values inherit the |
61 | | // data file's last_updated_sequence_number. |
62 | | LAST_UPDATED_SEQUENCE_NUMBER = 2, |
63 | | // Doris internal Iceberg row locator column `__DORIS_ICEBERG_ROWID_COL__`. |
64 | | // It is a struct used by delete/update/merge, not the Iceberg `_row_id`. |
65 | | ICEBERG_ROWID = 3, |
66 | | }; |
67 | | |
68 | | enum class FilterConversionType { |
69 | | COPY_DIRECTLY, // filter can be copied directly from file layer without any change, e.g. column type and table type are the same and no complex nested projection is involved. |
70 | | CAST_FILTER, // filter can be converted from file layer by adding a cast, e.g. column type is nullable but table type is not, or file column has a trivial nested projection but table column has a complex nested projection. |
71 | | READER_EXPRESSION, |
72 | | FINALIZE_ONLY, // filter cannot be converted to file layer and should be evaluated at table reader finalize phase, e.g. predicates on ICEBERG_ROW_ID column which is generated by IcebergReader. |
73 | | CONSTANT, |
74 | | }; |
75 | | |
76 | | // Nested global-to-local child mapping. The root index points either to a request-local slot or to |
77 | | // a child id, depending on the owner. child_mapping keeps the recursive table-child to file-child |
78 | | // relationship explicit instead of encoding it in ColumnMapping flags. |
79 | | struct IndexMapping { |
80 | | int32_t index = -1; |
81 | | std::map<int32_t, std::shared_ptr<IndexMapping>> child_mapping; |
82 | | }; |
83 | | |
84 | | // Recursive result produced after one table/global column is assigned to a file-local source. |
85 | | struct ColumnMapResult { |
86 | | std::optional<LocalColumnId> local_column_id; |
87 | | std::optional<LocalColumnIndex> column_index; |
88 | | std::optional<IndexMapping> mapping; |
89 | | }; |
90 | | |
91 | | // Final mapping entry from one global result column to one file-local source. |
92 | | struct ColumnMapEntry { |
93 | | IndexMapping mapping; |
94 | | DataTypePtr local_type; |
95 | | DataTypePtr global_type; |
96 | | FilterConversionType filter_conversion = FilterConversionType::FINALIZE_ONLY; |
97 | | }; |
98 | | |
99 | | // Collection of final result-column mappings produced for one file/split. |
100 | | struct ResultColumnMapping { |
101 | | std::map<GlobalIndex, ColumnMapEntry> global_to_local; |
102 | | }; |
103 | | |
104 | | // Mapping result from one table column to one file column. |
105 | | // This is the main boundary object between table-level schema semantics and file-local schema |
106 | | // semantics. |
107 | | struct ColumnMapping { |
108 | | // Position of the top-level projected column in the table/global output block. Table-level |
109 | | // filters and column predicates refer to this index after FileScannerV2 translates FE ids at |
110 | | // the scanner boundary. |
111 | | GlobalIndex global_index; |
112 | | std::string table_column_name; |
113 | | // File-reader local id for the mapped node. |
114 | | // |
115 | | // For a root mapping it is convertible to LocalColumnId. For a nested mapping it is the |
116 | | // LocalColumnIndex child id under the parent projection. This is deliberately separated from |
117 | | // ColumnDefinition::identifier, which is the table-to-file matching key such as Parquet/Iceberg |
118 | | // field_id or column name. |
119 | | // |
120 | | // Empty means the table column is constant, missing, partition-only, or virtual. |
121 | | std::optional<int32_t> file_local_id; |
122 | | std::string file_column_name; |
123 | | // Full file type/children before nested projection pruning. Used to rebuild projected types |
124 | | // and to localize nested filters that reference children not present in the output projection. |
125 | | DataTypePtr original_file_type; |
126 | | std::vector<ColumnDefinition> original_file_children; |
127 | | // File children after applying the scan projection. The order follows the file-local semantic |
128 | | // schema, not table child order. TableReader uses this to map table-output children back to the |
129 | | // file-local block layout when projection, predicate-only children, and schema evolution mix. |
130 | | std::vector<ColumnDefinition> projected_file_children; |
131 | | // Split/file-local constant entry when this mapping is produced from partition/default/virtual |
132 | | // expression instead of physical file data. |
133 | | std::optional<ConstantIndex> constant_index; |
134 | | // Effective file type after applying casts/remaps/nested projection pruning. |
135 | | DataTypePtr file_type; |
136 | | // Target table/global type after final materialization. |
137 | | DataTypePtr table_type; |
138 | | |
139 | | // Final projection expression used to convert file-local values into table/global values, such |
140 | | // as casts, defaults, partition values, generated columns, or complex-column remaps. |
141 | | VExprContextSPtr projection; |
142 | | |
143 | | // Mapping tree for nested table children. The order follows table output children, while file |
144 | | // children can be pruned/reordered through each child mapping's file-reader local id. |
145 | | std::vector<ColumnMapping> child_mappings; |
146 | | // True when file value can be used directly as table value without cast or child remap. |
147 | | bool is_trivial = false; |
148 | | // How filters referencing this table/global column can be converted below table-reader |
149 | | // finalize. This is metadata for localize_filters() and future constant-filter evaluation. |
150 | | FilterConversionType filter_conversion = FilterConversionType::FINALIZE_ONLY; |
151 | | TableVirtualColumnType virtual_column_type = TableVirtualColumnType::INVALID; |
152 | | VExprContextSPtr default_expr; |
153 | | |
154 | | std::string debug_string() const; |
155 | | }; |
156 | | |
157 | | struct TableColumnMapperOptions { |
158 | | TableColumnMappingMode mode = TableColumnMappingMode::BY_FIELD_ID; |
159 | | |
160 | | std::string debug_string() const; |
161 | | }; |
162 | | |
163 | | Status clone_table_expr_tree(const VExprSPtr& expr, VExprSPtr* cloned_expr); |
164 | | const Field* find_partition_value(const ColumnDefinition& table_column, |
165 | | const std::map<std::string, Field>& partition_values); |
166 | | |
167 | | // Generic mapping layer from table schema to file schema. |
168 | | // Iceberg uses BY_FIELD_ID. Plain by-name formats can reuse this component as well, so keep this |
169 | | // abstraction table-format neutral instead of making it Iceberg-only. |
170 | | class TableColumnMapper { |
171 | | public: |
172 | | explicit TableColumnMapper(TableColumnMapperOptions options = {}) |
173 | 160 | : _options(std::move(options)) {} |
174 | 160 | virtual ~TableColumnMapper() = default; |
175 | | |
176 | | // Build column mappings from table schema to file schema. |
177 | | // The resulting ColumnMapping describes how each table column is produced from a file column, |
178 | | // a constant, or an expression. Later projection, filter localization, and table-block |
179 | | // finalization should all reuse the same mapping. |
180 | | virtual Status create_mapping(const std::vector<ColumnDefinition>& projected_columns, |
181 | | const std::map<std::string, Field>& partition_values, |
182 | | const std::vector<ColumnDefinition>& file_schema); |
183 | | |
184 | | // Convert a table-level scan request into a file-local scan request. table_filters preserve |
185 | | // row-level filtering semantics and are rewritten as file-local conjuncts. File-layer pruning |
186 | | // such as ZoneMap, dictionary, and bloom filter derives from those localized VExpr conjuncts. |
187 | | virtual Status create_scan_request(const std::vector<TableFilter>& table_filters, |
188 | | const std::vector<ColumnDefinition>& projected_columns, |
189 | | FileScanRequest* file_request, |
190 | | RuntimeState* runtime_state = nullptr); |
191 | | |
192 | | // Localize table-level filters to the file schema. |
193 | | // Trivial mappings can copy structured predicates directly. Type changes may be localized with |
194 | | // a safe cast. Expressions that cannot be pushed down safely should be handled through |
195 | | // reader_expression_map or table-level finalize/filter fallback. |
196 | | virtual Status localize_filters(const std::vector<TableFilter>& table_filters, |
197 | | FileScanRequest* file_request, |
198 | | RuntimeState* runtime_state = nullptr); |
199 | 223 | void clear() { |
200 | 223 | _mappings.clear(); |
201 | 223 | _hidden_mappings.clear(); |
202 | 223 | _constant_map.clear(); |
203 | 223 | _filter_entries.clear(); |
204 | 223 | _file_schema.clear(); |
205 | 223 | _partition_values.clear(); |
206 | 223 | } |
207 | 699 | const std::vector<ColumnMapping>& mappings() const { return _mappings; } |
208 | 47 | const std::map<GlobalIndex, FilterEntry>& filter_entries() const { return _filter_entries; } |
209 | 19 | const ConstantMap& constant_map() const { return _constant_map; } |
210 | | std::string debug_string() const; |
211 | | |
212 | | protected: |
213 | | // Columnar readers such as Parquet can read predicate columns first, evaluate row filters, and |
214 | | // lazily read the rest. Row-oriented readers such as CSV/Text materialize one row at a time and |
215 | | // should keep all required columns in one scan list. |
216 | 126 | virtual bool enable_lazy_materialization() const { return true; } |
217 | | // Row-oriented readers such as CSV/Text cannot physically read only a nested child from one |
218 | | // delimited text field. They must scan the whole complex top-level field and let TableReader |
219 | | // rematerialize the requested table child after row-level filters have run. |
220 | 141 | virtual bool force_full_complex_scan_projection() const { return false; } |
221 | | |
222 | | const ColumnDefinition* _find_file_field( |
223 | | const ColumnDefinition& table_column, |
224 | | const std::vector<ColumnDefinition>& file_schema) const; |
225 | | Status _create_direct_mapping(const ColumnDefinition& table_column, |
226 | | const ColumnDefinition& file_field, ColumnMapping* mapping) const; |
227 | | |
228 | | Status _create_by_index_mapping(const ColumnDefinition& table_column, |
229 | | const std::vector<ColumnDefinition>& file_schema, |
230 | | ColumnMapping* mapping); |
231 | | Status _build_filter_entries(const FileScanRequest& file_request); |
232 | | Status _build_result_column_mapping(const FileScanRequest& file_request); |
233 | | |
234 | | void _set_constant_mapping(ColumnMapping* mapping, VExprContextSPtr expr); |
235 | | Status _create_mapping_for_column(const ColumnDefinition& table_column, |
236 | | GlobalIndex global_index, ColumnMapping* mapping); |
237 | | Status _create_hidden_filter_mapping(const ColumnDefinition& table_column, |
238 | | GlobalIndex global_index, ColumnMapping* mapping); |
239 | | Status _build_hidden_filter_mappings(const std::vector<TableFilter>& table_filters); |
240 | | std::vector<ColumnMapping> _filter_visible_mappings() const; |
241 | | |
242 | | ColumnMapping* _find_mapping(GlobalIndex global_index); |
243 | | ColumnMapping* _find_filter_mapping(GlobalIndex global_index); |
244 | | |
245 | | TableColumnMapperOptions _options; |
246 | | // Column mapping for each projected column, in the same order as projected_columns. Each entry |
247 | | // describes how to get one table/global column from file-local sources, and carries metadata |
248 | | // for filter localization and result finalize. |
249 | | std::vector<ColumnMapping> _mappings; |
250 | | // Predicate-only top-level columns are not output projection columns, so keep their mappings |
251 | | // here. They are visible only to filter localization and file-reader predicate construction. |
252 | | std::vector<ColumnMapping> _hidden_mappings; |
253 | | std::map<GlobalIndex, FilterEntry> _filter_entries; |
254 | | ConstantMap _constant_map; |
255 | | // Split-local schema state retained from create_mapping() so create_scan_request() can build |
256 | | // hidden mappings for top-level filter slots that are absent from projected_columns. |
257 | | std::vector<ColumnDefinition> _file_schema; |
258 | | std::map<std::string, Field> _partition_values; |
259 | | }; |
260 | | |
261 | | // Parquet consumes the full FileScanRequest shape: predicate columns for lazy materialization and |
262 | | // file-local conjuncts for ZoneMap, dictionary, and bloom-filter pruning. |
263 | | class ParquetColumnMapper final : public TableColumnMapper { |
264 | | public: |
265 | | using TableColumnMapper::TableColumnMapper; |
266 | | }; |
267 | | |
268 | | // Mapper for readers that always materialize every required file column before filtering. The |
269 | | // table-to-file schema mapping is still generic, but the FileScanRequest layout is simpler: |
270 | | // predicate_columns are not populated. |
271 | | class MaterializedColumnMapper final : public TableColumnMapper { |
272 | | public: |
273 | | using TableColumnMapper::TableColumnMapper; |
274 | | |
275 | | protected: |
276 | 4 | bool enable_lazy_materialization() const override { return false; } |
277 | 7 | bool force_full_complex_scan_projection() const override { return true; } |
278 | | }; |
279 | | |
280 | | } // namespace doris::format |