mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-29483 Test for wrong result with window function and IN subquery
Add a test case to main.win that still returns a wrong result with
the previous commit applied. The result file records the current
(wrong) output: an empty set. The expected result is one row 'b'.
CREATE TABLE t1 (c0 TEXT, c1 INT);
INSERT INTO t1 VALUES ('a', 1), ('b', 2);
CREATE TABLE t2 (c0 TEXT);
INSERT INTO t2 VALUES ('b');
SELECT COALESCE(c0, LAST_VALUE(c0) OVER (ORDER BY c1))
FROM (SELECT c0, c1 FROM t1) dt
WHERE c1 = 0 OR c0 IN (SELECT c0 FROM t2);
Findings so far:
1. The derived table dt is merged. The outer references to c0 in the
WHERE clause and in the select list become Item_direct_view_ref
objects pointing to one shared Item_field.
2. The window function forces a post-join aggregation temporary table
(JOIN::make_aggr_tables_info() -> create_tmp_table()).
create_tmp_field() with modify_item redirects result_field of that
shared Item_field to the temporary table field.
3. The WHERE clause is evaluated in evaluate_join_record() for each
join row, before the row is written to the temporary table. The IN
predicate caches its left expression: Item_in_optimizer::val_bool()
-> Item_cache_str::cache_value() -> Item_direct_view_ref::str_result()
-> Item_field::str_result(), which reads result_field, not field.
4. So the WHERE clause compares the value copied by copy_fields() for
the last row that passed the WHERE clause, not the current row. In
the test, row 'a' sees an empty temporary table field and is
filtered out, so nothing is copied, and row 'b' sees the same empty
field and is filtered out too.
5. The heap-use-after-free from the original report is the same bug
with a BLOB column on InnoDB. copy_fields() copied the BLOB with
do_field_eq(), which keeps a pointer into the InnoDB row blob heap.
InnoDB frees that heap on the next row fetch
(row_mysql_prebuilt_free_blob_heap()), and the next WHERE
evaluation dereferences it in Binary_string::copy(). Recorded with
rr: the only free of the crashing block happens in the t1 scan on
the row1 -> row2 transition, and at the crash result_field belongs
to the temporary table while field holds row2's own value.
6. The previous commit deep-copies BLOBs for window function queries,
which removes the use-after-free but not the stale read. The wrong
result is not BLOB-specific and does not depend on InnoDB.
7. With a plain c0 in the select list instead of the COALESCE()
expression the result is correct, so the shape of the select list
item affects whether result_field is redirected.
The real fix belongs to the optimizer layer: either the merged
derived table must not share an item between a WHERE condition and a
select list item that gets a temporary table result_field, or the
temporary table setup must not redirect such an item, or the IN
predicate cache must read field when evaluated before the temporary
table is filled. MDEV-39868 (commit c0c4b5573f) is a similar case of
merged derived table columns sharing one Item_field.
This commit is contained in:
@@ -4794,5 +4794,18 @@ c1 c2
|
||||
2 2
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# MDEV-29483 Heap-use-after-free (Binary_string::copy()) with window functions
|
||||
#
|
||||
create table t1 (c0 text, c1 int);
|
||||
insert into t1 values ('a', 1), ('b', 2);
|
||||
create table t2 (c0 text);
|
||||
insert into t2 values ('b');
|
||||
# Expected: one row 'b'
|
||||
select coalesce(c0, last_value(c0) over (order by c1)) from (select c0, c1 from t1) dt
|
||||
where c1 = 0 or c0 in (select c0 from t2);
|
||||
coalesce(c0, last_value(c0) over (order by c1))
|
||||
b
|
||||
drop table t1, t2;
|
||||
#
|
||||
# End of 10.11 tests
|
||||
#
|
||||
|
||||
@@ -3108,6 +3108,20 @@ from t1;
|
||||
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-29483 Heap-use-after-free (Binary_string::copy()) with window functions
|
||||
--echo #
|
||||
create table t1 (c0 text, c1 int);
|
||||
insert into t1 values ('a', 1), ('b', 2);
|
||||
create table t2 (c0 text);
|
||||
insert into t2 values ('b');
|
||||
|
||||
--echo # Expected: one row 'b'
|
||||
select coalesce(c0, last_value(c0) over (order by c1)) from (select c0, c1 from t1) dt
|
||||
where c1 = 0 or c0 in (select c0 from t2);
|
||||
|
||||
drop table t1, t2;
|
||||
|
||||
--echo #
|
||||
--echo # End of 10.11 tests
|
||||
--echo #
|
||||
|
||||
Reference in New Issue
Block a user