Commit 56eb6d7e authored by Igor Babaev's avatar Igor Babaev

Fixed LP bug #794890.

Changed the code that processing of multi-updates and multi-deletes
with multitable views at the prepare stage.

A proper solution would be: never to perform any transformations of views
before and at the prepare stage. Yet it would  require re-engineering
of the code that checks privileges and updatability of views.
Ultimately this re-engineering has to be done to provide a clean solution
for INSERT/UPDATE/DELETE statements that use views.

Fixed a valgrind problem in the function TABLE::use_index.
  
parent ab411f8f
...@@ -589,3 +589,28 @@ f1 f1 ...@@ -589,3 +589,28 @@ f1 f1
224 224 224 224
DROP VIEW v1; DROP VIEW v1;
DROP TABLE t1,t2; DROP TABLE t1,t2;
#
# LP bug #794890: abort failure on multi-update with view
#
CREATE TABLE t1 (a int);
INSERT INTO t1 VALUES (20), (7);
CREATE TABLE t2 (a int);
INSERT INTO t2 VALUES (7), (9), (7);
CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT a FROM t1;
CREATE VIEW v2 AS SELECT t2.a FROM t2, v1 WHERE t2.a=t2.a;
UPDATE v2 SET a = 2;
SELECT * FROM t2;
a
2
2
2
UPDATE t1,v2 SET t1.a = 3;
SELECT * FROM t1;
a
3
3
DELETE t1 FROM t1,v2;
SELECT * FROM t1;
a
DROP VIEW v1,v2;
DROP TABLE t1,t2;
...@@ -235,3 +235,27 @@ SELECT * FROM v1 JOIN t2 ON v1.f1 = t2.f1; ...@@ -235,3 +235,27 @@ SELECT * FROM v1 JOIN t2 ON v1.f1 = t2.f1;
DROP VIEW v1; DROP VIEW v1;
DROP TABLE t1,t2; DROP TABLE t1,t2;
--echo #
--echo # LP bug #794890: abort failure on multi-update with view
--echo #
CREATE TABLE t1 (a int);
INSERT INTO t1 VALUES (20), (7);
CREATE TABLE t2 (a int);
INSERT INTO t2 VALUES (7), (9), (7);
CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT a FROM t1;
CREATE VIEW v2 AS SELECT t2.a FROM t2, v1 WHERE t2.a=t2.a;
UPDATE v2 SET a = 2;
SELECT * FROM t2;
UPDATE t1,v2 SET t1.a = 3;
SELECT * FROM t1;
DELETE t1 FROM t1,v2;
SELECT * FROM t1;
DROP VIEW v1,v2;
DROP TABLE t1,t2;
...@@ -7789,9 +7789,18 @@ bool setup_tables(THD *thd, Name_resolution_context *context, ...@@ -7789,9 +7789,18 @@ bool setup_tables(THD *thd, Name_resolution_context *context,
if (select_lex->first_cond_optimization) if (select_lex->first_cond_optimization)
{ {
leaves.empty(); leaves.empty();
select_lex->leaf_tables_exec.empty(); if (!select_lex->is_prep_leaf_list_saved)
make_leaves_list(leaves, tables, full_table_list, first_select_table); {
make_leaves_list(leaves, tables, full_table_list, first_select_table);
select_lex->leaf_tables_exec.empty();
}
else
{
List_iterator_fast <TABLE_LIST> ti(select_lex->leaf_tables_prep);
while ((table_list= ti++))
leaves.push_back(table_list);
}
while ((table_list= ti++)) while ((table_list= ti++))
{ {
TABLE *table= table_list->table; TABLE *table= table_list->table;
......
...@@ -814,6 +814,7 @@ THD::THD() ...@@ -814,6 +814,7 @@ THD::THD()
memset(&invoker_user, 0, sizeof(invoker_user)); memset(&invoker_user, 0, sizeof(invoker_user));
memset(&invoker_host, 0, sizeof(invoker_host)); memset(&invoker_host, 0, sizeof(invoker_host));
prepare_derived_at_open= FALSE; prepare_derived_at_open= FALSE;
save_prep_leaf_list= FALSE;
} }
......
...@@ -1571,6 +1571,8 @@ class THD :public Statement, ...@@ -1571,6 +1571,8 @@ class THD :public Statement,
bool prepare_derived_at_open; bool prepare_derived_at_open;
bool save_prep_leaf_list;
#ifndef MYSQL_CLIENT #ifndef MYSQL_CLIENT
int binlog_setup_trx_data(); int binlog_setup_trx_data();
......
...@@ -61,8 +61,9 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, ...@@ -61,8 +61,9 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
if (open_and_lock_tables(thd, table_list)) if (open_and_lock_tables(thd, table_list))
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
if (mysql_handle_list_of_derived(thd->lex, table_list, DT_MERGE_FOR_INSERT) || if (mysql_handle_list_of_derived(thd->lex, table_list, DT_MERGE_FOR_INSERT))
mysql_handle_list_of_derived(thd->lex, table_list, DT_PREPARE)) DBUG_RETURN(TRUE);
if (mysql_handle_list_of_derived(thd->lex, table_list, DT_PREPARE))
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
if (!table_list->updatable) if (!table_list->updatable)
...@@ -550,7 +551,7 @@ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds) ...@@ -550,7 +551,7 @@ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds)
fix_inner_refs(thd, all_fields, select_lex, select_lex->ref_pointer_array)) fix_inner_refs(thd, all_fields, select_lex, select_lex->ref_pointer_array))
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
select_lex->fix_prepare_information(thd, conds, &fake_conds); select_lex->fix_prepare_information(thd, conds, &fake_conds);
DBUG_RETURN(FALSE); DBUG_RETURN(FALSE);
} }
...@@ -586,10 +587,11 @@ int mysql_multi_delete_prepare(THD *thd) ...@@ -586,10 +587,11 @@ int mysql_multi_delete_prepare(THD *thd)
TABLE_LIST *target_tbl; TABLE_LIST *target_tbl;
DBUG_ENTER("mysql_multi_delete_prepare"); DBUG_ENTER("mysql_multi_delete_prepare");
TABLE_LIST *tables= lex->query_tables; if (mysql_handle_derived(lex, DT_INIT))
if (mysql_handle_derived(lex, DT_INIT) || DBUG_RETURN(TRUE);
mysql_handle_list_of_derived(lex, tables, DT_MERGE_FOR_INSERT) || if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT))
mysql_handle_list_of_derived(lex, tables, DT_PREPARE)) DBUG_RETURN(TRUE);
if (mysql_handle_derived(lex, DT_PREPARE))
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
/* /*
setup_tables() need for VIEWs. JOIN::prepare() will not do it second setup_tables() need for VIEWs. JOIN::prepare() will not do it second
...@@ -616,7 +618,8 @@ int mysql_multi_delete_prepare(THD *thd) ...@@ -616,7 +618,8 @@ int mysql_multi_delete_prepare(THD *thd)
target_tbl= target_tbl->next_local) target_tbl= target_tbl->next_local)
{ {
if (!(target_tbl->table= target_tbl->correspondent_table->table)) target_tbl->table= target_tbl->correspondent_table->table;
if (target_tbl->correspondent_table->is_multitable())
{ {
my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0),
target_tbl->correspondent_table->view_db.str, target_tbl->correspondent_table->view_db.str,
...@@ -651,6 +654,10 @@ int mysql_multi_delete_prepare(THD *thd) ...@@ -651,6 +654,10 @@ int mysql_multi_delete_prepare(THD *thd)
with further calls to unique_table with further calls to unique_table
*/ */
lex->select_lex.exclude_from_table_unique_test= FALSE; lex->select_lex.exclude_from_table_unique_test= FALSE;
if (lex->select_lex.save_prep_leaf_tables(thd))
DBUG_RETURN(TRUE);
DBUG_RETURN(FALSE); DBUG_RETURN(FALSE);
} }
......
...@@ -85,15 +85,18 @@ mysql_handle_derived(LEX *lex, uint phases) ...@@ -85,15 +85,18 @@ mysql_handle_derived(LEX *lex, uint phases)
cursor && !res; cursor && !res;
cursor= cursor->next_local) cursor= cursor->next_local)
{ {
if (!cursor->is_view_or_derived() && phases == DT_MERGE_FOR_INSERT)
continue;
uint8 allowed_phases= (cursor->is_merged_derived() ? DT_PHASES_MERGE : uint8 allowed_phases= (cursor->is_merged_derived() ? DT_PHASES_MERGE :
DT_PHASES_MATERIALIZE); DT_PHASES_MATERIALIZE | DT_MERGE_FOR_INSERT);
/* /*
Skip derived tables to which the phase isn't applicable. Skip derived tables to which the phase isn't applicable.
TODO: mark derived at the parse time, later set it's type TODO: mark derived at the parse time, later set it's type
(merged or materialized) (merged or materialized)
*/ */
if ((phase_flag != DT_PREPARE && !(allowed_phases & phase_flag)) || if ((phase_flag != DT_PREPARE && !(allowed_phases & phase_flag)) ||
(cursor->merged_for_insert && phase_flag != DT_REINIT)) (cursor->merged_for_insert && phase_flag != DT_REINIT &&
phase_flag != DT_PREPARE))
continue; continue;
res= (*processors[phase])(lex->thd, lex, cursor); res= (*processors[phase])(lex->thd, lex, cursor);
} }
...@@ -345,41 +348,43 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived) ...@@ -345,41 +348,43 @@ bool mysql_derived_merge(THD *thd, LEX *lex, TABLE_LIST *derived)
arena= thd->activate_stmt_arena_if_needed(&backup); // For easier test arena= thd->activate_stmt_arena_if_needed(&backup); // For easier test
derived->merged= TRUE; derived->merged= TRUE;
/*
Check whether there is enough free bits in table map to merge subquery.
If not - materialize it. This check isn't cached so when there is a big
and small subqueries, and the bigger one can't be merged it wouldn't
block the smaller one.
*/
if (parent_lex->get_free_table_map(&map, &tablenr))
{
/* There is no enough table bits, fall back to materialization. */
derived->change_refs_to_fields();
derived->set_materialized_derived();
goto exit_merge;
}
if (dt_select->leaf_tables.elements + tablenr > MAX_TABLES) if (!derived->merged_for_insert)
{ {
/* There is no enough table bits, fall back to materialization. */ /*
derived->change_refs_to_fields(); Check whether there is enough free bits in table map to merge subquery.
derived->set_materialized_derived(); If not - materialize it. This check isn't cached so when there is a big
goto exit_merge; and small subqueries, and the bigger one can't be merged it wouldn't
} block the smaller one.
*/
if (parent_lex->get_free_table_map(&map, &tablenr))
{
/* There is no enough table bits, fall back to materialization. */
derived->change_refs_to_fields();
derived->set_materialized_derived();
goto exit_merge;
}
if (dt_select->options & OPTION_SCHEMA_TABLE) if (dt_select->leaf_tables.elements + tablenr > MAX_TABLES)
parent_lex->options |= OPTION_SCHEMA_TABLE; {
/* There is no enough table bits, fall back to materialization. */
derived->change_refs_to_fields();
derived->set_materialized_derived();
goto exit_merge;
}
parent_lex->cond_count+= dt_select->cond_count; if (dt_select->options & OPTION_SCHEMA_TABLE)
parent_lex->options |= OPTION_SCHEMA_TABLE;
if (!derived->get_unit()->prepared) parent_lex->cond_count+= dt_select->cond_count;
{
dt_select->leaf_tables.empty();
make_leaves_list(dt_select->leaf_tables, derived, TRUE, 0);
}
if (!derived->merged_for_insert) if (!derived->get_unit()->prepared)
{ derived->nested_join= (NESTED_JOIN*) thd->calloc(sizeof(NESTED_JOIN)); {
dt_select->leaf_tables.empty();
make_leaves_list(dt_select->leaf_tables, derived, TRUE, 0);
}
derived->nested_join= (NESTED_JOIN*) thd->calloc(sizeof(NESTED_JOIN));
if (!derived->nested_join) if (!derived->nested_join)
{ {
res= TRUE; res= TRUE;
...@@ -467,14 +472,16 @@ bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived) ...@@ -467,14 +472,16 @@ bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived)
if (derived->merged_for_insert) if (derived->merged_for_insert)
return FALSE; return FALSE;
/* It's a target view for an INSERT, create field translation only. */ if (derived->is_materialized_derived())
if (!derived->updatable || derived->is_materialized_derived())
{ {
bool res= derived->create_field_translation(thd); bool res= mysql_derived_prepare(thd, lex, derived);
derived->select_lex->leaf_tables.push_back(derived);
return res; return res;
} }
if (!derived->is_multitable()) if (!derived->is_multitable())
{ {
if (!derived->updatable)
return derived->create_field_translation(thd);
TABLE_LIST *tl=((TABLE_LIST*)dt_select->table_list.first); TABLE_LIST *tl=((TABLE_LIST*)dt_select->table_list.first);
TABLE *table= tl->table; TABLE *table= tl->table;
/* preserve old map & tablenr. */ /* preserve old map & tablenr. */
...@@ -504,6 +511,9 @@ bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived) ...@@ -504,6 +511,9 @@ bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived)
} }
else else
{ {
if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
thd->lex->sql_command != SQLCOM_DELETE_MULTI)
thd->save_prep_leaf_list= TRUE;
if (!derived->merged_for_insert && mysql_derived_merge(thd, lex, derived)) if (!derived->merged_for_insert && mysql_derived_merge(thd, lex, derived))
return TRUE; return TRUE;
} }
...@@ -609,7 +619,11 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) ...@@ -609,7 +619,11 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)
bool res= FALSE; bool res= FALSE;
// Skip already prepared views/DT // Skip already prepared views/DT
if (!unit || unit->prepared || derived->merged_for_insert) if (!unit || unit->prepared ||
(derived->merged_for_insert &&
!(derived->is_multitable() &&
(thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
thd->lex->sql_command == SQLCOM_DELETE_MULTI))))
DBUG_RETURN(FALSE); DBUG_RETURN(FALSE);
Query_arena *arena= thd->stmt_arena, backup; Query_arena *arena= thd->stmt_arena, backup;
......
...@@ -1604,6 +1604,7 @@ void st_select_lex::init_query() ...@@ -1604,6 +1604,7 @@ void st_select_lex::init_query()
top_join_list.empty(); top_join_list.empty();
join_list= &top_join_list; join_list= &top_join_list;
embedding= 0; embedding= 0;
leaf_tables_prep.empty();
leaf_tables.empty(); leaf_tables.empty();
item_list.empty(); item_list.empty();
join= 0; join= 0;
...@@ -1672,6 +1673,7 @@ void st_select_lex::init_select() ...@@ -1672,6 +1673,7 @@ void st_select_lex::init_select()
cond_value= having_value= Item::COND_UNDEF; cond_value= having_value= Item::COND_UNDEF;
inner_refs_list.empty(); inner_refs_list.empty();
full_group_by_flag= 0; full_group_by_flag= 0;
is_prep_leaf_list_saved= FALSE;
insert_tables= 0; insert_tables= 0;
merged_into= 0; merged_into= 0;
} }
...@@ -3585,6 +3587,7 @@ void SELECT_LEX::mark_const_derived(bool empty) ...@@ -3585,6 +3587,7 @@ void SELECT_LEX::mark_const_derived(bool empty)
} }
} }
bool st_select_lex::save_leaf_tables(THD *thd) bool st_select_lex::save_leaf_tables(THD *thd)
{ {
Query_arena *arena= thd->stmt_arena, backup; Query_arena *arena= thd->stmt_arena, backup;
...@@ -3609,6 +3612,33 @@ bool st_select_lex::save_leaf_tables(THD *thd) ...@@ -3609,6 +3612,33 @@ bool st_select_lex::save_leaf_tables(THD *thd)
} }
bool st_select_lex::save_prep_leaf_tables(THD *thd)
{
if (!thd->save_prep_leaf_list)
return 0;
Query_arena *arena= thd->stmt_arena, backup;
if (arena->is_conventional())
arena= 0;
else
thd->set_n_backup_active_arena(arena, &backup);
List_iterator_fast<TABLE_LIST> li(leaf_tables);
TABLE_LIST *table;
while ((table= li++))
{
if (leaf_tables_prep.push_back(table))
return 1;
}
thd->lex->select_lex.is_prep_leaf_list_saved= TRUE;
thd->save_prep_leaf_list= FALSE;
if (arena)
thd->restore_active_arena(arena, &backup);
return 0;
}
/** /**
A routine used by the parser to decide whether we are specifying a full A routine used by the parser to decide whether we are specifying a full
partitioning or if only partitions to add or to split. partitioning or if only partitions to add or to split.
......
...@@ -639,6 +639,8 @@ class st_select_lex: public st_select_lex_node ...@@ -639,6 +639,8 @@ class st_select_lex: public st_select_lex_node
*/ */
List<TABLE_LIST> leaf_tables; List<TABLE_LIST> leaf_tables;
List<TABLE_LIST> leaf_tables_exec; List<TABLE_LIST> leaf_tables_exec;
List<TABLE_LIST> leaf_tables_prep;
bool is_prep_leaf_list_saved;
uint insert_tables; uint insert_tables;
st_select_lex *merged_into; /* select which this select is merged into */ st_select_lex *merged_into; /* select which this select is merged into */
/* (not 0 only for views/derived tables) */ /* (not 0 only for views/derived tables) */
...@@ -899,6 +901,7 @@ class st_select_lex: public st_select_lex_node ...@@ -899,6 +901,7 @@ class st_select_lex: public st_select_lex_node
void mark_const_derived(bool empty); void mark_const_derived(bool empty);
bool save_leaf_tables(THD *thd); bool save_leaf_tables(THD *thd);
bool save_prep_leaf_tables(THD *thd);
private: private:
/* current index hint kind. used in filling up index_hints */ /* current index hint kind. used in filling up index_hints */
......
...@@ -1269,8 +1269,9 @@ static int mysql_test_update(Prepared_statement *stmt, ...@@ -1269,8 +1269,9 @@ static int mysql_test_update(Prepared_statement *stmt,
thd->fill_derived_tables() is false here for sure (because it is thd->fill_derived_tables() is false here for sure (because it is
preparation of PS, so we even do not check it). preparation of PS, so we even do not check it).
*/ */
if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT) || if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT))
table_list->handle_derived(thd->lex, DT_PREPARE)) goto error;
if (table_list->handle_derived(thd->lex, DT_PREPARE))
goto error; goto error;
if (!table_list->updatable) if (!table_list->updatable)
...@@ -1340,9 +1341,11 @@ static bool mysql_test_delete(Prepared_statement *stmt, ...@@ -1340,9 +1341,11 @@ static bool mysql_test_delete(Prepared_statement *stmt,
open_tables(thd, &table_list, &table_count, 0)) open_tables(thd, &table_list, &table_count, 0))
goto error; goto error;
if (mysql_handle_derived(thd->lex, DT_INIT) || if (mysql_handle_derived(thd->lex, DT_INIT))
mysql_handle_list_of_derived(thd->lex, table_list, DT_MERGE_FOR_INSERT) || goto error;
mysql_handle_list_of_derived(thd->lex, table_list, DT_PREPARE)) if (mysql_handle_derived(thd->lex, DT_MERGE_FOR_INSERT))
goto error;
if (mysql_handle_derived(thd->lex, DT_PREPARE))
goto error; goto error;
if (!table_list->updatable) if (!table_list->updatable)
......
...@@ -1038,8 +1038,9 @@ int mysql_multi_update_prepare(THD *thd) ...@@ -1038,8 +1038,9 @@ int mysql_multi_update_prepare(THD *thd)
call in setup_tables()). call in setup_tables()).
*/ */
//We need to merge for insert prior to prepare. //We need to merge for insert prior to prepare.
if (mysql_handle_list_of_derived(lex, table_list, DT_MERGE_FOR_INSERT)) if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT))
DBUG_RETURN(1); DBUG_RETURN(TRUE);
if (mysql_handle_derived(lex, DT_PREPARE)) if (mysql_handle_derived(lex, DT_PREPARE))
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
...@@ -1237,6 +1238,9 @@ int mysql_multi_update_prepare(THD *thd) ...@@ -1237,6 +1238,9 @@ int mysql_multi_update_prepare(THD *thd)
further check in multi_update::prepare whether to use record cache. further check in multi_update::prepare whether to use record cache.
*/ */
lex->select_lex.exclude_from_table_unique_test= FALSE; lex->select_lex.exclude_from_table_unique_test= FALSE;
if (lex->select_lex.save_prep_leaf_tables(thd))
DBUG_RETURN(TRUE);
DBUG_RETURN (FALSE); DBUG_RETURN (FALSE);
} }
......
...@@ -5372,7 +5372,7 @@ void TABLE::use_index(int key_to_save) ...@@ -5372,7 +5372,7 @@ void TABLE::use_index(int key_to_save)
DBUG_ASSERT(!created && key_to_save < (int)s->keys); DBUG_ASSERT(!created && key_to_save < (int)s->keys);
if (key_to_save >= 0) if (key_to_save >= 0)
/* Save the given key. */ /* Save the given key. */
memcpy(key_info, key_info + key_to_save, sizeof(KEY)); memmove(key_info, key_info + key_to_save, sizeof(KEY));
else else
/* Drop all keys; */ /* Drop all keys; */
i= 0; i= 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment