Commit ac9f68b9 authored by unknown's avatar unknown

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.


mysql-test/r/lock.result:
  Replaced wrong error code with the correct one after fixing bug in
  SP-locking.
mysql-test/r/mysqldump.result:
  Added dropping of view which is used in test to its beginning.
mysql-test/r/sp.result:
  Added tests for improved SP-locking.
  Temporarily disabled tests for SHOW PROCEDURE STATUS and alike
  (Until Monty will allow to open mysql.proc under LOCK TABLES without
  mentioning it in lock list).
  Replaced wrong results of test for bug #5240 with correct results after
  fixing bug in handling of cursors.
mysql-test/t/lock.test:
  Replaced wrong error code with the correct one after fixing bug in
  SP-locking.
mysql-test/t/mysqldump.test:
  Added dropping of view which is used in test to its beginning.
mysql-test/t/sp.test:
  Added tests for improved SP-locking.
  Temporarily disabled tests for SHOW PROCEDURE STATUS and alike
  (Until Monty will allow to open mysql.proc under LOCK TABLES without
  mentioning it in lock list).
  Removed test for bug #1654 since we already test exactly this function
  in one of SP-locking tests.
  Removed comment about cursor's wrong behavior in test for bug #5240
  after fixing bug which was its cause.
sql/item_func.cc:
  Removed comment which is no longer true.
sql/mysql_priv.h:
  Changed open_tables() signature.
  Now its 2nd parameter is in/out since it can add elements to table list.
sql/sp.cc:
  sp_find_procedure():
   Added one more parameter which enforces cache only lookup.
  
  sp_merge_hash():
   Now uses its return value to indicate that first of two hashes changed
   as result of merge.
  
  sp_cache_routines():
   This function caches all stored routines used in query now.
sql/sp.h:
  - sp_find_procedure() now has one more parameter which enforces cache only
    lookup.
  - sp_merge_hash() now uses its return value to indicate that first of two
    hashes changed as result of merge.
  - sp_cache_routines() caches all stored routines now. So it does not need
    third argument any more.
sql/sp_head.cc:
  sp_head::sp_head():
   Added initialization of new m_spfuns and m_spprocs members.
  
  sp_head::execute():
   Let us save/restore part of thread context which can be damaged by
   execution of instructions.
  sp_head::execute_function()/execute_procedure():
   Now it is responsibility of caller to close tables used in
   subqueries which are passed as routine parameters.
  
  sp_head::restore_lex():
   Let us accumulate information about routines used by this one
   in new m_spfuns, m_spprocs hashes.
  
  sp_lex_keeper::reset_lex_and_exec_core()
   Main method of new auxilary sp_lex_keeper class to which instructions 
   delegate responsibility for handling LEX and preparations before
   executing statement or calculating complex expression.
  
  Since all instructions which calculate complex expression or execute
  command now use sp_lex_keeper they have to implement
  sp_instr::exec_core() method. Most of instruction specific logic
  has moved from sp_instr::execute() to this new method.
  
  Removed sp_instr_set_user_var class which is no longer used, because
  nowdays we allow execution of statements in stored functions and
  triggers.
  
  sp_merge_table_list() became sp_head::merge_table_list() method. It
  also treats sp_head::m_sptabs as multi-set of tables now.
  
  sp_hash_to_table_list() became sp_head::add_used_tables_to_table_list().
  It takes into account that sp_head::m_sptabs is multi-set and allocates
  object into persistent arena of PS.
  
  Removed sp_merge_table_hash(), sp_open_and_lock_tables(),
  sp_unlock_tables(), sp_merge_routine_tables() methods since they are not
  used by new prelocking mechanism.
  
  Added sp_add_sp_tables_to_table_list() which serves for adding tables needed
  by routines used in query to the query table list for prelocking.
