1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-10 04:22:00 +03:00

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()
This commit is contained in:
Sergei Golubchik
2018-04-04 15:34:40 +02:00
parent e17e798599
commit 28dbdf3d79
12 changed files with 218 additions and 12 deletions

View File

@ -968,3 +968,75 @@ NULL 6
7 7 7 7
8 8 8 8
drop table t1, t2; 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;

View File

@ -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; update t1, t2 set t1.c1=t2.c3 where t1.c3=t2.c3 order by t1.c3 desc limit 2;
select * from t1; select * from t1;
drop table t1, t2; 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;

View File

@ -6797,6 +6797,11 @@ fast_field_copier Item_field::setup_fast_field_copier(Field *to)
return to->get_fast_field_copier(field); 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. Set a field's value from a item.

View File

@ -2997,8 +2997,7 @@ public:
cond_equal_ref); cond_equal_ref);
} }
bool is_result_field() { return false; } 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); Item *get_tmp_table_item(THD *thd);
bool collect_item_field_processor(void * arg); bool collect_item_field_processor(void * arg);
bool add_field_to_set_processor(void * arg); bool add_field_to_set_processor(void * arg);

View File

@ -70,7 +70,7 @@ public:
SP_CONTAINS_FUNC,SP_OVERLAPS_FUNC, SP_CONTAINS_FUNC,SP_OVERLAPS_FUNC,
SP_STARTPOINT,SP_ENDPOINT,SP_EXTERIORRING, SP_STARTPOINT,SP_ENDPOINT,SP_EXTERIORRING,
SP_POINTN,SP_GEOMETRYN,SP_INTERIORRINGN, SP_RELATE_FUNC, 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, NOW_FUNC, NOW_UTC_FUNC, SYSDATE_FUNC, TRIG_COND_FUNC,
SUSERVAR_FUNC, GUSERVAR_FUNC, COLLATE_FUNC, SUSERVAR_FUNC, GUSERVAR_FUNC, COLLATE_FUNC,
EXTRACT_FUNC, CHAR_TYPECAST_FUNC, FUNC_SP, UDF_FUNC, EXTRACT_FUNC, CHAR_TYPECAST_FUNC, FUNC_SP, UDF_FUNC,

View File

@ -1765,6 +1765,7 @@ public:
Field *create_tmp_field(bool group, TABLE *table) Field *create_tmp_field(bool group, TABLE *table)
{ return create_table_field_from_handler(table); } { return create_table_field_from_handler(table); }
String *val_str(String *str); String *val_str(String *str);
enum Functype functype() const { return TEMPTABLE_ROWID; }
const char *func_name() const { return "<rowid>"; } const char *func_name() const { return "<rowid>"; }
void fix_length_and_dec(); void fix_length_and_dec();
Item *get_copy(THD *thd) Item *get_copy(THD *thd)

View File

