Repository navigation
[fix](fe) Fix nested column pruning OFFSET dedup crash on array<struct> - #62631
Conversation
|
/review |
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
|
run buildall |
There was a problem hiding this comment.
One blocking issue remains in the new OFFSET dedup logic.
Critical checkpoint conclusions:
- Goal of the current task: partially achieved. The array sibling-field case from the PR description is covered, but map value accesses that are spelled as
*vsVALUESare still treated as unrelated, so OFFSET stripping is not correct end-to-end. - Scope/focus: the patch is small and localized.
- Concurrency: no concurrency-sensitive code is involved here.
- Lifecycle/static init: no special lifecycle concerns.
- Config/compatibility/persistence/FE-BE protocol: no new config, compatibility, or persistence concerns.
- Parallel code paths: not fully handled. Map value access has semantically equivalent encodings (
*andVALUES), and the new raw-prefix comparison only handles one spelling. - Special conditional checks: the new
minLenprefix test is too weak because it compares raw path tokens instead of normalized semantic coverage. - Tests: the added regression covers the array crash path, but I did not find coverage for the still-broken map overlap case, e.g.
length(map_col['a'])together withmap_values(map_col)[1]. - Test result changes: the new
.outfile looks consistent with the added SQL case. - Observability: no additional logging or metrics are needed for this optimizer fix.
- Transaction/persistence/data writes: not applicable.
- Performance: no material concern in this change.
- Other issues: none beyond the blocking bug below.
| } | ||
| int minLen = Math.min(nonOffset.size(), prefix.size()); | ||
| if (minLen == 0 | ||
| || nonOffset.subList(0, minLen).equals(prefix.subList(0, minLen))) { |
There was a problem hiding this comment.
This raw-prefix comparison misses overlapping map-value accesses that are spelled with different path tokens. length(map_col['a']) is collected as [map_col, *, OFFSET], but map_values(map_col)[1] becomes [map_col, VALUES]. Those two paths still target the same value container, yet * and VALUES are not prefix-equal here, so the OFFSET path survives.
That leaves both val_col.OFFSET and val_col on the BE side. MapFileColumnIterator::set_access_paths() forwards both to the value iterator, and StringFileColumnIterator::set_access_paths() switches to OFFSET_ONLY as soon as it sees the first one, so the full-value consumer reads zero-filled strings instead of the real payload.
A query like select length(map_col['a']), map_values(map_col)[1] from ... should still suppress the OFFSET path. The dedup needs to normalize the map aliases (* vs VALUES/KEYS) or compare semantic container coverage instead of raw string prefixes.
FE UT Coverage ReportIncrement line coverage |
|
/review |
|
run buildall |
There was a problem hiding this comment.
Findings
- Blocking:
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/NestedColumnPruning.java
The new*vsVALUESsemantic equivalence is not safe in both directions.[map_col, *]is not just value access; it is also the FE signal that BE must read map keys soelement_at(map_col, key)can locate the entry. If a query mixeslength(map_col['a'])withmap_values(map_col)[1], the new dedup drops[map_col, *, OFFSET]and keeps only[map_col, VALUES]. In BE,MapFileColumnIterator::set_access_paths()then marks the key iteratorSKIP_READING, so theelement_at(map_col, 'a')insidelength()can no longer match the key and returns NULL/defaults. The new EXPLAIN-only regression test for this case therefore encodes the broken behavior instead of guarding against it.
Critical Checkpoints
- Goal: The array dedup fix and the
!accessPartialChildguard look correct, but the map*/VALUESchange does not accomplish the goal safely because it regresses mixedelement_at+map_valuesqueries. - Scope/minimality: The patch is small and focused.
- Concurrency: Not involved.
- Lifecycle/static init: Not involved.
- Config: None.
- Compatibility / FE-BE contract: Affected. The new equivalence violates the existing access-path contract in BE, where
*implies key + value access andVALUESimplies value-only access. - Parallel code paths: Not fully handled. Any path that uses
element_at(map, key)still needs keys even when another expression reads onlyVALUES. - Special conditions: The new suppression condition is missing the required asymmetry between
*andVALUES. - Test coverage: Good coverage for the array fix; insufficient for the map case because the new test only checks EXPLAIN and does not verify query results.
- Test result files: The added
.outfile appears auto-generated and consistent with the executed case. - Observability: No new observability needs.
- Transaction/persistence/data writes: Not applicable.
- FE-BE variable/path passing: No new fields, but the encoded access path is wrong for the mixed map case.
- Performance: No material concern beyond the correctness issue.
- Other issues: None beyond the blocking regression above.
Requesting changes because the current map-path suppression can produce wrong results.
| // Both cover the same value container, so treat them as equivalent for OFFSET suppression. | ||
| // Do NOT treat * as equivalent to KEYS. For example: | ||
| // select length(map_col['a']), map_keys(map_col) from t | ||
| // map_keys(map_col) only covers the key side, while length(map_col['a']) still needs |
There was a problem hiding this comment.
This comparison is not safe in both directions. A [map_col, *] path means element_at(map, key) style access, and the BE uses it to keep the map key iterator readable as well as the value iterator (see MapFileColumnIterator::set_access_paths()). [map_col, VALUES] only preserves the value side. With this change, select length(map_col['a']), map_values(map_col)[1] ... drops [map_col, *, OFFSET] and keeps only [map_col, VALUES]; BE then sets the key iterator to SKIP_READING, so the element_at(map_col, 'a') inside length() can no longer match the key and returns NULL/defaults. The * vs VALUES relation here needs to be directional, or this case needs to be rewritten to KEYS + VALUES rather than removing the * path entirely.
|
/review |
|
run buildall |
There was a problem hiding this comment.
Requesting changes because one correctness gap remains.
Critical checkpoints:
- Goal/task: The PR aims to make OFFSET-path dedup safe for mixed nested accesses. It fixes the top-level ARRAY/MAP cases covered by the new tests, but it does not yet cover the same access pattern when the container is nested under a STRUCT slot.
- Scope/focus: The change is localized to NestedColumnPruning, but the new guard
if (slot.getDataType().isArrayType() || slot.getDataType().isMapType())scopes the rewrite too narrowly. - Concurrency: Not applicable; this is FE-only rewrite logic with no new concurrent state.
- Lifecycle/config/compatibility: No new lifecycle, config, or upgrade concerns found.
- Parallel code paths: The BE access-path contract is the same for array/map children inside structs, so those paths need the same suppression/rewrite behavior.
- Special conditions: The current special handling only triggers when the root slot itself is ARRAY/MAP; nested containers under STRUCT still forward conflicting OFFSET and full-data paths together.
- Test coverage: Added FE unit/regression tests cover top-level arr_struct and map_col cases, but they do not cover STRUCT<arr/map ...> cases where the bug still reproduces.
- Observability/persistence/transactions/FE-BE variables: No additional blocking issues found.
- Validation: I attempted
bash run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.PruneNestedColumnTest, but this runner is missingthirdparty/installed/bin/protoc, so I could not rerun FE UT locally.
See the inline comment for the concrete failing shape.
FE UT Coverage ReportIncrement line coverage |
FE Regression Coverage ReportIncrement line coverage |
|
run external |
FE Regression Coverage ReportIncrement line coverage |
|
/review |
|
run buildall |
There was a problem hiding this comment.
Blocking finding:
regression-test/conf/regression-conf.groovywas committed with worktree-local port overrides. This file is explicitly local-only andAGENTS.mdsays env/run config changes must not be included in the PR.
Critical checkpoint conclusions:
- Goal of the task: The FE pruning change appears to address the reported
array<struct>sibling-field case and the mixedelement_at(map)+map_values(map)case. The added FE unit tests and regression tests exercise those paths. - Small / focused change: Not fully. The code fix itself is focused, but the PR also includes an unrelated local regression config edit.
- Concurrency: Not involved. I did not find shared-state or lock-order concerns in this change.
- Lifecycle / static initialization: Not involved.
- Configuration items: No new product config is added, but a checked-in regression harness config file was modified with local ports and should not be merged.
- Compatibility: No FE/BE protocol, symbol, or storage-format compatibility issue found in the pruning logic.
- Parallel code paths: I traced the collector ->
NestedColumnPruning-> slot replacement -> BE map/array/struct iterator flow. The new path-rewrite logic is consistent with BE's distinction between*,KEYS,VALUES, andOFFSET. - Special conditionals: The new type-aware
compareOffsetPrefixCoverage()logic is justified for the affected map/array cases. - Test coverage: Coverage for the newly fixed select-list cases is good in both FE UT and regression tests. I did not find another concrete blocker in the code path.
- Test result files: The new
.outfiles are consistent with the neworder_qtcases. - Observability: Not needed for this optimizer-side fix.
- Transaction / persistence / data write / FE-BE variable passing: Not applicable here.
- Performance: The additional per-slot path comparison work is small and acceptable for the expected number of collected access paths.
- Other issues: None beyond the accidental config check-in.
| // add allowLoadLocalInfile so that the jdbc can execute mysql load data from client. | ||
| jdbcUrl = "jdbc:mysql://127.0.0.1:9030/?useLocalSessionState=true&allowLoadLocalInfile=true&zeroDateTimeBehavior=round" | ||
| targetJdbcUrl = "jdbc:mysql://127.0.0.1:9030/?useLocalSessionState=true&allowLoadLocalInfile=true&zeroDateTimeBehavior=round" | ||
| jdbcUrl = "jdbc:mysql://127.0.0.1:39131/?useLocalSessionState=true&allowLoadLocalInfile=true&zeroDateTimeBehavior=round" |
There was a problem hiding this comment.
This looks like worktree-local test setup rather than PR content. regression-test/conf/regression-conf.groovy is the shared default harness config, and AGENTS.md explicitly says env/run config changes should not be committed. If this lands, CI and other developers will start targeting 39131/39121/38131 instead of the repo defaults. Please drop this file from the PR.
FE UT Coverage ReportIncrement line coverage |
FE Regression Coverage ReportIncrement line coverage |
|
run p0 |
|
run cloud_p0 |
|
run nonConcurrent |
FE Regression Coverage ReportIncrement line coverage |
1 similar comment
FE Regression Coverage ReportIncrement line coverage |
|
run cloud_p0 |
|
/review |
FE Regression Coverage ReportIncrement line coverage |
|
run buildall |
FE Regression Coverage ReportIncrement line coverage |
FE Regression Coverage ReportIncrement line coverage |
2 similar comments
FE Regression Coverage ReportIncrement line coverage |
FE Regression Coverage ReportIncrement line coverage |
e10d5f1 to
cf12c43
Compare
|
run buildall |
FE UT Coverage ReportIncrement line coverage |
FE Regression Coverage ReportIncrement line coverage |
|
PR approved by at least one committer and no changes requested. |
…t> (apache#62631) ### What problem does this PR solve? Issue Number: close #xxx Related PR: apache#62205 Problem Summary: PR apache#62205 introduced the `length(str)` OFFSET optimization for nested column pruning. When `length()` is applied to a string-like nested field, the pruner marks the access path with an OFFSET suffix so BE can read only the offset array instead of full element data. However, the OFFSET path dedup logic operates at **slot granularity**: if ANY non-OFFSET path exists for the same slot, ALL OFFSET paths are stripped. This is incorrect for `array<struct<...>>` columns where different struct fields have independent access patterns. **Bug 1 (crash):** Given a query like: ```sql SELECT array_match_all(x -> length(struct_element(x, 'str_field')) > 0, arr), struct_element(element_at(arr, 1), 'int_field') FROM t ``` The `int_field` non-OFFSET path causes the `str_field` OFFSET path to be stripped. BE never reads `str_field` data → crash or wrong results. **Fix:** Replace slot-level OFFSET dedup with per-field bidirectional prefix matching. An OFFSET path `P+["OFFSET"]` is only stripped when a non-OFFSET path Q shares a prefix relationship with P (covering the same container or ancestor), not when Q accesses a sibling struct field. **Bug 2 (wrong pruning):** When `isStringOffsetOnly=true` AND `accessPartialChild=true` (e.g., `cardinality(arr) + arr[*].f1`), `pruneDataType()` returns the full type instead of pruning unused fields. **Fix:** Add `!accessPartialChild` guard to the `isStringOffsetOnly` check. ### Release note Fix a BE crash caused by nested column pruning incorrectly stripping OFFSET access paths for array<struct> columns when different struct fields have independent access patterns (e.g., length() on one field + direct access on another). Co-authored-by: Copilot <[email protected]>
…t> (apache#62631) ### What problem does this PR solve? Issue Number: close #xxx Related PR: apache#62205 Problem Summary: PR apache#62205 introduced the `length(str)` OFFSET optimization for nested column pruning. When `length()` is applied to a string-like nested field, the pruner marks the access path with an OFFSET suffix so BE can read only the offset array instead of full element data. However, the OFFSET path dedup logic operates at **slot granularity**: if ANY non-OFFSET path exists for the same slot, ALL OFFSET paths are stripped. This is incorrect for `array<struct<...>>` columns where different struct fields have independent access patterns. **Bug 1 (crash):** Given a query like: ```sql SELECT array_match_all(x -> length(struct_element(x, 'str_field')) > 0, arr), struct_element(element_at(arr, 1), 'int_field') FROM t ``` The `int_field` non-OFFSET path causes the `str_field` OFFSET path to be stripped. BE never reads `str_field` data → crash or wrong results. **Fix:** Replace slot-level OFFSET dedup with per-field bidirectional prefix matching. An OFFSET path `P+["OFFSET"]` is only stripped when a non-OFFSET path Q shares a prefix relationship with P (covering the same container or ancestor), not when Q accesses a sibling struct field. **Bug 2 (wrong pruning):** When `isStringOffsetOnly=true` AND `accessPartialChild=true` (e.g., `cardinality(arr) + arr[*].f1`), `pruneDataType()` returns the full type instead of pruning unused fields. **Fix:** Add `!accessPartialChild` guard to the `isStringOffsetOnly` check. ### Release note Fix a BE crash caused by nested column pruning incorrectly stripping OFFSET access paths for array<struct> columns when different struct fields have independent access patterns (e.g., length() on one field + direct access on another).
…array<struct> (apache#62631) ### What problem does this PR solve? Issue Number: close #xxx Related PR: apache#62205 Problem Summary: PR apache#62205 introduced the `length(str)` OFFSET optimization for nested column pruning. When `length()` is applied to a string-like nested field, the pruner marks the access path with an OFFSET suffix so BE can read only the offset array instead of full element data. However, the OFFSET path dedup logic operates at **slot granularity**: if ANY non-OFFSET path exists for the same slot, ALL OFFSET paths are stripped. This is incorrect for `array<struct<...>>` columns where different struct fields have independent access patterns. **Bug 1 (crash):** Given a query like: ```sql SELECT array_match_all(x -> length(struct_element(x, 'str_field')) > 0, arr), struct_element(element_at(arr, 1), 'int_field') FROM t ``` The `int_field` non-OFFSET path causes the `str_field` OFFSET path to be stripped. BE never reads `str_field` data → crash or wrong results. **Fix:** Replace slot-level OFFSET dedup with per-field bidirectional prefix matching. An OFFSET path `P+["OFFSET"]` is only stripped when a non-OFFSET path Q shares a prefix relationship with P (covering the same container or ancestor), not when Q accesses a sibling struct field. **Bug 2 (wrong pruning):** When `isStringOffsetOnly=true` AND `accessPartialChild=true` (e.g., `cardinality(arr) + arr[*].f1`), `pruneDataType()` returns the full type instead of pruning unused fields. **Fix:** Add `!accessPartialChild` guard to the `isStringOffsetOnly` check. ### Release note Fix a BE crash caused by nested column pruning incorrectly stripping OFFSET access paths for array<struct> columns when different struct fields have independent access patterns (e.g., length() on one field + direct access on another).
… path series (#68214) Cherry-pick of #62205, #62315, #62631, #62304, #63229, #63736, #64486 to branch-4.2 (the meta path series, applied in merge order). Adaptations for branch-4.2: * `AccessPathInfo` already lives in `org.apache.doris.analysis` on this branch; the new `ACCESS_STRING_OFFSET` constant was ported there (aliasing the existing `ACCESS_OFFSET`). * master-only types renamed to this branch's thrift types (`ColumnAccessPath* -> TColumnAccessPath`, `ColumnAccessPathType -> TAccessPathType`); `NestedColumnPruning` keeps branch-4.2's `comparePathSegments`/variant branch alongside upstream's new logic. * `LazyMaterializeTopN` merges upstream's restructure while preserving branch-4.2's Lance lazy-materialization gate; `LogicalJoin.getRightConditionSlot()` (a master-only helper) had to be added for the ported `PushDownTopNThroughJoin`. * `StringEmptyToLengthRuleTest` uses `ElementAt` (branch-4.2 folded `StructElement` into `ElementAt`); the regression suite expectation for the same reason. REQUIRED EXTRA COMMIT: the FE now emits offset-only / null-only nested access paths, which branch-4.2's BE could not read — `null_column_pruning.groovy` aborted the BE with `std::vector<...ColumnIterator>::operator[]: Assertion '__n < this->size()' failed`. This PR therefore also cherry-picks the BE-side prerequisite #61888 "[Exec](be) Support offset prue column and null column in BE" (adapted to branch-4.2's `IColumn::mutate`/Defer COW shape and `ColumnNullable::get_null_map_column_ptr()` signature). Please review that extra commit carefully. Testing on branch-4.2 (FE + BE rebuilt from this branch): * FE unit tests: PruneNestedColumnTest (57), PullUpProjectExprUnderTopNTest (28), OperativeColumnDeriveTest (4), StringEmptyToLengthRuleTest (11), MaterializeProbeVisitorTest (8) — 108 tests, 0 failures. * Regression: nereids_rules_p0/column_pruning (7 suites incl. the new string_length/null/nested_container_offset/topn_expr_pullup/topn_lazy_nested suites) 0 failures; shape_check clickbench/tpcds_sf100/tpcds_sf1000/tpcds_sf10t_orc/tpcds_sf1000_constraints suites touched by the series regenerated with -forceGenOut and green; nereids_rules_p0 push_filter_through / null_un_safe_equals / lazy_materialize_topn green. * Known deviations: #62446 (struct field slot type for OFFSET-only access) is not part of the requested series and was not included; the BE-side `MapAccessAllWithOffsetDoesNotPropagateOffsetToKey` unit test from #61888 was kept byte-identical to upstream although it may not hold (test binary not built with MAKE_TEST=ON). --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Claude Opus 4.7 <[email protected]> Co-authored-by: HappenLee <[email protected]>
…-named nested fields (#65805) (#68314) ### What problem does this PR solve? Cherry-pick of #65805 (`[fix](nereids) Disambiguate NULL/OFFSET metadata from same-named nested fields`) to `branch-4.2`. The port also carries the two prerequisite commits that #65805 is written on top of, because branch-4.2 stopped at the state just before them: | commit | PR | why it is needed | | --- | --- | --- | | `branch-4.2 [fix](NestedColumnPruning) Rewrite the NestedColumnPruning meta-path handling ...` | #64535 | branch-4.2's FE pruning code was exactly master-before-#64535; #65805's FE hunks are written against the rewritten meta-path handling. | | `branch-4.2 [feat](olap) Support lazy reading mode for pruned complex columns` | #59263 | on master #59263 replaced #64535's `MetaPathStriper` approach again (predicate metadata paths are kept, `normalizePredicateMetaPathForAllAccessPath` / `addPredicatePathsToFinalAllAccessPaths` / `expandMapStarPaths`), and that is the code #65805 patches. | | `branch-4.2 [fix](nereids) Disambiguate NULL/OFFSET metadata from same-named nested fields` | #65805 | the requested pick. | Master order for reference: `#61888 → #62205 → #62315 → #62631 → #62304 → #63229 → #63736 → #64486 → #64535 → #59263 → … → #65805`; branch-4.2 already had everything up to #64486 (ported by #68214). ### What #65805 fixes * **Versioned, type-selected access paths.** `TColumnAccessPath` gets an optional `version` (`TCOLUMN_ACCESS_PATH_VERSION_LEGACY = 0`, `TCOLUMN_ACCESS_PATH_VERSION_TYPED = 1`) that is carried through thrift, protobuf and both FE/BE descriptor conversions. In the typed format the path type is authoritative: `DATA` selects `data_access_path`, `META` selects `meta_access_path`, and `NULL`/`OFFSET` are only ever emitted as typed `META` paths. New BEs still decode the legacy all-`DATA` encoding, so the supported rolling upgrade is: upgrade all BEs first, then let FEs send typed paths. * **FE.** Marks `NULL`/`OFFSET` collector contexts as `META`; checks the physical column's nullability instead of slot nullability, so an outer-join-nullable `NOT NULL` column no longer gets a `[col, NULL]` path; falls back to plain data reads for variant sub-columns; keeps the exact meta path when a sibling data path is also read; and keys pruning on the path type instead of string suffixes. * **BE.** Validates path versions and type-selected payloads; partitions current data / current metadata / descendant routing with explicit per-container ownership; derives `NULL_MAP_ONLY` / `OFFSET_ONLY` only when no current or predicate data path requires payload; keeps sibling `META` paths explicit; routes Map children by the logical `KEYS`/`VALUES` selectors instead of physical child column names; and rejects unrecognized Map selectors instead of silently pruning everything. ### branch-4.2 adaptations * branch-4.2 keeps the FE access-path model as thrift types instead of master's `ColumnAccessPath` / `DescriptorToThriftConverter`, so the typed version is stamped in `NestedColumnPruning.buildColumnAccessPaths` (the branch's single producer) and preserved by `SlotTypeReplacer.replaceIcebergAccessPathToId`. * The master-only `DescriptorToThriftConverter` / `DescriptorToThriftConverterTest` files do not exist on this branch and were not added; the equivalent coverage lives in `PruneNestedColumnTest`, whose `path()` / `metaPath()` helpers (and `VariantPruningLogicTest`'s) now carry the typed version. * Ported FE code was mapped onto this branch's names (`TAccessPathType`, `TColumnAccessPath`, `isFunctionNullCheckPath`) and keeps the segment-wise access-path comparator introduced by the branch's nested-column-pruning port. * `gensrc/thrift/PaloInternalService.thrift`: master uses field id `226` for `enable_prune_nested_column`, but branch-4.2 already uses `226`-`228`, so the option was moved to the next free id (`229`). * `segment_iterator.cpp` keeps branch-4.2's `_non_predicate_column_ids` naming and drops the master-only `_update_lsn_col_if_needed` / `_update_tso_col_if_needed` calls from the ported hunk; branch-4.2 keeps its own `common/compile_check_begin.h` include in front of the new `ScopedColumnIteratorReadPhase` helper. `_read_lazy_pruned_columns` uses the branch's `_schema_block_id_map` because master's `Schema::column_index()` does not exist here, and `FileColumnIterator::get_reader()` was added as in master. * Regression suites `lambda_null_pruning` and `map_contains_arg_pruning`, and the unit tests for lambda access paths / Map lazy-read-by-rowids, only exist on master (they come from PRs outside this chain) and were not added. ### Test * FE: `./build.sh --fe` OK. `./run-fe-ut.sh --run 'org.apache.doris.nereids.rules.rewrite.PruneNestedColumnTest'` → 63 passed; `VariantPruningLogicTest` 14, `IcebergScanNodeTest` 95, `MaterializeProbeVisitorTest` 8, `StringEmptyToLengthRuleTest`, `PullUpProjectExprUnderTopNTest` — all green. * BE: `./build.sh --be` OK (the two ported BE test sources were additionally checked with the unit-test flags, `-DBE_TEST -fno-access-control`). * Regression: `./run-regression-test.sh --run -d nereids_rules_p0/column_pruning` → 8 suites, 0 failed, including the new `left_join_not_null_column` suite that guards the outer-join null-map crash. ### Behavior changed Yes. Typed `META` access paths with an explicit version replace the legacy all-`DATA` encoding between new FEs and BEs (upgrade BEs before FEs); struct fields literally named `NULL`/`OFFSET` are now read correctly instead of being pruned as metadata; `IS NULL` on an outer-join-nullable `NOT NULL` column no longer crashes the BE; invalid Map descendant selectors now return an internal error instead of being silently ignored. The port also brings #59263, which defers reading non-predicate pruned complex columns until after predicate evaluation, reducing nested-column I/O. ### Release note None ### Rebased onto branch-4.2 (2026-09-23) The branch was rebased onto the current `branch-4.2` (`c0cbcc3e49c`) and is now a single commit `e044385682f`. The two prerequisite ports listed above are folded into it: branch-4.2 has since gained its own nested-column-pruning series (`#68214`) and the `#68331` predicate-path fix, and `#59263` had already replaced the `#64535` `MetaPathStriper` design inside this pick, so replaying the intermediate states is no longer meaningful. See the comment below for the conflict-resolution details and the verification results.
What problem does this PR solve?
Issue Number: close #xxx
Related PR: #62205
Problem Summary:
PR #62205 introduced the
length(str)OFFSET optimization for nested column pruning. Whenlength()is applied to a string-like nested field, the pruner marks the access path with an OFFSET suffix so BE can read only the offset array instead of full element data.However, the OFFSET path dedup logic operates at slot granularity: if ANY non-OFFSET path exists for the same slot, ALL OFFSET paths are stripped. This is incorrect for
array<struct<...>>columns where different struct fields have independent access patterns.Bug 1 (crash): Given a query like:
The
int_fieldnon-OFFSET path causes thestr_fieldOFFSET path to be stripped. BE never readsstr_fielddata → crash or wrong results.Fix: Replace slot-level OFFSET dedup with per-field bidirectional prefix matching. An OFFSET path
P+["OFFSET"]is only stripped when a non-OFFSET path Q shares a prefix relationship with P (covering the same container or ancestor), not when Q accesses a sibling struct field.Bug 2 (wrong pruning): When
isStringOffsetOnly=trueANDaccessPartialChild=true(e.g.,cardinality(arr) + arr[*].f1),pruneDataType()returns the full type instead of pruning unused fields.Fix: Add
!accessPartialChildguard to theisStringOffsetOnlycheck.Release note
Fix a BE crash caused by nested column pruning incorrectly stripping OFFSET access paths for array columns when different struct fields have independent access patterns (e.g., length() on one field + direct access on another).
Check List (For Author)
What problem does this PR solve?
Issue Number: close #xxx
Related PR: #xxx
Problem Summary:
Release note
None
Check List (For Author)
Test
Behavior changed:
Does this need documentation?
Check List (For Reviewer who merge this PR)