Skip to content

[fix](be) Harden external reader filtering, statistics, and nested Parquet reads#65500

Merged
Gabriel39 merged 23 commits into
apache:masterfrom
Gabriel39:agent/combine-parquet-statistics-fixes
Jul 13, 2026
Merged

[fix](be) Harden external reader filtering, statistics, and nested Parquet reads#65500
Gabriel39 merged 23 commits into
apache:masterfrom
Gabriel39:agent/combine-parquet-statistics-fixes

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: Supersedes #65471, #65489, #65490, #65493, #65494, #65497, #65498, and #65499.

Problem Summary: FileScannerV2 and its Parquet/ORC readers had several correctness and resource-control gaps:

  • late or pending runtime filters were not consistently reflected in split readers and could race with irreversible table/file COUNT and MIN/MAX shortcuts;
  • slotless or unsafe conjuncts could be omitted while split pruning, file-level predicate localization, or aggregate pushdown continued past them;
  • direct and nested VARBINARY filters, lossy schema-evolution casts, and TIMESTAMPTZ scale mismatches could be evaluated below the scanner with semantics different from the table expression;
  • missing, inverted, NaN, timezone-non-monotonic, truncated binary, or incorrectly cached Parquet/ORC statistics could drive unsafe pruning or synthetic aggregate results;
  • LIST, MAP, and STRUCT skips materialized discarded ranges and decoded unused leaf payloads, while nested binary builders could retain stale state between level batches;
  • direct slot-ref projections were treated as unsafe conversions, unnecessarily disabling valid MIN/MAX pushdown.

This PR consolidates the fixes into one external-reader hardening change. It refreshes predicates for every split, waits for runtime-filter completion before aggregate shortcuts, disables aggregate pushdown whenever original conjuncts remain, preserves only safe conjunct prefixes, and keeps VARBINARY and TIMESTAMPTZ scale-mismatch filters above the file reader. It also validates all decoded ORC statistics bounds, makes invalid or inexact Parquet/ORC statistics fall back conservatively, scopes Bloom filters by row group and column, permits MIN/MAX only for order-preserving trivial mappings, and processes nested Parquet skips in bounded levels-only batches with correct binary-builder reset.

Release note

Fix incorrect external-file filtering, partition/statistics pruning, and COUNT/MIN/MAX pushdown. Unsafe predicates or statistics now fall back to row-level scanning, late runtime filters use current split state, and nested Parquet skips use bounded memory without decoding discarded payloads.

Check List (For Author)

  • Test: Unit Test
    • Remote ASAN BE unit tests on gabriel@10.26.20.3: combined ColumnMapper, TableReader, and Parquet reader suite (121 tests passed).
    • Remote ASAN BE unit tests for the latest review fixes: TIMESTAMPTZ scale mismatch, nested VARBINARY, slotless aggregate gating, ORC decoded-bound validation, pending runtime filters, and ORC aggregate pushdown (20 tests passed).
    • Remote build-support/check-format.sh with clang-format 16 passed across 4,237 C/C++ files.
    • The Iceberg external regression was attempted but skipped because the remote validation configuration does not enable Iceberg tests.
    • clang-tidy was attempted with the generated compilation database but was blocked by the remote toolchain's missing stddef.h and pre-existing diagnostics outside these changes.
  • Behavior changed: Yes. Unsafe filtering and metadata shortcuts now fall back to row-level evaluation, runtime filters use current split state, and nested Parquet skips use bounded memory.
  • Does this need documentation: No.

### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: FileScannerV2 treated a missing optional Parquet null_count as proof that a row group contained no NULL values. When min/max statistics were present, IS NULL predicates could therefore prune a row group that actually contained NULL rows. Treat unknown null counts conservatively as possibly containing NULL while retaining available min/max statistics, and cover the metadata combination with a unit test.

### Release note

Fix incorrect IS NULL results for Parquet files whose writers omit null_count statistics.

### Check List (For Author)

- Test: Unit Test
    - ParquetStatistics* BE unit tests
- Behavior changed: Yes, row groups with unknown Parquet null counts are no longer discarded by IS NULL pruning
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#64456

Problem Summary: FileScannerV2 converts UTC timestamp statistics into session-local DATETIMEV2 values before ZoneMap evaluation. Across a daylight-saving time rollback, that conversion is not monotonic, so converted metadata endpoints may exclude local values that occur inside the UTC interval. Parquet row-group and page-index pruning, and ORC statistics pruning, could therefore skip matching data. Detect backward timezone transitions within UTC statistics ranges and conservatively disable min/max pruning for those ranges while retaining statistics for monotonic ranges and TIMESTAMPTZ mappings.

### Release note

Fix incorrect Parquet and ORC file scan pruning for UTC timestamps across daylight-saving time rollbacks.

### Check List (For Author)

- Test: Unit Test
    - Remote ASAN BE UT: TimestampStatisticsTest.*, ParquetStatisticsTransformTest.*, and NewOrcReaderTest.AggregatePushdownTimestamp* (7 passed)
