diff options
author | Sergei Golubchik <serg@mariadb.org> | 2018-04-04 15:34:40 +0200 |
---|---|---|
committer | Sergei Golubchik <serg@mariadb.org> | 2018-05-17 15:13:47 +0200 |
commit | 28dbdf3d79cfd39ffa2e1d087662ac82c9281d1d (patch) | |
tree | 7b93934b2e32c08e870351783f8933c62881cefa | |
parent | e17e7985999b10c602ba72258e9a4b6e5fb91ed5 (diff) | |
download | mariadb-git-28dbdf3d79cfd39ffa2e1d087662ac82c9281d1d.tar.gz |
MDEV-14551 Can't find record in table on multi-table update with ORDER BY
preserve positions if the multi-update join is using tmp table:
* store positions in the tmp table if needed
JOIN::add_fields_for_current_rowid()
* take positions from the tmp table, not from file->position():
multi_update::prepare2()
-rw-r--r-- | mysql-test/main/multi_update.result | 72 | ||||
-rw-r--r-- | mysql-test/main/multi_update.test | 46 | ||||
-rw-r--r-- | sql/item.cc | 5 | ||||
-rw-r--r-- | sql/item.h | 3 | ||||
-rw-r--r-- | sql/item_func.h | 2 | ||||
-rw-r--r-- | sql/item_strfunc.h | 1 | ||||
-rw-r--r-- | sql/sql_class.h | 7 | ||||
-rw-r--r-- | sql/sql_insert.cc | 2 | ||||
-rw-r--r-- | sql/sql_select.cc | 27 | ||||
-rw-r--r-- | sql/sql_select.h | 5 | ||||
-rw-r--r-- | sql/sql_union.cc | 4 | ||||
-rw-r--r-- | sql/sql_update.cc | 56 |
12 files changed, 218 insertions, 12 deletions
diff --git a/mysql-test/main/multi_update.result b/mysql-test/main/multi_update.result index 45239f6e090..c40de47668a 100644 --- a/mysql-test/main/multi_update.result +++ b/mysql-test/main/multi_update.result @@ -968,3 +968,75 @@ NULL 6 7 7 8 8 drop table t1, t2; +create table t1 (i int) engine=memory; +insert t1 values (1),(2); +create table t2 (f int) engine=myisam; +insert t2 values (1),(2); +explain update t1, t2 set f = 126 order by f limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 +update t1, t2 set f = 126 order by f limit 2; +select * from t2; +f +126 +2 +drop table t1, t2; +create table t0(a int); +insert t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (a int, b int, c int, key(a)); +insert t1 select a,a,a from t0; +create table t2 as select * from t1; +create table t3 as select * from t1; +select * from t1, t2 where t1.a=t2.a and t1.b in (select b from t3 where t3.c<=t2.c) order by t2.c, t1.c limit 5; +a b c a b c +0 0 0 0 0 0 +1 1 1 1 1 1 +2 2 2 2 2 2 +3 3 3 3 3 3 +4 4 4 4 4 4 +set optimizer_switch='firstmatch=off'; +explain update t1, t2 set t2.c=1 where t1.a=t2.a and t1.b in (select b from t3 where t3.c< t2.c) order by t2.c, t1.c limit 10; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 10 Using temporary; Using filesort +1 PRIMARY t1 ALL a NULL NULL NULL 10 Using where +1 PRIMARY t3 ALL NULL NULL NULL NULL 10 Using where; Start temporary; End temporary +update t1, t2 set t2.c=1 where t1.a=t2.a and t1.b in (select b from t3 where t3.c<=t2.c) order by t2.c, t1.c limit 5; +select * from t2; +a b c +0 0 1 +1 1 1 +2 2 1 +3 3 1 +4 4 1 +5 5 5 +6 6 6 +7 7 7 +8 8 8 +9 9 9 +set optimizer_switch=default; +drop table t0,t1,t2,t3; +create table t0 (x int); +create table t1 (a int); +create table t2 (b int, c int default 0); +insert t0 (x) values (0),(10); +insert t1 (a) values (1), (2); +insert t2 (b) values (1), (2); +create view v1 as select t2.b,t2.c from t1, t2 +where t1.a=t2.b and t2.b < 3 with check option; +select * from t0 join v1 on (x=c); +x b c +0 1 0 +0 2 0 +explain update v1,t0 set c=1 where b=1 and x=c order by x,b limit 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using where; Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 Using where +1 SIMPLE t0 ALL NULL NULL NULL NULL 2 Using where +update v1,t0 set c=1 where b<3 and x=c order by x,b limit 1; +select * from v1; +b c +1 1 +2 0 +drop view v1; +drop table t0, t1,t2; diff --git a/mysql-test/main/multi_update.test b/mysql-test/main/multi_update.test index 5feebe87a5a..42e34d1e4a1 100644 --- a/mysql-test/main/multi_update.test +++ b/mysql-test/main/multi_update.test @@ -914,3 +914,49 @@ update t1 set c1=NULL; update t1, t2 set t1.c1=t2.c3 where t1.c3=t2.c3 order by t1.c3 desc limit 2; select * from t1; drop table t1, t2; + +# +# MDEV-14551 Can't find record in table on multi-table update with ORDER BY +# + +# simple test with multi-update and Using temporary: +create table t1 (i int) engine=memory; +insert t1 values (1),(2); +create table t2 (f int) engine=myisam; +insert t2 values (1),(2); +explain update t1, t2 set f = 126 order by f limit 2; +update t1, t2 set f = 126 order by f limit 2; +select * from t2; +drop table t1, t2; + +# test with DuplicateElimination +# (so that keep_current_rowid is set for DuplicateElimination too) +create table t0(a int); +insert t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (a int, b int, c int, key(a)); +insert t1 select a,a,a from t0; +create table t2 as select * from t1; +create table t3 as select * from t1; +select * from t1, t2 where t1.a=t2.a and t1.b in (select b from t3 where t3.c<=t2.c) order by t2.c, t1.c limit 5; +set optimizer_switch='firstmatch=off'; +explain update t1, t2 set t2.c=1 where t1.a=t2.a and t1.b in (select b from t3 where t3.c< t2.c) order by t2.c, t1.c limit 10; +update t1, t2 set t2.c=1 where t1.a=t2.a and t1.b in (select b from t3 where t3.c<=t2.c) order by t2.c, t1.c limit 5; +select * from t2; +set optimizer_switch=default; +drop table t0,t1,t2,t3; + +# test WITH CHECK OPTION +create table t0 (x int); +create table t1 (a int); +create table t2 (b int, c int default 0); +insert t0 (x) values (0),(10); +insert t1 (a) values (1), (2); +insert t2 (b) values (1), (2); +create view v1 as select t2.b,t2.c from t1, t2 + where t1.a=t2.b and t2.b < 3 with check option; +select * from t0 join v1 on (x=c); +explain update v1,t0 set c=1 where b=1 and x=c order by x,b limit 1; +update v1,t0 set c=1 where b<3 and x=c order by x,b limit 1; +select * from v1; +drop view v1; +drop table t0, t1,t2; diff --git a/sql/item.cc b/sql/item.cc index bfd5ed78bf8..dcdd353162a 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -6797,6 +6797,11 @@ fast_field_copier Item_field::setup_fast_field_copier(Field *to) return to->get_fast_field_copier(field); } +void Item_field::save_in_result_field(bool no_conversions) +{ + bool unused; + save_field_in_field(field, &unused, result_field, no_conversions); +} /** Set a field's value from a item. diff --git a/sql/item.h b/sql/item.h index bd5c85e6140..7f2569d54d0 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2997,8 +2997,7 @@ public: cond_equal_ref); } bool is_result_field() { return false; } - void set_result_field(Field *field_arg) {} - void save_in_result_field(bool no_conversions) { } + void save_in_result_field(bool no_conversions); Item *get_tmp_table_item(THD *thd); bool collect_item_field_processor(void * arg); bool add_field_to_set_processor(void * arg); diff --git a/sql/item_func.h b/sql/item_func.h index 376f8fc16a1..d6bb0f18700 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -70,7 +70,7 @@ public: SP_CONTAINS_FUNC,SP_OVERLAPS_FUNC, SP_STARTPOINT,SP_ENDPOINT,SP_EXTERIORRING, SP_POINTN,SP_GEOMETRYN,SP_INTERIORRINGN, SP_RELATE_FUNC, - NOT_FUNC, NOT_ALL_FUNC, + NOT_FUNC, NOT_ALL_FUNC, TEMPTABLE_ROWID, NOW_FUNC, NOW_UTC_FUNC, SYSDATE_FUNC, TRIG_COND_FUNC, SUSERVAR_FUNC, GUSERVAR_FUNC, COLLATE_FUNC, EXTRACT_FUNC, CHAR_TYPECAST_FUNC, FUNC_SP, UDF_FUNC, diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 1132a947587..eb084c3f58d 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -1765,6 +1765,7 @@ public: Field *create_tmp_field(bool group, TABLE *table) { return create_table_field_from_handler(table); } String *val_str(String *str); + enum Functype functype() const { return TEMPTABLE_ROWID; } const char *func_name() const { return "<rowid>"; } void fix_length_and_dec(); Item *get_copy(THD *thd) diff --git a/sql/sql_class.h b/sql/sql_class.h index bba1740b46f..0ff588a6d38 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -4981,7 +4981,7 @@ public: unit= u; return 0; } - virtual int prepare2(void) { return 0; } + virtual int prepare2(JOIN *join) { return 0; } /* Because of peculiarities of prepared statements protocol we need to know number of columns in the result set (if @@ -5225,7 +5225,7 @@ class select_insert :public select_result_interceptor { enum_duplicates duplic, bool ignore); ~select_insert(); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); - virtual int prepare2(void); + virtual int prepare2(JOIN *join); virtual int send_data(List<Item> &items); virtual void store_values(List<Item> &values); virtual bool can_rollback_data() { return 0; } @@ -5277,7 +5277,7 @@ public: // Needed for access from local class MY_HOOKS in prepare(), since thd is proteted. const THD *get_thd(void) { return thd; } const HA_CREATE_INFO *get_create_info() { return create_info; }; - int prepare2(void) { return 0; } + int prepare2(JOIN *join) { return 0; } private: TABLE *create_table_from_items(THD *thd, @@ -5961,6 +5961,7 @@ public: int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int send_data(List<Item> &items); bool initialize_tables (JOIN *join); + int prepare2(JOIN *join); int do_updates(); bool send_eof(); inline ha_rows num_found() const { return found; } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index d36d9aa82d2..9cb6a88fd4d 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3786,7 +3786,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) 0 OK */ -int select_insert::prepare2(void) +int select_insert::prepare2(JOIN *) { DBUG_ENTER("select_insert::prepare2"); if (thd->lex->current_select->options & OPTION_BUFFER_RESULT && diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f8d3fb778e3..91d01a31181 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2671,6 +2671,25 @@ bool JOIN::add_having_as_table_cond(JOIN_TAB *tab) } +bool JOIN::add_fields_for_current_rowid(JOIN_TAB *cur, List<Item> *table_fields) +{ + /* + this will not walk into semi-join materialization nests but this is ok + because we will never need to save current rowids for those. + */ + for (JOIN_TAB *tab=join_tab; tab < cur; tab++) + { + if (!tab->keep_current_rowid) + continue; + Item *item= new (thd->mem_root) Item_temptable_rowid(tab->table); + item->fix_fields(thd, 0); + table_fields->push_back(item, thd->mem_root); + cur->tmp_table_param->func_count++; + } + return 0; +} + + /** Set info for aggregation tables @@ -3278,6 +3297,8 @@ JOIN::create_postjoin_aggr_table(JOIN_TAB *tab, List<Item> *table_fields, if (!(tab->tmp_table_param= new TMP_TABLE_PARAM(tmp_table_param))) DBUG_RETURN(true); + if (tmp_table_keep_current_rowid) + add_fields_for_current_rowid(tab, table_fields); tab->tmp_table_param->skip_create_table= true; TABLE* table= create_tmp_table(thd, tab->tmp_table_param, *table_fields, table_group, distinct, @@ -3672,7 +3693,7 @@ bool JOIN::prepare_result(List<Item> **columns_list) select_lex->handle_derived(thd->lex, DT_CREATE)) goto err; - if (result->prepare2()) + if (result->prepare2(this)) goto err; if ((select_lex->options & OPTION_SCHEMA_TABLE) && @@ -3809,7 +3830,7 @@ void JOIN::exec_inner() } columns_list= &procedure_fields_list; } - if (result->prepare2()) + if (result->prepare2(this)) DBUG_VOID_RETURN; if (!tables_list && (table_count || !select_lex->with_sum_func) && @@ -26225,7 +26246,7 @@ bool JOIN::change_result(select_result *new_result, select_result *old_result) { result= new_result; if (result->prepare(fields_list, select_lex->master_unit()) || - result->prepare2()) + result->prepare2(this)) DBUG_RETURN(true); /* purecov: inspected */ DBUG_RETURN(false); } diff --git a/sql/sql_select.h b/sql/sql_select.h index 20c2370d650..2cc47f6ec3b 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1438,6 +1438,9 @@ public: enum { QEP_NOT_PRESENT_YET, QEP_AVAILABLE, QEP_DELETED} have_query_plan; + // if keep_current_rowid=true, whether they should be saved in temporary table + bool tmp_table_keep_current_rowid; + /* Additional WHERE and HAVING predicates to be considered for IN=>EXISTS subquery transformation of a JOIN object. @@ -1543,6 +1546,7 @@ public: pushdown_query= 0; original_join_tab= 0; explain= NULL; + tmp_table_keep_current_rowid= 0; all_fields= fields_arg; if (&fields_list != &fields_arg) /* Avoid valgrind-warning */ @@ -1776,6 +1780,7 @@ private: void cleanup_item_list(List<Item> &items) const; bool add_having_as_table_cond(JOIN_TAB *tab); bool make_aggr_tables_info(); + bool add_fields_for_current_rowid(JOIN_TAB *cur, List<Item> *fields); }; enum enum_with_bush_roots { WITH_BUSH_ROOTS, WITHOUT_BUSH_ROOTS}; diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 346b21c0a53..8450a98ce3c 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -499,14 +499,14 @@ void select_union_recursive::cleanup() bool select_union_direct::change_result(select_result *new_result) { result= new_result; - return (result->prepare(unit->types, unit) || result->prepare2()); + return (result->prepare(unit->types, unit) || result->prepare2(NULL)); } bool select_union_direct::postponed_prepare(List<Item> &types) { if (result != NULL) - return (result->prepare(types, unit) || result->prepare2()); + return (result->prepare(types, unit) || result->prepare2(NULL)); else return false; } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 829bfa546ce..a8b86448d5e 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -2198,10 +2198,66 @@ loop_end: DBUG_RETURN(1); tmp_tables[cnt]->file->extra(HA_EXTRA_WRITE_CACHE); } + join->tmp_table_keep_current_rowid= TRUE; DBUG_RETURN(0); } +static TABLE *item_rowid_table(Item *item) +{ + if (item->type() != Item::FUNC_ITEM) + return NULL; + Item_func *func= (Item_func *)item; + if (func->functype() != Item_func::TEMPTABLE_ROWID) + return NULL; + Item_temptable_rowid *itr= (Item_temptable_rowid *)func; + return itr->table; +} + + +/* + multi_update stores a rowid and new field values for every updated row in a + temporary table (one temporary table per updated table). These rowids are + obtained via Item_temptable_rowid's by calling handler::position(). But if + the join is resolved via a temp table, rowids cannot be obtained from + handler::position() in the multi_update::send_data(). So, they're stored in + the join's temp table (JOIN::add_fields_for_current_rowid()) and here we + replace Item_temptable_rowid's (that would've done handler::position()) with + Item_field's (that will simply take the corresponding field value from the + temp table). +*/ +int multi_update::prepare2(JOIN *join) +{ + if (!join->need_tmp || !join->tmp_table_keep_current_rowid) + return 0; + + // there cannot be many tmp tables in multi-update + JOIN_TAB *tmptab= join->join_tab + join->exec_join_tab_cnt(); + + for (Item **it= tmptab->tmp_table_param->items_to_copy; *it ; it++) + { + TABLE *tbl= item_rowid_table(*it); + if (!tbl) + continue; + for (uint i= 0; i < table_count; i++) + { + for (Item **it2= tmp_table_param[i].items_to_copy; *it2; it2++) + { + if (item_rowid_table(*it2) != tbl) + continue; + Item *fld= new (thd->mem_root) + Item_field(thd, (*it)->get_tmp_table_field()); + if (!fld) + return 1; + fld->set_result_field((*it2)->get_tmp_table_field()); + *it2= fld; + } + } + } + return 0; +} + + multi_update::~multi_update() { TABLE_LIST *table; |