Skip to content

[improvement](hive) Support partition-column-value-only pushdown for Hive/Hudi scan - #68452

Open
liutang123 wants to merge 19 commits into
apache:masterfrom
liutang123:opt-partition-scan-master
Open

liutang123 wants to merge 19 commits into
apache:masterfrom
liutang123:opt-partition-scan-master

Conversation

@liutang123

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary:

SELECT max(dt) FROM hive_tbl (and the dt = (SELECT max(dt) FROM ...) latest-partition pattern) currently scans the whole table although the answer is already in the partition metadata. Add PARTITION_VALUE pushdown: the scan emits one row of partition column values per data file without opening any file.

Changes, in order:

  1. gensrc: add TPushAggOp.PARTITION_VALUE.
  2. BE: add PartitionColumnReader and install it in FileScanner when every requested column is a partition column.
  3. FE: add session variable enable_partition_column_value_only_optimization (default true).
  4. FE: add the two AggregateStrategies rules plus the PARTITION_VALUE helpers, including the two rules that cross a LogicalFilter.
  5. FE: treat a CTE producer/consumer, PartitionTopN and a no-group-by aggregate as effective runtime-filter sources, so the runtime filter is not pruned away.
  6. FE: add ConnectorCapability.SUPPORTS_PARTITION_VALUE_ONLY (HIVE/HUDI only) and plumb ConnectorScanRequest.partitionValuePushdown, so Hive stops splitting files for this scan.

Release note

New session variable enable_partition_column_value_only_optimization (default true): a min/max aggregation over only Hive/Hudi partition columns is answered from partition metadata without reading data files.

Check List (For Author)

  • Test: No need to test (with reason) — not run yet: the local FE build fails on a pre-existing thrift version mismatch (thrift 0.16.0 vs libthrift 0.24.0) and BE was not compiled. Regression tests still to be added.
  • Behavior changed: Yes — see the release note; gated by the new session variable.
  • Does this need documentation: No

What problem does this PR solve?

Issue Number: close #xxx

Related PR: #xxx

Problem Summary:

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

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

@liutang123

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: nine P1 and two P2 issues are detailed inline. The current patch can change query results for empty, deleted, and volatile inputs; it misses default and parallel execution paths; and it leaves both the BE build and connector SPI policy broken.

Critical checkpoint conclusions:

  • Goal and proof: Not achieved end to end. Default FileScannerV2 does not use the shortcut, Hudi cannot acquire the capability, and no test demonstrates the intended path.
  • Scope and focus: The separate RuntimeFilterPruner changes broaden the PR and include the RANK/DENSE_RANK issue below. No additional user focus was supplied.
  • Concurrency: No independent lock race was found, but statement-scope scan reuse omits the mode that changes split shape, so planning order between aliases can select the wrong cached result.
  • Lifecycle and static state: No new static-initialization or ownership leak was found. The read lifecycle is incorrect for transactional Hive because the shortcut runs before delete-delta handling.
  • Configuration: The new session variable is visible and dynamically read, but defaults on while the default V2 scanner path is unsupported.
  • Compatibility: The Thrift value is appended and unsupported readers otherwise fall back to normal scans, but an exhaustive BE switch is not updated and the public connector SPI baseline/API major are unchanged.
  • Parallel paths: FileScannerV2, the large-partition batch request, and delegated Hudi capability routing are not updated consistently.
  • Conditions and invariants: Eligibility incorrectly treats range existence as row existence, admits ACID tables, ignores volatile/NoneMovable evaluation cardinality, and treats peer-preserving RANK/DENSE_RANK as bounded.
  • Tests and results: No test file changed, and the PR reports that FE/BE were not built. The planner rule, wire value, V1/V2 readers, empty/ACID negatives, split reuse/batch modes, SPI surface, and RF pruning are unproved.
  • Observability: Existing scan logging and profiles are sufficient to trace planning; no separate instrumentation defect was found, but absent execution evidence leaves the new path unverified.
  • Transactions and persistence: This is a read-only change with no EditLog mutation; full-ACID delete deltas are nevertheless bypassed.
  • Data writes and crash safety: No write path changes.
  • FE-BE variable passing: Translator and Thrift propagation exist, but the default V2 consumer and batch planning request are incomplete.
  • Performance: Default V2 can read unsplit whole files, reuse can select the wrong granularity, batch mode creates duplicate ranges, and the RF change can retain a full-domain filter.
  • Memory and nullability: No independent allocation or nullability defect was found; explicit partition null markers use the existing filler contract.
  • Other error handling: Catching Throwable converts fatal VM/linkage and invariant failures into a normal optimization miss.

Validation was static only, as required by this review runner; no builds or tests were run.

Comment thread be/src/format/partition_column_reader.h Outdated
Comment thread be/src/exec/scan/file_scanner.cpp Outdated
Comment thread gensrc/thrift/PlanNodes.thrift

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

Request changes. A live exact-head review (5293038630) landed during this review with 11 inline comments covering every substantiated finding. I treated those comments as hard duplicate fences and am not reposting substantially similar inline issues.

