Skip to content

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

Open
Gabriel39 wants to merge 22 commits into
apache:masterfrom
Gabriel39:agent/combine-parquet-statistics-fixes
Open

[fix](be) Harden external file filtering, statistics, and nested reads#65500
Gabriel39 wants to merge 22 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 external file readers had several independent correctness and resource-control gaps:

  • runtime filters arriving after scanner initialization were not reflected in later V2 split readers, allowing file-level filtering and metadata aggregate shortcuts to use a stale predicate snapshot;
  • pending runtime filters could race with table-level COUNT or file-level COUNT/MINMAX synthetic rows, which cannot be retracted after a filter arrives;
  • direct VARBINARY mappings localized late filters into external readers even though VARBINARY predicate pushdown is unsupported, causing matching Iceberg BINARY partition rows to be rejected;
  • split and constant pruning could cross unsafe conjuncts, while lossy schema-evolution casts could change localized predicate boundaries;
  • missing, inverted, NaN, timezone-non-monotonic, truncated binary, or incorrectly cached file statistics could drive unsafe Parquet/ORC pruning and aggregate results;
  • LIST, MAP, and STRUCT skips materialized entire discarded ranges, causing memory and decoding work to grow with large gaps, and nested binary builders could retain stale state across level batches;
  • direct mappings use slot-ref projections for materialization, but treating every projection as unsafe disabled valid MIN/MAX statistics pushdown.

The fix refreshes conjuncts per split, gates irreversible aggregate shortcuts on runtime-filter completion, keeps VARBINARY filters on the scanner-level path, preserves only the safe conjunct prefix, requires lossless predicate localization, and makes invalid or inexact statistics fall back conservatively. It also scopes Bloom filters correctly, bounds nested skip work in reusable batches, avoids materializing skipped nested values where possible, resets binary builders between level reads, and permits MIN/MAX pushdown only for order-preserving trivial mappings.

Release note

Fix incorrect external-file filtering, partition and metadata pruning, aggregate pushdown, and nested Parquet skip behavior. Runtime filters now use current split state, unsafe statistics paths fall back conservatively, and nested skips use bounded memory.

Check List (For Author)

  • Test: Unit Test
    • Remote ASAN BE unit tests on gabriel@10.26.20.3: ColumnMapperLocalizeFiltersTest.*, TableReaderTest.*, and ParquetColumnReaderTest.* (121 tests passed).
    • The Iceberg external regression was attempted but skipped because the remote validation configuration does not enable Iceberg tests.
    • git diff --check passed.
    • 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

@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 complete. I found five blocking correctness/test issues:

  • file-local predicate localization can still cross slotless unsafe conjuncts (inline)
  • inverted Parquet min/max stats are still trusted for pruning and native aggregate pushdown (inline)
  • timestamp raw endpoint inversion and TIMESTAMPTZ bypasses can still trust corrupt stats (inline)
  • the new slotless TableReader unit test asserts a predicate path this harness never executes (inline)
  • MIN/MAX aggregate pushdown remains unsafe through non-order-preserving file-to-table mappings (inline)

Checkpoint conclusions:

  • Goal/tests: the PR targets predicate/statistics hardening, but the changed paths still leave correctness gaps.
  • Scope/focus: changes are focused, but safety barriers and aggregate/stat validation are incomplete in parallel execution paths.
  • Concurrency/lifecycle/config/persistence: no new concurrency, lifecycle, config, persistence, or FE/BE protocol concerns found in the reviewed changed paths.
  • Compatibility/data correctness: data correctness issues remain in pruning and aggregate pushdown; no storage-format compatibility change found.
  • Tests: review-only environment; no builds or tests run here. One changed test appears to assert an impossible path in its harness.
  • User focus: no additional user-provided focus.

Convergence: main review plus three real subagent rounds completed; the final round returned NO_NEW_VALUABLE_FINDINGS.

Comment thread be/src/format_v2/table_reader.cpp Outdated
Comment thread be/src/format_v2/parquet/parquet_statistics.cpp
Comment thread be/src/format_v2/parquet/parquet_statistics.cpp
Comment thread be/test/format_v2/table_reader_test.cpp Outdated
Comment thread be/src/format_v2/parquet/parquet_reader.cpp
@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 86.49% (224/259) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 56.93% (23564/41389)
Line Coverage 40.60% (230499/567683)
Region Coverage 36.51% (182174/498926)
Branch Coverage 37.59% (81339/216412)

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65500

Problem Summary: File-local predicate localization could cross a slotless unsafe conjunct, inverted Parquet and ORC statistics could still drive pruning or aggregate results, and MIN/MAX pushdown could use endpoints from a non-order-preserving file-to-table projection. Stop localization at the original conjunct-order barrier, reject inverted decoded and raw timestamp bounds, and require a direct mapping without a projection for MIN/MAX pushdown. Add focused coverage for inverted statistics, ordering barriers, and cast fallback.

### Release note

Prevent incorrect external file pruning and MIN/MAX results from corrupt statistics, unsafe predicate reordering, and non-order-preserving schema mappings.

### Check List (For Author)

- Test: Unit Test
    - Remote ASAN BE unit tests: 4 focused predicate/statistics/mapping tests passed
    - Remote ASAN BE unit tests: 13 Parquet, ORC, and timestamp statistics tests passed
    - Remote clang-tidy on the six modified files
