mirror of
https://github.com/MariaDB/server.git
synced 2025-01-29 02:05:57 +01:00
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''
This commit is contained in:
parent
7cfb6540f7
commit
a914b5274f
15 changed files with 505 additions and 111 deletions
108
mysql-test/r/rpl_trigger.result
Normal file
108
mysql-test/r/rpl_trigger.result
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
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|
|
||||||
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;
|
||||||
|
|
117
mysql-test/t/rpl_trigger.test
Normal file
117
mysql-test/t/rpl_trigger.test
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
#
|
||||||
|
# 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|
|
||||||
#
|
#
|
||||||
|
|
||||||
# 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;
|
||||||
|
|
||||||
# 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;
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
{
|
{
|
||||||
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)
|
||||||
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 @@ error_check_ctx:
|
||||||
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
|
||||||
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,7 +659,9 @@ 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));
|
||||||
thd->set_time(); // Make current_time() et al work
|
/* Don't change NOW() in FUNCTION or TRIGGER */
|
||||||
|
if (!thd->in_sub_stmt)
|
||||||
|
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
|
||||||
to store in the instruction free_list all new items, created
|
to store in the instruction free_list all new items, created
|
||||||
|
@ -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()
|
||||||
: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)
|
||||||
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 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* 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 @@ public:
|
||||||
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 @@ public:
|
||||||
*/
|
*/
|
||||||
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 @@ public:
|
||||||
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 @@ public:
|
||||||
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 @@ public:
|
||||||
{ 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)
|
||||||
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->set_time();
|
||||||
|
thd->init_for_queries();
|
||||||
}
|
}
|
||||||
|
|
||||||
thd->proc_info=0;
|
|
||||||
thd->set_time();
|
|
||||||
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)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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,
|
||||||
|
|
||||||
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)
|
||||||
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)
|
||||||
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 @@ end:
|
||||||
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 @@
|
||||||
|
|
||||||
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 @@ public:
|
||||||
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:
|
||||||
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:
|
||||||
|
|
||||||
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))
|
||||||
|
|
|
@ -300,7 +300,7 @@ sub test_select
|
||||||
|
|
||||||
#
|
#
|
||||||
# Do big select count(distinct..) over the table
|
# Do big select count(distinct..) over the table
|
||||||
#
|
#
|
||||||
|
|
||||||
sub test_select_count
|
sub test_select_count
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Reference in a new issue