HappenLee commented on code in PR #65805:
URL: https://github.com/apache/doris/pull/65805#discussion_r3747683558


##########
be/src/storage/segment/column_reader.cpp:
##########
@@ -90,25 +92,260 @@ inline bool read_as_string(PrimitiveType type) {
            type == PrimitiveType::TYPE_BITMAP || type == 
PrimitiveType::TYPE_FIXED_LENGTH_OBJECT;
 }
 
-bool is_current_level_meta_access_path(const TColumnAccessPath& path) {
-    if (path.data_access_path.path.size() != 1) {
-        return false;
-    }
-    const auto& component = path.data_access_path.path[0];
+bool is_meta_access_path_component(const std::string& component) {
     return StringCaseEqual()(component, ColumnIterator::ACCESS_OFFSET) ||
            StringCaseEqual()(component, ColumnIterator::ACCESS_NULL);
 }
 
-bool is_current_level_data_access_path(const TColumnAccessPath& path,
-                                       const std::string& column_name) {
-    return path.data_access_path.path.size() == 1 &&
-           StringCaseEqual()(path.data_access_path.path[0], column_name);
+bool uses_legacy_access_path_encoding(const TColumnAccessPath& path) {
+    return !path.__isset.version ||
+           path.version == 
g_Descriptors_constants.TCOLUMN_ACCESS_PATH_VERSION_LEGACY;
 }
 
-void remove_current_level_meta_access_paths(TColumnAccessPaths& paths) {
-    auto removed = std::ranges::remove_if(paths, 
is_current_level_meta_access_path);
-    paths.erase(removed.begin(), removed.end());
-}
+namespace {
+
+// Nested access paths are processed one container level at a time:
+//
+// 1. Each Map/Array/Struct iterator's set_access_paths() calls 
_prepare_nested_access_paths(). For
+//    the all-path and predicate-path channels independently, 
_split_access_paths() validates the
+//    wire encoding and type-selected payload, removes the current iterator 
name, and separates
+//    requests consumed by the current iterator from paths that still address 
a data descendant. A
+//    current DATA request requires all data children. Struct owns 
current-level NULL metadata;
+//    Map and Array own NULL and OFFSET metadata. A supported metadata-only 
request can stop before
+//    descendant routing and mark every data child SKIP.
+// 2. This router interprets only the first remaining component and routes the 
path according to
+//    the container topology:
+//    - Struct components already name fields. Select the paths for each field 
without rewriting.
+//    - Array `*` names its only item. Retarget `*` to the item iterator name.
+//    - Map `KEYS` and `VALUES` directly name its logical children. Map `*` 
creates a complete DATA
+//      path for `KEYS` and routes any trailing components through `VALUES`.
+// 3. The container forwards the routed all-path and predicate-path channels 
to each selected
+//    child's set_access_paths(), then finalizes that child's 
PREDICATE/LAZY_OUTPUT/SKIP
+//    requirement. The child repeats the same flow, which handles arbitrary 
Map/Array/Struct
+//    nesting.
+//
+// At this point versions have been validated, legacy paths have DATA type, 
and every payload
+// selected by type is non-empty. Routing never interprets or rewrites an 
unselected compatibility
+// payload.
+class DescendantAccessPathRouter final {
+public:
+    DescendantAccessPathRouter() = delete;
+
+    // Preserve the two set_access_paths() input channels. all_paths 
originates from the
+    // all-access-path superset, while predicate_paths separately records 
predicate-phase paths.
+    // Routing may omit all_paths when the parent already requires complete 
child data.
+    struct ChildAccessPaths {
+        TColumnAccessPaths all_paths;
+        TColumnAccessPaths predicate_paths;
+
+        bool empty() const { return all_paths.empty() && 
predicate_paths.empty(); }
+    };
+
+    struct MapChildAccessPaths {
+        ChildAccessPaths key;
+        ChildAccessPaths value;
+    };
+
+    // Map children use the logical KEYS/VALUES selectors as their access-path 
names, independent
+    // of physical child column names. Expand a wildcard to complete keys and 
route its trailing
+    // qualifiers to values.
+    static Result<MapChildAccessPaths> route_map_paths_to_children(
+            TColumnAccessPaths all_paths, TColumnAccessPaths predicate_paths) {
+        MapChildAccessPaths child_paths;
+        auto status = distribute_map_paths(std::move(all_paths), 
child_paths.key.all_paths,
+                                           child_paths.value.all_paths);
+        if (!status.ok()) {
+            return ResultError(std::move(status));
+        }
+        status = distribute_map_paths(std::move(predicate_paths), 
child_paths.key.predicate_paths,
+                                      child_paths.value.predicate_paths);
+        if (!status.ok()) {
+            return ResultError(std::move(status));
+        }
+        return child_paths;
+    }
+
+    // Array has one data child. Retarget its logical wildcard selector to the 
item iterator name.
+    static Result<ChildAccessPaths> 
route_array_paths_to_item(TColumnAccessPaths all_paths,
+                                                              
TColumnAccessPaths predicate_paths,
+                                                              const 
std::string& item_name) {
+        ChildAccessPaths child_paths {.all_paths = std::move(all_paths),
+                                      .predicate_paths = 
std::move(predicate_paths)};
+        auto retarget_wildcard_paths_to_item = [&](TColumnAccessPaths& paths) 
-> Status {
+            for (auto& path : paths) {
+                const bool is_wildcard = 
DORIS_TRY(selected_payload_head_matches(
+                        path, ColumnIterator::ACCESS_ALL, 
PathHeadMatchMode::EXACT));
+                if (is_wildcard) {
+                    RETURN_IF_ERROR(replace_selected_payload_head(path, 
item_name));
+                }
+            }
+            return Status::OK();
+        };
+
+        auto status = retarget_wildcard_paths_to_item(child_paths.all_paths);
+        if (!status.ok()) {
+            return ResultError(std::move(status));
+        }
+        status = retarget_wildcard_paths_to_item(child_paths.predicate_paths);
+        if (!status.ok()) {
+            return ResultError(std::move(status));
+        }
+        return child_paths;
+    }
+
+    // Struct selectors already use child field names. Select the paths for 
one child without
+    // rewriting them, so that the child can validate and strip its own name.
+    static Result<ChildAccessPaths> select_struct_paths_for_child(
+            const TColumnAccessPaths& all_paths, const TColumnAccessPaths& 
predicate_paths,
+            const std::string& child_name, bool include_all_paths) {
+        ChildAccessPaths child_paths;
+        auto select_matching_paths = [&](const TColumnAccessPaths& 
source_paths,
+                                         TColumnAccessPaths& child_paths) -> 
Status {
+            for (const auto& path : source_paths) {
+                const bool matches_child = 
DORIS_TRY(selected_payload_head_matches(
+                        path, child_name, 
PathHeadMatchMode::CASE_INSENSITIVE));
+                if (matches_child) {
+                    child_paths.emplace_back(path);
+                }
+            }
+            return Status::OK();
+        };
+
+        if (include_all_paths) {
+            auto status = select_matching_paths(all_paths, 
child_paths.all_paths);
+            if (!status.ok()) {
+                return ResultError(std::move(status));
+            }
+        }
+        auto status = select_matching_paths(predicate_paths, 
child_paths.predicate_paths);
+        if (!status.ok()) {
+            return ResultError(std::move(status));
+        }
+        return child_paths;
+    }
+
+private:
+    enum class PathHeadMatchMode { EXACT, CASE_INSENSITIVE };
+    enum class MapSelector { WILDCARD, KEYS, VALUES };
+
+    // TColumnAccessPath may carry both payload fields after compatibility 
forwarding. Selected
+    // means data_access_path for DATA and meta_access_path for META. Visit 
only that payload and
+    // leave the other payload untouched.
+    template <typename AccessPath, typename Visitor>
+    static Status visit_selected_payload(AccessPath& access_path, Visitor&& 
visitor) {
+        switch (access_path.type) {
+        case TAccessPathType::DATA:
+            if (!access_path.__isset.data_access_path) {
+                return Status::InternalError(
+                        "Invalid DATA access path: data_access_path payload is 
not set");
+            }
+            return 
std::forward<Visitor>(visitor)(access_path.data_access_path);
+        case TAccessPathType::META:
+            if (!access_path.__isset.meta_access_path) {
+                return Status::InternalError(
+                        "Invalid META access path: meta_access_path payload is 
not set");
+            }
+            return 
std::forward<Visitor>(visitor)(access_path.meta_access_path);
+        default:
+            return Status::InternalError("Invalid access path type: {}",
+                                         static_cast<int>(access_path.type));
+        }
+    }
+
+    static Result<bool> selected_payload_head_matches(const TColumnAccessPath& 
access_path,
+                                                      const std::string& 
expected_head,
+                                                      PathHeadMatchMode 
match_mode) {
+        bool matches = false;
+        auto status = visit_selected_payload(access_path, [&](const auto& 
payload) {
+            DORIS_CHECK(!payload.path.empty());
+            matches = match_mode == PathHeadMatchMode::EXACT
+                              ? payload.path.front() == expected_head
+                              : StringCaseEqual()(payload.path.front(), 
expected_head);
+            return Status::OK();
+        });
+        if (!status.ok()) {
+            return ResultError(std::move(status));
+        }
+        return matches;
+    }
+
+    static Status replace_selected_payload_head(TColumnAccessPath& access_path,
+                                                const std::string& child_name) 
{
+        return visit_selected_payload(access_path, [&](auto& payload) {
+            DORIS_CHECK(!payload.path.empty());
+            payload.path.front() = child_name;
+            return Status::OK();
+        });
+    }
+
+    // Map `*` applies trailing qualifiers only to values, while locating 
entries still requires

Review Comment:
   select length(m['xxx'])from t; `map<string, string>: ["m", "*", "OFFSET"]`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to