Commit e08a2b32 authored by unknown's avatar unknown

Fix for bug#13479 "REPLACE activates UPDATE trigger, not the DELETE and

INSERT triggers".

In cases when REPLACE was internally executed via update and table had
on update (on delete) triggers defined we exposed the fact that such
optimization used by callng on update (not calling on delete) triggers.
Such behavior contradicts our documentation which describes REPLACE as
INSERT with optional DELETE.

This fix just disables this optimization for tables with on delete triggers.
The optimization is still applied for tables which have on update but have
no on delete triggers, we just don't invoke on update triggers in this case
and thus don't expose information about optimization to user.

Also added test coverage for values returned by ROW_COUNT() function (and
thus for values returned by mysql_affected_rows()) for various forms of
INSERT.


mysql-test/r/insert.result:
  Added test for values returned by ROW_COUNT() function (and thus for values
  returned by mysql_affected_rows()) for various forms of INSERT. We didn't
  have coverage for this before and since this fix touches related code it is
  better to add it now.
mysql-test/r/trigger.result:
  Adjusted test after fixing bug#13479 "REPLACE activates UPDATE trigger, not
  the DELETE and INSERT triggers".
mysql-test/t/insert.test:
  Added test for values returned by ROW_COUNT() function (and thus for values
  returned by mysql_affected_rows()) for various forms of INSERT. We didn't
  have coverage for this before and since this fix touches related code it is
  better to add it now.
mysql-test/t/trigger.test:
  Adjusted test after fixing bug#13479 "REPLACE activates UPDATE trigger, not
  the DELETE and INSERT triggers".
sql/sql_insert.cc:
  write_record():
    We should not expose that internally we sometimes execute REPLACE
    via UPDATE instead of documented INSERT + DELETE pair. So we should not
    use this optimization for tables with on delete triggers. OTOH it is ok
    to use it for tables which have on update but have no on delete triggers,
    we just should not invoke on update triggers in this case.