@ -4981,7 +4981,7 @@ public:
unit= u; unit= u;
return 0; return 0;
} }
virtual int prepare2(void) { return 0; } virtual int prepare2(JOIN *join) { return 0; }
/* /*
Because of peculiarities of prepared statements protocol Because of peculiarities of prepared statements protocol
we need to know number of columns in the result set (if 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); enum_duplicates duplic, bool ignore);
~select_insert(); ~select_insert();
int prepare(List<Item> &list, SELECT_LEX_UNIT *u); 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 int send_data(List<Item> &items);
virtual void store_values(List<Item> &values); virtual void store_values(List<Item> &values);
virtual bool can_rollback_data() { return 0; } 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. // Needed for access from local class MY_HOOKS in prepare(), since thd is proteted.
const THD *get_thd(void) { return thd; } const THD *get_thd(void) { return thd; }
const HA_CREATE_INFO *get_create_info() { return create_info; }; const HA_CREATE_INFO *get_create_info() { return create_info; };
int prepare2(void) { return 0; } int prepare2(JOIN *join) { return 0; }
private: private:
TABLE *create_table_from_items(THD *thd, TABLE *create_table_from_items(THD *thd,
@ -5961,6 +5961,7 @@ public:
int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
int send_data(List<Item> &items); int send_data(List<Item> &items);
bool initialize_tables (JOIN *join); bool initialize_tables (JOIN *join);
int prepare2(JOIN *join);
int do_updates(); int do_updates();
bool send_eof(); bool send_eof();
inline ha_rows num_found() const { return found; } inline ha_rows num_found() const { return found; }

View File

@ -3786,7 +3786,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
0 OK 0 OK
*/ */
int select_insert::prepare2(void) int select_insert::prepare2(JOIN *)
{ {
DBUG_ENTER("select_insert::prepare2"); DBUG_ENTER("select_insert::prepare2");
if (thd->lex->current_select->options & OPTION_BUFFER_RESULT && if (thd->lex->current_select->options & OPTION_BUFFER_RESULT &&

View File

@ -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 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))) if (!(tab->tmp_table_param= new TMP_TABLE_PARAM(tmp_table_param)))
DBUG_RETURN(true); 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; tab->tmp_table_param->skip_create_table= true;
TABLE* table= create_tmp_table(thd, tab->tmp_table_param, *table_fields, TABLE* table= create_tmp_table(thd, tab->tmp_table_param, *table_fields,
table_group, distinct, table_group, distinct,
@ -3672,7 +3693,7 @@ bool JOIN::prepare_result(List<Item> **columns_list)
select_lex->handle_derived(thd->lex, DT_CREATE)) select_lex->handle_derived(thd->lex, DT_CREATE))
goto err; goto err;
if (result->prepare2()) if (result->prepare2(this))
goto err; goto err;
if ((select_lex->options & OPTION_SCHEMA_TABLE) && if ((select_lex->options & OPTION_SCHEMA_TABLE) &&
@ -3809,7 +3830,7 @@ void JOIN::exec_inner()
} }
columns_list= &procedure_fields_list; columns_list= &procedure_fields_list;
} }
if (result->prepare2()) if (result->prepare2(this))
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
if (!tables_list && (table_count || !select_lex->with_sum_func) && 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; result= new_result;
if (result->prepare(fields_list, select_lex->master_unit()) || if (result->prepare(fields_list, select_lex->master_unit()) ||
result->prepare2()) result->prepare2(this))
DBUG_RETURN(true); /* purecov: inspected */ DBUG_RETURN(true); /* purecov: inspected */
DBUG_RETURN(false); DBUG_RETURN(false);
} }

View File

@ -1438,6 +1438,9 @@ public:
enum { QEP_NOT_PRESENT_YET, QEP_AVAILABLE, QEP_DELETED} have_query_plan; 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 Additional WHERE and HAVING predicates to be considered for IN=>EXISTS
subquery transformation of a JOIN object. subquery transformation of a JOIN object.
@ -1543,6 +1546,7 @@ public:
pushdown_query= 0; pushdown_query= 0;
original_join_tab= 0; original_join_tab= 0;
explain= NULL; explain= NULL;
tmp_table_keep_current_rowid= 0;
all_fields= fields_arg; all_fields= fields_arg;
if (&fields_list != &fields_arg) /* Avoid valgrind-warning */ if (&fields_list != &fields_arg) /* Avoid valgrind-warning */
@ -1776,6 +1780,7 @@ private:
void cleanup_item_list(List<Item> &items) const; void cleanup_item_list(List<Item> &items) const;
bool add_having_as_table_cond(JOIN_TAB *tab); bool add_having_as_table_cond(JOIN_TAB *tab);
bool make_aggr_tables_info(); 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}; enum enum_with_bush_roots { WITH_BUSH_ROOTS, WITHOUT_BUSH_ROOTS};

View File

@ -499,14 +499,14 @@ void select_union_recursive::cleanup()
bool select_union_direct::change_result(select_result *new_result) bool select_union_direct::change_result(select_result *new_result)
{ {
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) bool select_union_direct::postponed_prepare(List<Item> &types)
{ {
if (result != NULL) if (result != NULL)
return (result->prepare(types, unit) || result->prepare2()); return (result->prepare(types, unit) || result->prepare2(NULL));
else else
return false; return false;
} }

View File

@ -2198,10 +2198,66 @@ loop_end:
DBUG_RETURN(1); DBUG_RETURN(1);
tmp_tables[cnt]->file->extra(HA_EXTRA_WRITE_CACHE); tmp_tables[cnt]->file->extra(HA_EXTRA_WRITE_CACHE);
} }
join->tmp_table_keep_current_rowid= TRUE;
DBUG_RETURN(0); 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() multi_update::~multi_update()
{ {
TABLE_LIST *table; TABLE_LIST *table;