Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
M
mariadb
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
mariadb
Commits
1449d44e
Commit
1449d44e
authored
May 24, 2005
by
dlenev@mysql.com
Browse files
Options
Browse Files
Download
Plain Diff
Manual merge of patch fixing several trigger related bugs with main tree.
parents
16c96b88
007a2059
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
634 additions
and
127 deletions
+634
-127
mysql-test/r/trigger.result
mysql-test/r/trigger.result
+109
-0
mysql-test/t/trigger.test
mysql-test/t/trigger.test
+118
-0
sql/item.cc
sql/item.cc
+22
-21
sql/item.h
sql/item.h
+14
-5
sql/mysql_priv.h
sql/mysql_priv.h
+10
-2
sql/sql_base.cc
sql/sql_base.cc
+71
-1
sql/sql_delete.cc
sql/sql_delete.cc
+38
-7
sql/sql_insert.cc
sql/sql_insert.cc
+123
-40
sql/sql_load.cc
sql/sql_load.cc
+22
-8
sql/sql_trigger.cc
sql/sql_trigger.cc
+24
-21
sql/sql_trigger.h
sql/sql_trigger.h
+33
-6
sql/sql_update.cc
sql/sql_update.cc
+50
-16
No files found.
mysql-test/r/trigger.result
View file @
1449d44e
...
@@ -140,6 +140,48 @@ drop trigger t1.trg1;
...
@@ -140,6 +140,48 @@ drop trigger t1.trg1;
drop trigger t1.trg2;
drop trigger t1.trg2;
drop trigger t1.trg3;
drop trigger t1.trg3;
drop table t1;
drop table t1;
create table t1 (id int not null primary key, data int);
create trigger t1_bi before insert on t1 for each row
set @log:= concat(@log, "(BEFORE_INSERT: new=(id=", new.id, ", data=", new.data,"))");
create trigger t1_ai after insert on t1 for each row
set @log:= concat(@log, "(AFTER_INSERT: new=(id=", new.id, ", data=", new.data,"))");
create trigger t1_bu before update on t1 for each row
set @log:= concat(@log, "(BEFORE_UPDATE: old=(id=", old.id, ", data=", old.data,
") new=(id=", new.id, ", data=", new.data,"))");
create trigger t1_au after update on t1 for each row
set @log:= concat(@log, "(AFTER_UPDATE: old=(id=", old.id, ", data=", old.data,
") new=(id=", new.id, ", data=", new.data,"))");
create trigger t1_bd before delete on t1 for each row
set @log:= concat(@log, "(BEFORE_DELETE: old=(id=", old.id, ", data=", old.data,"))");
create trigger t1_ad after delete on t1 for each row
set @log:= concat(@log, "(AFTER_DELETE: old=(id=", old.id, ", data=", old.data,"))");
set @log:= "";
insert into t1 values (1, 1);
select @log;
@log
(BEFORE_INSERT: new=(id=1, data=1))(AFTER_INSERT: new=(id=1, data=1))
set @log:= "";
insert ignore t1 values (1, 2);
select @log;
@log
(BEFORE_INSERT: new=(id=1, data=2))
set @log:= "";
replace t1 values (1, 3), (2, 2);
select @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))
alter table t1 add ts timestamp default now();
set @log:= "";
replace t1 (id, data) values (1, 4);
select @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))
set @log:= "";
insert into t1 (id, data) values (1, 5), (3, 3) on duplicate key update data= data + 2;
select @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))
drop table t1;
create table t1 (i int);
create table t1 (i int);
create trigger trg before insert on t1 for each row set @a:= old.i;
create trigger trg before insert on t1 for each row set @a:= old.i;
ERROR HY000: There is no OLD row in on INSERT trigger
ERROR HY000: There is no OLD row in on INSERT trigger
...
@@ -206,3 +248,70 @@ create table t1 (i int);
...
@@ -206,3 +248,70 @@ create table t1 (i int);
create trigger trg1 before insert on t1 for each row set @a:= 1;
create trigger trg1 before insert on t1 for each row set @a:= 1;
drop database mysqltest;
drop database mysqltest;
use test;
use test;
create table t1 (i int, j int default 10, k int not null, key (k));
create table t2 (i int);
insert into t1 (i, k) values (1, 1);
insert into t2 values (1);
create trigger trg1 before update on t1 for each row set @a:= @a + new.j - old.j;
create trigger trg2 after update on t1 for each row set @b:= "Fired";
set @a:= 0, @b:= "";
update t1, t2 set j = j + 10 where t1.i = t2.i;
select @a, @b;
@a @b
10 Fired
insert into t1 values (2, 13, 2);
insert into t2 values (2);
set @a:= 0, @b:= "";
update t1, t2 set j = j + 15 where t1.i = t2.i and t1.k >= 2;
select @a, @b;
@a @b
15 Fired
create trigger trg3 before delete on t1 for each row set @c:= @c + old.j;
create trigger trg4 before delete on t2 for each row set @d:= @d + old.i;
create trigger trg5 after delete on t1 for each row set @e:= "After delete t1 fired";
create trigger trg6 after delete on t2 for each row set @f:= "After delete t2 fired";
set @c:= 0, @d:= 0, @e:= "", @f:= "";
delete t1, t2 from t1, t2 where t1.i = t2.i;
select @c, @d, @e, @f;
@c @d @e @f
48 3 After delete t1 fired After delete t2 fired
drop table t1, t2;
create table t1 (i int, j int default 10)|
create table t2 (i int)|
insert into t2 values (1), (2)|
create trigger trg1 before insert on t1 for each row
begin
if new.i = 1 then
set new.j := 1;
end if;
end|
create trigger trg2 after insert on t1 for each row set @a:= 1|
set @a:= 0|
insert into t1 (i) select * from t2|
select * from t1|
i j
1 1
2 10
select @a|
@a
1
drop table t1, t2|
create table t1 (i int, j int, k int);
create trigger trg1 before insert on t1 for each row set new.k = new.i;
create trigger trg2 after insert on t1 for each row set @b:= "Fired";
set @b:="";
load data infile '../../std_data/rpl_loaddata.dat' into table t1 (@a, i);
select *, @b from t1;
i j k @b
10 NULL 10 Fired
15 NULL 15 Fired
set @b:="";
load data infile '../../std_data/loaddata5.dat' into table t1 fields terminated by '' enclosed by '' (i, j);
select *, @b from t1;
i j k @b
10 NULL 10 Fired
15 NULL 15 Fired
1 2 1 Fired
3 4 3 Fired
5 6 5 Fired
drop table t1;
mysql-test/t/trigger.test
View file @
1449d44e
...
@@ -150,6 +150,55 @@ drop trigger t1.trg3;
...
@@ -150,6 +150,55 @@ drop trigger t1.trg3;
drop
table
t1
;
drop
table
t1
;
# Let us test how triggers work for special forms of INSERT such as
# REPLACE and INSERT ... ON DUPLICATE KEY UPDATE
create
table
t1
(
id
int
not
null
primary
key
,
data
int
);
create
trigger
t1_bi
before
insert
on
t1
for
each
row
set
@
log
:=
concat
(
@
log
,
"(BEFORE_INSERT: new=(id="
,
new
.
id
,
", data="
,
new
.
data
,
"))"
);
create
trigger
t1_ai
after
insert
on
t1
for
each
row
set
@
log
:=
concat
(
@
log
,
"(AFTER_INSERT: new=(id="
,
new
.
id
,
", data="
,
new
.
data
,
"))"
);
create
trigger
t1_bu
before
update
on
t1
for
each
row
set
@
log
:=
concat
(
@
log
,
"(BEFORE_UPDATE: old=(id="
,
old
.
id
,
", data="
,
old
.
data
,
") new=(id="
,
new
.
id
,
", data="
,
new
.
data
,
"))"
);
create
trigger
t1_au
after
update
on
t1
for
each
row
set
@
log
:=
concat
(
@
log
,
"(AFTER_UPDATE: old=(id="
,
old
.
id
,
", data="
,
old
.
data
,
") new=(id="
,
new
.
id
,
", data="
,
new
.
data
,
"))"
);
create
trigger
t1_bd
before
delete
on
t1
for
each
row
set
@
log
:=
concat
(
@
log
,
"(BEFORE_DELETE: old=(id="
,
old
.
id
,
", data="
,
old
.
data
,
"))"
);
create
trigger
t1_ad
after
delete
on
t1
for
each
row
set
@
log
:=
concat
(
@
log
,
"(AFTER_DELETE: old=(id="
,
old
.
id
,
", data="
,
old
.
data
,
"))"
);
# Simple INSERT - both triggers should be called
set
@
log
:=
""
;
insert
into
t1
values
(
1
,
1
);
select
@
log
;
# INSERT IGNORE for already existing key - only before trigger should fire
set
@
log
:=
""
;
insert
ignore
t1
values
(
1
,
2
);
select
@
log
;
# REPLACE: before insert trigger should be called for both records,
# 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
:=
""
;
replace
t1
values
(
1
,
3
),
(
2
,
2
);
select
@
log
;
# Now let us change table in such way that REPLACE on won't be executed
# using update.
alter
table
t1
add
ts
timestamp
default
now
();
set
@
log
:=
""
;
# This REPLACE should be executed via DELETE and INSERT so proper
# triggers should be invoked.
replace
t1
(
id
,
data
)
values
(
1
,
4
);
select
@
log
;
# Finally let us test INSERT ... ON DUPLICATE KEY UPDATE ...
set
@
log
:=
""
;
insert
into
t1
(
id
,
data
)
values
(
1
,
5
),
(
3
,
3
)
on
duplicate
key
update
data
=
data
+
2
;
select
@
log
;
# This also drops associated triggers
drop
table
t1
;
#
#
# Test of wrong column specifiers in triggers
# Test of wrong column specifiers in triggers
#
#
...
@@ -249,3 +298,72 @@ create trigger trg1 before insert on t1 for each row set @a:= 1;
...
@@ -249,3 +298,72 @@ create trigger trg1 before insert on t1 for each row set @a:= 1;
# This should succeed
# This should succeed
drop
database
mysqltest
;
drop
database
mysqltest
;
use
test
;
use
test
;
# Test for bug #5860 "Multi-table UPDATE does not activate update triggers"
# We will also test how delete triggers wor for multi-table DELETE.
create
table
t1
(
i
int
,
j
int
default
10
,
k
int
not
null
,
key
(
k
));
create
table
t2
(
i
int
);
insert
into
t1
(
i
,
k
)
values
(
1
,
1
);
insert
into
t2
values
(
1
);
create
trigger
trg1
before
update
on
t1
for
each
row
set
@
a
:=
@
a
+
new
.
j
-
old
.
j
;
create
trigger
trg2
after
update
on
t1
for
each
row
set
@
b
:=
"Fired"
;
set
@
a
:=
0
,
@
b
:=
""
;
# Check that trigger works in case of update on the fly
update
t1
,
t2
set
j
=
j
+
10
where
t1
.
i
=
t2
.
i
;
select
@
a
,
@
b
;
insert
into
t1
values
(
2
,
13
,
2
);
insert
into
t2
values
(
2
);
set
@
a
:=
0
,
@
b
:=
""
;
# And now let us check that triggers work in case of multi-update which
# is done through temporary tables...
update
t1
,
t2
set
j
=
j
+
15
where
t1
.
i
=
t2
.
i
and
t1
.
k
>=
2
;
select
@
a
,
@
b
;
# Let us test delete triggers for multi-delete now.
# We create triggers for both tables because we want test how they
# work in both on-the-fly and via-temp-tables cases.
create
trigger
trg3
before
delete
on
t1
for
each
row
set
@
c
:=
@
c
+
old
.
j
;
create
trigger
trg4
before
delete
on
t2
for
each
row
set
@
d
:=
@
d
+
old
.
i
;
create
trigger
trg5
after
delete
on
t1
for
each
row
set
@
e
:=
"After delete t1 fired"
;
create
trigger
trg6
after
delete
on
t2
for
each
row
set
@
f
:=
"After delete t2 fired"
;
set
@
c
:=
0
,
@
d
:=
0
,
@
e
:=
""
,
@
f
:=
""
;
delete
t1
,
t2
from
t1
,
t2
where
t1
.
i
=
t2
.
i
;
select
@
c
,
@
d
,
@
e
,
@
f
;
# This also will drop triggers
drop
table
t1
,
t2
;
# Test for bug #6812 "Triggers are not activated for INSERT ... SELECT".
# (We also check the fact that trigger modifies some field does not affect
# value of next record inserted).
delimiter
|
;
create
table
t1
(
i
int
,
j
int
default
10
)
|
create
table
t2
(
i
int
)
|
insert
into
t2
values
(
1
),
(
2
)
|
create
trigger
trg1
before
insert
on
t1
for
each
row
begin
if
new
.
i
=
1
then
set
new
.
j
:=
1
;
end
if
;
end
|
create
trigger
trg2
after
insert
on
t1
for
each
row
set
@
a
:=
1
|
set
@
a
:=
0
|
insert
into
t1
(
i
)
select
*
from
t2
|
select
*
from
t1
|
select
@
a
|
# This also will drop triggers
drop
table
t1
,
t2
|
delimiter
;
|
# Test for bug #8755 "Trigger is not activated by LOAD DATA"
create
table
t1
(
i
int
,
j
int
,
k
int
);
create
trigger
trg1
before
insert
on
t1
for
each
row
set
new
.
k
=
new
.
i
;
create
trigger
trg2
after
insert
on
t1
for
each
row
set
@
b
:=
"Fired"
;
set
@
b
:=
""
;
# Test triggers with file with separators
load
data
infile
'../../std_data/rpl_loaddata.dat'
into
table
t1
(
@
a
,
i
);
select
*
,
@
b
from
t1
;
set
@
b
:=
""
;
# Test triggers with fixed size row file
load
data
infile
'../../std_data/loaddata5.dat'
into
table
t1
fields
terminated
by
''
enclosed
by
''
(
i
,
j
);
select
*
,
@
b
from
t1
;
# This also will drop triggers
drop
table
t1
;
sql/item.cc
View file @
1449d44e
...
@@ -4554,40 +4554,40 @@ void Item_insert_value::print(String *str)
...
@@ -4554,40 +4554,40 @@ void Item_insert_value::print(String *str)
/*
/*
Bind item representing field of row being changed in trigger
Find index of Field object which will be appropriate for item
to appropriate Field object
.
representing field of row being changed in trigger
.
SYNOPSIS
SYNOPSIS
setup_field()
setup_field()
thd - current thread context
thd - current thread context
table - table of trigger (and where we looking for fields)
table - table of trigger (and where we looking for fields)
event - type of trigger event
NOTE
NOTE
This function does almost the same as fix_fields() for Item_field
This function does almost the same as fix_fields() for Item_field
but is invoked during trigger definition parsing and takes TABLE
but is invoked right after trigger definition parsing. Since at
object as its argument. If proper field was not found in table
this stage we can't say exactly what Field object (corresponding
error will be reported at fix_fields() time.
to TABLE::record[0] or TABLE::record[1]) should be bound to this
Item, we only find out index of the Field and then select concrete
Field object in fix_fields() (by that time Table_trigger_list::old_field/
new_field should point to proper array of Fields).
It also binds Item_trigger_field to Table_triggers_list object for
table of trigger which uses this item.
*/
*/
void
Item_trigger_field
::
setup_field
(
THD
*
thd
,
TABLE
*
table
,
enum
trg_event_type
event
)
void
Item_trigger_field
::
setup_field
(
THD
*
thd
,
TABLE
*
table
)
{
{
uint
field_idx
=
(
uint
)
-
1
;
bool
save_set_query_id
=
thd
->
set_query_id
;
bool
save_set_query_id
=
thd
->
set_query_id
;
/* TODO: Think more about consequences of this step. */
/* TODO: Think more about consequences of this step. */
thd
->
set_query_id
=
0
;
thd
->
set_query_id
=
0
;
/*
if
(
find_field_in_real_table
(
thd
,
table
,
field_name
,
Try to find field by its name and if it will be found
strlen
(
field_name
),
0
,
0
,
set field_idx properly.
&
field_idx
))
*/
{
(
void
)
find_field_in_real_table
(
thd
,
table
,
field_name
,
strlen
(
field_name
),
field
=
(
row_version
==
OLD_ROW
&&
event
==
TRG_EVENT_UPDATE
)
?
0
,
0
,
&
field_idx
);
table
->
triggers
->
old_field
[
field_idx
]
:
table
->
field
[
field_idx
];
}
thd
->
set_query_id
=
save_set_query_id
;
thd
->
set_query_id
=
save_set_query_id
;
triggers
=
table
->
triggers
;
}
}
...
@@ -4612,9 +4612,10 @@ bool Item_trigger_field::fix_fields(THD *thd,
...
@@ -4612,9 +4612,10 @@ bool Item_trigger_field::fix_fields(THD *thd,
*/
*/
DBUG_ASSERT
(
fixed
==
0
);
DBUG_ASSERT
(
fixed
==
0
);
if
(
field
)
if
(
field
_idx
!=
(
uint
)
-
1
)
{
{
// QQ: May be this should be moved to setup_field?
field
=
(
row_version
==
OLD_ROW
)
?
triggers
->
old_field
[
field_idx
]
:
triggers
->
new_field
[
field_idx
];
set_field
(
field
);
set_field
(
field
);
fixed
=
1
;
fixed
=
1
;
return
0
;
return
0
;
...
...
sql/item.h
View file @
1449d44e
...
@@ -1632,13 +1632,18 @@ enum trg_event_type
...
@@ -1632,13 +1632,18 @@ enum trg_event_type
TRG_EVENT_INSERT
=
0
,
TRG_EVENT_UPDATE
=
1
,
TRG_EVENT_DELETE
=
2
TRG_EVENT_INSERT
=
0
,
TRG_EVENT_UPDATE
=
1
,
TRG_EVENT_DELETE
=
2
};
};
class
Table_triggers_list
;
/*
/*
Represents NEW/OLD version of field of row which is
Represents NEW/OLD version of field of row which is
changed/read in trigger.
changed/read in trigger.
Note: For this item actual binding to Field object happens not during
Note: For this item main part of actual binding to Field object happens
fix_fields() (like for Item_field) but during parsing of trigger
not during fix_fields() call (like for Item_field) but right after
definition, when table is opened, with special setup_field() call.
parsing of trigger definition, when table is opened, with special
setup_field() call. On fix_fields() stage we simply choose one of
two Field instances representing either OLD or NEW version of this
field.
*/
*/
class
Item_trigger_field
:
public
Item_field
class
Item_trigger_field
:
public
Item_field
{
{
...
@@ -1648,13 +1653,17 @@ public:
...
@@ -1648,13 +1653,17 @@ public:
row_version_type
row_version
;
row_version_type
row_version
;
/* Next in list of all Item_trigger_field's in trigger */
/* Next in list of all Item_trigger_field's in trigger */
Item_trigger_field
*
next_trg_field
;
Item_trigger_field
*
next_trg_field
;
/* Index of the field in the TABLE::field array */
uint
field_idx
;
/* Pointer to Table_trigger_list object for table of this trigger */
Table_triggers_list
*
triggers
;
Item_trigger_field
(
row_version_type
row_ver_par
,
Item_trigger_field
(
row_version_type
row_ver_par
,
const
char
*
field_name_par
)
:
const
char
*
field_name_par
)
:
Item_field
((
const
char
*
)
NULL
,
(
const
char
*
)
NULL
,
field_name_par
),
Item_field
((
const
char
*
)
NULL
,
(
const
char
*
)
NULL
,
field_name_par
),
row_version
(
row_ver_par
)
row_version
(
row_ver_par
)
,
field_idx
((
uint
)
-
1
)
{}
{}
void
setup_field
(
THD
*
thd
,
TABLE
*
table
,
enum
trg_event_type
event
);
void
setup_field
(
THD
*
thd
,
TABLE
*
table
);
enum
Type
type
()
const
{
return
TRIGGER_FIELD_ITEM
;
}
enum
Type
type
()
const
{
return
TRIGGER_FIELD_ITEM
;
}
bool
eq
(
const
Item
*
item
,
bool
binary_cmp
)
const
;
bool
eq
(
const
Item
*
item
,
bool
binary_cmp
)
const
;
bool
fix_fields
(
THD
*
,
struct
st_table_list
*
,
Item
**
);
bool
fix_fields
(
THD
*
,
struct
st_table_list
*
,
Item
**
);
...
...
sql/mysql_priv.h
View file @
1449d44e
...
@@ -924,10 +924,18 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table,
...
@@ -924,10 +924,18 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table,
bool
return_if_owned_by_thd
);
bool
return_if_owned_by_thd
);
bool
close_cached_tables
(
THD
*
thd
,
bool
wait_for_refresh
,
TABLE_LIST
*
tables
);
bool
close_cached_tables
(
THD
*
thd
,
bool
wait_for_refresh
,
TABLE_LIST
*
tables
);
void
copy_field_from_tmp_record
(
Field
*
field
,
int
offset
);
void
copy_field_from_tmp_record
(
Field
*
field
,
int
offset
);
bool
fill_record
(
THD
*
thd
,
List
<
Item
>
&
fields
,
List
<
Item
>
&
values
,
bool
ignore_errors
);
bool
fill_record
(
THD
*
thd
,
Field
**
field
,
List
<
Item
>
&
values
,
bool
fill_record
(
THD
*
thd
,
Field
**
field
,
List
<
Item
>
&
values
,
bool
ignore_errors
);
bool
ignore_errors
);
bool
fill_record_n_invoke_before_triggers
(
THD
*
thd
,
List
<
Item
>
&
fields
,
List
<
Item
>
&
values
,
bool
ignore_errors
,
Table_triggers_list
*
triggers
,
enum
trg_event_type
event
);
bool
fill_record_n_invoke_before_triggers
(
THD
*
thd
,
Field
**
field
,
List
<
Item
>
&
values
,
bool
ignore_errors
,
Table_triggers_list
*
triggers
,
enum
trg_event_type
event
);
OPEN_TABLE_LIST
*
list_open_tables
(
THD
*
thd
,
const
char
*
wild
);
OPEN_TABLE_LIST
*
list_open_tables
(
THD
*
thd
,
const
char
*
wild
);
inline
TABLE_LIST
*
find_table_in_global_list
(
TABLE_LIST
*
table
,
inline
TABLE_LIST
*
find_table_in_global_list
(
TABLE_LIST
*
table
,
...
...
sql/sql_base.cc
View file @
1449d44e
...
@@ -3813,7 +3813,7 @@ err_no_arena:
...
@@ -3813,7 +3813,7 @@ err_no_arena:
TRUE error occured
TRUE error occured
*/
*/
bool
static
bool
fill_record
(
THD
*
thd
,
List
<
Item
>
&
fields
,
List
<
Item
>
&
values
,
fill_record
(
THD
*
thd
,
List
<
Item
>
&
fields
,
List
<
Item
>
&
values
,
bool
ignore_errors
)
bool
ignore_errors
)
{
{
...
@@ -3839,6 +3839,41 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values,
...
@@ -3839,6 +3839,41 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values,
}
}
/*
Fill fields in list with values from the list of items and invoke
before triggers.
SYNOPSIS
fill_record_n_invoke_before_triggers()
thd thread context
fields Item_fields list to be filled
values values to fill with
ignore_errors TRUE if we should ignore errors
triggers object holding list of triggers to be invoked
event event type for triggers to be invoked
NOTE
This function assumes that fields which values will be set and triggers
to be invoked belong to the same table, and that TABLE::record[0] and
record[1] buffers correspond to new and old versions of row respectively.
RETURN
FALSE OK
TRUE error occured
*/
bool
fill_record_n_invoke_before_triggers
(
THD
*
thd
,
List
<
Item
>
&
fields
,
List
<
Item
>
&
values
,
bool
ignore_errors
,
Table_triggers_list
*
triggers
,
enum
trg_event_type
event
)
{
return
(
fill_record
(
thd
,
fields
,
values
,
ignore_errors
)
||
triggers
&&
triggers
->
process_triggers
(
thd
,
event
,
TRG_ACTION_BEFORE
,
TRUE
));
}
/*
/*
Fill field buffer with values from Field list
Fill field buffer with values from Field list
...
@@ -3875,6 +3910,41 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors)
...
@@ -3875,6 +3910,41 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors)
}
}
/*
Fill fields in array with values from the list of items and invoke
before triggers.
SYNOPSIS
fill_record_n_invoke_before_triggers()
thd thread context
ptr NULL-ended array of fields to be filled
values values to fill with
ignore_errors TRUE if we should ignore errors
triggers object holding list of triggers to be invoked
event event type for triggers to be invoked
NOTE
This function assumes that fields which values will be set and triggers
to be invoked belong to the same table, and that TABLE::record[0] and
record[1] buffers correspond to new and old versions of row respectively.
RETURN
FALSE OK
TRUE error occured
*/
bool
fill_record_n_invoke_before_triggers
(
THD
*
thd
,
Field
**
ptr
,
List
<
Item
>
&
values
,
bool
ignore_errors
,
Table_triggers_list
*
triggers
,
enum
trg_event_type
event
)
{
return
(
fill_record
(
thd
,
ptr
,
values
,
ignore_errors
)
||
triggers
&&
triggers
->
process_triggers
(
thd
,
event
,
TRG_ACTION_BEFORE
,
TRUE
));
}
static
void
mysql_rm_tmp_tables
(
void
)
static
void
mysql_rm_tmp_tables
(
void
)
{
{
uint
i
,
idx
;
uint
i
,
idx
;
...
...
sql/sql_delete.cc
View file @
1449d44e
...
@@ -176,13 +176,24 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
...
@@ -176,13 +176,24 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
if
(
!
(
select
&&
select
->
skip_record
())
&&
!
thd
->
net
.
report_error
)
if
(
!
(
select
&&
select
->
skip_record
())
&&
!
thd
->
net
.
report_error
)
{
{
if
(
table
->
triggers
)
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_BEFORE
);
TRG_ACTION_BEFORE
,
FALSE
))
{
error
=
1
;
break
;
}
if
(
!
(
error
=
table
->
file
->
delete_row
(
table
->
record
[
0
])))
if
(
!
(
error
=
table
->
file
->
delete_row
(
table
->
record
[
0
])))
{
{
deleted
++
;
deleted
++
;
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_AFTER
,
FALSE
))
{
error
=
1
;
break
;
}
if
(
!--
limit
&&
using_limit
)
if
(
!--
limit
&&
using_limit
)
{
{
error
=
-
1
;
error
=
-
1
;
...
@@ -203,10 +214,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
...
@@ -203,10 +214,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
error
=
1
;
error
=
1
;
break
;
break
;
}
}
if
(
table
->
triggers
)
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_AFTER
);
}
}
else
else
table
->
file
->
unlock_row
();
// Row failed selection, release lock on it
table
->
file
->
unlock_row
();
// Row failed selection, release lock on it
...
@@ -509,9 +516,19 @@ bool multi_delete::send_data(List<Item> &values)
...
@@ -509,9 +516,19 @@ bool multi_delete::send_data(List<Item> &values)
if
(
secure_counter
<
0
)
if
(
secure_counter
<
0
)
{
{
/* If this is the table we are scanning */
/* If this is the table we are scanning */
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_BEFORE
,
FALSE
))
DBUG_RETURN
(
1
);
table
->
status
|=
STATUS_DELETED
;
table
->
status
|=
STATUS_DELETED
;
if
(
!
(
error
=
table
->
file
->
delete_row
(
table
->
record
[
0
])))
if
(
!
(
error
=
table
->
file
->
delete_row
(
table
->
record
[
0
])))
{
deleted
++
;
deleted
++
;
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_AFTER
,
FALSE
))
DBUG_RETURN
(
1
);
}
else
if
(
!
table_being_deleted
->
next_local
||
else
if
(
!
table_being_deleted
->
next_local
||
table_being_deleted
->
table
->
file
->
has_transactions
())
table_being_deleted
->
table
->
file
->
has_transactions
())
{
{
...
@@ -614,12 +631,26 @@ int multi_delete::do_deletes(bool from_send_error)
...
@@ -614,12 +631,26 @@ int multi_delete::do_deletes(bool from_send_error)
info
.
ignore_not_found_rows
=
1
;
info
.
ignore_not_found_rows
=
1
;
while
(
!
(
local_error
=
info
.
read_record
(
&
info
))
&&
!
thd
->
killed
)
while
(
!
(
local_error
=
info
.
read_record
(
&
info
))
&&
!
thd
->
killed
)
{
{
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_BEFORE
,
FALSE
))
{
local_error
=
1
;
break
;
}
if
((
local_error
=
table
->
file
->
delete_row
(
table
->
record
[
0
])))
if
((
local_error
=
table
->
file
->
delete_row
(
table
->
record
[
0
])))
{
{
table
->
file
->
print_error
(
local_error
,
MYF
(
0
));
table
->
file
->
print_error
(
local_error
,
MYF
(
0
));
break
;
break
;
}
}
deleted
++
;
deleted
++
;
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_AFTER
,
FALSE
))
{
local_error
=
1
;
break
;
}
}
}
end_read_record
(
&
info
);
end_read_record
(
&
info
);
if
(
thd
->
killed
&&
!
local_error
)
if
(
thd
->
killed
&&
!
local_error
)
...
...
sql/sql_insert.cc
View file @
1449d44e
...
@@ -398,7 +398,9 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
...
@@ -398,7 +398,9 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
if
(
fields
.
elements
||
!
value_count
)
if
(
fields
.
elements
||
!
value_count
)
{
{
restore_record
(
table
,
s
->
default_values
);
// Get empty record
restore_record
(
table
,
s
->
default_values
);
// Get empty record
if
(
fill_record
(
thd
,
fields
,
*
values
,
0
))
if
(
fill_record_n_invoke_before_triggers
(
thd
,
fields
,
*
values
,
0
,
table
->
triggers
,
TRG_EVENT_INSERT
))
{
{
if
(
values_list
.
elements
!=
1
&&
!
thd
->
net
.
report_error
)
if
(
values_list
.
elements
!=
1
&&
!
thd
->
net
.
report_error
)
{
{
...
@@ -419,8 +421,17 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
...
@@ -419,8 +421,17 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
if
(
thd
->
used_tables
)
// Column used in values()
if
(
thd
->
used_tables
)
// Column used in values()
restore_record
(
table
,
s
->
default_values
);
// Get empty record
restore_record
(
table
,
s
->
default_values
);
// Get empty record
else
else
table
->
record
[
0
][
0
]
=
table
->
s
->
default_values
[
0
];
// Fix delete marker
{
if
(
fill_record
(
thd
,
table
->
field
,
*
values
,
0
))
/*
Fix delete marker. No need to restore rest of record since it will
be overwritten by fill_record() anyway (and fill_record() does not
use default values in this case).
*/
table
->
record
[
0
][
0
]
=
table
->
s
->
default_values
[
0
];
}
if
(
fill_record_n_invoke_before_triggers
(
thd
,
table
->
field
,
*
values
,
0
,
table
->
triggers
,
TRG_EVENT_INSERT
))
{
{
if
(
values_list
.
elements
!=
1
&&
!
thd
->
net
.
report_error
)
if
(
values_list
.
elements
!=
1
&&
!
thd
->
net
.
report_error
)
{
{
...
@@ -432,14 +443,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
...
@@ -432,14 +443,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
}
}
}
}
/*
FIXME: Actually we should do this before
check_that_all_fields_are_given_values Or even go into write_record ?
*/
if
(
table
->
triggers
)
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_INSERT
,
TRG_ACTION_BEFORE
);
if
((
res
=
table_list
->
view_check_option
(
thd
,
if
((
res
=
table_list
->
view_check_option
(
thd
,
(
values_list
.
elements
==
1
?
(
values_list
.
elements
==
1
?
0
:
0
:
...
@@ -473,9 +476,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
...
@@ -473,9 +476,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
if
(
error
)
if
(
error
)
break
;
break
;
thd
->
row_count
++
;
thd
->
row_count
++
;
if
(
table
->
triggers
)
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_INSERT
,
TRG_ACTION_AFTER
);
}
}
/*
/*
...
@@ -802,15 +802,35 @@ static int last_uniq_key(TABLE *table,uint keynr)
...
@@ -802,15 +802,35 @@ static int last_uniq_key(TABLE *table,uint keynr)
/*
/*
Write a record to table with optional deleting of conflicting records
Write a record to table with optional deleting of conflicting records,
invoke proper triggers if needed.
SYNOPSIS
write_record()
thd - thread context
table - table to which record should be written
info - COPY_INFO structure describing handling of duplicates
and which is used for counting number of records inserted
and deleted.
Sets thd->no_trans_update if table which is updated didn't have transactions
NOTE
Once this record will be written to table after insert trigger will
be invoked. If instead of inserting new record we will update old one
then both on update triggers will work instead. Similarly both on
delete triggers will be invoked if we will delete conflicting records.
Sets thd->no_trans_update if table which is updated didn't have
transactions.
RETURN VALUE
0 - success
non-0 - error
*/
*/
int
write_record
(
THD
*
thd
,
TABLE
*
table
,
COPY_INFO
*
info
)
int
write_record
(
THD
*
thd
,
TABLE
*
table
,
COPY_INFO
*
info
)
{
{
int
error
;
int
error
,
trg_error
=
0
;
char
*
key
=
0
;
char
*
key
=
0
;
DBUG_ENTER
(
"write_record"
);
DBUG_ENTER
(
"write_record"
);
...
@@ -881,25 +901,33 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
...
@@ -881,25 +901,33 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
restore_record
(
table
,
record
[
1
]);
restore_record
(
table
,
record
[
1
]);
DBUG_ASSERT
(
info
->
update_fields
->
elements
==
DBUG_ASSERT
(
info
->
update_fields
->
elements
==
info
->
update_values
->
elements
);
info
->
update_values
->
elements
);
if
(
fill_record
(
thd
,
*
info
->
update_fields
,
*
info
->
update_values
,
0
))
if
(
fill_record_n_invoke_before_triggers
(
thd
,
*
info
->
update_fields
,
goto
err
;
*
info
->
update_values
,
0
,
table
->
triggers
,
TRG_EVENT_UPDATE
))
goto
before_trg_err
;
/* CHECK OPTION for VIEW ... ON DUPLICATE KEY UPDATE ... */
/* CHECK OPTION for VIEW ... ON DUPLICATE KEY UPDATE ... */
if
(
info
->
view
&&
if
(
info
->
view
&&
(
res
=
info
->
view
->
view_check_option
(
current_thd
,
info
->
ignore
))
==
(
res
=
info
->
view
->
view_check_option
(
current_thd
,
info
->
ignore
))
==
VIEW_CHECK_SKIP
)
VIEW_CHECK_SKIP
)
break
;
goto
ok_or_after_trg_err
;
if
(
res
==
VIEW_CHECK_ERROR
)
if
(
res
==
VIEW_CHECK_ERROR
)
goto
err
;
goto
before_trg_
err
;
if
((
error
=
table
->
file
->
update_row
(
table
->
record
[
1
],
table
->
record
[
0
])))
if
((
error
=
table
->
file
->
update_row
(
table
->
record
[
1
],
table
->
record
[
0
])))
{
{
if
((
error
==
HA_ERR_FOUND_DUPP_KEY
)
&&
info
->
ignore
)
if
((
error
==
HA_ERR_FOUND_DUPP_KEY
)
&&
info
->
ignore
)
break
;
goto
ok_or_after_trg_err
;
goto
err
;
goto
err
;
}
}
info
->
updated
++
;
info
->
updated
++
;
break
;
trg_error
=
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_AFTER
,
TRUE
));
info
->
copied
++
;
goto
ok_or_after_trg_err
;
}
}
else
/* DUP_REPLACE */
else
/* DUP_REPLACE */
{
{
...
@@ -916,20 +944,48 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
...
@@ -916,20 +944,48 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
(
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
))
{
{
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_BEFORE
,
TRUE
))
goto
before_trg_err
;
if
((
error
=
table
->
file
->
update_row
(
table
->
record
[
1
],
if
((
error
=
table
->
file
->
update_row
(
table
->
record
[
1
],
table
->
record
[
0
])))
table
->
record
[
0
])))
goto
err
;
goto
err
;
info
->
deleted
++
;
info
->
deleted
++
;
break
;
/* Update logfile and count */
trg_error
=
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_AFTER
,
TRUE
));
/* Update logfile and count */
info
->
copied
++
;
goto
ok_or_after_trg_err
;
}
else
{
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_BEFORE
,
TRUE
))
goto
before_trg_err
;
if
((
error
=
table
->
file
->
delete_row
(
table
->
record
[
1
])))
goto
err
;
info
->
deleted
++
;
if
(
!
table
->
file
->
has_transactions
())
thd
->
no_trans_update
=
1
;
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_DELETE
,
TRG_ACTION_AFTER
,
TRUE
))
{
trg_error
=
1
;
goto
ok_or_after_trg_err
;
}
/* Let us attempt do write_row() once more */
}
}
else
if
((
error
=
table
->
file
->
delete_row
(
table
->
record
[
1
])))
goto
err
;
info
->
deleted
++
;
if
(
!
table
->
file
->
has_transactions
())
thd
->
no_trans_update
=
1
;
}
}
}
}
info
->
copied
++
;
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
])))
{
{
...
@@ -939,18 +995,27 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
...
@@ -939,18 +995,27 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
table
->
file
->
restore_auto_increment
();
table
->
file
->
restore_auto_increment
();
}
}
else
else
{
info
->
copied
++
;
info
->
copied
++
;
trg_error
=
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_INSERT
,
TRG_ACTION_AFTER
,
TRUE
));
}
ok_or_after_trg_err:
if
(
key
)
if
(
key
)
my_safe_afree
(
key
,
table
->
s
->
max_unique_length
,
MAX_KEY_LENGTH
);
my_safe_afree
(
key
,
table
->
s
->
max_unique_length
,
MAX_KEY_LENGTH
);
if
(
!
table
->
file
->
has_transactions
())
if
(
!
table
->
file
->
has_transactions
())
thd
->
no_trans_update
=
1
;
thd
->
no_trans_update
=
1
;
DBUG_RETURN
(
0
);
DBUG_RETURN
(
trg_error
);
err:
err:
if
(
key
)
my_safe_afree
(
key
,
table
->
s
->
max_unique_length
,
MAX_KEY_LENGTH
);
info
->
last_errno
=
error
;
info
->
last_errno
=
error
;
table
->
file
->
print_error
(
error
,
MYF
(
0
));
table
->
file
->
print_error
(
error
,
MYF
(
0
));
before_trg_err:
if
(
key
)
my_safe_afree
(
key
,
table
->
s
->
max_unique_length
,
MAX_KEY_LENGTH
);
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
}
}
...
@@ -2014,12 +2079,27 @@ bool select_insert::send_data(List<Item> &values)
...
@@ -2014,12 +2079,27 @@ bool select_insert::send_data(List<Item> &values)
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
}
}
}
}
if
(
!
(
error
=
write_record
(
thd
,
table
,
&
info
))
&&
table
->
next_number_field
)
if
(
!
(
error
=
write_record
(
thd
,
table
,
&
info
))
)
{
{
/* Clear for next record */
if
(
table
->
triggers
)
table
->
next_number_field
->
reset
();
{
if
(
!
last_insert_id
&&
thd
->
insert_id_used
)
/*
last_insert_id
=
thd
->
insert_id
();
If triggers exist then whey can modify some fields which were not
originally touched by INSERT ... SELECT, so we have to restore
their original values for the next row.
*/
restore_record
(
table
,
s
->
default_values
);
}
if
(
table
->
next_number_field
)
{
/*
Clear auto-increment field for the next record, if triggers are used
we will clear it twice, but this should be cheap.
*/
table
->
next_number_field
->
reset
();
if
(
!
last_insert_id
&&
thd
->
insert_id_used
)
last_insert_id
=
thd
->
insert_id
();
}
}
}
DBUG_RETURN
(
error
);
DBUG_RETURN
(
error
);
}
}
...
@@ -2028,9 +2108,11 @@ bool select_insert::send_data(List<Item> &values)
...
@@ -2028,9 +2108,11 @@ bool select_insert::send_data(List<Item> &values)
void
select_insert
::
store_values
(
List
<
Item
>
&
values
)
void
select_insert
::
store_values
(
List
<
Item
>
&
values
)
{
{
if
(
fields
->
elements
)
if
(
fields
->
elements
)
fill_record
(
thd
,
*
fields
,
values
,
1
);
fill_record_n_invoke_before_triggers
(
thd
,
*
fields
,
values
,
1
,
table
->
triggers
,
TRG_EVENT_INSERT
);
else
else
fill_record
(
thd
,
table
->
field
,
values
,
1
);
fill_record_n_invoke_before_triggers
(
thd
,
table
->
field
,
values
,
1
,
table
->
triggers
,
TRG_EVENT_INSERT
);
}
}
void
select_insert
::
send_error
(
uint
errcode
,
const
char
*
err
)
void
select_insert
::
send_error
(
uint
errcode
,
const
char
*
err
)
...
@@ -2173,7 +2255,8 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
...
@@ -2173,7 +2255,8 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
void
select_create
::
store_values
(
List
<
Item
>
&
values
)
void
select_create
::
store_values
(
List
<
Item
>
&
values
)
{
{
fill_record
(
thd
,
field
,
values
,
1
);
fill_record_n_invoke_before_triggers
(
thd
,
field
,
values
,
1
,
table
->
triggers
,
TRG_EVENT_INSERT
);
}
}
...
...
sql/sql_load.cc
View file @
1449d44e
...
@@ -21,6 +21,8 @@
...
@@ -21,6 +21,8 @@
#include <my_dir.h>
#include <my_dir.h>
#include <m_ctype.h>
#include <m_ctype.h>
#include "sql_repl.h"
#include "sql_repl.h"
#include "sp_head.h"
#include "sql_trigger.h"
class
READ_INFO
{
class
READ_INFO
{
File
file
;
File
file
;
...
@@ -568,7 +570,11 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
...
@@ -568,7 +570,11 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
ER
(
ER_WARN_TOO_MANY_RECORDS
),
thd
->
row_count
);
ER
(
ER_WARN_TOO_MANY_RECORDS
),
thd
->
row_count
);
}
}
if
(
fill_record
(
thd
,
set_fields
,
set_values
,
ignore_check_option_errors
))
if
(
thd
->
killed
||
fill_record_n_invoke_before_triggers
(
thd
,
set_fields
,
set_values
,
ignore_check_option_errors
,
table
->
triggers
,
TRG_EVENT_INSERT
))
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
switch
(
table_list
->
view_check_option
(
thd
,
switch
(
table_list
->
view_check_option
(
thd
,
...
@@ -580,7 +586,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
...
@@ -580,7 +586,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
DBUG_RETURN
(
-
1
);
DBUG_RETURN
(
-
1
);
}
}
if
(
thd
->
killed
||
write_record
(
thd
,
table
,
&
info
))
if
(
write_record
(
thd
,
table
,
&
info
))
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
thd
->
no_trans_update
=
no_trans_update
;
thd
->
no_trans_update
=
no_trans_update
;
...
@@ -592,8 +598,10 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
...
@@ -592,8 +598,10 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
*/
*/
if
(
!
id
&&
thd
->
insert_id_used
)
if
(
!
id
&&
thd
->
insert_id_used
)
id
=
thd
->
last_insert_id
;
id
=
thd
->
last_insert_id
;
if
(
table
->
next_number_field
)
/*
table
->
next_number_field
->
reset
();
// Clear for next record
We don't need to reset auto-increment field since we are restoring
its default value at the beginning of each loop iteration.
*/
if
(
read_info
.
next_line
())
// Skip to next line
if
(
read_info
.
next_line
())
// Skip to next line
break
;
break
;
if
(
read_info
.
line_cuted
)
if
(
read_info
.
line_cuted
)
...
@@ -725,7 +733,11 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
...
@@ -725,7 +733,11 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
}
}
}
}
if
(
fill_record
(
thd
,
set_fields
,
set_values
,
ignore_check_option_errors
))
if
(
thd
->
killed
||
fill_record_n_invoke_before_triggers
(
thd
,
set_fields
,
set_values
,
ignore_check_option_errors
,
table
->
triggers
,
TRG_EVENT_INSERT
))
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
switch
(
table_list
->
view_check_option
(
thd
,
switch
(
table_list
->
view_check_option
(
thd
,
...
@@ -738,7 +750,7 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
...
@@ -738,7 +750,7 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
}
}
if
(
thd
->
killed
||
write_record
(
thd
,
table
,
&
info
))
if
(
write_record
(
thd
,
table
,
&
info
))
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
/*
/*
If auto_increment values are used, save the first one
If auto_increment values are used, save the first one
...
@@ -748,8 +760,10 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
...
@@ -748,8 +760,10 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
*/
*/
if
(
!
id
&&
thd
->
insert_id_used
)
if
(
!
id
&&
thd
->
insert_id_used
)
id
=
thd
->
last_insert_id
;
id
=
thd
->
last_insert_id
;
if
(
table
->
next_number_field
)
/*
table
->
next_number_field
->
reset
();
// Clear for next record
We don't need to reset auto-increment field since we are restoring
its default value at the beginning of each loop iteration.
*/
thd
->
no_trans_update
=
no_trans_update
;
thd
->
no_trans_update
=
no_trans_update
;
if
(
read_info
.
next_line
())
// Skip to next line
if
(
read_info
.
next_line
())
// Skip to next line
break
;
break
;
...
...
sql/sql_trigger.cc
View file @
1449d44e
...
@@ -85,7 +85,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
...
@@ -85,7 +85,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
DBUG_RETURN
(
TRUE
);
DBUG_RETURN
(
TRUE
);
}
}
if
(
!
(
table
->
triggers
=
new
(
&
table
->
mem_root
)
Table_triggers_list
()))
if
(
!
(
table
->
triggers
=
new
(
&
table
->
mem_root
)
Table_triggers_list
(
table
)))
DBUG_RETURN
(
TRUE
);
DBUG_RETURN
(
TRUE
);
}
}
...
@@ -190,17 +190,16 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
...
@@ -190,17 +190,16 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
to other tables from trigger we won't be able to catch changes in other
to other tables from trigger we won't be able to catch changes in other
tables...
tables...
To simplify code a bit we have to create Fields for accessing to old row
Since we don't plan to access to contents of the fields it does not
values if we have ON UPDATE trigger.
matter that we choose for both OLD and NEW values the same versions
of Field objects here.
*/
*/
if
(
!
old_field
&&
lex
->
trg_chistics
.
event
==
TRG_EVENT_UPDATE
&&
old_field
=
new_field
=
table
->
field
;
prepare_old_row_accessors
(
table
))
return
1
;
for
(
trg_field
=
(
Item_trigger_field
*
)(
lex
->
trg_table_fields
.
first
);
for
(
trg_field
=
(
Item_trigger_field
*
)(
lex
->
trg_table_fields
.
first
);
trg_field
;
trg_field
=
trg_field
->
next_trg_field
)
trg_field
;
trg_field
=
trg_field
->
next_trg_field
)
{
{
trg_field
->
setup_field
(
thd
,
table
,
lex
->
trg_chistics
.
event
);
trg_field
->
setup_field
(
thd
,
table
);
if
(
!
trg_field
->
fixed
&&
if
(
!
trg_field
->
fixed
&&
trg_field
->
fix_fields
(
thd
,
(
TABLE_LIST
*
)
0
,
(
Item
**
)
0
))
trg_field
->
fix_fields
(
thd
,
(
TABLE_LIST
*
)
0
,
(
Item
**
)
0
))
return
1
;
return
1
;
...
@@ -318,34 +317,35 @@ Table_triggers_list::~Table_triggers_list()
...
@@ -318,34 +317,35 @@ Table_triggers_list::~Table_triggers_list()
for
(
int
j
=
0
;
j
<
2
;
j
++
)
for
(
int
j
=
0
;
j
<
2
;
j
++
)
delete
bodies
[
i
][
j
];
delete
bodies
[
i
][
j
];
if
(
old
_field
)
if
(
record1
_field
)
for
(
Field
**
fld_ptr
=
old
_field
;
*
fld_ptr
;
fld_ptr
++
)
for
(
Field
**
fld_ptr
=
record1
_field
;
*
fld_ptr
;
fld_ptr
++
)
delete
*
fld_ptr
;
delete
*
fld_ptr
;
}
}
/*
/*
Prepare array of Field objects which will represent OLD.* row values in
Prepare array of Field objects referencing to TABLE::record[1] instead
ON UPDATE trigger (by referencing to record[1] instead of record[0]).
of record[0] (they will represent OLD.* row values in ON UPDATE trigger
and in ON DELETE trigger which will be called during REPLACE execution).
SYNOPSIS
SYNOPSIS
prepare_
old_row
_accessors()
prepare_
record1
_accessors()
table - pointer to TABLE object for which we are creating fields.
table - pointer to TABLE object for which we are creating fields.
RETURN VALUE
RETURN VALUE
False - success
False - success
True - error
True - error
*/
*/
bool
Table_triggers_list
::
prepare_
old_row
_accessors
(
TABLE
*
table
)
bool
Table_triggers_list
::
prepare_
record1
_accessors
(
TABLE
*
table
)
{
{
Field
**
fld
,
**
old_fld
;
Field
**
fld
,
**
old_fld
;
if
(
!
(
old
_field
=
(
Field
**
)
alloc_root
(
&
table
->
mem_root
,
if
(
!
(
record1
_field
=
(
Field
**
)
alloc_root
(
&
table
->
mem_root
,
(
table
->
s
->
fields
+
1
)
*
(
table
->
s
->
fields
+
1
)
*
sizeof
(
Field
*
))))
sizeof
(
Field
*
))))
return
1
;
return
1
;
for
(
fld
=
table
->
field
,
old_fld
=
old
_field
;
*
fld
;
fld
++
,
old_fld
++
)
for
(
fld
=
table
->
field
,
old_fld
=
record1
_field
;
*
fld
;
fld
++
,
old_fld
++
)
{
{
/*
/*
QQ: it is supposed that it is ok to use this function for field
QQ: it is supposed that it is ok to use this function for field
...
@@ -406,7 +406,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
...
@@ -406,7 +406,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
parser
->
type
()
->
length
))
parser
->
type
()
->
length
))
{
{
Table_triggers_list
*
triggers
=
Table_triggers_list
*
triggers
=
new
(
&
table
->
mem_root
)
Table_triggers_list
();
new
(
&
table
->
mem_root
)
Table_triggers_list
(
table
);
if
(
!
triggers
)
if
(
!
triggers
)
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
...
@@ -417,8 +417,11 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
...
@@ -417,8 +417,11 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
table
->
triggers
=
triggers
;
table
->
triggers
=
triggers
;
/* TODO: This could be avoided if there is no ON UPDATE trigger. */
/*
if
(
triggers
->
prepare_old_row_accessors
(
table
))
TODO: This could be avoided if there is no triggers
for UPDATE and DELETE.
*/
if
(
triggers
->
prepare_record1_accessors
(
table
))
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
List_iterator_fast
<
LEX_STRING
>
it
(
triggers
->
definitions_list
);
List_iterator_fast
<
LEX_STRING
>
it
(
triggers
->
definitions_list
);
...
@@ -478,7 +481,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
...
@@ -478,7 +481,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
(
Item_trigger_field
*
)(
lex
.
trg_table_fields
.
first
);
(
Item_trigger_field
*
)(
lex
.
trg_table_fields
.
first
);
trg_field
;
trg_field
;
trg_field
=
trg_field
->
next_trg_field
)
trg_field
=
trg_field
->
next_trg_field
)
trg_field
->
setup_field
(
thd
,
table
,
lex
.
trg_chistics
.
event
);
trg_field
->
setup_field
(
thd
,
table
);
lex_end
(
&
lex
);
lex_end
(
&
lex
);
}
}
...
...
sql/sql_trigger.h
View file @
1449d44e
...
@@ -8,10 +8,20 @@ class Table_triggers_list: public Sql_alloc
...
@@ -8,10 +8,20 @@ class Table_triggers_list: public Sql_alloc
/* Triggers as SPs grouped by event, action_time */
/* Triggers as SPs grouped by event, action_time */
sp_head
*
bodies
[
3
][
2
];
sp_head
*
bodies
[
3
][
2
];
/*
/*
Copy of TABLE::Field array with field pointers set to old version
Copy of TABLE::Field array with field pointers set to TABLE::record[1]
of record, used for OLD values in trigger on UPDATE.
buffer instead of TABLE::record[0] (used for OLD values in on UPDATE
trigger and DELETE trigger when it is called for REPLACE).
*/
*/
Field
**
record1_field
;
/*
During execution of trigger new_field and old_field should point to the
array of fields representing new or old version of row correspondingly
(so it can point to TABLE::field or to Tale_triggers_list::record1_field)
*/
Field
**
new_field
;
Field
**
old_field
;
Field
**
old_field
;
/* TABLE instance for which this triggers list object was created */
TABLE
*
table
;
/*
/*
Names of triggers.
Names of triggers.
Should correspond to order of triggers on definitions_list,
Should correspond to order of triggers on definitions_list,
...
@@ -26,8 +36,8 @@ public:
...
@@ -26,8 +36,8 @@ public:
*/
*/
List
<
LEX_STRING
>
definitions_list
;
List
<
LEX_STRING
>
definitions_list
;
Table_triggers_list
()
:
Table_triggers_list
(
TABLE
*
table_arg
)
:
old_field
(
0
)
record1_field
(
0
),
table
(
table_arg
)
{
{
bzero
((
char
*
)
bodies
,
sizeof
(
bodies
));
bzero
((
char
*
)
bodies
,
sizeof
(
bodies
));
}
}
...
@@ -36,7 +46,8 @@ public:
...
@@ -36,7 +46,8 @@ public:
bool
create_trigger
(
THD
*
thd
,
TABLE_LIST
*
table
);
bool
create_trigger
(
THD
*
thd
,
TABLE_LIST
*
table
);
bool
drop_trigger
(
THD
*
thd
,
TABLE_LIST
*
table
);
bool
drop_trigger
(
THD
*
thd
,
TABLE_LIST
*
table
);
bool
process_triggers
(
THD
*
thd
,
trg_event_type
event
,
bool
process_triggers
(
THD
*
thd
,
trg_event_type
event
,
trg_action_time_type
time_type
)
trg_action_time_type
time_type
,
bool
old_row_is_record1
)
{
{
int
res
=
0
;
int
res
=
0
;
...
@@ -48,6 +59,17 @@ public:
...
@@ -48,6 +59,17 @@ public:
thd
->
net
.
no_send_ok
=
TRUE
;
thd
->
net
.
no_send_ok
=
TRUE
;
#endif
#endif
if
(
old_row_is_record1
)
{
old_field
=
record1_field
;
new_field
=
table
->
field
;
}
else
{
new_field
=
record1_field
;
old_field
=
table
->
field
;
}
/*
/*
FIXME: We should juggle with security context here (because trigger
FIXME: We should juggle with security context here (because trigger
should be invoked with creator rights).
should be invoked with creator rights).
...
@@ -79,8 +101,13 @@ public:
...
@@ -79,8 +101,13 @@ public:
bodies
[
TRG_EVENT_DELETE
][
TRG_ACTION_AFTER
]);
bodies
[
TRG_EVENT_DELETE
][
TRG_ACTION_AFTER
]);
}
}
bool
has_before_update_triggers
()
{
return
test
(
bodies
[
TRG_EVENT_UPDATE
][
TRG_ACTION_BEFORE
]);
}
friend
class
Item_trigger_field
;
friend
class
Item_trigger_field
;
private:
private:
bool
prepare_
old_row
_accessors
(
TABLE
*
table
);
bool
prepare_
record1
_accessors
(
TABLE
*
table
);
};
};
sql/sql_update.cc
View file @
1449d44e
...
@@ -398,14 +398,13 @@ int mysql_update(THD *thd,
...
@@ -398,14 +398,13 @@ int mysql_update(THD *thd,
if
(
!
(
select
&&
select
->
skip_record
()))
if
(
!
(
select
&&
select
->
skip_record
()))
{
{
store_record
(
table
,
record
[
1
]);
store_record
(
table
,
record
[
1
]);
if
(
fill_record
(
thd
,
fields
,
values
,
0
))
if
(
fill_record_n_invoke_before_triggers
(
thd
,
fields
,
values
,
0
,
table
->
triggers
,
TRG_EVENT_UPDATE
))
break
;
/* purecov: inspected */
break
;
/* purecov: inspected */
found
++
;
found
++
;
if
(
table
->
triggers
)
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_BEFORE
);
if
(
compare_record
(
table
,
query_id
))
if
(
compare_record
(
table
,
query_id
))
{
{
if
((
res
=
table_list
->
view_check_option
(
thd
,
ignore
))
!=
if
((
res
=
table_list
->
view_check_option
(
thd
,
ignore
))
!=
...
@@ -425,6 +424,14 @@ int mysql_update(THD *thd,
...
@@ -425,6 +424,14 @@ int mysql_update(THD *thd,
{
{
updated
++
;
updated
++
;
thd
->
no_trans_update
=
!
transactional_table
;
thd
->
no_trans_update
=
!
transactional_table
;
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_AFTER
,
TRUE
))
{
error
=
1
;
break
;
}
}
}
else
if
(
!
ignore
||
error
!=
HA_ERR_FOUND_DUPP_KEY
)
else
if
(
!
ignore
||
error
!=
HA_ERR_FOUND_DUPP_KEY
)
{
{
...
@@ -435,9 +442,6 @@ int mysql_update(THD *thd,
...
@@ -435,9 +442,6 @@ int mysql_update(THD *thd,
}
}
}
}
if
(
table
->
triggers
)
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_AFTER
);
if
(
!--
limit
&&
using_limit
)
if
(
!--
limit
&&
using_limit
)
{
{
error
=
-
1
;
// Simulate end of file
error
=
-
1
;
// Simulate end of file
...
@@ -1073,8 +1077,8 @@ multi_update::initialize_tables(JOIN *join)
...
@@ -1073,8 +1077,8 @@ multi_update::initialize_tables(JOIN *join)
NOTES
NOTES
We can update the first table in join on the fly if we know that
We can update the first table in join on the fly if we know that
a row in this tab
el
will never be read twice. This is true under
a row in this tab
le
will never be read twice. This is true under
the folloing conditions:
the follo
w
ing conditions:
- We are doing a table scan and the data is in a separate file (MyISAM) or
- We are doing a table scan and the data is in a separate file (MyISAM) or
if we don't update a clustered key.
if we don't update a clustered key.
...
@@ -1082,6 +1086,10 @@ multi_update::initialize_tables(JOIN *join)
...
@@ -1082,6 +1086,10 @@ multi_update::initialize_tables(JOIN *join)
- We are doing a range scan and we don't update the scan key or
- We are doing a range scan and we don't update the scan key or
the primary key for a clustered table handler.
the primary key for a clustered table handler.
When checking for above cases we also should take into account that
BEFORE UPDATE trigger potentially may change value of any field in row
being updated.
WARNING
WARNING
This code is a bit dependent of how make_join_readinfo() works.
This code is a bit dependent of how make_join_readinfo() works.
...
@@ -1100,15 +1108,21 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields)
...
@@ -1100,15 +1108,21 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields)
return
TRUE
;
// At most one matching row
return
TRUE
;
// At most one matching row
case
JT_REF
:
case
JT_REF
:
case
JT_REF_OR_NULL
:
case
JT_REF_OR_NULL
:
return
!
check_if_key_used
(
table
,
join_tab
->
ref
.
key
,
*
fields
);
return
!
check_if_key_used
(
table
,
join_tab
->
ref
.
key
,
*
fields
)
&&
!
(
table
->
triggers
&&
table
->
triggers
->
has_before_update_triggers
());
case
JT_ALL
:
case
JT_ALL
:
/* If range search on index */
/* If range search on index */
if
(
join_tab
->
quick
)
if
(
join_tab
->
quick
)
return
!
join_tab
->
quick
->
check_if_keys_used
(
fields
);
return
!
join_tab
->
quick
->
check_if_keys_used
(
fields
)
&&
!
(
table
->
triggers
&&
table
->
triggers
->
has_before_update_triggers
());
/* If scanning in clustered key */
/* If scanning in clustered key */
if
((
table
->
file
->
table_flags
()
&
HA_PRIMARY_KEY_IN_READ_INDEX
)
&&
if
((
table
->
file
->
table_flags
()
&
HA_PRIMARY_KEY_IN_READ_INDEX
)
&&
table
->
s
->
primary_key
<
MAX_KEY
)
table
->
s
->
primary_key
<
MAX_KEY
)
return
!
check_if_key_used
(
table
,
table
->
s
->
primary_key
,
*
fields
);
return
!
check_if_key_used
(
table
,
table
->
s
->
primary_key
,
*
fields
)
&&
!
(
table
->
triggers
&&
table
->
triggers
->
has_before_update_triggers
());
return
TRUE
;
return
TRUE
;
default:
default:
break
;
// Avoid compler warning
break
;
// Avoid compler warning
...
@@ -1171,8 +1185,10 @@ bool multi_update::send_data(List<Item> ¬_used_values)
...
@@ -1171,8 +1185,10 @@ bool multi_update::send_data(List<Item> ¬_used_values)
{
{
table
->
status
|=
STATUS_UPDATED
;
table
->
status
|=
STATUS_UPDATED
;
store_record
(
table
,
record
[
1
]);
store_record
(
table
,
record
[
1
]);
if
(
fill_record
(
thd
,
*
fields_for_table
[
offset
],
if
(
fill_record_n_invoke_before_triggers
(
thd
,
*
fields_for_table
[
offset
],
*
values_for_table
[
offset
],
0
))
*
values_for_table
[
offset
],
0
,
table
->
triggers
,
TRG_EVENT_UPDATE
))
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
found
++
;
found
++
;
...
@@ -1208,8 +1224,15 @@ bool multi_update::send_data(List<Item> ¬_used_values)
...
@@ -1208,8 +1224,15 @@ bool multi_update::send_data(List<Item> ¬_used_values)
DBUG_RETURN
(
1
);
DBUG_RETURN
(
1
);
}
}
}
}
else
if
(
!
table
->
file
->
has_transactions
())
else
thd
->
no_trans_update
=
1
;
{
if
(
!
table
->
file
->
has_transactions
())
thd
->
no_trans_update
=
1
;
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_AFTER
,
TRUE
))
DBUG_RETURN
(
1
);
}
}
}
}
}
else
else
...
@@ -1330,6 +1353,11 @@ int multi_update::do_updates(bool from_send_error)
...
@@ -1330,6 +1353,11 @@ int multi_update::do_updates(bool from_send_error)
copy_field_ptr
++
)
copy_field_ptr
++
)
(
*
copy_field_ptr
->
do_copy
)(
copy_field_ptr
);
(
*
copy_field_ptr
->
do_copy
)(
copy_field_ptr
);
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_BEFORE
,
TRUE
))
goto
err2
;
if
(
compare_record
(
table
,
thd
->
query_id
))
if
(
compare_record
(
table
,
thd
->
query_id
))
{
{
if
((
local_error
=
table
->
file
->
update_row
(
table
->
record
[
1
],
if
((
local_error
=
table
->
file
->
update_row
(
table
->
record
[
1
],
...
@@ -1339,6 +1367,11 @@ int multi_update::do_updates(bool from_send_error)
...
@@ -1339,6 +1367,11 @@ int multi_update::do_updates(bool from_send_error)
goto
err
;
goto
err
;
}
}
updated
++
;
updated
++
;
if
(
table
->
triggers
&&
table
->
triggers
->
process_triggers
(
thd
,
TRG_EVENT_UPDATE
,
TRG_ACTION_AFTER
,
TRUE
))
goto
err2
;
}
}
}
}
...
@@ -1361,6 +1394,7 @@ err:
...
@@ -1361,6 +1394,7 @@ err:
table
->
file
->
print_error
(
local_error
,
MYF
(
0
));
table
->
file
->
print_error
(
local_error
,
MYF
(
0
));
}
}
err2:
(
void
)
table
->
file
->
ha_rnd_end
();
(
void
)
table
->
file
->
ha_rnd_end
();
(
void
)
tmp_table
->
file
->
ha_rnd_end
();
(
void
)
tmp_table
->
file
->
ha_rnd_end
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment