summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergei Golubchik <serg@mariadb.org>2018-04-04 15:34:40 +0200
committerSergei Golubchik <serg@mariadb.org>2018-05-17 15:13:47 +0200
commit28dbdf3d79cfd39ffa2e1d087662ac82c9281d1d (patch)
tree7b93934b2e32c08e870351783f8933c62881cefa
parente17e7985999b10c602ba72258e9a4b6e5fb91ed5 (diff)
downloadmariadb-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.result72
-rw-r--r--mysql-test/main/multi_update.test46
-rw-r--r--sql/item.cc5
-rw-r--r--sql/item.h3
-rw-r--r--sql/item_func.h2
-rw-r--r--sql/item_strfunc.h1
-rw-r--r--sql/sql_class.h7
-rw-r--r--sql/sql_insert.cc2
-rw-r--r--sql/sql_select.cc27
-rw-r--r--sql/sql_select.h5
-rw-r--r--sql/sql_union.cc4
-rw-r--r--sql/sql_update.cc56
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;