- Behavior changed: Yes (unsafe metadata and mappings now fall back to normal scanning)
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at 5:19 PM.
Workflow run: https://github.com/apache/doris/actions/runs/29196259568

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at 5:19 PM.
Workflow run: https://github.com/apache/doris/actions/runs/29196889535

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at 5:19 PM.
Workflow run: https://github.com/apache/doris/actions/runs/29197565971

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17691	4126	4088	4088
q2	2053	336	198	198
q3	10281	1410	849	849
q4	4681	470	343	343
q5	7562	854	584	584
q6	188	179	137	137
q7	753	833	623	623
q8	9321	1462	1438	1438
q9	5621	4399	4389	4389
q10	6760	1807	1534	1534
q11	500	333	318	318
q12	714	545	420	420
q13	18073	3356	2785	2785
q14	266	257	236	236
q15	q16	786	783	708	708
q17	1055	1018	1003	1003
q18	7102	5657	5569	5569
q19	1306	1235	1083	1083
q20	783	638	535	535
q21	6131	2697	2448	2448
q22	438	355	301	301
Total cold run time: 102065 ms
Total hot run time: 29589 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4389	4284	4275	4275
q2	281	314	215	215
q3	4622	4946	4372	4372
q4	2051	2138	1376	1376
q5	4458	4313	4351	4313
q6	229	178	130	130
q7	1710	1924	1866	1866
q8	2560	2224	2169	2169
q9	8083	8359	7777	7777
q10	4748	4746	4265	4265
q11	574	414	376	376
q12	748	849	550	550
q13	3313	3581	2949	2949
q14	303	294	281	281
q15	q16	746	731	654	654
q17	1334	1341	1335	1335
q18	7903	7164	7243	7164
q19	1105	1094	1110	1094
q20	2205	2209	1966	1966
q21	5269	4568	4436	4436
q22	533	460	394	394
Total cold run time: 57164 ms
Total hot run time: 51957 ms

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

// escape before the filter arrives and cannot later be reconstructed from real file rows.
// This is the same irreversibility constraint as table-level metadata COUNT, and applies
// to COUNT and MIN/MAX for Parquet/ORC as well as COUNT for text readers.
if (!_all_runtime_filters_applied_for_split) {

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.

This still allows aggregate pushdown to reduce rows before an original row-level predicate has run. _build_table_filters_from_conjuncts() intentionally omits slotless conjuncts, so a scan with MIN/MAX(v) and a slotless unsafe predicate like random()/uuid() can reach this new RF gate with no pending filters and then pass the later _table_filters.empty() check. The reader then emits synthetic extrema rows and closes before Scanner::_filter_output_block() evaluates the original predicate, so the predicate runs on {min,max} instead of on every source row. For rows {1,2,3}, if the slotless predicate keeps only the middle row, normal scanning aggregates 2, but pushdown can aggregate {1,3}. Please disable aggregate pushdown whenever the original _conjuncts is non-empty, or otherwise prove every original conjunct has been represented by a safe, row-preserving pre-execution path before emitting synthetic COUNT/MIN/MAX rows.

// `value = 1.0` is false. Complex Field equality also does not compare nested contents.
// Restrict localization to scalar numeric casts that preserve every file value; unsupported
// and complex casts keep the table predicate and evaluate after materialization.
if (!is_lossless_file_to_table_numeric_cast(rewrite_info.file_type,

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.

This numeric-only guard leaves TIMESTAMPTZ scale-mismatch predicates to the generic slot rewrite path, which then wraps the file slot in Cast(file_slot AS table_type). That conflicts with the mapping contract added for TIMESTAMPTZ scale mismatch: finalization treats TIMESTAMPTZ(6) -> TIMESTAMPTZ(0) as pass-through because the SQL cast rounds fractional seconds. For example, a file value 12:34:56.789 and predicate ts < '12:34:57' should remain true under pass-through semantics, but a pushed file-reader cast to TIMESTAMPTZ(0) can round the value to 12:34:57 and filter it before the scanner evaluates the original predicate. Please keep these scale-mismatch TIMESTAMPTZ filters above the file reader, or add a no-rounding file-local rewrite that matches final materialization semantics.

// FileScanOperator deliberately keeps VARBINARY predicates above external readers. Their
// physical binary representations are not uniformly supported by reader-side expression and
// metadata filtering, so localizing a late runtime filter here can incorrectly reject rows.
if (remove_nullable(mapping.table_type)->get_primitive_type() == TYPE_VARBINARY) {

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.

This guard only protects top-level VARBINARY mappings. Predicates rooted at a complex column are admitted by localize_filters() based on the root mapping, so nested VARBINARY can still reach the file reader. For example, element_at(s, 'bin') = ... rewrites the struct selector/type to the file child without checking the child mapping's filter_conversion; similarly, generic complex-root predicates such as array_contains(array<varbinary>, ...) or map_contains_key(map<varbinary,...>, ...) rewrite the root slot directly when the root mapping has a local source. That sends nested VARBINARY comparisons into the same file-reader expression/metadata paths this guard is trying to avoid for top-level VARBINARY. Please reject localization for any complex-root predicate whose referenced nested mapping is FINALIZE_ONLY, or at least whose nested table type contains VARBINARY.

std::pair(timestamp_statistics->getMinimum(), timestamp_statistics->getMinimumNanos());
const auto max_endpoint =
std::pair(timestamp_statistics->getMaximum(), timestamp_statistics->getMaximumNanos());
if (min_endpoint > max_endpoint) {

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.

This adds conservative validation for timestamp stats only, but ORC MIN/MAX pushdown also consumes the sibling non-timestamp stripe stats through build_zone_map_from_orc_statistics(). The integer, floating, string, date, and decimal setters still return usable ZoneMaps without checking max_value < min_value, and the floating path also trusts NaN bounds. With corrupt ORC stats such as INT min=10/max=1 for a stripe that actually contains 5, aggregate pushdown can return synthetic extrema {10,1} instead of falling back to a real scan. Please apply the same conservative validation to every ORC statistics-backed min/max path, including rejecting NaN floating bounds and decoded inverted ranges, before allowing pruning or aggregate pushdown to trust the stats.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants