Skip to content

Commit 9f88d56

Browse files
roylysengdahlerlend
authored andcommitted
Bug#33856374: Query returns incorrect results in 8.0.22+
This problem requires a rather complex set of conditions to trigger: - We have a CTE that is materialized and used in multiple query blocks. - For the first query block that uses the CTE, there are multiple possible key definitions, which causes JOIN::finalize_derived_keys() to move the used key definition into position zero. - The second query block that uses the CTE manipulates an index that uses the same position as the original position for the key chosen for the first query block. Now, the function TABLE::copy_tmp_key() is supposed to move the first key into a position that does not overlap with the subsequent keys. And this is done for the KEY entry, however the associated entries (KEY_PART_INFO, key names and rec per key information) are kept as is, meaning that a subsequent key analysis for another query block may overwrite the information in these data structures, possibly causing invalid results to be returned. The fix is to also move these extra data structures, so that subsequent query block analyses use pristine data structures. The function TABLE::copy_tmp_key() is renamed to TABLE::move_tmp_key(), as this is really a move operation. In addition, old entries are zeroed out so that a later invalid access will likely fail. A synthetic test case is added, as the original case is too complex and contains too much data to be included. Change-Id: I908dca74560d20fecdd886e55cabfcaa348aba4d
1 parent b2770ad commit 9f88d56

File tree

6 files changed

+131
-12
lines changed

6 files changed

+131
-12
lines changed

mysql-test/r/derived.result

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4609,3 +4609,33 @@ GROUP BY b;
46094609
1
46104610
1
46114611
DROP TABLE t1, t2;
4612+
# Bug#33856374: Query returns incorrect results in 8.0.22+
4613+
CREATE TABLE t1
4614+
(a INTEGER,
4615+
b INTEGER,
4616+
c INTEGER
4617+
);
4618+
INSERT INTO t1 VALUES
4619+
(1, 1, 10), (1, 2, 20), (1, 3, 30), (2, 1, 40), (2, 2, 50), (2, 3, 60);
4620+
CREATE TABLE t2
4621+
(a INTEGER,
4622+
d INTEGER,
4623+
e INTEGER
4624+
);
4625+
INSERT INTO t2 VALUES
4626+
(1, 6, 60), (2, 6, 60), (3, 6, 60);
4627+
WITH
4628+
cte AS
4629+
(SELECT SUM(c) AS c, SUM(b) AS b, a
4630+
FROM t1
4631+
GROUP BY a)
4632+
SELECT t2.a, (SELECT MIN(c) FROM cte AS cte2 WHERE t2.d = cte2.b)
4633+
FROM t2 LEFT JOIN cte AS cte1 ON t2.a = cte1.a
4634+
LEFT JOIN t2 AS tx ON tx.e = cte1.c;
4635+
a (SELECT MIN(c) FROM cte AS cte2 WHERE t2.d = cte2.b)
4636+
1 60
4637+
1 60
4638+
1 60
4639+
2 60
4640+
3 60
4641+
DROP TABLE t1, t2;

mysql-test/t/derived.test

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3307,3 +3307,34 @@ SELECT 1 FROM t2, (
33073307
GROUP BY b;
33083308

33093309
DROP TABLE t1, t2;
3310+
3311+
--echo # Bug#33856374: Query returns incorrect results in 8.0.22+
3312+
3313+
CREATE TABLE t1
3314+
(a INTEGER,
3315+
b INTEGER,
3316+
c INTEGER
3317+
);
3318+
3319+
INSERT INTO t1 VALUES
3320+
(1, 1, 10), (1, 2, 20), (1, 3, 30), (2, 1, 40), (2, 2, 50), (2, 3, 60);
3321+
3322+
CREATE TABLE t2
3323+
(a INTEGER,
3324+
d INTEGER,
3325+
e INTEGER
3326+
);
3327+
3328+
INSERT INTO t2 VALUES
3329+
(1, 6, 60), (2, 6, 60), (3, 6, 60);
3330+
3331+
WITH
3332+
cte AS
3333+
(SELECT SUM(c) AS c, SUM(b) AS b, a
3334+
FROM t1
3335+
GROUP BY a)
3336+
SELECT t2.a, (SELECT MIN(c) FROM cte AS cte2 WHERE t2.d = cte2.b)
3337+
FROM t2 LEFT JOIN cte AS cte1 ON t2.a = cte1.a
3338+
LEFT JOIN t2 AS tx ON tx.e = cte1.c;
3339+
3340+
DROP TABLE t1, t2;

sql/key.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2006, 2021, Oracle and/or its affiliates.
1+
/* Copyright (c) 2006, 2022, Oracle and/or its affiliates.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License, version 2.0,
@@ -299,6 +299,28 @@ class KEY {
299299
rec_per_key_float = rec_per_key_float_arg;
300300
}
301301

302+
/**
303+
Move rec_per_key arrays from old to new position.
304+
305+
Ignore if arrays have not been set up yet.
306+
307+
@param rec_per_key_arg pointer to adjusted rec per key array
308+
@param rec_per_key_float_arg pointer to adjusted rec per key array (float)
309+
*/
310+
311+
void move_rec_per_key(ulong *rec_per_key_arg,
312+
rec_per_key_t *rec_per_key_float_arg) {
313+
if (rec_per_key_float == nullptr || rec_per_key == nullptr) return;
314+
315+
rec_per_key_t *old_rec_per_key_float = rec_per_key_float;
316+
ulong *old_rec_per_key = rec_per_key;
317+
set_rec_per_key_array(rec_per_key_arg, rec_per_key_float_arg);
318+
for (uint i = 0; i < actual_key_parts; i++) {
319+
rec_per_key[i] = old_rec_per_key[i];
320+
rec_per_key_float[i] = old_rec_per_key_float[i];
321+
}
322+
}
323+
302324
/**
303325
Retrieve the estimate for how much of the index data that is available
304326
in a memory buffer.

sql/sql_optimizer.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9094,7 +9094,7 @@ void JOIN::finalize_derived_keys() {
90949094
blocks must be considered here, as they need a key_info array
90959095
consistent with the to-be-changed table->s->keys.
90969096
*/
9097-
t->copy_tmp_key(old_idx, it.is_first());
9097+
t->move_tmp_key(old_idx, it.is_first());
90989098
}
90999099
} else
91009100
new_idx = old_idx; // Index stays at same slot

sql/table.cc

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "ft_global.h"
3939
#include "m_string.h"
4040
#include "map_helpers.h"
41+
#include "memory_debugging.h"
4142
#include "my_alloc.h"
4243
#include "my_byteorder.h"
4344
#include "my_dbug.h"
@@ -5784,14 +5785,15 @@ bool TABLE::alloc_tmp_keys(uint new_key_count, uint new_key_part_count,
57845785
s->key_names = static_cast<Key_name *>(
57855786
s->mem_root.Alloc(sizeof(Key_name) * new_key_count));
57865787
if (s->key_names == nullptr) return true; /* purecov: inspected */
5787-
memset(s->key_names, 0, sizeof(Key_name) * new_key_count);
5788+
TRASH(s->key_names, sizeof(Key_name) * new_key_count);
57885789
/*
57895790
A derived table may have a unique index with name stored in
57905791
s->key_info->name. Check for this special case, and copy the name into
57915792
the first location of key_names array.
57925793
*/
5793-
if (old_key_count > 0 && old_key_names == nullptr)
5794-
memcpy(&s->key_names->name, s->key_info->name, strlen(s->key_info->name));
5794+
if (old_key_count > 0 && old_key_names == nullptr) {
5795+
strcpy(pointer_cast<char *>(&s->key_names->name), s->key_info->name);
5796+
}
57955797

57965798
s->key_info = s->mem_root.ArrayAlloc<KEY>(new_key_count);
57975799
if (s->key_info == nullptr) return true; /* purecov: inspected */
@@ -6004,19 +6006,53 @@ uint TABLE_SHARE::find_first_unused_tmp_key(const Key_map &k) {
60046006
}
60056007

60066008
/**
6007-
For a materialized derived table: copies a KEY definition from a position to
6009+
For a materialized derived table: moves a KEY definition from a position to
60086010
the first not-yet-used position (which is lower).
60096011
6012+
@note memset operations are used to invalidate old entries, in order to
6013+
trap invalid accesses after the move. memset is considered cheap
6014+
in this context.
6015+
6016+
The function needs to move the following entries:
6017+
- The KEY (both for TABLE and TABLE_SHARE)
6018+
- The KEY_PART_INFO objects (TABLE only, TABLE_SHARE shares with first TABLE)
6019+
- The key names (TABLE_SHARE only)
6020+
- rec per key information (TABLE_SHARE only)
6021+
60106022
@param old_idx source position
60116023
@param modify_share Do modifications to TABLE_SHARE. @see alloc_tmp_keys
60126024
*/
6013-
void TABLE::copy_tmp_key(int old_idx, bool modify_share) {
6014-
if (modify_share)
6015-
s->key_info[s->first_unused_tmp_key++] = s->key_info[old_idx];
6025+
void TABLE::move_tmp_key(int old_idx, bool modify_share) {
6026+
if (modify_share) {
6027+
const int new_idx = s->first_unused_tmp_key++;
6028+
s->key_info[new_idx] = s->key_info[old_idx];
6029+
TRASH(pointer_cast<void *>(s->key_info + old_idx), sizeof(KEY));
6030+
s->key_names[new_idx] = s->key_names[old_idx];
6031+
TRASH(pointer_cast<void *>(s->key_names + old_idx), sizeof(Key_name));
6032+
s->key_info[new_idx].name = s->key_names[new_idx].name;
6033+
}
60166034
const int new_idx = s->first_unused_tmp_key - 1;
60176035
assert(!created && new_idx < old_idx && old_idx < (int)s->keys);
6036+
uint key_partno = 0;
6037+
for (int i = 0; i < new_idx; i++) {
6038+
key_partno += s->key_info[i].user_defined_key_parts;
6039+
}
60186040
key_info[new_idx] = key_info[old_idx];
6041+
KEY_PART_INFO *old_key_part = key_info[old_idx].key_part;
6042+
TRASH(pointer_cast<void *>(key_info + old_idx), sizeof(KEY));
6043+
key_info[new_idx].key_part = base_key_parts + key_partno;
6044+
key_info[new_idx].name = s->key_names[new_idx].name;
60196045

6046+
for (uint i = 0; i < s->key_info[new_idx].user_defined_key_parts; i++) {
6047+
base_key_parts[key_partno + i] = old_key_part[i];
6048+
TRASH(pointer_cast<void *>(old_key_part + i), sizeof(KEY_PART_INFO));
6049+
}
6050+
if (modify_share) {
6051+
s->key_info[new_idx].key_part = base_key_parts + key_partno;
6052+
s->key_info[new_idx].move_rec_per_key(
6053+
s->base_rec_per_key + key_partno,
6054+
s->base_rec_per_key_float + key_partno);
6055+
}
60206056
for (auto reg_field = field; *reg_field; reg_field++) {
60216057
auto f = *reg_field;
60226058
f->key_start.clear_bit(new_idx);
@@ -6037,7 +6073,7 @@ void TABLE::copy_tmp_key(int old_idx, bool modify_share) {
60376073
}
60386074

60396075
/**
6040-
For a materialized derived table: after copy_tmp_key() has copied all
6076+
For a materialized derived table: after move_tmp_key() has moved all
60416077
definitions of used KEYs, in TABLE::key_info we have a head of used keys
60426078
followed by a tail of unused keys; this function chops the tail.
60436079

sql/table.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#ifndef TABLE_INCLUDED
22
#define TABLE_INCLUDED
33

4-
/* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
4+
/* Copyright (c) 2000, 2022, Oracle and/or its affiliates.
55
66
This program is free software; you can redistribute it and/or modify
77
it under the terms of the GNU General Public License, version 2.0,
@@ -1843,7 +1843,7 @@ struct TABLE {
18431843
bool alloc_tmp_keys(uint new_key_count, uint new_key_part_count,
18441844
bool modify_share);
18451845
bool add_tmp_key(Field_map *key_parts, bool invisible, bool modify_share);
1846-
void copy_tmp_key(int old_idx, bool modify_share);
1846+
void move_tmp_key(int old_idx, bool modify_share);
18471847
void drop_unused_tmp_keys(bool modify_share);
18481848

18491849
void set_keyread(bool flag);

0 commit comments

Comments
 (0)