Checkpoint conclusions:

  • Goal and semantics: the partition-only shortcut is not correct or complete end to end. Existing threads cover volatile/non-movable expression cardinality, visible-row and full-ACID violations, and the pure-GROUP-BY retained-project eligibility gap.
  • Control flow and parallel paths: FE eligibility, translation, synchronous/batch planning, statement reuse, V1/V2 scanner selection, and base-scanner final conjunct handling were traced. Existing threads cover default V2, batch propagation, reuse identity, and delegated Hudi routing.
  • Data lifecycle: exact-head threads cover positive-byte empty files, tolerated missing files, and full-ACID delete deltas. No write or persistence path changes are in scope.
  • Configuration and compatibility: the optimization is default-on. Existing threads cover the frozen connector SPI/API-major gate and the exhaustive BE enum switch. Current old-BE consumers otherwise fall back to an ordinary scan, so no separate mixed-version correctness defect was substantiated.
  • Concurrency and state: no independent shared-state race survived review. RuntimeFilterPruner state is query-scoped; an existing live thread covers the RANK/DENSE_RANK peer-tie defect.
  • Conditions and error handling: existing threads cover broad catch (Throwable), non-movable evaluation behavior, and scan-range-versus-visible-row invariants.
  • Tests and observability: no test file changed. Required coverage includes planner eligibility, forced V1/default V2, empty/missing/full-ACID inputs, batch/reuse ordering, Hudi routing, connector SPI, and RuntimeFilterPruner tie behavior.
  • User focus: no additional focus points were provided.

Validation was static only, as required by the review task: I did not run builds or tests. The PR description also reports no tests run; compile/API-gate conclusions in the existing review are based on direct static inspection, not executed validation. No additional inline comment is included because the live exact-head threads already cover all accepted findings.

924060929
924060929 previously approved these changes Sep 24, 2026
@liutang123

Copy link
Copy Markdown
Contributor Author

/review

@liutang123

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.

COMPLETE static review of PR #68452 at head 32334ac6d3fbe2b1445e4508fdd5b605d400bb0e. I reviewed all 31 changed files, their relevant callers and readers, all existing inline threads, and three convergence rounds. Three new issues are attached inline (two P1, one P2). No user-specific focus was supplied. Previously reported P1 issues were independently checked as addressed on this head, so no existing blocking comment is carried forward. No build or test was run under the review contract.

Required checkpoint conclusions:

  1. Goal and proof: The change aims to answer partition-only Hive aggregates from metadata and refine runtime-filter pruning. The guarded V1/V2 reader and planner paths generally support that goal, but the retained-filter fallback loses splitting (P2), and the new regression fixture cannot exercise its intended null-partition data (P1). Added tests were inspected only; their results were not independently observed.
  2. Scope and clarity: The FE, connector, SPI, Thrift, BE, and test edits are related to those features. The two new test-source import placements break the FE style gate (P1); no separate unrelated change was identified.
  3. Concurrency and locks: Checked statement-scoped Hive split reuse, normal/batch planning, per-run runtime-filter state, and BE reader ownership. No new shared mutable state, lock-order problem, or heavy work under a new lock was substantiated.
  4. Lifecycle and static initialization: Checked V1/V2 scanner setup, EOF, fallback, and close paths, plus CTE producer/consumer traversal. No new cross-translation-unit static initialization dependency or separate lifecycle failure was substantiated.
  5. Configuration: The new optimization session variable is read when the plan is built, and regular and batch scan requests transport its selected mode. The default-enabled filtered plan still causes the P2 performance regression.
  6. Compatibility: The connector API version and surface baseline were updated, and the Thrift enum was appended with BE dispatch covered. Older/unsupported readers can take ordinary scan fallback; that path can share the P2 whole-file cost. No distinct confirmed rolling-upgrade failure remained.
  7. Parallel paths: Checked V1 and V2, Parquet and ORC, normal and batched Hive planning, sampled scans, Hudi delegation, and transactional-table exclusion. The filtered shortcut mismatch is the one confirmed parallel-path issue.
  8. Conditional guards: Inspected partition-slot, volatile-expression, sampling, no-predicate/delete, selected-footer-range, and TopN bound guards. The retained scan conjunct blocks both BE shortcuts after Hive has already disabled file splitting (P2); no separate wrong-result case was substantiated.
  9. Test coverage and results: The new BE/FE tests and Hive regression assertions were read, not run. The Hive fixture has two combined JDBC statements (P1), and FE test imports violate Checkstyle at validate (P1). Result and EXPLAIN assertions do not prove the filtered shortcut avoids data reads or retains parallel splits.
  10. Observability: Existing EXPLAIN mode and scan metrics help trace the path, but the added tests have no read/split counter assertion to expose the P2 fallback. No separate production metric defect was substantiated.
  11. Transactions, persistence, writes, and crash recovery: Product changes are read/plan paths and add no EditLog or data-write protocol. Transactional Hive is excluded from the shortcut. The regression fixture writes only test tables; its JDBC statement issue is reported inline. No separate persistence or crash issue was found.
  12. FE–BE transport: The new operation passes through the FE scan request, Thrift enum, and both BE reader paths, including batch mode. The retained filter reaches both BE guards, explaining the P2 fallback.
  13. Performance and remaining issues: Whole-file serial reads for filtered partition aggregates are the confirmed performance defect. All other candidates were either merged into the three comments or dismissed with code evidence; no suspicious point remains unresolved.

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.79% (2073/2665)
Line Coverage 65.94% (38058/57716)
Region Coverage 53.50% (35752/66831)
Branch Coverage 56.95% (11506/20204)

@liutang123

Copy link
Copy Markdown
Contributor Author

/review