sql/sp_head.h:
  class sp_head:
  - Added m_spfuns, m_spprocs members for storing names of routines used
    by this routine.
  - Added add_used_tables_to_table_list() method which allows to add
    tables needed by this routine to query's table list.
  - Converted sp_merge_table_list() to sp_head::merge_table_list() method.
  - Changed semantics of THD::m_sptabs. Now it is multi-set which contains
    only tables which are used by this routine and not routines that are
    called from this one.
  
  Removed sp_merge_routine_tables(), sp_merge_table_hash(),
  sp_open_and_lock_tables(), sp_unlock_tables() calls since they are not
  used for our prelocking list calculation.
  
  Added auxilary sp_lex_keeper class to which instructions delegate
  responsibility for handling LEX and preparations before executing
  statement or calculating complex expression. This class uses
  new sp_instr::exec_core() method which is responsible for executing
  instruction's core function after all preparations were made.
  
  All instructions which hold and calculate complex expression now have
  their own LEX (by aggregating sp_lex_keeper instance). sp_instr_stmt
  now uses sp_lex_keeper too.
  
  Removed sp_instr_set_user_var class which is no longer used, because
  nowdays we allow execution of statements in stored functions and
  triggers.
sql/sp_rcontext.cc:
  Now sp_cursor holds pointer to sp_lex_keeper instead of LEX.
sql/sp_rcontext.h:
  Now sp_cursor holds pointer to sp_lex_keeper instead of LEX.
sql/sql_acl.cc:
  acl_init(), grant_init():
    Now we use simple_open_n_lock_tables() instead of explicit
    calls to open_tables() and mysql_lock_tables().
sql/sql_base.cc:
  Implemented support for execution of statements in "prelocked" mode.
  
  When we have statement which uses stored routines explicitly or
  implicitly (via views or triggers) we have to open and lock all tables
  for these routines at the same time as tables for the main statement.
  In fact we have to do implicit LOCK TABLES at the begining of such
  statement and implict UNLOCK TABLES at its end. We call such mode
  "prelocked".
  
  When open_tables() is called for the statement tables which are needed
  for execution of routines used by it are added to its tables list
  (this process also caches all routines used). Implicit use of routines
  is discovered when we open view or table with trigger and apropriate
  tables are added to the table list at this moment. Statement which has
  such extra tables in its list (well actually any that uses functions)
  is marked as requiring prelocked mode for its execution.
  
  When lock_tables() sees such statement it will issue implicit LOCK TABLES
  for this extended table list instead of doing usual locking, it will also
  set THD::prelocked_mode to indicate that we are in prelocked mode.
  
  When open_tables()/lock_tables() are called for statement of stored
  routine (substatement), they notice that we are running in prelocked mode
  and use one of prelocked tables from those that are not used by upper
  levels of execution.
  
  close_thread_tables() for substatement won't really close tables used
  but will mark them as free for reuse instead.
  
  Finally when close_thread_tables() is called for the main statement it
  really unlocks and closes all tables used.
  
  Everything will work even if one uses such statement under real LOCK
  TABLES (we are simply not doing implicit LOCK/UNLOCK in this case).
sql/sql_class.cc:
  Added initialization of THD::prelocked_mode member.
sql/sql_class.h:
  - Added prelocked_mode_type enum and THD::prelocked_mode member
    which are used for indication whenever "prelocked mode" is on 
    (i.e. that statement uses stored routines and is executed under
     implicit LOCK TABLES).
  - Removed THD::shortcut_make_view which is no longer needed.
    We use TABLE_LIST::prelocking_placeholder for the same purprose
    now.
sql/sql_handler.cc:
  Changed open_tables() invocation.
  Now its 2nd parameter is in/out since it can add elements to table list.
sql/sql_lex.cc:
  lex_start():
    Added initialization of LEX::query_tables_own_last.
    Unused LEX::sptabs member was removed.
  st_lex::unlink_first_table()/link_first_table_back():
    We should update LEX::query_tables_last properly if table list
    contains(ed) only one element.
sql/sql_lex.h:
  LEX:
  - Removed sptabs member since it is no longer used.
  - Added query_tables_own_last member, which if non-0 indicates that
    statement requires prelocking (implicit LOCK TABLES) for its execution
    and points to last own element in query table list. If it is zero
    then this query does not need prelocking.
  - Added requires_prelocking(), mark_as_requiring_prelocking(),
    first_not_own_table() inline methods to incapsulate and simplify
    usage of this new member.
sql/sql_parse.cc:
  dispatch_command():
    To properly leave prelocked mode when needed we should call
    close_thread_tables() even if there are no open tables.
  mysql_execute_command():
  - Removed part of function which were responsible for doing implicit
    LOCK TABLES before statement execution if statement used stored 
    routines (and doing UNLOCK TABLES at the end).
    Now we do all this in open_tables()/lock_tables()/close_thread_tables()
    instead.
  - It is also sensible to reset errors before execution of statement
    which uses routines.
  - SQLCOM_DO, SQLCOM_SET_OPTION, SQLCOM_CALL
    We should always try to open tables because even if statement has empty
    table list, it can call routines using tables, which should be preopened
    before statement execution.
  - SQLCOM_CALL
    We should not look up routine called in mysql.proc, since it should be
    already cached by this moment by open_tables() call.
  - SQLCOM_LOCK_TABLES
    it is better to use simple_open_n_lock_tables() since we want to avoid
    materialization of derived tables for this command.
sql/sql_prepare.cc:
  mysql_test_update():
    Changed open_tables() invocations. Now its 2nd parameter is in/out
    since it can add elements to table list.
  check_prepared_statement():
    Since now we cache all routines used by statement in open_tables() we 
    don't need to do it explicitly.
  mysql_stmt_prepare():
    Now we should call close_thread_tables() when THD::lex points to the
    LEX of statement which opened tables.
  reset_stmt_for_execute():
    Commented why we are resetting all tables in table list.
sql/sql_trigger.h:
  Table_triggers_list::process_triggers():
    We should surpress sending of ok packet when we are calling trigger's
    routine, since now we allow statements in them.
sql/sql_update.cc:
  Changed open_tables() invocations.
  Now its 2nd parameter is in/out since it can add elements to table list.
sql/sql_view.cc:
  mysql_make_view():
  - Removed handling of routines used in view. Instead we add tables which
    are needed for their execution to statement's table list in 
    open_tables().
  - Now we use TABLE_LIST::prelocking_placeholder instead of 
    THD::shortcut_make_view for indicating that view is opened
    only to discover which tables and routines it uses (this happens
    when we build extended table list for prelocking). Also now we try
    to avoid to modify main LEX in this case (except of its table list).
  - Corrected small error we added tables to the table list of the main
    LEX without updating its query_tables_last member properly.
sql/sql_yacc.yy:
  Now each expression which is used in SP statements and can contain
  subquery has its own LEX. This LEX is stored in corresponding sp_instr
  object and used along with Item tree for expression calculation.
  
  We don't need sp_instr_set_user_var() anymore since now we allow
  execution of statements in stored functions and triggers.
sql/table.h:
  Added TABLE_LIST::prelocking_placeholder member for distinguishing
  elements of table list which does not belong to the statement itself
  and added there only for prelocking (as they are to be used by routines
  called by this statement).
sql/tztime.cc:
  my_tz_init():
    Now we use more simplier simple_open_n_lock_tables() call instead of 
    open_tables()/lock_tables() pair.
parent 6520c161
......@@ -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;
......
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"?>
......
......@@ -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
......
......@@ -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;
......
--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
......
......@@ -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|
......
......@@ -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
......
......@@ -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);
......
......@@ -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;
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;
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
HASH *h= (type == TYPE_ENUM_FUNCTION ? &lex->spfuns : &lex->spprocs);
for (uint i=0 ; i < h->records ; i++)
{
delete newlex;
thd->lex= oldlex;
LEX_STRING *ls= (LEX_STRING *)hash_element(h, i);
sp_name name(*ls);
sp_head *sp;
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;
}
/*
......
......@@ -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);
//
......
This diff is collapsed.
This diff is collapsed.
......@@ -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
......
......@@ -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 @@ class sp_cursor : public Sql_alloc
// 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 @@ class sp_cursor : public Sql_alloc
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
......
......@@ -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))
{
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)))
if (simple_open_n_lock_tables(thd, tables))
{
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:
......
This diff is collapsed.
......@@ -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;
}
......
......@@ -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 @@ class THD :public ilink,
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 @@ class THD :public ilink,
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 @@ class THD :public ilink,
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();
......
......@@ -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)
......
......@@ -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)
......
......@@ -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
......
......@@ -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 @@ mysql_execute_command(THD *thd)
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 @@ mysql_execute_command(THD *thd)
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 @@ mysql_execute_command(THD *thd)
{
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 @@ mysql_execute_command(THD *thd)
/* 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 @@ mysql_execute_command(THD *thd)
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);
}
......
......@@ -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)
......
......@@ -42,14 +42,21 @@ class Table_triggers_list: public Sql_alloc
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;
......
......@@ -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);
/*
......
......@@ -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 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table)
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);
......
This diff is collapsed.
......@@ -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();
......
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment