Coverage Report

Created: 2026-07-30 08:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/scan/access_path_parser.cpp
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
#include "exec/scan/access_path_parser.h"
19
20
#include <fmt/format.h>
21
22
#include <algorithm>
23
#include <charconv>
24
#include <map>
25
#include <string>
26
#include <string_view>
27
#include <utility>
28
29
#include "common/cast_set.h"
30
#include "common/consts.h"
31
#include "core/assert_cast.h"
32
#include "core/data_type/data_type.h"
33
#include "core/data_type/data_type_array.h"
34
#include "core/data_type/data_type_map.h"
35
#include "core/data_type/data_type_nullable.h"
36
#include "core/data_type/data_type_struct.h"
37
#include "runtime/descriptors.h"
38
#include "util/string_util.h"
39
40
namespace doris {
41
namespace {
42
43
147k
bool is_scanner_materialized_virtual_column(const std::string& column_name) {
44
147k
    return column_name == BeConsts::ICEBERG_ROWID_COL;
45
147k
}
46
47
24.3k
bool parse_non_negative_int(std::string_view value, int32_t* result) {
48
24.3k
    DORIS_CHECK(result != nullptr);
49
24.3k
    int32_t parsed = -1;
50
24.3k
    const auto* begin = value.data();
51
24.3k
    const auto* end = begin + value.size();
52
24.3k
    const auto [ptr, ec] = std::from_chars(begin, end, parsed);
53
24.3k
    if (ec != std::errc() || ptr != end || parsed < 0) {
54
12.0k
        return false;
55
12.0k
    }
56
12.2k
    *result = parsed;
57
12.2k
    return true;
58
24.3k
}
59
60
2
std::string access_path_to_string(const std::vector<std::string>& path) {
61
2
    return fmt::format("{}", fmt::join(path, "."));
62
2
}
63
64
format::ColumnDefinition* find_or_add_child(format::ColumnDefinition* parent, int32_t id,
65
99.5k
                                            std::string name, DataTypePtr type) {
66
99.5k
    DORIS_CHECK(parent != nullptr);
67
99.5k
    for (auto& child : parent->children) {
68
53.7k
        if ((child.has_identifier_field_id() && child.get_identifier_field_id() == id) ||
69
53.7k
            child.name == name) {
70
0
            return &child;
71
0
        }
72
53.7k
    }
73
99.5k
    parent->children.push_back({
74
99.5k
            .identifier = Field::create_field<TYPE_INT>(id),
75
99.5k
            .name = std::move(name),
76
99.5k
            .type = std::move(type),
77
99.5k
            .children = {},
78
99.5k
            .default_expr = nullptr,
79
99.5k
            .is_partition_key = false,
80
99.5k
    });
81
99.5k
    return &parent->children.back();
82
99.5k
}
83
84
void inherit_schema_metadata(format::ColumnDefinition* column,
85
99.5k
                             const format::ColumnDefinition* schema_column) {
86
99.5k
    if (column == nullptr || schema_column == nullptr) {
87
66.4k
        return;
88
66.4k
    }
89
33.1k
    column->name_mapping = schema_column->name_mapping;
90
    // The presence bit is part of the mapping contract: an explicit empty mapping must remain
91
    // authoritative after access-path pruning instead of enabling current-name fallback.
92
33.1k
    column->has_name_mapping = schema_column->has_name_mapping;
93
    // Initial defaults describe the logical value of fields absent from older files. Nested
94
    // access-path pruning must retain them just like it retains rename metadata.
95
33.1k
    column->initial_default_value = schema_column->initial_default_value;
96
33.1k
    column->initial_default_value_is_base64 = schema_column->initial_default_value_is_base64;
97
33.1k
}
98
99
const format::ColumnDefinition* find_schema_child_by_path(
100
        const format::ColumnDefinition* schema_column, const std::string& child_path,
101
29.0k
        bool prefer_exact_name_match) {
102
29.0k
    if (schema_column == nullptr) {
103
15.6k
        return nullptr;
104
15.6k
    }
105
13.3k
    int32_t parsed_field_id = -1;
106
13.3k
    if (parse_non_negative_int(child_path, &parsed_field_id)) {
107
1.28k
        const auto child_it = std::ranges::find_if(
108
1.82k
                schema_column->children, [&](const format::ColumnDefinition& child) {
109
1.82k
                    return child.has_identifier_field_id() &&
110
1.82k
                           child.get_identifier_field_id() == parsed_field_id;
111
1.82k
                });
112
1.28k
        return child_it == schema_column->children.end() ? nullptr : &*child_it;
113
1.28k
    }
114
12.0k
    if (!prefer_exact_name_match) {
115
7.45k
        const auto child_it = std::ranges::find_if(schema_column->children, [&](const auto& child) {
116
7.45k
            if (to_lower(child.name) == to_lower(child_path)) {
117
3.66k
                return true;
118
3.66k
            }
119
3.79k
            return std::ranges::any_of(child.name_mapping, [&](const std::string& alias) {
120
1
                return to_lower(alias) == to_lower(child_path);
121
1
            });
122
7.45k
        });
123
3.66k
        return child_it == schema_column->children.end() ? nullptr : &*child_it;
124
3.66k
    }
125
    // Iceberg can reuse a historical name for a newly added sibling. Current names therefore
126
    // have precedence across the entire struct; an earlier alias must not steal that access path.
127
14.8k
    const auto exact_it = std::ranges::find_if(schema_column->children, [&](const auto& child) {
128
14.8k
        return to_lower(child.name) == to_lower(child_path);
129
14.8k
    });
130
8.43k
    if (exact_it != schema_column->children.end()) {
131
8.43k
        return &*exact_it;
132
8.43k
    }
133
18.4E
    const auto alias_it = std::ranges::find_if(schema_column->children, [&](const auto& child) {
134
11
        return std::ranges::any_of(child.name_mapping, [&](const std::string& alias) {
135
6
            return to_lower(alias) == to_lower(child_path);
136
6
        });
137
11
    });
138
18.4E
    return alias_it == schema_column->children.end() ? nullptr : &*alias_it;
139
8.42k
}
140
141
99.5k
int32_t schema_field_id(const format::ColumnDefinition* schema_column) {
142
99.5k
    if (schema_column == nullptr || !schema_column->has_identifier_field_id()) {
143
73.3k
        return -1;
144
73.3k
    }
145
26.1k
    return schema_column->get_identifier_field_id();
146
99.5k
}
147
148
98.2k
int32_t schema_field_id_or(const format::ColumnDefinition* schema_column, int32_t fallback) {
149
98.2k
    const auto field_id = schema_field_id(schema_column);
150
98.2k
    return field_id >= 0 ? field_id : fallback;
151
98.2k
}
152
153
std::string schema_field_name_or(const format::ColumnDefinition* schema_column,
154
27.7k
                                 std::string fallback) {
155
27.7k
    return schema_column == nullptr || schema_column->name.empty() ? std::move(fallback)
156
27.7k
                                                                   : schema_column->name;
157
27.7k
}
158
159
struct AccessPathNode {
160
    bool project_all = false;
161
    std::map<std::string, AccessPathNode> children;
162
};
163
164
556
void merge_access_path_node(AccessPathNode* dst, const AccessPathNode& src) {
165
556
    DORIS_CHECK(dst != nullptr);
166
556
    if (dst->project_all) {
167
0
        return;
168
0
    }
169
556
    if (src.project_all) {
170
335
        dst->project_all = true;
171
335
        dst->children.clear();
172
335
        return;
173
335
    }
174
285
    for (const auto& [path, child] : src.children) {
175
285
        merge_access_path_node(&dst->children[path], child);
176
285
    }
177
221
}
178
179
void insert_access_path(AccessPathNode* root, const std::vector<std::string>& path,
180
13.9k
                        size_t path_idx) {
181
13.9k
    DORIS_CHECK(root != nullptr);
182
13.9k
    if (root->project_all) {
183
0
        return;
184
0
    }
185
13.9k
    if (path_idx >= path.size()) {
186
12.1k
        root->project_all = true;
187
12.1k
        root->children.clear();
188
12.1k
        return;
189
12.1k
    }
190
1.85k
    insert_access_path(&root->children[path[path_idx]], path, path_idx + 1);
191
1.85k
}
192
193
Status build_nested_children_from_access_node(format::ColumnDefinition* column,
194
                                              const DataTypePtr& type, const AccessPathNode& node,
195
                                              const std::string& path,
196
                                              const format::ColumnDefinition* schema_column,
197
                                              bool prefer_exact_name_match);
198
199
// Expand a full complex-column projection into table-schema children when the table format provides
200
// an external/current schema. Without this, `SELECT complex_col` or `SELECT *` leaves
201
// ColumnDefinition::children empty, so ColumnMapper treats the root complex column as a scalar
202
// mapping and later tries to cast the old file shape to the current table shape directly.
203
//
204
// Examples:
205
//   - STRUCT country/city projected from an old file STRUCT country/population/location should
206
//     create children country and city, so city can be materialized as missing/default.
207
//   - ARRAY<STRUCT<item, quantity>> should create the array element wrapper and then the element
208
//     struct children item and quantity.
209
//   - MAP<STRING, STRUCT<full_name, age>> should create semantic children key/value directly, then
210
//     expand the value struct children full_name and age. Do not introduce a physical entries
211
//     wrapper here: ColumnMapper and TableReader treat MAP children as [key, value].
212
Status build_all_nested_children_from_schema(format::ColumnDefinition* column,
213
                                             const DataTypePtr& type, const std::string& path,
214
                                             const format::ColumnDefinition* schema_column,
215
149k
                                             bool prefer_exact_name_match) {
216
149k
    DORIS_CHECK(column != nullptr);
217
218
149k
    const auto nested_type = remove_nullable(type);
219
149k
    AccessPathNode project_all;
220
149k
    project_all.project_all = true;
221
149k
    switch (nested_type->get_primitive_type()) {
222
12.6k
    case TYPE_STRUCT: {
223
12.6k
        const auto& struct_type = assert_cast<const DataTypeStruct&>(*nested_type);
224
40.4k
        for (size_t field_idx = 0; field_idx < struct_type.get_elements().size(); ++field_idx) {
225
27.7k
            const auto field_name = struct_type.get_element_name(field_idx);
226
27.7k
            const auto* schema_child =
227
27.7k
                    find_schema_child_by_path(schema_column, field_name, prefer_exact_name_match);
228
27.7k
            auto* child = find_or_add_child(
229
27.7k
                    column, schema_field_id_or(schema_child, cast_set<int32_t>(field_idx)),
230
27.7k
                    schema_field_name_or(schema_child, field_name),
231
27.7k
                    struct_type.get_element(field_idx));
232
27.7k
            inherit_schema_metadata(child, schema_child);
233
27.7k
            RETURN_IF_ERROR(build_nested_children_from_access_node(
234
27.7k
                    child, child->type, project_all, path + "." + child->name, schema_child,
235
27.7k
                    prefer_exact_name_match));
236
27.7k
        }
237
12.6k
        return Status::OK();
238
12.6k
    }
239
24.7k
    case TYPE_ARRAY: {
240
24.7k
        const auto& array_type = assert_cast<const DataTypeArray&>(*nested_type);
241
24.7k
        const auto* element_schema = schema_column != nullptr && !schema_column->children.empty()
242
24.7k
                                             ? &schema_column->children[0]
243
24.7k
                                             : nullptr;
244
24.7k
        auto* child = find_or_add_child(column, schema_field_id_or(element_schema, 0), "element",
245
24.7k
                                        array_type.get_nested_type());
246
24.7k
        inherit_schema_metadata(child, element_schema);
247
24.7k
        return build_nested_children_from_access_node(child, child->type, project_all, path + ".*",
248
24.7k
                                                      element_schema, prefer_exact_name_match);
249
12.6k
    }
250
22.5k
    case TYPE_MAP: {
251
22.5k
        const auto& map_type = assert_cast<const DataTypeMap&>(*nested_type);
252
22.5k
        const auto* key_schema = schema_column != nullptr && !schema_column->children.empty()
253
22.5k
                                         ? &schema_column->children[0]
254
22.5k
                                         : nullptr;
255
22.5k
        const auto* value_schema = schema_column != nullptr && schema_column->children.size() > 1
256
22.5k
                                           ? &schema_column->children[1]
257
22.5k
                                           : nullptr;
258
22.5k
        auto* key_child = find_or_add_child(column, schema_field_id_or(key_schema, 0), "key",
259
22.5k
                                            map_type.get_key_type());
260
22.5k
        inherit_schema_metadata(key_child, key_schema);
261
22.5k
        RETURN_IF_ERROR(build_nested_children_from_access_node(
262
22.5k
                key_child, key_child->type, project_all, path + ".KEYS", key_schema,
263
22.5k
                prefer_exact_name_match));
264
22.5k
        auto* value_child = find_or_add_child(column, schema_field_id_or(value_schema, 1), "value",
265
22.5k
                                              map_type.get_value_type());
266
22.5k
        inherit_schema_metadata(value_child, value_schema);
267
22.5k
        RETURN_IF_ERROR(build_nested_children_from_access_node(
268
22.5k
                value_child, value_child->type, project_all, path + ".VALUES", value_schema,
269
22.5k
                prefer_exact_name_match));
270
22.5k
        return Status::OK();
271
22.5k
    }
272
89.5k
    default:
273
89.5k
        return Status::OK();
274
149k
    }
275
149k
}
276
277
Status build_struct_children_from_access_node(format::ColumnDefinition* column,
278
                                              const DataTypeStruct& struct_type,
279
                                              const AccessPathNode& node, const std::string& path,
280
                                              const format::ColumnDefinition* schema_column,
281
979
                                              bool prefer_exact_name_match) {
282
979
    DORIS_CHECK(column != nullptr);
283
1.31k
    for (const auto& [child_path, child_node] : node.children) {
284
        // Struct children are resolved by name or schema field id. We do not treat a numeric
285
        // child token as a struct ordinal, because `col.0` becomes ambiguous once the struct
286
        // evolves. Position-based access needs a separate design if it is required later.
287
1.31k
        if (child_path == "OFFSET" || child_path == "*" || child_path == "KEYS" ||
288
1.31k
            child_path == "VALUES") {
289
4
            return Status::NotSupported(
290
4
                    "AccessPathParser does not support access path {} for slot {}",
291
4
                    path + "." + child_path, column->name);
292
4
        }
293
294
        // Prefer the table/schema ColumnDefinition because it carries field ids and aliases.
295
        // Fallback to the struct type name only for formats without external schema metadata.
296
1.30k
        const auto* schema_child =
297
1.30k
                find_schema_child_by_path(schema_column, child_path, prefer_exact_name_match);
298
1.30k
        int32_t field_id = schema_field_id(schema_child);
299
1.30k
        std::string field_name = schema_child == nullptr ? child_path : schema_child->name;
300
1.30k
        DataTypePtr field_type = schema_child == nullptr ? nullptr : schema_child->type;
301
1.30k
        if (field_id < 0 || field_type == nullptr) {
302
25
            for (size_t field_idx = 0; field_idx < struct_type.get_elements().size(); ++field_idx) {
303
22
                if (to_lower(struct_type.get_element_name(field_idx)) == to_lower(field_name)) {
304
11
                    field_id = cast_set<int32_t>(field_idx);
305
11
                    field_name = struct_type.get_element_name(field_idx);
306
11
                    field_type = struct_type.get_element(field_idx);
307
11
                    break;
308
11
                }
309
22
            }
310
14
        }
311
312
1.30k
        if (field_id < 0 || field_type == nullptr) {
313
3
            return Status::NotSupported(
314
3
                    "AccessPathParser does not support access path {} for slot {}",
315
3
                    path + "." + child_path, column->name);
316
3
        }
317
        // TODO: For TVF Parquet files without field ids, this fallback uses the struct ordinal as
318
        // the table child identifier. BY_NAME mapping should instead keep a string identifier and
319
        // let TableColumnMapper resolve the file-local child id from the Parquet schema.
320
1.30k
        auto* child = find_or_add_child(column, field_id, field_name, field_type);
321
1.30k
        inherit_schema_metadata(child, schema_child);
322
1.30k
        RETURN_IF_ERROR(build_nested_children_from_access_node(
323
1.30k
                child, child->type, child_node, path + "." + child_path, schema_child,
324
1.30k
                prefer_exact_name_match));
325
1.30k
    }
326
972
    return Status::OK();
327
979
}
328
329
Status build_map_children_from_access_node(format::ColumnDefinition* column,
330
                                           const DataTypeMap& map_type, const AccessPathNode& node,
331
                                           const std::string& path,
332
                                           const format::ColumnDefinition* schema_column,
333
240
                                           bool prefer_exact_name_match) {
334
240
    DORIS_CHECK(column != nullptr);
335
240
    AccessPathNode key_node;
336
240
    AccessPathNode value_node;
337
240
    bool need_key = false;
338
240
    bool need_value = false;
339
340
273
    for (const auto& [child_path, child_node] : node.children) {
341
273
        if (child_path == "OFFSET") {
342
1
            return Status::NotSupported(
343
1
                    "AccessPathParser does not support access path {} for slot {}",
344
1
                    path + "." + child_path, column->name);
345
1
        }
346
272
        if (child_path == "KEYS") {
347
41
            need_key = true;
348
41
            merge_access_path_node(&key_node, child_node);
349
41
            continue;
350
41
        }
351
231
        if (child_path == "VALUES") {
352
179
            need_key = true;
353
179
            key_node.project_all = true;
354
179
            key_node.children.clear();
355
179
            need_value = true;
356
179
            merge_access_path_node(&value_node, child_node);
357
179
            continue;
358
179
        }
359
52
        if (child_path == "*") {
360
51
            need_key = true;
361
51
            key_node.project_all = true;
362
51
            key_node.children.clear();
363
51
            need_value = true;
364
51
            merge_access_path_node(&value_node, child_node);
365
51
            continue;
366
51
        }
367
1
        return Status::NotSupported("AccessPathParser does not support access path {} for slot {}",
368
1
                                    path + "." + child_path, column->name);
369
52
    }
370
238
    if (need_key && !need_value) {
371
        // A key-only MAP projection is not independently materializable yet. FileScannerV2 can
372
        // describe a projection such as `m.KEYS`, but the downstream file block -> table block path
373
        // still builds a ColumnMap from key column + value column + offsets. If the value child is
374
        // omitted here, TableReader/ColumnMapper cannot reconstruct a valid table MAP column even
375
        // though the query only needs keys.
376
        //
377
        // Example:
378
        //   SELECT map_keys(m) FROM t;
379
        // or
380
        //   SELECT * FROM t WHERE array_contains(map_keys(m), 'k1');
381
        //
382
        // The access path only asks for `m.KEYS`, but the scan still has to read `m.VALUES` as a
383
        // temporary full projection until map materialization supports constructing a table MAP
384
        // from keys only.
385
9
        need_value = true;
386
9
        value_node.project_all = true;
387
9
        value_node.children.clear();
388
9
    }
389
390
238
    if (!need_key && !need_value) {
391
0
        return Status::OK();
392
0
    }
393
394
238
    const auto* key_schema = schema_column != nullptr && !schema_column->children.empty()
395
238
                                     ? &schema_column->children[0]
396
238
                                     : nullptr;
397
238
    const auto* value_schema = schema_column != nullptr && schema_column->children.size() > 1
398
238
                                       ? &schema_column->children[1]
399
238
                                       : nullptr;
400
238
    if (need_key) {
401
238
        auto* key_child = find_or_add_child(column, schema_field_id_or(key_schema, 0), "key",
402
238
                                            map_type.get_key_type());
403
238
        inherit_schema_metadata(key_child, key_schema);
404
238
        RETURN_IF_ERROR(build_nested_children_from_access_node(key_child, key_child->type, key_node,
405
238
                                                               path + ".KEYS", key_schema,
406
238
                                                               prefer_exact_name_match));
407
238
    }
408
238
    if (need_value) {
409
238
        auto* value_child = find_or_add_child(column, schema_field_id_or(value_schema, 1), "value",
410
238
                                              map_type.get_value_type());
411
238
        inherit_schema_metadata(value_child, value_schema);
412
238
        RETURN_IF_ERROR(build_nested_children_from_access_node(
413
238
                value_child, value_child->type, value_node, path + ".VALUES", value_schema,
414
238
                prefer_exact_name_match));
415
238
    }
416
237
    return Status::OK();
417
238
}
418
419
Status build_nested_children_from_access_node(format::ColumnDefinition* column,
420
                                              const DataTypePtr& type, const AccessPathNode& node,
421
                                              const std::string& path,
422
                                              const format::ColumnDefinition* schema_column,
423
150k
                                              bool prefer_exact_name_match) {
424
150k
    DORIS_CHECK(column != nullptr);
425
150k
    if (node.project_all || node.children.empty()) {
426
149k
        return build_all_nested_children_from_schema(column, type, path, schema_column,
427
149k
                                                     prefer_exact_name_match);
428
149k
    }
429
430
1.34k
    const auto nested_type = remove_nullable(type);
431
1.34k
    switch (nested_type->get_primitive_type()) {
432
979
    case TYPE_STRUCT:
433
979
        return build_struct_children_from_access_node(
434
979
                column, assert_cast<const DataTypeStruct&>(*nested_type), node, path, schema_column,
435
979
                prefer_exact_name_match);
436
134
    case TYPE_ARRAY: {
437
134
        if (node.children.size() != 1 || !node.children.contains("*")) {
438
2
            return Status::NotSupported(
439
2
                    "AccessPathParser does not support access path {} for slot {}", path,
440
2
                    column->name);
441
2
        }
442
132
        const auto& array_type = assert_cast<const DataTypeArray&>(*nested_type);
443
132
        const auto* element_schema = schema_column != nullptr && !schema_column->children.empty()
444
132
                                             ? &schema_column->children[0]
445
132
                                             : nullptr;
446
132
        auto* child = find_or_add_child(column, schema_field_id_or(element_schema, 0), "element",
447
132
                                        array_type.get_nested_type());
448
132
        inherit_schema_metadata(child, element_schema);
449
132
        return build_nested_children_from_access_node(child, child->type, node.children.at("*"),
450
132
                                                      path + ".*", element_schema,
451
132
                                                      prefer_exact_name_match);
452
134
    }
453
240
    case TYPE_MAP:
454
240
        return build_map_children_from_access_node(
455
240
                column, assert_cast<const DataTypeMap&>(*nested_type), node, path, schema_column,
456
240
                prefer_exact_name_match);
457
0
    default:
458
0
        return Status::NotSupported("AccessPathParser does not support access path {} for slot {}",
459
0
                                    path, column->name);
460
1.34k
    }
461
1.34k
}
462
463
} // namespace
464
465
Status AccessPathParser::build_nested_children(format::ColumnDefinition* column,
466
                                               const std::vector<TColumnAccessPath>& access_paths,
467
                                               const format::ColumnDefinition* schema_column,
468
147k
                                               bool prefer_exact_name_match) {
469
147k
    DORIS_CHECK(column != nullptr);
470
147k
    if (is_scanner_materialized_virtual_column(column->name)) {
471
255
        return Status::OK();
472
255
    }
473
147k
    if (!is_complex_type(remove_nullable(column->type)->get_primitive_type())) {
474
95.7k
        return Status::OK();
475
95.7k
    }
476
477
51.3k
    AccessPathNode root;
478
    // Build tree for AccessPathNode.
479
    // For example, for access paths ["a.b", "a.c", "d"], the tree will be:
480
    // root
481
    // ├── a
482
    // │   ├── b
483
    // │   └── c
484
    // └── d
485
51.3k
    for (const auto& access_path : access_paths) {
486
        // TODO: Support META access paths if needed. Currently AccessPathParser only supports
487
        // DATA access paths.
488
12.1k
        if (access_path.type != TAccessPathType::DATA || !access_path.__isset.data_access_path) {
489
2
            return Status::NotSupported(
490
2
                    "AccessPathParser only supports DATA access paths for slot {}", column->name);
491
2
        }
492
12.1k
        const auto& path = access_path.data_access_path.path;
493
12.1k
        if (path.empty()) {
494
0
            insert_access_path(&root, path, 0);
495
0
            continue;
496
0
        }
497
12.1k
        int32_t top_level_id = -1;
498
12.1k
        if (to_lower(path.front()) != to_lower(column->name) &&
499
12.1k
            (!parse_non_negative_int(path.front(), &top_level_id) ||
500
11.0k
             !column->has_identifier_field_id() ||
501
11.0k
             top_level_id != column->get_identifier_field_id())) {
502
2
            return Status::NotSupported("AccessPathParser access path {} does not match slot {}",
503
2
                                        access_path_to_string(path), column->name);
504
2
        }
505
12.1k
        insert_access_path(&root, path, 1);
506
12.1k
    }
507
    // Recursively build nested children for the column based on the AccessPathNode tree.
508
51.3k
    return build_nested_children_from_access_node(column, column->type, root, column->name,
509
51.3k
                                                  schema_column, prefer_exact_name_match);
510
51.3k
}
511
512
Status AccessPathParser::build_nested_children(format::ColumnDefinition* column,
513
                                               const SlotDescriptor* slot_desc,
514
                                               const format::ColumnDefinition* schema_column,
515
147k
                                               bool prefer_exact_name_match) {
516
147k
    DORIS_CHECK(column != nullptr);
517
147k
    DORIS_CHECK(slot_desc != nullptr);
518
147k
    return build_nested_children(column, slot_desc->all_access_paths(), schema_column,
519
147k
                                 prefer_exact_name_match);
520
147k
}
521
522
} // namespace doris