Commit 1d166b17 authored by unknown's avatar unknown

Proposed fix for bug #17764 "Trigger crashes MyISAM table"

A table with an on insert trigger was reported as crashed when the insert
was processed with bulk insert mode on (handler::start_bulk_insert).
The trigger was also selecting from the same table, and that caused
the "crash".
The same problem was present when an insert statement, which was processed
in bulk mode, also used a stored function that was reading the same table.

This fix disables bulk inserts if a statement uses functions or invokes
triggers. Implementing more granular checks will require much more code and
therefore can hardly be done in 5.0


mysql-test/r/trigger.result:
  Added test bug #17764 "Trigger crashes MyISAM table".
mysql-test/t/trigger.test:
  Added test bug #17764 "Trigger crashes MyISAM table".
sql/sql_insert.cc:
  We should not start bulk inserts for INSERT (or similar) statement if it uses
  functions or invokes triggers since they may access to the same table and
  therefore should not see its inconsistent state created by this optimization.
sql/sql_load.cc:
  We should not start bulk inserts for INSERT (or similar) statement if it uses
  functions or invokes triggers since they may access to the same table and
  therefore should not see its inconsistent state created by this optimization.
parent cfcc1dc6
......@@ -2,6 +2,7 @@ drop table if exists t1, t2, t3, t4;
drop view if exists v1;
drop database if exists mysqltest;
drop function if exists f1;
drop function if exists f2;
drop procedure if exists p1;
create table t1 (i int);
create trigger trg before insert on t1 for each row set @a:=1;
......@@ -897,3 +898,26 @@ create trigger t1_bi before insert on t1 for each row return 0;
ERROR 42000: RETURN is only allowed in a FUNCTION
insert into t1 values (1);
drop table t1;
create table t1 (a varchar(64), b int);
create table t2 like t1;
create trigger t1_ai after insert on t1 for each row
set @a:= (select max(a) from t1);
insert into t1 (a) values
("Twas"),("brillig"),("and"),("the"),("slithy"),("toves"),
("Did"),("gyre"),("and"),("gimble"),("in"),("the"),("wabe");
create trigger t2_ai after insert on t2 for each row
set @a:= (select max(a) from t2);
insert into t2 select * from t1;
load data infile '../std_data_ln/words.dat' into table t1 (a);
drop trigger t1_ai;
drop trigger t2_ai;
create function f1() returns int return (select max(b) from t1);
insert into t1 values
("All",f1()),("mimsy",f1()),("were",f1()),("the",f1()),("borogoves",f1()),
("And",f1()),("the",f1()),("mome", f1()),("raths",f1()),("outgrabe",f1());
create function f2() returns int return (select max(b) from t2);
insert into t2 select a, f2() from t1;
load data infile '../std_data_ln/words.dat' into table t1 (a) set b:= f1();
drop table t1;
drop function f1;
drop function f2;
......@@ -7,6 +7,7 @@ drop table if exists t1, t2, t3, t4;
drop view if exists v1;
drop database if exists mysqltest;
drop function if exists f1;
drop function if exists f2;
drop procedure if exists p1;
--enable_warnings
......@@ -1057,3 +1058,36 @@ create table t1 (i int);
create trigger t1_bi before insert on t1 for each row return 0;
insert into t1 values (1);
drop table t1;
# Test for bug #17764 "Trigger crashes MyISAM table"
#
# Table was reported as crashed when it was subject table of trigger invoked
# by insert statement which was executed with enabled bulk insert mode (which
# is actually set of optimizations enabled by handler::start_bulk_insert())
# and this trigger also explicitly referenced it.
# The same problem arose when table to which bulk insert was done was also
# referenced in function called by insert statement.
create table t1 (a varchar(64), b int);
create table t2 like t1;
create trigger t1_ai after insert on t1 for each row
set @a:= (select max(a) from t1);
insert into t1 (a) values
("Twas"),("brillig"),("and"),("the"),("slithy"),("toves"),
("Did"),("gyre"),("and"),("gimble"),("in"),("the"),("wabe");
create trigger t2_ai after insert on t2 for each row
set @a:= (select max(a) from t2);
insert into t2 select * from t1;
load data infile '../std_data_ln/words.dat' into table t1 (a);
drop trigger t1_ai;
drop trigger t2_ai;
# Test that the problem for functions is fixed as well
create function f1() returns int return (select max(b) from t1);
insert into t1 values
("All",f1()),("mimsy",f1()),("were",f1()),("the",f1()),("borogoves",f1()),
("And",f1()),("the",f1()),("mome", f1()),("raths",f1()),("outgrabe",f1());
create function f2() returns int return (select max(b) from t2);
insert into t2 select a, f2() from t1;
load data infile '../std_data_ln/words.dat' into table t1 (a) set b:= f1();
drop table t1;
drop function f1;
drop function f2;
......@@ -404,11 +404,15 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
let's *try* to start bulk inserts. It won't necessary
start them as values_list.elements should be greater than
some - handler dependent - threshold.
We should not start bulk inserts if this statement uses
functions or invokes triggers since they may access
to the same table and therefore should not see its
inconsistent state created by this optimization.
So we call start_bulk_insert to perform nesessary checks on
values_list.elements, and - if nothing else - to initialize
the code to make the call of end_bulk_insert() below safe.
*/
if (lock_type != TL_WRITE_DELAYED)
if (lock_type != TL_WRITE_DELAYED && !thd->prelocked_mode)
table->file->start_bulk_insert(values_list.elements);
thd->no_trans_update= 0;
......@@ -534,7 +538,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
else
#endif
{
if (table->file->end_bulk_insert() && !error)
if (!thd->prelocked_mode && table->file->end_bulk_insert() && !error)
{
table->file->print_error(my_errno,MYF(0));
error=1;
......@@ -2181,7 +2185,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
lex->current_select->options|= OPTION_BUFFER_RESULT;
lex->current_select->join->select_options|= OPTION_BUFFER_RESULT;
}
else
else if (!thd->prelocked_mode)
{
/*
We must not yet prepare the result table if it is the same as one of the
......@@ -2189,6 +2193,8 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
indexes on the result table, which may be used during the select, if it
is the same table (Bug #6034). Do the preparation after the select phase
in select_insert::prepare2().
We won't start bulk inserts at all if this statement uses functions or
should invoke triggers since they may access to the same table too.
*/
table->file->start_bulk_insert((ha_rows) 0);
}
......@@ -2229,7 +2235,8 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
int select_insert::prepare2(void)
{
DBUG_ENTER("select_insert::prepare2");
if (thd->lex->current_select->options & OPTION_BUFFER_RESULT)
if (thd->lex->current_select->options & OPTION_BUFFER_RESULT &&
!thd->prelocked_mode)
table->file->start_bulk_insert((ha_rows) 0);
DBUG_RETURN(0);
}
......@@ -2332,6 +2339,7 @@ void select_insert::send_error(uint errcode,const char *err)
*/
DBUG_VOID_RETURN;
}
if (!thd->prelocked_mode)
table->file->end_bulk_insert();
/*
If at least one row has been inserted/modified and will stay in the table
......@@ -2367,7 +2375,7 @@ bool select_insert::send_eof()
int error,error2;
DBUG_ENTER("select_insert::send_eof");
error=table->file->end_bulk_insert();
error= (!thd->prelocked_mode) ? table->file->end_bulk_insert():0;
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
/*
......@@ -2450,6 +2458,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
thd->cuted_fields=0;
if (info.ignore || info.handle_duplicates != DUP_ERROR)
table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
if (!thd->prelocked_mode)
table->file->start_bulk_insert((ha_rows) 0);
thd->no_trans_update= 0;
thd->abort_on_warning= (!info.ignore &&
......
......@@ -356,6 +356,7 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
if (ignore ||
handle_duplicates == DUP_REPLACE)
table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
if (!thd->prelocked_mode)
table->file->start_bulk_insert((ha_rows) 0);
table->copy_blobs=1;
......@@ -373,7 +374,7 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
error= read_sep_field(thd, info, table_list, fields_vars,
set_fields, set_values, read_info,
*enclosed, skip_lines, ignore);
if (table->file->end_bulk_insert() && !error)
if (!thd->prelocked_mode && table->file->end_bulk_insert() && !error)
{
table->file->print_error(my_errno, MYF(0));
error= 1;
......
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