From ddb5d63346b66037b5f2cd7ede55efdc5c709844 Mon Sep 17 00:00:00 2001 From: Luis Soares Date: Wed, 21 Apr 2010 13:47:55 +0100 Subject: [PATCH 01/15] BUG#52868: Wrong handling of NULL value during update, replication out of sync In RBR, sometimes the table->s->last_null_bit_pos can be zero. This has impact at the slave when it compares records fetched from the storage engine against records in the binary log event. If last_null_bit_pos is zero the slave, while comparing in log_event.cc:record_compare function, would set all bits in the last null_byte to 1 (assumed all 8 were unused) . Thence it would loose the ability to distinguish records that were similar in contents except for the fact that some field was null in one record, but not in the other. Ultimately this would cause wrong matches, and in the specific case depicted in the bug report the same record would be updated twice, resulting in a lost update. Additionally, in the record_compare function the slave was setting the X bit unconditionally. There are cases that the X bit does not exist in the record header. This could also lead to wrong matches between records. We fix both by conditionally resetting the bits: (i) unused null_bits are set if last_null_bit_pos > 0; (ii) X bit is set if HA_OPTION_PACK_RECORD is in use. --- .../extra/rpl_tests/rpl_record_compare.test | 68 +++++++++++++++++++ .../rpl/r/rpl_row_rec_comp_innodb.result | 46 +++++++++++++ .../rpl/r/rpl_row_rec_comp_myisam.result | 60 ++++++++++++++++ .../suite/rpl/t/rpl_row_rec_comp_innodb.test | 10 +++ .../suite/rpl/t/rpl_row_rec_comp_myisam.test | 31 +++++++++ sql/log_event.cc | 34 ++++++++-- sql/log_event_old.cc | 36 +++++++--- 7 files changed, 270 insertions(+), 15 deletions(-) create mode 100644 mysql-test/extra/rpl_tests/rpl_record_compare.test create mode 100644 mysql-test/suite/rpl/r/rpl_row_rec_comp_innodb.result create mode 100644 mysql-test/suite/rpl/r/rpl_row_rec_comp_myisam.result create mode 100644 mysql-test/suite/rpl/t/rpl_row_rec_comp_innodb.test create mode 100644 mysql-test/suite/rpl/t/rpl_row_rec_comp_myisam.test diff --git a/mysql-test/extra/rpl_tests/rpl_record_compare.test b/mysql-test/extra/rpl_tests/rpl_record_compare.test new file mode 100644 index 00000000000..dc27dcb1f9d --- /dev/null +++ b/mysql-test/extra/rpl_tests/rpl_record_compare.test @@ -0,0 +1,68 @@ + +# +# BUG#52868: Wrong handling of NULL value during update, replication out of sync +# +-- echo ## case #1 - last_null_bit_pos==0 in record_compare without X bit + +-- source include/master-slave-reset.inc +-- connection master + +-- eval CREATE TABLE t1 (c1 bigint(20) DEFAULT 0, c2 bigint(20) DEFAULT 0, c3 bigint(20) DEFAULT 0, c4 varchar(1) DEFAULT '', c5 bigint(20) DEFAULT 0, c6 bigint(20) DEFAULT 0, c7 bigint(20) DEFAULT 0, c8 bigint(20) DEFAULT 0) ENGINE=$engine DEFAULT CHARSET=latin1 + +INSERT INTO t1 ( c5, c6 ) VALUES ( 1 , 35 ); +INSERT INTO t1 ( c5, c6 ) VALUES ( NULL, 35 ); +-- disable_warnings +UPDATE t1 SET c5 = 'a'; +-- enable_warnings +-- sync_slave_with_master + +-- let $diff_table_1= master:test.t1 +-- let $diff_table_2= slave:test.t1 +-- source include/diff_tables.inc + +--connection master +DROP TABLE t1; +-- sync_slave_with_master + +-- echo ## case #1.1 - last_null_bit_pos==0 in record_compare with X bit +-- echo ## (1 column less and no varchar) +-- source include/master-slave-reset.inc +-- connection master + +-- eval CREATE TABLE t1 (c1 bigint(20) DEFAULT 0, c2 bigint(20) DEFAULT 0, c3 bigint(20) DEFAULT 0, c4 bigint(20) DEFAULT 0, c5 bigint(20) DEFAULT 0, c6 bigint(20) DEFAULT 0, c7 bigint(20) DEFAULT 0) ENGINE=$engine DEFAULT CHARSET=latin1 + +INSERT INTO t1 ( c5, c6 ) VALUES ( 1 , 35 ); +INSERT INTO t1 ( c5, c6 ) VALUES ( NULL, 35 ); +-- disable_warnings +UPDATE t1 SET c5 = 'a'; +-- enable_warnings +-- sync_slave_with_master + +-- let $diff_table_1= master:test.t1 +-- let $diff_table_2= slave:test.t1 +-- source include/diff_tables.inc + +--connection master +DROP TABLE t1; +-- sync_slave_with_master + +-- echo ## case #2 - X bit is wrongly set. + +-- source include/master-slave-reset.inc +-- connection master + +-- eval CREATE TABLE t1 (c1 int, c2 varchar(1) default '') ENGINE=$engine DEFAULT CHARSET= latin1 +INSERT INTO t1(c1) VALUES (10); +INSERT INTO t1(c1) VALUES (NULL); +UPDATE t1 SET c1= 0; +-- sync_slave_with_master + +-- let $diff_table_1= master:test.t1 +-- let $diff_table_2= slave:test.t1 +-- source include/diff_tables.inc + +-- connection master +DROP TABLE t1; +-- sync_slave_with_master + + diff --git a/mysql-test/suite/rpl/r/rpl_row_rec_comp_innodb.result b/mysql-test/suite/rpl/r/rpl_row_rec_comp_innodb.result new file mode 100644 index 00000000000..c461cafbd7c --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_rec_comp_innodb.result @@ -0,0 +1,46 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +## case #1 - last_null_bit_pos==0 in record_compare without X bit +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +CREATE TABLE t1 (c1 bigint(20) DEFAULT 0, c2 bigint(20) DEFAULT 0, c3 bigint(20) DEFAULT 0, c4 varchar(1) DEFAULT '', c5 bigint(20) DEFAULT 0, c6 bigint(20) DEFAULT 0, c7 bigint(20) DEFAULT 0, c8 bigint(20) DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=latin1; +INSERT INTO t1 ( c5, c6 ) VALUES ( 1 , 35 ); +INSERT INTO t1 ( c5, c6 ) VALUES ( NULL, 35 ); +UPDATE t1 SET c5 = 'a'; +Comparing tables master:test.t1 and slave:test.t1 +DROP TABLE t1; +## case #1.1 - last_null_bit_pos==0 in record_compare with X bit +## (1 column less and no varchar) +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +CREATE TABLE t1 (c1 bigint(20) DEFAULT 0, c2 bigint(20) DEFAULT 0, c3 bigint(20) DEFAULT 0, c4 bigint(20) DEFAULT 0, c5 bigint(20) DEFAULT 0, c6 bigint(20) DEFAULT 0, c7 bigint(20) DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=latin1; +INSERT INTO t1 ( c5, c6 ) VALUES ( 1 , 35 ); +INSERT INTO t1 ( c5, c6 ) VALUES ( NULL, 35 ); +UPDATE t1 SET c5 = 'a'; +Comparing tables master:test.t1 and slave:test.t1 +DROP TABLE t1; +## case #2 - X bit is wrongly set. +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +CREATE TABLE t1 (c1 int, c2 varchar(1) default '') ENGINE=InnoDB DEFAULT CHARSET= latin1; +INSERT INTO t1(c1) VALUES (10); +INSERT INTO t1(c1) VALUES (NULL); +UPDATE t1 SET c1= 0; +Comparing tables master:test.t1 and slave:test.t1 +DROP TABLE t1; diff --git a/mysql-test/suite/rpl/r/rpl_row_rec_comp_myisam.result b/mysql-test/suite/rpl/r/rpl_row_rec_comp_myisam.result new file mode 100644 index 00000000000..38fbe486d2e --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_rec_comp_myisam.result @@ -0,0 +1,60 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +## case #1 - last_null_bit_pos==0 in record_compare without X bit +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +CREATE TABLE t1 (c1 bigint(20) DEFAULT 0, c2 bigint(20) DEFAULT 0, c3 bigint(20) DEFAULT 0, c4 varchar(1) DEFAULT '', c5 bigint(20) DEFAULT 0, c6 bigint(20) DEFAULT 0, c7 bigint(20) DEFAULT 0, c8 bigint(20) DEFAULT 0) ENGINE=MyISAM DEFAULT CHARSET=latin1; +INSERT INTO t1 ( c5, c6 ) VALUES ( 1 , 35 ); +INSERT INTO t1 ( c5, c6 ) VALUES ( NULL, 35 ); +UPDATE t1 SET c5 = 'a'; +Comparing tables master:test.t1 and slave:test.t1 +DROP TABLE t1; +## case #1.1 - last_null_bit_pos==0 in record_compare with X bit +## (1 column less and no varchar) +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +CREATE TABLE t1 (c1 bigint(20) DEFAULT 0, c2 bigint(20) DEFAULT 0, c3 bigint(20) DEFAULT 0, c4 bigint(20) DEFAULT 0, c5 bigint(20) DEFAULT 0, c6 bigint(20) DEFAULT 0, c7 bigint(20) DEFAULT 0) ENGINE=MyISAM DEFAULT CHARSET=latin1; +INSERT INTO t1 ( c5, c6 ) VALUES ( 1 , 35 ); +INSERT INTO t1 ( c5, c6 ) VALUES ( NULL, 35 ); +UPDATE t1 SET c5 = 'a'; +Comparing tables master:test.t1 and slave:test.t1 +DROP TABLE t1; +## case #2 - X bit is wrongly set. +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +CREATE TABLE t1 (c1 int, c2 varchar(1) default '') ENGINE=MyISAM DEFAULT CHARSET= latin1; +INSERT INTO t1(c1) VALUES (10); +INSERT INTO t1(c1) VALUES (NULL); +UPDATE t1 SET c1= 0; +Comparing tables master:test.t1 and slave:test.t1 +DROP TABLE t1; +## coverage purposes - Field_bits +## 1 X bit + 2 Null bits + 5 bits => last_null_bit_pos==0 +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +CREATE TABLE t1 (c1 bigint(20) DEFAULT 0, c2 bit(5)) ENGINE=MyISAM DEFAULT CHARSET=latin1; +INSERT INTO t1(c1,c2) VALUES (10, b'1'); +INSERT INTO t1(c1,c2) VALUES (NULL, b'1'); +UPDATE t1 SET c1= 0; +Comparing tables master:test.t1 and slave:test.t1 +DROP TABLE t1; diff --git a/mysql-test/suite/rpl/t/rpl_row_rec_comp_innodb.test b/mysql-test/suite/rpl/t/rpl_row_rec_comp_innodb.test new file mode 100644 index 00000000000..67e4c4fb14d --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_rec_comp_innodb.test @@ -0,0 +1,10 @@ +-- source include/have_binlog_format_row.inc +-- source include/master-slave.inc +-- source include/have_innodb.inc + +# +# BUG#52868 Wrong handling of NULL value during update, replication out of sync +# + +-- let $engine= InnoDB +-- source extra/rpl_tests/rpl_record_compare.test diff --git a/mysql-test/suite/rpl/t/rpl_row_rec_comp_myisam.test b/mysql-test/suite/rpl/t/rpl_row_rec_comp_myisam.test new file mode 100644 index 00000000000..43fa99a51da --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_rec_comp_myisam.test @@ -0,0 +1,31 @@ +-- source include/have_binlog_format_row.inc +-- source include/master-slave.inc + +# +# BUG#52868 Wrong handling of NULL value during update, replication out of sync +# + +-- let $engine= MyISAM +-- source extra/rpl_tests/rpl_record_compare.test + +-- echo ## coverage purposes - Field_bits +-- echo ## 1 X bit + 2 Null bits + 5 bits => last_null_bit_pos==0 +## Added here because AFAIK it's only MyISAM and NDB that use Field_bits + +-- source include/master-slave-reset.inc +-- connection master + +-- eval CREATE TABLE t1 (c1 bigint(20) DEFAULT 0, c2 bit(5)) ENGINE=$engine DEFAULT CHARSET=latin1 + +INSERT INTO t1(c1,c2) VALUES (10, b'1'); +INSERT INTO t1(c1,c2) VALUES (NULL, b'1'); +UPDATE t1 SET c1= 0; +-- sync_slave_with_master + +-- let $diff_table_1= master:test.t1 +-- let $diff_table_2= slave:test.t1 +-- source include/diff_tables.inc + +-- connection master +DROP TABLE t1; +-- sync_slave_with_master diff --git a/sql/log_event.cc b/sql/log_event.cc index a8e227fa99b..057cd0cb178 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8758,11 +8758,28 @@ static bool record_compare(TABLE *table) { for (int i = 0 ; i < 2 ; ++i) { - saved_x[i]= table->record[i][0]; - saved_filler[i]= table->record[i][table->s->null_bytes - 1]; - table->record[i][0]|= 1U; - table->record[i][table->s->null_bytes - 1]|= - 256U - (1U << table->s->last_null_bit_pos); + /* + If we have an X bit then we need to take care of it. + */ + if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD)) + { + saved_x[i]= table->record[i][0]; + table->record[i][0]|= 1U; + } + + /* + If (last_null_bit_pos == 0 && null_bytes > 1), then: + + X bit (if any) + N nullable fields + M Field_bit fields = 8 bits + + Ie, the entire byte is used. + */ + if (table->s->last_null_bit_pos > 0) + { + saved_filler[i]= table->record[i][table->s->null_bytes - 1]; + table->record[i][table->s->null_bytes - 1]|= + 256U - (1U << table->s->last_null_bit_pos); + } } } @@ -8802,8 +8819,11 @@ record_compare_exit: { for (int i = 0 ; i < 2 ; ++i) { - table->record[i][0]= saved_x[i]; - table->record[i][table->s->null_bytes - 1]= saved_filler[i]; + if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD)) + table->record[i][0]= saved_x[i]; + + if (table->s->last_null_bit_pos) + table->record[i][table->s->null_bytes - 1]= saved_filler[i]; } } diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index df162761b35..60b0df5253a 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -342,12 +342,29 @@ static bool record_compare(TABLE *table) if (table->s->null_bytes > 0) { for (int i = 0 ; i < 2 ; ++i) - { - saved_x[i]= table->record[i][0]; - saved_filler[i]= table->record[i][table->s->null_bytes - 1]; - table->record[i][0]|= 1U; - table->record[i][table->s->null_bytes - 1]|= - 256U - (1U << table->s->last_null_bit_pos); + { + /* + If we have an X bit then we need to take care of it. + */ + if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD)) + { + saved_x[i]= table->record[i][0]; + table->record[i][0]|= 1U; + } + + /* + If (last_null_bit_pos == 0 && null_bytes > 1), then: + + X bit (if any) + N nullable fields + M Field_bit fields = 8 bits + + Ie, the entire byte is used. + */ + if (table->s->last_null_bit_pos > 0) + { + saved_filler[i]= table->record[i][table->s->null_bytes - 1]; + table->record[i][table->s->null_bytes - 1]|= + 256U - (1U << table->s->last_null_bit_pos); + } } } @@ -387,8 +404,11 @@ record_compare_exit: { for (int i = 0 ; i < 2 ; ++i) { - table->record[i][0]= saved_x[i]; - table->record[i][table->s->null_bytes - 1]= saved_filler[i]; + if (!(table->s->db_options_in_use & HA_OPTION_PACK_RECORD)) + table->record[i][0]= saved_x[i]; + + if (table->s->last_null_bit_pos > 0) + table->record[i][table->s->null_bytes - 1]= saved_filler[i]; } } From e97a0c188139806bcc1144695fd95ece28f8fa25 Mon Sep 17 00:00:00 2001 From: Andrei Elkin Date: Tue, 4 May 2010 22:31:49 +0300 Subject: [PATCH 02/15] Bug #50942 mix_innodb_myisam_side_effects.test is not deterministic The test was used to fail because of UPDATE t3,t4 SET t3.a=t4.a + bug27417(1); did not prescribe the order of two row operations implied by the update. Fixed with forcing the order with adding a where condition w/o affecting the former bug fixes logics. --- .../extra/binlog_tests/mix_innodb_myisam_side_effects.test | 2 +- mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result | 4 ++-- mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mysql-test/extra/binlog_tests/mix_innodb_myisam_side_effects.test b/mysql-test/extra/binlog_tests/mix_innodb_myisam_side_effects.test index 26a70c4319e..68aa949a7c7 100644 --- a/mysql-test/extra/binlog_tests/mix_innodb_myisam_side_effects.test +++ b/mysql-test/extra/binlog_tests/mix_innodb_myisam_side_effects.test @@ -214,7 +214,7 @@ CREATE TABLE t5 (a int, PRIMARY KEY (a)) ENGINE=InnoDB; # execute --error ER_DUP_ENTRY - UPDATE t3,t4 SET t3.a=t4.a + bug27417(1); + UPDATE t3,t4 SET t3.a = t4.a + bug27417(1) where t3.a = 1; # check select count(*) from t1 /* must be 1 */; diff --git a/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result b/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result index 9057395ab82..8b42cadf6cb 100644 --- a/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result @@ -879,11 +879,11 @@ delete from t4; insert into t3 values (1,1),(2,2); insert into t4 values (1,1),(2,2); reset master; -UPDATE t3,t4 SET t3.a=t4.a + bug27417(1); +UPDATE t3,t4 SET t3.a = t4.a + bug27417(1) where t3.a = 1; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' select count(*) from t1 /* must be 1 */; count(*) -2 +1 drop table t4; delete from t1; delete from t2; diff --git a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result index 75094ca3b4c..b3f9baed2dd 100644 --- a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result @@ -802,7 +802,7 @@ delete from t4; insert into t3 values (1,1),(2,2); insert into t4 values (1,1),(2,2); reset master; -UPDATE t3,t4 SET t3.a=t4.a + bug27417(1); +UPDATE t3,t4 SET t3.a = t4.a + bug27417(1) where t3.a = 1; ERROR 23000: Duplicate entry '2' for key 'PRIMARY' select count(*) from t1 /* must be 1 */; count(*) From 27ac666fea63fe5001784c9cd747ea66b3b82271 Mon Sep 17 00:00:00 2001 From: Martin Hansson Date: Tue, 11 May 2010 16:21:05 +0200 Subject: [PATCH 03/15] Bug#48157: crash in Item_field::used_tables MySQL handles the join syntax "JOIN ... USING( field1, ... )" and natural joins by building the same parse tree as a corresponding join with an "ON t1.field1 = t2.field1 ..." expression would produce. This parse tree was not cleaned up properly in the following scenario. If a thread tries to lock some tables and finds that the tables were dropped and re-created while waiting for the lock, it cleans up column references in the statement by means a per-statement free list. But if the statement was part of a stored procedure, column references on the stored procedure's free list weren't cleaned up and thus contained pointers to freed objects. Fixed by adding a call to clean up the current prepared statement's free list. This is a backport from MySQL 5.1 --- sql/item.h | 7 +++++++ sql/sql_parse.cc | 6 ++++-- sql/sql_update.cc | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/sql/item.h b/sql/item.h index 22eb0c08e2d..31d501dc5c3 100644 --- a/sql/item.h +++ b/sql/item.h @@ -470,6 +470,13 @@ public: my_string name; /* Name from select */ /* Original item name (if it was renamed)*/ my_string orig_name; + /** + Intrusive list pointer for free list. If not null, points to the next + Item on some Query_arena's free list. For instance, stored procedures + have their own Query_arena's. + + @see Query_arena::free_list + */ Item *next; uint32 max_length; uint name_length; /* Length of name */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 807d6c09a46..d0a4fff442f 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1411,8 +1411,10 @@ end: } - /* This works because items are allocated with sql_alloc() */ - +/** + This works because items are allocated with sql_alloc(). + @note The function also handles null pointers (empty list). +*/ void cleanup_items(Item *item) { DBUG_ENTER("cleanup_items"); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 35ae0febcec..8d666c771ec 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -908,8 +908,9 @@ reopen_tables: items from 'fields' list, so the cleanup above is necessary to. */ cleanup_items(thd->free_list); - + cleanup_items(thd->stmt_arena->free_list); close_tables_for_reopen(thd, &table_list); + goto reopen_tables; } From b0b0000d4069d055cdbe9c5c7f2fd59eb31cc529 Mon Sep 17 00:00:00 2001 From: Sven Sandberg Date: Wed, 12 May 2010 12:29:02 +0200 Subject: [PATCH 04/15] BUG#50410: rpl_ndb tests should run with binlog_format=row Problem: The rpl_ndb did not set binlog_format explicitly. Since the default is binlog_format=statement, it means that the suite ran with that. ndb does not support binlog_format=statement, and many tests were skipped because they sourced include/have_binlog_format_row_or_mixed.inc Fix: set binlog_format=row explicitly in the configuration file for the rpl_ndb suite. --- Makefile.am | 4 ++-- mysql-test/suite/rpl_ndb/my.cnf | 4 ++++ mysql-test/suite/rpl_ndb/r/rpl_ndb_stm_innodb.result | 1 + mysql-test/suite/rpl_ndb/t/rpl_ndb_stm_innodb.test | 4 +++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index 821f7e4fd0f..7953b81fb7b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -147,8 +147,8 @@ test-bt: -if [ -e bin/ndbd -o -e storage/ndb/src/kernel/ndbd ] ; then \ cd mysql-test ; \ MTR_BUILD_THREAD=auto \ - @PERL@ ./mysql-test-run.pl --comment=ndb+rpl_ndb+ps --force --timer \ - --ps-protocol --mysqld=--binlog-format=row --suite=ndb,rpl_ndb ; \ + @PERL@ ./mysql-test-run.pl --comment=ndb+ps --force --timer \ + --ps-protocol --mysqld=--binlog-format=row --suite=ndb ; \ MTR_BUILD_THREAD=auto \ @PERL@ ./mysql-test-run.pl --comment=ndb --force --timer \ --with-ndbcluster-only ; \ diff --git a/mysql-test/suite/rpl_ndb/my.cnf b/mysql-test/suite/rpl_ndb/my.cnf index 58fec36eedd..426608fc7fa 100644 --- a/mysql-test/suite/rpl_ndb/my.cnf +++ b/mysql-test/suite/rpl_ndb/my.cnf @@ -18,6 +18,8 @@ mysqld= ndbcluster # Turn on bin logging log-bin= master-bin +# Cluster only supports row format +binlog-format= row [mysqld.1.1] @@ -41,6 +43,8 @@ master-connect-retry= 1 log-bin= slave-bin relay-log= slave-relay-bin +# Cluster only supports row format +binlog-format= row init-rpl-role= slave log-slave-updates diff --git a/mysql-test/suite/rpl_ndb/r/rpl_ndb_stm_innodb.result b/mysql-test/suite/rpl_ndb/r/rpl_ndb_stm_innodb.result index 675a69d17a4..5327bfde7e0 100644 --- a/mysql-test/suite/rpl_ndb/r/rpl_ndb_stm_innodb.result +++ b/mysql-test/suite/rpl_ndb/r/rpl_ndb_stm_innodb.result @@ -4,6 +4,7 @@ reset master; reset slave; drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; start slave; +SET binlog_format = STATEMENT; *** Test 1 *** diff --git a/mysql-test/suite/rpl_ndb/t/rpl_ndb_stm_innodb.test b/mysql-test/suite/rpl_ndb/t/rpl_ndb_stm_innodb.test index bf7f1eed7b2..7c65e04717a 100644 --- a/mysql-test/suite/rpl_ndb/t/rpl_ndb_stm_innodb.test +++ b/mysql-test/suite/rpl_ndb/t/rpl_ndb_stm_innodb.test @@ -27,9 +27,11 @@ --disable_query_log --source include/have_ndb.inc --source include/have_innodb.inc ---source include/have_binlog_format_statement.inc --source include/ndb_master-slave.inc --enable_query_log + +# statement format is supported because master uses innodb +SET binlog_format = STATEMENT; let $off_set = 6; let $rpl_format = 'SBR'; --source extra/rpl_tests/rpl_ndb_apply_status.test From 44fe4c707b3aaa923d414efd801462658d800c1d Mon Sep 17 00:00:00 2001 From: Staale Smedseng Date: Wed, 12 May 2010 13:19:12 +0200 Subject: [PATCH 05/15] Bug #49756 Rows_examined is always 0 in the slow query log for update statements Only SELECT statements report any examined rows in the slow log. Slow UPDATE, DELETE and INSERT statements report 0 rows examined, unless the statement has a condition including a SELECT substatement. This patch adds counting of examined rows for the UPDATE and DELETE statements. An INSERT ... VALUES statement will still not report any rows as examined. --- mysql-test/r/log_state.result | 33 ++++++++++++++++++++++++++++++ mysql-test/t/log_state.test | 38 +++++++++++++++++++++++++++++++++++ sql/sql_class.h | 11 ++++++++-- sql/sql_delete.cc | 2 ++ sql/sql_update.cc | 3 +++ 5 files changed, 85 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/log_state.result b/mysql-test/r/log_state.result index 4ce678e37aa..654f9d127d3 100644 --- a/mysql-test/r/log_state.result +++ b/mysql-test/r/log_state.result @@ -308,8 +308,41 @@ SET @@global.general_log = @old_general_log; SET @@global.general_log_file = @old_general_log_file; SET @@global.slow_query_log = @old_slow_query_log; SET @@global.slow_query_log_file = @old_slow_query_log_file; +# +# Bug #49756 Rows_examined is always 0 in the slow query log +# for update statements +# +SET @old_log_output = @@global.log_output; +SET GLOBAL log_output = "TABLE"; +SET GLOBAL slow_query_log = ON; +SET GLOBAL long_query_time = 0.001; +TRUNCATE TABLE mysql.slow_log; +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (b INT, PRIMARY KEY (b)); +INSERT INTO t2 VALUES (3),(4); +INSERT INTO t1 VALUES (1+sleep(.01)),(2); +INSERT INTO t1 SELECT b+sleep(.01) from t2; +UPDATE t1 SET a=a+sleep(.01) WHERE a>2; +UPDATE t1 SET a=a+sleep(.01) ORDER BY a DESC; +UPDATE t2 set b=b+sleep(.01) limit 1; +UPDATE t1 SET a=a+sleep(.01) WHERE a in (SELECT b from t2); +DELETE FROM t1 WHERE a=a+sleep(.01) ORDER BY a LIMIT 2; +SELECT rows_examined,sql_text FROM mysql.slow_log; +rows_examined sql_text +0 INSERT INTO t1 VALUES (1+sleep(.01)),(2) +2 INSERT INTO t1 SELECT b+sleep(.01) from t2 +4 UPDATE t1 SET a=a+sleep(.01) WHERE a>2 +8 UPDATE t1 SET a=a+sleep(.01) ORDER BY a DESC +2 UPDATE t2 set b=b+sleep(.01) limit 1 +4 UPDATE t1 SET a=a+sleep(.01) WHERE a in (SELECT b from t2) +6 DELETE FROM t1 WHERE a=a+sleep(.01) ORDER BY a LIMIT 2 +DROP TABLE t1,t2; +TRUNCATE TABLE mysql.slow_log; +# end of bug#49756 End of 5.1 tests # Close connection con1 +SET GLOBAL long_query_time = DEFAULT; +SET GLOBAL log_output = @old_log_output; SET global general_log = @old_general_log; SET global general_log_file = @old_general_log_file; SET global slow_query_log = @old_slow_query_log; diff --git a/mysql-test/t/log_state.test b/mysql-test/t/log_state.test index e40dd1e3491..05e17dc9fa7 100644 --- a/mysql-test/t/log_state.test +++ b/mysql-test/t/log_state.test @@ -362,6 +362,42 @@ if(!$fixed_bug38124) } +########################################################################### + +--echo # +--echo # Bug #49756 Rows_examined is always 0 in the slow query log +--echo # for update statements +--echo # + +SET @old_log_output = @@global.log_output; +SET GLOBAL log_output = "TABLE"; +SET GLOBAL slow_query_log = ON; +SET GLOBAL long_query_time = 0.001; + +# clear slow_log of any residual slow queries +TRUNCATE TABLE mysql.slow_log; +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (b INT, PRIMARY KEY (b)); +INSERT INTO t2 VALUES (3),(4); + +connect (con2,localhost,root,,); +INSERT INTO t1 VALUES (1+sleep(.01)),(2); +INSERT INTO t1 SELECT b+sleep(.01) from t2; +UPDATE t1 SET a=a+sleep(.01) WHERE a>2; +UPDATE t1 SET a=a+sleep(.01) ORDER BY a DESC; +UPDATE t2 set b=b+sleep(.01) limit 1; +UPDATE t1 SET a=a+sleep(.01) WHERE a in (SELECT b from t2); +DELETE FROM t1 WHERE a=a+sleep(.01) ORDER BY a LIMIT 2; + +SELECT rows_examined,sql_text FROM mysql.slow_log; +disconnect con2; +connection default; +DROP TABLE t1,t2; +TRUNCATE TABLE mysql.slow_log; + +--echo # end of bug#49756 + + --echo End of 5.1 tests --enable_ps_protocol @@ -376,6 +412,8 @@ disconnect con1; connection default; # Reset global system variables to initial values if forgotten somewhere above. +SET GLOBAL long_query_time = DEFAULT; +SET GLOBAL log_output = @old_log_output; SET global general_log = @old_general_log; SET global general_log_file = @old_general_log_file; SET global slow_query_log = @old_slow_query_log; diff --git a/sql/sql_class.h b/sql/sql_class.h index 4d0552c5b9d..bd3cf8bc401 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1716,8 +1716,15 @@ public: */ ha_rows sent_row_count; - /* - number of rows we read, sent or not, including in create_sort_index() + /** + Number of rows read and/or evaluated for a statement. Used for + slow log reporting. + + An examined row is defined as a row that is read and/or evaluated + according to a statement condition, including in + create_sort_index(). Rows may be counted more than once, e.g., a + statement including ORDER BY could possibly evaluate the row in + filesort() before reading it for e.g. update. */ ha_rows examined_row_count; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 7e91a37257b..cc29b6f1b6b 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -248,6 +248,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, free_underlaid_joins(thd, &thd->lex->select_lex); DBUG_RETURN(TRUE); } + thd->examined_row_count+= examined_rows; /* Filesort has already found and selected the rows we want to delete, so we don't need the where clause @@ -304,6 +305,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, while (!(error=info.read_record(&info)) && !thd->killed && ! thd->is_error()) { + thd->examined_row_count++; // thd->is_error() is tested to disallow delete row on error if (!(select && select->skip_record())&& ! thd->is_error() ) { diff --git a/sql/sql_update.cc b/sql/sql_update.cc index d8141deba63..69f3a29e923 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -425,6 +425,7 @@ int mysql_update(THD *thd, { goto err; } + thd->examined_row_count+= examined_rows; /* Filesort has already found and selected the rows we want to update, so we don't need the where clause @@ -471,6 +472,7 @@ int mysql_update(THD *thd, while (!(error=info.read_record(&info)) && !thd->killed) { + thd->examined_row_count++; if (!(select && select->skip_record())) { if (table->file->was_semi_consistent_read()) @@ -577,6 +579,7 @@ int mysql_update(THD *thd, while (!(error=info.read_record(&info)) && !thd->killed) { + thd->examined_row_count++; if (!(select && select->skip_record())) { if (table->file->was_semi_consistent_read()) From a882f7e683782d317db778ff7683be760c38dcf8 Mon Sep 17 00:00:00 2001 From: Ramil Kalimullin Date: Wed, 12 May 2010 20:10:33 +0400 Subject: [PATCH 06/15] Fix for bug#52051: Aggregate functions incorrectly returns NULL from outer join query Problem: optimising MIN/MAX() queries without GROUP BY clause by replacing the aggregate expression with a constant, we may set it to NULL disregarding the fact that there may be outer joins involved. Fix: don't replace MIN/MAX() with NULL if there're outer joins. Note: the fix itself is just - if (!count) + if (!count && !outer_tables) set to NULL The rest of the patch eliminates repeated code to improve speed and for easy maintenance of the code. --- mysql-test/r/group_by.result | 20 +++ mysql-test/t/group_by.test | 15 ++ sql/opt_sum.cc | 296 ++++++++++++++++------------------- 3 files changed, 170 insertions(+), 161 deletions(-) diff --git a/mysql-test/r/group_by.result b/mysql-test/r/group_by.result index 90503300065..645dd460735 100644 --- a/mysql-test/r/group_by.result +++ b/mysql-test/r/group_by.result @@ -1790,4 +1790,24 @@ aa b COUNT( b) 1 10 1 DROP TABLE t1, t2; # +# Bug#52051: Aggregate functions incorrectly returns NULL from outer +# join query +# +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE TABLE t2 (a INT PRIMARY KEY); +INSERT INTO t2 VALUES (1), (2); +EXPLAIN SELECT MIN(t2.a) FROM t2 LEFT JOIN t1 ON t2.a = t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +SELECT MIN(t2.a) FROM t2 LEFT JOIN t1 ON t2.a = t1.a; +MIN(t2.a) +1 +EXPLAIN SELECT MAX(t2.a) FROM t2 LEFT JOIN t1 ON t2.a = t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +SELECT MAX(t2.a) FROM t2 LEFT JOIN t1 ON t2.a = t1.a; +MAX(t2.a) +2 +DROP TABLE t1, t2; +# # End of 5.1 tests diff --git a/mysql-test/t/group_by.test b/mysql-test/t/group_by.test index e6ea5ecc7f6..c5b27ee1a62 100644 --- a/mysql-test/t/group_by.test +++ b/mysql-test/t/group_by.test @@ -1205,6 +1205,21 @@ SELECT (SELECT t2.a FROM t2 WHERE t2.a = t1.a) aa, b, COUNT( b) DROP TABLE t1, t2; + +--echo # +--echo # Bug#52051: Aggregate functions incorrectly returns NULL from outer +--echo # join query +--echo # +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE TABLE t2 (a INT PRIMARY KEY); +INSERT INTO t2 VALUES (1), (2); +EXPLAIN SELECT MIN(t2.a) FROM t2 LEFT JOIN t1 ON t2.a = t1.a; +SELECT MIN(t2.a) FROM t2 LEFT JOIN t1 ON t2.a = t1.a; +EXPLAIN SELECT MAX(t2.a) FROM t2 LEFT JOIN t1 ON t2.a = t1.a; +SELECT MAX(t2.a) FROM t2 LEFT JOIN t1 ON t2.a = t1.a; +DROP TABLE t1, t2; + + --echo # --echo # End of 5.1 tests diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc index 8a3fe6c3ae8..666485fcfa2 100644 --- a/sql/opt_sum.cc +++ b/sql/opt_sum.cc @@ -88,6 +88,123 @@ static ulonglong get_exact_record_count(TABLE_LIST *tables) } +/** + Use index to read MIN(field) value. + + @param table Table object + @param ref Reference to the structure where we store the key value + @item_field Field used in MIN() + @range_fl Whether range endpoint is strict less than + @prefix_len Length of common key part for the range + + @retval + 0 No errors + HA_ERR_... Otherwise +*/ + +static int get_index_min_value(TABLE *table, TABLE_REF *ref, + Item_field *item_field, uint range_fl, + uint prefix_len) +{ + int error; + + if (!ref->key_length) + error= table->file->index_first(table->record[0]); + else + { + /* + Use index to replace MIN/MAX functions with their values + according to the following rules: + + 1) Insert the minimum non-null values where the WHERE clause still + matches, or + 2) a NULL value if there are only NULL values for key_part_k. + 3) Fail, producing a row of nulls + + Implementation: Read the smallest value using the search key. If + the interval is open, read the next value after the search + key. If read fails, and we're looking for a MIN() value for a + nullable column, test if there is an exact match for the key. + */ + if (!(range_fl & NEAR_MIN)) + /* + Closed interval: Either The MIN argument is non-nullable, or + we have a >= predicate for the MIN argument. + */ + error= table->file->index_read_map(table->record[0], + ref->key_buff, + make_prev_keypart_map(ref->key_parts), + HA_READ_KEY_OR_NEXT); + else + { + /* + Open interval: There are two cases: + 1) We have only MIN() and the argument column is nullable, or + 2) there is a > predicate on it, nullability is irrelevant. + We need to scan the next bigger record first. + */ + error= table->file->index_read_map(table->record[0], + ref->key_buff, + make_prev_keypart_map(ref->key_parts), + HA_READ_AFTER_KEY); + /* + If the found record is outside the group formed by the search + prefix, or there is no such record at all, check if all + records in that group have NULL in the MIN argument + column. If that is the case return that NULL. + + Check if case 1 from above holds. If it does, we should read + the skipped tuple. + */ + if (item_field->field->real_maybe_null() && + ref->key_buff[prefix_len] == 1 && + /* + Last keypart (i.e. the argument to MIN) is set to NULL by + find_key_for_maxmin only if all other keyparts are bound + to constants in a conjunction of equalities. Hence, we + can detect this by checking only if the last keypart is + NULL. + */ + (error == HA_ERR_KEY_NOT_FOUND || + key_cmp_if_same(table, ref->key_buff, ref->key, prefix_len))) + { + DBUG_ASSERT(item_field->field->real_maybe_null()); + error= table->file->index_read_map(table->record[0], + ref->key_buff, + make_prev_keypart_map(ref->key_parts), + HA_READ_KEY_EXACT); + } + } + } + return error; +} + + +/** + Use index to read MAX(field) value. + + @param table Table object + @param ref Reference to the structure where we store the key value + @range_fl Whether range endpoint is strict greater than + + @retval + 0 No errors + HA_ERR_... Otherwise +*/ + +static int get_index_max_value(TABLE *table, TABLE_REF *ref, uint range_fl) +{ + return (ref->key_length ? + table->file->index_read_map(table->record[0], ref->key_buff, + make_prev_keypart_map(ref->key_parts), + range_fl & NEAR_MAX ? + HA_READ_BEFORE_KEY : + HA_READ_PREFIX_LAST_OR_PREV) : + table->file->index_last(table->record[0])); +} + + + /** Substitutes constants for some COUNT(), MIN() and MAX() functions. @@ -220,9 +337,11 @@ int opt_sum_query(TABLE_LIST *tables, List &all_fields,COND *conds) const_result= 0; break; case Item_sum::MIN_FUNC: + case Item_sum::MAX_FUNC: { + int is_max= test(item_sum->sum_func() == Item_sum::MAX_FUNC); /* - If MIN(expr) is the first part of a key or if all previous + If MIN/MAX(expr) is the first part of a key or if all previous parts of the key is found in the COND, then we can use indexes to find the key. */ @@ -241,89 +360,26 @@ int opt_sum_query(TABLE_LIST *tables, List &all_fields,COND *conds) Look for a partial key that can be used for optimization. If we succeed, ref.key_length will contain the length of this key, while prefix_len will contain the length of - the beginning of this key without field used in MIN(). + the beginning of this key without field used in MIN/MAX(). Type of range for the key part for this field will be returned in range_fl. */ if (table->file->inited || (outer_tables & table->map) || - !find_key_for_maxmin(0, &ref, item_field->field, conds, + !find_key_for_maxmin(is_max, &ref, item_field->field, conds, &range_fl, &prefix_len)) { const_result= 0; break; } - error= table->file->ha_index_init((uint) ref.key, 1); + table->file->ha_index_init((uint) ref.key, 1); - if (!ref.key_length) - error= table->file->index_first(table->record[0]); - else - { - /* - Use index to replace MIN/MAX functions with their values - according to the following rules: - - 1) Insert the minimum non-null values where the WHERE clause still - matches, or - 2) a NULL value if there are only NULL values for key_part_k. - 3) Fail, producing a row of nulls + error= is_max ? + get_index_max_value(table, &ref, range_fl) : + get_index_min_value(table, &ref, item_field, range_fl, + prefix_len); - Implementation: Read the smallest value using the search key. If - the interval is open, read the next value after the search - key. If read fails, and we're looking for a MIN() value for a - nullable column, test if there is an exact match for the key. - */ - if (!(range_fl & NEAR_MIN)) - /* - Closed interval: Either The MIN argument is non-nullable, or - we have a >= predicate for the MIN argument. - */ - error= table->file->index_read_map(table->record[0], - ref.key_buff, - make_prev_keypart_map(ref.key_parts), - HA_READ_KEY_OR_NEXT); - else - { - /* - Open interval: There are two cases: - 1) We have only MIN() and the argument column is nullable, or - 2) there is a > predicate on it, nullability is irrelevant. - We need to scan the next bigger record first. - */ - error= table->file->index_read_map(table->record[0], - ref.key_buff, - make_prev_keypart_map(ref.key_parts), - HA_READ_AFTER_KEY); - /* - If the found record is outside the group formed by the search - prefix, or there is no such record at all, check if all - records in that group have NULL in the MIN argument - column. If that is the case return that NULL. - - Check if case 1 from above holds. If it does, we should read - the skipped tuple. - */ - if (item_field->field->real_maybe_null() && - ref.key_buff[prefix_len] == 1 && - /* - Last keypart (i.e. the argument to MIN) is set to NULL by - find_key_for_maxmin only if all other keyparts are bound - to constants in a conjunction of equalities. Hence, we - can detect this by checking only if the last keypart is - NULL. - */ - (error == HA_ERR_KEY_NOT_FOUND || - key_cmp_if_same(table, ref.key_buff, ref.key, prefix_len))) - { - DBUG_ASSERT(item_field->field->real_maybe_null()); - error= table->file->index_read_map(table->record[0], - ref.key_buff, - make_prev_keypart_map(ref.key_parts), - HA_READ_KEY_EXACT); - } - } - } /* Verify that the read tuple indeed matches the search key */ - if (!error && reckey_in_range(0, &ref, item_field->field, + if (!error && reckey_in_range(is_max, &ref, item_field->field, conds, range_fl, prefix_len)) error= HA_ERR_KEY_NOT_FOUND; table->set_keyread(FALSE); @@ -352,98 +408,16 @@ int opt_sum_query(TABLE_LIST *tables, List &all_fields,COND *conds) const_result= 0; break; } - if (!count) - { - /* If count == 0, then we know that is_exact_count == TRUE. */ - ((Item_sum_min*) item_sum)->clear(); /* Set to NULL. */ - } - else - ((Item_sum_min*) item_sum)->reset(); /* Set to the constant value. */ - ((Item_sum_min*) item_sum)->make_const(); - recalc_const_item= 1; - break; - } - case Item_sum::MAX_FUNC: - { /* - If MAX(expr) is the first part of a key or if all previous - parts of the key is found in the COND, then we can use - indexes to find the key. + If count == 0 (so is_exact_count == TRUE) and + there're no outer joins, set to NULL, + otherwise set to the constant value. */ - Item *expr=item_sum->get_arg(0); - if (expr->real_item()->type() == Item::FIELD_ITEM) - { - uchar key_buff[MAX_KEY_LENGTH]; - TABLE_REF ref; - uint range_fl, prefix_len; - - ref.key_buff= key_buff; - Item_field *item_field= (Item_field*) (expr->real_item()); - TABLE *table= item_field->field->table; - - /* - Look for a partial key that can be used for optimization. - If we succeed, ref.key_length will contain the length of - this key, while prefix_len will contain the length of - the beginning of this key without field used in MAX(). - Type of range for the key part for this field will be - returned in range_fl. - */ - if (table->file->inited || (outer_tables & table->map) || - !find_key_for_maxmin(1, &ref, item_field->field, conds, - &range_fl, &prefix_len)) - { - const_result= 0; - break; - } - error= table->file->ha_index_init((uint) ref.key, 1); - - if (!ref.key_length) - error= table->file->index_last(table->record[0]); - else - error= table->file->index_read_map(table->record[0], key_buff, - make_prev_keypart_map(ref.key_parts), - range_fl & NEAR_MAX ? - HA_READ_BEFORE_KEY : - HA_READ_PREFIX_LAST_OR_PREV); - if (!error && reckey_in_range(1, &ref, item_field->field, - conds, range_fl, prefix_len)) - error= HA_ERR_KEY_NOT_FOUND; - table->set_keyread(FALSE); - table->file->ha_index_end(); - if (error) - { - if (error == HA_ERR_KEY_NOT_FOUND || error == HA_ERR_END_OF_FILE) - return HA_ERR_KEY_NOT_FOUND; // No rows matching WHERE - /* HA_ERR_LOCK_DEADLOCK or some other error */ - table->file->print_error(error, MYF(0)); - table->in_use->fatal_error(); - return(error); - } - removed_tables|= table->map; - } - else if (!expr->const_item() || !is_exact_count) - { - /* - The optimization is not applicable in both cases: - (a) 'expr' is a non-constant expression. Then we can't - replace 'expr' by a constant. - (b) 'expr' is a costant. According to ANSI, MIN/MAX must return - NULL if the query does not return any rows. Thus, if we are not - able to determine if the query returns any rows, we can't apply - the optimization and replace MIN/MAX with a constant. - */ - const_result= 0; - break; - } - if (!count) - { - /* If count != 1, then we know that is_exact_count == TRUE. */ - ((Item_sum_max*) item_sum)->clear(); /* Set to NULL. */ - } + if (!count && !outer_tables) + item_sum->clear(); else - ((Item_sum_max*) item_sum)->reset(); /* Set to the constant value. */ - ((Item_sum_max*) item_sum)->make_const(); + item_sum->reset(); + item_sum->make_const(); recalc_const_item= 1; break; } From a6029500808b59598920caa5030224f73f7bc2df Mon Sep 17 00:00:00 2001 From: Luis Soares Date: Thu, 13 May 2010 16:40:31 +0100 Subject: [PATCH 07/15] BUG#53621: check_testcase fails for rpl_do_grant in mysql-5.1-bugteam MTR sporadically reported that rpl_do_grant does not clean up after itself. We fix this by backporting BUG 50984 fix. This deploys missing synchronization between master and slave. Additionally, it also fixes the check_testcase for rpl_tmp_table_and_DDL. --- mysql-test/suite/rpl/r/rpl_do_grant.result | 10 +++++++--- mysql-test/suite/rpl/t/rpl_do_grant.test | 18 ++++++++++++++---- .../suite/rpl/t/rpl_tmp_table_and_DDL.test | 1 + 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/mysql-test/suite/rpl/r/rpl_do_grant.result b/mysql-test/suite/rpl/r/rpl_do_grant.result index 4d8cfe3d8b8..1cea2cfa9ad 100644 --- a/mysql-test/suite/rpl/r/rpl_do_grant.result +++ b/mysql-test/suite/rpl/r/rpl_do_grant.result @@ -89,6 +89,7 @@ show grants for rpl_do_grant2@localhost; ERROR 42000: There is no such grant defined for user 'rpl_do_grant2' on host 'localhost' show grants for rpl_do_grant2@localhost; ERROR 42000: There is no such grant defined for user 'rpl_do_grant2' on host 'localhost' +call mtr.add_suppression("Slave: Operation DROP USER failed for 'create_rout_db'@'localhost' Error_code: 1396"); DROP DATABASE IF EXISTS bug42217_db; CREATE DATABASE bug42217_db; GRANT CREATE ROUTINE ON bug42217_db.* TO 'create_rout_db'@'localhost' @@ -166,9 +167,12 @@ DROP FUNCTION upgrade_del_func; DROP FUNCTION upgrade_alter_func; DROP DATABASE bug42217_db; DROP USER 'create_rout_db'@'localhost'; -call mtr.add_suppression("Slave: Operation DROP USER failed for 'create_rout_db'@'localhost' Error_code: 1396"); -USE mtr; -call mtr.add_suppression("Slave: Operation DROP USER failed for 'create_rout_db'@'localhost' Error_code: 1396"); +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; ######## BUG#49119 ####### ### i) test case from the 'how to repeat section' stop slave; diff --git a/mysql-test/suite/rpl/t/rpl_do_grant.test b/mysql-test/suite/rpl/t/rpl_do_grant.test index 88120aa3512..fb8ea35e869 100644 --- a/mysql-test/suite/rpl/t/rpl_do_grant.test +++ b/mysql-test/suite/rpl/t/rpl_do_grant.test @@ -120,6 +120,9 @@ show grants for rpl_do_grant2@localhost; # BUG42217 mysql.procs_priv does not get replicated ##################################################### connection master; +call mtr.add_suppression("Slave: Operation DROP USER failed for 'create_rout_db'@'localhost' Error_code: 1396"); +sync_slave_with_master; +connection master; --disable_warnings DROP DATABASE IF EXISTS bug42217_db; @@ -209,12 +212,19 @@ USE bug42217_db; DROP FUNCTION upgrade_del_func; DROP FUNCTION upgrade_alter_func; DROP DATABASE bug42217_db; +-- sync_slave_with_master +-- connection master + +# user was already dropped in the slave before +# so no need to wait for the slave to replicate +# this statement (if it did and we later synced +# the slave it would end up in an error anyway) DROP USER 'create_rout_db'@'localhost'; -call mtr.add_suppression("Slave: Operation DROP USER failed for 'create_rout_db'@'localhost' Error_code: 1396"); -connection slave; -USE mtr; -call mtr.add_suppression("Slave: Operation DROP USER failed for 'create_rout_db'@'localhost' Error_code: 1396"); +# finish entire clean up (remove binlogs) +# so that we leave a pristine environment for the +# following tests +-- source include/master-slave-reset.inc # BUG#49119: Master crashes when executing 'REVOKE ... ON # {PROCEDURE|FUNCTION} FROM ...' diff --git a/mysql-test/suite/rpl/t/rpl_tmp_table_and_DDL.test b/mysql-test/suite/rpl/t/rpl_tmp_table_and_DDL.test index 56924a2efe9..b3efb578b68 100644 --- a/mysql-test/suite/rpl/t/rpl_tmp_table_and_DDL.test +++ b/mysql-test/suite/rpl/t/rpl_tmp_table_and_DDL.test @@ -10,4 +10,5 @@ source include/have_binlog_format_row.inc; LET $ENGINE_TYPE= MyISAM; source extra/rpl_tests/rpl_tmp_table_and_DDL.test; +sync_slave_with_master; From 09b6efcc763d378e28e7fc2f75b82c2425447828 Mon Sep 17 00:00:00 2001 From: Gleb Shchepa Date: Fri, 14 May 2010 15:36:27 +0400 Subject: [PATCH 08/15] Bug #53450: Crash / assertion "virtual int ha_myisam::index_first(uchar*)") at assert.c:81 Single-table DELETE crash/assertion similar to single-table UPDATE bug 14272. Same resolution as for the bug 14272: Don't run index scan when we should use quick select. This could cause failures because there are table handlers (like federated) that support quick select scanning but do not support index scanning. --- mysql-test/r/delete.result | 9 +++++++++ mysql-test/t/delete.test | 12 ++++++++++++ sql/sql_delete.cc | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/delete.result b/mysql-test/r/delete.result index 77b2071494d..36025cbfb35 100644 --- a/mysql-test/r/delete.result +++ b/mysql-test/r/delete.result @@ -349,4 +349,13 @@ END | DELETE IGNORE FROM t1; ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. DROP TABLE t1; +# +# Bug #53450: Crash/assertion +# "virtual int ha_myisam::index_first(uchar*)") at assert.c:81 +# +CREATE TABLE t1 (a INT, b INT, c INT, +INDEX(a), INDEX(b), INDEX(c)); +INSERT INTO t1 VALUES (1,2,3), (4,5,6), (7,8,9); +DELETE FROM t1 WHERE a = 10 OR b = 20 ORDER BY c LIMIT 1; +DROP TABLE t1; End of 5.1 tests diff --git a/mysql-test/t/delete.test b/mysql-test/t/delete.test index 7bbc470137a..5a0e86568f3 100644 --- a/mysql-test/t/delete.test +++ b/mysql-test/t/delete.test @@ -374,5 +374,17 @@ DELETE IGNORE FROM t1; DROP TABLE t1; +--echo # +--echo # Bug #53450: Crash/assertion +--echo # "virtual int ha_myisam::index_first(uchar*)") at assert.c:81 +--echo # + +CREATE TABLE t1 (a INT, b INT, c INT, + INDEX(a), INDEX(b), INDEX(c)); +INSERT INTO t1 VALUES (1,2,3), (4,5,6), (7,8,9); + +DELETE FROM t1 WHERE a = 10 OR b = 20 ORDER BY c LIMIT 1; + +DROP TABLE t1; --echo End of 5.1 tests diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index cc29b6f1b6b..07421ca55a6 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -266,7 +266,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, free_underlaid_joins(thd, select_lex); DBUG_RETURN(TRUE); } - if (usable_index==MAX_KEY) + if (usable_index==MAX_KEY || (select && select->quick)) init_read_record(&info, thd, table, select, 1, 1, FALSE); else init_read_record_idx(&info, thd, table, 1, usable_index); From 6400d6d54a1a80046ad1b848887afddd36f533c1 Mon Sep 17 00:00:00 2001 From: Alfranio Correia Date: Sun, 16 May 2010 12:45:21 +0100 Subject: [PATCH 09/15] BUG#50410: rpl_ndb tests should run with binlog_format=row Post-fix: Updated a test case after the patch for BUG#50410, because the patch makes ndb to run in the row format and as such unsafe warning messages are not printed out. --- .../rpl_ndb/r/rpl_ndb_mixed_engines_transactions.result | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mysql-test/suite/rpl_ndb/r/rpl_ndb_mixed_engines_transactions.result b/mysql-test/suite/rpl_ndb/r/rpl_ndb_mixed_engines_transactions.result index e02c3b23cad..7caa88a16a1 100644 --- a/mysql-test/suite/rpl_ndb/r/rpl_ndb_mixed_engines_transactions.result +++ b/mysql-test/suite/rpl_ndb/r/rpl_ndb_mixed_engines_transactions.result @@ -345,27 +345,19 @@ SET AUTOCOMMIT = 1; BEGIN; INSERT INTO tndb VALUES (147); INSERT INTO tinnodb SELECT * FROM tndb ORDER BY a DESC LIMIT 1; -Warnings: -Note 1592 Statement may not be safe to log in statement format. COMMIT; INSERT INTO tndb VALUES (148); BEGIN; INSERT INTO tinnodb SELECT * FROM tndb ORDER BY a DESC LIMIT 1; -Warnings: -Note 1592 Statement may not be safe to log in statement format. INSERT INTO tndb VALUES (149); COMMIT; BEGIN; INSERT INTO tndb VALUES (150); INSERT INTO tmyisam SELECT * FROM tndb ORDER BY a DESC LIMIT 1; -Warnings: -Note 1592 Statement may not be safe to log in statement format. COMMIT; INSERT INTO tndb VALUES (151); BEGIN; INSERT INTO tmyisam SELECT * FROM tndb ORDER BY a DESC LIMIT 1; -Warnings: -Note 1592 Statement may not be safe to log in statement format. INSERT INTO tndb VALUES (152); COMMIT; ==== Verify the result ==== From 8edccf1e41c7474ab72b1403502ae8ebbcd52752 Mon Sep 17 00:00:00 2001 From: Alfranio Correia Date: Sun, 16 May 2010 15:37:44 +0100 Subject: [PATCH 10/15] BUG#49019 Mixing self-logging eng. and regular eng. does not switch to row in mixed mode Post-push fix after backporting the patch to 5.1-bugteam: 1 - changed the name of some variables to be equivalent to pe. 2 - fixed that patch to mark a statement as unsafe when both a self-logging eng. and regular eng. are accessed and one of them is updated. --- sql/sql_base.cc | 90 ++++++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 65649fc8921..55d4ab0c20f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -5154,63 +5154,75 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) set with all the capabilities bits set and one with no capabilities bits set. */ - handler::Table_flags flags_some_set= 0; - handler::Table_flags flags_all_set= + handler::Table_flags flags_write_some_set= 0; + handler::Table_flags flags_access_some_set= 0; + handler::Table_flags flags_write_all_set= HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE; - my_bool multi_engine= FALSE; - void* prev_ht= NULL; + /* + If different types of engines are about to be updated. + For example: Innodb and Falcon; Innodb and MyIsam. + */ + my_bool multi_write_engine= FALSE; + void* prev_write_ht= NULL; + + /* + If different types of engines are about to be accessed + and any of them is about to be updated. For example: + Innodb and Falcon; Innodb and MyIsam. + */ + my_bool multi_access_engine= FALSE; + void* prev_access_ht= NULL; for (TABLE_LIST *table= tables; table; table= table->next_global) { if (table->placeholder()) continue; if (table->table->s->table_category == TABLE_CATEGORY_PERFORMANCE) thd->lex->set_stmt_unsafe(); + ulonglong const flags= table->table->file->ha_table_flags(); if (table->lock_type >= TL_WRITE_ALLOW_WRITE) { - ulonglong const flags= table->table->file->ha_table_flags(); DBUG_PRINT("info", ("table: %s; ha_table_flags: %s%s", table->table_name, FLAGSTR(flags, HA_BINLOG_STMT_CAPABLE), FLAGSTR(flags, HA_BINLOG_ROW_CAPABLE))); - if (prev_ht && prev_ht != table->table->file->ht) - multi_engine= TRUE; - prev_ht= table->table->file->ht; - flags_all_set &= flags; - flags_some_set |= flags; + if (prev_write_ht && prev_write_ht != table->table->file->ht) + multi_write_engine= TRUE; + prev_write_ht= table->table->file->ht; + flags_write_all_set &= flags; + flags_write_some_set |= flags; } + if (prev_access_ht && prev_access_ht != table->table->file->ht) + multi_access_engine= TRUE; + prev_access_ht= table->table->file->ht; + flags_access_some_set |= flags; } - DBUG_PRINT("info", ("flags_all_set: %s%s", - FLAGSTR(flags_all_set, HA_BINLOG_STMT_CAPABLE), - FLAGSTR(flags_all_set, HA_BINLOG_ROW_CAPABLE))); - DBUG_PRINT("info", ("flags_some_set: %s%s", - FLAGSTR(flags_some_set, HA_BINLOG_STMT_CAPABLE), - FLAGSTR(flags_some_set, HA_BINLOG_ROW_CAPABLE))); + DBUG_PRINT("info", ("flags_write_all_set: %s%s", + FLAGSTR(flags_write_all_set, HA_BINLOG_STMT_CAPABLE), + FLAGSTR(flags_write_all_set, HA_BINLOG_ROW_CAPABLE))); + DBUG_PRINT("info", ("flags_write_some_set: %s%s", + FLAGSTR(flags_write_some_set, HA_BINLOG_STMT_CAPABLE), + FLAGSTR(flags_write_some_set, HA_BINLOG_ROW_CAPABLE))); + DBUG_PRINT("info", ("flags_access_some_set: %s%s", + FLAGSTR(flags_access_some_set, HA_BINLOG_STMT_CAPABLE), + FLAGSTR(flags_access_some_set, HA_BINLOG_ROW_CAPABLE))); + DBUG_PRINT("info", ("multi_write_engine: %s", + multi_write_engine ? "TRUE" : "FALSE")); + DBUG_PRINT("info", ("multi_access_engine: %s", + multi_access_engine ? "TRUE" : "FALSE")); DBUG_PRINT("info", ("thd->variables.binlog_format: %ld", thd->variables.binlog_format)); - DBUG_PRINT("info", ("multi_engine: %s", - multi_engine ? "TRUE" : "FALSE")); - /* - Reading from a self-logging engine and updating another engine - generates changes that are written to the binary log in the - statement format and may make slaves to diverge. In the mixed - mode, such changes should be written to the binary log in the - row format. - */ - if (multi_engine && - (flags_some_set & HA_HAS_OWN_BINLOGGING)) - thd->lex->set_stmt_unsafe(); int error= 0; - if (flags_all_set == 0) + if (flags_write_all_set == 0) { my_error((error= ER_BINLOG_LOGGING_IMPOSSIBLE), MYF(0), "Statement cannot be logged to the binary log in" " row-based nor statement-based format"); } else if (thd->variables.binlog_format == BINLOG_FORMAT_STMT && - (flags_all_set & HA_BINLOG_STMT_CAPABLE) == 0) + (flags_write_all_set & HA_BINLOG_STMT_CAPABLE) == 0) { my_error((error= ER_BINLOG_LOGGING_IMPOSSIBLE), MYF(0), "Statement-based format required for this statement," @@ -5218,7 +5230,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) } else if ((thd->variables.binlog_format == BINLOG_FORMAT_ROW || thd->lex->is_stmt_unsafe()) && - (flags_all_set & HA_BINLOG_ROW_CAPABLE) == 0) + (flags_write_all_set & HA_BINLOG_ROW_CAPABLE) == 0) { my_error((error= ER_BINLOG_LOGGING_IMPOSSIBLE), MYF(0), "Row-based format required for this statement," @@ -5231,8 +5243,8 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) statement cannot be logged atomically, so we generate an error rather than allowing the binlog to become corrupt. */ - if (multi_engine && - (flags_some_set & HA_HAS_OWN_BINLOGGING)) + if (multi_write_engine && + (flags_write_some_set & HA_HAS_OWN_BINLOGGING)) { error= ER_BINLOG_LOGGING_IMPOSSIBLE; my_error(error, MYF(0), @@ -5240,6 +5252,16 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) " than one engine involved and at least one engine" " is self-logging"); } + /* + Reading from a self-logging engine and updating another engine + generates changes that are written to the binary log in the + statement format and may make slaves to diverge. In the mixed + mode, such changes should be written to the binary log in the + row format. + */ + else if (multi_access_engine && + (flags_access_some_set & HA_HAS_OWN_BINLOGGING)) + thd->lex->set_stmt_unsafe(); DBUG_PRINT("info", ("error: %d", error)); @@ -5259,7 +5281,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) here. */ if (thd->lex->is_stmt_unsafe() || - (flags_all_set & HA_BINLOG_STMT_CAPABLE) == 0) + (flags_write_all_set & HA_BINLOG_STMT_CAPABLE) == 0) { thd->set_current_stmt_binlog_row_based_if_mixed(); } From f512cb4eaf0fbbf8cfa0444189da94f0215d8c5c Mon Sep 17 00:00:00 2001 From: Sergey Glukhov Date: Tue, 18 May 2010 13:28:21 +0500 Subject: [PATCH 11/15] Bug#48729 SELECT ... FROM INFORMATION_SCHEMA.ROUTINES causes memory to grow Analysis showed that in case of accessing I_S table ROUTINES we perform unnecessary allocations with get_field() function for every processed row that in their turn causes significant memory growth. the fix is to avoid use of get_field(). --- sql/sql_show.cc | 74 ++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 0afad764fac..61a414dcb6f 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4182,24 +4182,37 @@ int fill_schema_coll_charset_app(THD *thd, TABLE_LIST *tables, COND *cond) } +static inline void copy_field_as_string(Field *to_field, Field *from_field) +{ + char buff[MAX_FIELD_WIDTH]; + String tmp_str(buff, sizeof(buff), system_charset_info); + from_field->val_str(&tmp_str); + to_field->store(tmp_str.ptr(), tmp_str.length(), system_charset_info); +} + + bool store_schema_proc(THD *thd, TABLE *table, TABLE *proc_table, const char *wild, bool full_access, const char *sp_user) { - String tmp_string; - String sp_db, sp_name, definer; MYSQL_TIME time; LEX *lex= thd->lex; CHARSET_INFO *cs= system_charset_info; - get_field(thd->mem_root, proc_table->field[0], &sp_db); - get_field(thd->mem_root, proc_table->field[1], &sp_name); - get_field(thd->mem_root, proc_table->field[11], &definer); + char sp_db_buff[NAME_LEN + 1], sp_name_buff[NAME_LEN + 1], + definer_buff[USERNAME_LENGTH + HOSTNAME_LENGTH + 2]; + String sp_db(sp_db_buff, sizeof(sp_db_buff), cs); + String sp_name(sp_name_buff, sizeof(sp_name_buff), cs); + String definer(definer_buff, sizeof(definer_buff), cs); + + proc_table->field[0]->val_str(&sp_db); + proc_table->field[1]->val_str(&sp_name); + proc_table->field[11]->val_str(&definer); + if (!full_access) - full_access= !strcmp(sp_user, definer.ptr()); - if (!full_access && check_some_routine_access(thd, sp_db.ptr(), - sp_name.ptr(), - proc_table->field[2]-> - val_int() == - TYPE_ENUM_PROCEDURE)) + full_access= !strcmp(sp_user, definer.c_ptr_safe()); + if (!full_access && + check_some_routine_access(thd, sp_db.c_ptr_safe(), sp_name.c_ptr_safe(), + proc_table->field[2]->val_int() == + TYPE_ENUM_PROCEDURE)) return 0; if ((lex->sql_command == SQLCOM_SHOW_STATUS_PROC && @@ -4209,55 +4222,42 @@ bool store_schema_proc(THD *thd, TABLE *table, TABLE *proc_table, (sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND) == 0) { restore_record(table, s->default_values); - if (!wild || !wild[0] || !wild_compare(sp_name.ptr(), wild, 0)) + if (!wild || !wild[0] || !wild_compare(sp_name.c_ptr_safe(), wild, 0)) { int enum_idx= (int) proc_table->field[5]->val_int(); table->field[3]->store(sp_name.ptr(), sp_name.length(), cs); - get_field(thd->mem_root, proc_table->field[3], &tmp_string); - table->field[0]->store(tmp_string.ptr(), tmp_string.length(), cs); + copy_field_as_string(table->field[0], proc_table->field[3]); table->field[2]->store(sp_db.ptr(), sp_db.length(), cs); - get_field(thd->mem_root, proc_table->field[2], &tmp_string); - table->field[4]->store(tmp_string.ptr(), tmp_string.length(), cs); + copy_field_as_string(table->field[4], proc_table->field[2]); if (proc_table->field[2]->val_int() == TYPE_ENUM_FUNCTION) { - get_field(thd->mem_root, proc_table->field[9], &tmp_string); - table->field[5]->store(tmp_string.ptr(), tmp_string.length(), cs); + copy_field_as_string(table->field[5], proc_table->field[9]); table->field[5]->set_notnull(); } if (full_access) { - get_field(thd->mem_root, proc_table->field[19], &tmp_string); - table->field[7]->store(tmp_string.ptr(), tmp_string.length(), cs); + copy_field_as_string(table->field[7], proc_table->field[19]); table->field[7]->set_notnull(); } table->field[6]->store(STRING_WITH_LEN("SQL"), cs); table->field[10]->store(STRING_WITH_LEN("SQL"), cs); - get_field(thd->mem_root, proc_table->field[6], &tmp_string); - table->field[11]->store(tmp_string.ptr(), tmp_string.length(), cs); + copy_field_as_string(table->field[11], proc_table->field[6]); table->field[12]->store(sp_data_access_name[enum_idx].str, sp_data_access_name[enum_idx].length , cs); - get_field(thd->mem_root, proc_table->field[7], &tmp_string); - table->field[14]->store(tmp_string.ptr(), tmp_string.length(), cs); + copy_field_as_string(table->field[14], proc_table->field[7]); + bzero((char *)&time, sizeof(time)); ((Field_timestamp *) proc_table->field[12])->get_time(&time); table->field[15]->store_time(&time, MYSQL_TIMESTAMP_DATETIME); bzero((char *)&time, sizeof(time)); ((Field_timestamp *) proc_table->field[13])->get_time(&time); table->field[16]->store_time(&time, MYSQL_TIMESTAMP_DATETIME); - get_field(thd->mem_root, proc_table->field[14], &tmp_string); - table->field[17]->store(tmp_string.ptr(), tmp_string.length(), cs); - get_field(thd->mem_root, proc_table->field[15], &tmp_string); - table->field[18]->store(tmp_string.ptr(), tmp_string.length(), cs); + copy_field_as_string(table->field[17], proc_table->field[14]); + copy_field_as_string(table->field[18], proc_table->field[15]); table->field[19]->store(definer.ptr(), definer.length(), cs); - - get_field(thd->mem_root, proc_table->field[16], &tmp_string); - table->field[20]->store(tmp_string.ptr(), tmp_string.length(), cs); - - get_field(thd->mem_root, proc_table->field[17], &tmp_string); - table->field[21]->store(tmp_string.ptr(), tmp_string.length(), cs); - - get_field(thd->mem_root, proc_table->field[18], &tmp_string); - table->field[22]->store(tmp_string.ptr(), tmp_string.length(), cs); + copy_field_as_string(table->field[20], proc_table->field[16]); + copy_field_as_string(table->field[21], proc_table->field[17]); + copy_field_as_string(table->field[22], proc_table->field[18]); return schema_table_store_record(thd, table); } From a22c69b233e6dd027a3488feb2f5b909ca5681b1 Mon Sep 17 00:00:00 2001 From: Tor Didriksen Date: Wed, 19 May 2010 11:18:59 +0200 Subject: [PATCH 12/15] Backport from next-mr-bugfixing of tor.didriksen@sun.com-20100106140051-3j2iuag63eltsr2e Bug #50087 Interval arithmetic for Event_queue_element is not portable. Subtraction of two unsigned months yielded a (very large) positive value. Conversion of this to a signed value was not necessarily well defined. Solution: do the subtraction on signed values. --- mysql-test/r/events_scheduling.result | 19 +++++++++++++++++++ mysql-test/t/events_scheduling.test | 26 ++++++++++++++++++++++++++ sql/event_data_objects.cc | 5 +++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/events_scheduling.result b/mysql-test/r/events_scheduling.result index 7dfd10a53f8..262caea3d7f 100644 --- a/mysql-test/r/events_scheduling.result +++ b/mysql-test/r/events_scheduling.result @@ -82,5 +82,24 @@ DROP TABLE table_1; DROP TABLE table_2; DROP TABLE table_3; DROP TABLE table_4; + +Bug #50087 Interval arithmetic for Event_queue_element is not portable. + +CREATE TABLE t1(a int); +CREATE EVENT e1 ON SCHEDULE EVERY 1 MONTH +STARTS NOW() - INTERVAL 1 MONTH +ENDS NOW() + INTERVAL 2 MONTH +ON COMPLETION PRESERVE +DO +INSERT INTO t1 VALUES (1); +CREATE EVENT e2 ON SCHEDULE EVERY 1 MONTH +STARTS NOW() +ENDS NOW() + INTERVAL 11 MONTH +ON COMPLETION PRESERVE +DO +INSERT INTO t1 VALUES (1); +DROP TABLE t1; +DROP EVENT e1; +DROP EVENT e2; DROP DATABASE events_test; SET GLOBAL event_scheduler=@event_scheduler; diff --git a/mysql-test/t/events_scheduling.test b/mysql-test/t/events_scheduling.test index 041a2def490..5f16f8bea6a 100644 --- a/mysql-test/t/events_scheduling.test +++ b/mysql-test/t/events_scheduling.test @@ -108,6 +108,32 @@ DROP TABLE table_1; DROP TABLE table_2; DROP TABLE table_3; DROP TABLE table_4; + +-- echo +-- echo Bug #50087 Interval arithmetic for Event_queue_element is not portable. +-- echo + +CREATE TABLE t1(a int); + +CREATE EVENT e1 ON SCHEDULE EVERY 1 MONTH +STARTS NOW() - INTERVAL 1 MONTH +ENDS NOW() + INTERVAL 2 MONTH +ON COMPLETION PRESERVE +DO + INSERT INTO t1 VALUES (1); + +CREATE EVENT e2 ON SCHEDULE EVERY 1 MONTH +STARTS NOW() +ENDS NOW() + INTERVAL 11 MONTH +ON COMPLETION PRESERVE +DO + INSERT INTO t1 VALUES (1); + +DROP TABLE t1; +DROP EVENT e1; +DROP EVENT e2; + + DROP DATABASE events_test; SET GLOBAL event_scheduler=@event_scheduler; diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 92e237a17b7..0218092c4c7 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -834,8 +834,9 @@ bool get_next_time(const Time_zone *time_zone, my_time_t *next, } else { - long diff_months= (long) (local_now.year - local_start.year)*12 + - (local_now.month - local_start.month); + long diff_months= ((long) local_now.year - (long) local_start.year)*12 + + ((long) local_now.month - (long) local_start.month); + /* Unlike for seconds above, the formula below returns the interval that, when added to the local_start, will give the time in the From 7132ccd7caa5e96f97eb0b8a9242cc11693ab1c8 Mon Sep 17 00:00:00 2001 From: Sergey Glukhov Date: Thu, 20 May 2010 10:31:03 +0400 Subject: [PATCH 13/15] Bug#52884 mysql-test-run does not work with --debug option Server crashes on 64bit linux with 'double free or corruption' message, on 32bit mysql-test-run silently fails on bootstrap stage. The problem is that FreeState() is called twice for init_settings struct in _db_end_ function. The fix is to remove superfluous FreeState() call. Additional fix: fixed discrepancy of result file when debug & valgrind options are enabled for MTR. --- dbug/dbug.c | 3 --- mysql-test/r/variables_debug.result | 2 ++ mysql-test/t/variables_debug.test | 3 +++ sql/set_var.cc | 9 +++++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dbug/dbug.c b/dbug/dbug.c index 30ad6c2c6d1..8fa5ed9af6b 100644 --- a/dbug/dbug.c +++ b/dbug/dbug.c @@ -1517,10 +1517,7 @@ void _db_end_() while ((discard= cs->stack)) { if (discard == &init_settings) - { - FreeState (cs, discard, 0); break; - } cs->stack= discard->next; FreeState(cs, discard, 1); } diff --git a/mysql-test/r/variables_debug.result b/mysql-test/r/variables_debug.result index 85eaf34b033..d578fa957fc 100644 --- a/mysql-test/r/variables_debug.result +++ b/mysql-test/r/variables_debug.result @@ -1,3 +1,4 @@ +SET @old_debug = @@GLOBAL.debug; set debug= 'T'; select @@debug; @@debug @@ -22,4 +23,5 @@ SET GLOBAL debug=''; SELECT @@global.debug; @@global.debug +SET GLOBAL debug=@old_debug; End of 5.1 tests diff --git a/mysql-test/t/variables_debug.test b/mysql-test/t/variables_debug.test index 8f2bde7ae42..12f5d2e6ca5 100644 --- a/mysql-test/t/variables_debug.test +++ b/mysql-test/t/variables_debug.test @@ -1,5 +1,7 @@ --source include/have_debug.inc +SET @old_debug = @@GLOBAL.debug; + # # Bug#34678 @@debug variable's incremental mode # @@ -21,5 +23,6 @@ SELECT @@global.debug; SET GLOBAL debug=''; SELECT @@global.debug; +SET GLOBAL debug=@old_debug; --echo End of 5.1 tests diff --git a/sql/set_var.cc b/sql/set_var.cc index f7d9d9df42e..8f0ad93ba43 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -4239,10 +4239,15 @@ bool sys_var_thd_dbug::check(THD *thd, set_var *var) bool sys_var_thd_dbug::update(THD *thd, set_var *var) { + char buf[256]; + String str(buf, sizeof(buf), system_charset_info), *res; + + res= var->value->val_str(&str); + if (var->type == OPT_GLOBAL) - DBUG_SET_INITIAL(var ? var->value->str_value.c_ptr() : ""); + DBUG_SET_INITIAL(res ? res->c_ptr() : ""); else - DBUG_SET(var ? var->value->str_value.c_ptr() : ""); + DBUG_SET(res ? res->c_ptr() : ""); return 0; } From 836bb54c261817827569032e3da1bf7417aa0072 Mon Sep 17 00:00:00 2001 From: Sven Sandberg Date: Thu, 20 May 2010 17:38:01 +0200 Subject: [PATCH 14/15] BUG#52987: mysqldump fails if umask=0077 Problem: The test case mysqldump reads a file that must be world-readable. The test did not force the file to be world-readable, so if the tree was branched with a umask of 0077, the test would fail. Fix: chmod the file. --- mysql-test/t/mysqldump.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/t/mysqldump.test b/mysql-test/t/mysqldump.test index 0bf9916dcd5..9246d488dd8 100644 --- a/mysql-test/t/mysqldump.test +++ b/mysql-test/t/mysqldump.test @@ -1983,6 +1983,7 @@ drop table if exists `load`; create table `load` (a varchar(255)); --copy_file std_data/words.dat $MYSQLTEST_VARDIR/tmp/load.txt +--chmod 0644 $MYSQLTEST_VARDIR/tmp/load.txt --exec $MYSQL_IMPORT --ignore test $MYSQLTEST_VARDIR/tmp/load.txt From 6e34b8b0ce0bd52cdc36182baeb3f313012d694b Mon Sep 17 00:00:00 2001 From: Gleb Shchepa Date: Fri, 21 May 2010 22:47:32 +0400 Subject: [PATCH 15/15] Bug #53804: serious flaws in the alter database .. upgrade data directory name command The check_db_name function has been modified to validate tails of #mysql50#-prefixed database names for compliance with MySQL 5.0 database name encoding rules (the check_table_name function call has been reused). --- mysql-test/r/renamedb.result | 2 +- mysql-test/r/upgrade.result | 28 ++++++++++++++++++++++++++++ mysql-test/t/renamedb.test | 2 +- mysql-test/t/upgrade.test | 34 ++++++++++++++++++++++++++++++++++ sql/mysql_priv.h | 1 + sql/sql_table.cc | 23 ++++++++++++++++++++--- sql/table.cc | 34 ++++++++++------------------------ 7 files changed, 95 insertions(+), 29 deletions(-) diff --git a/mysql-test/r/renamedb.result b/mysql-test/r/renamedb.result index ff8f89592fc..e77aca0d0b7 100644 --- a/mysql-test/r/renamedb.result +++ b/mysql-test/r/renamedb.result @@ -7,6 +7,6 @@ ERROR HY000: Incorrect usage of ALTER DATABASE UPGRADE DATA DIRECTORY NAME and n ALTER DATABASE `#mysql51#not-yet` UPGRADE DATA DIRECTORY NAME; ERROR HY000: Incorrect usage of ALTER DATABASE UPGRADE DATA DIRECTORY NAME and name ALTER DATABASE `#mysql50#` UPGRADE DATA DIRECTORY NAME; -ERROR HY000: Incorrect usage of ALTER DATABASE UPGRADE DATA DIRECTORY NAME and name +ERROR 42000: Incorrect database name '#mysql50#' ALTER DATABASE `#mysql50#upgrade-me` UPGRADE DATA DIRECTORY NAME; ERROR 42000: Unknown database '#mysql50#upgrade-me' diff --git a/mysql-test/r/upgrade.result b/mysql-test/r/upgrade.result index 034242079b1..da2f55b5bb1 100644 --- a/mysql-test/r/upgrade.result +++ b/mysql-test/r/upgrade.result @@ -112,3 +112,31 @@ select * from `a-b-c`.v1; f1 drop database `a-b-c`; use test; +# End of 5.0 tests +# +# Bug #53804: serious flaws in the alter database .. upgrade data +# directory name command +# +ALTER DATABASE `#mysql50#:` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Unknown database '#mysql50#:' +ALTER DATABASE `#mysql50#.` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Incorrect database name '#mysql50#.' +ALTER DATABASE `#mysql50#../` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Incorrect database name '#mysql50#../' +ALTER DATABASE `#mysql50#../..` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Incorrect database name '#mysql50#../..' +ALTER DATABASE `#mysql50#../../` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Incorrect database name '#mysql50#../../' +ALTER DATABASE `#mysql50#./blablabla` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Incorrect database name '#mysql50#./blablabla' +ALTER DATABASE `#mysql50#../blablabla` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Incorrect database name '#mysql50#../blablabla' +ALTER DATABASE `#mysql50#/` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Incorrect database name '#mysql50#/' +ALTER DATABASE `#mysql50#/.` UPGRADE DATA DIRECTORY NAME; +ERROR 42000: Incorrect database name '#mysql50#/.' +USE `#mysql50#.`; +ERROR 42000: Incorrect database name '#mysql50#.' +USE `#mysql50#../blablabla`; +ERROR 42000: Incorrect database name '#mysql50#../blablabla' +# End of 5.1 tests diff --git a/mysql-test/t/renamedb.test b/mysql-test/t/renamedb.test index 84315090b7a..71d0c127058 100644 --- a/mysql-test/t/renamedb.test +++ b/mysql-test/t/renamedb.test @@ -44,7 +44,7 @@ ALTER DATABASE `#mysql41#not-supported` UPGRADE DATA DIRECTORY NAME; --error ER_WRONG_USAGE ALTER DATABASE `#mysql51#not-yet` UPGRADE DATA DIRECTORY NAME; ---error ER_WRONG_USAGE +--error ER_WRONG_DB_NAME ALTER DATABASE `#mysql50#` UPGRADE DATA DIRECTORY NAME; --error ER_BAD_DB_ERROR diff --git a/mysql-test/t/upgrade.test b/mysql-test/t/upgrade.test index e390e8a1253..a7b9a1531ff 100644 --- a/mysql-test/t/upgrade.test +++ b/mysql-test/t/upgrade.test @@ -137,3 +137,37 @@ select * from `a-b-c`.v1; --enable_ps_protocol drop database `a-b-c`; use test; + +--echo # End of 5.0 tests + +--echo # +--echo # Bug #53804: serious flaws in the alter database .. upgrade data +--echo # directory name command +--echo # + +--error ER_BAD_DB_ERROR +ALTER DATABASE `#mysql50#:` UPGRADE DATA DIRECTORY NAME; +--error ER_WRONG_DB_NAME +ALTER DATABASE `#mysql50#.` UPGRADE DATA DIRECTORY NAME; +--error ER_WRONG_DB_NAME +ALTER DATABASE `#mysql50#../` UPGRADE DATA DIRECTORY NAME; +--error ER_WRONG_DB_NAME +ALTER DATABASE `#mysql50#../..` UPGRADE DATA DIRECTORY NAME; +--error ER_WRONG_DB_NAME +ALTER DATABASE `#mysql50#../../` UPGRADE DATA DIRECTORY NAME; +--error ER_WRONG_DB_NAME +ALTER DATABASE `#mysql50#./blablabla` UPGRADE DATA DIRECTORY NAME; +--error ER_WRONG_DB_NAME +ALTER DATABASE `#mysql50#../blablabla` UPGRADE DATA DIRECTORY NAME; +--error ER_WRONG_DB_NAME +ALTER DATABASE `#mysql50#/` UPGRADE DATA DIRECTORY NAME; +--error ER_WRONG_DB_NAME +ALTER DATABASE `#mysql50#/.` UPGRADE DATA DIRECTORY NAME; + +--error ER_WRONG_DB_NAME +USE `#mysql50#.`; +--error ER_WRONG_DB_NAME +USE `#mysql50#../blablabla`; + +--echo # End of 5.1 tests + diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 57875089cd8..f61565fe6e7 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -2293,6 +2293,7 @@ uint explain_filename(THD* thd, const char *from, char *to, uint to_length, uint filename_to_tablename(const char *from, char *to, uint to_length); uint tablename_to_filename(const char *from, char *to, uint to_length); uint check_n_cut_mysql50_prefix(const char *from, char *to, uint to_length); +bool check_mysql50_prefix(const char *name); #endif /* MYSQL_SERVER || INNODB_COMPATIBILITY_HOOKS */ #ifdef MYSQL_SERVER uint build_table_filename(char *buff, size_t bufflen, const char *db, diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 2a2daacf724..9727ec43084 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -391,6 +391,25 @@ uint filename_to_tablename(const char *from, char *to, uint to_length) } +/** + Check if given string begins with "#mysql50#" prefix + + @param name string to check cut + + @retval + FALSE no prefix found + @retval + TRUE prefix found +*/ + +bool check_mysql50_prefix(const char *name) +{ + return (name[0] == '#' && + !strncmp(name, MYSQL50_TABLE_NAME_PREFIX, + MYSQL50_TABLE_NAME_PREFIX_LENGTH)); +} + + /** Check if given string begins with "#mysql50#" prefix, cut it if so. @@ -406,9 +425,7 @@ uint filename_to_tablename(const char *from, char *to, uint to_length) uint check_n_cut_mysql50_prefix(const char *from, char *to, uint to_length) { - if (from[0] == '#' && - !strncmp(from, MYSQL50_TABLE_NAME_PREFIX, - MYSQL50_TABLE_NAME_PREFIX_LENGTH)) + if (check_mysql50_prefix(from)) return (uint) (strmake(to, from + MYSQL50_TABLE_NAME_PREFIX_LENGTH, to_length - 1) - to); return 0; diff --git a/sql/table.cc b/sql/table.cc index bd6251b5743..8b97dd36170 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -2689,44 +2689,30 @@ bool check_db_name(LEX_STRING *org_name) { char *name= org_name->str; uint name_length= org_name->length; + bool check_for_path_chars; if (!name_length || name_length > NAME_LEN) return 1; + if ((check_for_path_chars= check_mysql50_prefix(name))) + { + name+= MYSQL50_TABLE_NAME_PREFIX_LENGTH; + name_length-= MYSQL50_TABLE_NAME_PREFIX_LENGTH; + } + if (lower_case_table_names && name != any_db) my_casedn_str(files_charset_info, name); -#if defined(USE_MB) && defined(USE_MB_IDENT) - if (use_mb(system_charset_info)) - { - name_length= 0; - bool last_char_is_space= TRUE; - char *end= name + org_name->length; - while (name < end) - { - int len; - last_char_is_space= my_isspace(system_charset_info, *name); - len= my_ismbchar(system_charset_info, name, end); - if (!len) - len= 1; - name+= len; - name_length++; - } - return (last_char_is_space || name_length > NAME_CHAR_LEN); - } - else -#endif - return ((org_name->str[org_name->length - 1] != ' ') || - (name_length > NAME_CHAR_LEN)); /* purecov: inspected */ + return check_table_name(name, name_length, check_for_path_chars); } + /* Allow anything as a table name, as long as it doesn't contain an ' ' at the end returns 1 on error */ - bool check_table_name(const char *name, uint length, bool check_for_path_chars) { uint name_length= 0; // name length in symbols @@ -2754,10 +2740,10 @@ bool check_table_name(const char *name, uint length, bool check_for_path_chars) continue; } } +#endif if (check_for_path_chars && (*name == '/' || *name == '\\' || *name == '~' || *name == FN_EXTCHAR)) return 1; -#endif name++; name_length++; }