diff options
Diffstat (limited to 'sql/sql_update.cc')
-rw-r--r-- | sql/sql_update.cc | 167 |
1 files changed, 104 insertions, 63 deletions
diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 0f470062981..5cb8d085c33 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. Copyright (c) 2011 Monty Program Ab This program is free software; you can redistribute it and/or modify @@ -46,7 +46,7 @@ /** True if the table's input and output record buffers are comparable using - compare_records(TABLE*). + compare_record(TABLE*). */ bool records_are_comparable(const TABLE *table) { return ((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) == 0) || @@ -56,18 +56,13 @@ bool records_are_comparable(const TABLE *table) { /** Compares the input and outbut record buffers of the table to see if a row - has changed. The algorithm iterates over updated columns and if they are - nullable compares NULL bits in the buffer before comparing actual - data. Special care must be taken to compare only the relevant NULL bits and - mask out all others as they may be undefined. The storage engine will not - and should not touch them. - - @param table The table to evaluate. + has changed. @return true if row has changed. @return false otherwise. */ -bool compare_records(const TABLE *table) + +bool compare_record(const TABLE *table) { DBUG_ASSERT(records_are_comparable(table)); @@ -104,7 +99,6 @@ bool compare_records(const TABLE *table) comparison done above. */ if (table->s->can_cmp_whole_record) - // Fixed-size record: do bitwise comparison of the records return cmp_record(table,record[1]); /* Compare null bits */ if (memcmp(table->null_flags, @@ -262,6 +256,7 @@ int mysql_update(THD *thd, bool using_limit= limit != HA_POS_ERROR; bool safe_update= test(thd->variables.option_bits & OPTION_SAFE_UPDATES); bool used_key_is_modified= FALSE, transactional_table, will_batch; + bool can_compare_record; int res; int error, loc_error; uint used_index, dup_key_found; @@ -285,7 +280,11 @@ int mysql_update(THD *thd, if (open_tables(thd, &table_list, &table_count, 0)) DBUG_RETURN(1); - if (table_list->multitable_view) + //Prepare views so they are handled correctly. + if (mysql_handle_derived(thd->lex, DT_INIT)) + DBUG_RETURN(1); + + if (table_list->is_multitable()) { DBUG_ASSERT(table_list->view != 0); DBUG_PRINT("info", ("Switch to multi-update")); @@ -297,20 +296,19 @@ int mysql_update(THD *thd, if (lock_tables(thd, table_list, table_count, 0)) DBUG_RETURN(1); - if (mysql_handle_derived(thd->lex, &mysql_derived_prepare)) + if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT)) DBUG_RETURN(1); - - if (thd->fill_derived_tables() && - mysql_handle_derived(thd->lex, &mysql_derived_filling)) - { - mysql_handle_derived(thd->lex, &mysql_derived_cleanup); + if (table_list->handle_derived(thd->lex, DT_PREPARE)) DBUG_RETURN(1); - } - mysql_handle_derived(thd->lex, &mysql_derived_cleanup); thd_proc_info(thd, "init"); table= table_list->table; + if (!table_list->updatable) + { + my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE"); + DBUG_RETURN(1); + } /* Calculate "table->covering_keys" based on the WHERE */ table->covering_keys= table->s->keys_in_use; table->quick_keys.clear_all(); @@ -329,13 +327,17 @@ int mysql_update(THD *thd, table_list->grant.want_privilege= table->grant.want_privilege= want_privilege; table_list->register_want_access(want_privilege); #endif + /* 'Unfix' fields to allow correct marking by the setup_fields function. */ + if (table_list->is_view()) + unfix_fields(fields); + if (setup_fields_with_no_wrap(thd, 0, fields, MARK_COLUMNS_WRITE, 0, 0)) DBUG_RETURN(1); /* purecov: inspected */ if (table_list->view && check_fields(thd, fields)) { DBUG_RETURN(1); } - if (!table_list->updatable || check_key_in_view(thd, table_list)) + if (check_key_in_view(thd, table_list)) { my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE"); DBUG_RETURN(1); @@ -365,6 +367,10 @@ int mysql_update(THD *thd, DBUG_RETURN(1); /* purecov: inspected */ } + /* Apply the IN=>EXISTS transformation to all subqueries and optimize them. */ + if (select_lex->optimize_unflattened_subqueries()) + DBUG_RETURN(TRUE); + if (select_lex->inner_refs_list.elements && fix_inner_refs(thd, all_fields, select_lex, select_lex->ref_pointer_array)) DBUG_RETURN(1); @@ -659,6 +665,13 @@ int mysql_update(THD *thd, if (table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) table->prepare_for_position(); + /* + We can use compare_record() to optimize away updates if + the table handler is returning all columns OR if + if all updated columns are read + */ + can_compare_record= records_are_comparable(table); + while (!(error=info.read_record(&info)) && !thd->killed) { update_virtual_fields(thd, table); @@ -676,7 +689,7 @@ int mysql_update(THD *thd, found++; - if (!records_are_comparable(table) || compare_records(table)) + if (!can_compare_record || compare_record(table)) { if ((res= table_list->view_check_option(thd, ignore)) != VIEW_CHECK_OK) @@ -924,6 +937,11 @@ int mysql_update(THD *thd, } thd->count_cuted_fields= CHECK_FIELD_IGNORE; /* calc cuted fields */ thd->abort_on_warning= 0; + if (thd->lex->current_select->first_cond_optimization) + { + thd->lex->current_select->save_leaf_tables(thd); + thd->lex->current_select->first_cond_optimization= 0; + } *found_return= found; *updated_return= updated; DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); @@ -973,8 +991,8 @@ bool mysql_prepare_update(THD *thd, TABLE_LIST *table_list, if (setup_tables_and_check_access(thd, &select_lex->context, &select_lex->top_join_list, table_list, - &select_lex->leaf_tables, - FALSE, UPDATE_ACL, SELECT_ACL) || + select_lex->leaf_tables, + FALSE, UPDATE_ACL, SELECT_ACL, TRUE) || setup_conds(thd, table_list, select_lex->leaf_tables, conds) || select_lex->setup_ref_array(thd, order_num) || setup_order(thd, select_lex->ref_pointer_array, @@ -1010,8 +1028,8 @@ static table_map get_table_map(List<Item> *items) Item_field *item; table_map map= 0; - while ((item= (Item_field *) item_it++)) - map|= item->used_tables(); + while ((item= (Item_field *) item_it++)) + map|= item->all_used_tables(); DBUG_PRINT("info", ("table_map: 0x%08lx", (long) map)); return map; } @@ -1046,11 +1064,12 @@ static table_map get_table_map(List<Item> *items) false otherwise. */ static -bool unsafe_key_update(TABLE_LIST *leaves, table_map tables_for_update) +bool unsafe_key_update(List<TABLE_LIST> leaves, table_map tables_for_update) { - TABLE_LIST *tl= leaves; + List_iterator_fast<TABLE_LIST> it(leaves), it2(leaves); + TABLE_LIST *tl, *tl2; - for (tl= leaves; tl ; tl= tl->next_leaf) + while ((tl= it++)) { if (tl->table->map & tables_for_update) { @@ -1066,14 +1085,16 @@ bool unsafe_key_update(TABLE_LIST *leaves, table_map tables_for_update) if (!table_partitioned && !primkey_clustered) continue; - for (TABLE_LIST* tl2= tl->next_leaf; tl2 ; tl2= tl2->next_leaf) + it2.rewind(); + while ((tl2= it2++)) { /* Look at "next" tables only since all previous tables have already been checked */ TABLE *table2= tl2->table; - if (table2->map & tables_for_update && table1->s == table2->s) + if (tl2 != tl && + table2->map & tables_for_update && table1->s == table2->s) { // A table is updated through two aliases if (table_partitioned && @@ -1135,7 +1156,7 @@ int mysql_multi_update_prepare(THD *thd) { LEX *lex= thd->lex; TABLE_LIST *table_list= lex->query_tables; - TABLE_LIST *tl, *leaves; + TABLE_LIST *tl; List<Item> *fields= &lex->select_lex.item_list; table_map tables_for_update; bool update_view= 0; @@ -1163,7 +1184,7 @@ int mysql_multi_update_prepare(THD *thd) open_tables(thd, &table_list, &table_count, (thd->stmt_arena->is_stmt_prepare() ? MYSQL_OPEN_FORCE_SHARED_MDL : 0))) || - mysql_handle_derived(lex, &mysql_derived_prepare)) + mysql_handle_derived(lex, DT_INIT)) DBUG_RETURN(TRUE); /* setup_tables() need for VIEWs. JOIN::prepare() will call setup_tables() @@ -1171,11 +1192,20 @@ int mysql_multi_update_prepare(THD *thd) call in setup_tables()). */ + //We need to merge for insert prior to prepare. + if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT)) + DBUG_RETURN(TRUE); + if (mysql_handle_derived(lex, DT_PREPARE)) + DBUG_RETURN(TRUE); + if (setup_tables_and_check_access(thd, &lex->select_lex.context, &lex->select_lex.top_join_list, table_list, - &lex->select_lex.leaf_tables, FALSE, - UPDATE_ACL, SELECT_ACL)) + lex->select_lex.leaf_tables, FALSE, + UPDATE_ACL, SELECT_ACL, FALSE)) + DBUG_RETURN(TRUE); + + if (lex->select_lex.handle_derived(thd->lex, DT_MERGE)) DBUG_RETURN(TRUE); if (setup_fields_with_no_wrap(thd, 0, *fields, MARK_COLUMNS_WRITE, 0, 0)) @@ -1197,15 +1227,14 @@ int mysql_multi_update_prepare(THD *thd) thd->table_map_for_update= tables_for_update= get_table_map(fields); - leaves= lex->select_lex.leaf_tables; - - if (unsafe_key_update(leaves, tables_for_update)) + if (unsafe_key_update(lex->select_lex.leaf_tables, tables_for_update)) DBUG_RETURN(true); /* Setup timestamp handling and locking mode */ - for (tl= leaves; tl; tl= tl->next_leaf) + List_iterator<TABLE_LIST> ti(lex->select_lex.leaf_tables); + while ((tl= ti++)) { TABLE *table= tl->table; /* Only set timestamp column if this is not modified */ @@ -1252,7 +1281,7 @@ int mysql_multi_update_prepare(THD *thd) for (tl= table_list; tl; tl= tl->next_local) { /* Check access privileges for table */ - if (!tl->derived) + if (!tl->is_derived()) { uint want_privilege= tl->updating ? UPDATE_ACL : SELECT_ACL; if (check_access(thd, want_privilege, tl->db, @@ -1267,7 +1296,7 @@ int mysql_multi_update_prepare(THD *thd) /* check single table update for view compound from several tables */ for (tl= table_list; tl; tl= tl->next_local) { - if (tl->effective_algorithm == VIEW_ALGORITHM_MERGE) + if (tl->is_merged_derived()) { TABLE_LIST *for_update= 0; if (tl->check_single_table(&for_update, tables_for_update, tl)) @@ -1293,7 +1322,8 @@ int mysql_multi_update_prepare(THD *thd) */ lex->select_lex.exclude_from_table_unique_test= TRUE; /* We only need SELECT privilege for columns in the values list */ - for (tl= leaves; tl; tl= tl->next_leaf) + ti.rewind(); + while ((tl= ti++)) { TABLE *table= tl->table; TABLE_LIST *tlist; @@ -1321,15 +1351,10 @@ int mysql_multi_update_prepare(THD *thd) further check in multi_update::prepare whether to use record cache. */ lex->select_lex.exclude_from_table_unique_test= FALSE; - - if (thd->fill_derived_tables() && - mysql_handle_derived(lex, &mysql_derived_filling)) - { - mysql_handle_derived(lex, &mysql_derived_cleanup); - DBUG_RETURN(TRUE); - } - mysql_handle_derived(lex, &mysql_derived_cleanup); + if (lex->select_lex.save_prep_leaf_tables(thd)) + DBUG_RETURN(TRUE); + DBUG_RETURN (FALSE); } @@ -1354,7 +1379,7 @@ bool mysql_multi_update(THD *thd, DBUG_ENTER("mysql_multi_update"); if (!(*result= new multi_update(table_list, - thd->lex->select_lex.leaf_tables, + &thd->lex->select_lex.leaf_tables, fields, values, handle_duplicates, ignore))) { @@ -1390,7 +1415,7 @@ bool mysql_multi_update(THD *thd, multi_update::multi_update(TABLE_LIST *table_list, - TABLE_LIST *leaves_list, + List<TABLE_LIST> *leaves_list, List<Item> *field_list, List<Item> *value_list, enum enum_duplicates handle_duplicates_arg, bool ignore_arg) @@ -1408,6 +1433,7 @@ multi_update::multi_update(TABLE_LIST *table_list, int multi_update::prepare(List<Item> ¬_used_values, SELECT_LEX_UNIT *lex_unit) + { TABLE_LIST *table_ref; SQL_I_List<TABLE_LIST> update; @@ -1417,6 +1443,7 @@ int multi_update::prepare(List<Item> ¬_used_values, List_iterator_fast<Item> value_it(*values); uint i, max_fields; uint leaf_table_count= 0; + List_iterator<TABLE_LIST> ti(*leaves); DBUG_ENTER("multi_update::prepare"); thd->count_cuted_fields= CHECK_FIELD_WARN; @@ -1436,7 +1463,7 @@ int multi_update::prepare(List<Item> ¬_used_values, TABLE::tmp_set by pointing TABLE::read_set to it and then restore it after setup_fields(). */ - for (table_ref= leaves; table_ref; table_ref= table_ref->next_leaf) + while ((table_ref= ti++)) { TABLE *table= table_ref->table; if (tables_to_update & table->map) @@ -1454,7 +1481,8 @@ int multi_update::prepare(List<Item> ¬_used_values, int error= setup_fields(thd, 0, *values, MARK_COLUMNS_READ, 0, 0); - for (table_ref= leaves; table_ref; table_ref= table_ref->next_leaf) + ti.rewind(); + while ((table_ref= ti++)) { TABLE *table= table_ref->table; if (tables_to_update & table->map) @@ -1483,7 +1511,8 @@ int multi_update::prepare(List<Item> ¬_used_values, */ update.empty(); - for (table_ref= leaves; table_ref; table_ref= table_ref->next_leaf) + ti.rewind(); + while ((table_ref= ti++)) { /* TODO: add support of view of join support */ TABLE *table=table_ref->table; @@ -1709,9 +1738,9 @@ loop_end: { table_map unupdated_tables= table_ref->check_option->used_tables() & ~first_table_for_update->map; - for (TABLE_LIST *tbl_ref =leaves; - unupdated_tables && tbl_ref; - tbl_ref= tbl_ref->next_leaf) + List_iterator<TABLE_LIST> ti(*leaves); + TABLE_LIST *tbl_ref; + while ((tbl_ref= ti++) && unupdated_tables) { if (unupdated_tables & tbl_ref->table->map) unupdated_tables&= ~tbl_ref->table->map; @@ -1737,7 +1766,8 @@ loop_end: do { Field_string *field= new Field_string(tbl->file->ref_length, 0, - tbl->alias, &my_charset_bin); + tbl->alias.c_ptr(), + &my_charset_bin); if (!field) DBUG_RETURN(1); field->init(tbl); @@ -1809,7 +1839,7 @@ multi_update::~multi_update() } -bool multi_update::send_data(List<Item> ¬_used_values) +int multi_update::send_data(List<Item> ¬_used_values) { TABLE_LIST *cur_table; DBUG_ENTER("multi_update::send_data"); @@ -1835,6 +1865,14 @@ bool multi_update::send_data(List<Item> ¬_used_values) if (table == table_to_update) { + /* + We can use compare_record() to optimize away updates if + the table handler is returning all columns OR if + if all updated columns are read + */ + bool can_compare_record; + can_compare_record= records_are_comparable(table); + table->status|= STATUS_UPDATED; store_record(table,record[1]); if (fill_record_n_invoke_before_triggers(thd, *fields_for_table[offset], @@ -1849,7 +1887,7 @@ bool multi_update::send_data(List<Item> ¬_used_values) */ table->auto_increment_field_not_null= FALSE; found++; - if (!records_are_comparable(table) || compare_records(table)) + if (!can_compare_record || compare_record(table)) { int error; if ((error= cur_table->view_check_option(thd, ignore)) != @@ -1946,7 +1984,7 @@ bool multi_update::send_data(List<Item> ¬_used_values) *values_for_table[offset], TRUE, FALSE); /* Write row, ignoring duplicated updates to a row */ - error= tmp_table->file->ha_write_row(tmp_table->record[0]); + error= tmp_table->file->ha_write_tmp_row(tmp_table->record[0]); if (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE) { if (error && @@ -2040,6 +2078,7 @@ int multi_update::do_updates() DBUG_RETURN(0); for (cur_table= update_tables; cur_table; cur_table= cur_table->next_local) { + bool can_compare_record; uint offset= cur_table->shared; table = cur_table->table; @@ -2088,6 +2127,8 @@ int multi_update::do_updates() goto err; } + can_compare_record= records_are_comparable(table); + for (;;) { if (thd->killed && trans_safe) @@ -2135,7 +2176,7 @@ int multi_update::do_updates() TRG_ACTION_BEFORE, TRUE)) goto err2; - if (!records_are_comparable(table) || compare_records(table)) + if (!can_compare_record || compare_record(table)) { int error; if ((error= cur_table->view_check_option(thd, ignore)) != |