Loading mysql-test/r/view.result +17 −0 Original line number Diff line number Diff line Loading @@ -3367,6 +3367,23 @@ SHOW CREATE VIEW v1; View Create View v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select cast(1.23456789 as decimal(8,0)) AS `col` DROP VIEW v1; CREATE TABLE t1 (a INT); CREATE TABLE t2 (b INT, c INT DEFAULT 0); INSERT INTO t1 (a) VALUES (1), (2); INSERT INTO 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 v1; b c 1 0 2 0 UPDATE v1 SET c=1 WHERE b=1; SELECT * FROM v1; b c 1 1 2 0 DROP VIEW v1; DROP TABLE t1,t2; CREATE TABLE t1 (id int); CREATE TABLE t2 (id int, c int DEFAULT 0); INSERT INTO t1 (id) VALUES (1); Loading mysql-test/t/view.test +16 −0 Original line number Diff line number Diff line Loading @@ -3233,6 +3233,22 @@ CREATE VIEW v1 AS SELECT CAST(1.23456789 AS DECIMAL(8,0)) AS col; SHOW CREATE VIEW v1; DROP VIEW v1; # # Bug #28716: CHECK OPTION expression is evaluated over expired record buffers # when VIEW is updated via temporary tables # CREATE TABLE t1 (a INT); CREATE TABLE t2 (b INT, c INT DEFAULT 0); INSERT INTO t1 (a) VALUES (1), (2); INSERT INTO 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 v1; UPDATE v1 SET c=1 WHERE b=1; SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1,t2; # # Bug #28561: update on multi-table view with CHECK OPTION and # a subquery in WHERE condition Loading sql/sql_class.h +5 −0 Original line number Diff line number Diff line Loading @@ -2283,6 +2283,11 @@ class multi_update :public select_result_interceptor List <Item> *fields, *values; List <Item> **fields_for_table, **values_for_table; uint table_count; /* List of tables referenced in the CHECK OPTION condition of the updated view excluding the updated table. */ List <TABLE> unupdated_check_opt_tables; Copy_field *copy_field; enum enum_duplicates handle_duplicates; bool do_update, trans_safe; Loading sql/sql_update.cc +112 −29 Original line number Diff line number Diff line Loading @@ -970,6 +970,7 @@ int multi_update::prepare(List<Item> ¬_used_values, List_iterator_fast<Item> field_it(*fields); List_iterator_fast<Item> value_it(*values); uint i, max_fields; uint leaf_table_count= 0; DBUG_ENTER("multi_update::prepare"); thd->count_cuted_fields= CHECK_FIELD_WARN; Loading Loading @@ -1003,6 +1004,7 @@ int multi_update::prepare(List<Item> ¬_used_values, { /* TODO: add support of view of join support */ TABLE *table=table_ref->table; leaf_table_count++; if (tables_to_update & table->map) { TABLE_LIST *tl= (TABLE_LIST*) thd->memdup((char*) table_ref, Loading Loading @@ -1067,7 +1069,7 @@ int multi_update::prepare(List<Item> ¬_used_values, /* Allocate copy fields */ max_fields=0; for (i=0 ; i < table_count ; i++) set_if_bigger(max_fields, fields_for_table[i]->elements); set_if_bigger(max_fields, fields_for_table[i]->elements + leaf_table_count); copy_field= new Copy_field[max_fields]; DBUG_RETURN(thd->is_fatal_error != 0); } Loading Loading @@ -1160,13 +1162,22 @@ multi_update::initialize_tables(JOIN *join) trans_safe= transactional_tables= main_table->file->has_transactions(); table_to_update= 0; /* Any update has at least one pair (field, value) */ DBUG_ASSERT(fields->elements); /* Only one table may be modified by UPDATE of an updatable view. For an updatable view first_table_for_update indicates this table. For a regular multi-update it refers to some updated table. */ TABLE *first_table_for_update= ((Item_field *) fields->head())->field->table; /* Create a temporary table for keys to all tables, except main table */ for (table_ref= update_tables; table_ref; table_ref= table_ref->next_local) { TABLE *table=table_ref->table; uint cnt= table_ref->shared; Item_field *ifield; List<Item> temp_fields= *fields_for_table[cnt]; List<Item> temp_fields; ORDER group; if (ignore) Loading @@ -1174,34 +1185,63 @@ multi_update::initialize_tables(JOIN *join) if (table == main_table) // First table in join { if (safe_update_on_fly(thd, join->join_tab, table_ref, all_tables, &temp_fields)) fields_for_table[cnt])) { table_to_update= main_table; // Update table on the fly continue; } } if (table == first_table_for_update && table_ref->check_option) { 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) { if (unupdated_tables & tbl_ref->table->map) unupdated_tables&= ~tbl_ref->table->map; else continue; if (unupdated_check_opt_tables.push_back(tbl_ref->table)) DBUG_RETURN(1); } } TMP_TABLE_PARAM *tmp_param= tmp_table_param+cnt; /* Create a temporary table to store all fields that are changed for this table. The first field in the temporary table is a pointer to the original row so that we can find and update it original row so that we can find and update it. For the updatable VIEW a few following fields are rowids of tables used in the CHECK OPTION condition. */ /* ok to be on stack as this is not referenced outside of this func */ Field_string offset(table->file->ref_length, 0, "offset", table, &my_charset_bin); List_iterator_fast<TABLE> tbl_it(unupdated_check_opt_tables); TABLE *tbl= table; do { Field_string *field= new Field_string(tbl->file->ref_length, 0, tbl->alias, tbl, &my_charset_bin); if (!field) DBUG_RETURN(1); /* The field will be converted to varstring when creating tmp table if table to be updated was created by mysql 4.1. Deny this. */ offset.can_alter_field_type= 0; if (!(ifield= new Item_field(((Field *) &offset)))) field->can_alter_field_type= 0; Item_field *ifield= new Item_field((Field *) field); if (!ifield) DBUG_RETURN(1); ifield->maybe_null= 0; if (temp_fields.push_front(ifield)) if (temp_fields.push_back(ifield)) DBUG_RETURN(1); } while ((tbl= tbl_it++)); temp_fields.concat(fields_for_table[cnt]); /* Make an unique key over the first field to avoid duplicated updates */ bzero((char*) &group, sizeof(group)); Loading Loading @@ -1350,10 +1390,26 @@ bool multi_update::send_data(List<Item> ¬_used_values) { int error; TABLE *tmp_table= tmp_tables[offset]; fill_record(thd, tmp_table->field+1, *values_for_table[offset], 1); /* Store pointer to row */ memcpy((char*) tmp_table->field[0]->ptr, (char*) table->file->ref, table->file->ref_length); /* Store regular updated fields in the row. */ fill_record(thd, tmp_table->field + 1 + unupdated_check_opt_tables.elements, *values_for_table[offset], 1); /* For updatable VIEW store rowid of the updated table and rowids of tables used in the CHECK OPTION condition. */ uint field_num= 0; List_iterator_fast<TABLE> tbl_it(unupdated_check_opt_tables); TABLE *tbl= table; do { if (tbl != table) tbl->file->position(tbl->record[0]); memcpy((char*) tmp_table->field[field_num]->ptr, (char*) tbl->file->ref, tbl->file->ref_length); field_num++; } while ((tbl= tbl_it++)); /* Write row, ignoring duplicated updates to a row */ error= tmp_table->file->write_row(tmp_table->record[0]); if (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE) Loading Loading @@ -1403,9 +1459,10 @@ void multi_update::send_error(uint errcode,const char *err) int multi_update::do_updates(bool from_send_error) { TABLE_LIST *cur_table; int local_error; int local_error= 0; ha_rows org_updated; TABLE *table, *tmp_table; List_iterator_fast<TABLE> check_opt_it(unupdated_check_opt_tables); DBUG_ENTER("do_updates"); do_update= 0; // Don't retry this function Loading @@ -1413,8 +1470,8 @@ int multi_update::do_updates(bool from_send_error) DBUG_RETURN(0); for (cur_table= update_tables; cur_table; cur_table= cur_table->next_local) { byte *ref_pos; bool can_compare_record; uint offset= cur_table->shared; table = cur_table->table; if (table == table_to_update) Loading @@ -1425,11 +1482,20 @@ int multi_update::do_updates(bool from_send_error) (void) table->file->ha_rnd_init(0); table->file->extra(HA_EXTRA_NO_CACHE); check_opt_it.rewind(); while(TABLE *tbl= check_opt_it++) { if (tbl->file->ha_rnd_init(1)) goto err; tbl->file->extra(HA_EXTRA_CACHE); } /* Setup copy functions to copy fields from temporary table */ List_iterator_fast<Item> field_it(*fields_for_table[cur_table->shared]); Field **field= tmp_table->field+1; // Skip row pointer List_iterator_fast<Item> field_it(*fields_for_table[offset]); Field **field= tmp_table->field + 1 + unupdated_check_opt_tables.elements; // Skip row pointers Copy_field *copy_field_ptr= copy_field, *copy_field_end; for ( ; *field ; field++) { Loading @@ -1444,7 +1510,6 @@ int multi_update::do_updates(bool from_send_error) can_compare_record= !(table->file->table_flags() & HA_PARTIAL_COLUMN_READ); ref_pos= (byte*) tmp_table->field[0]->ptr; for (;;) { if (thd->killed && trans_safe) Loading @@ -1457,8 +1522,19 @@ int multi_update::do_updates(bool from_send_error) continue; // May happen on dup key goto err; } if ((local_error= table->file->rnd_pos(table->record[0], ref_pos))) /* call rnd_pos() using rowids from temporary table */ check_opt_it.rewind(); TABLE *tbl= table; uint field_num= 0; do { if((local_error= tbl->file->rnd_pos(tbl->record[0], tmp_table->field[field_num]->ptr))) goto err; field_num++; } while((tbl= check_opt_it++)); table->status|= STATUS_UPDATED; store_record(table,record[1]); Loading Loading @@ -1508,6 +1584,10 @@ int multi_update::do_updates(bool from_send_error) } (void) table->file->ha_rnd_end(); (void) tmp_table->file->ha_rnd_end(); check_opt_it.rewind(); while (TABLE *tbl= check_opt_it++) tbl->file->ha_rnd_end(); } DBUG_RETURN(0); Loading @@ -1521,6 +1601,9 @@ int multi_update::do_updates(bool from_send_error) err2: (void) table->file->ha_rnd_end(); (void) tmp_table->file->ha_rnd_end(); check_opt_it.rewind(); while (TABLE *tbl= check_opt_it++) tbl->file->ha_rnd_end(); if (updated != org_updated) { Loading Loading
mysql-test/r/view.result +17 −0 Original line number Diff line number Diff line Loading @@ -3367,6 +3367,23 @@ SHOW CREATE VIEW v1; View Create View v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select cast(1.23456789 as decimal(8,0)) AS `col` DROP VIEW v1; CREATE TABLE t1 (a INT); CREATE TABLE t2 (b INT, c INT DEFAULT 0); INSERT INTO t1 (a) VALUES (1), (2); INSERT INTO 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 v1; b c 1 0 2 0 UPDATE v1 SET c=1 WHERE b=1; SELECT * FROM v1; b c 1 1 2 0 DROP VIEW v1; DROP TABLE t1,t2; CREATE TABLE t1 (id int); CREATE TABLE t2 (id int, c int DEFAULT 0); INSERT INTO t1 (id) VALUES (1); Loading
mysql-test/t/view.test +16 −0 Original line number Diff line number Diff line Loading @@ -3233,6 +3233,22 @@ CREATE VIEW v1 AS SELECT CAST(1.23456789 AS DECIMAL(8,0)) AS col; SHOW CREATE VIEW v1; DROP VIEW v1; # # Bug #28716: CHECK OPTION expression is evaluated over expired record buffers # when VIEW is updated via temporary tables # CREATE TABLE t1 (a INT); CREATE TABLE t2 (b INT, c INT DEFAULT 0); INSERT INTO t1 (a) VALUES (1), (2); INSERT INTO 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 v1; UPDATE v1 SET c=1 WHERE b=1; SELECT * FROM v1; DROP VIEW v1; DROP TABLE t1,t2; # # Bug #28561: update on multi-table view with CHECK OPTION and # a subquery in WHERE condition Loading
sql/sql_class.h +5 −0 Original line number Diff line number Diff line Loading @@ -2283,6 +2283,11 @@ class multi_update :public select_result_interceptor List <Item> *fields, *values; List <Item> **fields_for_table, **values_for_table; uint table_count; /* List of tables referenced in the CHECK OPTION condition of the updated view excluding the updated table. */ List <TABLE> unupdated_check_opt_tables; Copy_field *copy_field; enum enum_duplicates handle_duplicates; bool do_update, trans_safe; Loading
sql/sql_update.cc +112 −29 Original line number Diff line number Diff line Loading @@ -970,6 +970,7 @@ int multi_update::prepare(List<Item> ¬_used_values, List_iterator_fast<Item> field_it(*fields); List_iterator_fast<Item> value_it(*values); uint i, max_fields; uint leaf_table_count= 0; DBUG_ENTER("multi_update::prepare"); thd->count_cuted_fields= CHECK_FIELD_WARN; Loading Loading @@ -1003,6 +1004,7 @@ int multi_update::prepare(List<Item> ¬_used_values, { /* TODO: add support of view of join support */ TABLE *table=table_ref->table; leaf_table_count++; if (tables_to_update & table->map) { TABLE_LIST *tl= (TABLE_LIST*) thd->memdup((char*) table_ref, Loading Loading @@ -1067,7 +1069,7 @@ int multi_update::prepare(List<Item> ¬_used_values, /* Allocate copy fields */ max_fields=0; for (i=0 ; i < table_count ; i++) set_if_bigger(max_fields, fields_for_table[i]->elements); set_if_bigger(max_fields, fields_for_table[i]->elements + leaf_table_count); copy_field= new Copy_field[max_fields]; DBUG_RETURN(thd->is_fatal_error != 0); } Loading Loading @@ -1160,13 +1162,22 @@ multi_update::initialize_tables(JOIN *join) trans_safe= transactional_tables= main_table->file->has_transactions(); table_to_update= 0; /* Any update has at least one pair (field, value) */ DBUG_ASSERT(fields->elements); /* Only one table may be modified by UPDATE of an updatable view. For an updatable view first_table_for_update indicates this table. For a regular multi-update it refers to some updated table. */ TABLE *first_table_for_update= ((Item_field *) fields->head())->field->table; /* Create a temporary table for keys to all tables, except main table */ for (table_ref= update_tables; table_ref; table_ref= table_ref->next_local) { TABLE *table=table_ref->table; uint cnt= table_ref->shared; Item_field *ifield; List<Item> temp_fields= *fields_for_table[cnt]; List<Item> temp_fields; ORDER group; if (ignore) Loading @@ -1174,34 +1185,63 @@ multi_update::initialize_tables(JOIN *join) if (table == main_table) // First table in join { if (safe_update_on_fly(thd, join->join_tab, table_ref, all_tables, &temp_fields)) fields_for_table[cnt])) { table_to_update= main_table; // Update table on the fly continue; } } if (table == first_table_for_update && table_ref->check_option) { 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) { if (unupdated_tables & tbl_ref->table->map) unupdated_tables&= ~tbl_ref->table->map; else continue; if (unupdated_check_opt_tables.push_back(tbl_ref->table)) DBUG_RETURN(1); } } TMP_TABLE_PARAM *tmp_param= tmp_table_param+cnt; /* Create a temporary table to store all fields that are changed for this table. The first field in the temporary table is a pointer to the original row so that we can find and update it original row so that we can find and update it. For the updatable VIEW a few following fields are rowids of tables used in the CHECK OPTION condition. */ /* ok to be on stack as this is not referenced outside of this func */ Field_string offset(table->file->ref_length, 0, "offset", table, &my_charset_bin); List_iterator_fast<TABLE> tbl_it(unupdated_check_opt_tables); TABLE *tbl= table; do { Field_string *field= new Field_string(tbl->file->ref_length, 0, tbl->alias, tbl, &my_charset_bin); if (!field) DBUG_RETURN(1); /* The field will be converted to varstring when creating tmp table if table to be updated was created by mysql 4.1. Deny this. */ offset.can_alter_field_type= 0; if (!(ifield= new Item_field(((Field *) &offset)))) field->can_alter_field_type= 0; Item_field *ifield= new Item_field((Field *) field); if (!ifield) DBUG_RETURN(1); ifield->maybe_null= 0; if (temp_fields.push_front(ifield)) if (temp_fields.push_back(ifield)) DBUG_RETURN(1); } while ((tbl= tbl_it++)); temp_fields.concat(fields_for_table[cnt]); /* Make an unique key over the first field to avoid duplicated updates */ bzero((char*) &group, sizeof(group)); Loading Loading @@ -1350,10 +1390,26 @@ bool multi_update::send_data(List<Item> ¬_used_values) { int error; TABLE *tmp_table= tmp_tables[offset]; fill_record(thd, tmp_table->field+1, *values_for_table[offset], 1); /* Store pointer to row */ memcpy((char*) tmp_table->field[0]->ptr, (char*) table->file->ref, table->file->ref_length); /* Store regular updated fields in the row. */ fill_record(thd, tmp_table->field + 1 + unupdated_check_opt_tables.elements, *values_for_table[offset], 1); /* For updatable VIEW store rowid of the updated table and rowids of tables used in the CHECK OPTION condition. */ uint field_num= 0; List_iterator_fast<TABLE> tbl_it(unupdated_check_opt_tables); TABLE *tbl= table; do { if (tbl != table) tbl->file->position(tbl->record[0]); memcpy((char*) tmp_table->field[field_num]->ptr, (char*) tbl->file->ref, tbl->file->ref_length); field_num++; } while ((tbl= tbl_it++)); /* Write row, ignoring duplicated updates to a row */ error= tmp_table->file->write_row(tmp_table->record[0]); if (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE) Loading Loading @@ -1403,9 +1459,10 @@ void multi_update::send_error(uint errcode,const char *err) int multi_update::do_updates(bool from_send_error) { TABLE_LIST *cur_table; int local_error; int local_error= 0; ha_rows org_updated; TABLE *table, *tmp_table; List_iterator_fast<TABLE> check_opt_it(unupdated_check_opt_tables); DBUG_ENTER("do_updates"); do_update= 0; // Don't retry this function Loading @@ -1413,8 +1470,8 @@ int multi_update::do_updates(bool from_send_error) DBUG_RETURN(0); for (cur_table= update_tables; cur_table; cur_table= cur_table->next_local) { byte *ref_pos; bool can_compare_record; uint offset= cur_table->shared; table = cur_table->table; if (table == table_to_update) Loading @@ -1425,11 +1482,20 @@ int multi_update::do_updates(bool from_send_error) (void) table->file->ha_rnd_init(0); table->file->extra(HA_EXTRA_NO_CACHE); check_opt_it.rewind(); while(TABLE *tbl= check_opt_it++) { if (tbl->file->ha_rnd_init(1)) goto err; tbl->file->extra(HA_EXTRA_CACHE); } /* Setup copy functions to copy fields from temporary table */ List_iterator_fast<Item> field_it(*fields_for_table[cur_table->shared]); Field **field= tmp_table->field+1; // Skip row pointer List_iterator_fast<Item> field_it(*fields_for_table[offset]); Field **field= tmp_table->field + 1 + unupdated_check_opt_tables.elements; // Skip row pointers Copy_field *copy_field_ptr= copy_field, *copy_field_end; for ( ; *field ; field++) { Loading @@ -1444,7 +1510,6 @@ int multi_update::do_updates(bool from_send_error) can_compare_record= !(table->file->table_flags() & HA_PARTIAL_COLUMN_READ); ref_pos= (byte*) tmp_table->field[0]->ptr; for (;;) { if (thd->killed && trans_safe) Loading @@ -1457,8 +1522,19 @@ int multi_update::do_updates(bool from_send_error) continue; // May happen on dup key goto err; } if ((local_error= table->file->rnd_pos(table->record[0], ref_pos))) /* call rnd_pos() using rowids from temporary table */ check_opt_it.rewind(); TABLE *tbl= table; uint field_num= 0; do { if((local_error= tbl->file->rnd_pos(tbl->record[0], tmp_table->field[field_num]->ptr))) goto err; field_num++; } while((tbl= check_opt_it++)); table->status|= STATUS_UPDATED; store_record(table,record[1]); Loading Loading @@ -1508,6 +1584,10 @@ int multi_update::do_updates(bool from_send_error) } (void) table->file->ha_rnd_end(); (void) tmp_table->file->ha_rnd_end(); check_opt_it.rewind(); while (TABLE *tbl= check_opt_it++) tbl->file->ha_rnd_end(); } DBUG_RETURN(0); Loading @@ -1521,6 +1601,9 @@ int multi_update::do_updates(bool from_send_error) err2: (void) table->file->ha_rnd_end(); (void) tmp_table->file->ha_rnd_end(); check_opt_it.rewind(); while (TABLE *tbl= check_opt_it++) tbl->file->ha_rnd_end(); if (updated != org_updated) { Loading