Better approach for prelocking of tables for stored routines execution

and some SP-related cleanups.

- We don't have separate stage for calculation of list of tables
  to be prelocked and doing implicit LOCK/UNLOCK any more.
  Instead we calculate this list at open_tables() and do implicit
  LOCK in lock_tables() (and UNLOCK in close_thread_tables()).
  Also now we support cases when same table (with same alias) is
  used several times in the same query in SP.

- Cleaned up execution of SP. Moved all common code which handles
  LEX and does preparations before statement execution or complex
  expression evaluation to auxilary sp_lex_keeper class. Now 
  all statements in SP (and corresponding instructions) that
  evaluate expression which can contain subquery have their
  own LEX.
This commit is contained in:
dlenev@brandersnatch.localdomain 2005-03-04 16:35:28 +03:00
parent 6611d3d2f8
commit 5a6c7027f0
29 changed files with 1804 additions and 877 deletions

View File

@ -42,7 +42,7 @@ check table t2;
Table Op Msg_type Msg_text
test.t2 check error Table 't2' was not locked with LOCK TABLES
insert into t1 select index1,nr from t1;
ERROR 42000: INSERT command denied to user 'root'@'localhost' for column 'index1' in table 't1'
ERROR HY000: Table 't1' was not locked with LOCK TABLES
unlock tables;
lock tables t1 write, t1 as t1_alias read;
insert into t1 select index1,nr from t1 as t1_alias;

View File

@ -1,5 +1,6 @@
DROP TABLE IF EXISTS t1, `"t"1`, t1aa,t2aa;
drop database if exists mysqldump_test_db;
drop view if exists v1;
CREATE TABLE t1(a int);
INSERT INTO t1 VALUES (1), (2);
<?xml version="1.0"?>

View File

