Commit e921ac7a authored by evgen@moonbone.local's avatar evgen@moonbone.local

Bug#25123: ON DUPLICATE KEY clause allows fields not from the insert table.

When inserting into a join-based view the update fields from the ON DUPLICATE
KEY UPDATE wasn't checked to be from the table being inserted into and were
silently ignored.

The new check_view_single_update() function is added to check that
insert/update fields are being from the same single table of the view.
parent b8d55cc4
...@@ -325,3 +325,24 @@ select row_count(); ...@@ -325,3 +325,24 @@ select row_count();
row_count() row_count()
1 1
drop table t1; drop table t1;
create table t1 (f1 int unique, f2 int);
create table t2 (f3 int, f4 int);
create view v1 as select * from t1, t2 where f1= f3;
insert into t1 values (1,11), (2,22);
insert into t2 values (1,12), (2,24);
insert into v1 (f1) values (3) on duplicate key update f3= f3 + 10;
ERROR HY000: Can not modify more than one base table through a join view 'test.v1'
insert into v1 (f1) values (3) on duplicate key update f1= f3 + 10;
select * from t1;
f1 f2
1 11
2 22
3 NULL
insert into v1 (f1) values (3) on duplicate key update f1= f3 + 10;
select * from t1;
f1 f2
1 11
2 22
12 NULL
drop view v1;
drop table t1,t2;
...@@ -198,3 +198,21 @@ select row_count(); ...@@ -198,3 +198,21 @@ select row_count();
insert into t1 values (5, 5) on duplicate key update data= data + 10; insert into t1 values (5, 5) on duplicate key update data= data + 10;
select row_count(); select row_count();
drop table t1; drop table t1;
#
# Bug#25123: ON DUPLICATE KEY clause allows fields not from the insert table
#
create table t1 (f1 int unique, f2 int);
create table t2 (f3 int, f4 int);
create view v1 as select * from t1, t2 where f1= f3;
insert into t1 values (1,11), (2,22);
insert into t2 values (1,12), (2,24);
--error 1393
insert into v1 (f1) values (3) on duplicate key update f3= f3 + 10;
insert into v1 (f1) values (3) on duplicate key update f1= f3 + 10;
select * from t1;
insert into v1 (f1) values (3) on duplicate key update f1= f3 + 10;
select * from t1;
drop view v1;
drop table t1,t2;
...@@ -80,6 +80,65 @@ static bool check_view_insertability(THD *thd, TABLE_LIST *view); ...@@ -80,6 +80,65 @@ static bool check_view_insertability(THD *thd, TABLE_LIST *view);
#define my_safe_afree(ptr, size, min_length) if (size > min_length) my_free(ptr,MYF(0)) #define my_safe_afree(ptr, size, min_length) if (size > min_length) my_free(ptr,MYF(0))
#endif #endif
/*
Check that insert/update fields are from the same single table of a view.
SYNOPSIS
check_view_single_update()
fields The insert/update fields to be checked.
view The view for insert.
map [in/out] The insert table map.
DESCRIPTION
This function is called in 2 cases:
1. to check insert fields. In this case *map will be set to 0.
Insert fields are checked to be all from the same single underlying
table of the given view. Otherwise the error is thrown. Found table
map is returned in the map parameter.
2. to check update fields of the ON DUPLICATE KEY UPDATE clause.
In this case *map contains table_map found on the previous call of
the function to check insert fields. Update fields are checked to be
from the same table as the insert fields.
RETURN
0 OK
1 Error
*/
bool check_view_single_update(List<Item> &fields, TABLE_LIST *view,
table_map *map)
{
/* it is join view => we need to find the table for update */
List_iterator_fast<Item> it(fields);
Item *item;
TABLE_LIST *tbl= 0; // reset for call to check_single_table()
table_map tables= 0;
while ((item= it++))
tables|= item->used_tables();
/* Check found map against provided map */
if (*map)
{
if (tables != *map)
goto error;
return FALSE;
}
if (view->check_single_table(&tbl, tables, view) || tbl == 0)
goto error;
view->table= tbl->table;
*map= tables;
return FALSE;
error:
my_error(ER_VIEW_MULTIUPDATE, MYF(0),
view->view_db.str, view->view_name.str);
return TRUE;
}
/* /*
Check if insert fields are correct. Check if insert fields are correct.
...@@ -104,7 +163,7 @@ static bool check_view_insertability(THD *thd, TABLE_LIST *view); ...@@ -104,7 +163,7 @@ static bool check_view_insertability(THD *thd, TABLE_LIST *view);
static int check_insert_fields(THD *thd, TABLE_LIST *table_list, static int check_insert_fields(THD *thd, TABLE_LIST *table_list,
List<Item> &fields, List<Item> &values, List<Item> &fields, List<Item> &values,
bool check_unique) bool check_unique, table_map *map)
{ {
TABLE *table= table_list->table; TABLE *table= table_list->table;
...@@ -177,21 +236,9 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, ...@@ -177,21 +236,9 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list,
if (table_list->effective_algorithm == VIEW_ALGORITHM_MERGE) if (table_list->effective_algorithm == VIEW_ALGORITHM_MERGE)
{ {
/* it is join view => we need to find table for update */ if (check_view_single_update(fields, table_list, map))
List_iterator_fast<Item> it(fields);
Item *item;
TABLE_LIST *tbl= 0; // reset for call to check_single_table()
table_map map= 0;
while ((item= it++))
map|= item->used_tables();
if (table_list->check_single_table(&tbl, map, table_list) || tbl == 0)
{
my_error(ER_VIEW_MULTIUPDATE, MYF(0),
table_list->view_db.str, table_list->view_name.str);
return -1; return -1;
} table= table_list->table;
table_list->table= table= tbl->table;
} }
if (check_unique && thd->dupp_field) if (check_unique && thd->dupp_field)
...@@ -241,7 +288,7 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, ...@@ -241,7 +288,7 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list,
*/ */
static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list, static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list,
List<Item> &update_fields) List<Item> &update_fields, table_map *map)
{ {
TABLE *table= insert_table_list->table; TABLE *table= insert_table_list->table;
query_id_t timestamp_query_id; query_id_t timestamp_query_id;
...@@ -264,6 +311,10 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list, ...@@ -264,6 +311,10 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list,
if (setup_fields(thd, 0, update_fields, 1, 0, 0)) if (setup_fields(thd, 0, update_fields, 1, 0, 0))
return -1; return -1;
if (insert_table_list->effective_algorithm == VIEW_ALGORITHM_MERGE &&
check_view_single_update(update_fields, insert_table_list, map))
return -1;
if (table->timestamp_field) if (table->timestamp_field)
{ {
/* Don't set timestamp column if this is modified. */ /* Don't set timestamp column if this is modified. */
...@@ -910,6 +961,7 @@ bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, ...@@ -910,6 +961,7 @@ bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list,
Name_resolution_context_state ctx_state; Name_resolution_context_state ctx_state;
bool insert_into_view= (table_list->view != 0); bool insert_into_view= (table_list->view != 0);
bool res= 0; bool res= 0;
table_map map= 0;
DBUG_ENTER("mysql_prepare_insert"); DBUG_ENTER("mysql_prepare_insert");
DBUG_PRINT("enter", ("table_list 0x%lx, table 0x%lx, view %d", DBUG_PRINT("enter", ("table_list 0x%lx, table 0x%lx, view %d",
(ulong)table_list, (ulong)table, (ulong)table_list, (ulong)table,
...@@ -959,12 +1011,12 @@ bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, ...@@ -959,12 +1011,12 @@ bool mysql_prepare_insert(THD *thd, TABLE_LIST *table_list,
/* Prepare the fields in the statement. */ /* Prepare the fields in the statement. */
if (values && if (values &&
!(res= check_insert_fields(thd, context->table_list, fields, *values, !(res= check_insert_fields(thd, context->table_list, fields, *values,
!insert_into_view) || !insert_into_view, &map) ||
setup_fields(thd, 0, *values, 0, 0, 0)) && setup_fields(thd, 0, *values, 0, 0, 0)) &&
duplic == DUP_UPDATE) duplic == DUP_UPDATE)
{ {
select_lex->no_wrap_view_item= TRUE; select_lex->no_wrap_view_item= TRUE;
res= check_update_fields(thd, context->table_list, update_fields); res= check_update_fields(thd, context->table_list, update_fields, &map);
select_lex->no_wrap_view_item= FALSE; select_lex->no_wrap_view_item= FALSE;
/* /*
When we are not using GROUP BY we can refer to other tables in the When we are not using GROUP BY we can refer to other tables in the
...@@ -2286,6 +2338,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) ...@@ -2286,6 +2338,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
{ {
LEX *lex= thd->lex; LEX *lex= thd->lex;
int res; int res;
table_map map= 0;
SELECT_LEX *lex_current_select_save= lex->current_select; SELECT_LEX *lex_current_select_save= lex->current_select;
DBUG_ENTER("select_insert::prepare"); DBUG_ENTER("select_insert::prepare");
...@@ -2297,7 +2350,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) ...@@ -2297,7 +2350,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
*/ */
lex->current_select= &lex->select_lex; lex->current_select= &lex->select_lex;
res= check_insert_fields(thd, table_list, *fields, values, res= check_insert_fields(thd, table_list, *fields, values,
!insert_into_view) || !insert_into_view, &map) ||
setup_fields(thd, 0, values, 0, 0, 0); setup_fields(thd, 0, values, 0, 0, 0);
if (info.handle_duplicates == DUP_UPDATE) if (info.handle_duplicates == DUP_UPDATE)
...@@ -2315,7 +2368,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) ...@@ -2315,7 +2368,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
lex->select_lex.no_wrap_view_item= TRUE; lex->select_lex.no_wrap_view_item= TRUE;
res= res || check_update_fields(thd, context->table_list, res= res || check_update_fields(thd, context->table_list,
*info.update_fields); *info.update_fields, &map);
lex->select_lex.no_wrap_view_item= FALSE; lex->select_lex.no_wrap_view_item= FALSE;
/* /*
When we are not using GROUP BY we can refer to other tables in the When we are not using GROUP BY we can refer to other tables in the
......
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