Commit 9f9893c9 authored by unknown's avatar unknown

fixed detection of updating table on which we select (BUG#6032)


mysql-test/r/view.result:
  Trys update table from which we select using views and subqueries
mysql-test/t/view.test:
  Trys update table from which we select using views and subqueries
sql/sql_acl.cc:
  fix of fix for bug BUG#5976
sql/sql_base.cc:
  protection against temporary tables which have not table->table->table_cache_key
  fixed unique check to skip the same table instences
sql/sql_delete.cc:
  removed next_independent to allow to check VIEW subqueries
sql/sql_insert.cc:
  removed next_independent to allow to check VIEW subqueries
sql/sql_parse.cc:
  removed next_independent to allow to check VIEW subqueries
sql/sql_update.cc:
  removed next_independent to allow to check VIEW subqueries
sql/sql_view.cc:
  removed next_independent to allow to check VIEW subqueries
  optimisation to mark as non-updatable views with subqueries by same table.
sql/table.h:
  removed next_independent to allow to check VIEW subqueries
parent 4f410c4b
...@@ -1633,3 +1633,16 @@ use mysqltest; ...@@ -1633,3 +1633,16 @@ use mysqltest;
create view v1 as select * from t1; create view v1 as select * from t1;
revoke all privileges on mysqltest.* from mysqltest_1@localhost; revoke all privileges on mysqltest.* from mysqltest_1@localhost;
drop database mysqltest; drop database mysqltest;
create table t1 (s1 smallint);
create view v1 as select * from t1 where 20 < (select (s1) from t1);
insert into v1 values (30);
ERROR HY000: The target table v1 of the INSERT is not updatable
create view v2 as select * from t1;
create view v3 as select * from t1 where 20 < (select (s1) from v2);
insert into v3 values (30);
ERROR HY000: The target table v3 of the INSERT is not updatable
create view v4 as select * from v2 where 20 < (select (s1) from t1);
insert into v4 values (30);
ERROR HY000: You can't specify target table 'v4' for update in FROM clause
drop view v4, v3, v2, v1;
drop table t1;
...@@ -1568,7 +1568,23 @@ connection user1; ...@@ -1568,7 +1568,23 @@ connection user1;
use mysqltest; use mysqltest;
create view v1 as select * from t1; create view v1 as select * from t1;
connection root; connection root;
revoke all privileges on mysqltest.* from mysqltest_1@localhost; revoke all privileges on mysqltest.* from mysqltest_1@localhost;
drop database mysqltest; drop database mysqltest;
#
# Trys update table from which we select using views and subqueries
#
create table t1 (s1 smallint);
create view v1 as select * from t1 where 20 < (select (s1) from t1);
-- error 1288
insert into v1 values (30);
create view v2 as select * from t1;
create view v3 as select * from t1 where 20 < (select (s1) from v2);
-- error 1288
insert into v3 values (30);
create view v4 as select * from v2 where 20 < (select (s1) from t1);
-- error 1093
insert into v4 values (30);
drop view v4, v3, v2, v1;
drop table t1;
...@@ -3886,13 +3886,19 @@ int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr) ...@@ -3886,13 +3886,19 @@ int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr)
void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant,
const char *db, const char *table) const char *db, const char *table)
{ {
/* --skip-grants */
if (!initialized)
{
grant->privilege= ~NO_ACCESS; // everything is allowed
return;
}
/* global privileges */ /* global privileges */
grant->privilege= thd->master_access; grant->privilege= thd->master_access;
/* db privileges */ /* db privileges */
grant->privilege|= acl_get(thd->host, thd->ip, thd->priv_user, db, 0); grant->privilege|= acl_get(thd->host, thd->ip, thd->priv_user, db, 0);
/* if privileges ignored (--skip-grant-tables) above is enough */
if (!grant_option) if (!grant_option)
return; return;
......
...@@ -593,7 +593,8 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table, ...@@ -593,7 +593,8 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table,
{ {
if ((!strcmp(table->db, db_name) && if ((!strcmp(table->db, db_name) &&
!strcmp(table->real_name, table_name)) || !strcmp(table->real_name, table_name)) ||
(table->view && (table->view && // it is VIEW and
table->table->table_cache_key && // it is not temporary table
!strcmp(table->table->table_cache_key, db_name) && !strcmp(table->table->table_cache_key, db_name) &&
!strcmp(table->table->table_name, table_name))) !strcmp(table->table->table_name, table_name)))
break; break;
...@@ -618,6 +619,8 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table, ...@@ -618,6 +619,8 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table,
TABLE_LIST* unique_table(TABLE_LIST *table, TABLE_LIST *table_list) TABLE_LIST* unique_table(TABLE_LIST *table, TABLE_LIST *table_list)
{ {
DBUG_ENTER("unique_table");
DBUG_PRINT("enter", ("table alias: %s", table->alias));
TABLE_LIST *res; TABLE_LIST *res;
const char *d_name= table->db, *t_name= table->real_name; const char *d_name= table->db, *t_name= table->real_name;
char d_name_buff[MAX_ALIAS_NAME], t_name_buff[MAX_ALIAS_NAME]; char d_name_buff[MAX_ALIAS_NAME], t_name_buff[MAX_ALIAS_NAME];
...@@ -644,13 +647,18 @@ TABLE_LIST* unique_table(TABLE_LIST *table, TABLE_LIST *table_list) ...@@ -644,13 +647,18 @@ TABLE_LIST* unique_table(TABLE_LIST *table, TABLE_LIST *table_list)
return 0; return 0;
} }
} }
if ((res= find_table_in_global_list(table_list, d_name, t_name)) &&
res->table && res->table == table->table) DBUG_PRINT("info", ("real table: %s.%s", d_name, t_name));
for(;;)
{ {
// we found entry of this table try again. if (!(res= find_table_in_global_list(table_list, d_name, t_name)) ||
return find_table_in_global_list(res->next_global, d_name, t_name); !res->table || res->table != table->table)
break;
/* if we found entry of this table try again. */
table_list= res->next_global;
DBUG_PRINT("info", ("found same copy of table"));
} }
return res; DBUG_RETURN(res);
} }
......
...@@ -293,7 +293,7 @@ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds) ...@@ -293,7 +293,7 @@ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds)
my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "DELETE"); my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "DELETE");
DBUG_RETURN(-1); DBUG_RETURN(-1);
} }
if (unique_table(table_list, table_list->next_independent())) if (unique_table(table_list, table_list->next_global))
{ {
my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->real_name); my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->real_name);
DBUG_RETURN(-1); DBUG_RETURN(-1);
......
...@@ -641,7 +641,7 @@ int mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, TABLE *table, ...@@ -641,7 +641,7 @@ int mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, TABLE *table,
setup_fields(thd, 0, table_list, update_values, 0, 0, 0)))) setup_fields(thd, 0, table_list, update_values, 0, 0, 0))))
DBUG_RETURN(-1); DBUG_RETURN(-1);
if (unique_table(table_list, table_list->next_independent())) if (unique_table(table_list, table_list->next_global))
{ {
my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->real_name); my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->real_name);
DBUG_RETURN(-1); DBUG_RETURN(-1);
......
...@@ -2810,7 +2810,7 @@ unsent_create_error: ...@@ -2810,7 +2810,7 @@ unsent_create_error:
Is table which we are changing used somewhere in other parts of Is table which we are changing used somewhere in other parts of
query query
*/ */
if (unique_table(first_table, all_tables->next_independent())) if (unique_table(first_table, all_tables->next_global))
{ {
/* Using same table for INSERT and SELECT */ /* Using same table for INSERT and SELECT */
select_lex->options |= OPTION_BUFFER_RESULT; select_lex->options |= OPTION_BUFFER_RESULT;
......
...@@ -529,7 +529,7 @@ int mysql_prepare_update(THD *thd, TABLE_LIST *table_list, ...@@ -529,7 +529,7 @@ int mysql_prepare_update(THD *thd, TABLE_LIST *table_list,
DBUG_RETURN(-1); DBUG_RETURN(-1);
/* Check that we are not using table that we are updating in a sub select */ /* Check that we are not using table that we are updating in a sub select */
if (unique_table(table_list, table_list->next_independent())) if (unique_table(table_list, table_list->next_global))
{ {
my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->real_name); my_error(ER_UPDATE_TABLE_USED, MYF(0), table_list->real_name);
DBUG_RETURN(-1); DBUG_RETURN(-1);
......
...@@ -382,6 +382,7 @@ static LEX_STRING view_file_type[]= {{(char*)"VIEW", 4}}; ...@@ -382,6 +382,7 @@ static LEX_STRING view_file_type[]= {{(char*)"VIEW", 4}};
static int mysql_register_view(THD *thd, TABLE_LIST *view, static int mysql_register_view(THD *thd, TABLE_LIST *view,
enum_view_create_mode mode) enum_view_create_mode mode)
{ {
LEX *lex= thd->lex;
char buff[4096]; char buff[4096];
String str(buff,(uint32) sizeof(buff), system_charset_info); String str(buff,(uint32) sizeof(buff), system_charset_info);
char md5[33]; char md5[33];
...@@ -395,7 +396,7 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, ...@@ -395,7 +396,7 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view,
{ {
ulong sql_mode= thd->variables.sql_mode & MODE_ANSI_QUOTES; ulong sql_mode= thd->variables.sql_mode & MODE_ANSI_QUOTES;
thd->variables.sql_mode&= ~MODE_ANSI_QUOTES; thd->variables.sql_mode&= ~MODE_ANSI_QUOTES;
thd->lex->unit.print(&str); lex->unit.print(&str);
thd->variables.sql_mode|= sql_mode; thd->variables.sql_mode|= sql_mode;
} }
str.append('\0'); str.append('\0');
...@@ -474,21 +475,21 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, ...@@ -474,21 +475,21 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view,
view->calc_md5(md5); view->calc_md5(md5);
view->md5.str= md5; view->md5.str= md5;
view->md5.length= 32; view->md5.length= 32;
can_be_merged= thd->lex->can_be_merged(); can_be_merged= lex->can_be_merged();
if (thd->lex->create_view_algorithm == VIEW_ALGORITHM_MERGE && if (lex->create_view_algorithm == VIEW_ALGORITHM_MERGE &&
!thd->lex->can_be_merged()) !lex->can_be_merged())
{ {
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_VIEW_MERGE, push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_VIEW_MERGE,
ER(ER_WARN_VIEW_MERGE)); ER(ER_WARN_VIEW_MERGE));
thd->lex->create_view_algorithm= VIEW_ALGORITHM_UNDEFINED; lex->create_view_algorithm= VIEW_ALGORITHM_UNDEFINED;
} }
view->algorithm= thd->lex->create_view_algorithm; view->algorithm= lex->create_view_algorithm;
view->with_check= thd->lex->create_view_check; view->with_check= lex->create_view_check;
if ((view->updatable_view= (can_be_merged && if ((view->updatable_view= (can_be_merged &&
view->algorithm != VIEW_ALGORITHM_TMPTABLE))) view->algorithm != VIEW_ALGORITHM_TMPTABLE)))
{ {
/* TODO: change here when we will support UNIONs */ /* TODO: change here when we will support UNIONs */
for (TABLE_LIST *tbl= (TABLE_LIST *)thd->lex->select_lex.table_list.first; for (TABLE_LIST *tbl= (TABLE_LIST *)lex->select_lex.table_list.first;
tbl; tbl;
tbl= tbl->next_local) tbl= tbl->next_local)
{ {
...@@ -500,6 +501,26 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view, ...@@ -500,6 +501,26 @@ static int mysql_register_view(THD *thd, TABLE_LIST *view,
} }
} }
/*
Check that table of main select do not used in subqueries.
This test can catch only very simple cases of such non-updateable views,
all other will be detected before updating commands execution.
(it is more optimisation then real check)
NOTE: this skip cases of using table via VIEWs, joined VIEWs, VIEWs with
UNION
*/
if (view->updatable_view &&
!lex->select_lex.next_select() &&
!((TABLE_LIST*)lex->select_lex.table_list.first)->next_local &&
find_table_in_global_list(lex->query_tables->next_global,
lex->query_tables->db,
lex->query_tables->real_name))
{
view->updatable_view= 0;
}
if (view->with_check != VIEW_CHECK_NONE && if (view->with_check != VIEW_CHECK_NONE &&
!view->updatable_view) !view->updatable_view)
{ {
...@@ -698,13 +719,12 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table) ...@@ -698,13 +719,12 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table)
tables just after VIEW instead of tail of list, to be able check that tables just after VIEW instead of tail of list, to be able check that
table is unique. Also we store old next table for the same purpose. table is unique. Also we store old next table for the same purpose.
*/ */
table->old_next= table->next_global;
if (view_tables) if (view_tables)
{ {
if (table->next_global) if (table->next_global)
{ {
view_tables_tail->next_global= table->next_global;
table->next_global->prev_global= &view_tables_tail->next_global; table->next_global->prev_global= &view_tables_tail->next_global;
view_tables_tail->next_global= table->old_next;
} }
else else
{ {
......
...@@ -232,8 +232,6 @@ typedef struct st_table_list ...@@ -232,8 +232,6 @@ typedef struct st_table_list
st_table_list *ancestor; st_table_list *ancestor;
/* most upper view this table belongs to */ /* most upper view this table belongs to */
st_table_list *belong_to_view; st_table_list *belong_to_view;
/* next_global before adding VIEW tables */
st_table_list *old_next;
Item *where; /* VIEW WHERE clause condition */ Item *where; /* VIEW WHERE clause condition */
Item *check_option; /* WITH CHECK OPTION condition */ Item *check_option; /* WITH CHECK OPTION condition */
LEX_STRING query; /* text of (CRETE/SELECT) statement */ LEX_STRING query; /* text of (CRETE/SELECT) statement */
...@@ -286,12 +284,6 @@ typedef struct st_table_list ...@@ -286,12 +284,6 @@ typedef struct st_table_list
bool setup_ancestor(THD *thd, Item **conds, uint8 check_option); bool setup_ancestor(THD *thd, Item **conds, uint8 check_option);
bool placeholder() {return derived || view; } bool placeholder() {return derived || view; }
void print(THD *thd, String *str); void print(THD *thd, String *str);
inline st_table_list *next_independent()
{
if (view)
return old_next;
return next_global;
}
} TABLE_LIST; } TABLE_LIST;
class Item; class Item;
......
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