@liutang123

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 complete for head 61c55d34acb73436a204d9674bce9ca77df2035a. I found one new P2 performance issue in CTE runtime-filter pruning (inline). All previously reported P1 issues were rechecked against this head and are resolved; no earlier P0/P1 comment ID needs carrying forward. The existing P2 threads remain covered and are not reposted. There was no additional user review focus.

  • Goal and behavior: The change plans partition-value MIN/MAX and grouping for eligible Hive scans, translates PARTITION_VALUE through Thrift, and uses footer-backed nonempty checks in both BE scanner paths. The implementation reaches the intended paths under its stated guards, subject to the inline runtime-filter issue.
  • Scope and design: I traced the FE rewrite, connector capability and split planning, V1/V2 readers, and runtime-filter postprocessing. The cross-module changes are related to the feature. CTE source inheritance is broader than the producer key semantics support, causing the one finding below.
  • Concurrency and lifecycle: No new worker or lock was added. Scan reuse uses the existing concurrent statement cache, and CTE effectiveness state belongs to a fresh postprocessor instance. Reader initialization, synthetic-row production, EOF, fallback, and close were traced; no separate lifetime or ownership defect was substantiated.
  • Configuration and compatibility: The new session switch is read while planning each statement. The FE-to-BE enum translation, BE enum switch, connector SPI surface baseline, and API version 13.0 were checked. No distinct protocol failure was substantiated; mixed-version execution was not run.
  • Parallel paths and conditions: V1 and default V2 paths, split ranges, Parquet/ORC, empty or missing files, residual predicates, runtime filters, deletes, TABLESAMPLE, volatile expressions, and unsupported/transactional/delegated tables were reviewed. The guarded cases retain ordinary scan semantics and Hive keeps normal split sizing on fallback.
  • Tests and results: BE/FE/connector unit tests and the Hive regression changes were inspected, including negative and empty-file cases. No test or build was run in this review environment, so these are coverage observations, not execution results. There is no new result file. A two-consumer CTE plus left-join test is needed for the inline issue; mixed-version runtime behavior and actual I/O savings remain unverified here.
  • Observability, errors, and memory: Existing scanner/profile reporting remains available; no separate observability gap or new tracked-allocation issue was substantiated. The reviewed missing/corrupt-file and reader-error paths retain failure/fallback behavior rather than silently inventing a row.
  • Transactions, persistence, and writes: This is a read-only scan/planner feature. Transactional Hive is excluded, and the patch does not alter Doris data writes, EditLog, visible-version handling, or delete-bitmap persistence.
  • Performance and remaining issues: The accepted CTE case retains a runtime filter that rejects no rows and adds build/transfer work; it can also prevent the metadata shortcut. No other distinct correctness or significant performance issue survived three review rounds and the final 29-file sweep.

The PR description still mentions Hudi support and older test coverage; please update it to match this head.

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.79% (2073/2665)
Line Coverage 65.99% (38085/57716)
Region Coverage 53.54% (35781/66831)
Branch Coverage 56.94% (11504/20204)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	16599	4002	3869	3869
q2	2225	384	304	304
q3	9531	1476	829	829
q4	4504	484	347	347
q5	7298	858	556	556
q6	185	167	136	136
q7	741	799	595	595
q8	9281	1714	1646	1646
q9	5550	4247	4237	4237
q10	6826	1319	1019	1019
q11	452	270	244	244
q12	646	419	290	290
q13	17372	2623	2037	2037
q14	258	264	233	233
q15	q16	727	717	654	654
q17	1755	1112	1004	1004
q18	6513	5642	5548	5548
q19	1165	1288	1060	1060
q20	470	389	266	266
q21	5252	2881	3306	2881
q22	449	378	313	313
Total cold run time: 97799 ms
Total hot run time: 28068 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4708	4682	4801	4682
q2	716	554	517	517
q3	4850	5154	4639	4639
q4	2240	2334	1484	1484
q5	4609	4371	4389	4371
q6	231	176	139	139
q7	1934	1705	1500	1500
q8	2368	2022	2196	2022
q9	7344	7381	7432	7381
q10	3687	3644	3139	3139
q11	532	426	360	360
q12	730	705	508	508
q13	2342	2635	1981	1981
q14	269	275	245	245
q15	q16	654	681	602	602
q17	7318	6743	6863	6743
q18	11943	11064	11812	11064
q19	1127	1027	1042	1027
q20	2217	2208	1880	1880
q21	5022	4123	4312	4123
q22	520	462	404	404
Total cold run time: 65361 ms
Total hot run time: 58811 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152441 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 61c55d34acb73436a204d9674bce9ca77df2035a, data reload: false

query5	4312	595	458	458
query6	437	217	186	186
query7	4852	605	296	296
query8	325	177	170	170
query9	8861	4043	4048	4043
query10	467	309	263	263
query11	5780	3574	3233	3233
query12	145	93	88	88
query13	1283	578	426	426
query14	6568	4560	4253	4253
query14_1	3918	3982	3924	3924
query15	199	201	187	187
query16	993	490	432	432
query17	928	689	538	538
query18	2430	455	339	339
query19	214	185	144	144
query20	90	83	86	83
query21	236	136	116	116
query22	13024	13019	12757	12757
query23	14090	13149	12584	12584
query23_1	12453	12490	12559	12490
query24	6950	1136	640	640
query24_1	684	708	710	708
query25	564	445	380	380
query26	1274	305	166	166
query27	2729	530	331	331
query28	4427	1975	1970	1970
query29	1584	720	519	519
query30	300	240	192	192
query31	902	758	626	626
query32	149	102	99	99
query33	529	312	262	262
query34	1169	1111	643	643
query35	699	746	655	655
query36	772	785	750	750
query37	148	111	95	95
query38	1815	1770	1746	1746
query39	712	729	683	683
query39_1	655	652	653	652
query40	221	128	109	109
query41	70	67	68	67
query42	96	92	96	92
query43	343	344	299	299
query44	1367	706	712	706
query45	186	176	165	165
query46	1101	1177	753	753
query47	1514	1505	1447	1447
query48	395	417	296	296
query49	579	396	291	291
query50	1045	356	240	240
query51	10354	10230	10343	10230
query52	89	90	73	73
query53	241	254	176	176
query54	256	220	201	201
query55	80	77	67	67
query56	225	213	216	213
query57	1492	1408	1246	1246
query58	278	268	252	252
query59	2001	2059	1875	1875
query60	276	241	222	222
query61	147	150	153	150
query62	423	318	287	287
query63	214	176	181	176
query64	2737	1016	801	801
query65	3472	3418	3379	3379
query66	1821	430	310	310
query67	20155	20041	19758	19758
query68	3433	1502	874	874
query69	432	307	262	262
query70	892	805	823	805
query71	295	235	211	211
query72	2737	2574	2211	2211
query73	826	739	440	440
query74	4624	4494	4303	4303
query75	2301	2270	1913	1913
query76	2004	1093	720	720
query77	358	399	299	299
query78	9298	9152	8449	8449
query79	1336	1188	732	732
query80	850	462	370	370
query81	565	317	281	281
query82	605	159	125	125
query83	313	223	191	191
query84	331	147	110	110
query85	1026	483	383	383
query86	379	241	236	236
query87	1979	1973	1840	1840
query88	3609	2736	2698	2698
query89	365	288	248	248
query90	1748	185	173	173
query91	169	155	123	123
query92	113	91	90	90
query93	1493	1496	923	923
query94	608	326	293	293
query95	665	363	425	363
query96	995	761	320	320
query97	2428	2415	2295	2295
query98	162	152	141	141
query99	741	735	615	615
Total cold run time: 236067 ms
Total hot run time: 152441 ms

@hello-stephen

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

query1	0.01	0.00	0.01
query2	0.09	0.05	0.05
query3	0.26	0.13	0.13
query4	1.63	0.14	0.14
query5	0.24	0.22	0.22
query6	1.15	0.91	0.92
query7	0.04	0.01	0.01
query8	0.06	0.04	0.03
query9	0.39	0.34	0.33
query10	0.54	0.58	0.58
query11	0.20	0.15	0.14
query12	0.19	0.15	0.15
query13	0.47	0.46	0.47
query14	0.96	0.94	0.93
query15	0.59	0.58	0.57
query16	0.32	0.30	0.30
query17	1.12	1.08	1.09
query18	0.22	0.20	0.19
query19	1.99	1.91	1.97
query20	0.02	0.01	0.01
query21	15.49	0.20	0.14
query22	4.93	0.05	0.05
query23	16.19	0.30	0.12
query24	2.97	0.40	0.31
query25	0.12	0.05	0.04
query26	0.76	0.19	0.15
query27	0.05	0.04	0.04
query28	3.54	0.80	0.36
query29	12.49	4.16	3.25
query30	0.28	0.17	0.15
query31	2.77	0.56	0.32
query32	3.22	0.59	0.49
query33	3.17	3.26	3.26
query34	15.75	3.95	3.29
query35	3.21	3.20	3.21
query36	0.55	0.44	0.41
query37	0.08	0.06	0.06
query38	0.05	0.04	0.03
query39	0.04	0.03	0.03
query40	0.18	0.15	0.15
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.5 s
Total hot run time: 24.08 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 90.51% (124/137) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 85.40% (117/137) 🎉
Increment coverage report
Complete coverage report

@liutang123
liutang123 force-pushed the opt-partition-scan-master branch from 7159421 to b945d1c Compare October 2, 2026 12:16
liutang123 added 2 commits October 2, 2026 23:32
…Hive/Hudi scan

### What problem does this PR solve?

Problem Summary:

`SELECT max(dt) FROM hive_tbl` (and the `dt = (SELECT max(dt) FROM ...)` latest-partition
pattern) currently scans the whole table although the answer is already in the partition
metadata. Add PARTITION_VALUE pushdown: the scan emits one row of partition column values
per data file without opening any file.

Changes, in order:

1. gensrc: add `TPushAggOp.PARTITION_VALUE`.
2. BE: add `PartitionColumnReader` and install it in `FileScanner` when every requested
   column is a partition column.
3. FE: add session variable `enable_partition_column_value_only_optimization` (default true).
4. FE: add the two `AggregateStrategies` rules plus the PARTITION_VALUE helpers, including
   the two rules that cross a `LogicalFilter`.
5. FE: treat a CTE producer/consumer, `PartitionTopN` and a no-group-by aggregate as
   effective runtime-filter sources, so the runtime filter is not pruned away.
6. FE: add `ConnectorCapability.SUPPORTS_PARTITION_VALUE_ONLY` (HIVE/HUDI only) and plumb
   `ConnectorScanRequest.partitionValuePushdown`, so Hive stops splitting files for this scan.

### Release note

New session variable `enable_partition_column_value_only_optimization` (default true): a
min/max aggregation over only Hive/Hudi partition columns is answered from partition
metadata without reading data files.

### Check List (For Author)

- Test: No need to test (with reason) — not run yet: the local FE build fails on a
  pre-existing thrift version mismatch (thrift 0.16.0 vs libthrift 0.24.0) and BE was not
  compiled. Regression tests still to be added.
- Behavior changed: Yes — see the release note; gated by the new session variable.
- Does this need documentation: No
liutang123 and others added 9 commits October 2, 2026 23:32
… pushdown

### What problem does this PR solve?

Problem Summary:

The first version of this optimization treated "the scan range exists" as "the source
contributes a row": it opened no file and always emitted one partition row. A file with a
nonzero header but zero rows, or a transactional Hive base whose rows are all deleted by
delete deltas, therefore made MAX/GROUP BY/DISTINCT return a partition that a normal scan
never yields. The default FileScannerV2 path did not participate at all, so with
enable_file_scanner_v2=true (the default) the feature was inactive while the connector had
already stopped splitting files.

Changes, in order:

1. BE: replace the unconditional synthetic reader with a decorator that emits one partition
   row only after the real Parquet/ORC metadata proves the range is nonempty; unsupported
   formats, transactional tables, deletes, pending runtime filters and unproven counts fall
   back to a normal scan.
2. BE: add PARTITION_VALUE to FileScannerV2 aggregate pushdown, reuse the metadata count
   request, and add the missing generated-enum switch case.
3. Remove the nonexistent compile_check header include (4.1-only) from the new BE header.
4. FE: centralize one eligibility check for every PARTITION_VALUE entry point, rejecting
   volatile/NoneMovable aggregate, project and filter expressions and TABLESAMPLE; drop the
   Throwable catch; read partition columns at the scan reference snapshot, locale-independently.
5. FE: mark a PartitionTopN an effective runtime-filter source only for a real row bound
   (ROW_NUMBER without partition keys, or a global limit), and inherit child effectiveness
   instead of claiming one; keep CTE producer->consumer inheritance.
6. Connector: grant the capability only to nontransactional Hive Parquet/ORC, remove the
   unreachable Hudi claim, include the effective split size in the statement reuse key, and
   forward the mode on the batch split path.
7. Tests: FE unit tests for the rule and the pruner, connector tests for capability/reuse/
   batch, BE tests over real Parquet/ORC footers, and a Hive regression comparing every
   query against an optimization-off baseline across V1/V2.

### Release note

Session variable enable_partition_column_value_only_optimization now applies to
nontransactional Hive Parquet/ORC tables only, and yields a partition value only for ranges
whose file metadata proves at least one row; it is effective on the default FileScannerV2
path as well.

### Check List (For Author)

- Test: Regression test / Unit Test. FE unit and connector tests pass
  (PhysicalStorageLayerAggregateTest 19, RuntimeFilterTest targeted 3, SPI 11, Hive 56).
  BE tests could not be run: run-be-ut.sh fails configuring contrib/openblas (pre-existing,
  unrelated), and build.sh --fe stops at "Thirdparty libraries need to be build" and its
  cleanup of thirdparty/installed needs confirmation. The Hive regression requires the
  docker Hive environment (enableHiveTest=false locally) and was not executed.
- Behavior changed: Yes — see the release note.
- Does this need documentation: No
### What problem does this PR solve?

Problem Summary:

The Hive regression fixture built its null partition with one hive_docker call holding both
"set hive.exec.dynamic.partition.mode=nonstrict;" and the INSERT. hive_docker passes the whole
string to a single PreparedStatement.execute() (Suite.groovy:1780-1788 -> JdbcUtils:46-53) and
only strips a trailing semicolon, so Hive received one invalid two-command string: the SET never
applied and the INSERT never ran. The ORC fixture repeated it, and its two-column dynamic insert
additionally requires nonstrict mode, so it could not have succeeded either.

The fixture therefore silently lacked the null partition value that this suite exists to cover.
Because the baseline and the optimized runs read the same reduced data, every assertEquals below
still passed - the suite proved nothing about null or empty partition handling.

Changes:

1. Issue the SET and each INSERT as separate hive_docker calls; the Hive connection is
   thread-local and reused (SuiteContext.groovy:287-299), so the setting carries over.
2. Assert the fixture itself before querying Doris: p=4 must hold the null partition value and
   p=9 must be empty, for both the Parquet and the ORC table. A silently skipped insert now
   fails loudly instead of making the comparisons vacuous.

### Release note

None

### Check List (For Author)

- Test: Regression test. Not executed locally: it requires the docker Hive environment
  (enableHiveTest=false). Only the test file changed.
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Problem Summary:

Sparked by review: the connector stopped splitting files whenever the plan carried the
PARTITION_VALUE hint, but whether a reader actually takes the reduced path depends on
conditions the connector cannot see. A retained filter (SELECT MAX(p) FROM t WHERE p >= 2)
becomes scan conjuncts, and V1 requires _conjuncts.empty() while V2 requires the same; a
runtime filter that has not arrived fails V1 as well. In every such case the reader falls back
to an ordinary scan, yet the file had already been planned as one range, so a large surviving
file was read serially by a single scanner instead of the split count a normal scan uses --
slower than the baseline, not merely without gain. Result and EXPLAIN assertions cannot see
this, because the plan still reports PARTITION_VALUE in the declined case.

Changes, in order:

1. Drop the partitionValuePushdown hint: ConnectorScanRequest, both PluginDrivenScanNode
   request builders, the Hive connector's split sizing and its reuse key. Splitting is now
   planned exactly as for any other scan, so a declined reader costs nothing extra.
2. Keep V1's whole-range requirement and document why: a partial range's row count is only
   dependable when the Parquet reader filters row groups by range, and ORC's count reads 0
   until its row reader exists, which would drop a partition instead of duplicating a row.
   FileScannerV2 has no such requirement, so the default path still benefits per split.
3. Update the connector tests: batch planning now asserts ordinary splits, and the two
   reuse-isolation cases collapse into one, since differing split shapes no longer occur.

### Release note

None

### Check List (For Author)

- Test: Unit Test. Connector and SPI suites pass (76 tests, BUILD SUCCESS). The Hive
  regression is unchanged and still requires the docker environment (not executed). BE tests
  remain blocked by the pre-existing contrib/openblas configuration failure.
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Problem Summary:

The CTE producer->consumer inheritance copied whatever effective-source type the producer
root carried. NATIVE is not only set for a bounded relation: the join visitor also marks a
join NATIVE when its build side is selective, which is a property of one join key. For
c = Project(A.k, B.v) -> LeftJoin(A, Limit(1) -> B) the limited build side makes the join
visitor mark the producer NATIVE even though a LEFT JOIN preserves every A.k, so the producer
output is not bounded.

Copying that flag to every consumer of the CTE let a consumer that only keeps B.v live inherit
an effectiveness it cannot justify. Used as the build side of another join, it bypassed the
statistics-based runtime-filter pruning, so with un-analyzed external tables sharing the full
key domain the filter was built, transferred and evaluated per row while rejecting no row.

Changes:

1. Record a producer in the CTE map only when its effectiveness is NATIVE and its plan root
   has a genuinely bounded output: Limit, TopN, AssertNumRows, a no-group-by aggregate, or a
   PartitionTopN with a global limit (or ROW_NUMBER without partition keys).
2. Add a two-consumer test for the reviewed shape: the join is still marked from its build
   side, but neither the consumer keeping A.k nor the one keeping B.v inherits anything.

### Release note

None

### Check List (For Author)

- Test: Unit Test. RuntimeFilterTest (4 targeted) and PhysicalStorageLayerAggregateTest (19)
  pass, BUILD SUCCESS.
- Behavior changed: No
- Does this need documentation: No
…cers

A CTE producer handed its effectiveness to consumers only when its plan root was a
bounded operator. The root is almost always a Project or Distribute, so the canonical
`WITH m AS (SELECT max(p) AS p FROM t)` shape lost the runtime filter that prunes the
probe-side scan, and a producer read through a partition filter lost it too -- which
defeats the partition-value optimization this branch adds.

Rename hasBoundedOutput to hasRelationGlobalEffectiveness and let it accept an
Intersect, a predicate on a visible column, and look through Project/Distribute.
Propagate REF unchanged (criterion 4); keep NATIVE gated so a join's build-side
selectivity still cannot leak through a CTE.

Tests: RuntimeFilterTest 44/44; new CTE cases in the Hive partition-value regression
(noStatsRfPrune/query23 keeps two runtime filters it should); shape_check tpcds_sf100
rf_prune/shape 98/98, noStatsRfPrune 97/98 (query24, unrelated to this branch).

Co-Authored-By: Claude Code <noreply@anthropic.com>
@liutang123
liutang123 force-pushed the opt-partition-scan-master branch from fb77613 to ea7a6de Compare October 2, 2026 15:32
@liutang123

Copy link
Copy Markdown
Contributor Author

run buildall

@liutang123

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.

Review complete for PR #68452 at ea7a6de. I covered all 30 changed files, three normal/risk convergence rounds, the existing threads, and a final changed-file sweep. Two new P2 runtime-filter performance findings are inline. The previously reported P0/P1 cases have targeted fixes at this head, so no existing blocking comment ID is carried. No additional user focus was supplied.

Critical checkpoints:

  • Goal and proof: the change reduces partition-only Hive aggregation to one row per proven-nonempty Parquet/ORC range and retains useful CTE runtime filters. BE, FE, connector, and Hive regression tests were added, but they do not cover the two bounded grouped/window CTE shapes reported below.
  • Size and clarity: the FE rule, Hive capability, SPI/Thrift mode, and V1/V2 reader implementations are scoped to this feature; the CTE pruning helper has two missed row-preserving paths.
  • Concurrency: Hive statement-scope reuse uses its existing concurrent map, reader state is per scan/split, and CTE effectiveness state is per pruner invocation. I found no new shared mutable state, lock order, or heavy work inside a new lock.
  • Lifecycle: V1 initializes the real reader before wrapping its footer count; V2 checks each split before materializing a partition row. Empty counts and rejected shortcuts follow ordinary reader/close paths. No new cross-translation-unit static initialization dependency was found.
  • Configuration: the new session variable is read during planning and can affect subsequent statements without process restart; no process-wide dynamic setting was added.
  • Compatibility: connector API is versioned to 13.0 with its surface baseline; the Thrift enum, FE translator, and current BE switch agree. Mixed-version execution was not exercised.
  • Parallel paths: both V1 and default V2 scanner paths, batch and reused Hive splits, and fallback when filters or deletes apply were checked. Normal split sizing remains independent of the optimization hint.
  • Conditions: FE restricts the rewrite to partition-only, duplicate-insensitive aggregates and excludes volatile/nonmovable expressions and sampling; BE gates physical format, projection, filters, pending RFs, deletes, and positive row counts. The two new CTE source checks are too narrow for bounded children.
  • Tests and expected results: I inspected BE/FE unit cases, the Hive fixture and V1/V2 comparisons, negative cases, and the updated RF shape output. The missing grouped/window CTE cases are called out inline. This was a static review; no build or test was run, so runtime results are unverified.
  • Observability: existing scan profiles and EXPLAIN RF assertions expose most changed paths; the missing RF plans need specific EXPLAIN coverage.
  • Transactions, persistence, and writes: no storage write, EditLog, or transaction path changed; transactional Hive is excluded from the capability.
  • FE-to-BE state: PARTITION_VALUE is translated into the appended Thrift enum and handled by both current scanner implementations.
  • Performance and remaining issues: ordinary split sizing protects fallback parallelism; M2 and M3 can still discard selective Hive partition RFs and force extra scan work. No other distinct reachable issue survived deduplication and the final sweep.

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.79% (2073/2665)
Line Coverage 65.99% (38084/57716)
Region Coverage 53.49% (35749/66831)
Branch Coverage 57.01% (11518/20204)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17707	4120	4134	4120
q2	2210	373	326	326
q3	9944	1410	789	789
q4	4683	476	358	358
q5	7534	839	556	556
q6	193	173	139	139
q7	748	782	616	616
q8	9333	1547	1617	1547
q9	5894	4187	4165	4165
q10	6871	1331	1021	1021
q11	450	261	243	243
q12	632	409	293	293
q13	18103	2621	1994	1994
q14	258	266	234	234
q15	q16	740	725	655	655
q17	1835	1185	1004	1004
q18	6441	5603	5551	5551
q19	1193	1224	1112	1112
q20	463	400	252	252
q21	5343	3444	2926	2926
q22	418	351	314	314
Total cold run time: 100993 ms
Total hot run time: 28215 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4747	4589	4550	4550
q2	746	570	560	560
q3	4770	5241	4573	4573
q4	2300	2328	1417	1417
q5	4585	4521	4567	4521
q6	231	178	123	123
q7	1875	1707	1479	1479
q8	2354	2145	2037	2037
q9	7411	6938	6919	6919
q10	3628	3544	3091	3091
q11	530	388	361	361
q12	706	716	501	501
q13	2275	2588	1996	1996
q14	271	291	243	243
q15	q16	668	685	612	612
q17	7380	6731	6683	6683
q18	11895	11074	11792	11074
q19	1189	1056	1088	1056
q20	2214	2175	1902	1902
q21	5034	4123	4311	4123
q22	510	455	389	389
Total cold run time: 65319 ms
Total hot run time: 58210 ms

@hello-stephen

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

query5	4319	604	471	471
query6	426	218	194	194
query7	4808	566	292	292
query8	325	177	166	166
query9	8795	4010	4012	4010
query10	494	310	256	256
query11	5826	3538	3233	3233
query12	148	89	84	84
query13	1250	609	403	403
query14	6518	4535	4277	4277
query14_1	4026	3989	3916	3916
query15	202	201	188	188
query16	990	459	466	459
query17	920	670	557	557
query18	2454	474	343	343
query19	205	187	145	145
query20	87	82	83	82
query21	236	140	118	118
query22	13014	12937	12918	12918
query23	13994	13060	12391	12391
query23_1	12545	12581	12673	12581
query24	7281	1165	657	657
query24_1	696	740	700	700
query25	569	426	370	370
query26	1264	314	173	173
query27	2718	534	320	320
query28	4549	1979	1971	1971
query29	1670	714	539	539
query30	292	213	187	187
query31	890	747	633	633
query32	158	99	95	95
query33	518	309	254	254
query34	1155	1120	649	649
query35	725	762	641	641
query36	826	795	714	714
query37	153	106	99	99
query38	1842	1764	1685	1685
query39	686	679	648	648
query39_1	643	643	629	629
query40	238	124	108	108
query41	92	71	73	71
query42	96	97	94	94
query43	345	345	304	304
query44	1360	731	724	724
query45	185	180	169	169
query46	1064	1170	760	760
query47	1514	1475	1420	1420
query48	395	467	280	280
query49	578	389	281	281
query50	947	346	262	262
query51	10607	10537	10458	10458
query52	87	92	75	75
query53	246	255	181	181
query54	252	199	176	176
query55	85	74	71	71
query56	218	222	207	207
query57	1447	1489	1221	1221
query58	287	260	245	245
query59	1980	2039	1852	1852
query60	282	238	206	206
query61	150	146	156	146
query62	399	321	272	272
query63	213	173	176	173
query64	2809	1022	813	813
query65	3470	3406	3411	3406
query66	1818	422	300	300
query67	20276	20177	19776	19776
query68	3332	1497	890	890
query69	409	324	250	250
query70	875	830	797	797
query71	294	228	208	208
query72	2658	2495	2239	2239
query73	816	831	410	410
query74	4628	4504	4284	4284
query75	2276	2277	1935	1935
query76	2309	1147	752	752
query77	360	398	288	288
query78	9080	8974	8450	8450
query79	1400	1173	735	735
query80	1253	464	379	379
query81	587	326	277	277
query82	642	169	122	122
query83	307	218	191	191
query84	316	140	110	110
query85	895	475	384	384
query86	398	240	236	236
query87	2019	1949	1824	1824
query88	3689	2735	2708	2708
query89	360	287	249	249
query90	1982	176	174	174
query91	166	160	126	126
query92	101	87	86	86
query93	1585	1486	929	929
query94	726	349	284	284
query95	651	365	327	327
query96	1132	755	319	319
query97	2398	2394	2305	2305
query98	158	152	145	145
query99	721	739	611	611
Total cold run time: 237809 ms
Total hot run time: 152423 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.14	0.10	0.08
query3	0.37	0.24	0.24
query4	1.61	0.25	0.25
query5	0.33	0.31	0.31
query6	1.15	0.66	0.66
query7	0.04	0.01	0.01
query8	0.10	0.07	0.06
query9	0.52	0.40	0.39
query10	0.57	0.57	0.60
query11	0.32	0.19	0.19
query12	0.32	0.19	0.20
query13	0.54	0.52	0.52
query14	0.86	0.87	0.86
query15	0.68	0.60	0.59
query16	0.39	0.39	0.38
query17	1.01	1.01	0.99
query18	0.29	0.28	0.29
query19	1.94	1.80	1.77
query20	0.02	0.01	0.01
query21	15.40	0.35	0.30
query22	4.92	0.13	0.13
query23	15.83	0.48	0.29
query24	2.46	0.57	0.43
query25	0.15	0.10	0.09
query26	0.74	0.27	0.21
query27	0.09	0.09	0.10
query28	3.44	0.82	0.43
query29	12.47	4.22	3.30
query30	0.37	0.27	0.26
query31	2.77	0.59	0.34
query32	3.23	0.59	0.48
query33	3.06	2.95	2.97
query34	15.69	3.92	3.26
query35	3.17	3.15	3.16
query36	0.63	0.54	0.52
query37	0.12	0.09	0.10
query38	0.08	0.07	0.06
query39	0.05	0.06	0.07
query40	0.20	0.17	0.16
query41	0.10	0.06	0.05
query42	0.06	0.06	0.05
query43	0.07	0.05	0.05
Total cold run time: 96.3 s
Total hot run time: 25.1 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 81.99% (132/161) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (44/44) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.66% (35103/45791)
Line Coverage 61.84% (396483/641192)
Region Coverage 58.24% (334378/574173)
Branch Coverage 59.14% (153781/260045)

