-
Notifications
You must be signed in to change notification settings - Fork 4k
branch-4.2: [fix](external) Keep sub-field predicate access paths #68331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: branch-4.2
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -401,7 +401,7 @@ && containsDataSkippingOnlyAccessPath(collectAccessPathResults)) { | |
| buildColumnAccessPaths(slot, predicateAccessPaths); | ||
| AccessPathInfo accessPathInfo = result.get(slot.getExprId().asInt()); | ||
| if (accessPathInfo != null) { | ||
| retainPredicatePathsInFinalAllAccessPaths( | ||
| alignPredicatePathsWithFinalAllAccessPaths( | ||
| predicatePaths, accessPathInfo.getAllAccessPaths()); | ||
| accessPathInfo.getPredicateAccessPaths().addAll(predicatePaths); | ||
| } | ||
|
|
@@ -413,7 +413,7 @@ && containsDataSkippingOnlyAccessPath(collectAccessPathResults)) { | |
| buildColumnAccessPaths(slot, predicateAccessPaths); | ||
| AccessPathInfo accessPathInfo = result.get(slot.getExprId().asInt()); | ||
| if (accessPathInfo != null) { | ||
| retainPredicatePathsInFinalAllAccessPaths( | ||
| alignPredicatePathsWithFinalAllAccessPaths( | ||
| predicatePaths, accessPathInfo.getAllAccessPaths()); | ||
| accessPathInfo.getPredicateAccessPaths().addAll(predicatePaths); | ||
| } | ||
|
|
@@ -861,34 +861,49 @@ private static void stripNullSuffixPaths( | |
| } | ||
|
|
||
| /** | ||
| * Keep predicate access paths as a subset of final all access paths after NULL/OFFSET cleanup. | ||
| * Predicate paths are built from filter expressions first, but later all-path rewrites may drop | ||
| * metadata-only paths or collapse paths to whole-column access. Any predicate path not present | ||
| * in final all paths must be removed before sending access info to BE. | ||
| * Reconcile predicate access paths with the final all access paths. Predicate paths are built | ||
| * from filter expressions first, but later all-path rewrites drop redundant paths or collapse | ||
| * them to whole-column access, so a predicate path can end up outside the final all paths. | ||
| * | ||
| * <p>Examples: | ||
| * <ul> | ||
| * <li>All paths {@code [s]}, predicate paths {@code [s.city.NULL]} becomes no predicate | ||
| * paths after parent NULL removal.</li> | ||
| * <li>All paths {@code [s.city.NULL, s.zip]}, predicate paths | ||
| * {@code [s.NULL, s.city.NULL]} becomes {@code [s.city.NULL]}.</li> | ||
| * </ul> | ||
| * <p>A NULL/OFFSET path is dropped when it is no longer one of the all paths: BE switches the | ||
| * whole iterator to NULL_MAP_ONLY/OFFSET_ONLY when it sees such a path and skips the children, | ||
| * so it must not come back through the predicate paths either. | ||
| * | ||
| * <p>Any other path is kept, because BE needs it to read the predicate columns first and | ||
| * lazily materialize the rest. It is added to the all paths unless a wider path already covers | ||
| * it, e.g. the whole-column path {@code [s]} covers the predicate path {@code [s.city]}. | ||
| */ | ||
| private static void retainPredicatePathsInFinalAllAccessPaths( | ||
| private static void alignPredicatePathsWithFinalAllAccessPaths( | ||
| List<TColumnAccessPath> predicatePaths, List<TColumnAccessPath> allPaths) { | ||
| if (predicatePaths.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| List<TColumnAccessPath> toRemove = new ArrayList<>(); | ||
| for (TColumnAccessPath predicatePath : predicatePaths) { | ||
| if (!allPaths.contains(predicatePath)) { | ||
| toRemove.add(predicatePath); | ||
| if (isMetaOnlyAccessPath(predicatePath)) { | ||
| if (!allPaths.contains(predicatePath)) { | ||
| toRemove.add(predicatePath); | ||
| } | ||
| } else if (!isCoveredByAllPath(predicatePath, allPaths)) { | ||
| allPaths.add(predicatePath); | ||
| } | ||
| } | ||
| predicatePaths.removeAll(toRemove); | ||
| } | ||
|
|
||
| private static boolean isMetaOnlyAccessPath(TColumnAccessPath accessPath) { | ||
| return accessPath.getType() == TAccessPathType.META | ||
| || isDataSkippingOnlyAccessPath(getAccessPathList(accessPath)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Preserve literal Variant keys named NULL/OFFSET This classifies metadata solely from the final path string, but Variant string keys are copied verbatim. A valid predicate such as |
||
| } | ||
|
|
||
| private static boolean isCoveredByAllPath( | ||
| TColumnAccessPath predicatePath, List<TColumnAccessPath> allPaths) { | ||
| for (TColumnAccessPath allPath : allPaths) { | ||
| if (allPath.getType() == predicatePath.getType() | ||
| && pathCoversPrefix(getAccessPathList(allPath), getAccessPathList(predicatePath))) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Normalize the root before testing prefix coverage Complex access paths are collected with a lower-cased slot root, but a collapsed whole-column path is rebuilt from the catalog name. For an OLAP complex column named |
||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| private static boolean hasStrictPrefix(List<String> path, List<String> prefix) { | ||
| return path.size() > prefix.size() && path.subList(0, prefix.size()).equals(prefix); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Do not forward an unescaped literal
*struct fieldA struct field can legitimately be named
*(for example from an external schema, and ordinalelement_atcopies that real name). If the query also outputs the whole struct, all paths collapse to[s], but this branch leaves predicate path[s, *]because the root covers it. FileScannerV2 parses predicate paths independently, andAccessPathParser::build_struct_children_from_access_nodeexplicitly rejects*under STRUCT as a reserved array/map traversal token, so a query that previously read the full struct and evaluated the filter now fails during scanner setup. Please preserve/escape segment provenance or suppress unsupported covered subpaths, and add a file-scan regression for this field name.