parent 9b871930
...@@ -305,3 +305,29 @@ insert delayed into v1 values (1); ...@@ -305,3 +305,29 @@ insert delayed into v1 values (1);
ERROR HY000: 'test.v1' is not BASE TABLE ERROR HY000: 'test.v1' is not BASE TABLE
drop table t1; drop table t1;
drop view v1; drop view v1;
create table t1 (id int primary key, data int);
insert into t1 values (1, 1), (2, 2), (3, 3);
select row_count();
row_count()
3
insert ignore into t1 values (1, 1);
select row_count();
row_count()
0
replace into t1 values (1, 11);
select row_count();
row_count()
2
replace into t1 values (4, 4);
select row_count();
row_count()
1
insert into t1 values (2, 2) on duplicate key update data= data + 10;
select row_count();
row_count()
2
insert into t1 values (5, 5) on duplicate key update data= data + 10;
select row_count();
row_count()
1
drop table t1;
...@@ -169,21 +169,22 @@ select @log; ...@@ -169,21 +169,22 @@ select @log;
@log @log
(BEFORE_INSERT: new=(id=1, data=2)) (BEFORE_INSERT: new=(id=1, data=2))
set @log:= ""; set @log:= "";
replace t1 values (1, 3), (2, 2); insert into t1 (id, data) values (1, 3), (2, 2) on duplicate key update data= data + 1;
select @log; select @log;
@log @log
(BEFORE_INSERT: new=(id=1, data=3))(BEFORE_UPDATE: old=(id=1, data=1) new=(id=1, data=3))(AFTER_UPDATE: old=(id=1, data=1) new=(id=1, data=3))(BEFORE_INSERT: new=(id=2, data=2))(AFTER_INSERT: new=(id=2, data=2)) (BEFORE_INSERT: new=(id=1, data=3))(BEFORE_UPDATE: old=(id=1, data=1) new=(id=1, data=2))(AFTER_UPDATE: old=(id=1, data=1) new=(id=1, data=2))(BEFORE_INSERT: new=(id=2, data=2))(AFTER_INSERT: new=(id=2, data=2))
alter table t1 add ts timestamp default now();
set @log:= ""; set @log:= "";
replace t1 (id, data) values (1, 4); replace t1 values (1, 4), (3, 3);
select @log; select @log;
@log @log
(BEFORE_INSERT: new=(id=1, data=4))(BEFORE_DELETE: old=(id=1, data=3))(AFTER_DELETE: old=(id=1, data=3))(AFTER_INSERT: new=(id=1, data=4)) (BEFORE_INSERT: new=(id=1, data=4))(BEFORE_DELETE: old=(id=1, data=2))(AFTER_DELETE: old=(id=1, data=2))(AFTER_INSERT: new=(id=1, data=4))(BEFORE_INSERT: new=(id=3, data=3))(AFTER_INSERT: new=(id=3, data=3))
drop trigger t1_bd;
drop trigger t1_ad;
set @log:= ""; set @log:= "";
insert into t1 (id, data) values (1, 5), (3, 3) on duplicate key update data= data + 2; replace t1 values (1, 5);
select @log; select @log;
@log @log
(BEFORE_INSERT: new=(id=1, data=5))(BEFORE_UPDATE: old=(id=1, data=4) new=(id=1, data=6))(AFTER_UPDATE: old=(id=1, data=4) new=(id=1, data=6))(BEFORE_INSERT: new=(id=3, data=3))(AFTER_INSERT: new=(id=3, data=3)) (BEFORE_INSERT: new=(id=1, data=5))(AFTER_INSERT: new=(id=1, data=5))
drop table t1; drop table t1;
create table t1 (id int primary key, data varchar(10), fk int); create table t1 (id int primary key, data varchar(10), fk int);
create table t2 (event varchar(100)); create table t2 (event varchar(100));
...@@ -493,15 +494,9 @@ select * from t1; ...@@ -493,15 +494,9 @@ select * from t1;
i k i k
3 13 3 13
replace into t1 values (3, 3); replace into t1 values (3, 3);
ERROR 42S22: Unknown column 'at' in 'NEW'
select * from t1;
i k
3 3
alter table t1 add ts timestamp default now();
replace into t1 (i, k) values (3, 13);
ERROR 42S22: Unknown column 'at' in 'OLD' ERROR 42S22: Unknown column 'at' in 'OLD'
select * from t1; select * from t1;
i k ts i k
drop table t1, t2; drop table t1, t2;
create table t1 (i int, bt int, k int, key(k)) engine=myisam; create table t1 (i int, bt int, k int, key(k)) engine=myisam;
create table t2 (i int); create table t2 (i int);
...@@ -574,18 +569,11 @@ i k ...@@ -574,18 +569,11 @@ i k
1 1 1 1
2 2 2 2
replace into t1 values (2, 4); replace into t1 values (2, 4);
ERROR 42S22: Unknown column 'bt' in 'NEW' ERROR 42S22: Unknown column 'bt' in 'OLD'
select * from t1; select * from t1;
i k i k
1 1 1 1
2 2 2 2
alter table t1 add ts timestamp default now();
replace into t1 (i, k) values (2, 11);
ERROR 42S22: Unknown column 'bt' in 'OLD'
select * from t1;
i k ts
1 1 0000-00-00 00:00:00
2 2 0000-00-00 00:00:00
drop table t1, t2; drop table t1, t2;
drop function if exists bug5893; drop function if exists bug5893;
create table t1 (col1 int, col2 int); create table t1 (col1 int, col2 int);
......
...@@ -187,3 +187,26 @@ create view v1 as select * from t1; ...@@ -187,3 +187,26 @@ create view v1 as select * from t1;
insert delayed into v1 values (1); insert delayed into v1 values (1);
drop table t1; drop table t1;
drop view v1; drop view v1;
#
# Test for values returned by ROW_COUNT() function
# (and thus for values returned by mysql_affected_rows())
# for various forms of INSERT
#
create table t1 (id int primary key, data int);
insert into t1 values (1, 1), (2, 2), (3, 3);
select row_count();
insert ignore into t1 values (1, 1);
select row_count();
# Reports that 2 rows are affected (1 deleted + 1 inserted)
replace into t1 values (1, 11);
select row_count();
replace into t1 values (4, 4);
select row_count();
# Reports that 2 rows are affected. This conforms to documentation.
# (Useful for differentiating inserts from updates).
insert into t1 values (2, 2) on duplicate key update data= data + 10;
select row_count();
insert into t1 values (5, 5) on duplicate key update data= data + 10;
select row_count();
drop table t1;
...@@ -185,24 +185,26 @@ select @log; ...@@ -185,24 +185,26 @@ select @log;
set @log:= ""; set @log:= "";
insert ignore t1 values (1, 2); insert ignore t1 values (1, 2);
select @log; select @log;
# REPLACE: before insert trigger should be called for both records, # INSERT ... ON DUPLICATE KEY UPDATE ...
# but then for first one update will be executed (and both update
# triggers should fire). For second after insert trigger will be
# called as for usual insert
set @log:= ""; set @log:= "";
replace t1 values (1, 3), (2, 2); insert into t1 (id, data) values (1, 3), (2, 2) on duplicate key update data= data + 1;
select @log; select @log;
# Now let us change table in such way that REPLACE on won't be executed # REPLACE (also test for bug#13479 "REPLACE activates UPDATE trigger,
# using update. # not the DELETE and INSERT triggers")
alter table t1 add ts timestamp default now(); # We define REPLACE as INSERT which DELETEs old rows which conflict with
# row being inserted. So for the first record in statement below we will
# call before insert trigger, then delete will be executed (and both delete
# triggers should fire). Finally after insert trigger will be called.
# For the second record we will just call both on insert triggers.
set @log:= ""; set @log:= "";
# This REPLACE should be executed via DELETE and INSERT so proper replace t1 values (1, 4), (3, 3);
# triggers should be invoked.
replace t1 (id, data) values (1, 4);
select @log; select @log;
# Finally let us test INSERT ... ON DUPLICATE KEY UPDATE ... # Now we will drop ON DELETE triggers to test REPLACE which is internally
# executed via update
drop trigger t1_bd;
drop trigger t1_ad;
set @log:= ""; set @log:= "";
insert into t1 (id, data) values (1, 5), (3, 3) on duplicate key update data= data + 2; replace t1 values (1, 5);
select @log; select @log;
# This also drops associated triggers # This also drops associated triggers
...@@ -531,14 +533,11 @@ alter table t1 add primary key (i); ...@@ -531,14 +533,11 @@ alter table t1 add primary key (i);
--error 1054 --error 1054
insert into t1 values (3, 4) on duplicate key update k= k + 10; insert into t1 values (3, 4) on duplicate key update k= k + 10;
select * from t1; select * from t1;
# The following statement will delete old row and won't
# insert new one since after delete trigger will fail.
--error 1054 --error 1054
replace into t1 values (3, 3); replace into t1 values (3, 3);
select * from t1; select * from t1;
# Change table in such way that REPLACE will delete row
alter table t1 add ts timestamp default now();
--error 1054
replace into t1 (i, k) values (3, 13);
select * from t1;
# Also drops all triggers # Also drops all triggers
drop table t1, t2; drop table t1, t2;
...@@ -594,11 +593,6 @@ select * from t1; ...@@ -594,11 +593,6 @@ select * from t1;
--error 1054 --error 1054
replace into t1 values (2, 4); replace into t1 values (2, 4);
select * from t1; select * from t1;
# Change table in such way that REPLACE will delete row
alter table t1 add ts timestamp default now();
--error 1054
replace into t1 (i, k) values (2, 11);
select * from t1;
# Also drops all triggers # Also drops all triggers
drop table t1, t2; drop table t1, t2;
......
...@@ -1057,16 +1057,19 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) ...@@ -1057,16 +1057,19 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
to convert the latter operation internally to an UPDATE. to convert the latter operation internally to an UPDATE.
We also should not perform this conversion if we have We also should not perform this conversion if we have
timestamp field with ON UPDATE which is different from DEFAULT. timestamp field with ON UPDATE which is different from DEFAULT.
Another case when conversion should not be performed is when
we have ON DELETE trigger on table so user may notice that
we cheat here. Note that it is ok to do such conversion for
tables which have ON UPDATE but have no ON DELETE triggers,
we just should not expose this fact to users by invoking
ON UPDATE triggers.
*/ */
if (last_uniq_key(table,key_nr) && if (last_uniq_key(table,key_nr) &&
!table->file->referenced_by_foreign_key() && !table->file->referenced_by_foreign_key() &&
(table->timestamp_field_type == TIMESTAMP_NO_AUTO_SET || (table->timestamp_field_type == TIMESTAMP_NO_AUTO_SET ||
table->timestamp_field_type == TIMESTAMP_AUTO_SET_ON_BOTH)) table->timestamp_field_type == TIMESTAMP_AUTO_SET_ON_BOTH) &&
(!table->triggers || !table->triggers->has_delete_triggers()))
{ {
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_BEFORE, TRUE))
goto before_trg_err;
if (thd->clear_next_insert_id) if (thd->clear_next_insert_id)
{ {
/* Reset auto-increment cacheing if we do an update */ /* Reset auto-increment cacheing if we do an update */
...@@ -1077,13 +1080,11 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) ...@@ -1077,13 +1080,11 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
table->record[0]))) table->record[0])))
goto err; goto err;
info->deleted++; info->deleted++;
trg_error= (table->triggers && /*
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, Since we pretend that we have done insert we should call
TRG_ACTION_AFTER, its after triggers.
TRUE)); */
/* Update logfile and count */ goto after_trg_n_copied_inc;
info->copied++;
goto ok_or_after_trg_err;
} }
else else
{ {
...@@ -1107,10 +1108,6 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) ...@@ -1107,10 +1108,6 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
} }
} }
} }
info->copied++;
trg_error= (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
TRG_ACTION_AFTER, TRUE));
} }
else if ((error=table->file->write_row(table->record[0]))) else if ((error=table->file->write_row(table->record[0])))
{ {
...@@ -1118,14 +1115,14 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) ...@@ -1118,14 +1115,14 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
(error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE)) (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE))
goto err; goto err;
table->file->restore_auto_increment(); table->file->restore_auto_increment();
goto ok_or_after_trg_err;
} }
else
{ after_trg_n_copied_inc:
info->copied++; info->copied++;
trg_error= (table->triggers && trg_error= (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_INSERT, table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
TRG_ACTION_AFTER, TRUE)); TRG_ACTION_AFTER, TRUE));
}
ok_or_after_trg_err: ok_or_after_trg_err:
if (key) if (key)
......
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