@ -237,6 +237,13 @@ insert into t2 values ("a", 1, 1.1), ("b", 2, 1.2), ("c", 3, 1.3)|
drop procedure if exists sub1|
create procedure sub1(id char(16), x int)
insert into test.t1 values (id, x)|
drop procedure if exists sub2|
create procedure sub2(id char(16))
begin
declare x int;
set x = (select sum(t.i) from test.t2 t);
insert into test.t1 values (id, x);
end|
drop procedure if exists sub3|
create function sub3(i int) returns int
return i+1|
@ -244,16 +251,19 @@ call sub1("sub1a", (select 7))|
call sub1("sub1b", (select max(i) from t2))|
call sub1("sub1c", (select i,d from t2 limit 1))|
call sub1("sub1d", (select 1 from (select 1) a))|
call sub2("sub2");
select * from t1|
id data
sub1a 7
sub1b 3
sub1c 1
sub1d 1
sub2 6
select sub3((select max(i) from t2))|
sub3((select max(i) from t2))
4
drop procedure sub1|
drop procedure sub2|
drop function sub3|
delete from t2|
drop procedure if exists a0|
@ -269,6 +279,7 @@ sub1a 7
sub1b 3
sub1c 1
sub1d 1
sub2 6
a0 2
a0 1
a0 0
@ -1046,6 +1057,200 @@ select row_count()|
row_count()
-1
drop procedure rc|
drop function if exists f0|
drop function if exists f1|
drop function if exists f2|
drop function if exists f3|
drop function if exists f4|
drop function if exists f5|
drop function if exists f6|
drop function if exists f7|
drop function if exists f8|
drop view if exists v0|
drop view if exists v1|
drop view if exists v2|
delete from t1|
delete from t2|
insert into t1 values ("a", 1), ("b", 2) |
insert into t2 values ("a", 1, 1.0), ("b", 2, 2.0), ("c", 3, 3.0) |
create function f1() returns int
return (select sum(data) from t1)|
select f1()|
f1()
3
select id, f1() from t1|
id f1()
a 3
b 3
create function f2() returns int
return (select data from t1 where data <= (select sum(data) from t1) limit 1)|
select f2()|
f2()
1
select id, f2() from t1|
id f2()
a 1
b 1
create function f3() returns int
begin
declare n int;
declare m int;
set n:= (select min(data) from t1);
set m:= (select max(data) from t1);
return n < m;
end|
select f3()|
f3()
1
select id, f3() from t1|
id f3()
a 1
b 1
select f1(), f3()|
f1() f3()
3 1
select id, f1(), f3() from t1|
id f1() f3()
a 3 1
b 3 1
create function f4() returns double
return (select d from t1, t2 where t1.data = t2.i and t1.id= "b")|
select f4()|
f4()
2
select s, f4() from t2|
s f4()
a 2
b 2
c 2
create function f5(i int) returns int
begin
if i <= 0 then
return 0;
elseif i = 1 then
return (select count(*) from t1 where data = i);
else
return (select count(*) + f5( i - 1) from t1 where data = i);
end if;
end|
select f5(1)|
f5(1)
1
select f5(2)|
ERROR HY000: Table 't1' was not locked with LOCK TABLES
create function f6() returns int
begin
declare n int;
set n:= f1();
return (select count(*) from t1 where data <= f7() and data <= n);
end|
create function f7() returns int
return (select sum(data) from t1 where data <= f1())|
select f6()|
f6()
2
select id, f6() from t1|
id f6()
a 2
b 2
create view v1 (a) as select f1()|
select * from v1|
a
3
select id, a from t1, v1|
id a
a 3
b 3
select * from v1, v1 as v|
a a
3 3
create view v2 (a) as select a*10 from v1|
select * from v2|
a
30
select id, a from t1, v2|
id a
a 30
b 30
select * from v1, v2|
a a
3 30
create function f8 () returns int
return (select count(*) from v2)|
select *, f8() from v1|
a f8()
3 1
drop function f1|
select * from v1|
ERROR HY000: View 'test.v1' references invalid table(s) or column(s) or function(s)
create function f1() returns int
return (select sum(data) from t1) + (select sum(data) from v1)|
drop function f1|
create function f1() returns int
return (select sum(data) from t1)|
create function f0() returns int
return (select * from (select 100) as r)|
select f0()|
f0()
100
select *, f0() from (select 1) as t|
1 f0()
1 100
create view v0 as select f0()|
select * from v0|
f0()
100
select *, f0() from v0|
f0() f0()
100 100
lock tables t1 read, t1 as t11 read, mysql.proc read|
select f3()|
f3()
1
select id, f3() from t1 as t11|
id f3()
a 1
b 1
select f0()|
f0()
100
select * from v0|
f0()
100
select *, f0() from v0, (select 123) as d1|
f0() 123 f0()
100 123 100
select id, f3() from t1|
ERROR HY000: Table 't1' was not locked with LOCK TABLES
select f4()|
ERROR HY000: Table 't2' was not locked with LOCK TABLES
unlock tables|
lock tables v2 read, mysql.proc read|
select * from v2|
a
30
select * from v1|
a
3
select * from v1, v2|
ERROR HY000: Table 't1' was not locked with LOCK TABLES
select f4()|
ERROR HY000: Table 't2' was not locked with LOCK TABLES
unlock tables|
drop function f0|
drop function f1|
drop function f2|
drop function f3|
drop function f4|
drop function f5|
drop function f6|
drop function f7|
drop function f8|
drop view v0|
drop view v1|
drop view v2|
delete from t1 |
delete from t2 |
drop procedure if exists bug822|
create procedure bug822(a_id char(16), a_data int)
begin
@ -1178,56 +1383,6 @@ select @x2|
@x2
2
drop procedure bug2260|
drop procedure if exists bug2267_1|
create procedure bug2267_1()
begin
show procedure status;
end|
drop procedure if exists bug2267_2|
create procedure bug2267_2()
begin
show function status;
end|
drop procedure if exists bug2267_3|
create procedure bug2267_3()
begin
show create procedure bug2267_1;
end|
drop procedure if exists bug2267_4|
create procedure bug2267_4()
begin
show create function fac;
end|
call bug2267_1()|
Db Name Type Definer Modified Created Security_type Comment
test bug2267_1 PROCEDURE root@localhost 0000-00-00 00:00:00 0000-00-00 00:00:00 DEFINER
test bug2267_2 PROCEDURE root@localhost 0000-00-00 00:00:00 0000-00-00 00:00:00 DEFINER
test bug2267_3 PROCEDURE root@localhost 0000-00-00 00:00:00 0000-00-00 00:00:00 DEFINER
test bug2267_4 PROCEDURE root@localhost 0000-00-00 00:00:00 0000-00-00 00:00:00 DEFINER
call bug2267_2()|
Db Name Type Definer Modified Created Security_type Comment
test fac FUNCTION root@localhost 0000-00-00 00:00:00 0000-00-00 00:00:00 DEFINER
call bug2267_3()|
Procedure sql_mode Create Procedure
bug2267_1 CREATE PROCEDURE `test`.`bug2267_1`()
begin
show procedure status;
end
call bug2267_4()|
Function sql_mode Create Function
fac CREATE FUNCTION `test`.`fac`(n int unsigned) RETURNS bigint unsigned
begin
declare f bigint unsigned default 1;
while n > 1 do
set f = f * n;
set n = n - 1;
end while;
return f;
end
drop procedure bug2267_1|
drop procedure bug2267_2|
drop procedure bug2267_3|
drop procedure bug2267_4|
drop procedure if exists bug2227|
create procedure bug2227(x int)
begin
@ -1332,7 +1487,7 @@ declare t2 int;
declare t3 int;
declare rc int default 0;
declare continue handler for 1065 set rc = 1;
drop table if exists temp_t1;
drop temporary table if exists temp_t1;
create temporary table temp_t1 (
f1 int auto_increment, f2 varchar(20), primary key (f1)
);
@ -1352,6 +1507,7 @@ f1 rc t3
2 0 NULL
2 0 NULL
drop procedure bug1863|
drop temporary table temp_t1;
drop table t3, t4|
drop table if exists t3, t4|
create table t3 (
@ -2366,7 +2522,7 @@ delete from t1|
insert into t1 values ("answer", 42)|
select id, bug5240() from t1|
id bug5240()
42 42
answer 42
drop function bug5240|
drop function if exists bug5278|
create function bug5278 () returns char

View File

@ -53,7 +53,7 @@ check table t1;
# Check error message
lock tables t1 write;
check table t2;
--error 1143
--error 1100
insert into t1 select index1,nr from t1;
unlock tables;
lock tables t1 write, t1 as t1_alias read;

View File

@ -1,6 +1,7 @@
--disable_warnings
DROP TABLE IF EXISTS t1, `"t"1`, t1aa,t2aa;
drop database if exists mysqldump_test_db;
drop view if exists v1;
--enable_warnings
# XML output

View File

@ -339,16 +339,15 @@ drop procedure if exists sub1|
create procedure sub1(id char(16), x int)
insert into test.t1 values (id, x)|
# QQ This doesn't work yet
#--disable_warnings
#drop procedure if exists sub2|
#--enable_warnings
#create procedure sub2(id char(16))
#begin
# declare x int;
# set x = (select sum(t.x) from test.t2 t);
# insert into test.t1 values (id, x);
#end|
--disable_warnings
drop procedure if exists sub2|
--enable_warnings
create procedure sub2(id char(16))
begin
declare x int;
set x = (select sum(t.i) from test.t2 t);
insert into test.t1 values (id, x);
end|
--disable_warnings
drop procedure if exists sub3|
@ -360,11 +359,11 @@ call sub1("sub1a", (select 7))|
call sub1("sub1b", (select max(i) from t2))|
call sub1("sub1c", (select i,d from t2 limit 1))|
call sub1("sub1d", (select 1 from (select 1) a))|
#call sub2("sub2");
call sub2("sub2");
select * from t1|
select sub3((select max(i) from t2))|
drop procedure sub1|
#drop procedure sub2|
drop procedure sub2|
drop function sub3|
delete from t2|
@ -1278,6 +1277,202 @@ select row_count()|
drop procedure rc|
#
# Let us test how well new locking scheme works.
#
# Let us prepare playground
--disable_warnings
drop function if exists f0|
drop function if exists f1|
drop function if exists f2|
drop function if exists f3|
drop function if exists f4|
drop function if exists f5|
drop function if exists f6|
drop function if exists f7|
drop function if exists f8|
drop view if exists v0|
drop view if exists v1|
drop view if exists v2|
--enable_warnings
delete from t1|
delete from t2|
insert into t1 values ("a", 1), ("b", 2) |
insert into t2 values ("a", 1, 1.0), ("b", 2, 2.0), ("c", 3, 3.0) |
# Test the simplest function using tables
create function f1() returns int
return (select sum(data) from t1)|
select f1()|
# This should work too (and give 2 rows as result)
select id, f1() from t1|
# Function which uses two instances of table simultaneously
create function f2() returns int
return (select data from t1 where data <= (select sum(data) from t1) limit 1)|
select f2()|
select id, f2() from t1|
# Function which uses the same table twice in different queries
create function f3() returns int
begin
declare n int;
declare m int;
set n:= (select min(data) from t1);
set m:= (select max(data) from t1);
return n < m;
end|
select f3()|
select id, f3() from t1|
# Calling two functions using same table
select f1(), f3()|
select id, f1(), f3() from t1|
# Function which uses two different tables
create function f4() returns double
return (select d from t1, t2 where t1.data = t2.i and t1.id= "b")|
select f4()|
select s, f4() from t2|
# Recursive functions which due to this recursion require simultaneous
# access to several instance of the same table won't work
create function f5(i int) returns int
begin
if i <= 0 then
return 0;
elseif i = 1 then
return (select count(*) from t1 where data = i);
else
return (select count(*) + f5( i - 1) from t1 where data = i);
end if;
end|
select f5(1)|
# This should generate an error about insuficient number of tables locked
--error 1100
select f5(2)|
# But now it simply miserably fails because we are trying to use the same
# lex on the next iteration :/ It should generate some error too...
# select f5(3)|
# OTOH this should work
create function f6() returns int
begin
declare n int;
set n:= f1();
return (select count(*) from t1 where data <= f7() and data <= n);
end|
create function f7() returns int
return (select sum(data) from t1 where data <= f1())|
select f6()|
select id, f6() from t1|
# TODO Test temporary table handling
#
# Let us test how new locking work with views
#
# The most trivial view
create view v1 (a) as select f1()|
select * from v1|
select id, a from t1, v1|
select * from v1, v1 as v|
# A bit more complex construction
create view v2 (a) as select a*10 from v1|
select * from v2|
select id, a from t1, v2|
select * from v1, v2|
# Nice example where the same view is used on
# on different expression levels
create function f8 () returns int
return (select count(*) from v2)|
select *, f8() from v1|
# Let us test what will happen if function is missing
drop function f1|
--error 1356
select * from v1|
# And what will happen if we have recursion which involves
# views and functions ?
create function f1() returns int
return (select sum(data) from t1) + (select sum(data) from v1)|
# FIXME All these just exceed file limit for me :)
#select f1()|
#select * from v1|
#select * from v2|
# Back to the normal cases
drop function f1|
create function f1() returns int
return (select sum(data) from t1)|
# Let us also test some weird cases where no real tables is used
create function f0() returns int
return (select * from (select 100) as r)|
select f0()|
select *, f0() from (select 1) as t|
create view v0 as select f0()|
select * from v0|
select *, f0() from v0|
#
# Let us test how well prelocking works with explicit LOCK TABLES.
#
# Nowdays we have to lock mysql.proc to be able to read SP definitions.
# But Monty was going to fix this.
lock tables t1 read, t1 as t11 read, mysql.proc read|
# These should work well
select f3()|
select id, f3() from t1 as t11|
# Degenerate cases work too :)
select f0()|
select * from v0|
select *, f0() from v0, (select 123) as d1|
# But these should not !
--error 1100
select id, f3() from t1|
--error 1100
select f4()|
unlock tables|
# Let us test how LOCK TABLES which implicitly depends on functions
# works
lock tables v2 read, mysql.proc read|
select * from v2|
select * from v1|
# These should not work as we have too little instances of tables locked
--error 1100
select * from v1, v2|
--error 1100
select f4()|
unlock tables|
# TODO We also should test integration with triggers
# Cleanup
drop function f0|
drop function f1|
drop function f2|
drop function f3|
drop function f4|
drop function f5|
drop function f6|
drop function f7|
drop function f8|
drop view v0|
drop view v1|
drop view v2|
delete from t1 |
delete from t2 |
# End of non-bug tests
#
# Test cases for old bugs
#
@ -1453,49 +1648,56 @@ drop procedure bug2260|
#
# BUG#2267
#
--disable_warnings
drop procedure if exists bug2267_1|
--enable_warnings
create procedure bug2267_1()
begin
show procedure status;
end|
--disable_warnings
drop procedure if exists bug2267_2|
--enable_warnings
create procedure bug2267_2()
begin
show function status;
end|
--disable_warnings
drop procedure if exists bug2267_3|
--enable_warnings
create procedure bug2267_3()
begin
show create procedure bug2267_1;
end|
--disable_warnings
drop procedure if exists bug2267_4|
--enable_warnings
create procedure bug2267_4()
begin
show create function fac;
end|
--replace_column 5 '0000-00-00 00:00:00' 6 '0000-00-00 00:00:00'
call bug2267_1()|
--replace_column 5 '0000-00-00 00:00:00' 6 '0000-00-00 00:00:00'
call bug2267_2()|
call bug2267_3()|
call bug2267_4()|
drop procedure bug2267_1|
drop procedure bug2267_2|
drop procedure bug2267_3|
drop procedure bug2267_4|
# NOTE: This test case will be fixed as soon as Monty
# will allow to open mysql.proc table under LOCK TABLES
# without mentioning in lock list.
#
# FIXME: Other solution would be to use preopened proc table
# instead of opening it anew.
#
#--disable_warnings
#drop procedure if exists bug2267_1|
#--enable_warnings
#create procedure bug2267_1()
#begin
# show procedure status;
#end|
#
#--disable_warnings
#drop procedure if exists bug2267_2|
#--enable_warnings
#create procedure bug2267_2()
#begin
# show function status;
#end|
#
#--disable_warnings
#drop procedure if exists bug2267_3|
#--enable_warnings
#create procedure bug2267_3()
#begin
# show create procedure bug2267_1;
#end|
#
#--disable_warnings
#drop procedure if exists bug2267_4|
#--enable_warnings
#create procedure bug2267_4()
#begin
# show create function fac;
#end|
#
#--replace_column 5 '0000-00-00 00:00:00' 6 '0000-00-00 00:00:00'
#call bug2267_1()|
#--replace_column 5 '0000-00-00 00:00:00' 6 '0000-00-00 00:00:00'
#call bug2267_2()|
#call bug2267_3()|
#call bug2267_4()|
#
#drop procedure bug2267_1|
#drop procedure bug2267_2|
#drop procedure bug2267_3|
#drop procedure bug2267_4|
#
# BUG#2227
@ -1529,7 +1731,7 @@ drop procedure bug2227|
#--enable_warnings
#create procedure bug2614()
#begin
# drop table if exists t3;
# drop temporary table if exists t3;
# create temporary table t3 (id int default '0' not null);
# insert into t3 select 12;
# insert into t3 select * from t3;
@ -1539,7 +1741,7 @@ drop procedure bug2227|
#call bug2614()|
#--enable_warnings
#call bug2614()|
#drop table t3|
#drop temporary table t3|
#drop procedure bug2614|
#
@ -1680,7 +1882,7 @@ begin
declare rc int default 0;
declare continue handler for 1065 set rc = 1;
drop table if exists temp_t1;
drop temporary table if exists temp_t1;
create temporary table temp_t1 (
f1 int auto_increment, f2 varchar(20), primary key (f1)
);
@ -1702,6 +1904,7 @@ call bug1863(10)|
select * from t4|
drop procedure bug1863|
drop temporary table temp_t1;
drop table t3, t4|
#
@ -2799,15 +3002,6 @@ select * from t3|
drop table t3|
drop function getcount|
#
# Former BUG#1654
# QQ Currently crashes
#
#create function bug1654() returns int
# return (select sum(t1.data) from test.t1 t)|
#
#select bug1654()|
#
# BUG#5240: Stored procedure crash if function has cursor declaration
#
@ -2827,7 +3021,6 @@ end|
delete from t1|
insert into t1 values ("answer", 42)|
# QQ BUG: This returns the wrong result, id=42 instead of "answer".
select id, bug5240() from t1|
drop function bug5240|

View File

@ -4370,11 +4370,6 @@ Item_func_sp::execute(Item **itp)
}
#endif
/*
We don't need to suppress sending 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

View File

@ -845,7 +845,7 @@ int setup_conds(THD *thd, TABLE_LIST *tables, TABLE_LIST *leaves,
int setup_ftfuncs(SELECT_LEX* select);
int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order);
void wait_for_refresh(THD *thd);
int open_tables(THD *thd, TABLE_LIST *tables, uint *counter);
int open_tables(THD *thd, TABLE_LIST **tables, uint *counter);
int simple_open_n_lock_tables(THD *thd,TABLE_LIST *tables);
bool open_and_lock_tables(THD *thd,TABLE_LIST *tables);
bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables);

187
sql/sp.cc
View File

@ -718,8 +718,29 @@ sp_drop_db_routines(THD *thd, char *db)
PROCEDURE
******************************************************************************/
/*
Obtain object representing stored procedure by its name from
stored procedures cache and looking into mysql.proc if needed.
SYNOPSIS
sp_find_procedure()
thd - thread context
name - name of procedure
cache_only - if true perform cache-only lookup
(Don't look in mysql.proc).
TODO
We should consider merging of sp_find_procedure() and
sp_find_function() into one sp_find_routine() function
(the same applies to other similarly paired functions).
RETURN VALUE
Non-0 pointer to sp_head object for the procedure, or
0 - in case of error.
*/
sp_head *
sp_find_procedure(THD *thd, sp_name *name)
sp_find_procedure(THD *thd, sp_name *name, bool cache_only)
{
sp_head *sp;
DBUG_ENTER("sp_find_procedure");
@ -727,7 +748,7 @@ sp_find_procedure(THD *thd, sp_name *name)
name->m_db.length, name->m_db.str,
name->m_name.length, name->m_name.str));
if (!(sp= sp_cache_lookup(&thd->sp_proc_cache, name)))
if (!(sp= sp_cache_lookup(&thd->sp_proc_cache, name)) && !cache_only)
{
if (db_find_routine(thd, TYPE_ENUM_PROCEDURE, name, &sp) == SP_OK)
sp_cache_insert(&thd->sp_proc_cache, sp);
@ -853,6 +874,25 @@ sp_show_status_procedure(THD *thd, const char *wild)
FUNCTION
******************************************************************************/
/*
Obtain object representing stored function by its name from
stored functions cache and looking into mysql.proc if needed.
SYNOPSIS
sp_find_function()
thd - thread context
name - name of function
cache_only - if true perform cache-only lookup
(Don't look in mysql.proc).
NOTE
See TODO section for sp_find_procedure().
RETURN VALUE
Non-0 pointer to sp_head object for the function, or
0 - in case of error.
*/
sp_head *
sp_find_function(THD *thd, sp_name *name, bool cache_only)
{
@ -986,79 +1026,120 @@ sp_add_to_hash(HASH *h, sp_name *fun)
}
void
/*
Merge contents of two hashes containing LEX_STRING's
SYNOPSIS
sp_merge_hash()
dst - hash to which elements should be added
src - hash from which elements merged
RETURN VALUE
TRUE - if we have added some new elements to destination hash.
FALSE - there were no new elements in src.
*/
bool
sp_merge_hash(HASH *dst, HASH *src)
{
bool res= FALSE;
for (uint i=0 ; i < src->records ; i++)
{
LEX_STRING *ls= (LEX_STRING *)hash_element(src, i);
if (! hash_search(dst, (byte *)ls->str, ls->length))
{
my_hash_insert(dst, (byte *)ls);
res= TRUE;
}
}
return res;
}
int
sp_cache_routines(THD *thd, LEX *lex, int type)
/*
Cache all routines implicitly or explicitly used by query
(or whatever object is represented by LEX).
SYNOPSIS
sp_cache_routines()
thd - thread context
lex - LEX representing query
NOTE
If some function is missing this won't be reported here.
Instead this fact will be discovered during query execution.
TODO
Currently if after passing through routine hashes we discover
that we have added something to them, we do one more pass to
process all routines which were missed on previous pass because
of these additions. We can avoid this if along with hashes
we use lists holding routine names and iterate other these
lists instead of hashes (since addition to the end of list
does not reorder elements in it).
*/
void
sp_cache_routines(THD *thd, LEX *lex)
{
HASH *h= (type == TYPE_ENUM_FUNCTION ? &lex->spfuns : &lex->spprocs);
int ret= 0;
bool routines_added= TRUE;
for (uint i=0 ; i < h->records ; i++)
DBUG_ENTER("sp_cache_routines");
while (routines_added)
{
LEX_STRING *ls= (LEX_STRING *)hash_element(h, i);
sp_name name(*ls);
routines_added= FALSE;
name.m_qname= *ls;
if (! sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
&thd->sp_func_cache : &thd->sp_proc_cache),
&name))
for (int type= TYPE_ENUM_FUNCTION; type < TYPE_ENUM_TRIGGER; type++)
{
sp_head *sp;
LEX *oldlex= thd->lex;
LEX *newlex= new st_lex;
HASH *h= (type == TYPE_ENUM_FUNCTION ? &lex->spfuns : &lex->spprocs);
thd->lex= newlex;
newlex->proc_table= oldlex->proc_table; // hint if mysql.oper is opened
newlex->current_select= NULL;
name.m_name.str= strchr(name.m_qname.str, '.');
name.m_db.length= name.m_name.str - name.m_qname.str;
name.m_db.str= strmake_root(thd->mem_root,
name.m_qname.str, name.m_db.length);
name.m_name.str+= 1;
name.m_name.length= name.m_qname.length - name.m_db.length - 1;
for (uint i=0 ; i < h->records ; i++)
{
LEX_STRING *ls= (LEX_STRING *)hash_element(h, i);
sp_name name(*ls);
sp_head *sp;
if (db_find_routine(thd, type, &name, &sp) == SP_OK)
{
if (type == TYPE_ENUM_FUNCTION)
sp_cache_insert(&thd->sp_func_cache, sp);
else
sp_cache_insert(&thd->sp_proc_cache, sp);
ret= sp_cache_routines(thd, newlex, TYPE_ENUM_FUNCTION);
if (!ret)
{
sp_merge_hash(&lex->spfuns, &newlex->spfuns);
ret= sp_cache_routines(thd, newlex, TYPE_ENUM_PROCEDURE);
}
if (!ret)
{
sp_merge_hash(&lex->spprocs, &newlex->spprocs);
sp_merge_table_hash(&lex->sptabs, &sp->m_sptabs);
}
delete newlex;
thd->lex= oldlex;
if (ret)
break;
}
else
{
delete newlex;
thd->lex= oldlex;
name.m_qname= *ls;
if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
&thd->sp_func_cache : &thd->sp_proc_cache),
&name)))
{
LEX *oldlex= thd->lex;
LEX *newlex= new st_lex;
thd->lex= newlex;
/* Pass hint pointer to mysql.proc table */
newlex->proc_table= oldlex->proc_table;
newlex->current_select= NULL;
name.m_name.str= strchr(name.m_qname.str, '.');
name.m_db.length= name.m_name.str - name.m_qname.str;
name.m_db.str= strmake_root(thd->mem_root, name.m_qname.str,
name.m_db.length);
name.m_name.str+= 1;
name.m_name.length= name.m_qname.length - name.m_db.length - 1;
if (db_find_routine(thd, type, &name, &sp) == SP_OK)
{
if (type == TYPE_ENUM_FUNCTION)
sp_cache_insert(&thd->sp_func_cache, sp);
else
sp_cache_insert(&thd->sp_proc_cache, sp);
}
delete newlex;
thd->lex= oldlex;
}
if (sp)
{
routines_added|= sp_merge_hash(&lex->spfuns, &sp->m_spfuns);
routines_added|= sp_merge_hash(&lex->spprocs, &sp->m_spprocs);
}
}
}
}
return ret;
DBUG_VOID_RETURN;
}
/*

View File

@ -34,7 +34,7 @@ int
sp_drop_db_routines(THD *thd, char *db);
sp_head *
sp_find_procedure(THD *thd, sp_name *name);
sp_find_procedure(THD *thd, sp_name *name, bool cache_only = 0);
int
sp_exists_routine(THD *thd, TABLE_LIST *procs, bool any, bool no_error);
@ -82,10 +82,10 @@ sp_function_exists(THD *thd, sp_name *name);
*/
void
sp_add_to_hash(HASH *h, sp_name *fun);
void
bool
sp_merge_hash(HASH *dst, HASH *src);
int
sp_cache_routines(THD *thd, LEX *lex, int type);
void
sp_cache_routines(THD *thd, LEX *lex);
//

View File

@ -334,12 +334,16 @@ sp_head::sp_head()
{
extern byte *
sp_table_key(const byte *ptr, uint *plen, my_bool first);
extern byte
*sp_lex_sp_key(const byte *ptr, uint *plen, my_bool first);
DBUG_ENTER("sp_head::sp_head");
state= INITIALIZED;
m_backpatch.empty();
m_lex.empty();
hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0);
hash_init(&m_spfuns, system_charset_info, 0, 0, 0, sp_lex_sp_key, 0, 0);
hash_init(&m_spprocs, system_charset_info, 0, 0, 0, sp_lex_sp_key, 0, 0);
DBUG_VOID_RETURN;
}
@ -515,8 +519,9 @@ sp_head::destroy()
if (lex != &m_thd->main_lex) // We got interrupted and have lex'es left
delete lex;
}
if (m_sptabs.array.buffer)
hash_free(&m_sptabs);
hash_free(&m_sptabs);
hash_free(&m_spfuns);
hash_free(&m_spprocs);
DBUG_VOID_RETURN;
}
@ -530,6 +535,11 @@ sp_head::execute(THD *thd)
int ret= 0;
uint ip= 0;
Item_arena *old_arena;
ulong old_query_id;
TABLE *old_derived_tables;
LEX *old_lex;
Item_change_list old_change_list;
String old_packet;
#ifndef EMBEDDED_LIBRARY
@ -550,6 +560,34 @@ sp_head::execute(THD *thd)
old_arena= thd->current_arena;
thd->current_arena= this;
/*
We have to save/restore this info when we are changing call level to
be able properly do close_thread_tables() in instructions.
*/
old_query_id= thd->query_id;
old_derived_tables= thd->derived_tables;
thd->derived_tables= 0;
/*
It is also more efficient to save/restore current thd->lex once when
do it in each instruction
*/
old_lex= thd->lex;
/*
We should also save Item tree change list to avoid rollback something
too early in the calling query.
*/
old_change_list= thd->change_list;
thd->change_list.empty();
/*
Cursors will use thd->packet, so they may corrupt data which was prepared
for sending by upper level. OTOH cursors in the same routine can share this
buffer safely so let use use routine-local packet instead of having own
packet buffer for each cursor.
It is probably safe to use same thd->convert_buff everywhere.
*/
old_packet.swap(thd->packet);
do
{
sp_instr *i;
@ -588,6 +626,17 @@ sp_head::execute(THD *thd)
}
} while (ret == 0 && !thd->killed);
/* Restore all saved */
old_packet.swap(thd->packet);
DBUG_ASSERT(thd->change_list.is_empty());
thd->change_list= old_change_list;
/* To avoid wiping out thd->change_list on old_change_list destruction */
old_change_list.empty();
thd->lex= old_lex;
thd->query_id= old_query_id;
DBUG_ASSERT(!thd->derived_tables);
thd->derived_tables= old_derived_tables;
cleanup_items(thd->current_arena->free_list);
thd->current_arena= old_arena;
@ -645,14 +694,6 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
DBUG_RETURN(-1);
}
}
#ifdef NOT_WORKING
/*
Close tables opened for subselect in argument list
This can't be done as this will close all other tables used
by the query.
*/
close_thread_tables(thd);
#endif
// The rest of the frame are local variables which are all IN.
// Default all variables to null (those with default clauses will
// be set by an set instruction).
@ -758,10 +799,6 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
nctx->set_oindex(i, static_cast<Item_splocal *>(it)->get_offset());
}
}
// Clean up the joins before closing the tables.
thd->lex->unit.cleanup();
// Close tables opened for subselect in argument list
close_thread_tables(thd);
// The rest of the frame are local variables which are all IN.
// Default all variables to null (those with default clauses will
@ -880,11 +917,17 @@ sp_head::restore_lex(THD *thd)
oldlex->next_state= sublex->next_state;
oldlex->trg_table_fields.push_back(&sublex->trg_table_fields);
// Collect some data from the sub statement lex.
sp_merge_hash(&oldlex->spfuns, &sublex->spfuns);
sp_merge_hash(&oldlex->spprocs, &sublex->spprocs);
// Merge used tables
sp_merge_table_list(thd, &m_sptabs, sublex->query_tables, sublex);
/*
Add routines which are used by statement to respective sets for
this routine
*/
sp_merge_hash(&m_spfuns, &sublex->spfuns);
sp_merge_hash(&m_spprocs, &sublex->spprocs);
/*
Merge tables used by this statement (but not by its functions or
procedures) to multiset of tables used by this routine.
*/
merge_table_list(thd, sublex->query_tables, sublex);
if (! sublex->sp_lex_in_use)
delete sublex;
thd->lex= oldlex;
@ -1186,22 +1229,121 @@ sp_head::opt_mark(uint ip)
// ------------------------------------------------------------------
/*
Prepare LEX and thread for execution of instruction, if requested open
and lock LEX's tables, execute instruction's core function, perform
cleanup afterwards.
SYNOPSIS
reset_lex_and_exec_core()
thd - thread context
nextp - out - next instruction
open_tables - if TRUE then check read access to tables in LEX's table
list and open and lock them (used in instructions which
need to calculate some expression and don't execute
complete statement).
sp_instr - instruction for which we prepare context, and which core
function execute by calling its exec_core() method.
NOTE
We are not saving/restoring some parts of THD which may need this because
we do this once for whole routine execution in sp_head::execute().
RETURN VALUE
0/non-0 - Success/Failure
*/
int
sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
bool open_tables, sp_instr* instr)
{
int res= 0;
DBUG_ASSERT(!thd->derived_tables);
DBUG_ASSERT(thd->change_list.is_empty());
/*
Use our own lex.
We should not save old value since it is saved/restored in
sp_head::execute() when we are entering/leaving routine.
*/
thd->lex= m_lex;
VOID(pthread_mutex_lock(&LOCK_thread_count));
thd->query_id= query_id++;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
/*
FIXME. Resetting statement (and using it) is not reentrant, thus recursive
functions which try to use the same LEX twice will crash server.
We should prevent such situations by tracking if LEX is already
in use and throwing error about unallowed recursion if needed.
OTOH it is nice to allow recursion in cases when LEX is not really
used (e.g. in mathematical functions), so such tracking should be
implemented at the same time as ability not to store LEX for
instruction if it is not really used.
*/
reset_stmt_for_execute(thd, m_lex);
/*
If requested check whenever we have access to tables in LEX's table list
and open and lock them before executing instructtions core function.
*/
if (open_tables &&
(check_table_access(thd, SELECT_ACL, m_lex->query_tables, 0) ||
open_and_lock_tables(thd, m_lex->query_tables)))
res= -1;
if (!res)
res= instr->exec_core(thd, nextp);
m_lex->unit.cleanup();
thd->proc_info="closing tables";
close_thread_tables(thd);
thd->rollback_item_tree_changes();
/*
Unlike for PS we should not call Item's destructors for newly created
items after execution of each instruction in stored routine. This is
because SP often create Item (like Item_int, Item_string etc...) when
they want to store some value in local variable, pass return value and
etc... So their life time should be longer than one instruction.
Probably we can call destructors for most of them then we are leaving
routine. But this won't help much as they are allocated in main query
MEM_ROOT anyway. So they all go to global thd->free_list.
May be we can use some other MEM_ROOT for this purprose ???
What else should we do for cleanup ?
cleanup_items() is called in sp_head::execute()
*/
return res;
}
//
// sp_instr
//
int sp_instr::exec_core(THD *thd, uint *nextp)
{
DBUG_ASSERT(0);
return 0;
}
//
// sp_instr_stmt
//
sp_instr_stmt::~sp_instr_stmt()
{
if (m_lex)
delete m_lex;
}
int
sp_instr_stmt::execute(THD *thd, uint *nextp)
{
char *query;
uint32 query_length;
DBUG_ENTER("sp_instr_stmt::execute");
DBUG_PRINT("info", ("command: %d", m_lex->sql_command));
DBUG_PRINT("info", ("command: %d", m_lex_keeper.sql_command()));
int res;
query= thd->query;
@ -1211,13 +1353,14 @@ sp_instr_stmt::execute(THD *thd, uint *nextp)
if (query_cache_send_result_to_client(thd,
thd->query, thd->query_length) <= 0)
{
res= exec_stmt(thd, m_lex);
res= m_lex_keeper.reset_lex_and_exec_core(thd, nextp, FALSE, this);
query_cache_end_of_result(thd);
}
else
*nextp= m_ip+1;
thd->query= query;
thd->query_length= query_length;
}
*nextp = m_ip+1;
DBUG_RETURN(res);
}
@ -1226,39 +1369,15 @@ sp_instr_stmt::print(String *str)
{
str->reserve(12);
str->append("stmt ");
str->qs_append((uint)m_lex->sql_command);
str->qs_append((uint)m_lex_keeper.sql_command());
}
int
sp_instr_stmt::exec_stmt(THD *thd, LEX *lex)
sp_instr_stmt::exec_core(THD *thd, uint *nextp)
{
LEX *olex; // The other lex
int res;
olex= thd->lex; // Save the other lex
thd->lex= lex; // Use my own lex
thd->lex->thd = thd; // QQ Not reentrant!
thd->lex->unit.thd= thd; // QQ Not reentrant
thd->free_list= NULL;
VOID(pthread_mutex_lock(&LOCK_thread_count));
thd->query_id= query_id++;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
reset_stmt_for_execute(thd, lex);
res= mysql_execute_command(thd);
lex->unit.cleanup();
if (thd->lock || thd->open_tables || thd->derived_tables)
{
thd->proc_info="closing tables";
close_thread_tables(thd); /* Free tables */
}
thd->lex= olex; // Restore the other lex
int res= mysql_execute_command(thd);
*nextp= m_ip+1;
return res;
}
@ -1270,14 +1389,16 @@ sp_instr_set::execute(THD *thd, uint *nextp)
{
DBUG_ENTER("sp_instr_set::execute");
DBUG_PRINT("info", ("offset: %u", m_offset));
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
}
int
sp_instr_set::exec_core(THD *thd, uint *nextp)
{
Item *it;
int res;
if (tables &&
((res= check_table_access(thd, SELECT_ACL, tables, 0)) ||
(res= open_and_lock_tables(thd, tables))))
DBUG_RETURN(res);
it= sp_eval_func_item(thd, m_value, m_type);
if (! it)
res= -1;
@ -1287,9 +1408,8 @@ sp_instr_set::execute(THD *thd, uint *nextp)
thd->spcont->set_item(m_offset, it);
}
*nextp = m_ip+1;
if (tables && (thd->lock || thd->open_tables || thd->derived_tables))
close_thread_tables(thd);
DBUG_RETURN(res);
return res;
}
void
@ -1302,32 +1422,6 @@ 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
@ -1420,19 +1514,21 @@ sp_instr_jump::opt_move(uint dst, List<sp_instr> *bp)
//
// sp_instr_jump_if
//
int
sp_instr_jump_if::execute(THD *thd, uint *nextp)
{
DBUG_ENTER("sp_instr_jump_if::execute");
DBUG_PRINT("info", ("destination: %u", m_dest));
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
}
int
sp_instr_jump_if::exec_core(THD *thd, uint *nextp)
{
Item *it;
int res;
if (tables &&
((res= check_table_access(thd, SELECT_ACL, tables, 0)) ||
(res= open_and_lock_tables(thd, tables))))
DBUG_RETURN(res);
it= sp_eval_func_item(thd, m_expr, MYSQL_TYPE_TINY);
if (!it)
res= -1;
@ -1444,9 +1540,8 @@ sp_instr_jump_if::execute(THD *thd, uint *nextp)
else
*nextp = m_ip+1;
}
if (tables && (thd->lock || thd->open_tables || thd->derived_tables))
close_thread_tables(thd);
DBUG_RETURN(res);
return res;
}
void
@ -1482,14 +1577,16 @@ sp_instr_jump_if_not::execute(THD *thd, uint *nextp)
{
DBUG_ENTER("sp_instr_jump_if_not::execute");
DBUG_PRINT("info", ("destination: %u", m_dest));
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
}
int
sp_instr_jump_if_not::exec_core(THD *thd, uint *nextp)
{
Item *it;
int res;
if (tables &&
((res= check_table_access(thd, SELECT_ACL, tables, 0)) ||
(res= open_and_lock_tables(thd, tables))))
DBUG_RETURN(res);
it= sp_eval_func_item(thd, m_expr, MYSQL_TYPE_TINY);
if (! it)
res= -1;
@ -1501,9 +1598,8 @@ sp_instr_jump_if_not::execute(THD *thd, uint *nextp)
else
*nextp = m_ip+1;
}
if (tables && (thd->lock || thd->open_tables || thd->derived_tables))
close_thread_tables(thd);
DBUG_RETURN(res);
return res;
}
void
@ -1534,18 +1630,21 @@ sp_instr_jump_if_not::opt_mark(sp_head *sp)
//
// sp_instr_freturn
//
int
sp_instr_freturn::execute(THD *thd, uint *nextp)
{
DBUG_ENTER("sp_instr_freturn::execute");
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
}
int
sp_instr_freturn::exec_core(THD *thd, uint *nextp)
{
Item *it;
int res;
if (tables &&
((res= check_table_access(thd, SELECT_ACL, tables, 0)) ||
(res= open_and_lock_tables(thd, tables))))
DBUG_RETURN(res);
it= sp_eval_func_item(thd, m_value, m_type);
if (! it)
res= -1;
@ -1555,7 +1654,8 @@ sp_instr_freturn::execute(THD *thd, uint *nextp)
thd->spcont->set_result(it);
}
*nextp= UINT_MAX;
DBUG_RETURN(res);
return res;
}
void
@ -1689,17 +1789,11 @@ int
sp_instr_cpush::execute(THD *thd, uint *nextp)
{
DBUG_ENTER("sp_instr_cpush::execute");
thd->spcont->push_cursor(m_lex);
thd->spcont->push_cursor(&m_lex_keeper);
*nextp= m_ip+1;
DBUG_RETURN(0);
}
sp_instr_cpush::~sp_instr_cpush()
{
if (m_lex)
delete m_lex;
}
void
sp_instr_cpush::print(String *str)
{
@ -1746,19 +1840,30 @@ sp_instr_copen::execute(THD *thd, uint *nextp)
res= -1;
else
{
LEX *lex= c->pre_open(thd);
sp_lex_keeper *lex_keeper= c->pre_open(thd);
if (! lex)
if (!lex_keeper)
{
res= -1;
*nextp= m_ip+1;
}
else
res= exec_stmt(thd, lex);
c->post_open(thd, (lex ? TRUE : FALSE));
res= lex_keeper->reset_lex_and_exec_core(thd, nextp, FALSE, this);
c->post_open(thd, (lex_keeper ? TRUE : FALSE));
}
*nextp= m_ip+1;
DBUG_RETURN(res);
}
int
sp_instr_copen::exec_core(THD *thd, uint *nextp)
{
int res= mysql_execute_command(thd);
*nextp= m_ip+1;
return res;
}
void
sp_instr_copen::print(String *str)
{
@ -1910,14 +2015,17 @@ sp_restore_security_context(THD *thd, sp_head *sp, st_sp_security_context *ctxp)
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
/*
* Table merge hash table
*
*/
Structure that represent all instances of one table
in optimized multi-set of tables used by routine.
*/
typedef struct st_sp_table
{
LEX_STRING qname;
bool temp;
TABLE_LIST *table;
uint lock_count;
uint query_lock_count;
} SP_TABLE;
byte *
@ -1928,23 +2036,47 @@ sp_table_key(const byte *ptr, uint *plen, my_bool first)
return (byte *)tab->qname.str;
}
/*
* Merge the table list into the hash table.
* If the optional lex is provided, it's used to check and set
* the flag for creation of a temporary table.
*/
Merge the list of tables used by some query into the multi-set of
tables used by routine.
SYNOPSIS
merge_table_list()
thd - thread context
table - table list
lex_for_tmp_check - LEX of the query for which we are merging
table list.
NOTE
This method will use LEX provided to check whenever we are creating
temporary table and mark it as such in target multi-set.
RETURN VALUE
TRUE - Success
FALSE - Error
*/
bool
sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table,
LEX *lex_for_tmp_check)
sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check)
{
SP_TABLE *tab;
if (lex_for_tmp_check->sql_command == SQLCOM_DROP_TABLE &&
lex_for_tmp_check->drop_temporary)
return TRUE;
for (uint i= 0 ; i < m_sptabs.records ; i++)
{
tab= (SP_TABLE *)hash_element(&m_sptabs, i);
tab->query_lock_count= 0;
}
for (; table ; table= table->next_global)
if (!table->derived &&
(!table->select_lex ||
!(table->select_lex->options & OPTION_SCHEMA_TABLE)))
if (!table->derived && !table->schema_table)
{
char tname[64+1+64+1+64+1]; // db.table.alias\0
uint tlen, alen;
SP_TABLE *tab;
tlen= table->db_length;
memcpy(tname, table->db, tlen);
@ -1957,10 +2089,17 @@ sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table,
tlen+= alen;
tname[tlen]= '\0';
if ((tab= (SP_TABLE *)hash_search(h, (byte *)tname, tlen)))
/*
It is safe to store pointer to table list elements in hash,
since they are supposed to have the same lifetime.
*/
if ((tab= (SP_TABLE *)hash_search(&m_sptabs, (byte *)tname, tlen)))
{
if (tab->table->lock_type < table->lock_type)
tab->table= table; // Use the table with the highest lock type
tab->query_lock_count++;
if (tab->query_lock_count > tab->lock_count)
tab->lock_count++;
}
else
{
@ -1970,152 +2109,102 @@ sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table,
tab->qname.str= (char *)thd->strmake(tname, tab->qname.length);
if (!tab->qname.str)
return FALSE;
if (lex_for_tmp_check &&
lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE &&
if (lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE &&
lex_for_tmp_check->query_tables == table &&
lex_for_tmp_check->create_info.options & HA_LEX_CREATE_TMP_TABLE)
tab->temp= TRUE;
tab->table= table;
my_hash_insert(h, (byte *)tab);
tab->lock_count= tab->query_lock_count= 1;
my_hash_insert(&m_sptabs, (byte *)tab);
}
}
return TRUE;
}
void
sp_merge_routine_tables(THD *thd, LEX *lex)
{
uint i;
for (i= 0 ; i < lex->spfuns.records ; i++)
{
sp_head *sp;
LEX_STRING *ls= (LEX_STRING *)hash_element(&lex->spfuns, i);
sp_name name(*ls);
/*
Add tables used by routine to the table list.
name.m_qname= *ls;
if ((sp= sp_cache_lookup(&thd->sp_func_cache, &name)))
sp_merge_table_hash(&lex->sptabs, &sp->m_sptabs);
}
for (i= 0 ; i < lex->spprocs.records ; i++)
{
sp_head *sp;
LEX_STRING *ls= (LEX_STRING *)hash_element(&lex->spprocs, i);
sp_name name(*ls);
SYNOPSIS
add_used_tables_to_table_list()
thd - thread context
query_tables_last_ptr - (in/out) pointer the next_global member of last
element of the list where tables will be added
(or to its root).
name.m_qname= *ls;
if ((sp= sp_cache_lookup(&thd->sp_proc_cache, &name)))
sp_merge_table_hash(&lex->sptabs, &sp->m_sptabs);
}
}
DESCRIPTION
Converts multi-set of tables used by this routine to table list and adds
this list to the end of table list specified by 'query_tables_last_ptr'.
void
sp_merge_table_hash(HASH *hdst, HASH *hsrc)
{
for (uint i=0 ; i < hsrc->records ; i++)
{
SP_TABLE *tabdst;
SP_TABLE *tabsrc= (SP_TABLE *)hash_element(hsrc, i);
Elements of list will be allocated in PS memroot, so this list will be
persistent between PS executions.
if (! (tabdst= (SP_TABLE *)hash_search(hdst,
tabsrc->qname.str,
tabsrc->qname.length)))
{
my_hash_insert(hdst, (byte *)tabsrc);
}
else
{
if (tabdst->table->lock_type < tabsrc->table->lock_type)
tabdst->table= tabsrc->table; // Use the highest lock type
}
}
}
TABLE_LIST *
sp_hash_to_table_list(THD *thd, HASH *h)
{
uint i;
TABLE_LIST *tables= NULL;
DBUG_ENTER("sp_hash_to_table_list");
for (i=0 ; i < h->records ; i++)
{
SP_TABLE *stab= (SP_TABLE *)hash_element(h, i);
if (stab->temp)
continue;
TABLE_LIST *table, *otable= stab->table;
if (! (table= (TABLE_LIST *)thd->calloc(sizeof(TABLE_LIST))))
return NULL;
table->db= otable->db;
table->db_length= otable->db_length;
table->alias= otable->alias;
table->table_name= otable->table_name;
table->table_name_length= otable->table_name_length;
table->lock_type= otable->lock_type;
table->updating= otable->updating;
table->force_index= otable->force_index;
table->ignore_leaves= otable->ignore_leaves;
table->derived= otable->derived;
table->schema_table= otable->schema_table;
table->select_lex= otable->select_lex;
table->cacheable_table= otable->cacheable_table;
table->use_index= otable->use_index;
table->ignore_index= otable->ignore_index;
table->option= otable->option;
table->next_global= tables;
tables= table;
}
DBUG_RETURN(tables);
}
RETURN VALUE
TRUE - if some elements were added, FALSE - otherwise.
*/
bool
sp_open_and_lock_tables(THD *thd, TABLE_LIST *tables)
sp_head::add_used_tables_to_table_list(THD *thd,
TABLE_LIST ***query_tables_last_ptr)
{
DBUG_ENTER("sp_open_and_lock_tables");
bool ret;
uint i;
Item_arena *arena, backup;
bool result= FALSE;
DBUG_ENTER("sp_head::add_used_tables_to_table_list");
thd->in_lock_tables= 1;
thd->options|= OPTION_TABLE_LOCK;
if (simple_open_n_lock_tables(thd, tables))
{
thd->options&= ~(ulong)(OPTION_TABLE_LOCK);
ret= FALSE;
}
else
{
#if 0
// QQ What about this?
#ifdef HAVE_QUERY_CACHE
if (thd->variables.query_cache_wlock_invalidate)
query_cache.invalidate_locked_for_write(first_table); // QQ first_table?
#endif /* HAVE_QUERY_CACHE */
#endif
thd->locked_tables= thd->lock;
thd->lock= 0;
ret= TRUE;
}
thd->in_lock_tables= 0;
DBUG_RETURN(ret);
}
/*
Use persistent arena for table list allocation to be PS friendly.
*/
arena= thd->change_arena_if_needed(&backup);
void
sp_unlock_tables(THD *thd)
{
thd->lock= thd->locked_tables;
thd->locked_tables= 0;
close_thread_tables(thd); // Free tables
if (thd->options & OPTION_TABLE_LOCK)
for (i=0 ; i < m_sptabs.records ; i++)
{
#if 0
// QQ What about this?
end_active_trans(thd);
#endif
thd->options&= ~(ulong)(OPTION_TABLE_LOCK);
char *tab_buff;
TABLE_LIST *table, *otable;
SP_TABLE *stab= (SP_TABLE *)hash_element(&m_sptabs, i);
if (stab->temp)
continue;
otable= stab->table;
if (!(tab_buff= (char *)thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST)) *
stab->lock_count)))
DBUG_RETURN(FALSE);
for (uint j= 0; j < stab->lock_count; j++)
{
table= (TABLE_LIST *)tab_buff;
/*
It's enough to just copy the pointers as the data will not change
during the lifetime of the SP. If the SP is used by PS, we assume
that the PS will be invalidated if the functions is deleted or
changed.
*/
table->db= otable->db;
table->db_length= otable->db_length;
table->alias= otable->alias;
table->table_name= otable->table_name;
table->table_name_length= otable->table_name_length;
table->lock_type= otable->lock_type;
table->cacheable_table= 1;
table->prelocking_placeholder= 1;
/* Everyting else should be zeroed */
**query_tables_last_ptr= table;
table->prev_global= *query_tables_last_ptr;
*query_tables_last_ptr= &table->next_global;
tab_buff+= ALIGN_SIZE(sizeof(TABLE_LIST));
result= TRUE;
}
}
if (thd->global_read_lock)
unlock_global_read_lock(thd);
if (arena)
thd->restore_backup_item_arena(arena, &backup);
DBUG_RETURN(result);
}
/*
@ -2147,3 +2236,73 @@ sp_add_to_query_tables(THD *thd, LEX *lex,
lex->add_to_query_tables(table);
return table;
}
/*
Auxilary function for adding tables used by routines used in query
to table lists.
SYNOPSIS
sp_add_sp_tables_to_table_list_aux()
thd - thread context
lex - LEX to which table list tables will be added
func_hash - routines for which tables should be added
func_cache- SP cache in which this routines should be looked up
NOTE
See sp_add_sp_tables_to_table_list() for more info.
RETURN VALUE
TRUE - some tables were added
FALSE - no tables were added.
*/
static bool
sp_add_sp_tables_to_table_list_aux(THD *thd, LEX *lex, HASH *func_hash,
sp_cache **func_cache)
{
uint i;
bool result= FALSE;
for (i= 0 ; i < func_hash->records ; i++)
{
sp_head *sp;
LEX_STRING *ls= (LEX_STRING *)hash_element(func_hash, i);
sp_name name(*ls);
name.m_qname= *ls;
if ((sp= sp_cache_lookup(func_cache, &name)))
result|= sp->add_used_tables_to_table_list(thd, &lex->query_tables_last);
}
return result;
}
/*
Add tables used by routines used in query to table list.
SYNOPSIS
sp_add_sp_tables_to_table_list()
thd - thread context
lex - LEX to which table list tables will be added
func_lex - LEX for which functions we get tables
(useful for adding tables used by view routines)
NOTE
Elements of list will be allocated in PS memroot, so this
list will be persistent between PS execetutions.
RETURN VALUE
TRUE - some tables were added
FALSE - no tables were added.
*/
bool
sp_add_sp_tables_to_table_list(THD *thd, LEX *lex, LEX *func_lex)
{
return (sp_add_sp_tables_to_table_list_aux(thd, lex, &func_lex->spfuns,
&thd->sp_func_cache) |
sp_add_sp_tables_to_table_list_aux(thd, lex, &func_lex->spprocs,
&thd->sp_proc_cache));
}

View File

@ -103,7 +103,14 @@ public:
LEX_STRING m_definer_host;
longlong m_created;
longlong m_modified;
HASH m_sptabs; /* Merged table lists */
/*
Sets containing names of SP and SF used by this routine.
TODO Probably we should combine these two hashes in one. It will
decrease memory overhead ans simplify algorithms using them. The
same applies to similar hashes in LEX.
*/
HASH m_spfuns, m_spprocs;
// Pointers set during parsing
uchar *m_param_begin, *m_param_end, *m_returns_begin, *m_returns_end,
*m_body_begin;
@ -225,6 +232,10 @@ public:
return ip;
}
/* Add tables used by routine to the table list. */
bool add_used_tables_to_table_list(THD *thd,
TABLE_LIST ***query_tables_last_ptr);
private:
MEM_ROOT *m_thd_root; // Temp. store for thd's mem_root
@ -240,10 +251,20 @@ private:
sp_instr *instr;
} bp_t;
List<bp_t> m_backpatch; // Instructions needing backpatching
/*
Multi-set representing optimized list of tables to be locked by this
routine. Does not include tables which are used by invoked routines.
*/
HASH m_sptabs;
int
execute(THD *thd);
/*
Merge the list of tables used by query into the multi-set of tables used
by routine.
*/
bool merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check);
}; // class sp_head : public Sql_alloc
@ -277,6 +298,17 @@ public:
// Returns 0 on success, non-zero if some error occured.
virtual int execute(THD *thd, uint *nextp) = 0;
/*
Execute core function of instruction after all preparations (e.g.
setting of proper LEX, saving part of the thread context have been
done).
Should be implemented for instructions using expressions or whole
statements (thus having to have own LEX). Used in concert with
sp_lex_keeper class and its descendants.
*/
virtual int exec_core(THD *thd, uint *nextp);
virtual void print(String *str) = 0;
virtual void backpatch(uint dest, sp_pcontext *dst_ctx)
@ -301,6 +333,60 @@ public:
}; // class sp_instr : public Sql_alloc
/*
Auxilary class to which instructions delegate responsibility
for handling LEX and preparations before executing statement
or calculating complex expression.
Exist mainly to avoid having double hierarchy between instruction
classes.
TODO: Add ability to not store LEX and do any preparations if
expression used is simple.
*/
class sp_lex_keeper
{
/* Prevent use of these */
sp_lex_keeper(const sp_lex_keeper &);
void operator=(sp_lex_keeper &);
public:
sp_lex_keeper(LEX *lex, bool lex_resp)
: m_lex(lex), m_lex_resp(lex_resp)
{
lex->sp_lex_in_use= TRUE;
}
virtual ~sp_lex_keeper()
{
if (m_lex_resp)
delete m_lex;
}
/*
Prepare execution of instruction using LEX, if requested check whenever
we have read access to tables used and open/lock them, call instruction's
exec_core() method, perform cleanup afterwards.
*/
int reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables,
sp_instr* instr);
inline uint sql_command() const
{
return (uint)m_lex->sql_command;
}
private:
LEX *m_lex;
/*
Indicates whenever this sp_lex_keeper instance responsible
for LEX deletion.
*/
bool m_lex_resp;
};
//
// Call out to some prepared SQL statement.
//
@ -313,38 +399,25 @@ public:
LEX_STRING m_query; // For thd->query
sp_instr_stmt(uint ip, sp_pcontext *ctx)
: sp_instr(ip, ctx), m_lex(NULL)
sp_instr_stmt(uint ip, sp_pcontext *ctx, LEX *lex)
: sp_instr(ip, ctx), m_lex_keeper(lex, TRUE)
{
m_query.str= 0;
m_query.length= 0;
}
virtual ~sp_instr_stmt();
virtual ~sp_instr_stmt()
{};
virtual int execute(THD *thd, uint *nextp);
virtual int exec_core(THD *thd, uint *nextp);
virtual void print(String *str);
inline void
set_lex(LEX *lex)
{
m_lex= lex;
}
inline LEX *
get_lex()
{
return m_lex;
}
protected:
int exec_stmt(THD *thd, LEX *lex); // Execute a statement
private:
LEX *m_lex; // My own lex
sp_lex_keeper m_lex_keeper;
}; // class sp_instr_stmt : public sp_instr
@ -356,12 +429,11 @@ class sp_instr_set : public sp_instr
public:
TABLE_LIST *tables;
sp_instr_set(uint ip, sp_pcontext *ctx,
uint offset, Item *val, enum enum_field_types type)
: sp_instr(ip, ctx),
tables(NULL), m_offset(offset), m_value(val), m_type(type)
uint offset, Item *val, enum enum_field_types type,
LEX *lex, bool lex_resp)
: sp_instr(ip, ctx), m_offset(offset), m_value(val), m_type(type),
m_lex_keeper(lex, lex_resp)
{}
virtual ~sp_instr_set()
@ -369,6 +441,8 @@ public:
virtual int execute(THD *thd, uint *nextp);
virtual int exec_core(THD *thd, uint *nextp);
virtual void print(String *str);
private:
@ -376,41 +450,11 @@ private:
uint m_offset; // Frame offset
Item *m_value;
enum enum_field_types m_type; // The declared type
sp_lex_keeper m_lex_keeper;
}; // 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.
*/
@ -492,14 +536,12 @@ class sp_instr_jump_if : public sp_instr_jump
public:
TABLE_LIST *tables;
sp_instr_jump_if(uint ip, sp_pcontext *ctx, Item *i)
: sp_instr_jump(ip, ctx), tables(NULL), m_expr(i)
sp_instr_jump_if(uint ip, sp_pcontext *ctx, Item *i, LEX *lex)
: sp_instr_jump(ip, ctx), m_expr(i), m_lex_keeper(lex, TRUE)
{}
sp_instr_jump_if(uint ip, sp_pcontext *ctx, Item *i, uint dest)
: sp_instr_jump(ip, ctx, dest), tables(NULL), m_expr(i)
sp_instr_jump_if(uint ip, sp_pcontext *ctx, Item *i, uint dest, LEX *lex)
: sp_instr_jump(ip, ctx, dest), m_expr(i), m_lex_keeper(lex, TRUE)
{}
virtual ~sp_instr_jump_if()
@ -507,6 +549,8 @@ public:
virtual int execute(THD *thd, uint *nextp);
virtual int exec_core(THD *thd, uint *nextp);
virtual void print(String *str);
virtual uint opt_mark(sp_head *sp);
@ -519,6 +563,7 @@ public:
private:
Item *m_expr; // The condition
sp_lex_keeper m_lex_keeper;
}; // class sp_instr_jump_if : public sp_instr_jump
@ -530,14 +575,12 @@ class sp_instr_jump_if_not : public sp_instr_jump
public:
TABLE_LIST *tables;
sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i)
: sp_instr_jump(ip, ctx), tables(NULL), m_expr(i)
sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i, LEX *lex)
: sp_instr_jump(ip, ctx), m_expr(i), m_lex_keeper(lex, TRUE)
{}
sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i, uint dest)
: sp_instr_jump(ip, ctx, dest), tables(NULL), m_expr(i)
sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i, uint dest, LEX *lex)
: sp_instr_jump(ip, ctx, dest), m_expr(i), m_lex_keeper(lex, TRUE)
{}
virtual ~sp_instr_jump_if_not()
@ -545,6 +588,8 @@ public:
virtual int execute(THD *thd, uint *nextp);
virtual int exec_core(THD *thd, uint *nextp);
virtual void print(String *str);
virtual uint opt_mark(sp_head *sp);
@ -557,6 +602,7 @@ public:
private:
Item *m_expr; // The condition
sp_lex_keeper m_lex_keeper;
}; // class sp_instr_jump_if_not : public sp_instr_jump
@ -568,11 +614,9 @@ class sp_instr_freturn : public sp_instr
public:
TABLE_LIST *tables;
sp_instr_freturn(uint ip, sp_pcontext *ctx,
Item *val, enum enum_field_types type)
: sp_instr(ip, ctx), tables(NULL), m_value(val), m_type(type)
Item *val, enum enum_field_types type, LEX *lex)
: sp_instr(ip, ctx), m_value(val), m_type(type), m_lex_keeper(lex, TRUE)
{}
virtual ~sp_instr_freturn()
@ -580,6 +624,8 @@ public:
virtual int execute(THD *thd, uint *nextp);
virtual int exec_core(THD *thd, uint *nextp);
virtual void print(String *str);
virtual uint opt_mark(sp_head *sp)
@ -592,6 +638,7 @@ protected:
Item *m_value;
enum enum_field_types m_type;
sp_lex_keeper m_lex_keeper;
}; // class sp_instr_freturn : public sp_instr
@ -710,10 +757,11 @@ class sp_instr_cpush : public sp_instr
public:
sp_instr_cpush(uint ip, sp_pcontext *ctx, LEX *lex)
: sp_instr(ip, ctx), m_lex(lex)
: sp_instr(ip, ctx), m_lex_keeper(lex, TRUE)
{}
virtual ~sp_instr_cpush();
virtual ~sp_instr_cpush()
{}
virtual int execute(THD *thd, uint *nextp);
@ -721,7 +769,7 @@ public:
private:
LEX *m_lex;
sp_lex_keeper m_lex_keeper;
}; // class sp_instr_cpush : public sp_instr
@ -760,7 +808,7 @@ private:
}; // class sp_instr_cpop : public sp_instr
class sp_instr_copen : public sp_instr_stmt
class sp_instr_copen : public sp_instr
{
sp_instr_copen(const sp_instr_copen &); /* Prevent use of these */
void operator=(sp_instr_copen &);
@ -768,7 +816,7 @@ class sp_instr_copen : public sp_instr_stmt
public:
sp_instr_copen(uint ip, sp_pcontext *ctx, uint c)
: sp_instr_stmt(ip, ctx), m_cursor(c)
: sp_instr(ip, ctx), m_cursor(c)
{}
virtual ~sp_instr_copen()
@ -776,6 +824,8 @@ public:
virtual int execute(THD *thd, uint *nextp);
virtual int exec_core(THD *thd, uint *nextp);
virtual void print(String *str);
private:
@ -893,22 +943,11 @@ void
sp_restore_security_context(THD *thd, sp_head *sp,st_sp_security_context *ctxp);
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
bool
sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table,
LEX *lex_for_tmp_check = 0);
void
sp_merge_routine_tables(THD *thd, LEX *lex);
void
sp_merge_table_hash(HASH *hdst, HASH *hsrc);
TABLE_LIST *
sp_hash_to_table_list(THD *thd, HASH *h);
bool
sp_open_and_lock_tables(THD *thd, TABLE_LIST *tables);
void
sp_unlock_tables(THD *thd);
TABLE_LIST *
sp_add_to_query_tables(THD *thd, LEX *lex,
const char *db, const char *name,
thr_lock_type locktype);
bool
sp_add_sp_tables_to_table_list(THD *thd, LEX *lex, LEX *func_lex);
#endif /* _SP_HEAD_H_ */

