MDEV-19848 Server crashes in check_vcol_forward_refs upon INSERT DELAYED into table with long blob key
Problem:- Insert delayed is not working with Long Unique Index. It is failing with 1. INSERT DELAYED INTO t1 VALUES(); 2. INSERT DELAYED INTO t1 VALUES(1); 3. Potential Race condition When Insert DELAYED gets dup key error(After fix), And it will change original table key_info by calling re_setup_keyinfo_hash, And second thread is in check_duplicate_long_entries 4. Insert delayed into INVISIBLE COLUMN will also not work.
There are 4 main issue
1. while calling make_new_field we forgot to & LONG_UNIQUE_HASH_FIELD flag into new field flags.
2. New field created created into get_local_table by make_new_field does not respect old field visibility, Assigning old field visibility will solve Problem 4 and part of problem 2.
3. As we know Problem 3 race condition is caused because table and delayed table share same key_info, So we will make a copy of original table key_info in get_local_table.
4. In parse_vcol_defs we have this code block keypart->field->vcol_info= table->field[keypart->field->field_index]->vcol_info; Which is wrong because we should not change original table->field->vcol_info with vcol_info which is create on delayed thread.
diff --git a/mysql-test/main/long_unique_bugs.result b/mysql-test/main/long_unique_bugs.result index c0ba4d0b87d..16e825f0905 100644 --- a/mysql-test/main/long_unique_bugs.result +++ b/mysql-test/main/long_unique_bugs.result @@ -270,3 +270,32 @@ ERROR 42000: Specified key was too long; max key length is 1000 bytes create table t1(a int, unique(a) using hash); #BULK insert > 100 rows (MI_MIN_ROWS_TO_DISABLE_INDEXES) drop table t1; +CREATE TABLE t1 (a blob, UNIQUE(a)) ENGINE=MyISAM; +INSERT DELAYED t1 () VALUES (1); +INSERT t1 () VALUES (2); +DROP TABLE t1; +CREATE TABLE t1 (a char(50), UNIQUE(a(10)) USING HASH); +INSERT DELAYED t1 () VALUES (1); +INSERT t1 () VALUES (2); +DROP TABLE t1; +CREATE TABLE t1 ( +a CHAR(128), +b CHAR(128) AS (a), +c varchar(5000), +UNIQUE(c,b(64)) +) ENGINE=myisam; +INSERT DELAYED t1 (a,c) VALUES (1,1); +INSERT t1 (a,c) VALUES (2,2); +INSERT t1 (a,c) VALUES (3,3); +drop table t1; +create table t1(a int , b int invisible); +insert into t1 values(1); +insert delayed into t1(a,b) values(2,2); +#Should not fails +insert delayed into t1 values(2); +select a,b from t1 order by a; +a b +1 NULL +2 2 +2 NULL +DROP TABLE t1; diff --git a/mysql-test/main/long_unique_bugs.test b/mysql-test/main/long_unique_bugs.test index 13a4e1367a0..365393aa2ea 100644 --- a/mysql-test/main/long_unique_bugs.test +++ b/mysql-test/main/long_unique_bugs.test @@ -340,3 +340,35 @@ while ($count) --eval $insert_stmt --enable_query_log drop table t1; +# +# MDEV-19848 Server crashes in check_vcol_forward_refs upon INSERT DELAYED into table with long blob key +# +CREATE TABLE t1 (a blob, UNIQUE(a)) ENGINE=MyISAM; +INSERT DELAYED t1 () VALUES (1); +INSERT t1 () VALUES (2); +# Cleanup +DROP TABLE t1; +CREATE TABLE t1 (a char(50), UNIQUE(a(10)) USING HASH); +INSERT DELAYED t1 () VALUES (1); +INSERT t1 () VALUES (2); +# Cleanup +DROP TABLE t1; +CREATE TABLE t1 ( + a CHAR(128), + b CHAR(128) AS (a), + c varchar(5000), + UNIQUE(c,b(64)) +) ENGINE=myisam; +INSERT DELAYED t1 (a,c) VALUES (1,1); +--sleep 1 +INSERT t1 (a,c) VALUES (2,2); +INSERT t1 (a,c) VALUES (3,3); +drop table t1; +create table t1(a int , b int invisible); +insert into t1 values(1); +insert delayed into t1(a,b) values(2,2); +--echo #Should not fails +insert delayed into t1 values(2); +select a,b from t1 order by a; +# Cleanup +DROP TABLE t1; diff --git a/sql/field.cc b/sql/field.cc index 0eb53f40a54..d1187237f23 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -2373,8 +2373,13 @@ Field *Field::make_new_field(MEM_ROOT *root, TABLE *new_table, tmp->flags&= (NOT_NULL_FLAG | BLOB_FLAG | UNSIGNED_FLAG | ZEROFILL_FLAG | BINARY_FLAG | ENUM_FLAG | SET_FLAG | VERS_SYS_START_FLAG | VERS_SYS_END_FLAG | - VERS_UPDATE_UNVERSIONED_FLAG); + VERS_UPDATE_UNVERSIONED_FLAG | LONG_UNIQUE_HASH_FIELD); tmp->reset_fields(); + /* + Calling make_new_field will return a VISIBLE field, If caller function + wants original visibility he should change it later. + This is done because view created on invisible fields are visible. + */ tmp->invisible= VISIBLE; return tmp; } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index fe6c5fa8ec4..d80044ec37d 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2607,6 +2607,11 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) { if (!(*field= (*org_field)->make_new_field(client_thd->mem_root, copy, 1))) goto error; + /* + We want same visibility as of original table because we are just creating + a clone for delayed insert. + */ + (*field)->invisible= (*org_field)->invisible; (*field)->unireg_check= (*org_field)->unireg_check; (*field)->orig_table= copy; // Remove connection (*field)->move_field_offset(adjust_ptrs); // Point at copy->record[0] @@ -2621,7 +2626,33 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) if (share->virtual_fields || share->default_expressions || share->default_fields) { + /* + If we have long unique table then delayed insert can modify key structure + (re/setup_keyinfo_hash_all) of original table when it gets insert error, + parse_vcol_defs will also modify key_info structure. So it is better to + clone the table->key_info for copy table. + We will not be cloning key_part_info or even changing any field ptr. + Because re/setup_keyinfo_hash_all only modify key_info array. So it will + be like having new key_info array for copy table with old key_part_info + ptr. + */ + if (share->long_unique_table) + { + KEY *key_info; + if (!(key_info= (KEY*) client_thd->alloc(share->keys*sizeof(KEY)))) + goto error; + copy->key_info= key_info; + memcpy(key_info, table->key_info, sizeof(KEY)*share->keys); + } + /* + parse_vcol_defs expects key_infos to be in user defined format. + */ + copy->setup_keyinfo_hash_all(); bool error_reported= FALSE; + /* + We won't be calling re_setup_keyinfo_hash because parse_vcol_defs changes + key_infos to storage engine format + */ if (unlikely(parse_vcol_defs(client_thd, client_thd->mem_root, copy, &error_reported, VCOL_INIT_DEPENDENCY_FAILURE_IS_WARNING))) diff --git a/sql/table.cc b/sql/table.cc index 718d0dce072..d0fcd449098 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -1223,7 +1223,16 @@ bool parse_vcol_defs(THD *thd, MEM_ROOT *mem_root, TABLE *table, new (mem_root) Item_field(thd, keypart->field), new (mem_root) Item_int(thd, length)); list_item->fix_fields(thd, NULL); - keypart->field->vcol_info= + /* + Do not change the vcol_info when vcol_info->expr is not NULL + This will happen in the case of Delayed_insert::get_local_table() + And if we change the vcol_info in Delayed insert , then original + table field->vcol_info will be created on delayed insert thread + mem_root. + */ + if (!keypart->field->vcol_info || + !keypart->field->vcol_info->expr) + keypart->field->vcol_info= table->field[keypart->field->field_index]->vcol_info; } else @@ -9084,6 +9093,26 @@ void re_setup_keyinfo_hash(KEY *key_info) key_info->ext_key_parts= 1; key_info->flags&= ~HA_NOSAME; } + +/* + call setup_keyinfo_hash for all keys in table + */ +void TABLE::setup_keyinfo_hash_all() +{ + for (uint i= 0; i < s->keys; i++) + if (key_info[i].algorithm == HA_KEY_ALG_LONG_HASH) + setup_keyinfo_hash(&key_info[i]); +} + +/* + call re_setup_keyinfo_hash for all keys in table + */ +void TABLE::re_setup_keyinfo_hash_all() +{ + for (uint i= 0; i < s->keys; i++) + if (key_info[i].algorithm == HA_KEY_ALG_LONG_HASH) + re_setup_keyinfo_hash(&key_info[i]); +} /** @brief clone of current handler. Creates a clone of handler used in update for diff --git a/sql/table.h b/sql/table.h index 1c721624d5d..76d11bbd604 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1612,6 +1612,8 @@ struct TABLE void vers_update_fields(); void vers_update_end(); void find_constraint_correlated_indexes(); + void setup_keyinfo_hash_all(); + void re_setup_keyinfo_hash_all(); void clone_handler_for_update(); void delete_update_handler();