diff options
author | Igor Babaev <igor@askmonty.org> | 2011-06-13 19:03:03 -0700 |
---|---|---|
committer | Igor Babaev <igor@askmonty.org> | 2011-06-13 19:03:03 -0700 |
commit | 56eb6d7e69ecce856e2d54e2404157407cb7203b (patch) | |
tree | ed1868d2b843805abf292c87b538e4a7983ae9a4 | |
parent | ab411f8f1c2e84082623c038eb024c15c58745b5 (diff) | |
download | mariadb-git-56eb6d7e69ecce856e2d54e2404157407cb7203b.tar.gz |
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.
-rw-r--r-- | mysql-test/r/derived_view.result | 25 | ||||
-rw-r--r-- | mysql-test/t/derived_view.test | 24 | ||||
-rw-r--r-- | sql/sql_base.cc | 15 | ||||
-rw-r--r-- | sql/sql_class.cc | 1 | ||||
-rw-r--r-- | sql/sql_class.h | 2 | ||||
-rw-r--r-- | sql/sql_delete.cc | 23 | ||||
-rw-r--r-- | sql/sql_derived.cc | 84 | ||||
-rw-r--r-- | sql/sql_lex.cc | 30 | ||||
-rw-r--r-- | sql/sql_lex.h | 3 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 13 | ||||
-rw-r--r-- | sql/sql_update.cc | 8 | ||||
-rw-r--r-- | sql/table.cc | 2 |
12 files changed, 176 insertions, 54 deletions
diff --git a/mysql-test/r/derived_view.result b/mysql-test/r/derived_view.result index 5e3fc1e8cf1..2b0ef412460 100644 --- a/mysql-test/r/derived_view.result +++ b/mysql-test/r/derived_view.result @@ -589,3 +589,28 @@ f1 f1 224 224 DROP VIEW v1; 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; diff --git a/mysql-test/t/derived_view.test b/mysql-test/t/derived_view.test index 93c2eb48fcd..e5d5b0f797d 100644 --- a/mysql-test/t/derived_view.test +++ b/mysql-test/t/derived_view.test @@ -235,3 +235,27 @@ SELECT * FROM v1 JOIN t2 ON v1.f1 = t2.f1; DROP VIEW v1; 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; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 136ae9cb9f0..dd1c89a8fb4 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7789,9 +7789,18 @@ bool setup_tables(THD *thd, Name_resolution_context *context, if (select_lex->first_cond_optimization) { leaves.empty(); - select_lex->leaf_tables_exec.empty(); - make_leaves_list(leaves, tables, full_table_list, first_select_table); - + if (!select_lex->is_prep_leaf_list_saved) + { + 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++)) { TABLE *table= table_list->table; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 925a39afa33..d8aece52c53 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -814,6 +814,7 @@ THD::THD() memset(&invoker_user, 0, sizeof(invoker_user)); memset(&invoker_host, 0, sizeof(invoker_host)); prepare_derived_at_open= FALSE; + save_prep_leaf_list= FALSE; } diff --git a/sql/sql_class.h b/sql/sql_class.h index aec7839455e..152d65e9d9e 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1571,6 +1571,8 @@ public: bool prepare_derived_at_open; + bool save_prep_leaf_list; + #ifndef MYSQL_CLIENT int binlog_setup_trx_data(); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 6ace7ba8086..7f3ed2e705f 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -61,8 +61,9 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (open_and_lock_tables(thd, table_list)) DBUG_RETURN(TRUE); - 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)) + if (mysql_handle_list_of_derived(thd->lex, table_list, DT_MERGE_FOR_INSERT)) + DBUG_RETURN(TRUE); + if (mysql_handle_list_of_derived(thd->lex, table_list, DT_PREPARE)) DBUG_RETURN(TRUE); if (!table_list->updatable) @@ -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)) DBUG_RETURN(TRUE); - select_lex->fix_prepare_information(thd, conds, &fake_conds); + select_lex->fix_prepare_information(thd, conds, &fake_conds); DBUG_RETURN(FALSE); } @@ -586,10 +587,11 @@ int mysql_multi_delete_prepare(THD *thd) TABLE_LIST *target_tbl; DBUG_ENTER("mysql_multi_delete_prepare"); - TABLE_LIST *tables= lex->query_tables; - if (mysql_handle_derived(lex, DT_INIT) || - mysql_handle_list_of_derived(lex, tables, DT_MERGE_FOR_INSERT) || - mysql_handle_list_of_derived(lex, tables, DT_PREPARE)) + if (mysql_handle_derived(lex, DT_INIT)) + DBUG_RETURN(TRUE); + if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT)) + DBUG_RETURN(TRUE); + if (mysql_handle_derived(lex, DT_PREPARE)) DBUG_RETURN(TRUE); /* setup_tables() need for VIEWs. JOIN::prepare() will not do it second @@ -616,7 +618,8 @@ int mysql_multi_delete_prepare(THD *thd) 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), target_tbl->correspondent_table->view_db.str, @@ -651,6 +654,10 @@ int mysql_multi_delete_prepare(THD *thd) with further calls to unique_table */ lex->select_lex.exclude_from_table_unique_test= FALSE; + + if (lex->select_lex.save_prep_leaf_tables(thd)) + DBUG_RETURN(TRUE); + DBUG_RETURN(FALSE); } diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 9bfa49c5f2d..7474566b184 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -85,15 +85,18 @@ mysql_handle_derived(LEX *lex, uint phases) cursor && !res; 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 : - DT_PHASES_MATERIALIZE); + DT_PHASES_MATERIALIZE | DT_MERGE_FOR_INSERT); /* Skip derived tables to which the phase isn't applicable. TODO: mark derived at the parse time, later set it's type (merged or materialized) */ 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; res= (*processors[phase])(lex->thd, lex, cursor); } @@ -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 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(); - derived->set_materialized_derived(); - goto exit_merge; - } + /* + 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->options & OPTION_SCHEMA_TABLE) - parent_lex->options |= OPTION_SCHEMA_TABLE; + if (dt_select->leaf_tables.elements + tablenr > MAX_TABLES) + { + /* 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) - { - dt_select->leaf_tables.empty(); - make_leaves_list(dt_select->leaf_tables, derived, TRUE, 0); - } + parent_lex->cond_count+= dt_select->cond_count; - if (!derived->merged_for_insert) - { derived->nested_join= (NESTED_JOIN*) thd->calloc(sizeof(NESTED_JOIN)); + if (!derived->get_unit()->prepared) + { + 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) { res= TRUE; @@ -467,14 +472,16 @@ bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived) if (derived->merged_for_insert) return FALSE; - /* It's a target view for an INSERT, create field translation only. */ - if (!derived->updatable || derived->is_materialized_derived()) + if (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; } if (!derived->is_multitable()) { + if (!derived->updatable) + return derived->create_field_translation(thd); TABLE_LIST *tl=((TABLE_LIST*)dt_select->table_list.first); TABLE *table= tl->table; /* preserve old map & tablenr. */ @@ -504,6 +511,9 @@ bool mysql_derived_merge_for_insert(THD *thd, LEX *lex, TABLE_LIST *derived) } 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)) return TRUE; } @@ -609,7 +619,11 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived) bool res= FALSE; // 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); Query_arena *arena= thd->stmt_arena, backup; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index d3613d0d660..27562238734 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1604,6 +1604,7 @@ void st_select_lex::init_query() top_join_list.empty(); join_list= &top_join_list; embedding= 0; + leaf_tables_prep.empty(); leaf_tables.empty(); item_list.empty(); join= 0; @@ -1672,6 +1673,7 @@ void st_select_lex::init_select() cond_value= having_value= Item::COND_UNDEF; inner_refs_list.empty(); full_group_by_flag= 0; + is_prep_leaf_list_saved= FALSE; insert_tables= 0; merged_into= 0; } @@ -3585,6 +3587,7 @@ void SELECT_LEX::mark_const_derived(bool empty) } } + bool st_select_lex::save_leaf_tables(THD *thd) { Query_arena *arena= thd->stmt_arena, backup; @@ -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 partitioning or if only partitions to add or to split. diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 5a8b5a5f361..57eeb963b89 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -639,6 +639,8 @@ public: */ List<TABLE_LIST> leaf_tables; List<TABLE_LIST> leaf_tables_exec; + List<TABLE_LIST> leaf_tables_prep; + bool is_prep_leaf_list_saved; uint insert_tables; st_select_lex *merged_into; /* select which this select is merged into */ /* (not 0 only for views/derived tables) */ @@ -899,6 +901,7 @@ public: void mark_const_derived(bool empty); bool save_leaf_tables(THD *thd); + bool save_prep_leaf_tables(THD *thd); private: /* current index hint kind. used in filling up index_hints */ diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index ece4b8a1014..5b5ed004006 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1269,8 +1269,9 @@ static int mysql_test_update(Prepared_statement *stmt, thd->fill_derived_tables() is false here for sure (because it is preparation of PS, so we even do not check it). */ - if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT) || - table_list->handle_derived(thd->lex, DT_PREPARE)) + if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT)) + goto error; + if (table_list->handle_derived(thd->lex, DT_PREPARE)) goto error; if (!table_list->updatable) @@ -1340,9 +1341,11 @@ static bool mysql_test_delete(Prepared_statement *stmt, open_tables(thd, &table_list, &table_count, 0)) goto error; - if (mysql_handle_derived(thd->lex, DT_INIT) || - mysql_handle_list_of_derived(thd->lex, table_list, DT_MERGE_FOR_INSERT) || - mysql_handle_list_of_derived(thd->lex, table_list, DT_PREPARE)) + if (mysql_handle_derived(thd->lex, DT_INIT)) + goto error; + if (mysql_handle_derived(thd->lex, DT_MERGE_FOR_INSERT)) + goto error; + if (mysql_handle_derived(thd->lex, DT_PREPARE)) goto error; if (!table_list->updatable) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 4821fc2bd8f..0bfbcf2b23f 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1038,8 +1038,9 @@ reopen_tables: call in setup_tables()). */ //We need to merge for insert prior to prepare. - if (mysql_handle_list_of_derived(lex, table_list, DT_MERGE_FOR_INSERT)) - DBUG_RETURN(1); + if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT)) + DBUG_RETURN(TRUE); + if (mysql_handle_derived(lex, DT_PREPARE)) DBUG_RETURN(TRUE); @@ -1237,6 +1238,9 @@ reopen_tables: further check in multi_update::prepare whether to use record cache. */ lex->select_lex.exclude_from_table_unique_test= FALSE; + + if (lex->select_lex.save_prep_leaf_tables(thd)) + DBUG_RETURN(TRUE); DBUG_RETURN (FALSE); } diff --git a/sql/table.cc b/sql/table.cc index 8427ca6e112..512214b155e 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -5372,7 +5372,7 @@ void TABLE::use_index(int key_to_save) DBUG_ASSERT(!created && key_to_save < (int)s->keys); if (key_to_save >= 0) /* 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 /* Drop all keys; */ i= 0; |