Skip to content

[fix](be) Refresh V2 predicates and bound nested Parquet skips#65498

Closed
Gabriel39 wants to merge 8 commits into
apache:masterfrom
Gabriel39:codex/fix-v2-late-runtime-filter-refresh
Closed

[fix](be) Refresh V2 predicates and bound nested Parquet skips#65498
Gabriel39 wants to merge 8 commits into
apache:masterfrom
Gabriel39:codex/fix-v2-late-runtime-filter-refresh

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: FileScannerV2 cloned table-level conjuncts only during TableReader initialization. Runtime filters arriving after scanner open were visible to the scanner but not to later split readers, so native and JNI file-level filtering could not use them. The table-level COUNT shortcut could also return synthetic rows for a split without evaluating a refreshed predicate.

Separately, LIST, MAP, and STRUCT Parquet readers implemented skip() by materializing the complete discarded range into one scratch column. Large selection gaps therefore caused memory and decoding work to grow with the entire gap.

This change passes a fresh conjunct snapshot into every split, prepares JNI conjuncts per split, and disables metadata-only table counts whenever a split has active conjuncts. Nested Parquet skips now reuse a shared bounded-batch path that materializes at most 4096 parent rows per scratch column and releases each scratch column before continuing.

Release note

Refresh FileScannerV2 predicates for each split, preserve filtered COUNT semantics, and bound memory usage while skipping nested Parquet columns.

Check List (For Author)

  • Test: Unit Test
    • ParquetColumnReaderControlTest.* (16 tests passed)
    • TableReaderTest.PrepareSplitReplacesInitialConjunctSnapshot
    • TableReaderTest.RefreshedConjunctDisablesTableLevelCount
  • Behavior changed: Yes. Later splits use current runtime filters, filtered splits do not use metadata-only COUNT, and nested Parquet skip work is processed in bounded batches.
  • 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
@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 Gabriel39 marked this pull request as ready for review July 12, 2026 07:17
@Gabriel39 Gabriel39 requested a review from yiguolei as a code owner July 12, 2026 07:17
@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.

I found one correctness issue in the refreshed split-conjunct path. The change installs a fresh split snapshot, but table-level COUNT splits can still return synthetic rows before those filters are built or evaluated.

Critical checkpoint conclusions:

  • Goal/test: the PR mostly addresses late runtime-filter refresh for normal native/JNI split filtering and adds a focused unit test, but the table-level COUNT path remains uncovered.
  • Scope: the implementation is narrow and focused.
  • Concurrency: no new shared-state race found; late RF updates are still cloned through the existing scanner/local-state path.
  • Lifecycle: JNI per-split preparation was reviewed; preserved initial contexts remain effectively idempotent due concrete expression prepare guards and idempotent open.
  • Config/compatibility: no new config or serialization/storage compatibility issue found.
  • Parallel paths: native/JNI/hybrid forwarding is covered, but COUNT/table_level_row_count is a parallel fast path that bypasses the refreshed predicates.
  • Tests: added coverage validates request replacement, but not refreshed conjuncts combined with table_level_row_count.
  • Observability/performance/persistence: no additional issue found.
  • User focus: no additional user-provided focus points were supplied.

Subagent conclusions:

  • tests-session-config-001 was accepted as the inline comment below.
  • optimizer-rewrite-001 was dismissed after verifying expression prepare/open idempotence for preserved JNI snapshots.
  • Convergence round 1 ended with both live subagents reporting NO_NEW_VALUABLE_FINDINGS for this same ledger/comment set.

Validation note: I attempted ./run-be-ut.sh --run --filter=TableReaderTest.PrepareSplitReplacesInitialConjunctSnapshot, but this checkout failed during BE UT configure because thirdparty/installed/bin/protoc is missing, so the test binary did not run.

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

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 27.27% (3/11) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 56.90% (23542/41373)
Line Coverage 40.57% (230209/567446)
Region Coverage 36.48% (181918/498746)
Branch Coverage 37.55% (81214/216286)

### 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
@Gabriel39 Gabriel39 changed the title [fix](be) Refresh V2 file predicates for each split [fix](be) Refresh V2 predicates and bound nested Parquet skips Jul 12, 2026
@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

1 similar comment
@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.

Requesting changes for one correctness issue in the table-level COUNT shortcut.

Summary: the per-split conjunct refresh now covers newly prepared splits, native reader requests, JNI filtering, and hybrid delegation, and the parquet nested skip batching looks semantically safe in static review. However, an already-prepared large table-level COUNT split can keep emitting synthetic metadata rows after a runtime filter arrives between scheduler reads, so the accepted inline comment should be addressed.

Checkpoint conclusions: goal is mostly met but incomplete for the active-split COUNT/RF lifecycle; scope is focused; no new config, persistence, protocol, or data-write behavior is involved; concurrency risk is a scheduler/lifecycle timing gap rather than a lock issue; parallel native/JNI/hybrid paths were checked; parquet nested skip preserves the existing load/build cursor path and has focused unit coverage; validation was static only per the review prompt, so no builds or tests were run.

User focus: no additional user-provided review focus was supplied.

@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 completed for PR #65498.

I did not find a new, non-duplicate issue to request changes on. The existing inline thread on TableReader::prepare_split() already covered the table-level COUNT bypass for refreshed split conjuncts; the current patch addresses that with the _conjuncts.empty() guard and the new RefreshedConjunctDisablesTableLevelCount unit test, so I am not duplicating it.

Critical checkpoint conclusions:

  • Goal and tests: the PR refreshes V2 table-reader predicates per split and bounds nested Parquet skip scratch materialization; the added BE unit tests cover split conjunct replacement, table-level count disabling with refreshed conjuncts, and bounded nested skip batches.
  • Scope and parallel paths: the change is focused. Native TableReader, JNI readers, and Paimon/Hudi hybrid delegation were checked; the fresh SplitReadOptions::conjuncts snapshot reaches each split path.
  • Concurrency and lifecycle: no new shared-state concurrency was introduced. JNI scanner open/close and VExpr prepare/open behavior were checked for repeated splits.
  • Correctness and pushdown: table-level COUNT and aggregate pushdown were checked against refreshed row predicates and runtime filters. No additional bypass beyond the existing reviewed thread was found.
  • Nested Parquet skip: LIST/MAP/STRUCT skip now reuses the old load/build protocol in bounded chunks; cursor advancement, null/empty parents, repeated children, and map key/value alignment were reviewed.
  • Compatibility, persistence, configs, writes, observability: no new persistent format, FE/BE protocol, config, write-transaction, or observability requirement was introduced by this PR.
  • User focus: no additional user-provided review focus was present.

Validation was static only in this review runner: .worktree_initialized, thirdparty/installed, and thirdparty/installed/bin/protoc are absent, so I did not run BE unit tests or builds. The bundle patch reverse-applies cleanly to the checked-out PR head (7f06a4677d9e12cabc91f164e16814d1123fb2bc) with whitespace checking.

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

Correcting the inline anchor for the table-level COUNT/runtime-filter issue from review 4680101694.

Comment thread be/src/format_v2/table_reader.cpp Outdated
### 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
@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.

Review completed with one remaining correctness issue.

Critical checkpoint conclusions:

  • Goal/test coverage: the PR addresses late-runtime-filter handling for V2 table-reader shortcuts and bounded nested Parquet skip memory. The new tests cover fresh conjunct snapshots, pending RFs for table-level metadata COUNT, and bounded nested skip batching, but there is no coverage for pending RFs with the normal aggregate-pushdown path.
  • Scope/focus: the changes are focused and mostly align with existing TableReader, FileScannerV2, JNI reader, and Parquet nested-reader patterns.
  • Concurrency/lifecycle: the relevant lifecycle is scanner scheduler turns and late RF refresh. The table-level metadata COUNT path is now guarded, but the separate aggregate-pushdown path can still emit synthetic rows before a later RF refresh.
  • Compatibility/config/persistence: no new config, persistence, transaction, or FE-BE protocol incompatibility was found.
  • Parallel paths: JNI does not use the base aggregate-pushdown branch; native Parquet/ORC/text paths do, and the pending-RF state is not carried into that branch.
  • Parquet nested skip: the chunked skip helper reuses the old load/build/verify path per parent-row chunk and no LIST/MAP/STRUCT correctness issue was found.

No additional user-provided focus was supplied. This was a static review only; no builds or tests were run in the review-only checkout.

Comment thread be/src/format_v2/table_reader.h
### 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
@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/29198093566

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

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 88.15% (238/270) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 56.91% (23554/41385)
Line Coverage 40.58% (230327/567598)
Region Coverage 36.47% (181929/498892)
Branch Coverage 37.56% (81276/216375)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17728	4033	4020	4020
q2	1994	318	209	209
q3	10309	1406	814	814
q4	4682	476	342	342
q5	7478	850	578	578
q6	179	166	135	135
q7	779	865	639	639
q8	9387	1606	1577	1577
q9	5979	4351	4402	4351
q10	6820	1789	1528	1528
q11	509	350	318	318
q12	702	537	428	428
q13	18105	3367	2732	2732
q14	272	262	245	245
q15	q16	781	778	705	705
q17	1065	923	1053	923
q18	7094	5776	5483	5483
q19	1376	1337	1067	1067
q20	744	603	510	510
q21	5803	2593	2277	2277
q22	432	355	298	298
Total cold run time: 102218 ms
Total hot run time: 29179 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4348	4234	4253	4234
q2	287	326	218	218
q3	4585	4906	4392	4392
q4	2068	2144	1355	1355
q5	4437	4318	4342	4318
q6	223	173	130	130
q7	1738	1794	1781	1781
q8	2476	2151	2079	2079
q9	7917	7797	7686	7686
q10	4745	4697	4221	4221
q11	548	427	385	385
q12	734	752	546	546
q13	3341	3608	2902	2902
q14	297	300	301	300
q15	q16	736	746	702	702
q17	1325	1304	1300	1300
q18	8205	7588	6867	6867
q19	1086	1105	1037	1037
q20	2214	2202	1938	1938
q21	5241	4525	4371	4371
q22	511	471	415	415
Total cold run time: 57062 ms
Total hot run time: 51177 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 179918 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 4c870ef0bd9700dd24218293254878c38f20f1a2, data reload: false

query5	4325	633	485	485
query6	457	231	197	197
query7	4929	614	362	362
query8	338	192	177	177
query9	8777	4005	3966	3966
query10	441	350	303	303
query11	5931	2328	2192	2192
query12	154	104	100	100
query13	1286	628	428	428
query14	6303	5302	4950	4950
query14_1	4290	4308	4299	4299
query15	208	207	180	180
query16	999	461	472	461
query17	1010	733	594	594
query18	2446	478	354	354
query19	213	193	153	153
query20	114	109	108	108
query21	229	159	136	136
query22	13723	13748	13605	13605
query23	17730	16644	16136	16136
query23_1	16267	16253	16345	16253
query24	7601	1770	1331	1331
query24_1	1295	1329	1297	1297
query25	562	460	396	396
query26	1340	334	200	200
query27	2615	599	380	380
query28	4463	2011	2029	2011
query29	1122	609	479	479
query30	332	260	225	225
query31	1110	1084	984	984
query32	111	61	59	59
query33	518	315	244	244
query34	1187	1134	642	642
query35	759	787	666	666
query36	1400	1396	1196	1196
query37	150	111	91	91
query38	1878	1704	1674	1674
query39	919	913	895	895
query39_1	881	883	903	883
query40	241	162	136	136
query41	64	66	66	66
query42	95	91	92	91
query43	319	321	277	277
query44	1406	784	777	777
query45	208	185	177	177
query46	1080	1212	766	766
query47	2367	2325	2205	2205
query48	418	373	296	296
query49	573	417	308	308
query50	1081	423	347	347
query51	10820	10711	10465	10465
query52	83	84	79	79
query53	271	276	203	203
query54	279	245	235	235
query55	76	73	65	65
query56	281	278	277	277
query57	1424	1419	1326	1326
query58	293	258	262	258
query59	1543	1623	1398	1398
query60	310	273	237	237
query61	148	149	151	149
query62	687	642	585	585
query63	248	206	200	200
query64	2865	1036	875	875
query65	4903	4781	4744	4744
query66	1864	517	384	384
query67	29585	29464	29296	29296
query68	3184	1580	973	973
query69	408	307	274	274
query70	1117	1001	939	939
query71	336	329	321	321
query72	3050	2759	2401	2401
query73	835	753	423	423
query74	5131	4992	4745	4745
query75	2622	2588	2244	2244
query76	2344	1183	780	780
query77	362	386	306	306
query78	12300	12357	11885	11885
query79	1420	1202	796	796
query80	732	539	445	445
query81	479	334	292	292
query82	602	155	121	121
query83	397	311	291	291
query84	313	154	128	128
query85	958	630	515	515
query86	394	291	295	291
query87	1815	1829	1755	1755
query88	3679	2816	2767	2767
query89	455	409	362	362
query90	1802	191	193	191
query91	196	190	163	163
query92	65	57	57	57
query93	1512	1503	935	935
query94	618	359	307	307
query95	775	599	490	490
query96	1107	777	366	366
query97	2701	2667	2560	2560
query98	212	214	201	201
query99	1181	1173	1075	1075
Total cold run time: 265555 ms
Total hot run time: 179918 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.09	0.05	0.06
query3	0.25	0.14	0.14
query4	1.60	0.15	0.12
query5	0.23	0.22	0.23
query6	1.23	1.07	1.08
query7	0.04	0.00	0.00
query8	0.06	0.04	0.04
query9	0.37	0.32	0.31
query10	0.56	0.55	0.56
query11	0.20	0.14	0.14
query12	0.18	0.14	0.14
query13	0.49	0.49	0.48
query14	1.01	1.01	1.02
query15	0.62	0.61	0.62
query16	0.31	0.30	0.32
query17	1.15	1.14	1.11
query18	0.22	0.21	0.21
query19	2.00	1.97	1.94
query20	0.02	0.01	0.01
query21	15.43	0.22	0.13
query22	4.79	0.05	0.05
query23	16.17	0.32	0.12
query24	2.88	0.45	0.33
query25	0.11	0.05	0.04
query26	0.73	0.20	0.14
query27	0.04	0.04	0.04
query28	3.54	1.04	0.54
query29	12.49	4.07	3.22
query30	0.27	0.15	0.15
query31	2.77	0.60	0.31
query32	3.23	0.59	0.48
query33	3.16	3.17	3.19
query34	15.67	4.24	3.54
query35	3.51	3.50	3.55
query36	0.54	0.46	0.43
query37	0.09	0.07	0.07
query38	0.04	0.04	0.04
query39	0.04	0.04	0.03
query40	0.18	0.17	0.15
query41	0.08	0.04	0.04
query42	0.04	0.03	0.03
query43	0.04	0.04	0.04
Total cold run time: 96.48 s
Total hot run time: 25.04 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 91.48% (247/270) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 74.76% (30189/40380)
Line Coverage 58.75% (331383/564036)
Region Coverage 55.46% (277762/500788)
Branch Coverage 56.73% (122798/216453)

### 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
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65498

Problem Summary: Split-level conjunct refresh can expose late runtime filters to TableReader. VARBINARY predicates are intentionally unsupported for external-reader pushdown, but ColumnMapper treated direct VARBINARY mappings as COPY_DIRECTLY and localized late filters into file readers. This caused Iceberg BINARY partition runtime-filter queries to drop matching rows. Mark VARBINARY mappings FINALIZE_ONLY so Scanner evaluates them after materialization while the refreshed snapshot still gates unsafe aggregate shortcuts.

### 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.* and TableReaderTest.PrepareSplitReplacesInitialConjunctSnapshot (6 passed)
    - Iceberg regression attempted but skipped because remote config disables Iceberg external tests
- Behavior changed: Yes (VARBINARY runtime filters stay on scanner-level evaluation path)
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@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 found two correctness issues, so I am requesting changes.

Critical checkpoints: runtime-filter aggregate shortcuts were checked and the remaining known cases are already covered by existing review threads; split conjunct and JNI lifecycle were checked with no distinct issue; Parquet nested consume found M-1; live-head VARBINARY filter localization found M-2; test coverage gaps C-1/C-2 were dismissed as not independently substantiated, with regressions requested for M-1 and M-2. No additional user-provided review focus was present.

Validation: static review only. I did not run builds or tests because the review prompt forbids builds for this review environment.

Protocol note: this automated review is incomplete under the prompt's convergence protocol because M-2 was discovered in round 3, the maximum allowed round, so no fourth convergence round was run. All currently known candidates were still accepted or dismissed before submission.

}
scalar_key_reader->set_nested_build_level_cursor(level_idx);
} else {
RETURN_IF_ERROR(_key_reader->consume_nested_column(static_cast<int64_t>(total_entries),

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.

consume_nested_column() is documented to preserve the same null/alignment validation as build_nested_column(), but this non-scalar key branch only checks that the complex key reader consumed the expected count. MAP schemas intentionally accept optional key fields for compatibility, and the materialized path still rejects actual null keys by checking the appended key column. With a nullable STRUCT/LIST MAP key, skipping a row now takes this branch; the complex key reader accepts the null key as a valid nullable value and returns the expected count, so malformed data is silently accepted whenever that row is skipped. Please make the consume-only MAP key path validate that every key entry is non-null regardless of key reader kind, and add a skip regression for an optional complex key containing a null key.

Comment thread be/src/format_v2/column_mapper.cpp Outdated
// 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) {
return FilterConversionType::FINALIZE_ONLY;

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 keeps projected VARBINARY filters above the reader, but it breaks predicate-only VARBINARY filters. Hidden filter mappings are created after the visible projected_columns loop, and FINALIZE_ONLY makes filter_conversion_has_local_source() false, so localize_filters() skips the hidden VARBINARY mapping instead of adding it as a predicate column. It is also never added as a non-predicate column, and TableReader::finalize_chunk() only materializes visible mappings(), not hidden mappings. A query that filters on a non-projected VARBINARY column therefore reaches the scanner-side fallback without the VARBINARY slot in the table block. Please make predicate-only VARBINARY filters materialize a column for final evaluation while still avoiding reader-local predicate pushdown, and add a regression where the output projects a different column and the VARBINARY column appears only in the filter.

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex completed, but no new pull request review was submitted for the current head SHA.
Workflow run: https://github.com/apache/doris/actions/runs/29216946545

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

### What problem does this PR solve?

Issue Number: None

Related PR: apache#65498

Problem Summary: The VARBINARY external-reader filter fix and its unit test were committed to this PR by mistake. Restore the two affected files to their state before the misplaced commits so this PR contains only its intended changes.

### Release note

None

### Check List (For Author)

- Test: No need to test (the commit only restores the exact pre-change file contents)
- Behavior changed: No
- Does this need documentation: No

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

Requesting changes for one non-duplicate VARBINARY localization issue found on reviewed commit 5b3c674.

Process note: before submission, the live PR head moved to 77a59cb. The review prompt names 5b3c674 and forbids refreshing the authoritative diff through GitHub, so this inline comment is anchored to the reviewed bundle commit. Live API checks also showed two comments raised after the bundle snapshot: comment 3567612022 already covers the hidden predicate-only VARBINARY materialization issue, and comment 3567612017 already covers a Parquet MAP consume-only key-validation issue, so I did not duplicate either one.

Critical checkpoint conclusions: the split-level runtime-filter snapshot, TableReader metadata shortcut gates, JNI split conjunct lifecycle, and nested Parquet skip batching were reviewed against the bundle diff and existing/live comments. The remaining non-duplicate problem is that the new VARBINARY guard is top-level-only, while nested STRUCT and same-shape ARRAY/MAP predicates can still be localized into external reader conjuncts. The change is otherwise focused and does not add persistent format, config, transaction, FE-BE protocol, or observability requirements. Test coverage now includes visible top-level VARBINARY, but not nested STRUCT<... VARBINARY ...> or same-shape ARRAY/MAP predicates with VARBINARY leaves. No additional user-provided focus was supplied.

Validation: static review only; no builds or tests were run in this review-only checkout.

Comment thread be/src/format_v2/column_mapper.cpp Outdated
return FilterConversionType::FINALIZE_ONLY;
}
return mapping.is_trivial ? FilterConversionType::COPY_DIRECTLY
: FilterConversionType::CAST_FILTER;

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.

Please extend this VARBINARY guard to recursive complex dependencies, not just the top-level primitive mapping. For s STRUCT<bin VARBINARY>, the bin child mapping gets FINALIZE_ONLY, but the root struct can still be COPY_DIRECTLY/READER_EXPRESSION; localize_filters() decides from the root global index and rewrite_struct_element_path_to_file_expr() can still push element_at(s, 'bin') = ... into file_request->conjuncts. The same root-type-only check also leaves same-shape ARRAY<VARBINARY> or MAP key/value VARBINARY roots local, so generic expressions such as array_contains(arr, ...) or array_contains(map_keys(m), ...) can be localized through rewrite_table_expr_to_file_expr(). In all of these cases the external reader can reject rows using VARBINARY physical binary semantics before scanner-side final evaluation can correct them. Please make filter localization detect terminal/nested VARBINARY dependencies before adding reader-local predicate columns/conjuncts, with regressions for a VARBINARY struct child and same-shape ARRAY/MAP VARBINARY predicates. The hidden predicate-only VARBINARY materialization case is already covered by a live thread in this PR, so I am not duplicating it here.

Gabriel39 added a commit to Gabriel39/incubator-doris that referenced this pull request Jul 13, 2026
### 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
Gabriel39 added a commit to Gabriel39/incubator-doris that referenced this pull request Jul 13, 2026
### 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
Gabriel39 added a commit to Gabriel39/incubator-doris that referenced this pull request Jul 13, 2026
### 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
@Gabriel39 Gabriel39 closed this Jul 13, 2026
@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17649	4101	4086	4086
q2	2024	343	206	206
q3	10286	1457	837	837
q4	4689	480	343	343
q5	7560	869	584	584
q6	179	173	138	138
q7	811	857	624	624
q8	10016	1579	1646	1579
q9	6270	4430	4442	4430
q10	6706	1811	1528	1528
q11	518	346	317	317
q12	721	568	440	440
q13	18199	3451	2670	2670
q14	268	263	253	253
q15	q16	801	796	716	716
q17	1029	966	907	907
q18	7083	5907	5583	5583
q19	1210	1297	1010	1010
q20	820	673	538	538
q21	5774	2623	2552	2552
q22	440	355	304	304
Total cold run time: 103053 ms
Total hot run time: 29645 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4533	4434	4448	4434
q2	285	322	218	218
q3	4648	4965	4405	4405
q4	2094	2175	1397	1397
q5	4526	4343	4341	4341
q6	230	178	131	131
q7	2243	1917	1659	1659
q8	2592	2177	2232	2177
q9	7921	7845	7958	7845
q10	4769	4770	4279	4279
q11	579	439	535	439
q12	761	764	573	573
q13	3274	3719	3062	3062
q14	297	307	293	293
q15	q16	722	759	647	647
q17	1391	1313	1334	1313
q18	8103	7580	7306	7306
q19	1126	1046	1092	1046
q20	2205	2222	1952	1952
q21	5366	4736	4549	4549
q22	524	478	426	426
Total cold run time: 58189 ms
Total hot run time: 52492 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 180919 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 5b3c674ef08ee9d554f285b57e17aca4513f8669, data reload: false

query5	4360	644	491	491
query6	466	227	215	215
query7	4853	598	347	347
query8	338	197	182	182
query9	8736	4022	4050	4022
query10	468	361	327	327
query11	5975	2376	2201	2201
query12	154	109	105	105
query13	1352	607	445	445
query14	6274	5333	5010	5010
query14_1	4324	4296	4319	4296
query15	220	214	186	186
query16	1013	489	444	444
query17	980	742	612	612
query18	2456	487	362	362
query19	218	194	168	168
query20	118	114	108	108
query21	234	161	139	139
query22	13721	13624	13411	13411
query23	17403	16657	16180	16180
query23_1	16466	16347	16392	16347
query24	7482	1768	1268	1268
query24_1	1346	1330	1322	1322
query25	571	470	398	398
query26	1343	371	220	220
query27	2564	591	363	363
query28	4498	2013	2025	2013
query29	1114	676	521	521
query30	347	268	228	228
query31	1118	1102	993	993
query32	109	63	65	63
query33	572	336	263	263
query34	1220	1156	651	651
query35	780	790	681	681
query36	1405	1437	1202	1202
query37	158	108	96	96
query38	1888	1703	1679	1679
query39	937	915	908	908
query39_1	926	878	901	878
query40	248	157	138	138
query41	65	63	63	63
query42	94	94	91	91
query43	321	328	280	280
query44	1422	789	779	779
query45	203	199	192	192
query46	1101	1222	741	741
query47	2301	2305	2198	2198
query48	401	422	301	301
query49	581	426	319	319
query50	1092	447	334	334
query51	10936	10931	10749	10749
query52	88	88	76	76
query53	256	276	204	204
query54	290	233	237	233
query55	78	72	66	66
query56	303	291	281	281
query57	1428	1398	1309	1309
query58	281	251	248	248
query59	1572	1657	1474	1474
query60	327	270	254	254
query61	156	153	156	153
query62	695	644	587	587
query63	247	203	217	203
query64	2836	1033	883	883
query65	4897	4751	4810	4751
query66	1831	511	390	390
query67	29707	29614	29516	29516
query68	3016	1652	1084	1084
query69	418	294	263	263
query70	1068	945	969	945
query71	350	323	298	298
query72	3115	2698	2440	2440
query73	809	862	425	425
query74	5105	4963	4770	4770
query75	2617	2619	2241	2241
query76	2305	1215	820	820
query77	359	382	320	320
query78	12479	12321	11817	11817
query79	1399	1194	776	776
query80	1299	542	455	455
query81	526	332	282	282
query82	630	157	120	120
query83	370	322	304	304
query84	341	164	132	132
query85	974	581	522	522
query86	438	300	288	288
query87	1839	1822	1778	1778
query88	3778	2871	2814	2814
query89	459	414	352	352
query90	1912	205	199	199
query91	204	187	169	169
query92	64	62	59	59
query93	1696	1532	994	994
query94	739	381	335	335
query95	827	584	477	477
query96	1109	834	362	362
query97	2715	2701	2529	2529
query98	217	208	201	201
query99	1168	1187	1038	1038
Total cold run time: 266939 ms
Total hot run time: 180919 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.10	0.05	0.04
query3	0.25	0.13	0.14
query4	1.61	0.14	0.13
query5	0.26	0.23	0.25
query6	1.27	1.09	1.08
query7	0.03	0.01	0.00
query8	0.06	0.04	0.04
query9	0.40	0.33	0.32
query10	0.56	0.62	0.57
query11	0.19	0.15	0.15
query12	0.19	0.14	0.14
query13	0.48	0.50	0.49
query14	1.04	0.99	1.01
query15	0.62	0.61	0.61
query16	0.32	0.34	0.34
query17	1.10	1.12	1.16
query18	0.22	0.22	0.23
query19	2.03	2.00	2.01
query20	0.02	0.01	0.01
query21	15.47	0.22	0.15
query22	4.79	0.05	0.05
query23	16.12	0.33	0.12
query24	3.04	0.39	0.32
query25	0.11	0.05	0.04
query26	0.72	0.20	0.15
query27	0.05	0.04	0.03
query28	3.48	0.99	0.54
query29	12.50	4.15	3.25
query30	0.27	0.15	0.18
query31	2.77	0.60	0.31
query32	3.23	0.60	0.49
query33	3.12	3.18	3.19
query34	15.54	4.26	3.50
query35	3.52	3.55	3.54
query36	0.56	0.43	0.42
query37	0.08	0.06	0.06
query38	0.06	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.16	0.16
query41	0.09	0.03	0.03
query42	0.04	0.03	0.04
query43	0.05	0.03	0.04
Total cold run time: 96.57 s
Total hot run time: 25.23 s

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