- Behavior changed: Yes. Non-monotonic local timestamp statistics no longer prune rows or pages.
- Does this need documentation: No
Disable unsafe ORC timestamp SARG pushdown in civil years with backward timezone transitions, while preserving Parquet min/max pruning for TIMESTAMPTZ values that retain UTC ordering. Add ORC, Parquet, and timezone transition regression coverage.
### What problem does this PR solve?

Issue Number: N/A

Related PR: N/A

Problem Summary: Parquet row-group pruning cached Bloom filters only by leaf column id. After the first row group loaded a column Bloom filter, later row groups reused that filter and could be incorrectly pruned when a matching value existed only outside the first row group. Scope cache entries by both row group and leaf column so absence checks always use the current row group's Bloom filter. Add a parquet-mr fixture with two row groups and real Bloom data plus an enabled/disabled differential unit test.

### Release note

Fix incorrect Parquet Bloom filter pruning that could silently omit rows from later row groups.

### Check List (For Author)

- Test: Unit Test
    - ParquetStatisticsPruningTest.BloomFilterCacheIsScopedToRowGroupAndColumn
- Behavior changed: Yes, Parquet Bloom pruning now uses each row group's own filter
- Does this need documentation: No
Issue Number: N/A

Related PR: N/A

Problem Summary: Parquet FLOAT and DOUBLE min/max statistics containing NaN were treated as valid by FileScannerV2. Doris comparisons can then interpret NaN as equal to predicate bounds, causing range and inequality predicates to incorrectly prune row groups or pages that contain matching finite values. Reject footer and ColumnIndex min/max statistics when either floating-point bound is NaN, while preserving null-count information and finite statistics.

Fix missing rows when Parquet floating-point min/max statistics contain NaN.

- Test: Unit Test
    - ParquetStatisticsTransformTest targeted NaN footer and ColumnIndex tests
- Behavior changed: Yes, NaN floating-point min/max statistics are ignored for pruning.
- Does this need documentation: No
Issue Number: N/A

Related PR: apache#65493

Problem Summary: A NaN min/max in one Parquet ColumnIndex page was reported as a transform failure, which disabled page-index pruning for the entire slot. Treat NaN bounds as unavailable only for that page so it is kept conservatively while later pages with finite bounds remain eligible for pruning.

Preserve Parquet page-index pruning for finite pages when another page has NaN statistics.

- Test: Unit Test
    - ParquetStatisticsTransformTest.* (4 tests passed)
- Behavior changed: Yes. NaN page bounds no longer disable pruning for other pages in the slot.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#65493

Problem Summary: When NaN invalidated Parquet min/max bounds, the statistics adapter returned no zonemap and discarded otherwise valid null-count metadata. Build a pass-all zonemap instead so range predicates remain conservative while IS NULL and IS NOT NULL can still prune from has_null and has_not_null for both footer and page-index statistics.

### Release note

Preserve Parquet null-predicate pruning when floating-point min/max bounds contain NaN.

### Check List (For Author)

- Test: Unit Test
    - ParquetStatisticsTransformTest.* (5 tests passed)
- Behavior changed: Yes. Invalid range bounds no longer disable valid null-count pruning.
- Does this need documentation: No
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review completed for PR 65500 at head 6f4be2243b4fa9c3fe509b46daf961d7652fc932.

I did not find a substantiated correctness issue requiring an inline comment.

Critical checkpoint conclusions:

  • Goal and tests: the PR hardens Parquet/ORC metadata pruning for missing null counts, DST rollback timestamp ranges, row-group bloom-filter cache scoping, and NaN min/max statistics. The changed code matches that goal in the reviewed paths, and targeted BE tests were added for each failure mode.
  • Scope and focus: the changes are focused on BE format_v2 statistics/SARG pruning and matching unit tests.
  • Data correctness: unsafe timestamp or NaN bounds fall back to no range pruning or aggregate pushdown failure; null-count-only pruning preserves only trustworthy IS NULL / IS NOT NULL decisions; bloom filters are now keyed by row group and leaf column.
  • Parallel paths: Parquet footer statistics, page-index statistics, aggregate min/max, ORC zone maps, ORC SARG construction, and TIMESTAMPTZ bypass paths were checked.
  • Concurrency, lifecycle, config, persistence, and protocol compatibility: no new concurrency, persistent metadata, runtime config, FE/BE protocol, or storage-format compatibility path is introduced by this PR.
  • Performance and observability: the timestamp rollback guard intentionally trades some metadata pruning in rollback years for correctness; no new observability requirement was identified.
  • Test coverage: the added tests cover the changed behaviors statically. I did not run builds or tests because the review prompt forbids builds in this runner; the exact base commit is also not present locally, so git diff --check on the commit range could not run. I checked the authoritative bundled patch for added-line trailing whitespace instead.
  • User focus: no additional user-provided review focus was supplied.

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65449

Problem Summary: FileScannerV2 split-level partition pruning classified predicates only by their referenced slots. A predicate could therefore reference only partition columns while also containing a zero-slot non-deterministic or error-preserving expression. Evaluating such a predicate once before reading the split could change its normal row-level semantics and incorrectly prune the whole file. Check the unwrapped predicate's existing safe-to-reorder contract before evaluating it at split level, and add unit coverage proving an unsafe runtime-filter predicate is neither executed nor used to prune a split.

### Release note

Prevent FileScannerV2 partition pruning from pre-evaluating non-deterministic or error-preserving predicates.

### Check List (For Author)

- Test: Unit Test
    - `TableReaderTest.PrepareSplit*` (2 tests passed)
    - `build-support/clang-format.sh`
    - `build-support/check-format.sh`
    - `git diff --check`
- Behavior changed: Yes. Unsafe predicates remain on the normal row-level evaluation path instead of being evaluated once per split.
- Does this need documentation: No
Issue Number: None

Related PR: apache#65449

Problem Summary: Split pruning could skip an unsafe conjunct and still pre-execute later predicates. A later predicate could then prune a split before the earlier non-deterministic or error-preserving predicate reached its normal row-level evaluation point, changing conjunct ordering semantics. Preserve only the safe prefix for V2 partition pruning, V2 constant pruning, and the legacy FileScanner runtime-filter partition pruning path. Add unit coverage for all three paths.

Split pruning now preserves row-level conjunct order when predicates are unsafe to pre-execute.

- Test: Unit Test
    - ASAN BE unit tests: TableReaderTest.PrepareSplit*, TableReaderTest.ConstantPruningStopsAtUnsafePredicate, FileScannerTest.PartitionPruningStopsAtUnsafePredicate
    - C++ format check
- Behavior changed: Yes. Split pruning stops at the first predicate that is unsafe to pre-execute.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: N/A

Related PR: N/A

Problem Summary: FileScannerV2 localized predicate literals to the file column type whenever conversion succeeded, even when the conversion changed the literal value. For example, localizing a DOUBLE predicate value of 1.5 to an INT file column changed the boundary to 1 and could make file-level filtering silently drop matching rows. Require an exact round trip through the file type before localizing a literal; otherwise, preserve table semantics by casting the file slot. Exact conversions such as BIGINT 1 to INT 1 remain localized.

### Release note

Fix FileScannerV2 predicate localization to avoid missing rows after lossy literal conversion.

### Check List (For Author)

- Test: Unit Test
    - ColumnMapperCastTest targeted literal localization tests
- Behavior changed: Yes, lossy literal conversions now fall back to casting the file slot.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#65490

Problem Summary: Round-trip equality alone can accept unsafe predicate localization when file values lose information during table materialization or when complex Field equality ignores nested contents. Restrict localization to scalar numeric file-to-table casts that preserve every file value, retain the exact literal round-trip check, and fall back to the table predicate for complex or unsupported casts.

### Release note

Prevent file predicate localization from dropping rows for lossy schema-evolution casts.

### Check List (For Author)

- Test: Unit Test
    - ColumnMapperCastTest (19 tests passed)
- Behavior changed: Yes. Unsafe literal localization now falls back to evaluating the table-typed predicate after materialization.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: N/A

Related PR: N/A

Problem Summary: FileScannerV2 returned Parquet footer min/max statistics as exact aggregate values. For BYTE_ARRAY and FIXED_LEN_BYTE_ARRAY columns, Parquet permits truncated lower and upper bounds, and Arrow 17 does not expose the exactness flags. MIN/MAX pushdown could therefore return a synthetic bound that does not occur in the data. Reject aggregate pushdown for these physical types so the upper reader falls back to a normal scan, while retaining exact numeric pushdown and safe statistics pruning.

### Release note

Fix incorrect Parquet MIN/MAX aggregate results for truncated binary statistics.

### Check List (For Author)

- Test: Unit Test
    - ParquetScanTest targeted binary rejection and numeric success tests
- Behavior changed: Yes, Parquet binary MIN/MAX aggregate pushdown now falls back to scanning.
- Does this need documentation: No
@Gabriel39 Gabriel39 changed the title [fix](be) Harden file statistics pruning for edge cases [fix](be) Harden FileScannerV2 predicate and statistics pruning Jul 12, 2026
@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found two correctness issues that need changes before merge.

Checkpoint summary:

  • Goal/test proof: the PR improves several pruning and metadata conservatism paths, but constant split pruning still has a safe-prefix gap and invalid timestamp metadata can still assert instead of falling back.
  • Scope/focus: the changes are focused on BE file scanner and format_v2 metadata paths; I did not find unrelated refactoring issues.
  • Parallel paths: legacy and V2 partition pruning now stop at unsafe predicates, but the constant-pruning path still iterates the reduced _table_filters list and misses slotless unsafe barriers.
  • Error handling/data quality: most missing, NaN, unsupported, and non-monotonic metadata cases fall back conservatively; inverted timestamp min/max is the remaining non-conservative case.
  • Tests/validation: tests were not run because the review prompt forbids builds/tests in this runner. .worktree_initialized and thirdparty/installed are missing, and git diff --check could not run because the base object is absent in this shallow/grafted checkout.
  • User focus: no additional user-provided focus points were present.
  • Convergence: all five subagents re-read the final two-comment set and returned NO_NEW_VALUABLE_FINDINGS.

Comment thread be/src/format_v2/timestamp_statistics.h Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anchor correction for MAIN-1. The original table-reader inline comment landed one line early and was removed; this comment keeps the same issue text on the intended line. The earlier review remains changes requested.

Comment thread be/src/format_v2/table_reader.h Outdated
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65500

Problem Summary: Corrupt external timestamp statistics with inverted bounds could terminate the BE, and a slotless unsafe conjunct could be omitted from localized table filters so a later constant predicate incorrectly pruned the split. Treat inverted statistics as unusable and track the constant-pruning safe prefix from the original conjunct order, including slotless ordering barriers.

### Release note

Corrupt timestamp statistics and unsafe slotless predicates now fall back to conservative scan behavior.

### Check List (For Author)

- Test: Regression test / Unit Test
    - TimestampStatisticsTest.*
    - TableReaderTest.ConstantPruningStopsAtUnsafePredicate
    - TableReaderTest.ConstantPruningStopsAtUnsafeSlotlessPredicate
    - Remote BE build
- Behavior changed: Yes (split pruning now falls back conservatively for corrupt timestamp statistics and unsafe slotless predicates)
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 179920 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit f5da27fff5332530f334a86e8a53a12692993799, data reload: false

query5	4352	632	475	475
query6	466	228	202	202
query7	4870	602	336	336
query8	333	187	171	171
query9	8770	4052	4036	4036
query10	479	342	291	291
query11	5914	2336	2152	2152
query12	163	107	100	100
query13	1313	627	456	456
query14	6186	5302	4971	4971
query14_1	4284	4266	4290	4266
query15	217	212	181	181
query16	1018	474	453	453
query17	941	743	585	585
query18	2436	474	360	360
query19	207	197	152	152
query20	113	111	108	108
query21	238	158	134	134
query22	13683	13560	13428	13428
query23	17480	16582	16116	16116
query23_1	16386	16273	16223	16223
query24	7443	1756	1314	1314
query24_1	1347	1306	1300	1300
query25	589	455	393	393
query26	1315	339	215	215
query27	2642	612	366	366
query28	4457	2044	2042	2042
query29	1079	652	501	501
query30	344	271	231	231
query31	1123	1096	998	998
query32	104	64	65	64
query33	555	334	256	256
query34	1195	1157	655	655
query35	773	781	671	671
query36	1399	1453	1240	1240
query37	154	100	89	89
query38	1879	1709	1642	1642
query39	915	929	899	899
query39_1	867	883	873	873
query40	247	160	134	134
query41	65	62	63	62
query42	98	93	93	93
query43	325	321	282	282
query44	1397	798	777	777
query45	200	192	174	174
query46	1111	1208	723	723
query47	2393	2333	2231	2231
query48	401	407	298	298
query49	579	444	303	303
query50	1134	441	338	338
query51	10991	10802	10772	10772
query52	88	86	73	73
query53	259	271	200	200
query54	270	225	213	213
query55	72	69	66	66
query56	302	270	270	270
query57	1442	1395	1321	1321
query58	274	251	250	250
query59	1558	1653	1416	1416
query60	301	264	256	256
query61	151	140	153	140
query62	701	648	584	584
query63	240	203	206	203
query64	2853	1078	884	884
query65	4838	4845	4762	4762
query66	1815	507	384	384
query67	29542	29426	29319	29319
query68	3144	1511	942	942
query69	399	300	271	271
query70	1076	974	1008	974
query71	355	340	326	326
query72	3091	2696	2406	2406
query73	816	734	452	452
query74	5100	4965	4735	4735
query75	2603	2591	2212	2212
query76	2322	1173	770	770
query77	346	378	280	280
query78	12303	12320	11596	11596
query79	1353	1193	761	761
query80	850	537	450	450
query81	497	321	277	277
query82	557	162	121	121
query83	358	330	297	297
query84	323	157	130	130
query85	939	588	527	527
query86	405	306	287	287
query87	1820	1819	1758	1758
query88	3705	2819	2800	2800
query89	474	422	362	362
query90	1886	197	190	190
query91	207	199	166	166
query92	63	59	57	57
query93	1578	1587	990	990
query94	624	362	324	324
query95	772	523	563	523
query96	1107	836	335	335
query97	2687	2677	2556	2556
query98	221	211	200	200
query99	1134	1182	1020	1020
Total cold run time: 265262 ms
Total hot run time: 179920 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 25.05 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit f5da27fff5332530f334a86e8a53a12692993799, data reload: false

query1	0.01	0.01	0.01
query2	0.10	0.05	0.06
query3	0.26	0.13	0.13
query4	1.62	0.14	0.14
query5	0.27	0.24	0.22
query6	1.24	1.08	1.11
query7	0.03	0.01	0.00
query8	0.06	0.04	0.04
query9	0.38	0.32	0.31
query10	0.54	0.57	0.60
query11	0.19	0.14	0.14
query12	0.18	0.14	0.15
query13	0.47	0.48	0.47
query14	1.01	1.00	1.01
query15	0.62	0.58	0.60
query16	0.34	0.32	0.33
query17	1.09	1.07	1.13
query18	0.22	0.20	0.22
query19	2.02	1.97	2.01
query20	0.01	0.01	0.01
query21	15.44	0.21	0.14
query22	4.88	0.05	0.05
query23	16.15	0.31	0.12
query24	2.95	0.41	0.30
query25	0.10	0.05	0.04
query26	0.73	0.22	0.16
query27	0.04	0.04	0.03
query28	3.47	0.88	0.56
query29	12.52	4.04	3.21
query30	0.27	0.15	0.16
query31	2.77	0.61	0.31
query32	3.22	0.59	0.49
query33	3.27	3.16	3.17
query34	15.64	4.20	3.54
query35	3.53	3.55	3.52
query36	0.55	0.44	0.44
query37	0.08	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.17	0.16
query41	0.08	0.04	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.69 s
Total hot run time: 25.05 s

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65500

Problem Summary: Direct VARBINARY mappings localized late runtime filters into external file readers even though those readers intentionally do not support VARBINARY predicate pushdown. This could reject matching Iceberg BINARY partition rows. Keep VARBINARY filters for scanner-level evaluation after materialization.

### Release note

Fix incorrect results for runtime filters on external VARBINARY columns, including Iceberg BINARY partition columns.

### Check List (For Author)

- Test: Unit Test
    - Remote ASAN BE unit tests: ColumnMapperLocalizeFiltersTest.* (5 passed)
- Behavior changed: Yes (VARBINARY runtime filters stay on scanner-level evaluation path)
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: FileScannerV2 cloned its table-level conjuncts only when TableReader was
initialized. Runtime filters arriving after scanner open were visible to the scanner but never
reached subsequent split readers, so Parquet and ORC metadata pruning, native file filtering, and
JNI block filtering could not benefit from late runtime filters. Pass a freshly cloned conjunct
snapshot into every split, replace TableReader's initial snapshot during split preparation, and
prepare JNI conjuncts per split.

### Release note

Enable late runtime filters to participate in FileScannerV2 file-level filtering for subsequent
splits.

### Check List (For Author)

- Test: Unit Test
    - `TableReaderTest.PrepareSplitReplacesInitialConjunctSnapshot`
    - `FileScannerV2Test.RewriteSlotRefsToGlobalIndexMatrix`
- Behavior changed: Yes. Subsequent FileScannerV2 splits use the latest runtime-filter snapshot for
  native and JNI filtering.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Complex Parquet column readers materialized every discarded row in a selection gap into one scratch column, so memory and decoding work grew with the entire gap. In addition, a refreshed split predicate could be bypassed by the table-level COUNT shortcut. Materialize skipped nested rows in bounded batches with per-batch scratch columns, and only use metadata row counts when the split has no active conjuncts.

### Release note

Bound memory usage while skipping nested Parquet columns and preserve late runtime-filter semantics for table-level COUNT.

### Check List (For Author)

- Test: Unit Test
    - `ParquetColumnReaderControlTest.*` (16 tests)
    - `TableReaderTest.PrepareSplitReplacesInitialConjunctSnapshot`
    - `TableReaderTest.RefreshedConjunctDisablesTableLevelCount`
- Behavior changed: Yes. Nested Parquet skip work is processed in bounded batches, and table-level COUNT no longer bypasses active split predicates.
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65498

Problem Summary: A table-level metadata COUNT split can emit several synthetic batches across scheduler turns. If a runtime filter was still pending when the shortcut started, it could arrive after an unfiltered batch had already escaped, and the active split could not recover the corresponding real file rows. Pass the scanner runtime-filter completion state into split preparation and allow metadata COUNT only after every assigned runtime filter has arrived.

### Release note

Table-level metadata COUNT now falls back to reading file rows while runtime filters are pending.

### Check List (For Author)

- Test: Unit Test
    - TableReaderTest.PrepareSplitReplacesInitialConjunctSnapshot
    - TableReaderTest.RefreshedConjunctDisablesTableLevelCount
    - TableReaderTest.PendingRuntimeFilterDisablesTableLevelCount
    - Remote BE build
- Behavior changed: Yes (metadata COUNT waits for all scanner runtime filters before using synthetic rows)
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: close apache#65498

Related PR: apache#65498

Problem Summary: Nested Parquet skips rebuilt parent boundaries into scratch Columns and decoded leaf payloads that were immediately discarded. In addition, pending runtime filters disabled table-level metadata COUNT but could still allow normal COUNT or MIN/MAX aggregate pushdown to emit irreversible synthetic rows before the filter arrived. Add a levels-only nested consume protocol for scalar, list, map, and struct readers, preserving nullability and stream-alignment validation without payload materialization. Gate every aggregate pushdown path on the split runtime-filter snapshot.

### Release note

Improve nested Parquet skip efficiency and preserve runtime-filter correctness for aggregate pushdown.

### Check List (For Author)

- Test: Regression test / Unit Test
    - ParquetColumnReaderTest, ParquetColumnReaderBaseTest, ParquetColumnReaderControlTest, and targeted TableReader tests
    - Remote ./build.sh --be
- Behavior changed: Yes (nested skip avoids decoding discarded payloads; aggregate pushdown waits for pending runtime filters)
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: close apache#65498

Related PR: apache#65498

Problem Summary: The Parquet levels-only nested path called Arrow RecordReader::Reset() without first releasing ByteArray and FLBA builder chunks. Arrow requires GetBuilderChunks() to reset those builders, so a nested skip followed by a normal read mixed values from both batches and failed with values greater than the current definition/repetition levels. Release and immediately discard binary chunks after copying levels, without copying payloads into Doris Columns. Also derive the scalar consume slot threshold from schema metadata because levels-only batches intentionally omit value-slot metadata.

### Release note

Fix nested Parquet queries that skip filtered rows before reading plain-encoded string values.

### Check List (For Author)

- Test: Unit Test
    - 95 targeted Parquet reader, levels-only COUNT, and TableReader tests
    - Plain-encoded nested MAP skip-then-read regression test
    - Remote ./build.sh --be
- Behavior changed: Yes (levels-only nested reads correctly reset Arrow binary builders)
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65500

Problem Summary: Combining the Parquet reader changes exposed that direct file mappings carry a slot-ref materialization projection. Treating every projection as unsafe disabled valid MIN/MAX statistics pushdown and made existing direct-mapping tests return raw rows. Use mapping triviality as the safety boundary: direct slot-ref mappings remain eligible while casts and other conversions still fall back to row scanning.

### Release note

Fix MIN/MAX aggregate pushdown for direct external-file column mappings.

### Check List (For Author)

- Test: Unit Test
    - Remote ASAN BE unit tests: ColumnMapperLocalizeFiltersTest.*, TableReaderTest.*, and ParquetColumnReaderTest.* (121 passed)
- Behavior changed: Yes (direct mappings retain MIN/MAX statistics pushdown; converted mappings still fall back)
- Does this need documentation: No
@Gabriel39 Gabriel39 changed the title [fix](be) Harden FileScannerV2 predicate and statistics pruning [fix](be) Harden external file filtering, statistics, and nested reads Jul 13, 2026
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review only: this checkout is missing .worktree_initialized, thirdparty/installed, and thirdparty/installed/bin/protoc, so I did not run builds or tests.

I reviewed the prompt-specified scope, existing review threads/comments, changed-file list, authoritative diff, and the shared subagent ledger. After accepting these four findings and rerunning final convergence, all live final agents returned NO_NEW_VALUABLE_FINDINGS for the same final comment set.

Checkpoint conclusions: the PR is focused on external-reader predicate/statistics/nested-reader correctness, but the remaining issues below can still produce wrong scan or aggregate results. I did not find a separate concurrency, lifecycle, config, persistence, transaction, FE/BE protocol, or observability issue. The main parallel-path gaps are ORC non-timestamp stats and complex-root VARBINARY predicate localization. The added tests were reviewed statically only; they do not cover the four residual failures called out inline.

Comment thread be/src/format_v2/table_reader.h
Comment thread be/src/format_v2/column_mapper.cpp
Comment thread be/src/format_v2/column_mapper.cpp Outdated
Comment thread be/src/format_v2/orc/orc_reader.cpp
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65500

Problem Summary: External reader review found four remaining correctness gaps: slotless conjuncts could still permit aggregate pushdown, TIMESTAMPTZ scale-mismatch filters could be localized with different rounding semantics, nested VARBINARY predicates could reach file readers, and decoded ORC statistics did not consistently reject inverted or NaN bounds. Disable aggregate pushdown whenever original conjuncts remain, keep unsafe filter conversions above the reader, and validate every decoded ORC statistics range before using it for metadata pruning or MIN/MAX aggregation.

### Release note

Fix external table filtering and metadata aggregate correctness for slotless predicates, TIMESTAMPTZ scale mismatches, nested VARBINARY values, and invalid ORC statistics.

### Check List (For Author)

- Test: Unit Test
    - Remote ASAN BE unit tests: 20 targeted tests passed
    - Remote clang-format 16 check passed
    - Remote clang-tidy attempted; blocked by missing stddef.h and pre-existing diagnostics in the validation toolchain
- Behavior changed: Yes, unsafe filter and aggregate pushdown paths now fall back to row-level evaluation
- Does this need documentation: No
@Gabriel39 Gabriel39 changed the title [fix](be) Harden external file filtering, statistics, and nested reads [fix](be) Harden external reader filtering, statistics, and nested Parquet reads Jul 13, 2026
@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review found one additional non-duplicate correctness issue; I left one inline comment.

Checkpoint conclusions:

  • Goal/tests: the PR hardens external-reader filtering, metadata fallback, and nested Parquet reading. Many previously reported paths are addressed or already covered by existing review threads, but the JNI conjunct lifecycle still has a standalone multi-split correctness gap. The added tests do not cover this JNI lifecycle case.
  • Scope/focus: the changes are focused on BE scan/format_v2 code and matching tests. No additional user-provided review focus was supplied.
  • Concurrency/thread safety: no new thread, lock-order, or shared-state concurrency issue was found in this pass.
  • Lifecycle: the accepted issue is a lifecycle bug: preserved JNI VExprContext objects can be prepared again across splits while open() is skipped after the first split.
  • Error handling: Status returns in the reviewed changed paths are checked; the accepted issue is not an ignored-Status problem.
  • Memory safety/nullability: no separate memory-tracking, ownership, nullable, or ColumnConst/ColumnNullable issue was substantiated.
  • Data correctness: the JNI lifecycle issue can make JNI block filtering fail or evaluate with stale/mismatched function state for predicates containing functions. Other mapper/statistics/nested-reader data-correctness concerns were either already commented in existing threads or conservatively handled in the current diff.
  • Observability: no new observability requirement was identified for this patch.
  • Config, compatibility, transaction/persistence, and FE/BE protocol: no new config item, persisted format, transaction, or protocol compatibility issue was found.
  • Parallel paths: scanner-driven FileScannerV2 passes fresh split conjunct clones and is not the affected path; native TableReader and metadata fallback paths were rechecked against the existing comments and current diff.
  • Tests/validation: static review only. I did not run builds or tests because the review prompt forbids builds in this runner; the exact base object is absent locally, and the checkout is missing worktree/dependency setup.

Convergence: three normal review agents plus two risk-focused agents re-read the final single-comment set and returned NO_NEW_VALUABLE_FINDINGS.

Comment thread be/src/format_v2/jni/jni_table_reader.cpp
@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29308 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit db14c3e2ef430d234ebb7b375ee19156ebb39132, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17633	4130	4033	4033
q2	2027	324	208	208
q3	10264	1442	808	808
q4	4742	473	347	347
q5	7971	858	589	589
q6	284	169	140	140
q7	808	876	634	634
q8	10655	1716	1557	1557
q9	5900	4409	4427	4409
q10	6802	1807	1525	1525
q11	501	339	317	317
q12	715	540	427	427
q13	18111	3330	2707	2707
q14	276	268	250	250
q15	q16	789	786	714	714
q17	962	930	997	930
q18	7029	5716	5518	5518
q19	1152	1172	971	971
q20	807	648	555	555
q21	5558	2688	2373	2373
q22	437	352	296	296
Total cold run time: 103423 ms
Total hot run time: 29308 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4350	4268	4250	4250
q2	285	327	214	214
q3	4616	5001	4410	4410
q4	2094	2170	1363	1363
q5	4479	4295	4351	4295
q6	227	175	126	126
q7	1757	1648	1469	1469
q8	2277	1998	1938	1938
q9	7311	7297	7304	7297
q10	4720	4644	4173	4173
q11	544	400	360	360
q12	756	740	527	527
q13	2941	3336	2817	2817
q14	281	285	260	260
q15	q16	686	711	636	636
q17	1279	1243	1243	1243
q18	7360	6757	6912	6757
q19	1113	1088	1070	1070
q20	2229	2215	1952	1952
q21	5230	4591	4460	4460
q22	531	454	401	401
Total cold run time: 55066 ms
Total hot run time: 50018 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 180613 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit db14c3e2ef430d234ebb7b375ee19156ebb39132, data reload: false

query5	4334	634	500	500
query6	487	256	210	210
query7	4485	603	345	345
query8	328	193	176	176
query9	4478	4082	4052	4052
query10	432	365	311	311
query11	2589	2382	2160	2160
query12	157	110	105	105
query13	735	617	447	447
query14	5816	5256	4953	4953
query14_1	4333	4284	4309	4284
query15	220	205	189	189
query16	1064	517	414	414
query17	952	716	572	572
query18	2436	483	336	336
query19	206	186	143	143
query20	112	109	108	108
query21	233	157	136	136
query22	13569	13683	13440	13440
query23	17577	16579	16152	16152
query23_1	16200	16373	16207	16207
query24	7515	1773	1309	1309
query24_1	1289	1305	1281	1281
query25	626	445	382	382
query26	1979	375	222	222
query27	2637	580	401	401
query28	4726	2026	2002	2002
query29	1140	616	489	489
query30	339	251	221	221
query31	1153	1085	972	972
query32	102	63	63	63
query33	626	325	260	260
query34	1182	1158	629	629
query35	789	779	677	677
query36	1440	1401	1259	1259
query37	187	121	96	96
query38	1870	1706	1671	1671
query39	930	918	911	911
query39_1	880	879	876	876
query40	252	165	140	140
query41	73	72	71	71
query42	98	95	95	95
query43	318	327	288	288
query44	1466	797	778	778
query45	205	193	183	183
query46	1087	1174	758	758
query47	2377	2376	2255	2255
query48	406	416	301	301
query49	574	422	333	333
query50	1016	432	339	339
query51	10808	10574	10706	10574
query52	94	90	77	77
query53	261	286	211	211
query54	319	249	250	249
query55	83	79	72	72
query56	304	309	297	297
query57	1441	1415	1325	1325
query58	283	293	258	258
query59	1583	1672	1482	1482
query60	357	293	263	263
query61	184	177	176	176
query62	692	658	598	598
query63	245	210	215	210
query64	2881	1141	852	852
query65	4870	4833	4758	4758
query66	1848	515	382	382
query67	29497	29532	29386	29386
query68	2969	1637	1005	1005
query69	400	317	266	266
query70	1066	952	1037	952
query71	338	319	299	299
query72	3191	2716	2458	2458
query73	832	821	407	407
query74	5109	4947	4755	4755
query75	2614	2585	2245	2245
query76	2352	1194	781	781
query77	353	389	280	280
query78	12347	12438	11858	11858
query79	1359	1166	773	773
query80	656	558	504	504
query81	480	323	278	278
query82	533	159	125	125
query83	368	317	292	292
query84	315	161	133	133
query85	927	607	500	500
query86	415	290	276	276
query87	1823	1826	1752	1752
query88	3642	2771	2765	2765
query89	458	410	350	350
query90	1701	201	193	193
query91	197	186	160	160
query92	77	64	59	59
query93	1635	1501	946	946
query94	583	346	308	308
query95	780	551	493	493
query96	1083	790	349	349
query97	2718	2667	2575	2575
query98	222	212	200	200
query99	1142	1184	1038	1038
Total cold run time: 253948 ms
Total hot run time: 180613 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 25.11 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit db14c3e2ef430d234ebb7b375ee19156ebb39132, data reload: false

query1	0.01	0.00	0.01
query2	0.10	0.05	0.05
query3	0.26	0.14	0.13
query4	1.61	0.14	0.14
query5	0.24	0.21	0.21
query6	1.25	1.11	1.06
query7	0.05	0.01	0.00
query8	0.07	0.04	0.04
query9	0.37	0.32	0.31
query10	0.56	0.54	0.58
query11	0.19	0.14	0.14
query12	0.18	0.14	0.14
query13	0.47	0.49	0.48
query14	1.03	1.02	1.00
query15	0.62	0.62	0.59
query16	0.31	0.34	0.30
query17	1.14	1.11	1.08
query18	0.22	0.21	0.21
query19	2.07	1.97	1.96
query20	0.02	0.02	0.01
query21	15.42	0.20	0.13
query22	4.96	0.05	0.05
query23	16.15	0.31	0.11
query24	2.98	0.43	0.33
query25	0.10	0.06	0.05
query26	0.74	0.21	0.16
query27	0.03	0.04	0.04
query28	3.50	0.94	0.54
query29	12.46	4.12	3.26
query30	0.28	0.16	0.16
query31	2.77	0.58	0.30
query32	3.22	0.60	0.49
query33	3.14	3.25	3.22
query34	15.74	4.24	3.54
query35	3.53	3.52	3.54
query36	0.55	0.44	0.43
query37	0.08	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.18	0.16	0.16
query41	0.08	0.04	0.04
query42	0.04	0.02	0.03
query43	0.05	0.04	0.04
Total cold run time: 96.86 s
Total hot run time: 25.11 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 84.16% (574/682) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 56.97% (23594/41418)
Line Coverage 40.64% (230859/568065)
Region Coverage 36.51% (182306/499276)
Branch Coverage 37.64% (81516/216593)

@github-actions

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Jul 13, 2026
@Gabriel39 Gabriel39 merged commit b2026de into apache:master Jul 13, 2026
32 of 33 checks passed
hello-stephen pushed a commit that referenced this pull request Jul 13, 2026
### What problem does this PR solve?

Fix a BE UT compile failure introduced by the combination of #65500 and
#65503.
`SplitReadOptions` now has `conjuncts` and
`all_runtime_filters_applied`, while
the new `jni_table_reader_test.cpp` designated initializer did not
initialize
them. With `-Werror,-Wmissing-designated-field-initializers`, this fails
compilation.

This PR explicitly initializes the two fields in the test and adds the
missing
`<optional>` include for `std::nullopt`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants