Merge sanja.is.com.ua:/home/bell/mysql/bk/mysql-5.0
into sanja.is.com.ua:/home/bell/mysql/bk/work-show-5.0 sql/item.cc: Auto merged sql/item.h: Auto merged sql/mysql_priv.h: Auto merged sql/sql_base.cc: Auto merged sql/sql_delete.cc: Auto merged sql/sql_insert.cc: Auto merged sql/sql_lex.cc: Auto merged sql/sql_parse.cc: Auto merged sql/sql_update.cc: Auto merged sql/table.h: Auto merged
This commit is contained in:
commit
f3d4db99c3
@ -375,4 +375,9 @@
|
|||||||
#define ER_VIEW_INVALID 1356
|
#define ER_VIEW_INVALID 1356
|
||||||
#define ER_SP_NO_DROP_SP 1357
|
#define ER_SP_NO_DROP_SP 1357
|
||||||
#define ER_SP_GOTO_IN_HNDLR 1358
|
#define ER_SP_GOTO_IN_HNDLR 1358
|
||||||
#define ER_ERROR_MESSAGES 359
|
#define ER_TRG_ALREADY_EXISTS 1359
|
||||||
|
#define ER_TRG_DOES_NOT_EXIST 1360
|
||||||
|
#define ER_TRG_ON_VIEW_OR_TEMP_TABLE 1361
|
||||||
|
#define ER_TRG_CANT_CHANGE_ROW 1362
|
||||||
|
#define ER_TRG_NO_SUCH_ROW_IN_TRG 1363
|
||||||
|
#define ER_ERROR_MESSAGES 364
|
||||||
|
@ -1820,6 +1820,19 @@ Ok
|
|||||||
Ok
|
Ok
|
||||||
drop procedure bug5258|
|
drop procedure bug5258|
|
||||||
drop procedure bug5258_aux|
|
drop procedure bug5258_aux|
|
||||||
|
create function bug4487() returns char
|
||||||
|
begin
|
||||||
|
declare v char;
|
||||||
|
return v;
|
||||||
|
end|
|
||||||
|
Warnings:
|
||||||
|
Warning 1311 Referring to uninitialized variable v
|
||||||
|
select bug4487()|
|
||||||
|
bug4487()
|
||||||
|
NULL
|
||||||
|
Warnings:
|
||||||
|
Warning 1311 Referring to uninitialized variable v
|
||||||
|
drop function bug4487|
|
||||||
drop table if exists fac|
|
drop table if exists fac|
|
||||||
create table fac (n int unsigned not null primary key, f bigint unsigned)|
|
create table fac (n int unsigned not null primary key, f bigint unsigned)|
|
||||||
create procedure ifac(n int unsigned)
|
create procedure ifac(n int unsigned)
|
||||||
|
171
mysql-test/r/trigger.result
Normal file
171
mysql-test/r/trigger.result
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
drop table if exists t1, t2;
|
||||||
|
drop view if exists v1;
|
||||||
|
create table t1 (i int);
|
||||||
|
create trigger trg before insert on t1 for each row set @a:=1;
|
||||||
|
set @a:=0;
|
||||||
|
select @a;
|
||||||
|
@a
|
||||||
|
0
|
||||||
|
insert into t1 values (1);
|
||||||
|
select @a;
|
||||||
|
@a
|
||||||
|
1
|
||||||
|
drop trigger t1.trg;
|
||||||
|
create trigger trg before insert on t1 for each row set @a:=new.i;
|
||||||
|
insert into t1 values (123);
|
||||||
|
select @a;
|
||||||
|
@a
|
||||||
|
123
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
create table t1 (i int not null, j int);
|
||||||
|
create trigger trg before insert on t1 for each row
|
||||||
|
begin
|
||||||
|
if isnull(new.j) then
|
||||||
|
set new.j:= new.i * 10;
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
insert into t1 (i) values (1)|
|
||||||
|
insert into t1 (i,j) values (2, 3)|
|
||||||
|
select * from t1|
|
||||||
|
i j
|
||||||
|
1 10
|
||||||
|
2 3
|
||||||
|
drop trigger t1.trg|
|
||||||
|
drop table t1|
|
||||||
|
create table t1 (i int not null primary key);
|
||||||
|
create trigger trg after insert on t1 for each row
|
||||||
|
set @a:= if(@a,concat(@a, ":", new.i), new.i);
|
||||||
|
set @a:="";
|
||||||
|
insert into t1 values (2),(3),(4),(5);
|
||||||
|
select @a;
|
||||||
|
@a
|
||||||
|
2:3:4:5
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
create table t1 (aid int not null primary key, balance int not null default 0);
|
||||||
|
insert into t1 values (1, 1000), (2,3000);
|
||||||
|
create trigger trg before update on t1 for each row
|
||||||
|
begin
|
||||||
|
declare loc_err varchar(255);
|
||||||
|
if abs(new.balance - old.balance) > 1000 then
|
||||||
|
set new.balance:= old.balance;
|
||||||
|
set loc_err := concat("Too big change for aid = ", new.aid);
|
||||||
|
set @update_failed:= if(@update_failed, concat(@a, ":", loc_err), loc_err);
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
set @update_failed:=""|
|
||||||
|
update t1 set balance=1500|
|
||||||
|
select @update_failed;
|
||||||
|
select * from t1|
|
||||||
|
@update_failed
|
||||||
|
Too big change for aid = 2
|
||||||
|
aid balance
|
||||||
|
1 1500
|
||||||
|
2 3000
|
||||||
|
drop trigger t1.trg|
|
||||||
|
drop table t1|
|
||||||
|
create table t1 (i int);
|
||||||
|
insert into t1 values (1),(2),(3),(4);
|
||||||
|
create trigger trg after update on t1 for each row
|
||||||
|
set @total_change:=@total_change + new.i - old.i;
|
||||||
|
set @total_change:=0;
|
||||||
|
update t1 set i=3;
|
||||||
|
select @total_change;
|
||||||
|
@total_change
|
||||||
|
2
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
create table t1 (i int);
|
||||||
|
insert into t1 values (1),(2),(3),(4);
|
||||||
|
create trigger trg before delete on t1 for each row
|
||||||
|
set @del_sum:= @del_sum + old.i;
|
||||||
|
set @del_sum:= 0;
|
||||||
|
delete from t1 where i <= 3;
|
||||||
|
select @del_sum;
|
||||||
|
@del_sum
|
||||||
|
6
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
create table t1 (i int);
|
||||||
|
insert into t1 values (1),(2),(3),(4);
|
||||||
|
create trigger trg after delete on t1 for each row set @del:= 1;
|
||||||
|
set @del:= 0;
|
||||||
|
delete from t1 where i <> 0;
|
||||||
|
select @del;
|
||||||
|
@del
|
||||||
|
1
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
create table t1 (i int, j int);
|
||||||
|
create trigger trg1 before insert on t1 for each row
|
||||||
|
begin
|
||||||
|
if new.j > 10 then
|
||||||
|
set new.j := 10;
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
create trigger trg2 before update on t1 for each row
|
||||||
|
begin
|
||||||
|
if old.i % 2 = 0 then
|
||||||
|
set new.j := -1;
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
create trigger trg3 after update on t1 for each row
|
||||||
|
begin
|
||||||
|
if new.j = -1 then
|
||||||
|
set @fired:= "Yes";
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
set @fired:="";
|
||||||
|
insert into t1 values (1,2),(2,3),(3,14);
|
||||||
|
select @fired;
|
||||||
|
@fired
|
||||||
|
|
||||||
|
select * from t1;
|
||||||
|
i j
|
||||||
|
1 2
|
||||||
|
2 3
|
||||||
|
3 10
|
||||||
|
update t1 set j= 20;
|
||||||
|
select @fired;
|
||||||
|
@fired
|
||||||
|
Yes
|
||||||
|
select * from t1;
|
||||||
|
i j
|
||||||
|
1 20
|
||||||
|
2 -1
|
||||||
|
3 20
|
||||||
|
drop trigger t1.trg1;
|
||||||
|
drop trigger t1.trg2;
|
||||||
|
drop trigger t1.trg3;
|
||||||
|
drop table t1;
|
||||||
|
create table t1 (i int);
|
||||||
|
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
|
||||||
|
create trigger trg before delete on t1 for each row set @a:= new.i;
|
||||||
|
ERROR HY000: There is no NEW row in on DELETE trigger
|
||||||
|
create trigger trg before update on t1 for each row set old.i:=1;
|
||||||
|
ERROR HY000: Updating of OLD row is not allowed in trigger
|
||||||
|
create trigger trg before delete on t1 for each row set new.i:=1;
|
||||||
|
ERROR HY000: There is no NEW row in on DELETE trigger
|
||||||
|
create trigger trg after update on t1 for each row set new.i:=1;
|
||||||
|
ERROR HY000: Updating of NEW row is not allowed in after trigger
|
||||||
|
create trigger trg before insert on t2 for each row set @a:=1;
|
||||||
|
ERROR 42S02: Table 'test.t2' doesn't exist
|
||||||
|
create trigger trg before insert on t1 for each row set @a:=1;
|
||||||
|
create trigger trg after insert on t1 for each row set @a:=1;
|
||||||
|
ERROR HY000: Trigger already exists
|
||||||
|
create trigger trg2 before insert on t1 for each row set @a:=1;
|
||||||
|
ERROR HY000: Trigger already exists
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop trigger t1.trg;
|
||||||
|
ERROR HY000: Trigger does not exist
|
||||||
|
create view v1 as select * from t1;
|
||||||
|
create trigger trg before insert on v1 for each row set @a:=1;
|
||||||
|
ERROR HY000: Trigger's 'v1' is view or temporary table
|
||||||
|
drop view v1;
|
||||||
|
drop table t1;
|
||||||
|
create temporary table t1 (i int);
|
||||||
|
create trigger trg before insert on t1 for each row set @a:=1;
|
||||||
|
ERROR HY000: Trigger's 't1' is view or temporary table
|
||||||
|
drop table t1;
|
@ -1987,6 +1987,18 @@ call bug5258_aux()|
|
|||||||
drop procedure bug5258|
|
drop procedure bug5258|
|
||||||
drop procedure bug5258_aux|
|
drop procedure bug5258_aux|
|
||||||
|
|
||||||
|
#
|
||||||
|
# BUG#4487: Stored procedure connection aborted if uninitialized char
|
||||||
|
#
|
||||||
|
create function bug4487() returns char
|
||||||
|
begin
|
||||||
|
declare v char;
|
||||||
|
return v;
|
||||||
|
end|
|
||||||
|
|
||||||
|
select bug4487()|
|
||||||
|
drop function bug4487|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Some "real" examples
|
# Some "real" examples
|
||||||
|
195
mysql-test/t/trigger.test
Normal file
195
mysql-test/t/trigger.test
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
#
|
||||||
|
# Basic triggers test
|
||||||
|
#
|
||||||
|
|
||||||
|
--disable_warnings
|
||||||
|
drop table if exists t1, t2;
|
||||||
|
drop view if exists v1;
|
||||||
|
--enable_warnings
|
||||||
|
|
||||||
|
create table t1 (i int);
|
||||||
|
|
||||||
|
# let us test some very simple trigger
|
||||||
|
create trigger trg before insert on t1 for each row set @a:=1;
|
||||||
|
set @a:=0;
|
||||||
|
select @a;
|
||||||
|
insert into t1 values (1);
|
||||||
|
select @a;
|
||||||
|
drop trigger t1.trg;
|
||||||
|
|
||||||
|
# let us test simple trigger reading some values
|
||||||
|
create trigger trg before insert on t1 for each row set @a:=new.i;
|
||||||
|
insert into t1 values (123);
|
||||||
|
select @a;
|
||||||
|
drop trigger t1.trg;
|
||||||
|
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
# Let us test before insert trigger
|
||||||
|
# Such triggers can be used for setting complex default values
|
||||||
|
create table t1 (i int not null, j int);
|
||||||
|
delimiter |;
|
||||||
|
create trigger trg before insert on t1 for each row
|
||||||
|
begin
|
||||||
|
if isnull(new.j) then
|
||||||
|
set new.j:= new.i * 10;
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
insert into t1 (i) values (1)|
|
||||||
|
insert into t1 (i,j) values (2, 3)|
|
||||||
|
select * from t1|
|
||||||
|
drop trigger t1.trg|
|
||||||
|
drop table t1|
|
||||||
|
delimiter ;|
|
||||||
|
|
||||||
|
# After insert trigger
|
||||||
|
# Useful for aggregating data
|
||||||
|
create table t1 (i int not null primary key);
|
||||||
|
create trigger trg after insert on t1 for each row
|
||||||
|
set @a:= if(@a,concat(@a, ":", new.i), new.i);
|
||||||
|
set @a:="";
|
||||||
|
insert into t1 values (2),(3),(4),(5);
|
||||||
|
select @a;
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
# Before update trigger
|
||||||
|
# (In future we will achieve this via proper error handling in triggers)
|
||||||
|
create table t1 (aid int not null primary key, balance int not null default 0);
|
||||||
|
insert into t1 values (1, 1000), (2,3000);
|
||||||
|
delimiter |;
|
||||||
|
create trigger trg before update on t1 for each row
|
||||||
|
begin
|
||||||
|
declare loc_err varchar(255);
|
||||||
|
if abs(new.balance - old.balance) > 1000 then
|
||||||
|
set new.balance:= old.balance;
|
||||||
|
set loc_err := concat("Too big change for aid = ", new.aid);
|
||||||
|
set @update_failed:= if(@update_failed, concat(@a, ":", loc_err), loc_err);
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
set @update_failed:=""|
|
||||||
|
update t1 set balance=1500|
|
||||||
|
select @update_failed;
|
||||||
|
select * from t1|
|
||||||
|
drop trigger t1.trg|
|
||||||
|
drop table t1|
|
||||||
|
delimiter ;|
|
||||||
|
|
||||||
|
# After update trigger
|
||||||
|
create table t1 (i int);
|
||||||
|
insert into t1 values (1),(2),(3),(4);
|
||||||
|
create trigger trg after update on t1 for each row
|
||||||
|
set @total_change:=@total_change + new.i - old.i;
|
||||||
|
set @total_change:=0;
|
||||||
|
update t1 set i=3;
|
||||||
|
select @total_change;
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
# Before delete trigger
|
||||||
|
# This can be used for aggregation too :)
|
||||||
|
create table t1 (i int);
|
||||||
|
insert into t1 values (1),(2),(3),(4);
|
||||||
|
create trigger trg before delete on t1 for each row
|
||||||
|
set @del_sum:= @del_sum + old.i;
|
||||||
|
set @del_sum:= 0;
|
||||||
|
delete from t1 where i <= 3;
|
||||||
|
select @del_sum;
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
# After delete trigger.
|
||||||
|
# Just run out of imagination.
|
||||||
|
create table t1 (i int);
|
||||||
|
insert into t1 values (1),(2),(3),(4);
|
||||||
|
create trigger trg after delete on t1 for each row set @del:= 1;
|
||||||
|
set @del:= 0;
|
||||||
|
delete from t1 where i <> 0;
|
||||||
|
select @del;
|
||||||
|
drop trigger t1.trg;
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
# Several triggers on one table
|
||||||
|
create table t1 (i int, j int);
|
||||||
|
|
||||||
|
delimiter |;
|
||||||
|
create trigger trg1 before insert on t1 for each row
|
||||||
|
begin
|
||||||
|
if new.j > 10 then
|
||||||
|
set new.j := 10;
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
create trigger trg2 before update on t1 for each row
|
||||||
|
begin
|
||||||
|
if old.i % 2 = 0 then
|
||||||
|
set new.j := -1;
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
create trigger trg3 after update on t1 for each row
|
||||||
|
begin
|
||||||
|
if new.j = -1 then
|
||||||
|
set @fired:= "Yes";
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
delimiter ;|
|
||||||
|
set @fired:="";
|
||||||
|
insert into t1 values (1,2),(2,3),(3,14);
|
||||||
|
select @fired;
|
||||||
|
select * from t1;
|
||||||
|
update t1 set j= 20;
|
||||||
|
select @fired;
|
||||||
|
select * from t1;
|
||||||
|
|
||||||
|
drop trigger t1.trg1;
|
||||||
|
drop trigger t1.trg2;
|
||||||
|
drop trigger t1.trg3;
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test of wrong column specifiers in triggers
|
||||||
|
#
|
||||||
|
create table t1 (i int);
|
||||||
|
|
||||||
|
--error 1363
|
||||||
|
create trigger trg before insert on t1 for each row set @a:= old.i;
|
||||||
|
--error 1363
|
||||||
|
create trigger trg before delete on t1 for each row set @a:= new.i;
|
||||||
|
--error 1362
|
||||||
|
create trigger trg before update on t1 for each row set old.i:=1;
|
||||||
|
--error 1363
|
||||||
|
create trigger trg before delete on t1 for each row set new.i:=1;
|
||||||
|
--error 1362
|
||||||
|
create trigger trg after update on t1 for each row set new.i:=1;
|
||||||
|
# TODO: We should also test wrong field names here, we don't do it now
|
||||||
|
# because proper error handling is not in place yet.
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Let us test various trigger creation errors
|
||||||
|
#
|
||||||
|
#
|
||||||
|
--error 1146
|
||||||
|
create trigger trg before insert on t2 for each row set @a:=1;
|
||||||
|
|
||||||
|
create trigger trg before insert on t1 for each row set @a:=1;
|
||||||
|
--error 1359
|
||||||
|
create trigger trg after insert on t1 for each row set @a:=1;
|
||||||
|
--error 1359
|
||||||
|
create trigger trg2 before insert on t1 for each row set @a:=1;
|
||||||
|
drop trigger t1.trg;
|
||||||
|
|
||||||
|
--error 1360
|
||||||
|
drop trigger t1.trg;
|
||||||
|
|
||||||
|
create view v1 as select * from t1;
|
||||||
|
--error 1361
|
||||||
|
create trigger trg before insert on v1 for each row set @a:=1;
|
||||||
|
drop view v1;
|
||||||
|
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
create temporary table t1 (i int);
|
||||||
|
--error 1361
|
||||||
|
create trigger trg before insert on t1 for each row set @a:=1;
|
||||||
|
drop table t1;
|
@ -61,7 +61,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
|
|||||||
spatial.h gstream.h client_settings.h tzfile.h \
|
spatial.h gstream.h client_settings.h tzfile.h \
|
||||||
tztime.h \
|
tztime.h \
|
||||||
sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \
|
sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \
|
||||||
parse_file.h sql_view.h \
|
parse_file.h sql_view.h sql_trigger.h \
|
||||||
examples/ha_example.h examples/ha_archive.h \
|
examples/ha_example.h examples/ha_archive.h \
|
||||||
examples/ha_tina.h
|
examples/ha_tina.h
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \
|
|||||||
gstream.cc spatial.cc sql_help.cc protocol_cursor.cc \
|
gstream.cc spatial.cc sql_help.cc protocol_cursor.cc \
|
||||||
tztime.cc my_time.c \
|
tztime.cc my_time.c \
|
||||||
sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \
|
sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \
|
||||||
sp_cache.cc parse_file.cc \
|
sp_cache.cc parse_file.cc sql_trigger.cc \
|
||||||
examples/ha_example.cc examples/ha_archive.cc \
|
examples/ha_example.cc examples/ha_archive.cc \
|
||||||
examples/ha_tina.cc
|
examples/ha_tina.cc
|
||||||
gen_lex_hash_SOURCES = gen_lex_hash.cc
|
gen_lex_hash_SOURCES = gen_lex_hash.cc
|
||||||
|
93
sql/item.cc
93
sql/item.cc
@ -24,6 +24,8 @@
|
|||||||
#include "my_dir.h"
|
#include "my_dir.h"
|
||||||
#include "sp_rcontext.h"
|
#include "sp_rcontext.h"
|
||||||
#include "sql_acl.h"
|
#include "sql_acl.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sql_trigger.h"
|
||||||
|
|
||||||
static void mark_as_dependent(THD *thd,
|
static void mark_as_dependent(THD *thd,
|
||||||
SELECT_LEX *last, SELECT_LEX *current,
|
SELECT_LEX *last, SELECT_LEX *current,
|
||||||
@ -2462,6 +2464,97 @@ void Item_insert_value::print(String *str)
|
|||||||
str->append(')');
|
str->append(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Bind item representing field of row being changed in trigger
|
||||||
|
to appropriate Field object.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
setup_field()
|
||||||
|
thd - current thread context
|
||||||
|
table - table of trigger (and where we looking for fields)
|
||||||
|
event - type of trigger event
|
||||||
|
|
||||||
|
NOTE
|
||||||
|
This function does almost the same as fix_fields() for Item_field
|
||||||
|
but is invoked during trigger definition parsing and takes TABLE
|
||||||
|
object as its argument.
|
||||||
|
|
||||||
|
RETURN VALUES
|
||||||
|
0 ok
|
||||||
|
1 field was not found.
|
||||||
|
*/
|
||||||
|
bool Item_trigger_field::setup_field(THD *thd, TABLE *table,
|
||||||
|
enum trg_event_type event)
|
||||||
|
{
|
||||||
|
bool result= 1;
|
||||||
|
uint field_idx= (uint)-1;
|
||||||
|
bool save_set_query_id= thd->set_query_id;
|
||||||
|
|
||||||
|
/* TODO: Think more about consequences of this step. */
|
||||||
|
thd->set_query_id= 0;
|
||||||
|
|
||||||
|
if (find_field_in_real_table(thd, table, field_name,
|
||||||
|
strlen(field_name), 0, 0,
|
||||||
|
&field_idx))
|
||||||
|
{
|
||||||
|
field= (row_version == OLD_ROW && event == TRG_EVENT_UPDATE) ?
|
||||||
|
table->triggers->old_field[field_idx] :
|
||||||
|
table->field[field_idx];
|
||||||
|
result= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
thd->set_query_id= save_set_query_id;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Item_trigger_field::eq(const Item *item, bool binary_cmp) const
|
||||||
|
{
|
||||||
|
return item->type() == TRIGGER_FIELD_ITEM &&
|
||||||
|
row_version == ((Item_trigger_field *)item)->row_version &&
|
||||||
|
!my_strcasecmp(system_charset_info, field_name,
|
||||||
|
((Item_trigger_field *)item)->field_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Item_trigger_field::fix_fields(THD *thd,
|
||||||
|
TABLE_LIST *table_list,
|
||||||
|
Item **items)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Since trigger is object tightly associated with TABLE object most
|
||||||
|
of its set up can be performed during trigger loading i.e. trigger
|
||||||
|
parsing! So we have little to do in fix_fields. :)
|
||||||
|
FIXME may be we still should bother about permissions here.
|
||||||
|
*/
|
||||||
|
DBUG_ASSERT(fixed == 0);
|
||||||
|
// QQ: May be this should be moved to setup_field?
|
||||||
|
set_field(field);
|
||||||
|
fixed= 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Item_trigger_field::print(String *str)
|
||||||
|
{
|
||||||
|
str->append((row_version == NEW_ROW) ? "NEW" : "OLD", 3);
|
||||||
|
str->append('.');
|
||||||
|
str->append(field_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Item_trigger_field::cleanup()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Since special nature of Item_trigger_field we should not do most of
|
||||||
|
things from Item_field::cleanup() or Item_ident::cleanup() here.
|
||||||
|
*/
|
||||||
|
Item::cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If item is a const function, calculate it and return a const item
|
If item is a const function, calculate it and return a const item
|
||||||
The original item is freed if not returned
|
The original item is freed if not returned
|
||||||
|
54
sql/item.h
54
sql/item.h
@ -105,7 +105,7 @@ public:
|
|||||||
PROC_ITEM,COND_ITEM, REF_ITEM, FIELD_STD_ITEM,
|
PROC_ITEM,COND_ITEM, REF_ITEM, FIELD_STD_ITEM,
|
||||||
FIELD_VARIANCE_ITEM, INSERT_VALUE_ITEM,
|
FIELD_VARIANCE_ITEM, INSERT_VALUE_ITEM,
|
||||||
SUBSELECT_ITEM, ROW_ITEM, CACHE_ITEM, TYPE_HOLDER,
|
SUBSELECT_ITEM, ROW_ITEM, CACHE_ITEM, TYPE_HOLDER,
|
||||||
PARAM_ITEM};
|
PARAM_ITEM, TRIGGER_FIELD_ITEM};
|
||||||
|
|
||||||
enum cond_result { COND_UNDEF,COND_OK,COND_TRUE,COND_FALSE };
|
enum cond_result { COND_UNDEF,COND_OK,COND_TRUE,COND_FALSE };
|
||||||
|
|
||||||
@ -440,6 +440,7 @@ public:
|
|||||||
|
|
||||||
class Item_field :public Item_ident
|
class Item_field :public Item_ident
|
||||||
{
|
{
|
||||||
|
protected:
|
||||||
void set_field(Field *field);
|
void set_field(Field *field);
|
||||||
public:
|
public:
|
||||||
Field *field,*result_field;
|
Field *field,*result_field;
|
||||||
@ -1198,6 +1199,57 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
We need this two enums here instead of sql_lex.h because
|
||||||
|
at least one of them is used by Item_trigger_field interface.
|
||||||
|
|
||||||
|
Time when trigger is invoked (i.e. before or after row actually
|
||||||
|
inserted/updated/deleted).
|
||||||
|
*/
|
||||||
|
enum trg_action_time_type
|
||||||
|
{
|
||||||
|
TRG_ACTION_BEFORE= 0, TRG_ACTION_AFTER= 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Event on which trigger is invoked.
|
||||||
|
*/
|
||||||
|
enum trg_event_type
|
||||||
|
{
|
||||||
|
TRG_EVENT_INSERT= 0 , TRG_EVENT_UPDATE= 1, TRG_EVENT_DELETE= 2
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Represents NEW/OLD version of field of row which is
|
||||||
|
changed/read in trigger.
|
||||||
|
|
||||||
|
Note: For this item actual binding to Field object happens not during
|
||||||
|
fix_fields() (like for Item_field) but during parsing of trigger
|
||||||
|
definition, when table is opened, with special setup_field() call.
|
||||||
|
*/
|
||||||
|
class Item_trigger_field : public Item_field
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/* Is this item represents row from NEW or OLD row ? */
|
||||||
|
enum row_version_type {OLD_ROW, NEW_ROW};
|
||||||
|
row_version_type row_version;
|
||||||
|
|
||||||
|
Item_trigger_field(row_version_type row_ver_par,
|
||||||
|
const char *field_name_par):
|
||||||
|
Item_field((const char *)NULL, (const char *)NULL, field_name_par),
|
||||||
|
row_version(row_ver_par)
|
||||||
|
{}
|
||||||
|
bool setup_field(THD *thd, TABLE *table, enum trg_event_type event);
|
||||||
|
enum Type type() const { return TRIGGER_FIELD_ITEM; }
|
||||||
|
bool eq(const Item *item, bool binary_cmp) const;
|
||||||
|
bool fix_fields(THD *, struct st_table_list *, Item **);
|
||||||
|
void print(String *str);
|
||||||
|
table_map used_tables() const { return (table_map)0L; }
|
||||||
|
void cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class Item_cache: public Item
|
class Item_cache: public Item
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
@ -2580,6 +2580,16 @@ void Item_func_set_user_var::print(String *str)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Item_func_set_user_var::print_as_stmt(String *str)
|
||||||
|
{
|
||||||
|
str->append("set @", 5);
|
||||||
|
str->append(name.str, name.length);
|
||||||
|
str->append(":=", 2);
|
||||||
|
args[0]->print(str);
|
||||||
|
str->append(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
String *
|
String *
|
||||||
Item_func_get_user_var::val_str(String *str)
|
Item_func_get_user_var::val_str(String *str)
|
||||||
{
|
{
|
||||||
@ -3329,6 +3339,11 @@ Item_func_sp::execute(Item **itp)
|
|||||||
sp_change_security_context(thd, m_sp, &save_ctx);
|
sp_change_security_context(thd, m_sp, &save_ctx);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
We don't need to surpress senfing of ok packet here (by setting
|
||||||
|
thd->net.no_send_ok to true), because we are not allowing statements
|
||||||
|
in functions now.
|
||||||
|
*/
|
||||||
res= m_sp->execute_function(thd, args, arg_count, itp);
|
res= m_sp->execute_function(thd, args, arg_count, itp);
|
||||||
|
|
||||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||||
|
@ -948,6 +948,7 @@ public:
|
|||||||
bool fix_fields(THD *thd, struct st_table_list *tables, Item **ref);
|
bool fix_fields(THD *thd, struct st_table_list *tables, Item **ref);
|
||||||
void fix_length_and_dec();
|
void fix_length_and_dec();
|
||||||
void print(String *str);
|
void print(String *str);
|
||||||
|
void print_as_stmt(String *str);
|
||||||
const char *func_name() const { return "set_user_var"; }
|
const char *func_name() const { return "set_user_var"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1130,25 +1131,31 @@ public:
|
|||||||
double val()
|
double val()
|
||||||
{
|
{
|
||||||
Item *it;
|
Item *it;
|
||||||
|
double d;
|
||||||
|
|
||||||
if (execute(&it))
|
if (execute(&it))
|
||||||
{
|
{
|
||||||
null_value= 1;
|
null_value= 1;
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
return it->val();
|
d= it->val();
|
||||||
|
null_value= it->null_value;
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
String *val_str(String *str)
|
String *val_str(String *str)
|
||||||
{
|
{
|
||||||
Item *it;
|
Item *it;
|
||||||
|
String *s;
|
||||||
|
|
||||||
if (execute(&it))
|
if (execute(&it))
|
||||||
{
|
{
|
||||||
null_value= 1;
|
null_value= 1;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return it->val_str(str);
|
s= it->val_str(str);
|
||||||
|
null_value= it->null_value;
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fix_length_and_dec();
|
void fix_length_and_dec();
|
||||||
|
@ -167,6 +167,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "DUMPFILE", SYM(DUMPFILE)},
|
{ "DUMPFILE", SYM(DUMPFILE)},
|
||||||
{ "DUPLICATE", SYM(DUPLICATE_SYM)},
|
{ "DUPLICATE", SYM(DUPLICATE_SYM)},
|
||||||
{ "DYNAMIC", SYM(DYNAMIC_SYM)},
|
{ "DYNAMIC", SYM(DYNAMIC_SYM)},
|
||||||
|
{ "EACH", SYM(EACH_SYM)},
|
||||||
{ "ELSE", SYM(ELSE)},
|
{ "ELSE", SYM(ELSE)},
|
||||||
{ "ELSEIF", SYM(ELSEIF_SYM)},
|
{ "ELSEIF", SYM(ELSEIF_SYM)},
|
||||||
{ "ENABLE", SYM(ENABLE_SYM)},
|
{ "ENABLE", SYM(ENABLE_SYM)},
|
||||||
@ -470,6 +471,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "TO", SYM(TO_SYM)},
|
{ "TO", SYM(TO_SYM)},
|
||||||
{ "TRAILING", SYM(TRAILING)},
|
{ "TRAILING", SYM(TRAILING)},
|
||||||
{ "TRANSACTION", SYM(TRANSACTION_SYM)},
|
{ "TRANSACTION", SYM(TRANSACTION_SYM)},
|
||||||
|
{ "TRIGGER", SYM(TRIGGER_SYM)},
|
||||||
{ "TRUE", SYM(TRUE_SYM)},
|
{ "TRUE", SYM(TRUE_SYM)},
|
||||||
{ "TRUNCATE", SYM(TRUNCATE_SYM)},
|
{ "TRUNCATE", SYM(TRUNCATE_SYM)},
|
||||||
{ "TYPE", SYM(TYPE_SYM)},
|
{ "TYPE", SYM(TYPE_SYM)},
|
||||||
|
@ -481,6 +481,7 @@ int mysql_rm_table_part2_with_lock(THD *thd, TABLE_LIST *tables,
|
|||||||
bool log_query);
|
bool log_query);
|
||||||
int quick_rm_table(enum db_type base,const char *db,
|
int quick_rm_table(enum db_type base,const char *db,
|
||||||
const char *table_name);
|
const char *table_name);
|
||||||
|
void close_cached_table(THD *thd, TABLE *table);
|
||||||
bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list);
|
bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list);
|
||||||
bool mysql_change_db(THD *thd,const char *name);
|
bool mysql_change_db(THD *thd,const char *name);
|
||||||
void mysql_parse(THD *thd,char *inBuf,uint length);
|
void mysql_parse(THD *thd,char *inBuf,uint length);
|
||||||
@ -622,6 +623,7 @@ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds);
|
|||||||
int mysql_delete(THD *thd, TABLE_LIST *table, COND *conds, SQL_LIST *order,
|
int mysql_delete(THD *thd, TABLE_LIST *table, COND *conds, SQL_LIST *order,
|
||||||
ha_rows rows, ulong options);
|
ha_rows rows, ulong options);
|
||||||
int mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok);
|
int mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok);
|
||||||
|
int mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create);
|
||||||
TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update);
|
TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update);
|
||||||
TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
|
TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
|
||||||
bool *refresh);
|
bool *refresh);
|
||||||
@ -650,6 +652,10 @@ find_field_in_table(THD *thd, TABLE_LIST *table_list,
|
|||||||
bool check_grants_table, bool check_grants_view,
|
bool check_grants_table, bool check_grants_view,
|
||||||
bool allow_rowid,
|
bool allow_rowid,
|
||||||
uint *cached_field_index_ptr);
|
uint *cached_field_index_ptr);
|
||||||
|
Field *
|
||||||
|
find_field_in_real_table(THD *thd, TABLE *table, const char *name,
|
||||||
|
uint length, bool check_grants, bool allow_rowid,
|
||||||
|
uint *cached_field_index_ptr);
|
||||||
#ifdef HAVE_OPENSSL
|
#ifdef HAVE_OPENSSL
|
||||||
#include <openssl/des.h>
|
#include <openssl/des.h>
|
||||||
struct st_des_keyblock
|
struct st_des_keyblock
|
||||||
|
@ -46,7 +46,7 @@ write_escaped_string(IO_CACHE *file, LEX_STRING *val_s)
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
Should be in sync with read_escaped_string() and
|
Should be in sync with read_escaped_string() and
|
||||||
parse_quated_escaped_string()
|
parse_quoted_escaped_string()
|
||||||
*/
|
*/
|
||||||
switch(*ptr) {
|
switch(*ptr) {
|
||||||
case '\\': // escape character
|
case '\\': // escape character
|
||||||
@ -154,11 +154,10 @@ write_parameter(IO_CACHE *file, gptr base, File_option *parameter,
|
|||||||
LEX_STRING *str;
|
LEX_STRING *str;
|
||||||
while ((str= it++))
|
while ((str= it++))
|
||||||
{
|
{
|
||||||
num.set((ulonglong)str->length, &my_charset_bin);
|
// We need ' ' after string to detect list continuation
|
||||||
// ',' after string to detect list continuation
|
|
||||||
if ((!first && my_b_append(file, (const byte *)" ", 1)) ||
|
if ((!first && my_b_append(file, (const byte *)" ", 1)) ||
|
||||||
my_b_append(file, (const byte *)"\'", 1) ||
|
my_b_append(file, (const byte *)"\'", 1) ||
|
||||||
my_b_append(file, (const byte *)str->str, str->length) ||
|
write_escaped_string(file, str) ||
|
||||||
my_b_append(file, (const byte *)"\'", 1))
|
my_b_append(file, (const byte *)"\'", 1))
|
||||||
{
|
{
|
||||||
DBUG_RETURN(TRUE);
|
DBUG_RETURN(TRUE);
|
||||||
@ -486,7 +485,7 @@ read_escaped_string(char *ptr, char *eol, LEX_STRING *str)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
/*
|
/*
|
||||||
Should be in sync with write_escaped_string() and
|
Should be in sync with write_escaped_string() and
|
||||||
parse_quated_escaped_string()
|
parse_quoted_escaped_string()
|
||||||
*/
|
*/
|
||||||
switch(*ptr) {
|
switch(*ptr) {
|
||||||
case '\\':
|
case '\\':
|
||||||
@ -562,7 +561,7 @@ parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
parse_quated_escaped_string(char *ptr, char *end,
|
parse_quoted_escaped_string(char *ptr, char *end,
|
||||||
MEM_ROOT *mem_root, LEX_STRING *str)
|
MEM_ROOT *mem_root, LEX_STRING *str)
|
||||||
{
|
{
|
||||||
char *eol;
|
char *eol;
|
||||||
@ -684,7 +683,6 @@ File_parser::parse(gptr base, MEM_ROOT *mem_root,
|
|||||||
my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
|
my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
|
||||||
parameter->name.str, line);
|
parameter->name.str, line);
|
||||||
DBUG_RETURN(TRUE);
|
DBUG_RETURN(TRUE);
|
||||||
DBUG_RETURN(TRUE);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -724,6 +722,7 @@ File_parser::parse(gptr base, MEM_ROOT *mem_root,
|
|||||||
/*
|
/*
|
||||||
TODO: remove play with mem_root, when List will be able
|
TODO: remove play with mem_root, when List will be able
|
||||||
to store MEM_ROOT* pointer for list elements allocation
|
to store MEM_ROOT* pointer for list elements allocation
|
||||||
|
FIXME: we can't handle empty lists
|
||||||
*/
|
*/
|
||||||
sql_mem= my_pthread_getspecific_ptr(MEM_ROOT*, THR_MALLOC);
|
sql_mem= my_pthread_getspecific_ptr(MEM_ROOT*, THR_MALLOC);
|
||||||
list= (List<LEX_STRING>*)(base + parameter->offset);
|
list= (List<LEX_STRING>*)(base + parameter->offset);
|
||||||
@ -741,7 +740,7 @@ File_parser::parse(gptr base, MEM_ROOT *mem_root,
|
|||||||
sizeof(LEX_STRING))) ||
|
sizeof(LEX_STRING))) ||
|
||||||
list->push_back(str))
|
list->push_back(str))
|
||||||
goto list_err;
|
goto list_err;
|
||||||
if(!(ptr= parse_quated_escaped_string(ptr, end, mem_root, str)))
|
if(!(ptr= parse_quoted_escaped_string(ptr, end, mem_root, str)))
|
||||||
goto list_err_w_message;
|
goto list_err_w_message;
|
||||||
switch (*ptr) {
|
switch (*ptr) {
|
||||||
case '\n':
|
case '\n':
|
||||||
|
@ -27,7 +27,8 @@ enum file_opt_type {
|
|||||||
FILE_OPTIONS_REV, /* Revision version number (ulonglong) */
|
FILE_OPTIONS_REV, /* Revision version number (ulonglong) */
|
||||||
FILE_OPTIONS_TIMESTAMP, /* timestamp (LEX_STRING have to be
|
FILE_OPTIONS_TIMESTAMP, /* timestamp (LEX_STRING have to be
|
||||||
allocated with length 20 (19+1) */
|
allocated with length 20 (19+1) */
|
||||||
FILE_OPTIONS_STRLIST /* list of strings (List<char*>) */
|
FILE_OPTIONS_STRLIST /* list of escaped strings
|
||||||
|
(List<LEX_STRING>) */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct File_option
|
struct File_option
|
||||||
|
@ -387,3 +387,8 @@ character-set=latin2
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -378,3 +378,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -387,3 +387,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -375,3 +375,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -380,3 +380,8 @@ character-set=latin7
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -375,3 +375,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -388,3 +388,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -375,3 +375,8 @@ character-set=greek
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -380,3 +380,8 @@ character-set=latin2
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -375,3 +375,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -379,3 +379,8 @@ character-set=ujis
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -375,3 +375,8 @@ character-set=euckr
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -377,3 +377,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -377,3 +377,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -380,3 +380,8 @@ character-set=latin2
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -377,3 +377,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -380,3 +380,8 @@ character-set=latin2
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -377,6 +377,11 @@ character-set=koi8r
|
|||||||
"View SELECT и список полей view имеют разное количество столбцов"
|
"View SELECT и список полей view имеют разное количество столбцов"
|
||||||
"Алгоритм слияния view не может быть использован сейчас (алгоритм будет неопеределенным)"
|
"Алгоритм слияния view не может быть использован сейчас (алгоритм будет неопеределенным)"
|
||||||
"Обновляемый view не содержит ключа использованной в нем таблиц(ы)"
|
"Обновляемый view не содержит ключа использованной в нем таблиц(ы)"
|
||||||
"View '%-.64s.%-.64s' ссылается на несуществующие таблицы или слолбцы"
|
"View '%-.64s.%-.64s' ÓÓÙÌÁÅÔÓÑ ÎÁ ÎÅÓÕÝÅÓÔ×ÕÀÝÉÅ ÔÁÂÌÉÃÙ ÉÌÉ ÓÔÏÌÂÃÙ"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -368,3 +368,8 @@ character-set=cp1250
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -383,3 +383,8 @@ character-set=latin2
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -379,3 +379,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -375,3 +375,8 @@ character-set=latin1
|
|||||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
@ -381,3 +381,8 @@ character-set=koi8u
|
|||||||
"View '%-.64s.%-.64s' ÐÏÓÉÌÁ¤ÔÓÑ ÎÁ ÎŦÓÎÕÀÞ¦ ÔÁÂÌÉæ ÁÂÏ ÓÔÏ×Âæ"
|
"View '%-.64s.%-.64s' ÐÏÓÉÌÁ¤ÔÓÑ ÎÁ ÎŦÓÎÕÀÞ¦ ÔÁÂÌÉæ ÁÂÏ ÓÔÏ×Âæ"
|
||||||
"Can't drop a %s from within another stored routine"
|
"Can't drop a %s from within another stored routine"
|
||||||
"GOTO is not allowed in a stored procedure handler"
|
"GOTO is not allowed in a stored procedure handler"
|
||||||
|
"Trigger already exists"
|
||||||
|
"Trigger does not exist"
|
||||||
|
"Trigger's '%-.64s' is view or temporary table"
|
||||||
|
"Updating of %s row is not allowed in %strigger"
|
||||||
|
"There is no %s row in %s trigger"
|
||||||
|
110
sql/sp_head.cc
110
sql/sp_head.cc
@ -293,26 +293,35 @@ sp_head::init_strings(THD *thd, LEX *lex, sp_name *name)
|
|||||||
/* During parsing, we must use thd->mem_root */
|
/* During parsing, we must use thd->mem_root */
|
||||||
MEM_ROOT *root= &thd->mem_root;
|
MEM_ROOT *root= &thd->mem_root;
|
||||||
|
|
||||||
DBUG_PRINT("info", ("name: %*.s%*s",
|
|
||||||
name->m_db.length, name->m_db.str,
|
|
||||||
name->m_name.length, name->m_name.str));
|
|
||||||
/* We have to copy strings to get them into the right memroot */
|
/* We have to copy strings to get them into the right memroot */
|
||||||
m_db.length= name->m_db.length;
|
if (name)
|
||||||
if (name->m_db.length == 0)
|
{
|
||||||
m_db.str= NULL;
|
m_db.length= name->m_db.length;
|
||||||
else
|
if (name->m_db.length == 0)
|
||||||
m_db.str= strmake_root(root, name->m_db.str, name->m_db.length);
|
m_db.str= NULL;
|
||||||
m_name.length= name->m_name.length;
|
else
|
||||||
m_name.str= strmake_root(root, name->m_name.str, name->m_name.length);
|
m_db.str= strmake_root(root, name->m_db.str, name->m_db.length);
|
||||||
|
m_name.length= name->m_name.length;
|
||||||
|
m_name.str= strmake_root(root, name->m_name.str, name->m_name.length);
|
||||||
|
|
||||||
if (name->m_qname.length == 0)
|
if (name->m_qname.length == 0)
|
||||||
name->init_qname(thd);
|
name->init_qname(thd);
|
||||||
m_qname.length= name->m_qname.length;
|
m_qname.length= name->m_qname.length;
|
||||||
m_qname.str= strmake_root(root, name->m_qname.str, m_qname.length);
|
m_qname.str= strmake_root(root, name->m_qname.str, m_qname.length);
|
||||||
|
}
|
||||||
|
else if (thd->db)
|
||||||
|
{
|
||||||
|
m_db.length= thd->db_length;
|
||||||
|
m_db.str= strmake_root(root, thd->db, m_db.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_param_begin && m_param_end)
|
||||||
|
{
|
||||||
|
m_params.length= m_param_end - m_param_begin;
|
||||||
|
m_params.str= strmake_root(root,
|
||||||
|
(char *)m_param_begin, m_params.length);
|
||||||
|
}
|
||||||
|
|
||||||
m_params.length= m_param_end- m_param_begin;
|
|
||||||
m_params.str= strmake_root(root,
|
|
||||||
(char *)m_param_begin, m_params.length);
|
|
||||||
if (m_returns_begin && m_returns_end)
|
if (m_returns_begin && m_returns_end)
|
||||||
{
|
{
|
||||||
/* QQ KLUDGE: We can't seem to cut out just the type in the parser
|
/* QQ KLUDGE: We can't seem to cut out just the type in the parser
|
||||||
@ -575,8 +584,10 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
|
|||||||
thd->spcont= nctx;
|
thd->spcont= nctx;
|
||||||
|
|
||||||
ret= execute(thd);
|
ret= execute(thd);
|
||||||
if (ret == 0)
|
|
||||||
|
if (m_type == TYPE_ENUM_FUNCTION && ret == 0)
|
||||||
{
|
{
|
||||||
|
/* We need result only in function but not in trigger */
|
||||||
Item *it= nctx->get_result();
|
Item *it= nctx->get_result();
|
||||||
|
|
||||||
if (it)
|
if (it)
|
||||||
@ -763,6 +774,9 @@ sp_head::reset_lex(THD *thd)
|
|||||||
/* And keep the SP stuff too */
|
/* And keep the SP stuff too */
|
||||||
sublex->sphead= oldlex->sphead;
|
sublex->sphead= oldlex->sphead;
|
||||||
sublex->spcont= oldlex->spcont;
|
sublex->spcont= oldlex->spcont;
|
||||||
|
/* And trigger related stuff too */
|
||||||
|
sublex->trg_chistics= oldlex->trg_chistics;
|
||||||
|
sublex->trg_table= oldlex->trg_table;
|
||||||
sublex->sp_lex_in_use= FALSE;
|
sublex->sp_lex_in_use= FALSE;
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
@ -1211,7 +1225,7 @@ sp_instr_set::execute(THD *thd, uint *nextp)
|
|||||||
thd->spcont->set_item(m_offset, it);
|
thd->spcont->set_item(m_offset, it);
|
||||||
}
|
}
|
||||||
*nextp = m_ip+1;
|
*nextp = m_ip+1;
|
||||||
if (thd->lock || thd->open_tables || thd->derived_tables)
|
if (tables && (thd->lock || thd->open_tables || thd->derived_tables))
|
||||||
close_thread_tables(thd);
|
close_thread_tables(thd);
|
||||||
DBUG_RETURN(res);
|
DBUG_RETURN(res);
|
||||||
}
|
}
|
||||||
@ -1226,6 +1240,60 @@ sp_instr_set::print(String *str)
|
|||||||
m_value->print(str);
|
m_value->print(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// sp_instr_set_user_var
|
||||||
|
//
|
||||||
|
int
|
||||||
|
sp_instr_set_user_var::execute(THD *thd, uint *nextp)
|
||||||
|
{
|
||||||
|
int res= 0;
|
||||||
|
|
||||||
|
DBUG_ENTER("sp_instr_set_user_var::execute");
|
||||||
|
/*
|
||||||
|
It is ok to pass 0 as 3rd argument to fix_fields() since
|
||||||
|
Item_func_set_user_var::fix_fields() won't use it.
|
||||||
|
QQ: Still unsure what should we return in case of error 1 or -1 ?
|
||||||
|
*/
|
||||||
|
if (!m_set_var_item.fixed && m_set_var_item.fix_fields(thd, 0, 0) ||
|
||||||
|
m_set_var_item.check() || m_set_var_item.update())
|
||||||
|
res= -1;
|
||||||
|
*nextp= m_ip + 1;
|
||||||
|
DBUG_RETURN(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_instr_set_user_var::print(String *str)
|
||||||
|
{
|
||||||
|
m_set_var_item.print_as_stmt(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// sp_instr_set_trigger_field
|
||||||
|
//
|
||||||
|
int
|
||||||
|
sp_instr_set_trigger_field::execute(THD *thd, uint *nextp)
|
||||||
|
{
|
||||||
|
int res= 0;
|
||||||
|
|
||||||
|
DBUG_ENTER("sp_instr_set_trigger_field::execute");
|
||||||
|
/* QQ: Still unsure what should we return in case of error 1 or -1 ? */
|
||||||
|
if (!value->fixed && value->fix_fields(thd, 0, &value) ||
|
||||||
|
trigger_field.fix_fields(thd, 0, 0) ||
|
||||||
|
(value->save_in_field(trigger_field.field, 0) < 0))
|
||||||
|
res= -1;
|
||||||
|
*nextp= m_ip + 1;
|
||||||
|
DBUG_RETURN(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_instr_set_trigger_field::print(String *str)
|
||||||
|
{
|
||||||
|
str->append("set ", 4);
|
||||||
|
trigger_field.print(str);
|
||||||
|
str->append(":=", 2);
|
||||||
|
value->print(str);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// sp_instr_jump
|
// sp_instr_jump
|
||||||
//
|
//
|
||||||
@ -1314,7 +1382,7 @@ sp_instr_jump_if::execute(THD *thd, uint *nextp)
|
|||||||
else
|
else
|
||||||
*nextp = m_ip+1;
|
*nextp = m_ip+1;
|
||||||
}
|
}
|
||||||
if (thd->lock || thd->open_tables || thd->derived_tables)
|
if (tables && (thd->lock || thd->open_tables || thd->derived_tables))
|
||||||
close_thread_tables(thd);
|
close_thread_tables(thd);
|
||||||
DBUG_RETURN(res);
|
DBUG_RETURN(res);
|
||||||
}
|
}
|
||||||
@ -1371,7 +1439,7 @@ sp_instr_jump_if_not::execute(THD *thd, uint *nextp)
|
|||||||
else
|
else
|
||||||
*nextp = m_ip+1;
|
*nextp = m_ip+1;
|
||||||
}
|
}
|
||||||
if (thd->lock || thd->open_tables || thd->derived_tables)
|
if (tables && (thd->lock || thd->open_tables || thd->derived_tables))
|
||||||
close_thread_tables(thd);
|
close_thread_tables(thd);
|
||||||
DBUG_RETURN(res);
|
DBUG_RETURN(res);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
// in the CREATE TABLE command.
|
// in the CREATE TABLE command.
|
||||||
#define TYPE_ENUM_FUNCTION 1
|
#define TYPE_ENUM_FUNCTION 1
|
||||||
#define TYPE_ENUM_PROCEDURE 2
|
#define TYPE_ENUM_PROCEDURE 2
|
||||||
|
#define TYPE_ENUM_TRIGGER 3
|
||||||
|
|
||||||
Item_result
|
Item_result
|
||||||
sp_map_result_type(enum enum_field_types type);
|
sp_map_result_type(enum enum_field_types type);
|
||||||
@ -377,6 +378,72 @@ private:
|
|||||||
}; // class sp_instr_set : public sp_instr
|
}; // class sp_instr_set : public sp_instr
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set user variable instruction.
|
||||||
|
Used in functions and triggers to set user variables because we don't
|
||||||
|
want use sp_instr_stmt + "SET @a:=..." statement in this case since
|
||||||
|
latter will close all tables and thus will ruin execution of statement
|
||||||
|
calling/invoking this function/trigger.
|
||||||
|
*/
|
||||||
|
class sp_instr_set_user_var : public sp_instr
|
||||||
|
{
|
||||||
|
sp_instr_set_user_var(const sp_instr_set_user_var &);
|
||||||
|
void operator=(sp_instr_set_user_var &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_instr_set_user_var(uint ip, sp_pcontext *ctx, LEX_STRING var, Item *val)
|
||||||
|
: sp_instr(ip, ctx), m_set_var_item(var, val)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr_set_user_var()
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual int execute(THD *thd, uint *nextp);
|
||||||
|
|
||||||
|
virtual void print(String *str);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Item_func_set_user_var m_set_var_item;
|
||||||
|
}; // class sp_instr_set_user_var : public sp_instr
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set NEW/OLD row field value instruction. Used in triggers.
|
||||||
|
*/
|
||||||
|
class sp_instr_set_trigger_field : public sp_instr
|
||||||
|
{
|
||||||
|
sp_instr_set_trigger_field(const sp_instr_set_trigger_field &);
|
||||||
|
void operator=(sp_instr_set_trigger_field &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_instr_set_trigger_field(uint ip, sp_pcontext *ctx,
|
||||||
|
LEX_STRING field_name, Item *val)
|
||||||
|
: sp_instr(ip, ctx),
|
||||||
|
trigger_field(Item_trigger_field::NEW_ROW, field_name.str),
|
||||||
|
value(val)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr_set_trigger_field()
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual int execute(THD *thd, uint *nextp);
|
||||||
|
|
||||||
|
virtual void print(String *str);
|
||||||
|
|
||||||
|
bool setup_field(THD *thd, TABLE *table, enum trg_event_type event)
|
||||||
|
{
|
||||||
|
return trigger_field.setup_field(thd, table, event);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
|
||||||
|
Item_trigger_field trigger_field;
|
||||||
|
Item *value;
|
||||||
|
}; // class sp_instr_trigger_field : public sp_instr
|
||||||
|
|
||||||
|
|
||||||
class sp_instr_jump : public sp_instr
|
class sp_instr_jump : public sp_instr
|
||||||
{
|
{
|
||||||
sp_instr_jump(const sp_instr_jump &); /* Prevent use of these */
|
sp_instr_jump(const sp_instr_jump &); /* Prevent use of these */
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
#include "mysql_priv.h"
|
#include "mysql_priv.h"
|
||||||
#include "sql_acl.h"
|
#include "sql_acl.h"
|
||||||
#include "sql_select.h"
|
#include "sql_select.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sql_trigger.h"
|
||||||
#include <m_ctype.h>
|
#include <m_ctype.h>
|
||||||
#include <my_dir.h>
|
#include <my_dir.h>
|
||||||
#include <hash.h>
|
#include <hash.h>
|
||||||
@ -42,10 +44,6 @@ static my_bool open_new_frm(const char *path, const char *alias,
|
|||||||
uint db_stat, uint prgflag,
|
uint db_stat, uint prgflag,
|
||||||
uint ha_open_flags, TABLE *outparam,
|
uint ha_open_flags, TABLE *outparam,
|
||||||
TABLE_LIST *table_desc, MEM_ROOT *mem_root);
|
TABLE_LIST *table_desc, MEM_ROOT *mem_root);
|
||||||
static Field *find_field_in_real_table(THD *thd, TABLE *table,
|
|
||||||
const char *name, uint length,
|
|
||||||
bool check_grants, bool allow_rowid,
|
|
||||||
uint *cached_field_index_ptr);
|
|
||||||
|
|
||||||
extern "C" byte *table_cache_key(const byte *record,uint *length,
|
extern "C" byte *table_cache_key(const byte *record,uint *length,
|
||||||
my_bool not_used __attribute__((unused)))
|
my_bool not_used __attribute__((unused)))
|
||||||
@ -210,6 +208,7 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *wild)
|
|||||||
void intern_close_table(TABLE *table)
|
void intern_close_table(TABLE *table)
|
||||||
{ // Free all structures
|
{ // Free all structures
|
||||||
free_io_cache(table);
|
free_io_cache(table);
|
||||||
|
delete table->triggers;
|
||||||
if (table->file)
|
if (table->file)
|
||||||
VOID(closefrm(table)); // close file
|
VOID(closefrm(table)); // close file
|
||||||
}
|
}
|
||||||
@ -818,6 +817,7 @@ TABLE *reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
|
|||||||
!(table->table_cache_key =memdup_root(&table->mem_root,(char*) key,
|
!(table->table_cache_key =memdup_root(&table->mem_root,(char*) key,
|
||||||
key_length)))
|
key_length)))
|
||||||
{
|
{
|
||||||
|
delete table->triggers;
|
||||||
closefrm(table);
|
closefrm(table);
|
||||||
pthread_mutex_unlock(&LOCK_open);
|
pthread_mutex_unlock(&LOCK_open);
|
||||||
DBUG_RETURN(0);
|
DBUG_RETURN(0);
|
||||||
@ -1081,6 +1081,7 @@ bool reopen_table(TABLE *table,bool locked)
|
|||||||
if (!(tmp.table_cache_key= memdup_root(&tmp.mem_root,db,
|
if (!(tmp.table_cache_key= memdup_root(&tmp.mem_root,db,
|
||||||
table->key_length)))
|
table->key_length)))
|
||||||
{
|
{
|
||||||
|
delete tmp.triggers;
|
||||||
closefrm(&tmp); // End of memory
|
closefrm(&tmp); // End of memory
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@ -1109,6 +1110,7 @@ bool reopen_table(TABLE *table,bool locked)
|
|||||||
tmp.next= table->next;
|
tmp.next= table->next;
|
||||||
tmp.prev= table->prev;
|
tmp.prev= table->prev;
|
||||||
|
|
||||||
|
delete table->triggers;
|
||||||
if (table->file)
|
if (table->file)
|
||||||
VOID(closefrm(table)); // close file, free everything
|
VOID(closefrm(table)); // close file, free everything
|
||||||
|
|
||||||
@ -1495,6 +1497,9 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db,
|
|||||||
if (error == 5)
|
if (error == 5)
|
||||||
DBUG_RETURN(0); // we have just opened VIEW
|
DBUG_RETURN(0); // we have just opened VIEW
|
||||||
|
|
||||||
|
if (Table_triggers_list::check_n_load(thd, db, name, entry))
|
||||||
|
goto err;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If we are here, there was no fatal error (but error may be still
|
If we are here, there was no fatal error (but error may be still
|
||||||
unitialized).
|
unitialized).
|
||||||
@ -1523,6 +1528,7 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db,
|
|||||||
*/
|
*/
|
||||||
sql_print_error("Error: when opening HEAP table, could not allocate \
|
sql_print_error("Error: when opening HEAP table, could not allocate \
|
||||||
memory to write 'DELETE FROM `%s`.`%s`' to the binary log",db,name);
|
memory to write 'DELETE FROM `%s`.`%s`' to the binary log",db,name);
|
||||||
|
delete entry->triggers;
|
||||||
if (entry->file)
|
if (entry->file)
|
||||||
closefrm(entry);
|
closefrm(entry);
|
||||||
goto err;
|
goto err;
|
||||||
@ -2070,10 +2076,10 @@ find_field_in_table(THD *thd, TABLE_LIST *table_list,
|
|||||||
# pointer to field
|
# pointer to field
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static Field *find_field_in_real_table(THD *thd, TABLE *table,
|
Field *find_field_in_real_table(THD *thd, TABLE *table,
|
||||||
const char *name, uint length,
|
const char *name, uint length,
|
||||||
bool check_grants, bool allow_rowid,
|
bool check_grants, bool allow_rowid,
|
||||||
uint *cached_field_index_ptr)
|
uint *cached_field_index_ptr)
|
||||||
{
|
{
|
||||||
Field **field_ptr, *field;
|
Field **field_ptr, *field;
|
||||||
uint cached_field_index= *cached_field_index_ptr;
|
uint cached_field_index= *cached_field_index_ptr;
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
#include "mysql_priv.h"
|
#include "mysql_priv.h"
|
||||||
#include "ha_innodb.h"
|
#include "ha_innodb.h"
|
||||||
#include "sql_select.h"
|
#include "sql_select.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sql_trigger.h"
|
||||||
|
|
||||||
int mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SQL_LIST *order,
|
int mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SQL_LIST *order,
|
||||||
ha_rows limit, ulong options)
|
ha_rows limit, ulong options)
|
||||||
@ -160,6 +162,11 @@ int mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SQL_LIST *order,
|
|||||||
// thd->net.report_error is tested to disallow delete row on error
|
// thd->net.report_error is tested to disallow delete row on error
|
||||||
if (!(select && select->skip_record())&& !thd->net.report_error )
|
if (!(select && select->skip_record())&& !thd->net.report_error )
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (table->triggers)
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||||
|
TRG_ACTION_BEFORE);
|
||||||
|
|
||||||
if (!(error=table->file->delete_row(table->record[0])))
|
if (!(error=table->file->delete_row(table->record[0])))
|
||||||
{
|
{
|
||||||
deleted++;
|
deleted++;
|
||||||
@ -183,6 +190,10 @@ int mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SQL_LIST *order,
|
|||||||
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
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "mysql_priv.h"
|
#include "mysql_priv.h"
|
||||||
#include "sql_acl.h"
|
#include "sql_acl.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sql_trigger.h"
|
||||||
|
|
||||||
static int check_null_fields(THD *thd,TABLE *entry);
|
static int check_null_fields(THD *thd,TABLE *entry);
|
||||||
#ifndef EMBEDDED_LIBRARY
|
#ifndef EMBEDDED_LIBRARY
|
||||||
@ -302,6 +304,12 @@ int mysql_insert(THD *thd,TABLE_LIST *table_list,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Actually we should do this before check_null_fields.
|
||||||
|
// Or even go into write_record ?
|
||||||
|
if (table->triggers)
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_INSERT, TRG_ACTION_BEFORE);
|
||||||
|
|
||||||
#ifndef EMBEDDED_LIBRARY
|
#ifndef EMBEDDED_LIBRARY
|
||||||
if (lock_type == TL_WRITE_DELAYED)
|
if (lock_type == TL_WRITE_DELAYED)
|
||||||
{
|
{
|
||||||
@ -324,6 +332,9 @@ int mysql_insert(THD *thd,TABLE_LIST *table_list,
|
|||||||
id= thd->last_insert_id;
|
id= thd->last_insert_id;
|
||||||
}
|
}
|
||||||
thd->row_count++;
|
thd->row_count++;
|
||||||
|
|
||||||
|
if (table->triggers)
|
||||||
|
table->triggers->process_triggers(thd, TRG_EVENT_INSERT, TRG_ACTION_AFTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -24,6 +24,11 @@
|
|||||||
#include "sp.h"
|
#include "sp.h"
|
||||||
#include "sp_head.h"
|
#include "sp_head.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
We are using pointer to this variable for distinguishing between assignment
|
||||||
|
to NEW row field (when parsing trigger definition) and structured variable.
|
||||||
|
*/
|
||||||
|
sys_var_long_ptr trg_new_row_fake_var(0, 0);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Fake table list object, pointer to which is used as special value for
|
Fake table list object, pointer to which is used as special value for
|
||||||
@ -139,6 +144,7 @@ void lex_start(THD *thd, uchar *buf,uint length)
|
|||||||
lex->duplicates= DUP_ERROR;
|
lex->duplicates= DUP_ERROR;
|
||||||
lex->sphead= NULL;
|
lex->sphead= NULL;
|
||||||
lex->spcont= NULL;
|
lex->spcont= NULL;
|
||||||
|
lex->trg_table= NULL;
|
||||||
|
|
||||||
extern byte *sp_lex_spfuns_key(const byte *ptr, uint *plen, my_bool first);
|
extern byte *sp_lex_spfuns_key(const byte *ptr, uint *plen, my_bool first);
|
||||||
hash_free(&lex->spfuns);
|
hash_free(&lex->spfuns);
|
||||||
|
@ -85,6 +85,7 @@ enum enum_sql_command {
|
|||||||
SQLCOM_SHOW_STATUS_PROC, SQLCOM_SHOW_STATUS_FUNC,
|
SQLCOM_SHOW_STATUS_PROC, SQLCOM_SHOW_STATUS_FUNC,
|
||||||
SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE,
|
SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE,
|
||||||
SQLCOM_CREATE_VIEW, SQLCOM_DROP_VIEW,
|
SQLCOM_CREATE_VIEW, SQLCOM_DROP_VIEW,
|
||||||
|
SQLCOM_CREATE_TRIGGER, SQLCOM_DROP_TRIGGER,
|
||||||
/* This should be the last !!! */
|
/* This should be the last !!! */
|
||||||
SQLCOM_END
|
SQLCOM_END
|
||||||
};
|
};
|
||||||
@ -602,6 +603,15 @@ struct st_sp_chistics
|
|||||||
bool detistic;
|
bool detistic;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct st_trg_chistics
|
||||||
|
{
|
||||||
|
enum trg_action_time_type action_time;
|
||||||
|
enum trg_event_type event;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern sys_var_long_ptr trg_new_row_fake_var;
|
||||||
|
|
||||||
/* The state of the lex parsing. This is saved in the THD struct */
|
/* The state of the lex parsing. This is saved in the THD struct */
|
||||||
|
|
||||||
typedef struct st_lex
|
typedef struct st_lex
|
||||||
@ -713,6 +723,14 @@ typedef struct st_lex
|
|||||||
rexecuton
|
rexecuton
|
||||||
*/
|
*/
|
||||||
bool empty_field_list_on_rset;
|
bool empty_field_list_on_rset;
|
||||||
|
/* Characterstics of trigger being created */
|
||||||
|
st_trg_chistics trg_chistics;
|
||||||
|
/*
|
||||||
|
Points to table being opened when we are parsing trigger definition
|
||||||
|
while opening table. 0 if we are parsing user provided CREATE TRIGGER
|
||||||
|
or any other statement. Used for NEW/OLD row field lookup in trigger.
|
||||||
|
*/
|
||||||
|
TABLE *trg_table;
|
||||||
|
|
||||||
st_lex() :result(0)
|
st_lex() :result(0)
|
||||||
{
|
{
|
||||||
|
@ -3877,6 +3877,20 @@ purposes internal to the MySQL server", MYF(0));
|
|||||||
res= mysql_drop_view(thd, first_table, thd->lex->drop_mode);
|
res= mysql_drop_view(thd, first_table, thd->lex->drop_mode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SQLCOM_CREATE_TRIGGER:
|
||||||
|
{
|
||||||
|
/* We don't care much about trigger body at that point */
|
||||||
|
delete lex->sphead;
|
||||||
|
lex->sphead= 0;
|
||||||
|
|
||||||
|
res= mysql_create_or_drop_trigger(thd, all_tables, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SQLCOM_DROP_TRIGGER:
|
||||||
|
{
|
||||||
|
res= mysql_create_or_drop_trigger(thd, all_tables, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: /* Impossible */
|
default: /* Impossible */
|
||||||
send_ok(thd);
|
send_ok(thd);
|
||||||
break;
|
break;
|
||||||
|
@ -1517,7 +1517,7 @@ static void wait_while_table_is_used(THD *thd,TABLE *table,
|
|||||||
Win32 clients must also have a WRITE LOCK on the table !
|
Win32 clients must also have a WRITE LOCK on the table !
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static bool close_cached_table(THD *thd, TABLE *table)
|
void close_cached_table(THD *thd, TABLE *table)
|
||||||
{
|
{
|
||||||
DBUG_ENTER("close_cached_table");
|
DBUG_ENTER("close_cached_table");
|
||||||
|
|
||||||
@ -1533,7 +1533,6 @@ static bool close_cached_table(THD *thd, TABLE *table)
|
|||||||
|
|
||||||
/* When lock on LOCK_open is freed other threads can continue */
|
/* When lock on LOCK_open is freed other threads can continue */
|
||||||
pthread_cond_broadcast(&COND_refresh);
|
pthread_cond_broadcast(&COND_refresh);
|
||||||
DBUG_RETURN(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int send_check_errmsg(THD *thd, TABLE_LIST* table,
|
static int send_check_errmsg(THD *thd, TABLE_LIST* table,
|
||||||
@ -3140,12 +3139,7 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name,
|
|||||||
close the original table at before doing the rename
|
close the original table at before doing the rename
|
||||||
*/
|
*/
|
||||||
table_name=thd->strdup(table_name); // must be saved
|
table_name=thd->strdup(table_name); // must be saved
|
||||||
if (close_cached_table(thd, table))
|
close_cached_table(thd, table);
|
||||||
{ // Aborted
|
|
||||||
VOID(quick_rm_table(new_db_type,new_db,tmp_name));
|
|
||||||
VOID(pthread_mutex_unlock(&LOCK_open));
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
table=0; // Marker that table is closed
|
table=0; // Marker that table is closed
|
||||||
}
|
}
|
||||||
#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2))
|
#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2))
|
||||||
|
433
sql/sql_trigger.cc
Normal file
433
sql/sql_trigger.cc
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
#include "mysql_priv.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sql_trigger.h"
|
||||||
|
#include "parse_file.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const LEX_STRING triggers_file_type= {(char *)"TRIGGERS", 8};
|
||||||
|
static const char * const triggers_file_ext= ".TRG";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Table of .TRG file field descriptors.
|
||||||
|
We have here only one field now because in nearest future .TRG
|
||||||
|
files will be merged into .FRM files (so we don't need something
|
||||||
|
like md5 or created fields).
|
||||||
|
*/
|
||||||
|
static File_option triggers_file_parameters[]=
|
||||||
|
{
|
||||||
|
{{(char*)"triggers", 8}, offsetof(Table_triggers_list, definitions_list),
|
||||||
|
FILE_OPTIONS_STRLIST},
|
||||||
|
{{NULL, 0}, 0, FILE_OPTIONS_STRING}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create or drop trigger for table.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
mysql_create_or_drop_trigger()
|
||||||
|
thd - current thread context (including trigger definition in LEX)
|
||||||
|
tables - table list containing one table for which trigger is created.
|
||||||
|
create - whenever we create (true) or drop (false) trigger
|
||||||
|
|
||||||
|
NOTE
|
||||||
|
This function is mainly responsible for opening and locking of table and
|
||||||
|
invalidation of all its instances in table cache after trigger creation.
|
||||||
|
Real work on trigger creation/dropping is done inside Table_triggers_list
|
||||||
|
methods.
|
||||||
|
|
||||||
|
RETURN VALUE
|
||||||
|
0 - Success, non-0 in case of error.
|
||||||
|
*/
|
||||||
|
int mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
|
||||||
|
{
|
||||||
|
TABLE *table;
|
||||||
|
int result= 0;
|
||||||
|
|
||||||
|
DBUG_ENTER("mysql_create_or_drop_trigger");
|
||||||
|
|
||||||
|
/*
|
||||||
|
QQ: This function could be merged in mysql_alter_table() function
|
||||||
|
But do we want this ?
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (open_and_lock_tables(thd, tables))
|
||||||
|
DBUG_RETURN(-1);
|
||||||
|
|
||||||
|
// TODO: We should check if user has TRIGGER privilege for table here.
|
||||||
|
|
||||||
|
table= tables->table;
|
||||||
|
|
||||||
|
/*
|
||||||
|
We do not allow creation of triggers on views or temporary tables.
|
||||||
|
We have to do this check here and not in
|
||||||
|
Table_triggers_list::create_trigger() because we want to avoid messing
|
||||||
|
with table cash for views and temporary tables.
|
||||||
|
*/
|
||||||
|
if (tables->view || table->tmp_table != NO_TMP_TABLE)
|
||||||
|
{
|
||||||
|
my_error(ER_TRG_ON_VIEW_OR_TEMP_TABLE, MYF(0), tables->alias);
|
||||||
|
DBUG_RETURN(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table->triggers)
|
||||||
|
{
|
||||||
|
if (!create)
|
||||||
|
{
|
||||||
|
my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
|
||||||
|
DBUG_RETURN(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(table->triggers= new (&table->mem_root) Table_triggers_list()))
|
||||||
|
DBUG_RETURN(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We don't want perform our operations while global read lock is held
|
||||||
|
so we have to wait until its end and then prevent it from occuring
|
||||||
|
again until we are done. (Acquiring LOCK_open is not enough because
|
||||||
|
global read lock is held without helding LOCK_open).
|
||||||
|
*/
|
||||||
|
if (wait_if_global_read_lock(thd, 0, 0))
|
||||||
|
DBUG_RETURN(-1);
|
||||||
|
|
||||||
|
VOID(pthread_mutex_lock(&LOCK_open));
|
||||||
|
if ((create ? table->triggers->create_trigger(thd, tables):
|
||||||
|
table->triggers->drop_trigger(thd, tables)))
|
||||||
|
result= -1;
|
||||||
|
|
||||||
|
/* It is sensible to invalidate table in any case */
|
||||||
|
close_cached_table(thd, table);
|
||||||
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
||||||
|
start_waiting_global_read_lock(thd);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
send_ok(thd);
|
||||||
|
|
||||||
|
DBUG_RETURN(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create trigger for table.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
create_trigger()
|
||||||
|
thd - current thread context (including trigger definition in LEX)
|
||||||
|
tables - table list containing one open table for which trigger is
|
||||||
|
created.
|
||||||
|
|
||||||
|
RETURN VALUE
|
||||||
|
False - success
|
||||||
|
True - error
|
||||||
|
*/
|
||||||
|
bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables)
|
||||||
|
{
|
||||||
|
LEX *lex= thd->lex;
|
||||||
|
TABLE *table= tables->table;
|
||||||
|
char dir_buff[FN_REFLEN], file_buff[FN_REFLEN];
|
||||||
|
LEX_STRING dir, file;
|
||||||
|
MEM_ROOT *old_global_root;
|
||||||
|
LEX_STRING *trg_def, *name;
|
||||||
|
List_iterator_fast<LEX_STRING> it(names_list);
|
||||||
|
|
||||||
|
/* We don't allow creation of several triggers of the same type yet */
|
||||||
|
if (bodies[lex->trg_chistics.event][lex->trg_chistics.action_time])
|
||||||
|
{
|
||||||
|
my_error(ER_TRG_ALREADY_EXISTS, MYF(0));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let us check if trigger with the same name exists */
|
||||||
|
while ((name= it++))
|
||||||
|
{
|
||||||
|
if (my_strcasecmp(system_charset_info, lex->name_and_length.str,
|
||||||
|
name->str) == 0)
|
||||||
|
{
|
||||||
|
my_error(ER_TRG_ALREADY_EXISTS, MYF(0));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Here we are creating file with triggers and save all triggers in it.
|
||||||
|
sql_create_definition_file() files handles renaming and backup of older
|
||||||
|
versions
|
||||||
|
*/
|
||||||
|
strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", tables->db, "/", NullS);
|
||||||
|
dir.length= unpack_filename(dir_buff, dir_buff);
|
||||||
|
dir.str= dir_buff;
|
||||||
|
file.length= strxnmov(file_buff, FN_REFLEN, tables->real_name,
|
||||||
|
triggers_file_ext, NullS) - file_buff;
|
||||||
|
file.str= file_buff;
|
||||||
|
|
||||||
|
old_global_root= my_pthread_getspecific_ptr(MEM_ROOT*, THR_MALLOC);
|
||||||
|
my_pthread_setspecific_ptr(THR_MALLOC, &table->mem_root);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Soon we will invalidate table object and thus Table_triggers_list object
|
||||||
|
so don't care about place to which trg_def->ptr points and other
|
||||||
|
invariants (e.g. we don't bother to update names_list)
|
||||||
|
|
||||||
|
QQ: Hmm... probably we should not care about setting up active thread
|
||||||
|
mem_root too.
|
||||||
|
*/
|
||||||
|
if (!(trg_def= (LEX_STRING *)alloc_root(&table->mem_root,
|
||||||
|
sizeof(LEX_STRING))) ||
|
||||||
|
definitions_list.push_back(trg_def))
|
||||||
|
{
|
||||||
|
my_pthread_setspecific_ptr(THR_MALLOC, old_global_root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
trg_def->str= thd->query;
|
||||||
|
trg_def->length= thd->query_length;
|
||||||
|
|
||||||
|
my_pthread_setspecific_ptr(THR_MALLOC, old_global_root);
|
||||||
|
|
||||||
|
return sql_create_definition_file(&dir, &file, &triggers_file_type,
|
||||||
|
(gptr)this, triggers_file_parameters, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Drop trigger for table.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
drop_trigger()
|
||||||
|
thd - current thread context (including trigger definition in LEX)
|
||||||
|
tables - table list containing one open table for which trigger is
|
||||||
|
dropped.
|
||||||
|
|
||||||
|
RETURN VALUE
|
||||||
|
False - success
|
||||||
|
True - error
|
||||||
|
*/
|
||||||
|
bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables)
|
||||||
|
{
|
||||||
|
LEX *lex= thd->lex;
|
||||||
|
LEX_STRING *name;
|
||||||
|
List_iterator_fast<LEX_STRING> it_name(names_list);
|
||||||
|
List_iterator<LEX_STRING> it_def(definitions_list);
|
||||||
|
|
||||||
|
while ((name= it_name++))
|
||||||
|
{
|
||||||
|
it_def++;
|
||||||
|
|
||||||
|
if (my_strcasecmp(system_charset_info, lex->name_and_length.str,
|
||||||
|
name->str) == 0)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Again we don't care much about other things required for
|
||||||
|
clean trigger removing since table will be reopened anyway.
|
||||||
|
*/
|
||||||
|
it_def.remove();
|
||||||
|
|
||||||
|
if (definitions_list.is_empty())
|
||||||
|
{
|
||||||
|
char path[FN_REFLEN];
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: Probably instead of removing .TRG file we should move
|
||||||
|
to archive directory but this should be done as part of
|
||||||
|
parse_file.cc functionality (because we will need it
|
||||||
|
elsewhere).
|
||||||
|
*/
|
||||||
|
strxnmov(path, FN_REFLEN, mysql_data_home, "/", tables->db, "/",
|
||||||
|
tables->real_name, triggers_file_ext, NullS);
|
||||||
|
unpack_filename(path, path);
|
||||||
|
return my_delete(path, MYF(MY_WME));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char dir_buff[FN_REFLEN], file_buff[FN_REFLEN];
|
||||||
|
LEX_STRING dir, file;
|
||||||
|
|
||||||
|
strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", tables->db,
|
||||||
|
"/", NullS);
|
||||||
|
dir.length= unpack_filename(dir_buff, dir_buff);
|
||||||
|
dir.str= dir_buff;
|
||||||
|
file.length= strxnmov(file_buff, FN_REFLEN, tables->real_name,
|
||||||
|
triggers_file_ext, NullS) - file_buff;
|
||||||
|
file.str= file_buff;
|
||||||
|
|
||||||
|
return sql_create_definition_file(&dir, &file, &triggers_file_type,
|
||||||
|
(gptr)this,
|
||||||
|
triggers_file_parameters, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Table_triggers_list::~Table_triggers_list()
|
||||||
|
{
|
||||||
|
for (int i= 0; i < 3; i++)
|
||||||
|
for (int j= 0; j < 2; j++)
|
||||||
|
delete bodies[i][j];
|
||||||
|
|
||||||
|
if (old_field)
|
||||||
|
for (Field **fld_ptr= old_field; *fld_ptr; fld_ptr++)
|
||||||
|
delete *fld_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check whenever .TRG file for table exist and load all triggers it contains.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
check_n_load()
|
||||||
|
thd - current thread context
|
||||||
|
db - table's database name
|
||||||
|
table_name - table's name
|
||||||
|
table - pointer to table object
|
||||||
|
|
||||||
|
RETURN VALUE
|
||||||
|
False - success
|
||||||
|
True - error
|
||||||
|
*/
|
||||||
|
bool Table_triggers_list::check_n_load(THD *thd, const char *db,
|
||||||
|
const char *table_name, TABLE *table)
|
||||||
|
{
|
||||||
|
char path_buff[FN_REFLEN];
|
||||||
|
LEX_STRING path;
|
||||||
|
File_parser *parser;
|
||||||
|
MEM_ROOT *old_global_mem_root;
|
||||||
|
|
||||||
|
DBUG_ENTER("Table_triggers_list::check_n_load");
|
||||||
|
|
||||||
|
strxnmov(path_buff, FN_REFLEN, mysql_data_home, "/", db, "/", table_name,
|
||||||
|
triggers_file_ext, NullS);
|
||||||
|
path.length= unpack_filename(path_buff, path_buff);
|
||||||
|
path.str= path_buff;
|
||||||
|
|
||||||
|
// QQ: should we analyze errno somehow ?
|
||||||
|
if (access(path_buff, F_OK))
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
File exists so we got to load triggers
|
||||||
|
FIXME: A lot of things to do here e.g. how about other funcs and being
|
||||||
|
more paranoical ?
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((parser= sql_parse_prepare(&path, &table->mem_root, 1)))
|
||||||
|
{
|
||||||
|
if (!strncmp(triggers_file_type.str, parser->type()->str,
|
||||||
|
parser->type()->length))
|
||||||
|
{
|
||||||
|
Field **fld, **old_fld;
|
||||||
|
Table_triggers_list *triggers=
|
||||||
|
new (&table->mem_root) Table_triggers_list();
|
||||||
|
|
||||||
|
if (!triggers)
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
|
||||||
|
if (parser->parse((gptr)triggers, &table->mem_root,
|
||||||
|
triggers_file_parameters, 1))
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
|
||||||
|
table->triggers= triggers;
|
||||||
|
|
||||||
|
/*
|
||||||
|
We have to prepare array of Field objects which will represent OLD.*
|
||||||
|
row values by referencing to record[1] instead of record[0]
|
||||||
|
|
||||||
|
TODO: This could be avoided if there is no ON UPDATE trigger.
|
||||||
|
*/
|
||||||
|
if (!(triggers->old_field=
|
||||||
|
(Field **)alloc_root(&table->mem_root, (table->fields + 1) *
|
||||||
|
sizeof(Field*))))
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
|
||||||
|
for (fld= table->field, old_fld= triggers->old_field; *fld;
|
||||||
|
fld++, old_fld++)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
QQ: it is supposed that it is ok to use this function for field
|
||||||
|
cloning...
|
||||||
|
*/
|
||||||
|
if (!(*old_fld= (*fld)->new_field(&table->mem_root, table)))
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
(*old_fld)->move_field((my_ptrdiff_t)(table->record[1] -
|
||||||
|
table->record[0]));
|
||||||
|
}
|
||||||
|
*old_fld= 0;
|
||||||
|
|
||||||
|
List_iterator_fast<LEX_STRING> it(triggers->definitions_list);
|
||||||
|
LEX_STRING *trg_create_str, *trg_name_str;
|
||||||
|
char *trg_name_buff;
|
||||||
|
LEX *old_lex= thd->lex, lex;
|
||||||
|
|
||||||
|
thd->lex= &lex;
|
||||||
|
|
||||||
|
while ((trg_create_str= it++))
|
||||||
|
{
|
||||||
|
mysql_init_query(thd, (uchar*)trg_create_str->str,
|
||||||
|
trg_create_str->length, true);
|
||||||
|
lex.trg_table= table;
|
||||||
|
if (yyparse((void *)thd) || thd->is_fatal_error)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Free lex associated resources
|
||||||
|
QQ: Do we really need all this stuff here ?
|
||||||
|
*/
|
||||||
|
if (lex.sphead)
|
||||||
|
{
|
||||||
|
if (&lex != thd->lex)
|
||||||
|
thd->lex->sphead->restore_lex(thd);
|
||||||
|
delete lex.sphead;
|
||||||
|
}
|
||||||
|
goto err_with_lex_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers->bodies[lex.trg_chistics.event]
|
||||||
|
[lex.trg_chistics.action_time]= lex.sphead;
|
||||||
|
lex.sphead= 0;
|
||||||
|
|
||||||
|
if (!(trg_name_buff= alloc_root(&table->mem_root,
|
||||||
|
sizeof(LEX_STRING) +
|
||||||
|
lex.name_and_length.length + 1)))
|
||||||
|
goto err_with_lex_cleanup;
|
||||||
|
|
||||||
|
trg_name_str= (LEX_STRING *)trg_name_buff;
|
||||||
|
trg_name_buff+= sizeof(LEX_STRING);
|
||||||
|
memcpy(trg_name_buff, lex.name_and_length.str,
|
||||||
|
lex.name_and_length.length + 1);
|
||||||
|
trg_name_str->str= trg_name_buff;
|
||||||
|
trg_name_str->length= lex.name_and_length.length;
|
||||||
|
|
||||||
|
old_global_mem_root= my_pthread_getspecific_ptr(MEM_ROOT*, THR_MALLOC);
|
||||||
|
my_pthread_setspecific_ptr(THR_MALLOC, &table->mem_root);
|
||||||
|
|
||||||
|
if (triggers->names_list.push_back(trg_name_str))
|
||||||
|
goto err_with_lex_cleanup;
|
||||||
|
|
||||||
|
my_pthread_setspecific_ptr(THR_MALLOC, old_global_mem_root);
|
||||||
|
|
||||||
|
lex_end(&lex);
|
||||||
|
}
|
||||||
|
thd->lex= old_lex;
|
||||||
|
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
|
||||||
|
err_with_lex_cleanup:
|
||||||
|
// QQ: anything else ?
|
||||||
|
lex_end(&lex);
|
||||||
|
thd->lex= old_lex;
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We don't care about this error message much because .TRG files will
|
||||||
|
be merged into .FRM anyway.
|
||||||
|
*/
|
||||||
|
my_error(ER_WRONG_OBJECT, MYF(0), table_name, triggers_file_ext, "TRIGGER");
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
}
|
62
sql/sql_trigger.h
Normal file
62
sql/sql_trigger.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
This class holds all information about triggers of table.
|
||||||
|
|
||||||
|
QQ: Will it be merged into TABLE in future ?
|
||||||
|
*/
|
||||||
|
class Table_triggers_list: public Sql_alloc
|
||||||
|
{
|
||||||
|
/* Triggers as SPs grouped by event, action_time */
|
||||||
|
sp_head *bodies[3][2];
|
||||||
|
/*
|
||||||
|
Copy of TABLE::Field array with field pointers set to old version
|
||||||
|
of record, used for OLD values in trigger on UPDATE.
|
||||||
|
*/
|
||||||
|
Field **old_field;
|
||||||
|
/*
|
||||||
|
Names of triggers.
|
||||||
|
Should correspond to order of triggers on definitions_list,
|
||||||
|
used in CREATE/DROP TRIGGER for looking up trigger by name.
|
||||||
|
*/
|
||||||
|
List<LEX_STRING> names_list;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
Field responsible for storing triggers definitions in file.
|
||||||
|
It have to be public because we are using it directly from parser.
|
||||||
|
*/
|
||||||
|
List<LEX_STRING> definitions_list;
|
||||||
|
|
||||||
|
Table_triggers_list():
|
||||||
|
old_field(0)
|
||||||
|
{
|
||||||
|
bzero((char *)bodies, sizeof(bodies));
|
||||||
|
}
|
||||||
|
~Table_triggers_list();
|
||||||
|
|
||||||
|
bool create_trigger(THD *thd, TABLE_LIST *table);
|
||||||
|
bool drop_trigger(THD *thd, TABLE_LIST *table);
|
||||||
|
bool process_triggers(THD *thd, trg_event_type event,
|
||||||
|
trg_action_time_type time_type)
|
||||||
|
{
|
||||||
|
int res= 0;
|
||||||
|
|
||||||
|
if (bodies[event][time_type])
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Similar to function invocation we don't need to surpress sending of
|
||||||
|
ok packets here because don't allow execute statements from trigger.
|
||||||
|
|
||||||
|
FIXME: We should juggle with security context here (because trigger
|
||||||
|
should be invoked with creator rights).
|
||||||
|
*/
|
||||||
|
res= bodies[event][time_type]->execute_function(thd, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_n_load(THD *thd, const char *db, const char *table_name,
|
||||||
|
TABLE *table);
|
||||||
|
|
||||||
|
friend class Item_trigger_field;
|
||||||
|
};
|
@ -23,6 +23,8 @@
|
|||||||
#include "mysql_priv.h"
|
#include "mysql_priv.h"
|
||||||
#include "sql_acl.h"
|
#include "sql_acl.h"
|
||||||
#include "sql_select.h"
|
#include "sql_select.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sql_trigger.h"
|
||||||
|
|
||||||
static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields);
|
static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields);
|
||||||
|
|
||||||
@ -354,6 +356,10 @@ int mysql_update(THD *thd,
|
|||||||
if (fill_record(fields,values, 0) || thd->net.report_error)
|
if (fill_record(fields,values, 0) || thd->net.report_error)
|
||||||
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 (!(error=table->file->update_row((byte*) table->record[1],
|
if (!(error=table->file->update_row((byte*) table->record[1],
|
||||||
@ -369,6 +375,10 @@ int mysql_update(THD *thd,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
295
sql/sql_yacc.yy
295
sql/sql_yacc.yy
@ -242,6 +242,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
|
|||||||
%token DISTINCT
|
%token DISTINCT
|
||||||
%token DUPLICATE_SYM
|
%token DUPLICATE_SYM
|
||||||
%token DYNAMIC_SYM
|
%token DYNAMIC_SYM
|
||||||
|
%token EACH_SYM
|
||||||
%token ENABLE_SYM
|
%token ENABLE_SYM
|
||||||
%token ENCLOSED
|
%token ENCLOSED
|
||||||
%token ESCAPED
|
%token ESCAPED
|
||||||
@ -411,6 +412,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
|
|||||||
%token TO_SYM
|
%token TO_SYM
|
||||||
%token TRAILING
|
%token TRAILING
|
||||||
%token TRANSACTION_SYM
|
%token TRANSACTION_SYM
|
||||||
|
%token TRIGGER_SYM
|
||||||
%token TRUE_SYM
|
%token TRUE_SYM
|
||||||
%token TYPE_SYM
|
%token TYPE_SYM
|
||||||
%token TYPES_SYM
|
%token TYPES_SYM
|
||||||
@ -1208,6 +1210,65 @@ create:
|
|||||||
}
|
}
|
||||||
opt_view_list AS select_init check_option
|
opt_view_list AS select_init check_option
|
||||||
{}
|
{}
|
||||||
|
| CREATE TRIGGER_SYM ident trg_action_time trg_event
|
||||||
|
ON table_ident FOR_SYM EACH_SYM ROW_SYM
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
sp_head *sp;
|
||||||
|
|
||||||
|
if (lex->sphead)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_SP_NO_RECURSIVE_CREATE, "TRIGGER");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
sp= new sp_head();
|
||||||
|
sp->reset_thd_mem_root(YYTHD);
|
||||||
|
sp->init(lex);
|
||||||
|
|
||||||
|
sp->m_type= TYPE_ENUM_TRIGGER;
|
||||||
|
lex->sphead= sp;
|
||||||
|
/*
|
||||||
|
We have to turn of CLIENT_MULTI_QUERIES while parsing a
|
||||||
|
stored procedure, otherwise yylex will chop it into pieces
|
||||||
|
at each ';'.
|
||||||
|
*/
|
||||||
|
sp->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
|
||||||
|
YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES;
|
||||||
|
|
||||||
|
bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
|
||||||
|
lex->sphead->m_chistics= &lex->sp_chistics;
|
||||||
|
lex->sphead->m_body_begin= lex->tok_start;
|
||||||
|
}
|
||||||
|
sp_proc_stmt
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
sp_head *sp= lex->sphead;
|
||||||
|
|
||||||
|
lex->sql_command= SQLCOM_CREATE_TRIGGER;
|
||||||
|
sp->init_strings(YYTHD, lex, NULL);
|
||||||
|
/* Restore flag if it was cleared above */
|
||||||
|
if (sp->m_old_cmq)
|
||||||
|
YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
|
||||||
|
sp->restore_thd_mem_root(YYTHD);
|
||||||
|
|
||||||
|
lex->name_and_length= $3;
|
||||||
|
|
||||||
|
/*
|
||||||
|
We have to do it after parsing trigger body, because some of
|
||||||
|
sp_proc_stmt alternatives are not saving/restoring LEX, so
|
||||||
|
lex->query_tables can be wiped out.
|
||||||
|
|
||||||
|
QQ: What are other consequences of this?
|
||||||
|
|
||||||
|
QQ: Could we loosen lock type in certain cases ?
|
||||||
|
*/
|
||||||
|
if (!lex->select_lex.add_table_to_list(YYTHD, $7,
|
||||||
|
(LEX_STRING*) 0,
|
||||||
|
TL_OPTION_UPDATING,
|
||||||
|
TL_WRITE))
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
sp_name:
|
sp_name:
|
||||||
@ -1747,14 +1808,14 @@ sp_proc_stmt:
|
|||||||
if (lex->sql_command != SQLCOM_SET_OPTION ||
|
if (lex->sql_command != SQLCOM_SET_OPTION ||
|
||||||
! lex->var_list.is_empty())
|
! lex->var_list.is_empty())
|
||||||
{
|
{
|
||||||
/* Currently we can't handle queries inside a FUNCTION,
|
/*
|
||||||
** because of the way table locking works.
|
Currently we can't handle queries inside a FUNCTION or
|
||||||
** This is unfortunate, and limits the usefulness of functions
|
TRIGGER, because of the way table locking works. This is
|
||||||
** a great deal, but it's nothing we can do about this at the
|
unfortunate, and limits the usefulness of functions and
|
||||||
** moment.
|
especially triggers a tremendously, but it's nothing we
|
||||||
*/
|
can do about this at the moment.
|
||||||
if (lex->sphead->m_type == TYPE_ENUM_FUNCTION &&
|
*/
|
||||||
lex->sql_command != SQLCOM_SET_OPTION)
|
if (lex->sphead->m_type != TYPE_ENUM_PROCEDURE)
|
||||||
{
|
{
|
||||||
send_error(YYTHD, ER_SP_BADSTATEMENT);
|
send_error(YYTHD, ER_SP_BADSTATEMENT);
|
||||||
YYABORT;
|
YYABORT;
|
||||||
@ -2289,6 +2350,22 @@ sp_unlabeled_control:
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
trg_action_time:
|
||||||
|
BEFORE_SYM
|
||||||
|
{ Lex->trg_chistics.action_time= TRG_ACTION_BEFORE; }
|
||||||
|
| AFTER_SYM
|
||||||
|
{ Lex->trg_chistics.action_time= TRG_ACTION_AFTER; }
|
||||||
|
;
|
||||||
|
|
||||||
|
trg_event:
|
||||||
|
INSERT
|
||||||
|
{ Lex->trg_chistics.event= TRG_EVENT_INSERT; }
|
||||||
|
| UPDATE_SYM
|
||||||
|
{ Lex->trg_chistics.event= TRG_EVENT_UPDATE; }
|
||||||
|
| DELETE_SYM
|
||||||
|
{ Lex->trg_chistics.event= TRG_EVENT_DELETE; }
|
||||||
|
;
|
||||||
|
|
||||||
create2:
|
create2:
|
||||||
'(' create2a {}
|
'(' create2a {}
|
||||||
| opt_create_table_options create3 {}
|
| opt_create_table_options create3 {}
|
||||||
@ -5422,7 +5499,21 @@ drop:
|
|||||||
lex->sql_command= SQLCOM_DROP_VIEW;
|
lex->sql_command= SQLCOM_DROP_VIEW;
|
||||||
lex->drop_if_exists= $3;
|
lex->drop_if_exists= $3;
|
||||||
}
|
}
|
||||||
;
|
| DROP TRIGGER_SYM ident '.' ident
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
lex->sql_command= SQLCOM_DROP_TRIGGER;
|
||||||
|
/* QQ: Could we loosen lock type in certain cases ? */
|
||||||
|
if (!lex->select_lex.add_table_to_list(YYTHD,
|
||||||
|
new Table_ident($3),
|
||||||
|
(LEX_STRING*) 0,
|
||||||
|
TL_OPTION_UPDATING,
|
||||||
|
TL_WRITE))
|
||||||
|
YYABORT;
|
||||||
|
lex->name_and_length= $5;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
table_list:
|
table_list:
|
||||||
table_name
|
table_name
|
||||||
@ -6413,18 +6504,69 @@ simple_ident_q:
|
|||||||
{
|
{
|
||||||
THD *thd= YYTHD;
|
THD *thd= YYTHD;
|
||||||
LEX *lex= thd->lex;
|
LEX *lex= thd->lex;
|
||||||
SELECT_LEX *sel= lex->current_select;
|
|
||||||
if (sel->no_table_names_allowed)
|
/*
|
||||||
{
|
FIXME This will work ok in simple_ident_nospvar case because
|
||||||
my_printf_error(ER_TABLENAME_NOT_ALLOWED_HERE,
|
we can't meet simple_ident_nospvar in trigger now. But it
|
||||||
ER(ER_TABLENAME_NOT_ALLOWED_HERE),
|
should be changed in future.
|
||||||
MYF(0), $1.str, thd->where);
|
*/
|
||||||
}
|
if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER &&
|
||||||
$$= (sel->parsing_place != IN_HAVING ||
|
(!my_strcasecmp(system_charset_info, $1.str, "NEW") ||
|
||||||
sel->get_in_sum_expr() > 0) ?
|
!my_strcasecmp(system_charset_info, $1.str, "OLD")))
|
||||||
(Item*) new Item_field(NullS,$1.str,$3.str) :
|
{
|
||||||
(Item*) new Item_ref(0,0,NullS,$1.str,$3.str);
|
bool new_row= ($1.str[0]=='N' || $1.str[0]=='n');
|
||||||
}
|
|
||||||
|
if (lex->trg_chistics.event == TRG_EVENT_INSERT &&
|
||||||
|
!new_row)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_TRG_NO_SUCH_ROW_IN_TRG, "OLD",
|
||||||
|
"on INSERT");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lex->trg_chistics.event == TRG_EVENT_DELETE &&
|
||||||
|
new_row)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_TRG_NO_SUCH_ROW_IN_TRG, "NEW",
|
||||||
|
"on DELETE");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item_trigger_field *trg_fld=
|
||||||
|
new Item_trigger_field(new_row ? Item_trigger_field::NEW_ROW :
|
||||||
|
Item_trigger_field::OLD_ROW,
|
||||||
|
$3.str);
|
||||||
|
|
||||||
|
if (lex->trg_table &&
|
||||||
|
trg_fld->setup_field(thd, lex->trg_table,
|
||||||
|
lex->trg_chistics.event))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
FIXME. Far from perfect solution. See comment for
|
||||||
|
"SET NEW.field_name:=..." for more info.
|
||||||
|
*/
|
||||||
|
net_printf(YYTHD, ER_BAD_FIELD_ERROR, $3.str,
|
||||||
|
new_row ? "NEW": "OLD");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
$$= (Item *)trg_fld;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SELECT_LEX *sel= lex->current_select;
|
||||||
|
if (sel->no_table_names_allowed)
|
||||||
|
{
|
||||||
|
my_printf_error(ER_TABLENAME_NOT_ALLOWED_HERE,
|
||||||
|
ER(ER_TABLENAME_NOT_ALLOWED_HERE),
|
||||||
|
MYF(0), $1.str, thd->where);
|
||||||
|
}
|
||||||
|
$$= (sel->parsing_place != IN_HAVING ||
|
||||||
|
sel->get_in_sum_expr() > 0) ?
|
||||||
|
(Item*) new Item_field(NullS,$1.str,$3.str) :
|
||||||
|
(Item*) new Item_ref(0,0,NullS,$1.str,$3.str);
|
||||||
|
}
|
||||||
|
}
|
||||||
| '.' ident '.' ident
|
| '.' ident '.' ident
|
||||||
{
|
{
|
||||||
THD *thd= YYTHD;
|
THD *thd= YYTHD;
|
||||||
@ -6854,13 +6996,78 @@ opt_var_ident_type:
|
|||||||
option_value:
|
option_value:
|
||||||
'@' ident_or_text equal expr
|
'@' ident_or_text equal expr
|
||||||
{
|
{
|
||||||
Lex->var_list.push_back(new set_var_user(new Item_func_set_user_var($2,$4)));
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
We have to use special instruction in functions and triggers
|
||||||
|
because sp_instr_stmt will close all tables and thus ruin
|
||||||
|
execution of statement invoking function or trigger.
|
||||||
|
|
||||||
|
We also do not want to allow expression with subselects in
|
||||||
|
this case.
|
||||||
|
*/
|
||||||
|
if (lex->query_tables)
|
||||||
|
{
|
||||||
|
send_error(YYTHD, ER_SP_SUBSELECT_NYI);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
sp_instr_set_user_var *i=
|
||||||
|
new sp_instr_set_user_var(lex->sphead->instructions(),
|
||||||
|
lex->spcont, $2, $4);
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
lex->var_list.push_back(new set_var_user(new Item_func_set_user_var($2,$4)));
|
||||||
|
|
||||||
}
|
}
|
||||||
| internal_variable_name equal set_expr_or_default
|
| internal_variable_name equal set_expr_or_default
|
||||||
{
|
{
|
||||||
LEX *lex=Lex;
|
LEX *lex=Lex;
|
||||||
|
|
||||||
if ($1.var)
|
if ($1.var == &trg_new_row_fake_var)
|
||||||
|
{
|
||||||
|
/* We are in trigger and assigning value to field of new row */
|
||||||
|
Item *it;
|
||||||
|
sp_instr_set_trigger_field *i;
|
||||||
|
if (lex->query_tables)
|
||||||
|
{
|
||||||
|
send_error(YYTHD, ER_SP_SUBSELECT_NYI);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
if ($3)
|
||||||
|
it= $3;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* QQ: Shouldn't this be field's default value ? */
|
||||||
|
it= new Item_null();
|
||||||
|
}
|
||||||
|
i= new sp_instr_set_trigger_field(lex->sphead->instructions(),
|
||||||
|
lex->spcont, $1.base_name, it);
|
||||||
|
if (lex->trg_table && i->setup_field(YYTHD, lex->trg_table,
|
||||||
|
lex->trg_chistics.event))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
FIXME. Now we are catching this kind of errors only
|
||||||
|
during opening tables. But this doesn't save us from most
|
||||||
|
common user error - misspelling field name, because we
|
||||||
|
will bark too late in this case... Moreover it is easy to
|
||||||
|
make table unusable with such kind of error...
|
||||||
|
|
||||||
|
So in future we either have to parse trigger definition
|
||||||
|
second time during create trigger or gather all trigger
|
||||||
|
fields in one list and perform setup_field() for them as
|
||||||
|
separate stage.
|
||||||
|
|
||||||
|
Error message also should be improved.
|
||||||
|
*/
|
||||||
|
net_printf(YYTHD, ER_BAD_FIELD_ERROR, $1.base_name, "NEW");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
}
|
||||||
|
else if ($1.var)
|
||||||
{ /* System variable */
|
{ /* System variable */
|
||||||
lex->var_list.push_back(new set_var(lex->option_type, $1.var,
|
lex->var_list.push_back(new set_var(lex->option_type, $1.var,
|
||||||
&$1.base_name, $3));
|
&$1.base_name, $3));
|
||||||
@ -6975,18 +7182,46 @@ internal_variable_name:
|
|||||||
}
|
}
|
||||||
| ident '.' ident
|
| ident '.' ident
|
||||||
{
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
if (check_reserved_words(&$1))
|
if (check_reserved_words(&$1))
|
||||||
{
|
{
|
||||||
yyerror(ER(ER_SYNTAX_ERROR));
|
yyerror(ER(ER_SYNTAX_ERROR));
|
||||||
YYABORT;
|
YYABORT;
|
||||||
}
|
}
|
||||||
sys_var *tmp=find_sys_var($3.str, $3.length);
|
if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER &&
|
||||||
if (!tmp)
|
(!my_strcasecmp(system_charset_info, $1.str, "NEW") ||
|
||||||
YYABORT;
|
!my_strcasecmp(system_charset_info, $1.str, "OLD")))
|
||||||
if (!tmp->is_struct())
|
{
|
||||||
net_printf(YYTHD, ER_VARIABLE_IS_NOT_STRUCT, $3.str);
|
if ($1.str[0]=='O' || $1.str[0]=='o')
|
||||||
$$.var= tmp;
|
{
|
||||||
$$.base_name= $1;
|
net_printf(YYTHD, ER_TRG_CANT_CHANGE_ROW, "OLD", "");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
if (lex->trg_chistics.event == TRG_EVENT_DELETE)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_TRG_NO_SUCH_ROW_IN_TRG, "NEW",
|
||||||
|
"on DELETE");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
if (lex->trg_chistics.action_time == TRG_ACTION_AFTER)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_TRG_CANT_CHANGE_ROW, "NEW", "after ");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
/* This special combination will denote field of NEW row */
|
||||||
|
$$.var= &trg_new_row_fake_var;
|
||||||
|
$$.base_name= $3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sys_var *tmp=find_sys_var($3.str, $3.length);
|
||||||
|
if (!tmp)
|
||||||
|
YYABORT;
|
||||||
|
if (!tmp->is_struct())
|
||||||
|
net_printf(YYTHD, ER_VARIABLE_IS_NOT_STRUCT, $3.str);
|
||||||
|
$$.var= tmp;
|
||||||
|
$$.base_name= $1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
| DEFAULT '.' ident
|
| DEFAULT '.' ident
|
||||||
{
|
{
|
||||||
|
@ -73,6 +73,7 @@ typedef struct st_filesort_info
|
|||||||
|
|
||||||
class Field_timestamp;
|
class Field_timestamp;
|
||||||
class Field_blob;
|
class Field_blob;
|
||||||
|
class Table_triggers_list;
|
||||||
|
|
||||||
struct st_table {
|
struct st_table {
|
||||||
handler *file;
|
handler *file;
|
||||||
@ -154,6 +155,8 @@ struct st_table {
|
|||||||
REGINFO reginfo; /* field connections */
|
REGINFO reginfo; /* field connections */
|
||||||
MEM_ROOT mem_root;
|
MEM_ROOT mem_root;
|
||||||
GRANT_INFO grant;
|
GRANT_INFO grant;
|
||||||
|
/* Table's triggers, 0 if there are no of them */
|
||||||
|
Table_triggers_list *triggers;
|
||||||
|
|
||||||
char *table_cache_key;
|
char *table_cache_key;
|
||||||
char *table_name,*real_name,*path;
|
char *table_name,*real_name,*path;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user