View File

@ -125,9 +125,9 @@ sp_rcontext::restore_variables(uint fp)
}
void
sp_rcontext::push_cursor(LEX *lex)
sp_rcontext::push_cursor(sp_lex_keeper *lex_keeper)
{
m_cstack[m_ccount++]= new sp_cursor(lex);
m_cstack[m_ccount++]= new sp_cursor(lex_keeper);
}
void
@ -148,7 +148,7 @@ sp_rcontext::pop_cursors(uint count)
// We have split this in two to make it easy for sp_instr_copen
// to reuse the sp_instr::exec_stmt() code.
LEX *
sp_lex_keeper*
sp_cursor::pre_open(THD *thd)
{
if (m_isopen)
@ -168,7 +168,7 @@ sp_cursor::pre_open(THD *thd)
m_nseof= thd->net.no_send_eof;
thd->net.no_send_eof= TRUE;
return m_lex;
return m_lex_keeper;
}
void

View File

@ -25,6 +25,7 @@
struct sp_cond_type;
class sp_cursor;
struct sp_pvar;
class sp_lex_keeper;
#define SP_HANDLER_NONE 0
#define SP_HANDLER_EXIT 1
@ -164,7 +165,7 @@ class sp_rcontext : public Sql_alloc
restore_variables(uint fp);
void
push_cursor(LEX *lex);
push_cursor(sp_lex_keeper *lex_keeper);
void
pop_cursors(uint count);
@ -207,8 +208,8 @@ class sp_cursor : public Sql_alloc
{
public:
sp_cursor(LEX *lex)
: m_lex(lex), m_prot(NULL), m_isopen(0), m_current_row(NULL)
sp_cursor(sp_lex_keeper *lex_keeper)
: m_lex_keeper(lex_keeper), m_prot(NULL), m_isopen(0), m_current_row(NULL)
{
/* Empty */
}
@ -220,7 +221,7 @@ public:
// We have split this in two to make it easy for sp_instr_copen
// to reuse the sp_instr::exec_stmt() code.
LEX *
sp_lex_keeper *
pre_open(THD *thd);
void
post_open(THD *thd, my_bool was_opened);
@ -240,7 +241,7 @@ public:
private:
MEM_ROOT m_mem_root; // My own mem_root
LEX *m_lex;
sp_lex_keeper *m_lex_keeper;
Protocol_cursor *m_prot;
my_bool m_isopen;
my_bool m_nseof; // Original no_send_eof

View File

@ -138,7 +138,6 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables)
TABLE_LIST tables[3];
TABLE *table;
READ_RECORD read_record_info;
MYSQL_LOCK *lock;
my_bool return_val=1;
bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE;
DBUG_ENTER("acl_init");
@ -174,20 +173,9 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables)
tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ;
tables[0].db=tables[1].db=tables[2].db=thd->db;
uint counter;
if (open_tables(thd, tables, &counter))
if (simple_open_n_lock_tables(thd, tables))
{
sql_print_error("Fatal error: Can't open privilege tables: %s",
thd->net.last_error);
goto end;
}
TABLE *ptr[3]; // Lock tables for quick update
ptr[0]= tables[0].table;
ptr[1]= tables[1].table;
ptr[2]= tables[2].table;
if (!(lock=mysql_lock_tables(thd,ptr,3)))
{
sql_print_error("Fatal error: Can't lock privilege tables: %s",
sql_print_error("Fatal error: Can't open and lock privilege tables: %s",
thd->net.last_error);
goto end;
}
@ -425,7 +413,6 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables)
freeze_size(&acl_dbs);
init_check_host();
mysql_unlock_tables(thd, lock);
initialized=1;
thd->version--; // Force close to free memory
return_val=0;
@ -3091,7 +3078,6 @@ my_bool grant_init(THD *org_thd)
{
THD *thd;
TABLE_LIST tables[3];
MYSQL_LOCK *lock;
MEM_ROOT *memex_ptr;
my_bool return_val= 1;
TABLE *t_table, *c_table, *p_table;
@ -3125,15 +3111,7 @@ my_bool grant_init(THD *org_thd)
tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ;
tables[0].db=tables[1].db=tables[2].db=thd->db;
uint counter;
if (open_tables(thd, tables, &counter))
goto end;
TABLE *ptr[3]; // Lock tables for quick update
ptr[0]= tables[0].table;
ptr[1]= tables[1].table;
ptr[2]= tables[2].table;
if (!(lock=mysql_lock_tables(thd,ptr,3)))
if (simple_open_n_lock_tables(thd, tables))
goto end;
t_table = tables[0].table; c_table = tables[1].table;
@ -3223,7 +3201,6 @@ my_bool grant_init(THD *org_thd)
end_unlock:
t_table->file->ha_index_end();
p_table->file->ha_index_end();
mysql_unlock_tables(thd, lock);
thd->version--; // Force close to free memory
end:

View File

@ -20,6 +20,7 @@
#include "mysql_priv.h"
#include "sql_select.h"
#include "sp_head.h"
#include "sp.h"
#include "sql_trigger.h"
#include <m_ctype.h>
#include <my_dir.h>
@ -359,7 +360,30 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh,
/*
Close all tables used by thread
Mark all tables in the list which were used by current substatement
as free for reuse.
SYNOPSIS
mark_used_tables_as_free_for_reuse()
thd - thread context
table - head of the list of tables
DESCRIPTION
Marks all tables in the list which were used by current substatement
(they are marked by its query_id) as free for reuse.
*/
static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table)
{
for (; table ; table= table->next)
if (table->query_id == thd->query_id)
table->query_id= 0;
}
/*
Close all tables used by the current substatement, or all tables
used by this thread if we are on the upper level.
SYNOPSIS
close_thread_tables()
@ -372,14 +396,31 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh,
IMPLEMENTATION
Unlocks tables and frees derived tables.
Put all normal tables used by thread in free list.
When in prelocked mode it will only close/mark as free for reuse
tables opened by this substatement, it will also check if we are
closing tables after execution of complete query (i.e. we are on
upper level) and will leave prelocked mode if needed.
*/
void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived,
TABLE *stopper)
{
bool found_old_table;
prelocked_mode_type prelocked_mode= thd->prelocked_mode;
DBUG_ENTER("close_thread_tables");
/*
We are assuming here that thd->derived_tables contains ONLY derived
tables for this substatement. i.e. instead of approach which uses
query_id matching for determining which of the derived tables belong
to this substatement we rely on the ability of substatements to
save/restore thd->derived_tables during their execution.
TODO: Probably even better approach is to simply associate list of
derived tables with (sub-)statement instead of thread and destroy
them at the end of its execution.
*/
if (thd->derived_tables && !skip_derived)
{
TABLE *table, *next;
@ -394,10 +435,50 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived,
}
thd->derived_tables= 0;
}
if (thd->locked_tables)
if (prelocked_mode)
{
ha_commit_stmt(thd); // If select statement
DBUG_VOID_RETURN; // LOCK TABLES in use
/*
Mark all temporary tables used by this substatement as free for reuse.
*/
mark_used_tables_as_free_for_reuse(thd, thd->temporary_tables);
}
if (thd->locked_tables || prelocked_mode)
{
/*
TODO: It is not 100% clear whenever we should do ha_commit_stmt() for
sub-statements. This issue needs additional investigation.
*/
ha_commit_stmt(thd);
/* We are under simple LOCK TABLES so should not do anything else. */
if (!prelocked_mode)
DBUG_VOID_RETURN;
if (!thd->lex->requires_prelocking())
{
/*
If we are executing one of substatements we have to mark
all tables which it used as free for reuse.
*/
mark_used_tables_as_free_for_reuse(thd, thd->open_tables);
DBUG_VOID_RETURN;
}
DBUG_ASSERT(prelocked_mode);
/*
We are in prelocked mode, so we have to leave it now with doing
implicit UNLOCK TABLES if need.
*/
thd->prelocked_mode= NON_PRELOCKED;
if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES)
DBUG_VOID_RETURN;
thd->lock= thd->locked_tables;
thd->locked_tables= 0;
/* Fallthrough */
}
if (thd->lock)
@ -429,6 +510,17 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived,
if (!lock_in_use)
VOID(pthread_mutex_unlock(&LOCK_open));
/* VOID(pthread_sigmask(SIG_SETMASK,&thd->signals,NULL)); */
if (prelocked_mode == PRELOCKED)
{
/*
If we are here then we are leaving normal prelocked mode, so it is
good idea to turn off OPTION_TABLE_LOCK flag.
*/
DBUG_ASSERT(thd->lex->requires_prelocking());
thd->options&= ~(ulong) (OPTION_TABLE_LOCK);
}
DBUG_VOID_RETURN;
}
@ -898,7 +990,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
!memcmp(table->s->table_cache_key, key,
key_length + TMP_TABLE_KEY_EXTRA))
{
if (table->query_id == thd->query_id)
if (table->query_id == thd->query_id ||
thd->prelocked_mode && table->query_id)
{
my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
DBUG_RETURN(0);
@ -912,16 +1005,17 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
}
}
if (thd->locked_tables)
if (thd->locked_tables || thd->prelocked_mode)
{ // Using table locks
for (table=thd->open_tables; table ; table=table->next)
{
if (table->s->key_length == key_length &&
!memcmp(table->s->table_cache_key,key,key_length) &&
!my_strcasecmp(system_charset_info, table->alias, alias))
!memcmp(table->s->table_cache_key, key, key_length) &&
!my_strcasecmp(system_charset_info, table->alias, alias) &&
table->query_id != thd->query_id && /* skip tables already used by this query */
!(thd->prelocked_mode && table->query_id))
{
if (table->query_id != thd->query_id)
table->query_id=thd->query_id;
table->query_id= thd->query_id;
DBUG_PRINT("info",("Using locked table"));
goto reset;
}
@ -1613,21 +1707,34 @@ err:
SYNOPSIS
open_tables()
thd - thread handler
start - list of tables
start - list of tables in/out
counter - number of opened tables will be return using this parameter
NOTE
Unless we are already in prelocked mode, this function will also precache
all SP/SFs explicitly or implicitly (via views and triggers) used by the
query and add tables needed for their execution to table list. If resulting
tables list will be non empty it will mark query as requiring precaching.
Prelocked mode will be enabled for such query during lock_tables() call.
If query for which we are opening tables is already marked as requiring
prelocking it won't do such precaching and will simply reuse table list
which is already built.
RETURN
0 - OK
-1 - error
*/
int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
int open_tables(THD *thd, TABLE_LIST **start, uint *counter)
{
TABLE_LIST *tables;
bool refresh;
int result=0;
DBUG_ENTER("open_tables");
MEM_ROOT new_frm_mem;
/* Also used for indicating that prelocking is need */
TABLE_LIST **query_tables_last_own;
DBUG_ENTER("open_tables");
/*
temporary mem_root for new .frm parsing.
TODO: variables for size
@ -1637,8 +1744,51 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
thd->current_tablenr= 0;
restart:
*counter= 0;
query_tables_last_own= 0;
thd->proc_info="Opening tables";
for (tables= start; tables ;tables= tables->next_global)
/*
If we are not already executing prelocked statement and don't have
statement for which table list for prelocking is already built, let
us cache routines and try to build such table list.
NOTE: If we want queries with functions to work under explicit
LOCK TABLES we have to additionaly lock mysql.proc table in it.
At least until Monty will fix SP loading :)
NOTE: We can't delay prelocking until we will met some sub-statement
which really uses tables, since this will imply that we have to restore
its table list to be able execute it in some other context.
And current views implementation assumes that view tables are added to
global table list only once during PS preparing/first SP execution.
Also locking at earlier stage is probably faster altough may decrease
concurrency a bit.
NOTE: We will mark statement as requiring prelocking only if we will
have non empty table list. But this does not guarantee that in prelocked
mode we will have some locked tables, because queries which use only
derived/information schema tables and views possible. Thus "counter"
may be still zero for prelocked statement...
*/
if (!thd->prelocked_mode && !thd->lex->requires_prelocking() &&
(thd->lex->spfuns.records || thd->lex->spprocs.records))
{
TABLE_LIST **save_query_tables_last;
sp_cache_routines(thd, thd->lex);
save_query_tables_last= thd->lex->query_tables_last;
DBUG_ASSERT(thd->lex->query_tables == *start);
if (sp_add_sp_tables_to_table_list(thd, thd->lex, thd->lex) ||
*start)
{
query_tables_last_own= save_query_tables_last;
*start= thd->lex->query_tables;
}
}
for (tables= *start; tables ;tables= tables->next_global)
{
/*
Ignore placeholders for derived tables. After derived tables
@ -1659,8 +1809,27 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC));
if (tables->view)
{
/* VIEW placeholder */
(*counter)--;
continue; //VIEW placeholder
/*
Again if needed we have to get cache all routines used by this view
and add tables used by them to table list.
*/
if (!thd->prelocked_mode && !thd->lex->requires_prelocking() &&
(tables->view->spfuns.records || tables->view->spprocs.records))
{
// FIXME We should catch recursion for both views and funcs here
sp_cache_routines(thd, tables->view);
/* We have at least one table in TL here */
if (!query_tables_last_own)
query_tables_last_own= thd->lex->query_tables_last;
sp_add_sp_tables_to_table_list(thd, thd->lex, tables->view);
}
/* Cleanup hashes because destructo for this LEX is never called */
hash_free(&tables->view->spfuns);
hash_free(&tables->view->spprocs);
continue;
}
if (refresh) // Refresh in progress
@ -1672,7 +1841,12 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
thd->version=refresh_version;
TABLE **prev_table= &thd->open_tables;
bool found=0;
for (TABLE_LIST *tmp= start; tmp; tmp= tmp->next_global)
/*
QQ: What we should do if we have started building of table list
for prelocking ??? Probably throw it away ? But before we should
mark all temporary tables as free? How about locked ?
*/
for (TABLE_LIST *tmp= *start; tmp; tmp= tmp->next_global)
{
/* Close normal (not temporary) changed tables */
if (tmp->table && ! tmp->table->s->tmp_table)
@ -1701,7 +1875,27 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
break;
}
else
{
/*
If we are not already in prelocked mode and extended table list is not
yet built and we have trigger for table being opened then we should
cache all routines used by its triggers and add their tables to
prelocking list.
If we lock table for reading we won't update it so there is no need to
process its triggers since they never will be activated.
FIXME Now we are simply turning on prelocking. Proper integration
and testing is to be done later.
*/
if (!thd->prelocked_mode && !thd->lex->requires_prelocking() &&
tables->table->triggers &&
tables->lock_type >= TL_WRITE_ALLOW_WRITE)
{
if (!query_tables_last_own)
query_tables_last_own= thd->lex->query_tables_last;
}
free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC));
}
if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables)
tables->table->reginfo.lock_type=tables->lock_type;
@ -1709,6 +1903,10 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
}
thd->proc_info=0;
free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block
if (query_tables_last_own)
thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
DBUG_RETURN(result);
}
@ -1757,6 +1955,11 @@ static bool check_lock_and_start_stmt(THD *thd, TABLE *table,
table_list Table to open is first table in this list
lock_type Lock to use for open
NOTE
This function don't do anything like SP/SF/views/triggers analysis done
in open_tables(). It is intended for opening of only one concrete table.
And used only in special contexts.
RETURN VALUES
table Opened table
0 Error
@ -1831,7 +2034,7 @@ int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
{
DBUG_ENTER("simple_open_n_lock_tables");
uint counter;
if (open_tables(thd, tables, &counter) || lock_tables(thd, tables, counter))
if (open_tables(thd, &tables, &counter) || lock_tables(thd, tables, counter))
DBUG_RETURN(-1); /* purecov: inspected */
DBUG_RETURN(0);
}
@ -1858,7 +2061,7 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables)
{
uint counter;
DBUG_ENTER("open_and_lock_tables");
if (open_tables(thd, tables, &counter) ||
if (open_tables(thd, &tables, &counter) ||
lock_tables(thd, tables, counter) ||
mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
(thd->fill_derived_tables() &&
@ -1891,7 +2094,7 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables)
uint counter;
DBUG_ENTER("open_normal_and_derived_tables");
DBUG_ASSERT(!thd->fill_derived_tables());
if (open_tables(thd, tables, &counter) ||
if (open_tables(thd, &tables, &counter) ||
mysql_handle_derived(thd->lex, &mysql_derived_prepare))
DBUG_RETURN(TRUE); /* purecov: inspected */
relink_tables_for_multidelete(thd); // Not really needed, but
@ -1924,6 +2127,27 @@ static void relink_tables_for_multidelete(THD *thd)
}
/*
Mark all real tables in the list as free for reuse.
SYNOPSIS
mark_real_tables_as_free_for_reuse()
thd - thread context
table - head of the list of tables
DESCRIPTION
Marks all real tables in the list (i.e. not views, derived
or schema tables) as free for reuse.
*/
static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table)
{
for (; table; table= table->next_global)
if (!table->placeholder() && !table->schema_table)
table->table->query_id= 0;
}
/*
Lock all tables in list
@ -1938,6 +2162,10 @@ static void relink_tables_for_multidelete(THD *thd)
handling thr_lock gives us. You most always get all needed locks at
once.
If query for which we are calling this function marked as requring
prelocking, this function will do implicit LOCK TABLES and change
thd::prelocked_mode accordingly.
RETURN VALUES
0 ok
-1 Error
@ -1946,36 +2174,125 @@ static void relink_tables_for_multidelete(THD *thd)
int lock_tables(THD *thd, TABLE_LIST *tables, uint count)
{
TABLE_LIST *table;
if (!tables)
return 0;
if (!thd->locked_tables)
DBUG_ENTER("lock_tables");
/*
We can't meet statement requiring prelocking if we already
in prelocked mode.
*/
DBUG_ASSERT(!thd->prelocked_mode || !thd->lex->requires_prelocking());
/*
If statement requires prelocking then it has non-empty table list.
So it is safe to shortcut.
*/
DBUG_ASSERT(!thd->lex->requires_prelocking() || tables);
if (!tables)
DBUG_RETURN(0);
/*
We need this extra check for thd->prelocked_mode because we want to avoid
attempts to lock tables in substatements. Checking for thd->locked_tables
is not enough in some situations. For example for SP containing
"drop table t3; create temporary t3 ..; insert into t3 ...;"
thd->locked_tables may be 0 after drop tables, and without this extra
check insert will try to lock temporary table t3, that will lead
to memory leak...
*/
if (!thd->locked_tables && !thd->prelocked_mode)
{
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
TABLE **start,**ptr;
if (!(ptr=start=(TABLE**) thd->alloc(sizeof(TABLE*)*count)))
return -1;
DBUG_RETURN(-1);
for (table= tables; table; table= table->next_global)
{
if (!table->placeholder() && !table->schema_table)
*(ptr++)= table->table;
}
/* We have to emulate LOCK TABLES if we are statement needs prelocking. */
if (thd->lex->requires_prelocking())
{
thd->in_lock_tables=1;
thd->options|= OPTION_TABLE_LOCK;
}
if (!(thd->lock=mysql_lock_tables(thd,start, (uint) (ptr - start))))
return -1; /* purecov: inspected */
{
if (thd->lex->requires_prelocking())
{
thd->options&= ~(ulong) (OPTION_TABLE_LOCK);
thd->in_lock_tables=0;
}
DBUG_RETURN(-1);
}
if (thd->lex->requires_prelocking() &&
thd->lex->sql_command != SQLCOM_LOCK_TABLES)
{
TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
/*
We just have done implicit LOCK TABLES, and now we have
to emulate first open_and_lock_tables() after it.
Note that "LOCK TABLES" can also be marked as requiring prelocking
(e.g. if one locks view which uses functions). We should not emulate
such open_and_lock_tables() in this case. We also should not set
THD::prelocked_mode or first close_thread_tables() call will do
"UNLOCK TABLES".
*/
thd->locked_tables= thd->lock;
thd->lock= 0;
thd->in_lock_tables=0;
for (table= tables; table != first_not_own; table= table->next_global)
{
if (!table->placeholder() && !table->schema_table)
{
table->table->query_id= thd->query_id;
if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
{
ha_rollback_stmt(thd);
mysql_unlock_tables(thd, thd->locked_tables);
thd->locked_tables= 0;
thd->options&= ~(ulong) (OPTION_TABLE_LOCK);
DBUG_RETURN(-1);
}
}
}
/*
Let us mark all tables which don't belong to the statement itself,
and was marked as occupied during open_tables() as free for reuse.
*/
mark_real_tables_as_free_for_reuse(first_not_own);
thd->prelocked_mode= PRELOCKED;
}
}
else
{
for (table= tables; table; table= table->next_global)
TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
for (table= tables; table != first_not_own; table= table->next_global)
{
if (!table->placeholder() &&
if (!table->placeholder() && !table->schema_table &&
check_lock_and_start_stmt(thd, table->table, table->lock_type))
{
ha_rollback_stmt(thd);
return -1;
DBUG_RETURN(-1);
}
}
/*
If we are under explicit LOCK TABLES and our statement requires
prelocking, we should mark all "additional" tables as free for use
and enter prelocked mode.
*/
if (thd->lex->requires_prelocking())
{
mark_real_tables_as_free_for_reuse(first_not_own);
thd->prelocked_mode= PRELOCKED_UNDER_LOCK_TABLES;
}
}
return 0;
DBUG_RETURN(0);
}

View File

@ -272,6 +272,7 @@ THD::THD()
ulong tmp=sql_rnd_with_mutex();
randominit(&rand, tmp + (ulong) &rand, tmp + (ulong) ::query_id);
}
prelocked_mode= NON_PRELOCKED;
}

View File

@ -777,6 +777,15 @@ struct Item_change_record;
typedef I_List<Item_change_record> Item_change_list;
/*
Type of prelocked mode.
See comment for THD::prelocked_mode for complete description.
*/
enum prelocked_mode_type {NON_PRELOCKED= 0, PRELOCKED= 1,
PRELOCKED_UNDER_LOCK_TABLES= 2};
/*
For each client connection we create a separate thread with THD serving as
a thread/connection descriptor
@ -877,7 +886,13 @@ public:
See also lock_tables() for details.
*/
MYSQL_LOCK *lock; /* Current locks */
MYSQL_LOCK *locked_tables; /* Tables locked with LOCK */
/*
Tables that were locked with explicit or implicit LOCK TABLES.
(Implicit LOCK TABLES happens when we are prelocking tables for
execution of statement which uses stored routines. See description
THD::prelocked_mode for more info.)
*/
MYSQL_LOCK *locked_tables;
HASH handler_tables_hash;
/*
One thread can hold up to one named user-level lock. This variable
@ -1032,8 +1047,6 @@ public:
sp_rcontext *spcont; // SP runtime context
sp_cache *sp_proc_cache;
sp_cache *sp_func_cache;
bool shortcut_make_view; /* Don't do full mysql_make_view()
during pre-opening of tables. */
/*
If we do a purge of binary logs, log index info of the threads
@ -1049,6 +1062,31 @@ public:
long long_value;
} sys_var_tmp;
/*
prelocked_mode_type enum and prelocked_mode member are used for
indicating whenever "prelocked mode" is on, and what type of
"prelocked mode" is it.
Prelocked mode is used for execution of queries which explicitly
or implicitly (via views or triggers) use functions, thus may need
some additional tables (mentioned in query table list) for their
execution.
First open_tables() call for such query will analyse all functions
used by it and add all additional tables to table its list. It will
also mark this query as requiring prelocking. After that lock_tables()
will issue implicit LOCK TABLES for the whole table list and change
thd::prelocked_mode to non-0. All queries called in functions invoked
by the main query will use prelocked tables. Non-0 prelocked_mode
will also surpress mentioned analysys in those queries thus saving
cycles. Prelocked mode will be turned off once close_thread_tables()
for the main query will be called.
Note: Since not all "tables" present in table list are really locked
thd::relocked_mode does not imply thd::locked_tables.
*/
prelocked_mode_type prelocked_mode;
THD();
~THD();

View File

@ -187,7 +187,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
/* for now HANDLER can be used only for real TABLES */
tables->required_type= FRMTYPE_TABLE;
error= open_tables(thd, tables, &counter);
error= open_tables(thd, &tables, &counter);
HANDLER_TABLES_HACK(thd);
if (error)

View File

@ -169,13 +169,12 @@ void lex_start(THD *thd, uchar *buf,uint length)
lex->sphead= NULL;
lex->spcont= NULL;
lex->proc_list.first= 0;
lex->query_tables_own_last= 0;
if (lex->spfuns.records)
my_hash_reset(&lex->spfuns);
if (lex->spprocs.records)
my_hash_reset(&lex->spprocs);
if (lex->sptabs.records)
my_hash_reset(&lex->sptabs);
DBUG_VOID_RETURN;
}
@ -1843,6 +1842,8 @@ TABLE_LIST *st_lex::unlink_first_table(bool *link_to_local)
*/
if ((query_tables= query_tables->next_global))
query_tables->prev_global= &query_tables;
else
query_tables_last= &query_tables;
first->next_global= 0;
/*
@ -1948,6 +1949,8 @@ void st_lex::link_first_table_back(TABLE_LIST *first,
{
if ((first->next_global= query_tables))
query_tables->prev_global= &first->next_global;
else
query_tables_last= &first->next_global;
query_tables= first;
if (link_to_local)

View File

@ -749,7 +749,6 @@ typedef struct st_lex
sp_pcontext *spcont;
HASH spfuns; /* Called functions */
HASH spprocs; /* Called procedures */
HASH sptabs; /* Merged table lists */
st_sp_chistics sp_chistics;
bool only_view; /* used for SHOW CREATE TABLE/VIEW */
/*
@ -768,23 +767,25 @@ typedef struct st_lex
*/
SQL_LIST trg_table_fields;
st_lex() :result(0), sql_command(SQLCOM_END)
/*
If non-0 then indicates that query requires prelocking and points to
next_global member of last own element in query table list (i.e. last
table which was not added to it as part of preparation to prelocking).
0 - indicates that this query does not need prelocking.
*/
TABLE_LIST **query_tables_own_last;
st_lex() :result(0), sql_command(SQLCOM_END), query_tables_own_last(0)
{
extern byte *sp_lex_sp_key(const byte *ptr, uint *plen, my_bool first);
extern byte *sp_table_key(const byte *ptr, uint *plen, my_bool first);
hash_init(&spfuns, system_charset_info, 0, 0, 0, sp_lex_sp_key, 0, 0);
hash_init(&spprocs, system_charset_info, 0, 0, 0, sp_lex_sp_key, 0, 0);
hash_init(&sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0);
}
~st_lex()
{
if (spfuns.array.buffer)
hash_free(&spfuns);
if (spprocs.array.buffer)
hash_free(&spprocs);
if (sptabs.array.buffer)
hash_free(&sptabs);
hash_free(&spfuns);
hash_free(&spprocs);
}
inline void uncacheable(uint8 cause)
@ -821,6 +822,21 @@ typedef struct st_lex
bool can_not_use_merged();
bool only_view_structure();
bool need_correct_ident();
inline bool requires_prelocking()
{
return query_tables_own_last;
}
inline void mark_as_requiring_prelocking(TABLE_LIST **tables_own_last)
{
query_tables_own_last= tables_own_last;
}
/* Return pointer to first not-own table in query-tables or 0 */
TABLE_LIST* first_not_own_table()
{
return ( query_tables_own_last ? *query_tables_own_last : 0);
}
} LEX;
struct st_lex_local: public st_lex

View File

@ -1682,7 +1682,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
in embedded server - just store them to be executed later
*/
#ifndef EMBEDDED_LIBRARY
if (thd->lock || thd->open_tables || thd->derived_tables)
if (thd->lock || thd->open_tables || thd->derived_tables ||
thd->prelocked_mode)
close_thread_tables(thd);
#endif
ulong length= thd->query_length-(ulong)(packet-thd->query);
@ -2003,7 +2004,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
break;
}
if (thd->lock || thd->open_tables || thd->derived_tables)
if (thd->lock || thd->open_tables || thd->derived_tables ||
thd->prelocked_mode)
{
thd->proc_info="closing tables";
close_thread_tables(thd); /* Free tables */
@ -2231,12 +2233,7 @@ mysql_execute_command(THD *thd)
TABLE_LIST *all_tables;
/* most outer SELECT_LEX_UNIT of query */
SELECT_LEX_UNIT *unit= &lex->unit;
/* Locked closure of all tables */
TABLE_LIST *locked_tables= NULL;
/* Saved variable value */
#ifdef HAVE_INNOBASE_DB
my_bool old_innodb_table_locks= thd->variables.innodb_table_locks;
#endif
DBUG_ENTER("mysql_execute_command");
/*
@ -2258,100 +2255,14 @@ mysql_execute_command(THD *thd)
/* should be assigned after making first tables same */
all_tables= lex->query_tables;
thd->shortcut_make_view= 0;
if (lex->sql_command != SQLCOM_CREATE_PROCEDURE &&
lex->sql_command != SQLCOM_CREATE_SPFUNCTION &&
lex->sql_command != SQLCOM_LOCK_TABLES &&
lex->sql_command != SQLCOM_UNLOCK_TABLES)
{
while (1)
{
if (sp_cache_routines(thd, lex, TYPE_ENUM_FUNCTION))
DBUG_RETURN(-1);
if (sp_cache_routines(thd, lex, TYPE_ENUM_PROCEDURE))
DBUG_RETURN(-1);
if (!thd->locked_tables &&
lex->sql_command != SQLCOM_CREATE_TABLE &&
lex->sql_command != SQLCOM_CREATE_VIEW)
{
MEM_ROOT *thdmemroot= NULL;
sp_merge_routine_tables(thd, lex);
// QQ Preopen tables to find views and triggers.
// This means we open, close and open again, which sucks, but
// right now it's the easiest way to get it to work. A better
// solution will hopefully be found soon...
if (lex->sptabs.records || lex->query_tables)
{
uint procs, funs, tabs;
if (thd->mem_root != thd->current_arena->mem_root)
{
thdmemroot= thd->mem_root;
thd->mem_root= thd->current_arena->mem_root;
}
if (!sp_merge_table_list(thd, &lex->sptabs, lex->query_tables))
DBUG_RETURN(-1);
procs= lex->spprocs.records;
funs= lex->spfuns.records;
tabs= lex->sptabs.records;
if ((locked_tables= sp_hash_to_table_list(thd, &lex->sptabs)))
{
// We don't want these updated now
uint ctmpdtabs= thd->status_var.created_tmp_disk_tables;
uint ctmptabs= thd->status_var.created_tmp_tables;
uint count;
thd->shortcut_make_view= TRUE;
open_tables(thd, locked_tables, &count);
thd->shortcut_make_view= FALSE;
close_thread_tables(thd);
thd->status_var.created_tmp_disk_tables= ctmpdtabs;
thd->status_var.created_tmp_tables= ctmptabs;
thd->clear_error();
mysql_reset_errors(thd);
locked_tables= NULL;
}
// A kludge: Decrease all temp. table's query ids to allow a
// second opening.
for (TABLE *table= thd->temporary_tables; table ; table=table->next)
table->query_id-= 1;
if (procs < lex->spprocs.records ||
funs < lex->spfuns.records ||
tabs < lex->sptabs.records)
{
if (thdmemroot)
thd->mem_root= thdmemroot;
continue; // Found more SPs or tabs, try again
}
}
if (lex->sptabs.records &&
(lex->spfuns.records || lex->spprocs.records) &&
sp_merge_table_list(thd, &lex->sptabs, lex->query_tables))
{
if ((locked_tables= sp_hash_to_table_list(thd, &lex->sptabs)))
{
#ifdef HAVE_INNOBASE_DB
thd->variables.innodb_table_locks= FALSE;
#endif
sp_open_and_lock_tables(thd, locked_tables);
}
}
if (thdmemroot)
thd->mem_root= thdmemroot;
}
break;
} // while (1)
}
/*
Reset warning count for each query that uses tables
A better approach would be to reset this for any commands
that is not a SHOW command or a select that only access local
variables, but for now this is probably good enough.
*/
if (all_tables || &lex->select_lex != lex->all_selects_list)
if (all_tables || &lex->select_lex != lex->all_selects_list ||
lex->spfuns.records || lex->spprocs.records)
mysql_reset_errors(thd);
#ifdef HAVE_REPLICATION
@ -2580,9 +2491,8 @@ mysql_execute_command(THD *thd)
break;
}
case SQLCOM_DO:
if (all_tables &&
(check_table_access(thd, SELECT_ACL, all_tables, 0) ||
open_and_lock_tables(thd, all_tables)))
if (check_table_access(thd, SELECT_ACL, all_tables, 0) ||
open_and_lock_tables(thd, all_tables))
goto error;
res= mysql_do(thd, *lex->insert_list);
@ -3454,8 +3364,7 @@ unsent_create_error:
case SQLCOM_SET_OPTION:
{
List<set_var_base> *lex_var_list= &lex->var_list;
if (all_tables &&
(check_table_access(thd, SELECT_ACL, all_tables, 0) ||
if ((check_table_access(thd, SELECT_ACL, all_tables, 0) ||
open_and_lock_tables(thd, all_tables)))
goto error;
if (lex->one_shot_set && not_all_support_one_shot(lex_var_list))
@ -3501,7 +3410,7 @@ unsent_create_error:
thd->in_lock_tables=1;
thd->options|= OPTION_TABLE_LOCK;
if (!(res= open_and_lock_tables(thd, all_tables)))
if (!(res= simple_open_n_lock_tables(thd, all_tables)))
{
#ifdef HAVE_QUERY_CACHE
if (thd->variables.query_cache_wlock_invalidate)
@ -4004,7 +3913,20 @@ unsent_create_error:
{
sp_head *sp;
if (!(sp= sp_find_procedure(thd, lex->spname)))
/*
This will cache all SP and SF and open and lock all tables
required for execution.
*/
if (check_table_access(thd, SELECT_ACL, all_tables, 0) ||
open_and_lock_tables(thd, all_tables))
goto error;
/*
By this moment all needed SPs should be in cache so no need
to look into DB. Moreover we may be unable to do it becuase
we may don't have read lock on mysql.proc
*/
if (!(sp= sp_find_procedure(thd, lex->spname, TRUE)))
{
my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "PROCEDURE",
lex->spname->m_qname.str);
@ -4019,12 +3941,6 @@ unsent_create_error:
/* bits that should be cleared in thd->server_status */
uint bits_to_be_cleared= 0;
/* In case the arguments are subselects... */
if (all_tables &&
(check_table_access(thd, SELECT_ACL, all_tables, 0) ||
open_and_lock_tables(thd, all_tables)))
goto error;
#ifndef EMBEDDED_LIBRARY
my_bool nsok= thd->net.no_send_ok;
thd->net.no_send_ok= TRUE;
@ -4348,14 +4264,6 @@ cleanup:
thd->lock= 0;
}
if (locked_tables)
{
#ifdef HAVE_INNOBASE_DB
thd->variables.innodb_table_locks= old_innodb_table_locks;
#endif
if (thd->locked_tables)
sp_unlock_tables(thd);
}
DBUG_RETURN(res || thd->net.report_error);
}

View File

@ -1004,7 +1004,7 @@ static int mysql_test_update(Prepared_statement *stmt,
if (update_precheck(thd, table_list))
DBUG_RETURN(1);
if (!open_tables(thd, table_list, &table_count))
if (!open_tables(thd, &table_list, &table_count))
{
if (table_list->ancestor && table_list->ancestor->next_local)
{
@ -1545,22 +1545,6 @@ static int check_prepared_statement(Prepared_statement *stmt,
lex->first_lists_tables_same();
tables= lex->query_tables;
/*
Preopen 'proc' system table and cache all functions used in this
statement. We must do that before we open ordinary tables to avoid
deadlocks. We can't open and lock any table once query tables were
opened.
*/
if (lex->sql_command != SQLCOM_CREATE_PROCEDURE &&
lex->sql_command != SQLCOM_CREATE_SPFUNCTION)
{
/* The error is printed inside */
if (sp_cache_routines(thd, lex, TYPE_ENUM_FUNCTION))
DBUG_RETURN(-1);
if (sp_cache_routines(thd, lex, TYPE_ENUM_PROCEDURE))
DBUG_RETURN(-1);
}
switch (sql_command) {
case SQLCOM_REPLACE:
case SQLCOM_INSERT:
@ -1794,9 +1778,9 @@ bool mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
thd->lex->sphead= NULL;
}
lex_end(lex);
close_thread_tables(thd);
thd->restore_backup_statement(stmt, &thd->stmt_backup);
cleanup_items(stmt->free_list);
close_thread_tables(thd);
thd->rollback_item_tree_changes();
thd->cleanup_after_query();
thd->current_arena= thd;
@ -1873,6 +1857,11 @@ void reset_stmt_for_execute(THD *thd, LEX *lex)
to indicate the table is altered, and re-do the setup_*
and open the tables back.
*/
/*
NOTE: We should reset whole table list here including all tables added
by prelocking algorithm (it is not a problem for substatements since
they have their own table list).
*/
for (TABLE_LIST *tables= lex->query_tables;
tables;
tables= tables->next_global)

View File

@ -42,14 +42,21 @@ public:
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.
#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
/*
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);
#ifndef EMBEDDED_LIBRARY
thd->net.no_send_ok= nsok;
#endif
}
return res;

View File

@ -136,7 +136,7 @@ int mysql_update(THD *thd,
LINT_INIT(timestamp_query_id);
if (open_tables(thd, table_list, &table_count))
if (open_tables(thd, &table_list, &table_count))
DBUG_RETURN(1);
if (table_list->ancestor && table_list->ancestor->next_local)
@ -637,7 +637,7 @@ bool mysql_multi_update_prepare(THD *thd)
thd->lex->sql_command= SQLCOM_UPDATE_MULTI;
/* open tables and create derived ones, but do not lock and fill them */
if ((original_multiupdate && open_tables(thd, table_list, & table_count)) ||
if ((original_multiupdate && open_tables(thd, &table_list, & table_count)) ||
mysql_handle_derived(lex, &mysql_derived_prepare))
DBUG_RETURN(TRUE);
/*

View File

@ -19,6 +19,7 @@
#include "sql_select.h"
#include "parse_file.h"
#include "sp.h"
#include "sp_head.h"
#define MD5_BUFF_LENGTH 33
@ -613,10 +614,7 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table)
table->view= lex= thd->lex= (LEX*) new(thd->mem_root) st_lex_local;
lex_start(thd, (uchar*)table->query.str, table->query.length);
view_select= &lex->select_lex;
/* Only if we're not in the pre-open phase */
if (!thd->shortcut_make_view)
view_select->select_number= ++thd->select_number;
old_lex->derived_tables|= DERIVED_VIEW;
view_select->select_number= ++thd->select_number;
{
ulong options= thd->options;
/* switch off modes which can prevent normal parsing of VIEW
@ -660,35 +658,13 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table)
TABLE_LIST *view_tables_tail= 0;
TABLE_LIST *tbl;
/* move SP to main LEX */
if (lex->spfuns.records)
sp_merge_hash(&old_lex->spfuns, &lex->spfuns);
/* cleanup LEX */
if (lex->spfuns.array.buffer)
hash_free(&lex->spfuns);
if (lex->spprocs.array.buffer)
hash_free(&lex->spprocs);
if (lex->sptabs.array.buffer)
hash_free(&lex->sptabs);
/* If we're pre-opening tables to find SPs and tables we need
not go any further; doing so will cause an infinite loop. */
if (thd->shortcut_make_view)
{
extern bool
sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table,
LEX *lex_for_tmp_check = 0);
sp_merge_table_list(thd, &old_lex->sptabs, view_tables);
goto ok;
}
/*
check rights to run commands (EXPLAIN SELECT & SHOW CREATE) which show
underlying tables
Check rights to run commands (EXPLAIN SELECT & SHOW CREATE) which show
underlying tables.
Skip this step if we are opening view for prelocking only.
*/
if ((old_lex->sql_command == SQLCOM_SELECT && old_lex->describe))
if (!table->prelocking_placeholder &&
(old_lex->sql_command == SQLCOM_SELECT && old_lex->describe))
{
if (check_table_access(thd, SELECT_ACL, view_tables, 1) &&
check_table_access(thd, SHOW_VIEW_ACL, table, 1))
@ -697,7 +673,8 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table)
goto err;
}
}
else if (old_lex->sql_command == SQLCOM_SHOW_CREATE)
else if (!table->prelocking_placeholder &&
old_lex->sql_command == SQLCOM_SHOW_CREATE)
{
if (check_table_access(thd, SHOW_VIEW_ACL, table, 0))
goto err;
@ -715,13 +692,6 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table)
tbl->belong_to_view= top_view;
}
/* move SQL_NO_CACHE & Co to whole query */
old_lex->safe_to_cache_query= (old_lex->safe_to_cache_query &&
lex->safe_to_cache_query);
/* move SQL_CACHE to whole query */
if (view_select->options & OPTION_TO_QUERY_CACHE)
old_lex->select_lex.options|= OPTION_TO_QUERY_CACHE;
/*
Put tables of VIEW after VIEW TABLE_LIST
@ -738,12 +708,29 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table)
}
else
{
lex->query_tables_last= &view_tables_tail->next_global;
old_lex->query_tables_last= &view_tables_tail->next_global;
}
view_tables->prev_global= &table->next_global;
table->next_global= view_tables;
}
/*
If we are opening this view as part of implicit LOCK TABLES, then
this view serves as simple placeholder and we should not continue
further processing.
*/
if (table->prelocking_placeholder)
goto ok2;
old_lex->derived_tables|= DERIVED_VIEW;
/* move SQL_NO_CACHE & Co to whole query */
old_lex->safe_to_cache_query= (old_lex->safe_to_cache_query &&
lex->safe_to_cache_query);
/* move SQL_CACHE to whole query */
if (view_select->options & OPTION_TO_QUERY_CACHE)
old_lex->select_lex.options|= OPTION_TO_QUERY_CACHE;
/*
check MERGE algorithm ability
- algorithm is not explicit TEMPORARY TABLE
@ -848,8 +835,6 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table)
goto err;
ok:
if (arena)
thd->restore_backup_item_arena(arena, &backup);
/* global SELECT list linking */
end= view_select; // primary SELECT_LEX is always last
end->link_next= old_lex->all_selects_list;
@ -858,6 +843,9 @@ ok:
lex->all_selects_list->link_prev=
(st_select_lex_node**)&old_lex->all_selects_list;
ok2:
if (arena)
thd->restore_backup_item_arena(arena, &backup);
thd->lex= old_lex;
DBUG_RETURN(0);

View File

@ -1548,12 +1548,12 @@ sp_opt_inout:
sp_proc_stmts:
/* Empty */ {}
| sp_proc_stmts { Lex->query_tables= 0; } sp_proc_stmt ';'
| sp_proc_stmts sp_proc_stmt ';'
;
sp_proc_stmts1:
sp_proc_stmt ';' {}
| sp_proc_stmts1 { Lex->query_tables= 0; } sp_proc_stmt ';'
| sp_proc_stmts1 sp_proc_stmt ';'
;
sp_decls:
@ -1587,13 +1587,15 @@ sp_decls:
;
sp_decl:
DECLARE_SYM sp_decl_idents type sp_opt_default
DECLARE_SYM sp_decl_idents type
{ Lex->sphead->reset_lex(YYTHD); }
sp_opt_default
{
LEX *lex= Lex;
sp_pcontext *ctx= lex->spcont;
uint max= ctx->context_pvars();
enum enum_field_types type= (enum enum_field_types)$3;
Item *it= $4;
Item *it= $5;
for (uint i = max-$2 ; i < max ; i++)
{
@ -1605,15 +1607,19 @@ sp_decl:
sp_instr_set *in= new sp_instr_set(lex->sphead->instructions(),
ctx,
ctx->pvar_context2index(i),
it, type);
it, type, lex,
(i == max - 1));
in->tables= lex->query_tables;
lex->query_tables= 0;
/*
The last instruction is assigned to be responsible for
freeing LEX.
*/
lex->sphead->add_instr(in);
ctx->set_isset(i, TRUE);
ctx->set_default(i, it);
}
}
lex->sphead->restore_lex(YYTHD);
$$.vars= $2;
$$.conds= $$.hndlrs= $$.curs= 0;
}
@ -1863,15 +1869,17 @@ sp_proc_stmt:
my_message(ER_SP_NO_USE, ER(ER_SP_NO_USE), MYF(0));
YYABORT;
}
/* Don't add an instruction for empty SET statements.
** (This happens if the SET only contained local variables,
** which get their set instructions generated separately.)
/*
Don't add an instruction for SET statements, since all
instructions for them were already added during processing
of "set" rule.
*/
if (lex->sql_command != SQLCOM_SET_OPTION ||
! lex->var_list.is_empty())
DBUG_ASSERT(lex->sql_command != SQLCOM_SET_OPTION ||
lex->var_list.is_empty());
if (lex->sql_command != SQLCOM_SET_OPTION)
{
sp_instr_stmt *i=new sp_instr_stmt(sp->instructions(),
lex->spcont);
lex->spcont, lex);
/* Extract the query statement from the tokenizer:
The end is either lex->tok_end or tok->ptr. */
@ -1882,17 +1890,18 @@ sp_proc_stmt:
i->m_query.str= strmake_root(YYTHD->mem_root,
(char *)sp->m_tmp_query,
i->m_query.length);
i->set_lex(lex);
sp->add_instr(i);
lex->sp_lex_in_use= TRUE;
}
sp->restore_lex(YYTHD);
}
| RETURN_SYM expr
| RETURN_SYM
{ Lex->sphead->reset_lex(YYTHD); }
expr
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
if (lex->sphead->m_type == TYPE_ENUM_PROCEDURE)
if (sp->m_type == TYPE_ENUM_PROCEDURE)
{
my_message(ER_SP_BADRETURN, ER(ER_SP_BADRETURN), MYF(0));
YYABORT;
@ -1901,12 +1910,12 @@ sp_proc_stmt:
{
sp_instr_freturn *i;
i= new sp_instr_freturn(lex->sphead->instructions(),
lex->spcont,
$2, lex->sphead->m_returns);
lex->sphead->add_instr(i);
lex->sphead->m_has_return= TRUE;
i= new sp_instr_freturn(sp->instructions(), lex->spcont,
$3, sp->m_returns, lex);
sp->add_instr(i);
sp->m_has_return= TRUE;
}
sp->restore_lex(YYTHD);
}
| IF sp_if END IF {}
| CASE_SYM WHEN_SYM
@ -1914,7 +1923,9 @@ sp_proc_stmt:
Lex->sphead->m_simple_case= FALSE;
}
sp_case END CASE_SYM {}
| CASE_SYM expr WHEN_SYM
| CASE_SYM
{ Lex->sphead->reset_lex(YYTHD); }
expr WHEN_SYM
{
/* We "fake" this by using an anonymous variable which we
set to the expression. Note that all WHENs are evaluate
@ -1923,17 +1934,16 @@ sp_proc_stmt:
LEX *lex= Lex;
uint offset= lex->spcont->current_pvars();
sp_instr_set *i = new sp_instr_set(lex->sphead->instructions(),
lex->spcont,
offset, $2, MYSQL_TYPE_STRING);
lex->spcont, offset, $3,
MYSQL_TYPE_STRING, lex, TRUE);
LEX_STRING dummy;
dummy.str= (char *)"";
dummy.length= 0;
lex->spcont->push_pvar(&dummy, MYSQL_TYPE_STRING, sp_param_in);
i->tables= lex->query_tables;
lex->query_tables= 0;
lex->sphead->add_instr(i);
lex->sphead->m_simple_case= TRUE;
lex->sphead->restore_lex(YYTHD);
}
sp_case END CASE_SYM
{
@ -2187,18 +2197,19 @@ sp_fetch_list:
;
sp_if:
expr THEN_SYM
{ Lex->sphead->reset_lex(YYTHD); }
expr THEN_SYM
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
sp_pcontext *ctx= lex->spcont;
uint ip= sp->instructions();
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, ctx, $1);
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, ctx,
$2, lex);
i->tables= lex->query_tables;
lex->query_tables= 0;
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
sp->add_instr(i);
sp->restore_lex(YYTHD);
}
sp_proc_stmts1
{
@ -2226,7 +2237,8 @@ sp_elseifs:
;
sp_case:
expr THEN_SYM
{ Lex->sphead->reset_lex(YYTHD); }
expr THEN_SYM
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
@ -2235,7 +2247,7 @@ sp_case:
sp_instr_jump_if_not *i;
if (! sp->m_simple_case)
i= new sp_instr_jump_if_not(ip, ctx, $1);
i= new sp_instr_jump_if_not(ip, ctx, $2, lex);
else
{ /* Simple case: <caseval> = <whenval> */
LEX_STRING ivar;
@ -2244,15 +2256,14 @@ sp_case:
ivar.length= 5;
Item *var= (Item*) new Item_splocal(ivar,
ctx->current_pvars()-1);
Item *expr= new Item_func_eq(var, $1);
Item *expr= new Item_func_eq(var, $2);
i= new sp_instr_jump_if_not(ip, ctx, expr);
i= new sp_instr_jump_if_not(ip, ctx, expr, lex);
lex->variables_used= 1;
}
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
i->tables= lex->query_tables;
lex->query_tables= 0;
sp->add_instr(i);
sp->restore_lex(YYTHD);
}
sp_proc_stmts1
{
@ -2370,19 +2381,20 @@ sp_unlabeled_control:
lex->sphead->add_instr(i);
}
| WHILE_SYM expr DO_SYM
| WHILE_SYM
{ Lex->sphead->reset_lex(YYTHD); }
expr DO_SYM
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
uint ip= sp->instructions();
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont,
$2);
$3, lex);
/* Jumping forward */
sp->push_backpatch(i, lex->spcont->last_label());
i->tables= lex->query_tables;
lex->query_tables= 0;
sp->add_instr(i);
sp->restore_lex(YYTHD);
}
sp_proc_stmts1 END WHILE_SYM
{
@ -2393,17 +2405,18 @@ sp_unlabeled_control:
lex->sphead->add_instr(i);
}
| REPEAT_SYM sp_proc_stmts1 UNTIL_SYM expr END REPEAT_SYM
| REPEAT_SYM sp_proc_stmts1 UNTIL_SYM
{ Lex->sphead->reset_lex(YYTHD); }
expr END REPEAT_SYM
{
LEX *lex= Lex;
uint ip= lex->sphead->instructions();
sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont,
$4, lab->ip);
i->tables= lex->query_tables;
lex->query_tables= 0;
$5, lab->ip,
lex);
lex->sphead->add_instr(i);
lex->sphead->restore_lex(YYTHD);
}
;
@ -7140,8 +7153,75 @@ opt_option:
| OPTION {};
option_value_list:
option_type_value
| option_value_list ',' option_type_value;
option_type_value:
{
if (Lex->sphead)
{
/*
If we are in SP we want have own LEX for each assignment.
This is mostly because it is hard for several sp_instr_set
and sp_instr_set_trigger instructions share one LEX.
(Well, it is theoretically possible but adds some extra
overhead on preparation for execution stage and IMO less
robust).
QQ: May be we should simply prohibit group assignments in SP?
*/
LEX *lex;
Lex->sphead->reset_lex(YYTHD);
lex= Lex;
/* Set new LEX as if we at start of set rule. */
lex->sql_command= SQLCOM_SET_OPTION;
mysql_init_select(lex);
lex->option_type=OPT_SESSION;
lex->var_list.empty();
lex->one_shot_set= 0;
lex->sphead->m_tmp_query= lex->tok_start;
}
}
option_type option_value
| option_value_list ',' option_type option_value;
{
LEX *lex= Lex;
if (lex->sphead)
{
sp_head *sp= lex->sphead;
if (!lex->var_list.is_empty())
{
/*
We have assignment to user or system variable or
option setting, so we should construct sp_instr_stmt
for it.
*/
LEX_STRING qbuff;
sp_instr_stmt *i;
if (!(i= new sp_instr_stmt(sp->instructions(), lex->spcont,
lex)))
YYABORT;
if (lex->ptr - lex->tok_end > 1)
qbuff.length= lex->ptr - sp->m_tmp_query;
else
qbuff.length= lex->tok_end - sp->m_tmp_query;
if (!(qbuff.str= alloc_root(YYTHD->mem_root, qbuff.length + 5)))
YYABORT;
strmake(strmake(qbuff.str, "SET ", 4), (char *)sp->m_tmp_query,
qbuff.length);
qbuff.length+= 4;
i->m_query= qbuff;
sp->add_instr(i);
}
lex->sphead->restore_lex(YYTHD);
}
};
option_type:
/* empty */ {}
@ -7168,31 +7248,7 @@ opt_var_ident_type:
option_value:
'@' ident_or_text equal expr
{
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)
{
my_message(ER_SP_SUBSELECT_NYI, ER(ER_SP_SUBSELECT_NYI),
MYF(0));
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)));
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
{
@ -7253,9 +7309,7 @@ option_value:
else
it= new Item_null();
i= new sp_instr_set(lex->sphead->instructions(), ctx,
spv->offset, it, spv->type);
i->tables= lex->query_tables;
lex->query_tables= 0;
spv->offset, it, spv->type, lex, TRUE);
lex->sphead->add_instr(i);
spv->isset= TRUE;
}

View File

@ -420,6 +420,11 @@ typedef struct st_table_list
/* FRMTYPE_ERROR if any type is acceptable */
enum frm_type_enum required_type;
char timestamp_buffer[20]; /* buffer for timestamp (19+1) */
/*
This TABLE_LIST object is just placeholder for prelocking, it will be
used for implicit LOCK TABLES only and won't be used in real statement.
*/
bool prelocking_placeholder;
void calc_md5(char *buffer);
void set_ancestor();

View File

@ -1524,7 +1524,6 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap)
TZ_NAMES_ENTRY *tmp_tzname;
my_bool return_val= 1;
int res;
uint counter;
DBUG_ENTER("my_tz_init");
/*
@ -1593,8 +1592,7 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap)
last_global_next_ptr= &(tables_buff[0].next_global);
tz_init_table_list(tables_buff + 1, &last_global_next_ptr);
if (open_tables(thd, tables_buff, &counter) ||
lock_tables(thd, tables_buff, counter))
if (simple_open_n_lock_tables(thd, tables_buff))
{
sql_print_warning("Can't open and lock time zone table: %s "
"trying to live without them", thd->net.last_error);