From 5673aa41c3e0fe909689cea70b1ca46f10c38831 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 8 Sep 2011 19:48:14 +0400 Subject: [PATCH] BUG#830993: Crash in end_read_record with derived table - Let join buffering code correctly take into account rowids needed by DuplicateElimination when it is calculating minimum record sizes. - In JOIN_CACHE::write_record_data, added asserts that prevent us from writing beyond the end of the buffer. --- mysql-test/r/subselect_sj.result | 80 +++++++++++++++++++++++++++ mysql-test/r/subselect_sj_jcl6.result | 80 +++++++++++++++++++++++++++ mysql-test/t/subselect_sj.test | 80 +++++++++++++++++++++++++++ sql/sql_join_cache.cc | 19 ++++++- sql/sql_select.cc | 18 ++++-- sql/sql_select.h | 10 ---- 6 files changed, 268 insertions(+), 19 deletions(-) diff --git a/mysql-test/r/subselect_sj.result b/mysql-test/r/subselect_sj.result index 3bbb13dd42d..26c658ca312 100644 --- a/mysql-test/r/subselect_sj.result +++ b/mysql-test/r/subselect_sj.result @@ -1811,4 +1811,84 @@ a 0 DROP TABLE t2, t3, t4, t5; set @@optimizer_switch=@tmp834739; +# +# BUG#830993: Crash in end_read_record with derived table +# +set @tmp_830993=@@optimizer_switch; +set optimizer_switch='semijoin=on,loosescan=off,materialization=off,firstmatch=off'; +set @tmp_830993_jbs= @@join_buffer_size; +set join_buffer_size=160; +CREATE TABLE t1 ( +a int(11) NOT NULL AUTO_INCREMENT, +b int(11) DEFAULT NULL, +c int(11) DEFAULT NULL, +d time DEFAULT NULL, +e varchar(1) DEFAULT NULL, +f varchar(1) DEFAULT NULL, +PRIMARY KEY (a), +KEY c (c), +KEY d (d), +KEY e (e,c) +); +INSERT INTO t1 VALUES (10,NULL,8,'22:55:23','x','x'), +(11,8,7,'10:19:31','d','d'),(12,1,1,'14:40:36','r','r'), +(13,9,7,'04:37:47','f','f'),(14,4,9,'19:34:06','y','y'), +(15,3,NULL,'20:35:33','u','u'),(16,2,1,NULL,'m','m'), +(17,NULL,9,'14:43:37',NULL,NULL),(18,2,2,'02:23:09','o','o'), +(19,NULL,9,'01:22:45','w','w'),(20,6,2,'00:00:00','m','m'), +(21,7,4,'00:13:25','q','q'),(22,2,0,'03:47:16',NULL,NULL), +(23,5,4,'01:41:48','d','d'),(24,7,8,'00:00:00','g','g'), +(25,6,NULL,'22:32:04','x','x'),(26,6,NULL,'16:44:14','f','f'), +(27,2,0,'17:38:37','p','p'),(28,9,NULL,'08:46:48','j','j'), +(29,6,8,'14:11:27','c','c'); +CREATE TABLE t2 like t1; +INSERT INTO t2 VALUES (1,2,4,'22:34:09','v','v'), +(2,150,62,'14:26:02','v','v'),(3,NULL,7,'14:03:03','c','c'), +(4,2,1,'01:46:09',NULL,NULL),(5,5,0,'16:21:18','x','x'), +(6,3,7,'18:56:33','i','i'),(7,1,7,NULL,'e','e'), +(8,4,1,'09:29:08','p','p'),(9,NULL,7,'19:11:10','s','s'), +(10,2,1,'11:57:26','j','j'),(11,6,5,'00:39:46','z','z'), +(12,6,2,'03:28:15','c','c'),(13,8,0,'06:44:18','a','a'), +(14,2,1,'14:36:39','q','q'),(15,6,8,'18:42:45','y','y'), +(16,8,1,'02:57:29',NULL,NULL),(17,3,1,'16:46:13','r','r'), +(18,3,9,'19:39:02','v','v'),(19,9,1,NULL,NULL,NULL), +(20,6,5,'20:58:33','r','r'); +explain +SELECT +alias1.a, alias1.b, alias1.c, alias1.d, alias1.e, alias1.f, +alias2.a as a2_a, alias2.b as a2_b, alias2.c as a2_c, alias2.d as a2_d, +alias2.e as a2_e, alias2.f as a2_f, +t2.a as t2_a, t2.b as t2_b, t2.c as t2_c, t2.d as t2_d, t2.e as t2_e, t2.f as t2_f +FROM +(SELECT * FROM t2) AS alias1, +t1 AS alias2, +t2 +WHERE +alias1.c IN (SELECT SQ3_alias1.b +FROM t2 AS SQ3_alias1 STRAIGHT_JOIN t2 AS SQ3_alias2) +LIMIT 100; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY ALL NULL NULL NULL NULL 20 Start temporary +1 PRIMARY alias2 ALL NULL NULL NULL NULL 20 Using join buffer (flat, BNL join) +1 PRIMARY t2 ALL NULL NULL NULL NULL 20 Using join buffer (flat, BNL join) +1 PRIMARY SQ3_alias1 ALL NULL NULL NULL NULL 20 Using where; Using join buffer (flat, BNL join) +1 PRIMARY SQ3_alias2 index NULL PRIMARY 4 NULL 20 Using index; End temporary; Using join buffer (flat, BNL join) +2 DERIVED t2 ALL NULL NULL NULL NULL 20 +create table t3 as +SELECT +alias1.a, alias1.b, alias1.c, alias1.d, alias1.e, alias1.f, +alias2.a as a2_a, alias2.b as a2_b, alias2.c as a2_c, alias2.d as a2_d, +alias2.e as a2_e, alias2.f as a2_f, +t2.a as t2_a, t2.b as t2_b, t2.c as t2_c, t2.d as t2_d, t2.e as t2_e, t2.f as t2_f +FROM +(SELECT * FROM t2) AS alias1, +t1 AS alias2, +t2 +WHERE +alias1.c IN (SELECT SQ3_alias1.b +FROM t2 AS SQ3_alias1 STRAIGHT_JOIN t2 AS SQ3_alias2) +LIMIT 100; +drop table t1,t2, t3; +set optimizer_switch=@tmp_830993; +set join_buffer_size= @tmp_830993_jbs; set optimizer_switch=@subselect_sj_tmp; diff --git a/mysql-test/r/subselect_sj_jcl6.result b/mysql-test/r/subselect_sj_jcl6.result index f3c56084279..cb9b7ca25bb 100644 --- a/mysql-test/r/subselect_sj_jcl6.result +++ b/mysql-test/r/subselect_sj_jcl6.result @@ -1822,6 +1822,86 @@ a 0 DROP TABLE t2, t3, t4, t5; set @@optimizer_switch=@tmp834739; +# +# BUG#830993: Crash in end_read_record with derived table +# +set @tmp_830993=@@optimizer_switch; +set optimizer_switch='semijoin=on,loosescan=off,materialization=off,firstmatch=off'; +set @tmp_830993_jbs= @@join_buffer_size; +set join_buffer_size=160; +CREATE TABLE t1 ( +a int(11) NOT NULL AUTO_INCREMENT, +b int(11) DEFAULT NULL, +c int(11) DEFAULT NULL, +d time DEFAULT NULL, +e varchar(1) DEFAULT NULL, +f varchar(1) DEFAULT NULL, +PRIMARY KEY (a), +KEY c (c), +KEY d (d), +KEY e (e,c) +); +INSERT INTO t1 VALUES (10,NULL,8,'22:55:23','x','x'), +(11,8,7,'10:19:31','d','d'),(12,1,1,'14:40:36','r','r'), +(13,9,7,'04:37:47','f','f'),(14,4,9,'19:34:06','y','y'), +(15,3,NULL,'20:35:33','u','u'),(16,2,1,NULL,'m','m'), +(17,NULL,9,'14:43:37',NULL,NULL),(18,2,2,'02:23:09','o','o'), +(19,NULL,9,'01:22:45','w','w'),(20,6,2,'00:00:00','m','m'), +(21,7,4,'00:13:25','q','q'),(22,2,0,'03:47:16',NULL,NULL), +(23,5,4,'01:41:48','d','d'),(24,7,8,'00:00:00','g','g'), +(25,6,NULL,'22:32:04','x','x'),(26,6,NULL,'16:44:14','f','f'), +(27,2,0,'17:38:37','p','p'),(28,9,NULL,'08:46:48','j','j'), +(29,6,8,'14:11:27','c','c'); +CREATE TABLE t2 like t1; +INSERT INTO t2 VALUES (1,2,4,'22:34:09','v','v'), +(2,150,62,'14:26:02','v','v'),(3,NULL,7,'14:03:03','c','c'), +(4,2,1,'01:46:09',NULL,NULL),(5,5,0,'16:21:18','x','x'), +(6,3,7,'18:56:33','i','i'),(7,1,7,NULL,'e','e'), +(8,4,1,'09:29:08','p','p'),(9,NULL,7,'19:11:10','s','s'), +(10,2,1,'11:57:26','j','j'),(11,6,5,'00:39:46','z','z'), +(12,6,2,'03:28:15','c','c'),(13,8,0,'06:44:18','a','a'), +(14,2,1,'14:36:39','q','q'),(15,6,8,'18:42:45','y','y'), +(16,8,1,'02:57:29',NULL,NULL),(17,3,1,'16:46:13','r','r'), +(18,3,9,'19:39:02','v','v'),(19,9,1,NULL,NULL,NULL), +(20,6,5,'20:58:33','r','r'); +explain +SELECT +alias1.a, alias1.b, alias1.c, alias1.d, alias1.e, alias1.f, +alias2.a as a2_a, alias2.b as a2_b, alias2.c as a2_c, alias2.d as a2_d, +alias2.e as a2_e, alias2.f as a2_f, +t2.a as t2_a, t2.b as t2_b, t2.c as t2_c, t2.d as t2_d, t2.e as t2_e, t2.f as t2_f +FROM +(SELECT * FROM t2) AS alias1, +t1 AS alias2, +t2 +WHERE +alias1.c IN (SELECT SQ3_alias1.b +FROM t2 AS SQ3_alias1 STRAIGHT_JOIN t2 AS SQ3_alias2) +LIMIT 100; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY ALL NULL NULL NULL NULL 20 Start temporary +1 PRIMARY alias2 ALL NULL NULL NULL NULL 20 Using join buffer (flat, BNL join) +1 PRIMARY t2 ALL NULL NULL NULL NULL 20 Using join buffer (incremental, BNL join) +1 PRIMARY SQ3_alias1 ALL NULL NULL NULL NULL 20 Using where; Using join buffer (incremental, BNL join) +1 PRIMARY SQ3_alias2 index NULL PRIMARY 4 NULL 20 Using index; End temporary; Using join buffer (incremental, BNL join) +2 DERIVED t2 ALL NULL NULL NULL NULL 20 +create table t3 as +SELECT +alias1.a, alias1.b, alias1.c, alias1.d, alias1.e, alias1.f, +alias2.a as a2_a, alias2.b as a2_b, alias2.c as a2_c, alias2.d as a2_d, +alias2.e as a2_e, alias2.f as a2_f, +t2.a as t2_a, t2.b as t2_b, t2.c as t2_c, t2.d as t2_d, t2.e as t2_e, t2.f as t2_f +FROM +(SELECT * FROM t2) AS alias1, +t1 AS alias2, +t2 +WHERE +alias1.c IN (SELECT SQ3_alias1.b +FROM t2 AS SQ3_alias1 STRAIGHT_JOIN t2 AS SQ3_alias2) +LIMIT 100; +drop table t1,t2, t3; +set optimizer_switch=@tmp_830993; +set join_buffer_size= @tmp_830993_jbs; set optimizer_switch=@subselect_sj_tmp; # # BUG#49129: Wrong result with IN-subquery with join_cache_level=6 and firstmatch=off diff --git a/mysql-test/t/subselect_sj.test b/mysql-test/t/subselect_sj.test index 49ecfe86fbc..bac42fc684e 100644 --- a/mysql-test/t/subselect_sj.test +++ b/mysql-test/t/subselect_sj.test @@ -1642,5 +1642,85 @@ SELECT * FROM t3 WHERE t3.a IN (SELECT t5.a FROM t2, t4, t5 WHERE t2.c = t5.a AN DROP TABLE t2, t3, t4, t5; set @@optimizer_switch=@tmp834739; +--echo # +--echo # BUG#830993: Crash in end_read_record with derived table +--echo # +set @tmp_830993=@@optimizer_switch; +set optimizer_switch='semijoin=on,loosescan=off,materialization=off,firstmatch=off'; +set @tmp_830993_jbs= @@join_buffer_size; +--disable_warnings +set join_buffer_size=160; +--enable_warnings +CREATE TABLE t1 ( + a int(11) NOT NULL AUTO_INCREMENT, + b int(11) DEFAULT NULL, + c int(11) DEFAULT NULL, + d time DEFAULT NULL, + e varchar(1) DEFAULT NULL, + f varchar(1) DEFAULT NULL, + PRIMARY KEY (a), + KEY c (c), + KEY d (d), + KEY e (e,c) +); +INSERT INTO t1 VALUES (10,NULL,8,'22:55:23','x','x'), + (11,8,7,'10:19:31','d','d'),(12,1,1,'14:40:36','r','r'), + (13,9,7,'04:37:47','f','f'),(14,4,9,'19:34:06','y','y'), + (15,3,NULL,'20:35:33','u','u'),(16,2,1,NULL,'m','m'), + (17,NULL,9,'14:43:37',NULL,NULL),(18,2,2,'02:23:09','o','o'), + (19,NULL,9,'01:22:45','w','w'),(20,6,2,'00:00:00','m','m'), + (21,7,4,'00:13:25','q','q'),(22,2,0,'03:47:16',NULL,NULL), + (23,5,4,'01:41:48','d','d'),(24,7,8,'00:00:00','g','g'), + (25,6,NULL,'22:32:04','x','x'),(26,6,NULL,'16:44:14','f','f'), + (27,2,0,'17:38:37','p','p'),(28,9,NULL,'08:46:48','j','j'), + (29,6,8,'14:11:27','c','c'); + +CREATE TABLE t2 like t1; +INSERT INTO t2 VALUES (1,2,4,'22:34:09','v','v'), + (2,150,62,'14:26:02','v','v'),(3,NULL,7,'14:03:03','c','c'), + (4,2,1,'01:46:09',NULL,NULL),(5,5,0,'16:21:18','x','x'), + (6,3,7,'18:56:33','i','i'),(7,1,7,NULL,'e','e'), + (8,4,1,'09:29:08','p','p'),(9,NULL,7,'19:11:10','s','s'), + (10,2,1,'11:57:26','j','j'),(11,6,5,'00:39:46','z','z'), + (12,6,2,'03:28:15','c','c'),(13,8,0,'06:44:18','a','a'), + (14,2,1,'14:36:39','q','q'),(15,6,8,'18:42:45','y','y'), + (16,8,1,'02:57:29',NULL,NULL),(17,3,1,'16:46:13','r','r'), + (18,3,9,'19:39:02','v','v'),(19,9,1,NULL,NULL,NULL), + (20,6,5,'20:58:33','r','r'); + +explain +SELECT + alias1.a, alias1.b, alias1.c, alias1.d, alias1.e, alias1.f, + alias2.a as a2_a, alias2.b as a2_b, alias2.c as a2_c, alias2.d as a2_d, + alias2.e as a2_e, alias2.f as a2_f, + t2.a as t2_a, t2.b as t2_b, t2.c as t2_c, t2.d as t2_d, t2.e as t2_e, t2.f as t2_f +FROM + (SELECT * FROM t2) AS alias1, + t1 AS alias2, + t2 +WHERE + alias1.c IN (SELECT SQ3_alias1.b + FROM t2 AS SQ3_alias1 STRAIGHT_JOIN t2 AS SQ3_alias2) +LIMIT 100; + +create table t3 as +SELECT + alias1.a, alias1.b, alias1.c, alias1.d, alias1.e, alias1.f, + alias2.a as a2_a, alias2.b as a2_b, alias2.c as a2_c, alias2.d as a2_d, + alias2.e as a2_e, alias2.f as a2_f, + t2.a as t2_a, t2.b as t2_b, t2.c as t2_c, t2.d as t2_d, t2.e as t2_e, t2.f as t2_f +FROM + (SELECT * FROM t2) AS alias1, + t1 AS alias2, + t2 +WHERE + alias1.c IN (SELECT SQ3_alias1.b + FROM t2 AS SQ3_alias1 STRAIGHT_JOIN t2 AS SQ3_alias2) +LIMIT 100; + +drop table t1,t2, t3; +set optimizer_switch=@tmp_830993; +set join_buffer_size= @tmp_830993_jbs; + # The following command must be the last one the file set optimizer_switch=@subselect_sj_tmp; diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index f292375ee6f..bbc7e5cd26c 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -225,8 +225,6 @@ void JOIN_CACHE::calc_record_fields() flag_fields+= test(tab->table->maybe_null); fields+= tab->used_fields; blobs+= tab->used_blobs; - - fields+= tab->check_rowid_field(); } if ((with_match_flag= join_tab->use_match_flag())) flag_fields++; @@ -620,7 +618,12 @@ void JOIN_CACHE::create_remaining_fields() copy->type= CACHE_ROWID; copy->field= 0; copy->referenced_field_no= 0; - length+= copy->length; + /* + Note: this may seem odd, but at this point we have + table->file->ref==NULL while table->file->ref_length is already set + to correct value. + */ + length += table->file->ref_length; data_field_count++; copy++; } @@ -1297,6 +1300,7 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) if (with_length) { rec_len_ptr= cp; + DBUG_ASSERT(cp + size_of_rec_len <= buff + buff_size); cp+= size_of_rec_len; } @@ -1306,6 +1310,7 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) */ if (prev_cache) { + DBUG_ASSERT(cp + prev_cache->get_size_of_rec_offset() <= buff + buff_size); cp+= prev_cache->get_size_of_rec_offset(); prev_cache->store_rec_ref(cp, link); } @@ -1322,6 +1327,7 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) flags_pos= cp; for ( ; copy < copy_end; copy++) { + DBUG_ASSERT(cp + copy->length <= buff + buff_size); memcpy(cp, copy->str, copy->length); cp+= copy->length; } @@ -1348,6 +1354,7 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) { last_rec_blob_data_is_in_rec_buff= 1; /* Put down the length of the blob and the pointer to the data */ + DBUG_ASSERT(cp + copy->length + sizeof(char*) <= buff + buff_size); blob_field->get_image(cp, copy->length+sizeof(char*), blob_field->charset()); cp+= copy->length+sizeof(char*); @@ -1357,6 +1364,7 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) /* First put down the length of the blob and then copy the data */ blob_field->get_image(cp, copy->length, blob_field->charset()); + DBUG_ASSERT(cp + copy->length + copy->blob_length <= buff + buff_size); memcpy(cp+copy->length, copy->str, copy->blob_length); cp+= copy->length+copy->blob_length; } @@ -1367,12 +1375,14 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) case CACHE_VARSTR1: /* Copy the significant part of the short varstring field */ len= (uint) copy->str[0] + 1; + DBUG_ASSERT(cp + len <= buff + buff_size); memcpy(cp, copy->str, len); cp+= len; break; case CACHE_VARSTR2: /* Copy the significant part of the long varstring field */ len= uint2korr(copy->str) + 2; + DBUG_ASSERT(cp + len <= buff + buff_size); memcpy(cp, copy->str, len); cp+= len; break; @@ -1387,6 +1397,7 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) end > str && end[-1] == ' '; end--) ; len=(uint) (end-str); + DBUG_ASSERT(cp + len + 2 <= buff + buff_size); int2store(cp, len); memcpy(cp+2, str, len); cp+= len+2; @@ -1406,6 +1417,7 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) /* fall through */ default: /* Copy the entire image of the field from the record buffer */ + DBUG_ASSERT(cp + copy->length <= buff + buff_size); memcpy(cp, copy->str, copy->length); cp+= copy->length; } @@ -1425,6 +1437,7 @@ uint JOIN_CACHE::write_record_data(uchar * link, bool *is_full) cnt++; } } + DBUG_ASSERT(cp + size_of_fld_ofs*cnt <= buff + buff_size); cp+= size_of_fld_ofs*cnt; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 6ddb350db1d..ad683ea67bb 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -6572,6 +6572,16 @@ void JOIN_TAB::calc_used_field_length(bool max_fl) rec_length+=(table->s->null_fields+7)/8; if (table->maybe_null) rec_length+=sizeof(my_bool); + + /* Take into account that DuplicateElimination may need to store rowid */ + uint rowid_add_size= 0; + if (keep_current_rowid) + { + rowid_add_size= table->file->ref_length; + rec_length += rowid_add_size; + fields++; + } + if (max_fl) { // TODO: to improve this estimate for max expected length @@ -6585,13 +6595,9 @@ void JOIN_TAB::calc_used_field_length(bool max_fl) } max_used_fieldlength= rec_length; } - else if (table->file->stats.mean_rec_length) - set_if_smaller(rec_length, table->file->stats.mean_rec_length); + else if (table->file->stats.mean_rec_length) + set_if_smaller(rec_length, table->file->stats.mean_rec_length + rowid_add_size); - /* - TODO: why we don't count here rowid that we might need to store when - using DuplicateElimination? - */ used_fields=fields; used_fieldlength=rec_length; used_blobs=blobs; diff --git a/sql/sql_select.h b/sql/sql_select.h index 15711dca7b3..676cc7452f4 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -288,7 +288,6 @@ typedef struct st_join_table { ulong max_used_fieldlength; uint used_blobs; uint used_null_fields; - uint used_rowid_fields; uint used_uneven_bit_fields; enum join_type type; bool cached_eq_ref_table,eq_ref_table,not_used_in_distinct; @@ -387,15 +386,6 @@ typedef struct st_join_table { (select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)); } - bool check_rowid_field() - { - if (keep_current_rowid && !used_rowid_fields) - { - used_rowid_fields= 1; - used_fieldlength+= table->file->ref_length; - } - return test(used_rowid_fields); - } bool is_inner_table_of_semi_join_with_first_match() { return first_sj_inner_tab != NULL;