fix(hashmap): accept broadcast const iterator vectors - #26836
fix(hashmap): accept broadcast const iterator vectors#26836LeftHandCold wants to merge 12 commits into
Conversation
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Backport #26836 to `4.2-dev`. #26042 fixed the original prepared-parameter identity problem. Later, #26531 added strict HashMap iterator row-range validation and incorrectly treated a physical length-1 const vector as a flat vector. Prepared `GROUP BY ?` therefore fails whenever the input batch contains more than one logical row. This change preserves strict bounds for flat vectors while accepting non-empty const and const-null vectors as broadcast inputs, matching the existing IntHashMap and StrHashMap encoders. Zero-length const vectors are still rejected whenever rows are requested. Validation on the `4.2-dev` base (`4066fffaf7`): - malformed-flat and const-broadcast regression tests, `-count=10` - full `pkg/common/hashmap` test and race test - `go build` and `go vet` for `pkg/common/hashmap` - `validateIteratorVectors` coverage: `100%` The cherry-picked commit retains `-x` provenance for #26836. Approved by: @XuPeng-SH
XuPeng-SH
left a comment
There was a problem hiding this comment.
Blocking correctness issue: #25992 remains only partially fixed. On the exact PR head (f3cda59478f8120a7377cd36a35554cff3885e5d), a prepared projection constant still silently collapses grouped output:
CREATE TABLE metric_rows (bucket VARCHAR(20), value_col INT);
INSERT INTO metric_rows VALUES ('a', 10), ('b', 20), ('c', 30);
PREPARE p FROM
'SELECT ? AS projection_value, SUM(value_col) AS total
FROM metric_rows
GROUP BY bucket
ORDER BY total';
SET @v = 7;
EXECUTE p USING @v;Actual result:
7 10
Expected result, confirmed by the nearest literal oracle (SELECT 7 AS ...) and by selecting bucket:
7 10
7 20
7 30
I reproduced the same one-row result through both text PREPARE/EXECUTE and the MySQL binary prepared-statement protocol. EXPLAIN for the prepared and literal forms both retains Aggregate Group Key: bucket, so this is execution/output cardinality loss rather than the grouping being optimized away.
The iterator validation change does fix the other reproduction (GROUP BY ?) and its hashmap unit/race/counterexample tests pass. However, this PR says Fixes #25992; merging it would close that issue while its silent wrong-result branch remains.
Please either fix the projection-constant broadcast/result-cardinality path and add text plus binary-protocol regression coverage, or track that branch in a separate issue and adjust the issue-closing semantics before merge.
|
Addressed the blocking saved-result cardinality review in Root cause confirmed: the public INET/INET6 NULL shape can carry The saved-result boundary now enforces one invariant before
Deterministic evidence:
Fresh validation on head
This does not depend on #26877. That PR remains a useful upstream producer-invariant repair, while this change independently makes the persistence boundary correct and fail-closed for every supported cardinality mismatch. |
|
Fixed the SCA The fast path still returns an already row-aligned batch without allocating. When normalization is required, the helper now allocates the clone slice once at Validation: |
XuPeng-SH
left a comment
There was a problem hiding this comment.
Deep review result
[P2] Enforce query_result_maxsize on the normalized batch
saveBatch computes s from the executor-owned batch before prepareQueryResultBatchForWrite establishes the physical cardinality that is actually serialized. Vector.Size() is based on Vector.Length(), so every shape added by this PR can be under-accounted: a broadcast constant is charged for its old length, and a trailing-NULL vector is charged before its missing rows are materialized.
The exact public shape already covered by this PR makes the violation deterministic:
vector: const NULL varchar, Length() = 0
batch: RowCount() = 1
query_result_maxsize = 0
expected savedRowCount: 0
actual savedRowCount: 1
On this head, bat.Size() is zero, the limit check admits the batch, normalization changes the persisted vector to length 1, and the writer stores that row. curResultSize nevertheless remains zero while savedRowCount becomes one. This breaks the configured cap and leaves metadata size inconsistent with the batch that was written. The same counterexample on the exact base keeps savedRowCount at zero.
Please normalize first, perform admission and curResultSize accounting with writeBat.Size(), and release any temporary clones when the batch is rejected by the limit. Add a regression for the length-0/logical-1 NULL shape with query_result_maxsize=0 or another limit between the source and normalized sizes.
No other blocking findings remain in the reviewed hashmap broadcast paths, grouping/NULL/NaN handling, text and binary row output, normalized-vector ownership and cleanup, object write/read cardinality, or BVT expectation changes.
Validation on exact range 8be242b25bf9a44d73a3b1cc1db75fd0264a293c..b7e932d9f450f1449f13119429d88d271bb2dd0c:
- changed hashmap and frontend tests: PASS at
-count=10 - changed tests under
-race: PASS - full
./pkg/common/hashmapand./pkg/frontend: PASS - owning-package build and vet: PASS
git diff --check: PASS- deterministic size-limit counterexample: FAILS with
savedRowCount actual 1; exact-base control: PASS with 0
XuPeng-SH
left a comment
There was a problem hiding this comment.
Deep re-reviewed exact head 05cb1e22b9598e1fdc5708cd6da906e5118ae6dd. The previous query_result_maxsize blocker is closed: normalization now precedes admission, the limit and curResultSize use writeBat.Size(), and the cleanup defer is installed before the rejection branch.
The complete final paths are consistent:
- IntHashMap and StrHashMap encoders already broadcast constant values from physical row zero while flat-vector bounds remain strict.
- MySQL text and binary output, sent-row accounting, saved-result metadata, and persisted block rows all use the batch logical cardinality.
- Saved-result normalization handles constants and explicitly-null trailing flat rows, rejects unsupported mismatches, and never mutates executor-owned vectors.
- Temporary clones have one cleanup owner on validation failure, size rejection, writer/WriteEnd failure, and success; clone accumulation is bounded by batch columns and there are no new wait edges.
Local validation passed on this head: changed tests at high repeat counts, focused race tests, full pkg/common/hashmap and pkg/frontend, build, vet, and diff check. I also verified a counterexample where an earlier column is cloned before a later column fails validation; the mpool returns to its baseline. No blocking issue found.
Merge Queue Status
|
What type of PR is this?
Which issue(s) this PR fixes:
fixes #25992
What this PR does / why we need it:
Root cause
There were three constant-vector cardinality violations on prepared aggregate paths:
[start, start+count). Prepared parameters intentionally return a constant vector with one stored value, while hash encoders broadcast that value across the caller's logical range. The validator rejected the vector before encoding whencount > 1, returninginvalid allocation accountfor bareGROUP BY ?.bat.Vecs[0].Length()as the result cardinality. Grouped output with three logical rows therefore emitted only one row through both text and binary prepared-statement protocols. The same physical length was recorded asstatement_info.result_count.save_query_result=on, the same batch is persisted before it is sent to the client.saveBatchused the first vector length for saved/query row accounting, and objectio serialized the one-row logical length carried by the prepared constant. The client could receive three rows while saved-result metadata, the block header, and a column-prunedresult_scanexposed only one row.Fix
Batch.RowCount()as the authoritative client-output, sent-row, and saved-result cardinality.This changes neither allocation accounting nor hashmap capacity admission. Ordinary queries do not enter the saved-result normalization path, and normal flat result batches are unchanged because their vector lengths already equal their logical row count.
Tests
expected: 3, actual: 1; it passes for 10 consecutive runs after the fix.pkg/frontendandpkg/common/hashmappackage tests, plus owning-package build and vet, pass.