liulijia and others added 8 commits October 3, 2026 11:41
…ndows

A grouped aggregate never adds rows, so a bound proven on its input still holds
on its output; a window keeps exactly one output row per input row. Rejecting
both meant a twice-referenced materialized CTE such as
`HashAggregate(group=k) -> Limit(1) -> HiveScan` or
`Project(p,n) -> Window(...) -> Limit(1) -> HiveScan` handed no effectiveness to
either consumer, and a join building an RF for `probe.k = c.k` then dropped it.
That hurts external tables most: RuntimeFilter.canPruneScanRanges() only
classifies PhysicalOlapScan, so a Hive target has no fallback once the build side
is not effective.

A join still stops the walk, so a join's build-side selectivity cannot leak
through a CTE.

Tests: RuntimeFilterTest 46/46 with both two-consumer plans; shape_check
tpcds_sf100 rf_prune/shape 98/98, noStatsRfPrune 97/98 (query24, unrelated);
Hive partition-value regression passes on hive3.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The plan can report pushdown agg=PARTITION_VALUE while the reader declines the
range and falls back to an ordinary scan, so asserting on the plan does not prove
the optimization took effect. Check the profile instead: with the pushdown the
scan feeds the aggregate one row per nonempty range, without it one row per data
row (measured 5 vs 8 on this fixture).

Also covers PartitionColumnReader::supports_range, which only accepts a whole
file range: a split range makes the V1 reader fall back while the plan still
claims PARTITION_VALUE.

Co-Authored-By: Claude Code <noreply@anthropic.com>
enable_file_scanner_v2=false does not reach scanner V1 for Parquet: FileQueryScanNode
stamps parquet_timestamp_semantics_version=1, which FileScanLocalState treats as a
required timestamp contract and honours only on V2. So the V1 leg of the existing
loop ran on V2 like the other leg.

Run the profile check on the ORC fixture instead, where V1 is reachable, and assert
UseScannerV2 is false so the case cannot silently drift back to V2.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Checkstyle enforces lexicographical order and blank-line-separated groups; the
imports added with the window test violated both.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Drop the "the footer must prove the range is nonempty" gate: one row of partition
values is now emitted for every scan range, so the optimization no longer depends
on the reader being able to report a row count. That also lets V1 drop the
whole-file requirement, which existed only because a partial range's count was
unreliable, and it removes the count request from the V2 path entirely.

The trade-off is deliberate: a file that turns out to hold zero rows still yields
its partition value, so MAX/GROUP BY/DISTINCT can name a partition a full scan
would not return. A partition with no file at all still yields nothing.

Grant SUPPORTS_PARTITION_VALUE_ONLY to Hudi as well, and assert on the Hudi
counterpart suite that the pushdown is planned and actually ran.

Co-Authored-By: Claude Code <noreply@anthropic.com>
… predicate

PARTITION_VALUE used to require an empty conjunct list, so a query like
`SELECT MAX(p) FROM t WHERE p >= 2` fell back to a full scan even though the
predicate only reads partition columns.

Scanner::_filter_output_block() already evaluates the scanner's conjuncts on
whatever block the table reader returns, so the one-row block synthesized for
PARTITION_VALUE is filtered like any other row. The only real requirement is
that the conjuncts read nothing but partition columns: the synthesized row
carries partition values and nothing else, so a predicate on a data column or
on a slot outside the projection would be evaluated against unrelated values.
Slotless predicates stay excluded, since they would be evaluated once here
instead of once per source row.

COUNT and MIN/MAX keep the original blanket guard: their synthetic rows are a
reduced image of the whole file, not a real row, so no conjunct may see them.

Adds `select max(p) from ... where p<=1` to the Hive suite: the answer is 1,
not the unfiltered max of 4, which discriminates this from simply dropping the
predicate.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…shdown

COUNT(DISTINCT p) over a partition column is duplicate-insensitive: the scan emits one
row of partition values per file and every row of a file carries the same values, so
deduplicating that stream yields the same value set as deduplicating every row. Same
reasoning as MIN/MAX, which were already supported.

Plain COUNT stays rejected -- it counts rows, and the synthesized stream has one row
per file, so COUNT(p) would answer with the file count. Other distinct aggregates stay
rejected too.

Tests: `count(distinct p)` positive on both fixtures; negatives for `count(p)` and
`sum(distinct p)`; and a CTE consumed twice through different shapes (a join and a
scalar subquery), which must still inherit the producer's bounded output.

Co-Authored-By: Claude Code <noreply@anthropic.com>
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.

3 participants