morningman opened a new pull request, #69016:
URL: https://github.com/apache/doris/pull/69016

   ### What problem does this PR solve?
   
   Issue Number: None
   
   Related PR: #66575, #67022, #66413, #67675
   
   Problem Summary:
   
   **In short.** This ports branch-4.1 #66575 and its follow-up #67022 to 
master. The native Parquet reader of File Scanner V2 can now read a deep 
Variant object path such as `payload['profile']['address']['zip']` from its 
shredded leaf instead of rebuilding the whole Variant, and it decides this per 
row group instead of once per file. On the FE side, `explode` over an ordinary 
Variant keeps the whole input container in its access path, while a NestedGroup 
Variant keeps the exact leaf paths that NestedGroup storage needs.
   
   **Background.** A shredded Parquet Variant stores each shredded field as a 
wrapper with a `typed_value` (the field in its own Parquet column) and a 
`value` (a binary residual for rows whose value does not fit the shredded 
type). FE sends BE the paths a query reads as lists of segments, and BE can 
then read only the leaves of those paths. On master that projection is limited:
   
   - `column_mapper` projects only a single top-level key, so a path of two or 
more object keys reads the whole Variant root and rebuilds every row.
   - `AccessPathParser` has a separate branch for a root Variant that sorts and 
flattens its paths instead of merging them in the shared access-path tree.
   - `ParquetReader::open` keeps a projection only when every `value` along the 
path is NULL in the whole file; otherwise it rewrites the request to the full 
column for every row group of the file.
   - `AccessPathPlanCollector` narrows the input of `explode(v['arr'])` to the 
fields read from the output (`[v, arr, x]`). A generator can change the 
container kind of a Variant, which such a path cannot express; once BE projects 
multi-segment paths, the narrowed path could hide residual values of those 
rows. That is why #66575 changes FE and BE together.
   
   **The problem, and what it cost.**
   
   | Reading `payload['profile']['address']['zip']` and `['rank']` from a 
shredded Paimon table with the native reader | master | with this PR |
   |---|---|---|
   | Access paths FE sends | `[payload.profile.address.rank, 
payload.profile.address.zip]` | the same |
   | What BE reads | the whole `payload` Variant, rebuilt row by row: 
`VariantLeafProjections 0`, `VariantReconstructedRows 2` on the 2-row fixture | 
the `address` leaves: `VariantLeafProjectionRowGroupColumns 2`, 
`VariantFullProjectionRowGroupColumns 0`, `VariantReconstructedRows 0` |
   | A file where one row group has residual values on the path | every row 
group of the file reads the whole Variant | only that row group does |
   
   **How this PR fixes it.**
   
   - BE, `access_path_parser.cpp`: a root Variant goes through the generic 
`AccessPathNode` tree, which merges sibling and prefix paths (`[profile]` 
absorbs `[profile, city]`); a `project_all` node clears the Variant paths.
   - BE, `column_mapper.cpp`: `build_variant_leaf_path_projection` follows 
object paths of any depth. An empty path, a missing file column or a numeric 
segment (an array index; TODO: typed path segments) still reads the whole 
Variant, and every intermediate `typed_value` must be a STRUCT, so a path 
cannot cross a repeated node.
   - BE, `parquet_reader.cpp` / `parquet_scan.cpp`: `open` only counts the 
candidate projections (`VariantLeafProjections`). Each row group decides on its 
own: the projection is kept when the `value` beside each projected terminal 
leaf is all NULL in that row group, otherwise that row group reads the complete 
Variant. The decision is made when the row group plan is built 
(`prepare_row_group_physical_projection`) and the scheduler opens the row group 
with the resulting physical request; footer pruning and the filtered-bytes 
statistics use the leaf set of each row group. The reader and the scheduler now 
share one `RowGroupScanPlan`. Two counters report the outcome per opened 
column: `VariantLeafProjectionRowGroupColumns` and 
`VariantFullProjectionRowGroupColumns`.
   - BE, `variant_column_reader.cpp`: when a projected Variant is asked for a 
complex prefix of a validated leaf (a binary `element_at` chain), it serializes 
only the retained descendants, so a field that was projected out never becomes 
visible.
   - FE, `AccessPathPlanCollector`: `explode` / `explode_outer` over an 
ordinary Variant collects the whole input container, for both the unary and the 
multi-argument form (#66575). Over a NestedGroup Variant it keeps the exact 
suffixes (#67022).
   
   Two places differ from branch-4.x on purpose:
   
   - Statistics pruning keeps master's rule from #66413: a shredded leaf's 
min/max prunes a row group or page only when the `value` of every wrapper on 
the path is all NULL (`ShreddedVariantAncestorFallbackDisablesPruning`). 
branch-4.x only checks the terminal `value`; the three `parquet_statistics.cpp` 
hunks of #66575 that switch to that rule are not included. The tests of #66575 
pass under master's rule.
   - master #67675 makes `element_at` keep only the max sub-column count of its 
Variant type, so `v['arr']` no longer carries the NestedGroup flag. A straight 
port of #67022 would send `[v, arr]` instead of `[v, arr, x]` for a NestedGroup 
column whenever the explode argument is still an `element_at` (sub-path pruning 
disabled, or the load path). `isNestedGroupVariant` follows the `element_at` 
chain to the column it reads instead, and 
`testExplodeNestedGroupVariantAccessPathsWithoutSubPathPruning` covers that 
case.
   
   The residual-column pruning that branch-4.x added on top of these files 
later (#66941) is not part of this PR.
   
   **Results.**
   
   - BE UT: the six test files of #66575 pass, 376 tests 
(`AccessPathParserTest`, `ColumnMapper*`, `NewParquetReaderTest`, 
`FileReaderTest`, `ParquetVariantProjectionTest`, `ParquetScan*`, 
`ParquetRuntimeFilterDirectReaderTest`, `NativeParquetStatisticsTest`, 
`ParquetBloomFilterPruningTest`, `VariantColumnReaderTest`). Limiting the leaf 
projection to one segment again fails 
`ColumnMapperTest.VariantDeepObjectPathProjectsPhysicalTypedLeaf`; making every 
row group read the complete Variant fails 5 tests 
(`SwitchesVariantLeafProjectionPerRowGroup`, 
`ReadsFullyShreddedVariantTypedLeafProjection` and three 
`ParquetVariantProjectionTest` cases).
   - FE UT: `PruneNestedColumnTest` (71) and `VariantPruningLogicTest` (17) 
pass. With master's `AccessPathPlanCollector`, exactly 12 fail (the 11 changed 
expectations and `testMultiArgumentExplodePreservesVariantContainers`); without 
the NestedGroup check, the 3 NestedGroup cases fail; a straight port of #67022 
fails only `testExplodeNestedGroupVariantAccessPathsWithoutSubPathPruning`, 
which passes on master.
   - Regression: `test_paimon_catalog_variant` and `test_iceberg_variant_read` 
pass against the iceberg docker environment, and their `.out` files were 
generated by the script. The Paimon suite's profile check (leaf projections > 
0, full projections = 0) also shows that the docker Spark with Paimon 1.3.1 
writes the three-level shredding schema of `run13.sql`.
   
   Original authors: @Gabriel39 (#66575), @eldenmoon (#67022)
   
   ### Release note
   
   Iceberg and Paimon native Parquet scans read deep shredded Variant object 
paths from their leaves, deciding per row group, instead of rebuilding the 
whole Variant.
   
   ### Check List (For Author)
   
   - Test <!-- At least one of them must be included. -->
       - [x] Regression test
           - `test_paimon_catalog_variant` (deep projection results and profile 
counters on the shredded fixture) and `test_iceberg_variant_read` (a deep path 
on an unshredded table); `.out` files generated by the script.
       - [x] Unit Test
           - BE: the six test files of #66575 (376 tests). FE: 
`PruneNestedColumnTest`, `VariantPruningLogicTest`, including a NestedGroup 
case with sub-path pruning disabled.
       - [ ] Manual test (add detailed scripts or steps below)
       - [ ] No need to test or manual test. Explain why:
           - [ ] This is a refactor/code format and no logic has been changed.
           - [ ] Previous test can cover this change.
           - [ ] No code files have been changed.
           - [ ] Other reason <!-- Add your reason?  -->
   
   - Behavior changed:
       - [ ] No.
       - [x] Yes. <!-- Explain the behavior change -->
           - Deep shredded Variant object paths are read from their leaves, per 
row group. `VariantLeafProjections` now counts candidate projections, and two 
new counters report the per-row-group outcome.
           - `explode` over an ordinary Variant column reads the whole input 
container instead of the output-field leaves.
   
   - Does this need documentation?
       - [x] No.
       - [ ] Yes. <!-- Add document PR link here. eg: 
https://github.com/apache/doris-website/pull/1214 -->
   
   ### Check List (For Reviewer who merge this PR)
   
   - [ ] Confirm the release note
   - [ ] Confirm test cases
   - [ ] Confirm document
   - [ ] Add branch pick label <!-- Add branch pick label that this PR should 
merge into -->
   


-- 
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