Commit a914b527 authored by unknown's avatar unknown

Save and clear run context before executing a stored function or trigger and restore it afterwards.

This allows us to use statement replication with functions and triggers
The following things are fixed with this patch:
- NOW() and automatic timestamps takes the value from the main event for functions and triggers (which allows these to replicate with statement level logging)
- No side effects for triggers or functions with auto-increment values(), last_insert_id(), rand() or found_rows()
- Triggers can't return result sets

Fixes bugs:
#12480: NOW() is not constant in a trigger
#12481: Using NOW() in a stored function breaks statement based replication
#12482: Triggers has side effects with auto_increment values
#11587: trigger causes lost connection error


mysql-test/r/trigger.result:
  Added test fpr big
mysql-test/t/sp-error.test:
  Changed error message numbers
mysql-test/t/trigger.test:
  Added test for trigger returning result (#11587)
sql/item_func.cc:
  Store the first used seed value for RAND() value.
  (This makes rand() replicatable in functions and triggers)
  Save and clear run context before executing a stored function and restore it afterwards.
  This removes side effects of stored functions for RAND(), auto-increment values and NOW() and makes most stored function replicatable
sql/share/errmsg.txt:
  Reuse error message also for triggers
sql/sp_head.cc:
  If in function or trigger, don't change value of NOW()
  (This allows us to use statement replication with functions that directly or indirectly uses timestamps)
sql/sql_class.cc:
  Added framework for storing and retrieving run context while exceuting triggers or stored functions.
sql/sql_class.h:
  Added framework for storing and retrieving run context while exceuting triggers or stored functions.
sql/sql_parse.cc:
  If in function or trigger, don't change value of NOW()
  (This allows us to use statement replication with functions that directly or indirectly uses timestamps)
sql/sql_trigger.cc:
  Moved process_triggers function from sql_trigger.h
  Use reset/restore sub_statement_state while executing triggers to avoid side effects and make them replicatable
sql/sql_trigger.h:
  Moved process_triggers function from sql_trigger.h
  Use reset/restore sub_statement_state while executing triggers to avoid side effects and make them replicatable
sql/sql_yacc.yy:
  Give error message if trigger can return a result set (Bug #11587)
tests/fork_big2.pl:
  Removed return from end of lines
mysql-test/r/rpl_trigger.result:
  New BitKeeper file ``mysql-test/r/rpl_trigger.result''
mysql-test/t/rpl_trigger.test:
  New BitKeeper file ``mysql-test/t/rpl_trigger.test''
parent 7cfb6540
stop slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
reset master;
reset slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
start slave;
create table t1 (a int auto_increment, primary key (a), b int, rand_value double not null);
create table t2 (a int auto_increment, primary key (a), b int);
create table t3 (a int auto_increment, primary key (a), name varchar(64) not null, old_a int, old_b int, rand_value double not null);
create trigger t1 before insert on t1 for each row
begin
insert into t3 values (NULL, "t1", new.a, new.b, rand());
end|
create trigger t2 after insert on t2 for each row
begin
insert into t3 values (NULL, "t2", new.a, new.b, rand());
end|
insert into t3 values(100,"log",0,0,0);
SET @@RAND_SEED1=658490765, @@RAND_SEED2=635893186;
insert into t1 values(1,1,rand()),(NULL,2,rand());
insert into t2 (b) values(last_insert_id());
insert into t2 values(3,0),(NULL,0);
insert into t2 values(NULL,0),(500,0);
select a,b, truncate(rand_value,4) from t1;
a b truncate(rand_value,4)
1 1 0.4320
2 2 0.3055
select * from t2;
a b
1 2
3 0
4 0
5 0
500 0
select a,name, old_a, old_b, truncate(rand_value,4) from t3;
a name old_a old_b truncate(rand_value,4)
100 log 0 0 0.0000
101 t1 1 1 0.3203
102 t1 0 2 0.5666
103 t2 1 2 0.9164
104 t2 3 0 0.8826
105 t2 4 0 0.6635
106 t2 5 0 0.6699
107 t2 500 0 0.3593
--- On slave --
select a,b, truncate(rand_value,4) from t1;
a b truncate(rand_value,4)
1 1 0.4320
2 2 0.3055
select * from t2;
a b
1 2
3 0
4 0
5 0
500 0
select a,name, old_a, old_b, truncate(rand_value,4) from t3;
a name old_a old_b truncate(rand_value,4)
100 log 0 0 0.0000
101 t1 1 1 0.3203
102 t1 0 2 0.5666
103 t2 1 2 0.9164
104 t2 3 0 0.8826
105 t2 4 0 0.6635
106 t2 5 0 0.6699
107 t2 500 0 0.3593
drop table t1,t2,t3;
select get_lock("bug12480",2);
get_lock("bug12480",2)
1
create table t1 (a datetime,b datetime, c datetime);
drop function if exists bug12480;
Warnings:
Note 1305 FUNCTION bug12480 does not exist
create function bug12480() returns datetime
begin
set @a=get_lock("bug12480",2);
return now();
end|
create trigger t1_first before insert on t1
for each row begin
set @a=get_lock("bug12480",2);
set new.b= now();
set new.c= bug12480();
end
|
insert into t1 set a = now();
select a=b && a=c from t1;
a=b && a=c
1
--- On slave --
select a=b && a=c from t1;
a=b && a=c
1
test
1
truncate table t1;
drop trigger t1_first;
insert into t1 values ("2003-03-03","2003-03-03","2003-03-03"),(bug12480(),bug12480(),bug12480()),(now(),now(),now());
select a=b && a=c from t1;
a=b && a=c
1
1
1
drop function bug12480;
drop table t1;
...@@ -664,3 +664,36 @@ end| ...@@ -664,3 +664,36 @@ end|
update t1 set data = 1; update t1 set data = 1;
update t1 set data = 2; update t1 set data = 2;
drop table t1; drop table t1;
create table t1 (c1 int, c2 datetime);
create trigger tr1 before insert on t1 for each row
begin
set new.c2= '2004-04-01';
select 'hello';
end|
ERROR 0A000: Not allowed to return a result set from a trigger
insert into t1 (c1) values (1),(2),(3);
select * from t1;
c1 c2
1 NULL
2 NULL
3 NULL
drop procedure if exists bug11587;
create procedure bug11587(x char(16))
begin
select "hello";
select "hello again";
end|
create trigger tr1 before insert on t1 for each row
begin
call bug11587();
set new.c2= '2004-04-02';
end|
insert into t1 (c1) values (4),(5),(6);
ERROR 0A000: PROCEDURE test.bug11587 can't return a result set in the given context
select * from t1;
c1 c2
1 NULL
2 NULL
3 NULL
drop procedure bug11587;
drop table t1;
#
# Test of triggers with replication
#
source include/master-slave.inc;
#
# #12482: Triggers has side effects with auto_increment values
#
create table t1 (a int auto_increment, primary key (a), b int, rand_value double not null);
create table t2 (a int auto_increment, primary key (a), b int);
create table t3 (a int auto_increment, primary key (a), name varchar(64) not null, old_a int, old_b int, rand_value double not null);
delimiter |;
create trigger t1 before insert on t1 for each row
begin
insert into t3 values (NULL, "t1", new.a, new.b, rand());
end|
create trigger t2 after insert on t2 for each row
begin
insert into t3 values (NULL, "t2", new.a, new.b, rand());
end|
delimiter ;|
insert into t3 values(100,"log",0,0,0);
# Ensure we always have same random numbers
SET @@RAND_SEED1=658490765, @@RAND_SEED2=635893186;
# Emulate that we have rows 2-9 deleted on the slave
insert into t1 values(1,1,rand()),(NULL,2,rand());
insert into t2 (b) values(last_insert_id());
insert into t2 values(3,0),(NULL,0);
insert into t2 values(NULL,0),(500,0);
select a,b, truncate(rand_value,4) from t1;
select * from t2;
select a,name, old_a, old_b, truncate(rand_value,4) from t3;
save_master_pos;
connection slave;
sync_with_master;
--disable_query_log
select "--- On slave --" as "";
--enable_query_log
select a,b, truncate(rand_value,4) from t1;
select * from t2;
select a,name, old_a, old_b, truncate(rand_value,4) from t3;
connection master;
drop table t1,t2,t3;
#
# #12480: NOW() is not constant in a trigger
# #12481: Using NOW() in a stored function breaks statement based replication
#
# Start by getting a lock on 'bug12480' to be able to use get_lock() as sleep()
connect (con2,localhost,root,,);
connection con2;
select get_lock("bug12480",2);
connection default;
create table t1 (a datetime,b datetime, c datetime);
--ignore_warnings
drop function if exists bug12480;
--enable_warnings
delimiter |;
create function bug12480() returns datetime
begin
set @a=get_lock("bug12480",2);
return now();
end|
create trigger t1_first before insert on t1
for each row begin
set @a=get_lock("bug12480",2);
set new.b= now();
set new.c= bug12480();
end
|
delimiter ;|
insert into t1 set a = now();
select a=b && a=c from t1;
let $time=`select a from t1`;
connection slave;
sync_with_master;
--disable_query_log
select "--- On slave --" as "";
--enable_query_log
select a=b && a=c from t1;
--disable_query_log
eval select a='$time' as 'test' from t1;
--enable_query_log
connection master;
disconnect con2;
truncate table t1;
drop trigger t1_first;
insert into t1 values ("2003-03-03","2003-03-03","2003-03-03"),(bug12480(),bug12480(),bug12480()),(now(),now(),now());
select a=b && a=c from t1;
drop function bug12480;
drop table t1;
#
# End of test
#
save_master_pos;
connection slave;
sync_with_master;
...@@ -811,19 +811,19 @@ end| ...@@ -811,19 +811,19 @@ end|
# #
# Some things are caught when parsing # Some things are caught when parsing
--error ER_SP_NO_RETSET_IN_FUNC --error ER_SP_NO_RETSET
create function bug8408() returns int create function bug8408() returns int
begin begin
select * from t1; select * from t1;
return 0; return 0;
end| end|
--error ER_SP_NO_RETSET_IN_FUNC --error ER_SP_NO_RETSET
create function bug8408() returns int create function bug8408() returns int
begin begin
show warnings; show warnings;
return 0; return 0;
end| end|
--error ER_SP_NO_RETSET_IN_FUNC --error ER_SP_NO_RETSET
create function bug8408(a int) returns int create function bug8408(a int) returns int
begin begin
declare b int; declare b int;
......
...@@ -665,6 +665,7 @@ drop table t1; ...@@ -665,6 +665,7 @@ drop table t1;
# Test for bug #11973 "SELECT .. INTO var_name; in trigger cause # Test for bug #11973 "SELECT .. INTO var_name; in trigger cause
# crash on update" # crash on update"
create table t1 (id int, data int, username varchar(16)); create table t1 (id int, data int, username varchar(16));
insert into t1 (id, data) values (1, 0); insert into t1 (id, data) values (1, 0);
delimiter |; delimiter |;
...@@ -684,4 +685,47 @@ connection addconroot; ...@@ -684,4 +685,47 @@ connection addconroot;
update t1 set data = 2; update t1 set data = 2;
connection default; connection default;
disconnect addconroot;
drop table t1;
#
# #11587 Trigger causes lost connection error
#
create table t1 (c1 int, c2 datetime);
delimiter |;
--error ER_SP_NO_RETSET
create trigger tr1 before insert on t1 for each row
begin
set new.c2= '2004-04-01';
select 'hello';
end|
delimiter ;|
insert into t1 (c1) values (1),(2),(3);
select * from t1;
--disable_warnings
drop procedure if exists bug11587;
--enable_warnings
delimiter |;
create procedure bug11587(x char(16))
begin
select "hello";
select "hello again";
end|
create trigger tr1 before insert on t1 for each row
begin
call bug11587();
set new.c2= '2004-04-02';
end|
delimiter ;|
--error 726
insert into t1 (c1) values (4),(5),(6);
select * from t1;
drop procedure bug11587;
drop table t1; drop table t1;
...@@ -1873,6 +1873,9 @@ bool Item_func_rand::fix_fields(THD *thd,Item **ref) ...@@ -1873,6 +1873,9 @@ bool Item_func_rand::fix_fields(THD *thd,Item **ref)
Allocate rand structure once: we must use thd->current_arena Allocate rand structure once: we must use thd->current_arena
to create rand in proper mem_root if it's a prepared statement or to create rand in proper mem_root if it's a prepared statement or
stored procedure. stored procedure.
No need to send a Rand log event if seed was given eg: RAND(seed),
as it will be replicated in the query as such.
*/ */
if (!rand && !(rand= (struct rand_struct*) if (!rand && !(rand= (struct rand_struct*)
thd->current_arena->alloc(sizeof(*rand)))) thd->current_arena->alloc(sizeof(*rand))))
...@@ -1895,16 +1898,16 @@ bool Item_func_rand::fix_fields(THD *thd,Item **ref) ...@@ -1895,16 +1898,16 @@ bool Item_func_rand::fix_fields(THD *thd,Item **ref)
else else
{ {
/* /*
No need to send a Rand log event if seed was given eg: RAND(seed),
as it will be replicated in the query as such.
Save the seed only the first time RAND() is used in the query Save the seed only the first time RAND() is used in the query
Once events are forwarded rather than recreated, Once events are forwarded rather than recreated,
the following can be skipped if inside the slave thread the following can be skipped if inside the slave thread
*/ */
thd->rand_used=1; if (!thd->rand_used)
thd->rand_saved_seed1=thd->rand.seed1; {
thd->rand_saved_seed2=thd->rand.seed2; thd->rand_used= 1;
thd->rand_saved_seed1= thd->rand.seed1;
thd->rand_saved_seed2= thd->rand.seed2;
}
rand= &thd->rand; rand= &thd->rand;
} }
return FALSE; return FALSE;
...@@ -4665,10 +4668,9 @@ Item_func_sp::execute(Item **itp) ...@@ -4665,10 +4668,9 @@ Item_func_sp::execute(Item **itp)
{ {
DBUG_ENTER("Item_func_sp::execute"); DBUG_ENTER("Item_func_sp::execute");
THD *thd= current_thd; THD *thd= current_thd;
ulong old_client_capabilites;
int res= -1; int res= -1;
bool save_in_sub_stmt= thd->in_sub_stmt; Sub_statement_state statement_state;
my_bool save_no_send_ok;
#ifndef NO_EMBEDDED_ACCESS_CHECKS #ifndef NO_EMBEDDED_ACCESS_CHECKS
st_sp_security_context save_ctx; st_sp_security_context save_ctx;
#endif #endif
...@@ -4679,38 +4681,21 @@ Item_func_sp::execute(Item **itp) ...@@ -4679,38 +4681,21 @@ Item_func_sp::execute(Item **itp)
goto error; goto error;
} }
old_client_capabilites= thd->client_capabilities;
thd->client_capabilities &= ~CLIENT_MULTI_RESULTS;
#ifndef EMBEDDED_LIBRARY
save_no_send_ok= thd->net.no_send_ok;
thd->net.no_send_ok= TRUE;
#endif
#ifndef NO_EMBEDDED_ACCESS_CHECKS #ifndef NO_EMBEDDED_ACCESS_CHECKS
if (check_routine_access(thd, EXECUTE_ACL, if (check_routine_access(thd, EXECUTE_ACL,
m_sp->m_db.str, m_sp->m_name.str, 0, 0)) m_sp->m_db.str, m_sp->m_name.str, 0, 0))
goto error_check; goto error;
sp_change_security_context(thd, m_sp, &save_ctx); sp_change_security_context(thd, m_sp, &save_ctx);
if (save_ctx.changed && if (save_ctx.changed &&
check_routine_access(thd, EXECUTE_ACL, check_routine_access(thd, EXECUTE_ACL,
m_sp->m_db.str, m_sp->m_name.str, 0, 0)) m_sp->m_db.str, m_sp->m_name.str, 0, 0))
goto error_check_ctx; goto error_check_ctx;
#endif #endif
/*
Like for SPs, we don't binlog the substatements. If the statement which
called this function is an update statement, it will be binlogged; but if
it's not (e.g. SELECT myfunc()) it won't be binlogged (documented known
problem).
*/
tmp_disable_binlog(thd); /* don't binlog the substatements */
thd->in_sub_stmt= TRUE;
thd->reset_sub_statement_state(&statement_state, SUB_STMT_FUNCTION);
res= m_sp->execute_function(thd, args, arg_count, itp); res= m_sp->execute_function(thd, args, arg_count, itp);
thd->restore_sub_statement_state(&statement_state);
thd->in_sub_stmt= save_in_sub_stmt;
reenable_binlog(thd);
if (res && mysql_bin_log.is_open() && if (res && mysql_bin_log.is_open() &&
(m_sp->m_chistics->daccess == SP_CONTAINS_SQL || (m_sp->m_chistics->daccess == SP_CONTAINS_SQL ||
m_sp->m_chistics->daccess == SP_MODIFIES_SQL_DATA)) m_sp->m_chistics->daccess == SP_MODIFIES_SQL_DATA))
...@@ -4723,15 +4708,6 @@ Item_func_sp::execute(Item **itp) ...@@ -4723,15 +4708,6 @@ Item_func_sp::execute(Item **itp)
sp_restore_security_context(thd, m_sp, &save_ctx); sp_restore_security_context(thd, m_sp, &save_ctx);
#endif #endif
thd->client_capabilities|= old_client_capabilites & CLIENT_MULTI_RESULTS;
error_check:
#ifndef EMBEDDED_LIBRARY
thd->net.no_send_ok= save_no_send_ok;
#endif
thd->client_capabilities|= old_client_capabilites & CLIENT_MULTI_RESULTS;
error: error:
DBUG_RETURN(res); DBUG_RETURN(res);
} }
......
...@@ -5342,8 +5342,8 @@ ER_SP_DUP_HANDLER 42000 ...@@ -5342,8 +5342,8 @@ ER_SP_DUP_HANDLER 42000
eng "Duplicate handler declared in the same block" eng "Duplicate handler declared in the same block"
ER_SP_NOT_VAR_ARG 42000 ER_SP_NOT_VAR_ARG 42000
eng "OUT or INOUT argument %d for routine %s is not a variable" eng "OUT or INOUT argument %d for routine %s is not a variable"
ER_SP_NO_RETSET_IN_FUNC 0A000 ER_SP_NO_RETSET 0A000
eng "Not allowed to return a result set from a function" eng "Not allowed to return a result set from a %s"
ER_CANT_CREATE_GEOMETRY_OBJECT 22003 ER_CANT_CREATE_GEOMETRY_OBJECT 22003
eng "Cannot get geometry object from data you send to the GEOMETRY field" eng "Cannot get geometry object from data you send to the GEOMETRY field"
ER_FAILED_ROUTINE_BREAK_BINLOG ER_FAILED_ROUTINE_BREAK_BINLOG
......
...@@ -659,6 +659,8 @@ sp_head::execute(THD *thd) ...@@ -659,6 +659,8 @@ sp_head::execute(THD *thd)
if (i == NULL) if (i == NULL)
break; break;
DBUG_PRINT("execute", ("Instruction %u", ip)); DBUG_PRINT("execute", ("Instruction %u", ip));
/* Don't change NOW() in FUNCTION or TRIGGER */
if (!thd->in_sub_stmt)
thd->set_time(); // Make current_time() et al work thd->set_time(); // Make current_time() et al work
/* /*
We have to set thd->current_arena before executing the instruction We have to set thd->current_arena before executing the instruction
...@@ -690,8 +692,7 @@ sp_head::execute(THD *thd) ...@@ -690,8 +692,7 @@ sp_head::execute(THD *thd)
{ {
uint hf; uint hf;
switch (ctx->found_handler(&hip, &hf)) switch (ctx->found_handler(&hip, &hf)) {
{
case SP_HANDLER_NONE: case SP_HANDLER_NONE:
break; break;
case SP_HANDLER_CONTINUE: case SP_HANDLER_CONTINUE:
......
...@@ -174,7 +174,7 @@ THD::THD() ...@@ -174,7 +174,7 @@ THD::THD()
:Statement(CONVENTIONAL_EXECUTION, 0, ALLOC_ROOT_MIN_BLOCK_SIZE, 0), :Statement(CONVENTIONAL_EXECUTION, 0, ALLOC_ROOT_MIN_BLOCK_SIZE, 0),
Open_tables_state(refresh_version), Open_tables_state(refresh_version),
lock_id(&main_lock_id), lock_id(&main_lock_id),
user_time(0), in_sub_stmt(FALSE), global_read_lock(0), is_fatal_error(0), user_time(0), in_sub_stmt(0), global_read_lock(0), is_fatal_error(0),
rand_used(0), time_zone_used(0), rand_used(0), time_zone_used(0),
last_insert_id_used(0), insert_id_used(0), clear_next_insert_id(0), last_insert_id_used(0), insert_id_used(0), clear_next_insert_id(0),
in_lock_tables(0), bootstrap(0), derived_tables_processing(FALSE), in_lock_tables(0), bootstrap(0), derived_tables_processing(FALSE),
...@@ -1836,3 +1836,85 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup) ...@@ -1836,3 +1836,85 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup)
set_open_tables_state(backup); set_open_tables_state(backup);
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
/****************************************************************************
Handling of statement states in functions and triggers.
This is used to ensure that the function/trigger gets a clean state
to work with and does not cause any side effects of the calling statement.
It also allows most stored functions and triggers to replicate even
if they are used items that would normally be stored in the binary
replication (like last_insert_id() etc...)
The following things is done
- Disable binary logging for the duration of the statement
- Disable multi-result-sets for the duration of the statement
- Value of last_insert_id() is reset and restored
- Value set by 'SET INSERT_ID=#' is reset and restored
- Value for found_rows() is reset and restored
- examined_row_count is added to the total
- cuted_fields is added to the total
NOTES:
Seed for random() is saved for the first! usage of RAND()
We reset examined_row_count and cuted_fields and add these to the
result to ensure that if we have a bug that would reset these within
a function, we are not loosing any rows from the main statement.
****************************************************************************/
void THD::reset_sub_statement_state(Sub_statement_state *backup,
uint new_state)
{
backup->options= options;
backup->in_sub_stmt= in_sub_stmt;
backup->no_send_ok= net.no_send_ok;
backup->enable_slow_log= enable_slow_log;
backup->last_insert_id= last_insert_id;
backup->next_insert_id= next_insert_id;
backup->insert_id_used= insert_id_used;
backup->limit_found_rows= limit_found_rows;
backup->examined_row_count= examined_row_count;
backup->sent_row_count= sent_row_count;
backup->cuted_fields= cuted_fields;
backup->client_capabilities= client_capabilities;
options&= ~OPTION_BIN_LOG;
/* Disable result sets */
client_capabilities &= ~CLIENT_MULTI_RESULTS;
in_sub_stmt|= new_state;
last_insert_id= 0;
next_insert_id= 0;
insert_id_used= 0;
examined_row_count= 0;
sent_row_count= 0;
cuted_fields= 0;
#ifndef EMBEDDED_LIBRARY
/* Surpress OK packets in case if we will execute statements */
net.no_send_ok= TRUE;
#endif
}
void THD::restore_sub_statement_state(Sub_statement_state *backup)
{
options= backup->options;
in_sub_stmt= backup->in_sub_stmt;
net.no_send_ok= backup->no_send_ok;
enable_slow_log= backup->enable_slow_log;
last_insert_id= backup->last_insert_id;
next_insert_id= backup->next_insert_id;
insert_id_used= backup->insert_id_used;
limit_found_rows= backup->limit_found_rows;
sent_row_count= backup->sent_row_count;
client_capabilities= backup->client_capabilities;
/*
The following is added to the old values as we are interested in the
total complexity of the query
*/
examined_row_count+= backup->examined_row_count;
cuted_fields+= backup->cuted_fields;
}
...@@ -1031,6 +1031,27 @@ class Open_tables_state ...@@ -1031,6 +1031,27 @@ class Open_tables_state
}; };
/* class to save context when executing a function or trigger */
/* Defines used for Sub_statement_state::in_sub_stmt */
#define SUB_STMT_TRIGGER 1
#define SUB_STMT_FUNCTION 2
class Sub_statement_state
{
public:
ulonglong options;
ulonglong last_insert_id, next_insert_id;
ulonglong limit_found_rows;
ha_rows cuted_fields, sent_row_count, examined_row_count;
ulong client_capabilities;
uint in_sub_stmt;
bool enable_slow_log, insert_id_used;
my_bool no_send_ok;
};
/* /*
For each client connection we create a separate thread with THD serving as For each client connection we create a separate thread with THD serving as
a thread/connection descriptor a thread/connection descriptor
...@@ -1137,10 +1158,9 @@ class THD :public Statement, ...@@ -1137,10 +1158,9 @@ class THD :public Statement,
time_t connect_time,thr_create_time; // track down slow pthread_create time_t connect_time,thr_create_time; // track down slow pthread_create
thr_lock_type update_lock_default; thr_lock_type update_lock_default;
delayed_insert *di; delayed_insert *di;
my_bool tablespace_op; /* This is TRUE in DISCARD/IMPORT TABLESPACE */
/* TRUE if we are inside of trigger or stored function. */ /* <> 0 if we are inside of trigger or stored function. */
bool in_sub_stmt; uint in_sub_stmt;
/* container for handler's private per-connection data */ /* container for handler's private per-connection data */
void *ha_data[MAX_HA]; void *ha_data[MAX_HA];
...@@ -1223,6 +1243,8 @@ class THD :public Statement, ...@@ -1223,6 +1243,8 @@ class THD :public Statement,
*/ */
ulonglong current_insert_id; ulonglong current_insert_id;
ulonglong limit_found_rows; ulonglong limit_found_rows;
ulonglong options; /* Bitmap of states */
longlong row_count_func; /* For the ROW_COUNT() function */
ha_rows cuted_fields, ha_rows cuted_fields,
sent_row_count, examined_row_count; sent_row_count, examined_row_count;
table_map used_tables; table_map used_tables;
...@@ -1246,7 +1268,6 @@ class THD :public Statement, ...@@ -1246,7 +1268,6 @@ class THD :public Statement,
update auto-updatable fields (like auto_increment and timestamp). update auto-updatable fields (like auto_increment and timestamp).
*/ */
query_id_t query_id, warn_id; query_id_t query_id, warn_id;
ulonglong options;
ulong thread_id, col_access; ulong thread_id, col_access;
/* Statement id is thread-wide. This counter is used to generate ids */ /* Statement id is thread-wide. This counter is used to generate ids */
...@@ -1287,7 +1308,8 @@ class THD :public Statement, ...@@ -1287,7 +1308,8 @@ class THD :public Statement,
bool no_warnings_for_error; /* no warnings on call to my_error() */ bool no_warnings_for_error; /* no warnings on call to my_error() */
/* set during loop of derived table processing */ /* set during loop of derived table processing */
bool derived_tables_processing; bool derived_tables_processing;
longlong row_count_func; /* For the ROW_COUNT() function */ my_bool tablespace_op; /* This is TRUE in DISCARD/IMPORT TABLESPACE */
sp_rcontext *spcont; // SP runtime context sp_rcontext *spcont; // SP runtime context
sp_cache *sp_proc_cache; sp_cache *sp_proc_cache;
sp_cache *sp_func_cache; sp_cache *sp_func_cache;
...@@ -1495,6 +1517,8 @@ class THD :public Statement, ...@@ -1495,6 +1517,8 @@ class THD :public Statement,
{ return current_arena->is_stmt_prepare() || lex->view_prepare_mode; } { return current_arena->is_stmt_prepare() || lex->view_prepare_mode; }
void reset_n_backup_open_tables_state(Open_tables_state *backup); void reset_n_backup_open_tables_state(Open_tables_state *backup);
void restore_backup_open_tables_state(Open_tables_state *backup); void restore_backup_open_tables_state(Open_tables_state *backup);
void reset_sub_statement_state(Sub_statement_state *backup, uint new_state);
void restore_sub_statement_state(Sub_statement_state *backup);
}; };
......
...@@ -1101,11 +1101,11 @@ pthread_handler_decl(handle_one_connection,arg) ...@@ -1101,11 +1101,11 @@ pthread_handler_decl(handle_one_connection,arg)
execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect); execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect);
if (thd->query_error) if (thd->query_error)
thd->killed= THD::KILL_CONNECTION; thd->killed= THD::KILL_CONNECTION;
}
thd->proc_info=0; thd->proc_info=0;
thd->set_time(); thd->set_time();
thd->init_for_queries(); thd->init_for_queries();
}
while (!net->error && net->vio != 0 && while (!net->error && net->vio != 0 &&
!(thd->killed == THD::KILL_CONNECTION)) !(thd->killed == THD::KILL_CONNECTION))
{ {
...@@ -1464,6 +1464,7 @@ bool do_command(THD *thd) ...@@ -1464,6 +1464,7 @@ bool do_command(THD *thd)
/* /*
Perform one connection-level (COM_XXXX) command. Perform one connection-level (COM_XXXX) command.
SYNOPSIS SYNOPSIS
dispatch_command() dispatch_command()
thd connection handle thd connection handle
...@@ -2044,7 +2045,17 @@ bool dispatch_command(enum enum_server_command command, THD *thd, ...@@ -2044,7 +2045,17 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
void log_slow_statement(THD *thd) void log_slow_statement(THD *thd)
{ {
time_t start_of_query=thd->start_time; time_t start_of_query;
/*
The following should never be true with our current code base,
but better to keep this here so we don't accidently try to log a
statement in a trigger or stored function
*/
if (unlikely(thd->in_sub_stmt))
return; // Don't set time for sub stmt
start_of_query= thd->start_time;
thd->end_time(); // Set start time thd->end_time(); // Set start time
/* /*
...@@ -5157,10 +5168,8 @@ void mysql_reset_thd_for_next_command(THD *thd) ...@@ -5157,10 +5168,8 @@ void mysql_reset_thd_for_next_command(THD *thd)
DBUG_ENTER("mysql_reset_thd_for_next_command"); DBUG_ENTER("mysql_reset_thd_for_next_command");
thd->free_list= 0; thd->free_list= 0;
thd->select_number= 1; thd->select_number= 1;
thd->total_warn_count=0; // Warnings for this query
thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0; thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0;
thd->sent_row_count= thd->examined_row_count= 0; thd->is_fatal_error= thd->time_zone_used= 0;
thd->is_fatal_error= thd->rand_used= thd->time_zone_used= 0;
thd->server_status&= ~ (SERVER_MORE_RESULTS_EXISTS | thd->server_status&= ~ (SERVER_MORE_RESULTS_EXISTS |
SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_INDEX_USED |
SERVER_QUERY_NO_GOOD_INDEX_USED); SERVER_QUERY_NO_GOOD_INDEX_USED);
...@@ -5168,6 +5177,12 @@ void mysql_reset_thd_for_next_command(THD *thd) ...@@ -5168,6 +5177,12 @@ void mysql_reset_thd_for_next_command(THD *thd)
if (opt_bin_log) if (opt_bin_log)
reset_dynamic(&thd->user_var_events); reset_dynamic(&thd->user_var_events);
thd->clear_error(); thd->clear_error();
if (!thd->in_sub_stmt)
{
thd->total_warn_count=0; // Warnings for this query
thd->rand_used= 0;
thd->sent_row_count= thd->examined_row_count= 0;
}
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
......
...@@ -869,3 +869,39 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name) ...@@ -869,3 +869,39 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name)
free_root(&table.mem_root, MYF(0)); free_root(&table.mem_root, MYF(0));
DBUG_RETURN(result); DBUG_RETURN(result);
} }
bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event,
trg_action_time_type time_type,
bool old_row_is_record1)
{
int res= 0;
if (bodies[event][time_type])
{
Sub_statement_state statement_state;
if (old_row_is_record1)
{
old_field= record1_field;
new_field= table->field;
}
else
{
new_field= record1_field;
old_field= table->field;
}
/*
FIXME: We should juggle with security context here (because trigger
should be invoked with creator rights).
*/
thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
res= bodies[event][time_type]->execute_function(thd, 0, 0, 0);
thd->restore_sub_statement_state(&statement_state);
}
return res;
}
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
QQ: Will it be merged into TABLE in future ? QQ: Will it be merged into TABLE in future ?
*/ */
class Table_triggers_list: public Sql_alloc class Table_triggers_list: public Sql_alloc
{ {
/* Triggers as SPs grouped by event, action_time */ /* Triggers as SPs grouped by event, action_time */
...@@ -76,55 +77,7 @@ class Table_triggers_list: public Sql_alloc ...@@ -76,55 +77,7 @@ class Table_triggers_list: public Sql_alloc
bool drop_trigger(THD *thd, TABLE_LIST *table); bool drop_trigger(THD *thd, TABLE_LIST *table);
bool process_triggers(THD *thd, trg_event_type event, bool process_triggers(THD *thd, trg_event_type event,
trg_action_time_type time_type, trg_action_time_type time_type,
bool old_row_is_record1) bool old_row_is_record1);
{
int res= 0;
if (bodies[event][time_type])
{
bool save_in_sub_stmt= thd->in_sub_stmt;
#ifndef EMBEDDED_LIBRARY
/* Surpress OK packets in case if we will execute statements */
my_bool nsok= thd->net.no_send_ok;
thd->net.no_send_ok= TRUE;
#endif
if (old_row_is_record1)
{
old_field= record1_field;
new_field= table->field;
}
else
{
new_field= record1_field;
old_field= table->field;
}
/*
FIXME: We should juggle with security context here (because trigger
should be invoked with creator rights).
*/
/*
We disable binlogging, as in SP/functions, even though currently
triggers can't do updates. When triggers can do updates, someone
should add such a trigger to rpl_sp.test to verify that the update
does NOT go into binlog.
*/
tmp_disable_binlog(thd);
thd->in_sub_stmt= TRUE;
res= bodies[event][time_type]->execute_function(thd, 0, 0, 0);
thd->in_sub_stmt= save_in_sub_stmt;
reenable_binlog(thd);
#ifndef EMBEDDED_LIBRARY
thd->net.no_send_ok= nsok;
#endif
}
return res;
}
bool get_trigger_info(THD *thd, trg_event_type event, bool get_trigger_info(THD *thd, trg_event_type event,
trg_action_time_type time_type, trg_action_time_type time_type,
LEX_STRING *trigger_name, LEX_STRING *trigger_stmt, LEX_STRING *trigger_name, LEX_STRING *trigger_stmt,
......
...@@ -1311,6 +1311,12 @@ create: ...@@ -1311,6 +1311,12 @@ create:
YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES; YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
sp->restore_thd_mem_root(YYTHD); sp->restore_thd_mem_root(YYTHD);
if (sp->m_multi_results)
{
my_error(ER_SP_NO_RETSET, MYF(0), "trigger");
YYABORT;
}
/* /*
We have to do it after parsing trigger body, because some of We have to do it after parsing trigger body, because some of
sp_proc_stmt alternatives are not saving/restoring LEX, so sp_proc_stmt alternatives are not saving/restoring LEX, so
...@@ -1463,8 +1469,7 @@ create_function_tail: ...@@ -1463,8 +1469,7 @@ create_function_tail:
if (sp->m_multi_results) if (sp->m_multi_results)
{ {
my_message(ER_SP_NO_RETSET_IN_FUNC, ER(ER_SP_NO_RETSET_IN_FUNC), my_error(ER_SP_NO_RETSET, MYF(0), "function");
MYF(0));
YYABORT; YYABORT;
} }
if (sp->check_backpatch(YYTHD)) if (sp->check_backpatch(YYTHD))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment