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_SP_NO_DROP_SP 1357
|
||||
#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
|
||||
drop procedure bug5258|
|
||||
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|
|
||||
create table fac (n int unsigned not null primary key, f bigint 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_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
|
||||
|
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 \
|
||||
tztime.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_tina.h
|
||||
|
||||
@ -97,7 +97,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \
|
||||
gstream.cc spatial.cc sql_help.cc protocol_cursor.cc \
|
||||
tztime.cc my_time.c \
|
||||
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_tina.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 "sp_rcontext.h"
|
||||
#include "sql_acl.h"
|
||||
#include "sp_head.h"
|
||||
#include "sql_trigger.h"
|
||||
|
||||
static void mark_as_dependent(THD *thd,
|
||||
SELECT_LEX *last, SELECT_LEX *current,
|
||||
@ -2462,6 +2464,97 @@ void Item_insert_value::print(String *str)
|
||||
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
|
||||
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,
|
||||
FIELD_VARIANCE_ITEM, INSERT_VALUE_ITEM,
|
||||
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 };
|
||||
|
||||
@ -440,6 +440,7 @@ public:
|
||||
|
||||
class Item_field :public Item_ident
|
||||
{
|
||||
protected:
|
||||
void set_field(Field *field);
|
||||
public:
|
||||
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
|
||||
{
|
||||
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 *
|
||||
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);
|
||||
#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);
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
|
@ -948,6 +948,7 @@ public:
|
||||
bool fix_fields(THD *thd, struct st_table_list *tables, Item **ref);
|
||||
void fix_length_and_dec();
|
||||
void print(String *str);
|
||||
void print_as_stmt(String *str);
|
||||
const char *func_name() const { return "set_user_var"; }
|
||||
};
|
||||
|
||||
@ -1130,25 +1131,31 @@ public:
|
||||
double val()
|
||||
{
|
||||
Item *it;
|
||||
double d;
|
||||
|
||||
if (execute(&it))
|
||||
{
|
||||
null_value= 1;
|
||||
return 0.0;
|
||||
}
|
||||
return it->val();
|
||||
d= it->val();
|
||||
null_value= it->null_value;
|
||||
return d;
|
||||
}
|
||||
|
||||
String *val_str(String *str)
|
||||
{
|
||||
Item *it;
|
||||
String *s;
|
||||
|
||||
if (execute(&it))
|
||||
{
|
||||
null_value= 1;
|
||||
return NULL;
|
||||
}
|
||||
return it->val_str(str);
|
||||
s= it->val_str(str);
|
||||
null_value= it->null_value;
|
||||
return s;
|
||||
}
|
||||
|
||||
void fix_length_and_dec();
|
||||
|
@ -167,6 +167,7 @@ static SYMBOL symbols[] = {
|
||||
{ "DUMPFILE", SYM(DUMPFILE)},
|
||||
{ "DUPLICATE", SYM(DUPLICATE_SYM)},
|
||||
{ "DYNAMIC", SYM(DYNAMIC_SYM)},
|
||||
{ "EACH", SYM(EACH_SYM)},
|
||||
{ "ELSE", SYM(ELSE)},
|
||||
{ "ELSEIF", SYM(ELSEIF_SYM)},
|
||||
{ "ENABLE", SYM(ENABLE_SYM)},
|
||||
@ -470,6 +471,7 @@ static SYMBOL symbols[] = {
|
||||
{ "TO", SYM(TO_SYM)},
|
||||
{ "TRAILING", SYM(TRAILING)},
|
||||
{ "TRANSACTION", SYM(TRANSACTION_SYM)},
|
||||
{ "TRIGGER", SYM(TRIGGER_SYM)},
|
||||
{ "TRUE", SYM(TRUE_SYM)},
|
||||
{ "TRUNCATE", SYM(TRUNCATE_SYM)},
|
||||
{ "TYPE", SYM(TYPE_SYM)},
|
||||
|
@ -481,6 +481,7 @@ int mysql_rm_table_part2_with_lock(THD *thd, TABLE_LIST *tables,
|
||||
bool log_query);
|
||||
int quick_rm_table(enum db_type base,const char *db,
|
||||
const char *table_name);
|
||||
void close_cached_table(THD *thd, TABLE *table);
|
||||
bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list);
|
||||
bool mysql_change_db(THD *thd,const char *name);
|
||||
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,
|
||||
ha_rows rows, ulong options);
|
||||
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_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
|
||||
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 allow_rowid,
|
||||
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
|
||||
#include <openssl/des.h>
|
||||
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
|
||||
parse_quated_escaped_string()
|
||||
parse_quoted_escaped_string()
|
||||
*/
|
||||
switch(*ptr) {
|
||||
case '\\': // escape character
|
||||
@ -154,11 +154,10 @@ write_parameter(IO_CACHE *file, gptr base, File_option *parameter,
|
||||
LEX_STRING *str;
|
||||
while ((str= it++))
|
||||
{
|
||||
num.set((ulonglong)str->length, &my_charset_bin);
|
||||
// ',' after string to detect list continuation
|
||||
// We need ' ' after string to detect list continuation
|
||||
if ((!first && 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))
|
||||
{
|
||||
DBUG_RETURN(TRUE);
|
||||
@ -486,7 +485,7 @@ read_escaped_string(char *ptr, char *eol, LEX_STRING *str)
|
||||
return TRUE;
|
||||
/*
|
||||
Should be in sync with write_escaped_string() and
|
||||
parse_quated_escaped_string()
|
||||
parse_quoted_escaped_string()
|
||||
*/
|
||||
switch(*ptr) {
|
||||
case '\\':
|
||||
@ -562,7 +561,7 @@ parse_escaped_string(char *ptr, char *end, MEM_ROOT *mem_root, LEX_STRING *str)
|
||||
*/
|
||||
|
||||
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)
|
||||
{
|
||||
char *eol;
|
||||
@ -684,7 +683,6 @@ File_parser::parse(gptr base, MEM_ROOT *mem_root,
|
||||
my_error(ER_FPARSER_ERROR_IN_PARAMETER, MYF(0),
|
||||
parameter->name.str, line);
|
||||
DBUG_RETURN(TRUE);
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
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
|
||||
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);
|
||||
list= (List<LEX_STRING>*)(base + parameter->offset);
|
||||
@ -741,7 +740,7 @@ File_parser::parse(gptr base, MEM_ROOT *mem_root,
|
||||
sizeof(LEX_STRING))) ||
|
||||
list->push_back(str))
|
||||
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;
|
||||
switch (*ptr) {
|
||||
case '\n':
|
||||
|
@ -27,7 +27,8 @@ enum file_opt_type {
|
||||
FILE_OPTIONS_REV, /* Revision version number (ulonglong) */
|
||||
FILE_OPTIONS_TIMESTAMP, /* timestamp (LEX_STRING have to be
|
||||
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
|
||||
|
@ -387,3 +387,8 @@ character-set=latin2
|
||||
"View '%-.64s.%-.64s' references invalid table(s) or column(s)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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 не может быть использован сейчас (алгоритм будет неопеределенным)"
|
||||
"Обновляемый view не содержит ключа использованной в нем таблиц(ы)"
|
||||
"View '%-.64s.%-.64s' ссылается на несуществующие таблицы или слолбцы"
|
||||
"View '%-.64s.%-.64s' ÓÓÙÌÁÅÔÓÑ ÎÁ ÎÅÓÕÝÅÓÔ×ÕÀÝÉÅ ÔÁÂÌÉÃÙ ÉÌÉ ÓÔÏÌÂÃÙ"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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)"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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' ÐÏÓÉÌÁ¤ÔÓÑ ÎÁ ÎŦÓÎÕÀÞ¦ ÔÁÂÌÉæ ÁÂÏ ÓÔÏ×Âæ"
|
||||
"Can't drop a %s from within another stored routine"
|
||||
"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 */
|
||||
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 */
|
||||
m_db.length= name->m_db.length;
|
||||
if (name->m_db.length == 0)
|
||||
m_db.str= NULL;
|
||||
else
|
||||
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_db.length= name->m_db.length;
|
||||
if (name->m_db.length == 0)
|
||||
m_db.str= NULL;
|
||||
else
|
||||
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)
|
||||
name->init_qname(thd);
|
||||
m_qname.length= name->m_qname.length;
|
||||
m_qname.str= strmake_root(root, name->m_qname.str, m_qname.length);
|
||||
if (name->m_qname.length == 0)
|
||||
name->init_qname(thd);
|
||||
m_qname.length= name->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)
|
||||
{
|
||||
/* 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;
|
||||
|
||||
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();
|
||||
|
||||
if (it)
|
||||
@ -763,6 +774,9 @@ sp_head::reset_lex(THD *thd)
|
||||
/* And keep the SP stuff too */
|
||||
sublex->sphead= oldlex->sphead;
|
||||
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;
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
@ -1211,7 +1225,7 @@ sp_instr_set::execute(THD *thd, uint *nextp)
|
||||
thd->spcont->set_item(m_offset, it);
|
||||
}
|
||||
*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);
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
@ -1226,6 +1240,60 @@ sp_instr_set::print(String *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
|
||||
//
|
||||
@ -1314,7 +1382,7 @@ sp_instr_jump_if::execute(THD *thd, uint *nextp)
|
||||
else
|
||||
*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);
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
@ -1371,7 +1439,7 @@ sp_instr_jump_if_not::execute(THD *thd, uint *nextp)
|
||||
else
|
||||
*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);
|
||||
DBUG_RETURN(res);
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
// in the CREATE TABLE command.
|
||||
#define TYPE_ENUM_FUNCTION 1
|
||||
#define TYPE_ENUM_PROCEDURE 2
|
||||
#define TYPE_ENUM_TRIGGER 3
|
||||
|
||||
Item_result
|
||||
sp_map_result_type(enum enum_field_types type);
|
||||
@ -377,6 +378,72 @@ private:
|
||||
}; // 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
|
||||
{
|
||||
sp_instr_jump(const sp_instr_jump &); /* Prevent use of these */
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "mysql_priv.h"
|
||||
#include "sql_acl.h"
|
||||
#include "sql_select.h"
|
||||
#include "sp_head.h"
|
||||
#include "sql_trigger.h"
|
||||
#include <m_ctype.h>
|
||||
#include <my_dir.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 ha_open_flags, TABLE *outparam,
|
||||
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,
|
||||
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)
|
||||
{ // Free all structures
|
||||
free_io_cache(table);
|
||||
delete table->triggers;
|
||||
if (table->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,
|
||||
key_length)))
|
||||
{
|
||||
delete table->triggers;
|
||||
closefrm(table);
|
||||
pthread_mutex_unlock(&LOCK_open);
|
||||
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,
|
||||
table->key_length)))
|
||||
{
|
||||
delete tmp.triggers;
|
||||
closefrm(&tmp); // End of memory
|
||||
goto end;
|
||||
}
|
||||
@ -1109,6 +1110,7 @@ bool reopen_table(TABLE *table,bool locked)
|
||||
tmp.next= table->next;
|
||||
tmp.prev= table->prev;
|
||||
|
||||
delete table->triggers;
|
||||
if (table->file)
|
||||
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)
|
||||
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
|
||||
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 \
|
||||
memory to write 'DELETE FROM `%s`.`%s`' to the binary log",db,name);
|
||||
delete entry->triggers;
|
||||
if (entry->file)
|
||||
closefrm(entry);
|
||||
goto err;
|
||||
@ -2070,10 +2076,10 @@ find_field_in_table(THD *thd, TABLE_LIST *table_list,
|
||||
# pointer to field
|
||||
*/
|
||||
|
||||
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)
|
||||
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)
|
||||
{
|
||||
Field **field_ptr, *field;
|
||||
uint cached_field_index= *cached_field_index_ptr;
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include "mysql_priv.h"
|
||||
#include "ha_innodb.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,
|
||||
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
|
||||
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])))
|
||||
{
|
||||
deleted++;
|
||||
@ -183,6 +190,10 @@ int mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SQL_LIST *order,
|
||||
error= 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (table->triggers)
|
||||
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
|
||||
TRG_ACTION_AFTER);
|
||||
}
|
||||
else
|
||||
table->file->unlock_row(); // Row failed selection, release lock on it
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
#include "mysql_priv.h"
|
||||
#include "sql_acl.h"
|
||||
#include "sp_head.h"
|
||||
#include "sql_trigger.h"
|
||||
|
||||
static int check_null_fields(THD *thd,TABLE *entry);
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
@ -302,6 +304,12 @@ int mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
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
|
||||
if (lock_type == TL_WRITE_DELAYED)
|
||||
{
|
||||
@ -324,6 +332,9 @@ int mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||
id= thd->last_insert_id;
|
||||
}
|
||||
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_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
|
||||
@ -139,6 +144,7 @@ void lex_start(THD *thd, uchar *buf,uint length)
|
||||
lex->duplicates= DUP_ERROR;
|
||||
lex->sphead= NULL;
|
||||
lex->spcont= NULL;
|
||||
lex->trg_table= NULL;
|
||||
|
||||
extern byte *sp_lex_spfuns_key(const byte *ptr, uint *plen, my_bool first);
|
||||
hash_free(&lex->spfuns);
|
||||
|
@ -85,6 +85,7 @@ enum enum_sql_command {
|
||||
SQLCOM_SHOW_STATUS_PROC, SQLCOM_SHOW_STATUS_FUNC,
|
||||
SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE,
|
||||
SQLCOM_CREATE_VIEW, SQLCOM_DROP_VIEW,
|
||||
SQLCOM_CREATE_TRIGGER, SQLCOM_DROP_TRIGGER,
|
||||
/* This should be the last !!! */
|
||||
SQLCOM_END
|
||||
};
|
||||
@ -602,6 +603,15 @@ struct st_sp_chistics
|
||||
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 */
|
||||
|
||||
typedef struct st_lex
|
||||
@ -713,6 +723,14 @@ typedef struct st_lex
|
||||
rexecuton
|
||||
*/
|
||||
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)
|
||||
{
|
||||
|
@ -3877,6 +3877,20 @@ purposes internal to the MySQL server", MYF(0));
|
||||
res= mysql_drop_view(thd, first_table, thd->lex->drop_mode);
|
||||
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 */
|
||||
send_ok(thd);
|
||||
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 !
|
||||
*/
|
||||
|
||||
static bool close_cached_table(THD *thd, TABLE *table)
|
||||
void close_cached_table(THD *thd, TABLE *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 */
|
||||
pthread_cond_broadcast(&COND_refresh);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
table_name=thd->strdup(table_name); // must be saved
|
||||
if (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;
|
||||
}
|
||||
close_cached_table(thd, table);
|
||||
table=0; // Marker that table is closed
|
||||
}
|
||||
#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 "sql_acl.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);
|
||||
|
||||
@ -354,6 +356,10 @@ int mysql_update(THD *thd,
|
||||
if (fill_record(fields,values, 0) || thd->net.report_error)
|
||||
break; /* purecov: inspected */
|
||||
found++;
|
||||
|
||||
if (table->triggers)
|
||||
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_BEFORE);
|
||||
|
||||
if (compare_record(table, query_id))
|
||||
{
|
||||
if (!(error=table->file->update_row((byte*) table->record[1],
|
||||
@ -369,6 +375,10 @@ int mysql_update(THD *thd,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (table->triggers)
|
||||
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_AFTER);
|
||||
|
||||
if (!--limit && using_limit)
|
||||
{
|
||||
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 DUPLICATE_SYM
|
||||
%token DYNAMIC_SYM
|
||||
%token EACH_SYM
|
||||
%token ENABLE_SYM
|
||||
%token ENCLOSED
|
||||
%token ESCAPED
|
||||
@ -411,6 +412,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
|
||||
%token TO_SYM
|
||||
%token TRAILING
|
||||
%token TRANSACTION_SYM
|
||||
%token TRIGGER_SYM
|
||||
%token TRUE_SYM
|
||||
%token TYPE_SYM
|
||||
%token TYPES_SYM
|
||||
@ -1208,6 +1210,65 @@ create:
|
||||
}
|
||||
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:
|
||||
@ -1747,14 +1808,14 @@ sp_proc_stmt:
|
||||
if (lex->sql_command != SQLCOM_SET_OPTION ||
|
||||
! lex->var_list.is_empty())
|
||||
{
|
||||
/* Currently we can't handle queries inside a FUNCTION,
|
||||
** because of the way table locking works.
|
||||
** This is unfortunate, and limits the usefulness of functions
|
||||
** a great deal, 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)
|
||||
/*
|
||||
Currently we can't handle queries inside a FUNCTION or
|
||||
TRIGGER, because of the way table locking works. This is
|
||||
unfortunate, and limits the usefulness of functions and
|
||||
especially triggers a tremendously, but it's nothing we
|
||||
can do about this at the moment.
|
||||
*/
|
||||
if (lex->sphead->m_type != TYPE_ENUM_PROCEDURE)
|
||||
{
|
||||
send_error(YYTHD, ER_SP_BADSTATEMENT);
|
||||
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:
|
||||
'(' create2a {}
|
||||
| opt_create_table_options create3 {}
|
||||
@ -5422,7 +5499,21 @@ drop:
|
||||
lex->sql_command= SQLCOM_DROP_VIEW;
|
||||
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_name
|
||||
@ -6413,18 +6504,69 @@ simple_ident_q:
|
||||
{
|
||||
THD *thd= YYTHD;
|
||||
LEX *lex= thd->lex;
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME This will work ok in simple_ident_nospvar case because
|
||||
we can't meet simple_ident_nospvar in trigger now. But it
|
||||
should be changed in future.
|
||||
*/
|
||||
if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER &&
|
||||
(!my_strcasecmp(system_charset_info, $1.str, "NEW") ||
|
||||
!my_strcasecmp(system_charset_info, $1.str, "OLD")))
|
||||
{
|
||||
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
|
||||
{
|
||||
THD *thd= YYTHD;
|
||||
@ -6854,13 +6996,78 @@ opt_var_ident_type:
|
||||
option_value:
|
||||
'@' 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
|
||||
{
|
||||
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 */
|
||||
lex->var_list.push_back(new set_var(lex->option_type, $1.var,
|
||||
&$1.base_name, $3));
|
||||
@ -6975,18 +7182,46 @@ internal_variable_name:
|
||||
}
|
||||
| ident '.' ident
|
||||
{
|
||||
LEX *lex= Lex;
|
||||
if (check_reserved_words(&$1))
|
||||
{
|
||||
yyerror(ER(ER_SYNTAX_ERROR));
|
||||
YYABORT;
|
||||
}
|
||||
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;
|
||||
if (lex->sphead && lex->sphead->m_type == TYPE_ENUM_TRIGGER &&
|
||||
(!my_strcasecmp(system_charset_info, $1.str, "NEW") ||
|
||||
!my_strcasecmp(system_charset_info, $1.str, "OLD")))
|
||||
{
|
||||
if ($1.str[0]=='O' || $1.str[0]=='o')
|
||||
{
|
||||
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
|
||||
{
|
||||
|
@ -73,6 +73,7 @@ typedef struct st_filesort_info
|
||||
|
||||
class Field_timestamp;
|
||||
class Field_blob;
|
||||
class Table_triggers_list;
|
||||
|
||||
struct st_table {
|
||||
handler *file;
|
||||
@ -154,6 +155,8 @@ struct st_table {
|
||||
REGINFO reginfo; /* field connections */
|
||||
MEM_ROOT mem_root;
|
||||
GRANT_INFO grant;
|
||||
/* Table's triggers, 0 if there are no of them */
|
||||
Table_triggers_list *triggers;
|
||||
|
||||
char *table_cache_key;
|
||||
char *table_name,*real_name,*path;
|
||||
|
Loading…
x
Reference in New Issue
Block a user