diff --git a/mysql-test/r/myisam.result b/mysql-test/r/myisam.result index 2ea317754ec..3cc37701e86 100644 --- a/mysql-test/r/myisam.result +++ b/mysql-test/r/myisam.result @@ -1621,3 +1621,19 @@ select * from t1; a 42 drop table t1; +CREATE TABLE t1(a VARCHAR(16)); +INSERT INTO t1 VALUES('aaaaaaaa'),(NULL); +UPDATE t1 AS ta1, t1 AS ta2 SET ta1.a='aaaaaaaaaaaaaaaa'; +SELECT * FROM t1; +a +aaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaa +DROP TABLE t1; +CREATE TABLE t1(a INT); +INSERT INTO t1 VALUES(1),(2); +UPDATE t1,t1 AS t2 SET t1.a=t1.a+2 WHERE t1.a=t2.a-1; +SELECT * FROM t1 ORDER BY a; +a +2 +3 +DROP TABLE t1; diff --git a/mysql-test/t/myisam.test b/mysql-test/t/myisam.test index d785002abdd..34267c3754a 100644 --- a/mysql-test/t/myisam.test +++ b/mysql-test/t/myisam.test @@ -1004,4 +1004,23 @@ connection default; select * from t1; drop table t1; +# +# BUG#21310 - Trees in SQL causing a "crashed" table with MyISAM storage +# engine +# + +# A simplified test case that reflect crashed table issue. +CREATE TABLE t1(a VARCHAR(16)); +INSERT INTO t1 VALUES('aaaaaaaa'),(NULL); +UPDATE t1 AS ta1, t1 AS ta2 SET ta1.a='aaaaaaaaaaaaaaaa'; +SELECT * FROM t1; +DROP TABLE t1; + +# A test case that reflect wrong result set. +CREATE TABLE t1(a INT); +INSERT INTO t1 VALUES(1),(2); +UPDATE t1,t1 AS t2 SET t1.a=t1.a+2 WHERE t1.a=t2.a-1; +SELECT * FROM t1 ORDER BY a; +DROP TABLE t1; + # End of 4.1 tests diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 3b6aa5f1aa2..5b7b7f594da 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -25,8 +25,6 @@ #include "sp_head.h" #include "sql_trigger.h" -static bool safe_update_on_fly(JOIN_TAB *join_tab, List *fields); - /* Return 0 if row hasn't changed */ static bool compare_record(TABLE *table, query_id_t query_id) @@ -1041,30 +1039,75 @@ int multi_update::prepare(List ¬_used_values, for (i=0 ; i < table_count ; i++) set_if_bigger(max_fields, fields_for_table[i]->elements); copy_field= new Copy_field[max_fields]; - - /* - Mark all copies of tables that are updates to ensure that - init_read_record() will not try to enable a cache on them - - The problem is that for queries like - - UPDATE t1, t1 AS t2 SET t1.b=t2.c WHERE t1.a=t2.a; - - the row buffer may contain things that doesn't match what is on disk - which will cause an error when reading a row. - (This issue is mostly relevent for MyISAM tables) - */ - for (table_ref= leaves; table_ref; table_ref= table_ref->next_leaf) - { - TABLE *table=table_ref->table; - if ((tables_to_update & table->map) && - unique_table(thd, table_ref, update_tables)) - table->no_cache= 1; // Disable row cache - } DBUG_RETURN(thd->is_fatal_error != 0); } +/* + Check if table is safe to update on fly + + SYNOPSIS + safe_update_on_fly() + thd Thread handler + join_tab How table is used in join + all_tables List of tables + fields Fields that are updated + + NOTES + We can update the first table in join on the fly if we know that + a row in this table will never be read twice. This is true under + the following conditions: + + - We are doing a table scan and the data is in a separate file (MyISAM) or + if we don't update a clustered key. + + - We are doing a range scan and we don't update the scan key or + the primary key for a clustered table handler. + + - Table is not joined to itself. + + When checking for above cases we also should take into account that + BEFORE UPDATE trigger potentially may change value of any field in row + being updated. + + WARNING + This code is a bit dependent of how make_join_readinfo() works. + + RETURN + 0 Not safe to update + 1 Safe to update +*/ + +static bool safe_update_on_fly(THD *thd, JOIN_TAB *join_tab, + TABLE_LIST *all_tables, List *fields) +{ + TABLE *table= join_tab->table; + if (unique_table(thd, table, all_tables)) + return 0; + switch (join_tab->type) { + case JT_SYSTEM: + case JT_CONST: + case JT_EQ_REF: + return TRUE; // At most one matching row + case JT_REF: + case JT_REF_OR_NULL: + return !is_key_used(table, join_tab->ref.key, *fields); + case JT_ALL: + /* If range search on index */ + if (join_tab->quick) + return !join_tab->quick->is_keys_used(fields); + /* If scanning in clustered key */ + if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) && + table->s->primary_key < MAX_KEY) + return !is_key_used(table, table->s->primary_key, *fields); + return TRUE; + default: + break; // Avoid compler warning + } + return FALSE; +} + + /* Initialize table for multi table @@ -1099,7 +1142,7 @@ multi_update::initialize_tables(JOIN *join) table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); if (table == main_table) // First table in join { - if (safe_update_on_fly(join->join_tab, &temp_fields)) + if (safe_update_on_fly(thd, join->join_tab, all_tables, &temp_fields)) { table_to_update= main_table; // Update table on the fly continue; @@ -1150,63 +1193,6 @@ multi_update::initialize_tables(JOIN *join) DBUG_RETURN(0); } -/* - Check if table is safe to update on fly - - SYNOPSIS - safe_update_on_fly - join_tab How table is used in join - fields Fields that are updated - - NOTES - We can update the first table in join on the fly if we know that - a row in this table will never be read twice. This is true under - the following conditions: - - - We are doing a table scan and the data is in a separate file (MyISAM) or - if we don't update a clustered key. - - - We are doing a range scan and we don't update the scan key or - the primary key for a clustered table handler. - - When checking for above cases we also should take into account that - BEFORE UPDATE trigger potentially may change value of any field in row - being updated. - - WARNING - This code is a bit dependent of how make_join_readinfo() works. - - RETURN - 0 Not safe to update - 1 Safe to update -*/ - -static bool safe_update_on_fly(JOIN_TAB *join_tab, List *fields) -{ - TABLE *table= join_tab->table; - switch (join_tab->type) { - case JT_SYSTEM: - case JT_CONST: - case JT_EQ_REF: - return TRUE; // At most one matching row - case JT_REF: - case JT_REF_OR_NULL: - return !is_key_used(table, join_tab->ref.key, *fields); - case JT_ALL: - /* If range search on index */ - if (join_tab->quick) - return !join_tab->quick->is_keys_used(fields); - /* If scanning in clustered key */ - if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) && - table->s->primary_key < MAX_KEY) - return !is_key_used(table, table->s->primary_key, *fields); - return TRUE; - default: - break; // Avoid compler warning - } - return FALSE; -} - multi_update::~multi_update() {