MDEV-41026 ER_DUP_KEY on '(temporary)' converting a subquery cache - #5636
MDEV-41026 ER_DUP_KEY on '(temporary)' converting a subquery cache#5636arcivanov wants to merge 1 commit into
Conversation
A subquery expression cache whose parameter is NULL for many rows
fills its in-memory table with rows that all carry a NULL key.
`TABLE::add_tmp_key()` builds that key without `HA_NULL_ARE_EQUAL`,
`ha_heap::create()` propagates the absence, and `hp_write_key()` then
skips the duplicate check for any key value containing NULL, so such
rows are legal there. They accumulate rather than being exceptional:
`ref.null_rejecting` is set, so every NULL parameter is a miss and
adds one more NULL-keyed row.
The on-disk creators disagreed. Both the key and the unique
constraint forced `HA_NULL_ARE_EQUAL` on, making the destination of a
conversion stricter about NULLs than the table being converted from.
`create_internal_tmp_table_from_heap()` re-inserts every stored row
and treats a duplicate among them as fatal, so the second NULL-keyed
row was rejected. The user saw either
Can't write; duplicate key in table '(temporary)'
or, when the key is too wide for the on-disk engine to index and is
built as a unique constraint over a row hash instead,
Can't write, because of unique constraint, to table '(temporary)'
Make `KEY::flags & HA_NULL_ARE_EQUAL` the single source of truth and
read it at all four creator sites rather than deciding it there. For
the flag to be truthful the GROUP BY key has to carry it when it is
built as a unique constraint as well; the assignment sat inside the
branch that only the real-key case takes, which also lays the group
buffer out around the NULL flag.
The MyISAM halves of both creators compile only with
`-DUSE_ARIA_FOR_TMP_TABLES=OFF`, which is not the default. They are
changed to match and verified to compile warning-free in that
configuration, but **no test run has executed them**.
The test covers both on-disk shapes: a narrow key that stays a key,
and a key too wide for Aria that becomes a unique constraint. Each
block reports whether the conversion happened at all, so the test
cannot pass by quietly ceasing to overflow. The count itself is not
asserted: `--ps-protocol` executes the statement twice and so
converts twice. Neither cached query aborts on error, so a
regression in either shape is reported rather than hidden behind the
other one.
3192bfc to
d9392aa
Compare
gkodinov
left a comment
There was a problem hiding this comment.
Thank you for your contribution. This is a preliminary review.
There's a test failing in msan that looks related to your change. Can you please rectify?
|
Important The MSAN failures on this pull request are MDEV-27562, not a defect in this change. They come from the construction of the test added here: its cached subquery parameter is a |
gkodinov
left a comment
There was a problem hiding this comment.
LGTM. Please stand by for the final review.
|
FYI: According to our development cycle we work on bugs In the following periods 15 Mar-30 Apr, 15 Jun-30 Jul, 15 Sep-30 Oct and 15 Dec-31 Jan. So, please, expect to get a review somewhere between these two dates and the goal is to have your PR merged before the second date |
https://jira.mariadb.org/browse/MDEV-41026
Important
The MSAN failures on this pull request are MDEV-27562, not a defect in this change. They come from the construction of the test added here: its cached subquery parameter is a
VARCHARcolumn of an Aria table, so every cached lookup reaches the comparison that ticket reports. MDEV-27562 is fixed by #5648 against 10.11; these builders go green here once that merges up intomain.The defect
A subquery expression cache whose parameter is NULL for many rows fills its in-memory table with rows that all carry a NULL key.
TABLE::add_tmp_key()builds that key withoutHA_NULL_ARE_EQUAL,ha_heap::create()propagates the absence, andhp_write_key()then skips the duplicate check for any key value containing NULL, so such rows are legal there. They accumulate rather than being exceptional:ref.null_rejectingis set, so every NULL parameter is a cache miss and adds one more NULL-keyed row.The on-disk creators disagreed. Both the key and the unique constraint forced
HA_NULL_ARE_EQUALon, making the destination of a conversion stricter about NULLs than the table being converted from.create_internal_tmp_table_from_heap()re-inserts every stored row and treats a duplicate among them as fatal, so the second NULL-keyed row was rejected.The reported symptom is
ER_DUP_KEY. The same defect surfaces asER_DUP_ENTRYonce the cache table is already on disk, and asER_DUP_UNIQUEwhen the key is too wide for the on-disk engine to index and is built as a unique constraint over a row hash instead.The fix
Make
KEY::flags & HA_NULL_ARE_EQUALthe single source of truth and read it at all four creator sites -keydefanduniquedef, in each of the Aria and the MyISAM creator - rather than deciding it there.For that flag to be truthful, the GROUP BY key has to carry it when it is built as a unique constraint as well. The assignment sat inside the branch that only the real-key case takes, which also lays the group buffer out around the NULL flag, so it is now set in the unique-constraint case too. This is behaviour-neutral on Aria, whose creator tests
HA_UNIQUE_HASHexplicitly and therefore always took theuniquedefbranch for such a key, wherenull_are_equalwas hardcoded to 1.choose_engine()routes everym_using_unique_constrainttable to the on-disk engine, so that change cannot reachha_heap::create(), the only other place aKEY'sHA_NULL_ARE_EQUALis read.Testing
mysql-test/main/subquery_cache_null_convertcovers both on-disk shapes. Each block printsCreated_tmp_disk_tables, so the test cannot pass by quietly ceasing to overflow, and neither cached query aborts on error, so a regression in either shape is reported rather than hidden behind the other one. Reverting the fix and running the test unchanged gives, in one run:ERROR 23000: Can't write; duplicate key in table '(temporary)'(1022)ERROR 23000: Can't write, because of unique constraint, to table '(temporary)'(1169)main,heapandmariasuites: 1500 of 1500 pass.Not covered
The MyISAM halves of both creators compile only with
-DUSE_ARIA_FOR_TMP_TABLES=OFF, which is not the default. They are changed to match and verified to compile warning-free in that configuration, but no test run has executed them.