Apply and review:

3655 Jon Olav Hauglid   2009-10-19
Bug #30977 Concurrent statement using stored function and DROP FUNCTION 
           breaks SBR
Bug #48246 assert in close_thread_table

Implement a fix for:
Bug #41804 purge stored procedure cache causes mysterious hang for many
           minutes
Bug #49972 Crash in prepared statements

The problem was that concurrent execution of DML statements that
use stored functions and DDL statements that drop/modify the same
function might result in incorrect binary log in statement (and
mixed) mode and therefore break replication.

This patch fixes the problem by introducing metadata locking for
stored procedures and functions. This is similar to what is done
in Bug#25144 for views. Procedures and functions now are
locked using metadata locks until the transaction is either
committed or rolled back. This prevents other statements from
modifying the procedure/function while it is being executed. This
provides commit ordering - guaranteeing serializability across
multiple transactions and thus fixes the reported binlog problem.

Note that we do not take locks for top-level CALLs. This means
that procedures called directly are not protected from changes by
simultaneous DDL operations so they are executed at the state they
had at the time of the CALL. By not taking locks for top-level
CALLs, we still allow transactions to be started inside
procedures.

This patch also changes stored procedure cache invalidation.
Upon a change of cache version, we no longer invalidate the entire
cache, but only those routines which we use, only when a statement
is executed that uses them.

This patch also changes the logic of prepared statement validation.
A stored procedure used by a prepared statement is now validated
only once a metadata lock has been acquired. A version mismatch
causes a flush of the obsolete routine from the cache and
statement reprepare.
Incompatible changes:
1) ER_LOCK_DEADLOCK is reported for a transaction trying to access
   a procedure/function that is locked by a DDL operation in
   another connection.

2) Procedure/function DDL operations are now prohibited in LOCK
   TABLES mode as exclusive locks must be taken all at once and
   LOCK TABLES provides no way to specifiy procedures/functions to
   be locked.

Test cases have been added to sp-lock.test and rpl_sp.test.

Work on this bug has very much been a team effort and this patch
includes and is based on contributions from Davi Arnaut, Dmitry
Lenev, Magne Mæhre and Konstantin Osipov.


mysql-test/r/ps_ddl.result:
  Update results (Bug#30977).
mysql-test/r/ps_ddl1.result:
  Update results (Bug#30977).
mysql-test/r/sp-error.result:
  Update results (Bug#30977).
mysql-test/r/sp-lock.result:
  Update results (Bug#30977).
mysql-test/suite/rpl/r/rpl_sp.result:
  Update results (Bug#30977).
mysql-test/suite/rpl/t/rpl_sp.test:
  Add a test case for Bug#30977.
mysql-test/t/ps_ddl.test:
  Update comments. We no longer re-prepare a prepared statement
  when a stored procedure used in top-level CALL is changed.
mysql-test/t/ps_ddl1.test:
  Modifying stored procedure p1 no longer invalidates prepared
  statement "call p1" -- we can re-use the prepared statement
  without invalidation.
mysql-test/t/sp-error.test:
  Use a constant for an error value.
mysql-test/t/sp-lock.test:
  Add test coverage for Bug#30977.
sql/lock.cc:
  Implement lock_routine_name() - a way to acquire an 
  exclusive metadata lock (ex- name-lock) on 
  stored procedure/function.
sql/sp.cc:
  Change semantics of sp_cache_routine() -- now it has an option
  to make sure that the routine that is cached is up to date (has
  the latest sp cache version).
  
  Add sp_cache_invalidate() to sp_drop_routine(), where it was
  missing (a bug!).
  
  Acquire metadata locks for SP DDL (ALTER/CREATE/DROP). This is
  the core of the fix for Bug#30977.
  
  Since caching and cache invalidation scheme was changed, make 
  sure we don't invalidate the SP cache in the middle of a stored
  routine execution. At the same time, make sure we don't access
  stale data due to lack of invalidation. 
  For that, change ALTER FUNCTION/PROCEDURE to not use the cache,
  and SHOW PROCEDURE CODE/SHOW CREATE PROCEDURE/FUNCTION to always
  read an up to date version of the routine from the cache.
sql/sp.h:
  Add a helper wrapper around sp_cache_routine().
sql/sp_cache.cc:
  Implement new sp_cache_version() and sp_cache_flush_obsolete().
  Now we flush stale routines individually, rather than all at once.
sql/sp_cache.h:
  Update signatures of sp_cache_version() and sp_cache_flush_obsolete().
sql/sp_head.cc:
  Add a default initialization of sp_head::m_sp_cache_version.
  Remove a redundant sp_head::create().
sql/sp_head.h:
  Add m_sp_cache_version to sp_head class - we now 
  keep track of every routine in the stored procedure cache, rather than
  of the entire cache.
sql/sql_base.cc:
  Implement prelocking for stored routines. Validate stored
  routines after they were locked.
  Flush obsolete routines upon next access, one by one, not all at once
  (Bug#41804).
  Style fixes.
sql/sql_class.h:
  Rename a Open_table_context method.
sql/sql_parse.cc:
  Make sure stored procedures DDL commits the active transaction 
  (issues an implicit commit before and after).
  Remove sp_head::create(), a pure redundancy.
  Move the semantical check during alter routine inside sp_update_routine() code in order to:
  - avoid using SP cache during update, it may be obsolete.
  - speed up and simplify the update procedure.
  
  Remove sp_cache_flush_obsolete() calls, we no longer flush the entire
  cache, ever, stale routines are flushed before next use, one at a time.
sql/sql_prepare.cc:
  Move routine metadata validation to open_and_process_routine().
  Fix Bug#49972 (don't swap flags at reprepare).
  Reset Sroutine_hash_entries in reinit_stmt_before_use().
  Remove SP cache invalidation, it's now done by open_tables().
sql/sql_show.cc:
  Fix a warning: remove an unused label.
sql/sql_table.cc:
  Reset mdl_request.ticket for tickets acquired for routines inlined
  through a view, in CHECK TABLE statement, to satisfy an MDL assert.
sql/sql_update.cc:
  Move the cleanup of "translation items" to close_tables_for_reopen(),
  since it's needed in all cases when we back off, not just
  the back-off in multi-update. This fixes a bug when the server
  would crash on attempt to back off when opening tables
  for a statement that uses information_schema tables.
This commit is contained in:
Konstantin Osipov 2009-12-29 15:19:05 +03:00
parent dfdbc84585
commit bf9c1b7353
25 changed files with 2243 additions and 307 deletions

View file

@ -269,8 +269,6 @@ Part 7: TABLE -> TABLE (TRIGGER dependencies) transitions
=====================================================================
# Test 7-a: dependent PROCEDURE has changed
#
# Note, this scenario is not supported, subject of Bug#12093
#
create table t1 (a int);
create trigger t1_ai after insert on t1 for each row
call p1(new.a);
@ -282,10 +280,9 @@ drop procedure p1;
create procedure p1 (a int) begin end;
set @var= 2;
execute stmt using @var;
ERROR 42000: PROCEDURE test.p1 does not exist
# Cleanup
drop procedure p1;
call p_verify_reprepare_count(0);
call p_verify_reprepare_count(1);
SUCCESS
# Test 7-b: dependent FUNCTION has changed
@ -361,11 +358,13 @@ set @var=8;
# XXX: bug, the SQL statement in the trigger is still
# pointing at table 't3', since the view was expanded
# at first statement execution.
# Since the view definition is inlined in the statement
# at prepare, changing the view definition does not cause
# repreparation.
# Repreparation of the main statement doesn't cause repreparation
# of trigger statements.
execute stmt using @var;
ERROR 42S02: Table 'test.t3' doesn't exist
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
SUCCESS
#
@ -382,6 +381,7 @@ select * from t3;
a
6
7
8
flush table t1;
set @var=9;
execute stmt using @var;
@ -396,6 +396,7 @@ select * from t3;
a
6
7
8
drop view v1;
drop table t1,t2,t3;
# Test 7-d: dependent TABLE has changed

View file

@ -460,7 +460,7 @@ create schema mysqltest;
end|
execute stmt;
ERROR 42000: PROCEDURE test.p1 does not exist
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
SUCCESS
execute stmt;

View file

@ -512,7 +512,7 @@ select * from t1;
end|
lock table t1 read|
alter procedure bug9566 comment 'Some comment'|
ERROR HY000: Table 'proc' was not locked with LOCK TABLES
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
unlock tables|
drop procedure bug9566|
drop procedure if exists bug7299|

697
mysql-test/r/sp-lock.result Normal file
View file

@ -0,0 +1,697 @@
#
# Test coverage for changes performed by the fix
# for Bug#30977 "Concurrent statement using stored function
# and DROP FUNCTION breaks SBR.
#
#
# 1) Verify that the preceding transaction is
# (implicitly) committed before CREATE/ALTER/DROP
# PROCEDURE. Note, that this is already tested
# in implicit_commit.test, but here we use an alternative
# approach.
#
# Start a transaction, create a savepoint,
# then call a DDL operation on a procedure, and then check
# that the savepoint is no longer present.
drop table if exists t1;
drop procedure if exists p1;
drop procedure if exists p2;
drop procedure if exists p3;
drop procedure if exists p4;
drop function if exists f1;
create table t1 (a int);
#
# Test 'CREATE PROCEDURE'.
#
begin;
savepoint sv;
create procedure p1() begin end;
rollback to savepoint sv;
ERROR 42000: SAVEPOINT sv does not exist
#
# Test 'ALTER PROCEDURE'.
#
begin;
savepoint sv;
alter procedure p1 comment 'changed comment';
rollback to savepoint sv;
ERROR 42000: SAVEPOINT sv does not exist
#
# Test 'DROP PROCEDURE'.
#
begin;
savepoint sv;
drop procedure p1;
rollback to savepoint sv;
ERROR 42000: SAVEPOINT sv does not exist
#
# Test 'CREATE FUNCTION'.
#
begin;
savepoint sv;
create function f1() returns int return 1;
rollback to savepoint sv;
ERROR 42000: SAVEPOINT sv does not exist
#
# Test 'ALTER FUNCTION'.
#
begin;
savepoint sv;
alter function f1 comment 'new comment';
rollback to savepoint sv;
ERROR 42000: SAVEPOINT sv does not exist
#
# Test 'DROP FUNCTION'.
#
begin;
savepoint sv;
drop function f1;
rollback to savepoint sv;
ERROR 42000: SAVEPOINT sv does not exist
#
# 2) Verify that procedure DDL operations fail
# under lock tables.
#
# Auxiliary routines to test ALTER.
create procedure p1() begin end;
create function f1() returns int return 1;
lock table t1 write;
create procedure p2() begin end;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
alter procedure p1 comment 'changed comment';
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
drop procedure p1;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
create function f2() returns int return 1;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
alter function f1 comment 'changed comment';
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
lock table t1 read;
create procedure p2() begin end;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
alter procedure p1 comment 'changed comment';
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
drop procedure p1;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
create function f2() returns int return 1;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
alter function f1 comment 'changed comment';
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
unlock tables;
#
# Even if we locked a temporary table.
# Todo: this is a restriction we could possibly lift.
#
drop table t1;
create temporary table t1 (a int);
lock table t1 read;
create procedure p2() begin end;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
alter procedure p1 comment 'changed comment';
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
drop procedure p1;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
create function f2() returns int return 1;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
alter function f1 comment 'changed comment';
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
unlock tables;
drop function f1;
drop procedure p1;
drop temporary table t1;
#
# 3) Verify that CREATE/ALTER/DROP routine grab an
# exclusive lock.
#
# For that, start a transaction, use a routine. In a concurrent
# connection, try to drop or alter the routine. It should place
# a pending or exlusive lock and block. In a concurrnet
# connection, try to use the routine under LOCK TABLES.
# That should yield ER_LOCK_DEADLOCK.
#
# Establish helper connections.
#
# Test DROP PROCEDURE.
#
# --> connection default
create table t1 (a int);
create procedure p1() begin end;
create function f1() returns int
begin
call p1();
return 1;
end|
begin;
select f1();
f1()
1
# --> connection con1
# Sending 'drop procedure p1'...
drop procedure p1;
# --> connection con2
# Waitng for 'drop procedure t1' to get blocked on MDL lock...
# Demonstrate that there is a pending exclusive lock.
lock table t1 read;
select f1();
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
unlock tables;
# --> connection default
commit;
# --> connection con1
# Reaping 'drop procedure p1'...
# --> connection default
#
# Test CREATE PROCEDURE.
#
create procedure p1() begin end;
begin;
select f1();
f1()
1
# --> connection con1
# Sending 'create procedure p1'...
create procedure p1() begin end;
# --> connection con2
# Waitng for 'create procedure t1' to get blocked on MDL lock...
# Demonstrate that there is a pending exclusive lock.
lock table t1 read;
select f1();
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
unlock tables;
# --> connection default
commit;
# --> connection con1
# Reaping 'create procedure p1'...
ERROR 42000: PROCEDURE p1 already exists
#
# Test ALTER PROCEDURE.
begin;
select f1();
f1()
1
# --> connection con1
# Sending 'alter procedure p1'...
alter procedure p1 contains sql;
# --> connection con2
# Waitng for 'alter procedure t1' to get blocked on MDL lock...
# Demonstrate that there is a pending exclusive lock.
lock table t1 read;
select f1();
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
unlock tables;
# --> connection default
commit;
# --> connection con1
# Reaping 'alter procedure p1'...
# --> connection default
#
# Test DROP FUNCTION.
#
begin;
select f1();
f1()
1
# --> connection con1
# Sending 'drop function f1'...
drop function f1;
# --> connection con2
# Waitng for 'drop function f1' to get blocked on MDL lock...
# Demonstrate that there is a pending exclusive lock.
lock table t1 read;
select f1();
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
unlock tables;
# --> connection default
commit;
# --> connection con1
# Reaping 'drop function f1'...
# --> connection default
#
# Test CREATE FUNCTION.
#
create function f1() returns int return 1;
begin;
select f1();
f1()
1
# --> connection con1
# Sending 'create function f1'...
create function f1() returns int return 2;
# --> connection con2
# Waitng for 'create function f1' to get blocked on MDL lock...
# Demonstrate that there is a pending exclusive lock.
lock table t1 read;
select f1();
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
unlock tables;
# --> connection default
commit;
# --> connection con1
# Reaping 'create function f1'...
ERROR 42000: FUNCTION f1 already exists
# --> connection default
#
# Test ALTER FUNCTION.
begin;
select f1();
f1()
1
# --> connection con1
# Sending 'alter function f1'...
alter function f1 contains sql;
# --> connection con2
# Waitng for 'alter function f1' to get blocked on MDL lock...
# Demonstrate that there is a pending exclusive lock.
lock table t1 read;
select f1();
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
unlock tables;
# --> connection default
commit;
# --> connection con1
# Reaping 'alter function f1'...
# --> connection default
drop function f1;
drop procedure p1;
#
# 4) MDL lock should not be taken for
# unrolled CALL statements.
# The primary goal of metadata locks is a consistent binary log.
# When a call statement is unrolled, it doesn't get to the
# binary log, instead the statements that are contained
# in the procedure body do. This can nest to any level.
#
create procedure p1() begin end;
create procedure p2() begin end;
create procedure p3()
begin
call p1();
call p1();
call p2();
end|
create procedure p4()
begin
call p1();
call p1();
call p2();
call p2();
call p3();
end|
begin;
select * from t1;
a
savepoint sv;
call p4();
# Prepared statement should not add any locks either.
prepare stmt from "call p4()";
execute stmt;
execute stmt;
# --> connection con1
drop procedure p1;
drop procedure p2;
drop procedure p3;
drop procedure p4;
# --> connection default
# This is to verify there was no implicit commit.
rollback to savepoint sv;
call p4();
ERROR 42000: PROCEDURE test.p4 does not exist
commit;
drop table t1;
#
# 5) Locks should be taken on routines
# used indirectly by views or triggers.
#
#
# A function is used from a trigger.
#
create function f1() returns int return 1;
create table t1 (a int);
create table t2 (a int, b int);
create trigger t1_ai after insert on t1 for each row
insert into t2 (a, b) values (new.a, f1());
begin;
insert into t1 (a) values (1);
# --> connection con1
# Sending 'drop function f1'
drop function f1;
# --> connection con2
# Waitng for 'drop function f1' to get blocked on MDL lock...
# --> connnection default
commit;
# --> connection con1
# Reaping 'drop function f1'...
# --> connection default
#
# A function is used from a view.
#
create function f1() returns int return 1;
create view v1 as select f1() as a;
begin;
select * from v1;
a
1
# --> connection con1
# Sending 'drop function f1'
drop function f1;
# --> connection con2
# Waitng for 'drop function f1' to get blocked on MDL lock...
# --> connnection default
commit;
# --> connection con1
# Reaping 'drop function f1'...
# --> connection default
#
# A procedure is used from a function.
#
create function f1() returns int
begin
declare v_out int;
call p1(v_out);
return v_out;
end|
create procedure p1(out v_out int) set v_out=3;
begin;
select * from v1;
a
3
# --> connection con1
# Sending 'drop procedure p1'
drop procedure p1;
# --> connection con2
# Waitng for 'drop procedure p1' to get blocked on MDL lock...
# --> connnection default
commit;
# --> connection con1
# Reaping 'drop procedure p1'...
# --> connection default
#
# Deep nesting: a function is used from a procedure used
# from a function used from a view used in a trigger.
#
create function f2() returns int return 4;
create procedure p1(out v_out int) set v_out=f2();
drop trigger t1_ai;
create trigger t1_ai after insert on t1 for each row
insert into t2 (a, b) values (new.a, (select max(a) from v1));
begin;
insert into t1 (a) values (3);
# --> connection con1
# Sending 'drop function f2'
drop function f2;
# --> connection con2
# Waitng for 'drop function f2' to get blocked on MDL lock...
# --> connnection default
commit;
# --> connection con1
# Reaping 'drop function f2'...
# --> connection default
drop view v1;
drop function f1;
drop procedure p1;
drop table t1, t2;
#
# 6) Check that ER_LOCK_DEADLOCK is reported if
# acquisition of a shared lock fails during a transaction or
# we need to back off to flush the sp cache.
#
# a) A back off due to a lock conflict.
#
create table t1 (a int);
create function f1() returns int return 6;
begin;
select f1();
f1()
6
# --> connection con1
# Sending 'drop function f1'...
drop function f1;
# --> connection con2
# Waitng for 'drop function f1' to get blocked on MDL lock...
begin;
select * from t1;
a
select f1();
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
commit;
# --> connection default
commit;
# --> connection con1
# Reaping 'drop function f1'...
# --> connection default
#
# b) A back off to flush the cache.
# Sic: now this situation does not require a back off since we
# flush the cache on the fly.
#
create function f1() returns int return 7;
begin;
select * from t1;
a
select f1();
f1()
7
commit;
drop table t1;
drop function f1;
#
# 7) Demonstrate that under LOCK TABLES we accumulate locks
# on stored routines, and release metadata locks in
# ROLLBACK TO SAVEPOINT. That is done only for those stored
# routines that are not part of LOCK TABLES prelocking list.
# Those stored routines that are part of LOCK TABLES
# prelocking list are implicitly locked when entering
# LOCK TABLES, and ROLLBACK TO SAVEPOINT has no effect on
# them.
#
create function f1() returns varchar(20) return "f1()";
create function f2() returns varchar(20) return "f2()";
create view v1 as select f1() as a;
set @@session.autocommit=0;
lock table v1 read;
select * from v1;
a
f1()
savepoint sv;
select f2();
f2()
f2()
# --> connection con1
# Sending 'drop function f1'...
drop function f1;
# --> connection con2
# Waitng for 'drop function f1' to get blocked on MDL lock...
# Sending 'drop function f2'...
drop function f2;
# --> connection default
# Waitng for 'drop function f2' to get blocked on MDL lock...
rollback to savepoint sv;
# --> connection con2
# Reaping 'drop function f2'...
# --> connection default
unlock tables;
# --> connection con1
# Reaping 'drop function f1'...
# --> connection default
drop function f1;
ERROR 42000: FUNCTION test.f1 does not exist
drop function f2;
ERROR 42000: FUNCTION test.f2 does not exist
drop view v1;
set @@session.autocommit=default;
#
# 8) Check the situation when we're preparing or executing a
# prepared statement, and as part of that try to flush the
# session sp cache. However, one of the procedures that
# needs a flush is in use. Verify that there is no infinite
# reprepare loop and no crash.
#
create function f1() returns int return 1;
#
# We just mention p1() in the body of f2() to make
# sure that p1() metadata is validated when validating
# 'select f2()'.
# Recursion is not allowed in stored functions, so
# an attempt to just invoke p1() from f2() which is in turn
# called from p1() would have given a run-time error.
#
create function f2() returns int
begin
if @var is null then
call p1();
end if;
return 1;
end|
create procedure p1()
begin
select f1() into @var;
execute stmt;
end|
# --> connection con2
prepare stmt from "select f2()";
# --> connection default
begin;
select f1();
f1()
1
# --> connection con1
# Sending 'alter function f1 ...'...
alter function f1 comment "comment";
# --> connection con2
# Waitng for 'alter function f1 ...' to get blocked on MDL lock...
# Sending 'call p1()'...
call p1();
# Waitng for 'call p1()' to get blocked on MDL lock on f1...
# Let 'alter function f1 ...' go through...
commit;
# --> connection con1
# Reaping 'alter function f1 ...'
# --> connection con2
# Reaping 'call p1()'...
f2()
1
deallocate prepare stmt;
# --> connection default
drop function f1;
drop function f2;
drop procedure p1;
#
# 9) Check the situation when a stored function is invoked
# from a stored procedure, and recursively invokes the
# stored procedure that is in use. But for the second
# invocation, a cache flush is requested. We can't
# flush the procedure that's in use, and are forced
# to use an old version. It is not a violation of
# consistency, since we unroll top-level calls.
# Just verify the code works.
#
create function f1() returns int return 1;
begin;
select f1();
f1()
1
# --> connection con1
# Sending 'alter function f1 ...'...
alter function f1 comment "comment";
# --> connection con2
# Waitng for 'alter function f1 ...' to get blocked on MDL lock...
#
# We just mention p1() in the body of f2() to make
# sure that p1() is prelocked for f2().
# Recursion is not allowed in stored functions, so
# an attempt to just invoke p1() from f2() which is in turn
# called from p1() would have given a run-time error.
#
create function f2() returns int
begin
if @var is null then
call p1();
end if;
return 1;
end|
create procedure p1()
begin
select f1() into @var;
select f2() into @var;
end|
# Sending 'call p1()'...
call p1();
# Waitng for 'call p1()' to get blocked on MDL lock on f1...
# Let 'alter function f1 ...' go through...
commit;
# --> connection con1
# Reaping 'alter function f1 ...'
# --> connection con2
# Reaping 'call p1()'...
# --> connection default
drop function f1;
drop function f2;
drop procedure p1;
#
# 10) A select from information_schema.routines now
# flushes the stored routines caches. Test that this
# does not remove from the cache a stored routine
# that is already prelocked.
#
create function f1() returns int return get_lock("30977", 100000);
create function f2() returns int return 2;
create function f3() returns varchar(255)
begin
declare res varchar(255);
declare c cursor for select routine_name from
information_schema.routines where routine_name='f1';
select f1() into @var;
open c;
fetch c into res;
close c;
select f2() into @var;
return res;
end|
# --> connection con1
select get_lock("30977", 0);
get_lock("30977", 0)
1
# --> connection default
# Sending 'select f3()'...
select f3();
# --> connection con1
# Waitng for 'select f3()' to get blocked on the user level lock...
# Do something to change the cache version.
create function f4() returns int return 4;
drop function f4;
select release_lock("30977");
release_lock("30977")
1
# --> connection default
# Reaping 'select f3()'...
# Routine 'f2()' should exist and get executed successfully.
f3()
f1
select @var;
@var
2
drop function f1;
drop function f2;
drop function f3;
# 11) Check the situation when the connection is flushing the
# SP cache which contains a procedure that is being executed.
#
# Function f1() calls p1(). Procedure p1() has a DROP
# VIEW statement, which, we know, invalidates the routines cache.
# During cache flush p1() must not be flushed since it's in
# use.
#
create function f1() returns int
begin
call p1();
return 1;
end|
create procedure p1()
begin
create view v1 as select 1;
drop view v1;
select f1() into @var;
set @exec_count=@exec_count+1;
end|
set @exec_count=0;
call p1();
ERROR HY000: Recursive limit 0 (as set by the max_sp_recursion_depth variable) was exceeded for routine p1
select @exec_count;
@exec_count
0
set @@session.max_sp_recursion_depth=5;
set @exec_count=0;
call p1();
ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger.
select @exec_count;
@exec_count
0
drop procedure p1;
drop function f1;
set @@session.max_sp_recursion_depth=default;
# --> connection con1
# --> connection con2
# --> connection default
#
# End of 5.5 tests
#

View file

@ -977,4 +977,47 @@ drop procedure mysqltestbug36570_p1;
drop procedure ` mysqltestbug36570_p2`;
drop function mysqltestbug36570_f1;
End of 5.0 tests
End of 5.1 tests
# End of 5.1 tests
#
# Test Bug#30977 Concurrent statement using stored
# function and DROP FUNCTION breaks SBR.
#
# Demonstrate that stored function DDL can not go through,
# or, worse yet, make its way into the binary log, while
# the stored function is in use.
# For that, try to insert a result of a stored function
# into a table. Block the insert in the beginning, waiting
# on a table lock. While insert is blocked, attempt to
# drop the routine. Verify that this attempt
# blocks and waits for INSERT to complete. Commit and
# reap the chain of events. Master and slave must contain
# identical data. Statements in the binrary log must be
# consistent with data in the table.
#
# --> connection default
drop table if exists t1, t2;
drop function if exists t1;
create table t1 (a int);
create table t2 (a int) as select 1 as a;
create function f1() returns int deterministic return (select max(a) from t2);
lock table t2 write;
# --> connection master
# Sending 'insert into t1 (a) values (f1())'...
insert into t1 (a) values (f1());
# Waitng for 'insert into t1 ...' to get blocked on table lock...
# Sending 'drop function f1'. It will abort the table lock wait.
drop function f1;
# --> connection default
# Now let's let 'insert' go through...
unlock tables;
# --> connection con1
# Reaping 'insert into t1 (a) values (f1())'...
ERROR 42000: FUNCTION test.f1 does not exist
select * from t1;
a
select * from t1;
a
drop table t1, t2;
drop function f1;
ERROR 42000: FUNCTION test.f1 does not exist
# End of 5.5 tests.

View file

@ -621,7 +621,64 @@ drop procedure mysqltestbug36570_p1;
drop procedure ` mysqltestbug36570_p2`;
drop function mysqltestbug36570_f1;
--echo End of 5.0 tests
--echo End of 5.1 tests
--echo # End of 5.1 tests
--echo #
--echo # Test Bug#30977 Concurrent statement using stored
--echo # function and DROP FUNCTION breaks SBR.
--echo #
--echo # Demonstrate that stored function DDL can not go through,
--echo # or, worse yet, make its way into the binary log, while
--echo # the stored function is in use.
--echo # For that, try to insert a result of a stored function
--echo # into a table. Block the insert in the beginning, waiting
--echo # on a table lock. While insert is blocked, attempt to
--echo # drop the routine. Verify that this attempt
--echo # blocks and waits for INSERT to complete. Commit and
--echo # reap the chain of events. Master and slave must contain
--echo # identical data. Statements in the binrary log must be
--echo # consistent with data in the table.
--echo #
--echo # --> connection default
connection default;
--disable_warnings
drop table if exists t1, t2;
drop function if exists t1;
--enable_warnings
create table t1 (a int);
create table t2 (a int) as select 1 as a;
create function f1() returns int deterministic return (select max(a) from t2);
lock table t2 write;
--echo # --> connection master
connection master;
--echo # Sending 'insert into t1 (a) values (f1())'...
--send insert into t1 (a) values (f1())
connection master1;
--echo # Waitng for 'insert into t1 ...' to get blocked on table lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Table lock' and info='insert into t1 (a) values (f1())';
--source include/wait_condition.inc
--echo # Sending 'drop function f1'. It will abort the table lock wait.
drop function f1;
--echo # --> connection default
connection default;
--echo # Now let's let 'insert' go through...
unlock tables;
--echo # --> connection con1
connection master;
--echo # Reaping 'insert into t1 (a) values (f1())'...
--error ER_SP_DOES_NOT_EXIST
--reap
connection master1;
select * from t1;
connection slave;
select * from t1;
connection master;
drop table t1, t2;
--error ER_SP_DOES_NOT_EXIST
drop function f1;
--echo # End of 5.5 tests.
# Cleanup
sync_slave_with_master;

View file

@ -278,8 +278,6 @@ deallocate prepare stmt;
--echo # Test 7-a: dependent PROCEDURE has changed
--echo #
--echo # Note, this scenario is not supported, subject of Bug#12093
--echo #
create table t1 (a int);
create trigger t1_ai after insert on t1 for each row
@ -291,11 +289,10 @@ execute stmt using @var;
drop procedure p1;
create procedure p1 (a int) begin end;
set @var= 2;
--error ER_SP_DOES_NOT_EXIST
execute stmt using @var;
--echo # Cleanup
drop procedure p1;
call p_verify_reprepare_count(0);
call p_verify_reprepare_count(1);
--echo # Test 7-b: dependent FUNCTION has changed
--echo #
@ -355,11 +352,13 @@ set @var=8;
--echo # XXX: bug, the SQL statement in the trigger is still
--echo # pointing at table 't3', since the view was expanded
--echo # at first statement execution.
--echo # Since the view definition is inlined in the statement
--echo # at prepare, changing the view definition does not cause
--echo # repreparation.
--echo # Repreparation of the main statement doesn't cause repreparation
--echo # of trigger statements.
--error ER_NO_SUCH_TABLE
execute stmt using @var;
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
--echo #
--echo # Sic: the insert went into t3, even though the view now
--echo # points at t2. This is because neither the merged view

View file

@ -363,7 +363,7 @@ end|
delimiter ;|
--error ER_SP_DOES_NOT_EXIST
execute stmt;
call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
--error ER_SP_DOES_NOT_EXIST
execute stmt;
call p_verify_reprepare_count(0);

View file

@ -723,7 +723,7 @@ lock table t1 read|
# This should fail since we forgot to lock mysql.proc for writing
# explicitly, and we can't open mysql.proc for _writing_ if there
# are locked tables.
--error 1100
--error ER_LOCK_OR_ACTIVE_TRANSACTION
alter procedure bug9566 comment 'Some comment'|
unlock tables|
# This should succeed

876
mysql-test/t/sp-lock.test Normal file
View file

@ -0,0 +1,876 @@
# Copyright (C) 2009 Sun Microsystems, Inc
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Metadata lock handling for stored procedures and
# functions.
#
--echo #
--echo # Test coverage for changes performed by the fix
--echo # for Bug#30977 "Concurrent statement using stored function
--echo # and DROP FUNCTION breaks SBR.
--echo #
--echo #
--echo # 1) Verify that the preceding transaction is
--echo # (implicitly) committed before CREATE/ALTER/DROP
--echo # PROCEDURE. Note, that this is already tested
--echo # in implicit_commit.test, but here we use an alternative
--echo # approach.
--echo #
--echo # Start a transaction, create a savepoint,
--echo # then call a DDL operation on a procedure, and then check
--echo # that the savepoint is no longer present.
--disable_warnings
drop table if exists t1;
drop procedure if exists p1;
drop procedure if exists p2;
drop procedure if exists p3;
drop procedure if exists p4;
drop function if exists f1;
--enable_warnings
create table t1 (a int);
--echo #
--echo # Test 'CREATE PROCEDURE'.
--echo #
begin;
savepoint sv;
create procedure p1() begin end;
--error ER_SP_DOES_NOT_EXIST
rollback to savepoint sv;
--echo #
--echo # Test 'ALTER PROCEDURE'.
--echo #
begin;
savepoint sv;
alter procedure p1 comment 'changed comment';
--error ER_SP_DOES_NOT_EXIST
rollback to savepoint sv;
--echo #
--echo # Test 'DROP PROCEDURE'.
--echo #
begin;
savepoint sv;
drop procedure p1;
--error ER_SP_DOES_NOT_EXIST
rollback to savepoint sv;
--echo #
--echo # Test 'CREATE FUNCTION'.
--echo #
begin;
savepoint sv;
create function f1() returns int return 1;
--error ER_SP_DOES_NOT_EXIST
rollback to savepoint sv;
--echo #
--echo # Test 'ALTER FUNCTION'.
--echo #
begin;
savepoint sv;
alter function f1 comment 'new comment';
--error ER_SP_DOES_NOT_EXIST
rollback to savepoint sv;
--echo #
--echo # Test 'DROP FUNCTION'.
--echo #
begin;
savepoint sv;
drop function f1;
--error ER_SP_DOES_NOT_EXIST
rollback to savepoint sv;
--echo #
--echo # 2) Verify that procedure DDL operations fail
--echo # under lock tables.
--echo #
--echo # Auxiliary routines to test ALTER.
create procedure p1() begin end;
create function f1() returns int return 1;
lock table t1 write;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
create procedure p2() begin end;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
alter procedure p1 comment 'changed comment';
--error ER_LOCK_OR_ACTIVE_TRANSACTION
drop procedure p1;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
create function f2() returns int return 1;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
alter function f1 comment 'changed comment';
lock table t1 read;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
create procedure p2() begin end;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
alter procedure p1 comment 'changed comment';
--error ER_LOCK_OR_ACTIVE_TRANSACTION
drop procedure p1;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
create function f2() returns int return 1;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
alter function f1 comment 'changed comment';
unlock tables;
--echo #
--echo # Even if we locked a temporary table.
--echo # Todo: this is a restriction we could possibly lift.
--echo #
drop table t1;
create temporary table t1 (a int);
lock table t1 read;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
create procedure p2() begin end;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
alter procedure p1 comment 'changed comment';
--error ER_LOCK_OR_ACTIVE_TRANSACTION
drop procedure p1;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
create function f2() returns int return 1;
--error ER_LOCK_OR_ACTIVE_TRANSACTION
alter function f1 comment 'changed comment';
unlock tables;
drop function f1;
drop procedure p1;
drop temporary table t1;
--echo #
--echo # 3) Verify that CREATE/ALTER/DROP routine grab an
--echo # exclusive lock.
--echo #
--echo # For that, start a transaction, use a routine. In a concurrent
--echo # connection, try to drop or alter the routine. It should place
--echo # a pending or exlusive lock and block. In a concurrnet
--echo # connection, try to use the routine under LOCK TABLES.
--echo # That should yield ER_LOCK_DEADLOCK.
--echo #
--echo # Establish helper connections.
connect(con1, localhost, root,,);
connect(con2, localhost, root,,);
--echo #
--echo # Test DROP PROCEDURE.
--echo #
--echo # --> connection default
connection default;
create table t1 (a int);
create procedure p1() begin end;
delimiter |;
create function f1() returns int
begin
call p1();
return 1;
end|
delimiter ;|
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'drop procedure p1'...
--send drop procedure p1
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop procedure t1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop procedure p1';
--source include/wait_condition.inc
--echo # Demonstrate that there is a pending exclusive lock.
lock table t1 read;
--error ER_LOCK_DEADLOCK
select f1();
unlock tables;
--echo # --> connection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop procedure p1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # Test CREATE PROCEDURE.
--echo #
create procedure p1() begin end;
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'create procedure p1'...
--send create procedure p1() begin end
--echo # --> connection con2
connection con2;
--echo # Waitng for 'create procedure t1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='create procedure p1() begin end';
--source include/wait_condition.inc
--echo # Demonstrate that there is a pending exclusive lock.
lock table t1 read;
--error ER_LOCK_DEADLOCK
select f1();
unlock tables;
--echo # --> connection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'create procedure p1'...
--error ER_SP_ALREADY_EXISTS
--reap
connection default;
--echo #
--echo # Test ALTER PROCEDURE.
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'alter procedure p1'...
--send alter procedure p1 contains sql
--echo # --> connection con2
connection con2;
--echo # Waitng for 'alter procedure t1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='alter procedure p1 contains sql';
--source include/wait_condition.inc
--echo # Demonstrate that there is a pending exclusive lock.
lock table t1 read;
--error ER_LOCK_DEADLOCK
select f1();
unlock tables;
--echo # --> connection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'alter procedure p1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # Test DROP FUNCTION.
--echo #
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'drop function f1'...
--send drop function f1
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop function f1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop function f1';
--source include/wait_condition.inc
--echo # Demonstrate that there is a pending exclusive lock.
lock table t1 read;
--error ER_LOCK_DEADLOCK
select f1();
unlock tables;
--echo # --> connection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop function f1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # Test CREATE FUNCTION.
--echo #
create function f1() returns int return 1;
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'create function f1'...
--send create function f1() returns int return 2
--echo # --> connection con2
connection con2;
--echo # Waitng for 'create function f1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='create function f1() returns int return 2';
--source include/wait_condition.inc
--echo # Demonstrate that there is a pending exclusive lock.
lock table t1 read;
--error ER_LOCK_DEADLOCK
select f1();
unlock tables;
--echo # --> connection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'create function f1'...
--error ER_SP_ALREADY_EXISTS
--reap
--echo # --> connection default
connection default;
--echo #
--echo # Test ALTER FUNCTION.
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'alter function f1'...
--send alter function f1 contains sql
--echo # --> connection con2
connection con2;
--echo # Waitng for 'alter function f1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='alter function f1 contains sql';
--source include/wait_condition.inc
--echo # Demonstrate that there is a pending exclusive lock.
lock table t1 read;
--error ER_LOCK_DEADLOCK
select f1();
unlock tables;
--echo # --> connection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'alter function f1'...
--reap
--echo # --> connection default
connection default;
drop function f1;
drop procedure p1;
--echo #
--echo # 4) MDL lock should not be taken for
--echo # unrolled CALL statements.
--echo # The primary goal of metadata locks is a consistent binary log.
--echo # When a call statement is unrolled, it doesn't get to the
--echo # binary log, instead the statements that are contained
--echo # in the procedure body do. This can nest to any level.
--echo #
create procedure p1() begin end;
create procedure p2() begin end;
delimiter |;
create procedure p3()
begin
call p1();
call p1();
call p2();
end|
create procedure p4()
begin
call p1();
call p1();
call p2();
call p2();
call p3();
end|
delimiter ;|
begin;
select * from t1;
savepoint sv;
call p4();
--echo # Prepared statement should not add any locks either.
prepare stmt from "call p4()";
execute stmt;
execute stmt;
--echo # --> connection con1
connection con1;
drop procedure p1;
drop procedure p2;
drop procedure p3;
drop procedure p4;
--echo # --> connection default
connection default;
--echo # This is to verify there was no implicit commit.
rollback to savepoint sv;
--error ER_SP_DOES_NOT_EXIST
call p4();
commit;
drop table t1;
--echo #
--echo # 5) Locks should be taken on routines
--echo # used indirectly by views or triggers.
--echo #
--echo #
--echo # A function is used from a trigger.
--echo #
create function f1() returns int return 1;
create table t1 (a int);
create table t2 (a int, b int);
create trigger t1_ai after insert on t1 for each row
insert into t2 (a, b) values (new.a, f1());
begin;
insert into t1 (a) values (1);
--echo # --> connection con1
connection con1;
--echo # Sending 'drop function f1'
--send drop function f1
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop function f1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop function f1';
--source include/wait_condition.inc
--echo # --> connnection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop function f1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # A function is used from a view.
--echo #
create function f1() returns int return 1;
create view v1 as select f1() as a;
begin;
select * from v1;
--echo # --> connection con1
connection con1;
--echo # Sending 'drop function f1'
--send drop function f1
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop function f1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop function f1';
--source include/wait_condition.inc
--echo # --> connnection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop function f1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # A procedure is used from a function.
--echo #
delimiter |;
create function f1() returns int
begin
declare v_out int;
call p1(v_out);
return v_out;
end|
delimiter ;|
create procedure p1(out v_out int) set v_out=3;
begin;
select * from v1;
--echo # --> connection con1
connection con1;
--echo # Sending 'drop procedure p1'
--send drop procedure p1
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop procedure p1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop procedure p1';
--source include/wait_condition.inc
--echo # --> connnection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop procedure p1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # Deep nesting: a function is used from a procedure used
--echo # from a function used from a view used in a trigger.
--echo #
create function f2() returns int return 4;
create procedure p1(out v_out int) set v_out=f2();
drop trigger t1_ai;
create trigger t1_ai after insert on t1 for each row
insert into t2 (a, b) values (new.a, (select max(a) from v1));
begin;
insert into t1 (a) values (3);
--echo # --> connection con1
connection con1;
--echo # Sending 'drop function f2'
--send drop function f2
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop function f2' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop function f2';
--source include/wait_condition.inc
--echo # --> connnection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop function f2'...
--reap
--echo # --> connection default
connection default;
drop view v1;
drop function f1;
drop procedure p1;
drop table t1, t2;
--echo #
--echo # 6) Check that ER_LOCK_DEADLOCK is reported if
--echo # acquisition of a shared lock fails during a transaction or
--echo # we need to back off to flush the sp cache.
--echo #
--echo # a) A back off due to a lock conflict.
--echo #
create table t1 (a int);
create function f1() returns int return 6;
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'drop function f1'...
--send drop function f1
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop function f1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop function f1';
--source include/wait_condition.inc
begin;
select * from t1;
--error ER_LOCK_DEADLOCK
select f1();
commit;
--echo # --> connection default
connection default;
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop function f1'...
--reap
--echo # --> connection default
connection default;
--echo #
--echo # b) A back off to flush the cache.
--echo # Sic: now this situation does not require a back off since we
--echo # flush the cache on the fly.
--echo #
create function f1() returns int return 7;
begin;
select * from t1;
# Used to have a back-off here, with optional ER_LOCK_DEADLOCK
#--error ER_LOCK_DEADLOCK
select f1();
commit;
drop table t1;
drop function f1;
--echo #
--echo # 7) Demonstrate that under LOCK TABLES we accumulate locks
--echo # on stored routines, and release metadata locks in
--echo # ROLLBACK TO SAVEPOINT. That is done only for those stored
--echo # routines that are not part of LOCK TABLES prelocking list.
--echo # Those stored routines that are part of LOCK TABLES
--echo # prelocking list are implicitly locked when entering
--echo # LOCK TABLES, and ROLLBACK TO SAVEPOINT has no effect on
--echo # them.
--echo #
create function f1() returns varchar(20) return "f1()";
create function f2() returns varchar(20) return "f2()";
create view v1 as select f1() as a;
set @@session.autocommit=0;
lock table v1 read;
select * from v1;
savepoint sv;
select f2();
--echo # --> connection con1
connection con1;
--echo # Sending 'drop function f1'...
--send drop function f1
--echo # --> connection con2
connection con2;
--echo # Waitng for 'drop function f1' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop function f1';
--source include/wait_condition.inc
--echo # Sending 'drop function f2'...
--send drop function f2
--echo # --> connection default
connection default;
--echo # Waitng for 'drop function f2' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='drop function f2';
--source include/wait_condition.inc
rollback to savepoint sv;
--echo # --> connection con2
connection con2;
--echo # Reaping 'drop function f2'...
--reap
--echo # --> connection default
connection default;
unlock tables;
--echo # --> connection con1
connection con1;
--echo # Reaping 'drop function f1'...
--reap
--echo # --> connection default
connection default;
--error ER_SP_DOES_NOT_EXIST
drop function f1;
--error ER_SP_DOES_NOT_EXIST
drop function f2;
drop view v1;
set @@session.autocommit=default;
--echo #
--echo # 8) Check the situation when we're preparing or executing a
--echo # prepared statement, and as part of that try to flush the
--echo # session sp cache. However, one of the procedures that
--echo # needs a flush is in use. Verify that there is no infinite
--echo # reprepare loop and no crash.
--echo #
create function f1() returns int return 1;
delimiter |;
--echo #
--echo # We just mention p1() in the body of f2() to make
--echo # sure that p1() metadata is validated when validating
--echo # 'select f2()'.
--echo # Recursion is not allowed in stored functions, so
--echo # an attempt to just invoke p1() from f2() which is in turn
--echo # called from p1() would have given a run-time error.
--echo #
create function f2() returns int
begin
if @var is null then
call p1();
end if;
return 1;
end|
create procedure p1()
begin
select f1() into @var;
execute stmt;
end|
delimiter ;|
--echo # --> connection con2
connection con2;
prepare stmt from "select f2()";
--echo # --> connection default
connection default;
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'alter function f1 ...'...
--send alter function f1 comment "comment"
--echo # --> connection con2
connection con2;
--echo # Waitng for 'alter function f1 ...' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info like 'alter function f1 comment%';
--source include/wait_condition.inc
--echo # Sending 'call p1()'...
--send call p1()
connection default;
--echo # Waitng for 'call p1()' to get blocked on MDL lock on f1...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='select f1() into @var';
--source include/wait_condition.inc
--echo # Let 'alter function f1 ...' go through...
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'alter function f1 ...'
--reap
--echo # --> connection con2
connection con2;
--echo # Reaping 'call p1()'...
--reap
deallocate prepare stmt;
--echo # --> connection default
connection default;
drop function f1;
drop function f2;
drop procedure p1;
--echo #
--echo # 9) Check the situation when a stored function is invoked
--echo # from a stored procedure, and recursively invokes the
--echo # stored procedure that is in use. But for the second
--echo # invocation, a cache flush is requested. We can't
--echo # flush the procedure that's in use, and are forced
--echo # to use an old version. It is not a violation of
--echo # consistency, since we unroll top-level calls.
--echo # Just verify the code works.
--echo #
create function f1() returns int return 1;
begin;
select f1();
--echo # --> connection con1
connection con1;
--echo # Sending 'alter function f1 ...'...
--send alter function f1 comment "comment"
--echo # --> connection con2
connection con2;
--echo # Waitng for 'alter function f1 ...' to get blocked on MDL lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info like 'alter function f1 comment%';
--source include/wait_condition.inc
delimiter |;
--echo #
--echo # We just mention p1() in the body of f2() to make
--echo # sure that p1() is prelocked for f2().
--echo # Recursion is not allowed in stored functions, so
--echo # an attempt to just invoke p1() from f2() which is in turn
--echo # called from p1() would have given a run-time error.
--echo #
create function f2() returns int
begin
if @var is null then
call p1();
end if;
return 1;
end|
create procedure p1()
begin
select f1() into @var;
select f2() into @var;
end|
delimiter ;|
--echo # Sending 'call p1()'...
--send call p1()
connection default;
--echo # Waitng for 'call p1()' to get blocked on MDL lock on f1...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='Waiting for table' and info='select f1() into @var';
--source include/wait_condition.inc
--echo # Let 'alter function f1 ...' go through...
commit;
--echo # --> connection con1
connection con1;
--echo # Reaping 'alter function f1 ...'
--reap
--echo # --> connection con2
connection con2;
--echo # Reaping 'call p1()'...
--reap
--echo # --> connection default
connection default;
drop function f1;
drop function f2;
drop procedure p1;
--echo #
--echo # 10) A select from information_schema.routines now
--echo # flushes the stored routines caches. Test that this
--echo # does not remove from the cache a stored routine
--echo # that is already prelocked.
--echo #
create function f1() returns int return get_lock("30977", 100000);
create function f2() returns int return 2;
delimiter |;
create function f3() returns varchar(255)
begin
declare res varchar(255);
declare c cursor for select routine_name from
information_schema.routines where routine_name='f1';
select f1() into @var;
open c;
fetch c into res;
close c;
select f2() into @var;
return res;
end|
delimiter ;|
--echo # --> connection con1
connection con1;
select get_lock("30977", 0);
--echo # --> connection default
connection default;
--echo # Sending 'select f3()'...
--send select f3()
--echo # --> connection con1
connection con1;
--echo # Waitng for 'select f3()' to get blocked on the user level lock...
let $wait_condition=select count(*)=1 from information_schema.processlist
where state='User lock' and info='select f1() into @var';
--source include/wait_condition.inc
--echo # Do something to change the cache version.
create function f4() returns int return 4;
drop function f4;
select release_lock("30977");
--echo # --> connection default
connection default;
--echo # Reaping 'select f3()'...
--echo # Routine 'f2()' should exist and get executed successfully.
--reap
select @var;
drop function f1;
drop function f2;
drop function f3;
--echo # 11) Check the situation when the connection is flushing the
--echo # SP cache which contains a procedure that is being executed.
--echo #
--echo # Function f1() calls p1(). Procedure p1() has a DROP
--echo # VIEW statement, which, we know, invalidates the routines cache.
--echo # During cache flush p1() must not be flushed since it's in
--echo # use.
--echo #
delimiter |;
create function f1() returns int
begin
call p1();
return 1;
end|
create procedure p1()
begin
create view v1 as select 1;
drop view v1;
select f1() into @var;
set @exec_count=@exec_count+1;
end|
delimiter ;|
set @exec_count=0;
--error ER_SP_RECURSION_LIMIT
call p1();
select @exec_count;
set @@session.max_sp_recursion_depth=5;
set @exec_count=0;
--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG
call p1();
select @exec_count;
drop procedure p1;
drop function f1;
set @@session.max_sp_recursion_depth=default;
--echo # --> connection con1
connection con1;
disconnect con1;
--source include/wait_until_disconnected.inc
--echo # --> connection con2
connection con2;
disconnect con2;
--source include/wait_until_disconnected.inc
--echo # --> connection default
connection default;
--echo #
--echo # End of 5.5 tests
--echo #

View file

@ -980,6 +980,57 @@ void unlock_table_names(THD *thd)
}
/**
Obtain an exclusive metadata lock on the stored routine name.
@param thd Thread handle.
@param is_function Stored routine type (only functions or procedures
are name-locked.
@param db The schema the routine belongs to.
@param name Routine name.
This function assumes that no metadata locks were acquired
before calling it. Additionally, it cannot be called while
holding LOCK_open mutex. Both these invariants are enforced by
asserts in MDL_context::acquire_exclusive_locks().
To avoid deadlocks, we do not try to obtain exclusive metadata
locks in LOCK TABLES mode, since in this mode there may be
other metadata locks already taken by the current connection,
and we must not wait for MDL locks while holding locks.
@retval FALSE Success.
@retval TRUE Failure: we're in LOCK TABLES mode, or out of memory,
or this connection was killed.
*/
bool lock_routine_name(THD *thd, bool is_function,
const char *db, const char *name)
{
MDL_key::enum_mdl_namespace mdl_type= (is_function ?
MDL_key::FUNCTION :
MDL_key::PROCEDURE);
MDL_request mdl_request;
if (thd->locked_tables_mode)
{
my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
return TRUE;
}
DBUG_ASSERT(name);
DEBUG_SYNC(thd, "before_wait_locked_pname");
mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE);
if (thd->mdl_context.acquire_exclusive_lock(&mdl_request))
return TRUE;
DEBUG_SYNC(thd, "after_wait_locked_pname");
return FALSE;
}
static void print_lock_error(int error, const char *table)
{
int textno;

View file

@ -2111,6 +2111,9 @@ void broadcast_refresh(void);
bool lock_table_names(THD *thd, TABLE_LIST *table_list);
void unlock_table_names(THD *thd);
/* Lock based on stored routine name */
bool lock_routine_name(THD *thd, bool is_function, const char *db,
const char *name);
/* old unireg functions */

168
sql/sp.cc
View file

@ -753,6 +753,11 @@ sp_create_routine(THD *thd, int type, sp_head *sp)
*/
thd->clear_current_stmt_binlog_row_based();
/* Grab an exclusive MDL lock. */
if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
sp->m_db.str, sp->m_name.str))
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
saved_count_cuted_fields= thd->count_cuted_fields;
thd->count_cuted_fields= CHECK_FIELD_WARN;
@ -919,7 +924,10 @@ sp_create_routine(THD *thd, int type, sp_head *sp)
ret= SP_OK;
if (table->file->ha_write_row(table->record[0]))
ret= SP_WRITE_ROW_FAILED;
else if (mysql_bin_log.is_open())
if (ret == SP_OK)
sp_cache_invalidate();
if (ret == SP_OK && mysql_bin_log.is_open())
{
thd->clear_error();
@ -948,7 +956,6 @@ sp_create_routine(THD *thd, int type, sp_head *sp)
FALSE, FALSE, 0);
thd->variables.sql_mode= 0;
}
}
done:
@ -994,6 +1001,11 @@ sp_drop_routine(THD *thd, int type, sp_name *name)
*/
thd->clear_current_stmt_binlog_row_based();
/* Grab an exclusive MDL lock. */
if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
name->m_db.str, name->m_name.str))
DBUG_RETURN(SP_DELETE_ROW_FAILED);
if (!(table= open_proc_table_for_update(thd)))
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK)
@ -1006,6 +1018,20 @@ sp_drop_routine(THD *thd, int type, sp_name *name)
{
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
sp_cache_invalidate();
/*
A lame workaround for lack of cache flush:
make sure the routine is at least gone from the
local cache.
*/
{
sp_head *sp;
sp_cache **spc= (type == TYPE_ENUM_FUNCTION ?
&thd->sp_func_cache : &thd->sp_proc_cache);
sp= sp_cache_lookup(spc, name);
if (sp)
sp_cache_flush_obsolete(spc, &sp);
}
}
close_thread_tables(thd);
@ -1041,6 +1067,12 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics)
DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
type == TYPE_ENUM_FUNCTION);
/* Grab an exclusive MDL lock. */
if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
name->m_db.str, name->m_name.str))
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
/*
This statement will be replicated as a statement, even when using
row-based replication. The flag will be reset at the end of the
@ -1052,6 +1084,30 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics)
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK)
{
if (type == TYPE_ENUM_FUNCTION && ! trust_function_creators &&
mysql_bin_log.is_open() &&
(chistics->daccess == SP_CONTAINS_SQL ||
chistics->daccess == SP_MODIFIES_SQL_DATA))
{
char *ptr;
bool is_deterministic;
ptr= get_field(thd->mem_root,
table->field[MYSQL_PROC_FIELD_DETERMINISTIC]);
if (ptr == NULL)
{
ret= SP_INTERNAL_ERROR;
goto err;
}
is_deterministic= ptr[0] == 'N' ? FALSE : TRUE;
if (!is_deterministic)
{
my_message(ER_BINLOG_UNSAFE_ROUTINE,
ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0));
ret= SP_INTERNAL_ERROR;
goto err;
}
}
store_record(table,record[1]);
table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time();
@ -1077,7 +1133,7 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics)
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
sp_cache_invalidate();
}
err:
close_thread_tables(thd);
DBUG_RETURN(ret);
}
@ -1161,10 +1217,7 @@ err:
bool
sp_show_create_routine(THD *thd, int type, sp_name *name)
{
bool err_status= TRUE;
sp_head *sp;
sp_cache **cache = type == TYPE_ENUM_PROCEDURE ?
&thd->sp_proc_cache : &thd->sp_func_cache;
DBUG_ENTER("sp_show_create_routine");
DBUG_PRINT("enter", ("name: %.*s",
@ -1174,28 +1227,29 @@ sp_show_create_routine(THD *thd, int type, sp_name *name)
DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
type == TYPE_ENUM_FUNCTION);
if (type == TYPE_ENUM_PROCEDURE)
/*
@todo: Consider using prelocking for this code as well. Currently
SHOW CREATE PROCEDURE/FUNCTION is a dirty read of the data
dictionary, i.e. takes no metadata locks.
It is "safe" to do as long as it doesn't affect the results
of the binary log or the query cache, which currently it does not.
*/
if (sp_cache_routine(thd, type, name, FALSE, &sp))
DBUG_RETURN(TRUE);
if (sp == NULL || sp->show_create_routine(thd, type))
{
/*
SHOW CREATE PROCEDURE may require two instances of one sp_head
object when SHOW CREATE PROCEDURE is called for the procedure that
is being executed. Basically, there is no actual recursion, so we
increase the recursion limit for this statement (kind of hack).
SHOW CREATE FUNCTION does not require this because SHOW CREATE
statements are prohibitted within stored functions.
*/
thd->variables.max_sp_recursion_depth++;
If we have insufficient privileges, pretend the routine
does not exist.
*/
my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE",
name->m_name.str);
DBUG_RETURN(TRUE);
}
if ((sp= sp_find_routine(thd, type, name, cache, FALSE)))
err_status= sp->show_create_routine(thd, type);
if (type == TYPE_ENUM_PROCEDURE)
thd->variables.max_sp_recursion_depth--;
DBUG_RETURN(err_status);
DBUG_RETURN(FALSE);
}
@ -1451,6 +1505,7 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn);
prelocking_ctx->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next);
rn->belong_to_view= belong_to_view;
rn->m_sp_cache_version= 0;
return TRUE;
}
return FALSE;
@ -1596,41 +1651,81 @@ void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
}
/**
A helper wrapper around sp_cache_routine() to use from
prelocking until 'sp_name' is eradicated as a class.
*/
int sp_cache_routine(THD *thd, Sroutine_hash_entry *rt,
bool lookup_only, sp_head **sp)
{
char qname_buff[NAME_LEN*2+1+1];
sp_name name(&rt->mdl_request.key, qname_buff);
MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace();
int type= ((mdl_type == MDL_key::FUNCTION) ?
TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE);
/*
Check that we have an MDL lock on this routine, unless it's a top-level
CALL. The assert below should be unambiguous: the first element
in sroutines_list has an MDL lock unless it's a top-level call, or a
trigger, but triggers can't occur here (see the preceding assert).
*/
DBUG_ASSERT(rt->mdl_request.ticket ||
rt == (Sroutine_hash_entry*) thd->lex->sroutines_list.first);
return sp_cache_routine(thd, type, &name, lookup_only, sp);
}
/**
Ensure that routine is present in cache by loading it from the mysql.proc
table if needed. Emit an appropriate error if there was a problem during
table if needed. If the routine is present but old, reload it.
Emit an appropriate error if there was a problem during
loading.
@param[in] thd Thread context.
@param[in] type Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE).
@param[in] name Name of routine.
@param[in] lookup_only Only check that the routine is in the cache.
If it's not, don't try to load. If it is present,
but old, don't try to reload.
@param[out] sp Pointer to sp_head object for routine, NULL if routine was
not found,
not found.
@retval 0 Either routine is found and was succesfully loaded into cache
or it does not exist.
@retval non-0 Error while loading routine from mysql,proc table.
*/
int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp)
int sp_cache_routine(THD *thd, int type, sp_name *name,
bool lookup_only, sp_head **sp)
{
int ret= 0;
sp_cache **spc= (type == TYPE_ENUM_FUNCTION ?
&thd->sp_func_cache : &thd->sp_proc_cache);
DBUG_ENTER("sp_cache_routine");
DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE);
if (!(*sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
&thd->sp_func_cache : &thd->sp_proc_cache),
name)))
*sp= sp_cache_lookup(spc, name);
if (lookup_only)
DBUG_RETURN(SP_OK);
if (*sp)
{
sp_cache_flush_obsolete(spc, sp);
if (*sp)
DBUG_RETURN(SP_OK);
}
switch ((ret= db_find_routine(thd, type, name, sp)))
{
switch ((ret= db_find_routine(thd, type, name, sp)))
{
case SP_OK:
if (type == TYPE_ENUM_FUNCTION)
sp_cache_insert(&thd->sp_func_cache, *sp);
else
sp_cache_insert(&thd->sp_proc_cache, *sp);
sp_cache_insert(spc, *sp);
break;
case SP_KEY_NOT_FOUND:
ret= SP_OK;
@ -1669,7 +1764,6 @@ int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp)
my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
}
break;
}
}
DBUG_RETURN(ret);
}

View file

@ -43,7 +43,13 @@ sp_find_routine(THD *thd, int type, sp_name *name,
sp_cache **cp, bool cache_only);
int
sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp);
sp_cache_routine(THD *thd, Sroutine_hash_entry *rt,
bool lookup_only, sp_head **sp);
int
sp_cache_routine(THD *thd, int type, sp_name *name,
bool lookup_only, sp_head **sp);
bool
sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any);
@ -88,6 +94,16 @@ public:
statement uses routine both via view and directly.
*/
TABLE_LIST *belong_to_view;
/**
This is for prepared statement validation purposes.
A statement looks up and pre-loads all its stored functions
at prepare. Later on, if a function is gone from the cache,
execute may fail.
Remember the version of sp_head at prepare to be able to
invalidate the prepared statement at execute if it
changes.
*/
ulong m_sp_cache_version;
};

View file

@ -31,8 +31,6 @@ static ulong volatile Cversion= 0;
class sp_cache
{
public:
ulong version;
sp_cache();
~sp_cache();
@ -48,25 +46,10 @@ public:
namelen);
}
#ifdef NOT_USED
inline bool remove(char *name, uint namelen)
inline void remove(sp_head *sp)
{
sp_head *sp= lookup(name, namelen);
if (sp)
{
hash_delete(&m_hashtable, (uchar *)sp);
return TRUE;
}
return FALSE;
my_hash_delete(&m_hashtable, (uchar *)sp);
}
#endif
inline void remove_all()
{
cleanup();
init();
}
private:
void init();
void cleanup();
@ -129,8 +112,9 @@ void sp_cache_insert(sp_cache **cp, sp_head *sp)
{
if (!(c= new sp_cache()))
return; // End of memory error
c->version= Cversion; // No need to lock when reading long variable
}
/* Reading a ulong variable with no lock. */
sp->set_sp_cache_version(Cversion);
DBUG_PRINT("info",("sp_cache: inserting: %.*s", (int) sp->m_qname.length,
sp->m_qname.str));
c->insert(sp);
@ -181,46 +165,34 @@ void sp_cache_invalidate()
}
/*
Remove out-of-date SPs from the cache.
SYNOPSIS
sp_cache_flush_obsolete()
cp Cache to flush
/**
Remove an out-of-date SP from the cache.
NOTE
This invalidates pointers to sp_head objects this thread uses.
In practice that means 'dont call this function when inside SP'.
@param[in] cp Cache to flush
@param[in] sp SP to remove.
@note This invalidates pointers to sp_head objects this thread
uses. In practice that means 'dont call this function when
inside SP'.
*/
void sp_cache_flush_obsolete(sp_cache **cp)
void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp)
{
sp_cache *c= *cp;
if (c)
if ((*sp)->sp_cache_version() < Cversion && !(*sp)->is_invoked())
{
ulong v;
v= Cversion; // No need to lock when reading long variable
if (c->version < v)
{
DBUG_PRINT("info",("sp_cache: deleting all functions"));
/* We need to delete all elements. */
c->remove_all();
c->version= v;
}
(*cp)->remove(*sp);
*sp= NULL;
}
}
/**
Return the current version of the cache.
Return the current global version of the cache.
*/
ulong sp_cache_version(sp_cache **cp)
ulong sp_cache_version()
{
sp_cache *c= *cp;
if (c)
return c->version;
return 0;
return Cversion;
}
@ -265,7 +237,6 @@ sp_cache::init()
{
my_hash_init(&m_hashtable, system_charset_info, 0, 0, 0,
hash_get_key_for_sp_head, hash_free_sp_head, 0);
version= 0;
}

View file

@ -57,7 +57,7 @@ void sp_cache_clear(sp_cache **cp);
void sp_cache_insert(sp_cache **cp, sp_head *sp);
sp_head *sp_cache_lookup(sp_cache **cp, sp_name *name);
void sp_cache_invalidate();
void sp_cache_flush_obsolete(sp_cache **cp);
ulong sp_cache_version(sp_cache **cp);
void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp);
ulong sp_cache_version();
#endif /* _SP_CACHE_H_ */

View file

@ -511,7 +511,10 @@ sp_head::operator delete(void *ptr, size_t size) throw()
sp_head::sp_head()
:Query_arena(&main_mem_root, INITIALIZED_FOR_SP),
m_flags(0), m_recursion_level(0), m_next_cached_sp(0),
m_flags(0),
m_sp_cache_version(0),
m_recursion_level(0),
m_next_cached_sp(0),
m_cont_level(0)
{
const LEX_STRING str_reset= { NULL, 0 };
@ -727,16 +730,6 @@ create_typelib(MEM_ROOT *mem_root, Create_field *field_def, List<String> *src)
}
int
sp_head::create(THD *thd)
{
DBUG_ENTER("sp_head::create");
DBUG_PRINT("info", ("type: %d name: %s params: %s body: %s",
m_type, m_name.str, m_params.str, m_body.str));
DBUG_RETURN(sp_create_routine(thd, m_type, this));
}
sp_head::~sp_head()
{
DBUG_ENTER("sp_head::~sp_head");

View file

@ -175,7 +175,34 @@ public:
LEX_STRING m_definer_user;
LEX_STRING m_definer_host;
/**
Is this routine being executed?
*/
bool is_invoked() const { return m_flags & IS_INVOKED; }
/**
Get the value of the SP cache version, as remembered
when the routine was inserted into the cache.
*/
ulong sp_cache_version() const { return m_sp_cache_version; }
/** Set the value of the SP cache version. */
void set_sp_cache_version(ulong version_arg)
{
m_sp_cache_version= version_arg;
}
private:
/**
Version of the stored routine cache at the moment when the
routine was added to it. Is used only for functions and
procedures, not used for triggers or events. When sp_head is
created, its version is 0. When it's added to the cache, the
version is assigned the global value 'Cversion'.
If later on Cversion is incremented, we know that the routine
is obsolete and should not be used --
sp_cache_flush_obsolete() will purge it.
*/
ulong m_sp_cache_version;
Stored_program_creation_ctx *m_creation_ctx;
public:
@ -263,9 +290,6 @@ public:
void
set_stmt_end(THD *thd);
int
create(THD *thd);
virtual ~sp_head();
/// Free memory

View file

@ -21,6 +21,7 @@
#include "sql_select.h"
#include "sp_head.h"
#include "sp.h"
#include "sp_cache.h"
#include "sql_trigger.h"
#include "transaction.h"
#include "sql_prepare.h"
@ -3479,6 +3480,66 @@ check_and_update_table_version(THD *thd,
}
/**
Compares versions of a stored routine obtained from the sp cache
and the version used at prepare.
@details If the new and the old values mismatch, invoke
Metadata_version_observer.
At prepared statement prepare, all Sroutine_hash_entry version values
are NULL and we always have a mismatch. But there is no observer set
in THD, and therefore no error is reported. Instead, we update
the value in Sroutine_hash_entry, effectively recording the original
version.
At prepared statement execute, an observer may be installed. If
there is a version mismatch, we push an error and return TRUE.
For conventional execution (no prepared statements), the
observer is never installed.
@param[in] thd used to report errors
@param[in/out] rt pointer to stored routine entry in the
parse tree
@param[in] sp pointer to stored routine cache entry.
Can be NULL if there is no such routine.
@retval TRUE an error, which has been reported
@retval FALSE success, version in Sroutine_hash_entry has been updated
*/
static bool
check_and_update_routine_version(THD *thd, Sroutine_hash_entry *rt,
sp_head *sp)
{
ulong spc_version= sp_cache_version();
/* sp is NULL if there is no such routine. */
ulong version= sp ? sp->sp_cache_version() : spc_version;
/*
If the version in the parse tree is stale,
or the version in the cache is stale and sp is not used,
we need to reprepare.
Sic: version != spc_version <--> sp is not NULL.
*/
if (rt->m_sp_cache_version != version ||
(version != spc_version && !sp->is_invoked()))
{
if (thd->m_reprepare_observer &&
thd->m_reprepare_observer->report_error(thd))
{
/*
Version of the sp cache is different from the
previous execution of the prepared statement, and it is
unacceptable for this SQLCOM. Error has been reported.
*/
DBUG_ASSERT(thd->is_error());
return TRUE;
}
/* Always maintain the latest cache version. */
rt->m_sp_cache_version= version;
}
return FALSE;
}
/**
Open view by getting its definition from disk (and table cache in future).
@ -3696,13 +3757,16 @@ request_backoff_action(enum_open_table_action action_arg)
/**
Recover from failed attempt ot open table by performing requested action.
Recover from failed attempt of open table by performing requested action.
@param thd Thread context
@param table Table list element for table that caused problem
@param action Type of action requested by failed open_table() call
@param mdl_request MDL_request of the object that caused the problem.
@param table Optional (can be NULL). Used only if action is OT_REPAIR.
In that case a TABLE_LIST for the table to be repaired.
@todo: It's unnecessary and should be removed.
@pre This function should be called only with "action" != OT_NO_ACTION.
@pre This function should be called only with "action" != OT_NO_ACTION
and after having called @sa close_tables_for_reopen().
@retval FALSE - Success. One should try to open tables once again.
@retval TRUE - Error
@ -3710,7 +3774,8 @@ request_backoff_action(enum_open_table_action action_arg)
bool
Open_table_context::
recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table)
recover_from_failed_open(THD *thd, MDL_request *mdl_request,
TABLE_LIST *table)
{
bool result= FALSE;
/* Execute the action. */
@ -3723,14 +3788,20 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table)
break;
case OT_DISCOVER:
{
MDL_request mdl_xlock_request(&table->mdl_request);
MDL_request mdl_xlock_request(mdl_request);
mdl_xlock_request.set_type(MDL_EXCLUSIVE);
if ((result=
thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request)))
break;
DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE);
pthread_mutex_lock(&LOCK_open);
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name);
ha_create_table_from_engine(thd, table->db, table->table_name);
tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
mdl_request->key.db_name(),
mdl_request->key.name());
ha_create_table_from_engine(thd,
mdl_request->key.db_name(),
mdl_request->key.name());
pthread_mutex_unlock(&LOCK_open);
thd->warning_info->clear_warning_info(thd->query_id);
@ -3740,14 +3811,17 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table)
}
case OT_REPAIR:
{
MDL_request mdl_xlock_request(&table->mdl_request);
MDL_request mdl_xlock_request(mdl_request);
mdl_xlock_request.set_type(MDL_EXCLUSIVE);
if ((result=
thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request)))
break;
DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE);
pthread_mutex_lock(&LOCK_open);
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name);
tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
mdl_request->key.db_name(),
mdl_request->key.name());
pthread_mutex_unlock(&LOCK_open);
result= auto_repair_table(thd, table);
@ -3808,7 +3882,11 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table)
@param[in] prelocking_strategy Strategy which specifies how the
prelocking set should be extended when
one of its elements is processed.
@param[out] need_prelocking Set to TRUE if it was detected that this
@param[in] has_prelocking_list Indicates that prelocking set/list for
this statement has already been built.
@param[in] ot_ctx Context of open_table used to recover from
locking failures.
@param[out] need_prelocking Set to TRUE if it was detected that this
statement will require prelocked mode for
its execution, not touched otherwise.
@ -3820,32 +3898,99 @@ static bool
open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
Sroutine_hash_entry *rt,
Prelocking_strategy *prelocking_strategy,
bool has_prelocking_list,
Open_table_context *ot_ctx,
bool *need_prelocking)
{
MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace();
DBUG_ENTER("open_and_process_routine");
switch (rt->mdl_request.key.mdl_namespace())
switch (mdl_type)
{
case MDL_key::FUNCTION:
case MDL_key::PROCEDURE:
{
char qname_buff[NAME_LEN*2+1+1];
sp_name name(&rt->mdl_request.key, qname_buff);
sp_head *sp;
int type= (rt->mdl_request.key.mdl_namespace() == MDL_key::FUNCTION) ?
TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE;
if (sp_cache_routine(thd, type, &name, &sp))
DBUG_RETURN(TRUE);
if (sp)
/*
Try to get MDL lock on the routine.
Note that we do not take locks on top-level CALLs as this can
lead to a deadlock. Not locking top-level CALLs does not break
the binlog as only the statements in the called procedure show
up there, not the CALL itself.
*/
if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first ||
mdl_type != MDL_key::PROCEDURE)
{
prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
need_prelocking);
ot_ctx->add_request(&rt->mdl_request);
if (thd->mdl_context.try_acquire_shared_lock(&rt->mdl_request))
DBUG_RETURN(TRUE);
if (rt->mdl_request.ticket == NULL)
{
/* A lock conflict. Someone's trying to modify SP metadata. */
ot_ctx->request_backoff_action(Open_table_context::OT_WAIT);
DBUG_RETURN(TRUE);
}
DEBUG_SYNC(thd, "after_shared_lock_pname");
/* Ensures the routine is up-to-date and cached, if exists. */
if (sp_cache_routine(thd, rt, has_prelocking_list, &sp))
DBUG_RETURN(TRUE);
/* Remember the version of the routine in the parse tree. */
if (check_and_update_routine_version(thd, rt, sp))
DBUG_RETURN(TRUE);
/* 'sp' is NULL when there is no such routine. */
if (sp && !has_prelocking_list)
{
prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
need_prelocking);
}
}
else
{
/*
If it's a top level call, just make sure we have a recent
version of the routine, if it exists.
Validating routine version is unnecessary, since CALL
does not affect the prepared statement prelocked list.
*/
sp_cache_routine(thd, rt, FALSE, &sp);
}
}
break;
case MDL_key::TRIGGER:
/**
We add trigger entries to lex->sroutines_list, but we don't
load them here. The trigger entry is only used when building
a transitive closure of objects used in a statement, to avoid
adding to this closure objects that are used in the trigger more
than once.
E.g. if a trigger trg refers to table t2, and the trigger table t1
is used multiple times in the statement (say, because it's used in
function f1() twice), we will only add t2 once to the list of
tables to prelock.
We don't take metadata locks on triggers either: they are protected
by a respective lock on the table, on which the trigger is defined.
The only two cases which give "trouble" are SHOW CREATE TRIGGER
and DROP TRIGGER statements. For these, statement syntax doesn't
specify the table on which this trigger is defined, so we have
to make a "dirty" read in the data dictionary to find out the
table name. Once we discover the table name, we take a metadata
lock on it, and this protects all trigger operations.
Of course the table, in theory, may disappear between the dirty
read and metadata lock acquisition, but in that case we just return
a run-time error.
Grammar of other trigger DDL statements (CREATE, DROP) requires
the table to be specified explicitly, so we use the table metadata
lock to protect trigger metadata in these statements. Similarly, in
DML we always use triggers together with their tables, and thus don't
need to take separate metadata locks on them.
*/
break;
default:
/* Impossible type value. */
@ -3965,7 +4110,7 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables,
if (error)
{
if (! ot_ctx->can_recover_from_failed_open_table() && safe_to_ignore_table)
if (! ot_ctx->can_recover_from_failed_open() && safe_to_ignore_table)
{
DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'",
tables->db, tables->alias));
@ -4150,7 +4295,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
Open_table_context ot_ctx(thd);
bool error= FALSE;
MEM_ROOT new_frm_mem;
bool has_prelocking_list= thd->lex->requires_prelocking();
bool has_prelocking_list;
DBUG_ENTER("open_tables");
/*
@ -4172,7 +4317,8 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
init_sql_alloc(&new_frm_mem, 8024, 8024);
thd->current_tablenr= 0;
restart:
restart:
has_prelocking_list= thd->lex->requires_prelocking();
table_to_open= start;
sroutine_to_open= (Sroutine_hash_entry**) &thd->lex->sroutines_list.first;
*counter= 0;
@ -4184,7 +4330,6 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
*/
while (*table_to_open ||
(thd->locked_tables_mode <= LTM_LOCK_TABLES &&
! has_prelocking_list &&
*sroutine_to_open))
{
/*
@ -4201,7 +4346,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
if (error)
{
if (ot_ctx.can_recover_from_failed_open_table())
if (ot_ctx.can_recover_from_failed_open())
{
/*
We have met exclusive metadata lock or old version of table.
@ -4220,12 +4365,14 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
*/
TABLE_LIST *failed_table= *table_to_open;
close_tables_for_reopen(thd, start);
/*
Here we rely on the fact that 'tables' still points to the valid
TABLE_LIST element. Altough currently this assumption is valid
it may change in future.
*/
if (ot_ctx.recover_from_failed_open_table_attempt(thd, failed_table))
if (ot_ctx.recover_from_failed_open(thd, &failed_table->mdl_request,
failed_table))
goto err;
error= FALSE;
@ -4239,8 +4386,11 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
If we are not already in prelocked mode and extended table list is
not yet built for our statement we need to cache routines it uses
and build the prelocking list for it.
If we are not in prelocked mode but have built the extended table
list, we still need to call open_and_process_routine() to take
MDL locks on the routines.
*/
if (thd->locked_tables_mode <= LTM_LOCK_TABLES && ! has_prelocking_list)
if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
{
bool need_prelocking= FALSE;
TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
@ -4256,12 +4406,21 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
for (Sroutine_hash_entry *rt= *sroutine_to_open; rt;
sroutine_to_open= &rt->next, rt= rt->next)
{
error= open_and_process_routine(thd, thd->lex, rt,
prelocking_strategy,
error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy,
has_prelocking_list, &ot_ctx,
&need_prelocking);
if (error)
{
if (ot_ctx.can_recover_from_failed_open())
{
close_tables_for_reopen(thd, start);
if (ot_ctx.recover_from_failed_open(thd, &rt->mdl_request, NULL))
goto err;
error= FALSE;
goto restart;
}
/*
Serious error during reading stored routines from mysql.proc table.
Something is wrong with the table or its contents, and an error has
@ -4666,7 +4825,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
retry:
while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, 0)) &&
ot_ctx.can_recover_from_failed_open_table())
ot_ctx.can_recover_from_failed_open())
{
/* We can't back off with an open HANDLER, we don't wait with locks. */
DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL);
@ -4677,7 +4836,8 @@ retry:
*/
thd->mdl_context.release_transactional_locks();
table_list->mdl_request.ticket= 0;
if (ot_ctx.recover_from_failed_open_table_attempt(thd, table_list))
if (ot_ctx.recover_from_failed_open(thd, &table_list->mdl_request,
table_list))
break;
}
@ -5242,11 +5402,18 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
if (first_not_own_table == *tables)
*tables= 0;
thd->lex->chop_off_not_own_tables();
/* Reset MDL tickets for procedures/functions */
for (Sroutine_hash_entry *rt=
(Sroutine_hash_entry*)thd->lex->sroutines_list.first;
rt; rt= rt->next)
rt->mdl_request.ticket= NULL;
sp_remove_not_own_routines(thd->lex);
for (tmp= *tables; tmp; tmp= tmp->next_global)
{
tmp->table= 0;
tmp->mdl_request.ticket= NULL;
/* We have to cleanup translation tables of views. */
tmp->cleanup_items();
}
/*
Metadata lock requests for tables from extended part of prelocking set
@ -8363,6 +8530,10 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests)
MDL_request_list::Iterator it(*mdl_requests);
while ((mdl_request= it++))
{
/* Skip requests on non-TDC objects. */
if (mdl_request->key.mdl_namespace() != MDL_key::TABLE)
continue;
if ((share= get_cached_table_share(mdl_request->key.db_name(),
mdl_request->key.name())) &&
share->version != refresh_version)

View file

@ -1271,7 +1271,7 @@ private:
/**
A context of open_tables() function, used to recover
from a failed open_table() attempt.
from a failed open_table() or open_routine() attempt.
Implemented in sql_base.cc.
*/
@ -1288,13 +1288,14 @@ public:
};
Open_table_context(THD *thd);
bool recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *tables);
bool recover_from_failed_open(THD *thd, MDL_request *mdl_request,
TABLE_LIST *table);
bool request_backoff_action(enum_open_table_action action_arg);
void add_request(MDL_request *request)
{ m_mdl_requests.push_front(request); }
bool can_recover_from_failed_open_table() const
bool can_recover_from_failed_open() const
{ return m_action != OT_NO_ACTION; }
bool can_deadlock() const { return m_can_deadlock; }
private:

View file

@ -284,14 +284,14 @@ void init_update_queries(void)
sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_REVOKE_ALL]= CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA;
@ -319,10 +319,6 @@ void init_update_queries(void)
sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_PROCEDURE]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_SPFUNCTION]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_PROCEDURE]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_FUNCTION]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS;
@ -1988,9 +1984,10 @@ mysql_execute_command(THD *thd)
#endif
case SQLCOM_SHOW_STATUS_PROC:
case SQLCOM_SHOW_STATUS_FUNC:
if (!(res= check_table_access(thd, SELECT_ACL, all_tables, FALSE,
if ((res= check_table_access(thd, SELECT_ACL, all_tables, FALSE,
UINT_MAX, FALSE)))
res= execute_sqlcom_select(thd, all_tables);
goto error;
res= execute_sqlcom_select(thd, all_tables);
break;
case SQLCOM_SHOW_STATUS:
{
@ -3939,7 +3936,7 @@ end_with_restore_list:
if (sp_process_definer(thd))
goto create_sp_error;
res= (sp_result= lex->sphead->create(thd));
res= (sp_result= sp_create_routine(thd, lex->sphead->m_type, lex->sphead));
switch (sp_result) {
case SP_OK: {
#ifndef NO_EMBEDDED_ACCESS_CHECKS
@ -3949,6 +3946,16 @@ end_with_restore_list:
bool restore_backup_context= false;
Security_context *backup= NULL;
LEX_USER *definer= thd->lex->definer;
/*
We're going to issue an implicit GRANT statement.
It takes metadata locks and updates system tables.
Make sure that sp_create_routine() did not leave any
locks in the MDL context, so there is no risk to
deadlock.
*/
trans_commit_implicit(thd);
close_thread_tables(thd);
thd->mdl_context.release_transactional_locks();
/*
Check if the definer exists on slave,
then use definer privilege to insert routine privileges to mysql.procs_priv.
@ -4021,7 +4028,6 @@ create_sp_error:
case SQLCOM_CALL:
{
sp_head *sp;
/*
This will cache all SP and SF and open and lock all tables
required for execution.
@ -4117,65 +4123,22 @@ create_sp_error:
case SQLCOM_ALTER_FUNCTION:
{
int sp_result;
sp_head *sp;
st_sp_chistics chistics;
int type= (lex->sql_command == SQLCOM_ALTER_PROCEDURE ?
TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
memcpy(&chistics, &lex->sp_chistics, sizeof(chistics));
if (lex->sql_command == SQLCOM_ALTER_PROCEDURE)
sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname,
&thd->sp_proc_cache, FALSE);
else
sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname,
&thd->sp_func_cache, FALSE);
thd->warning_info->opt_clear_warning_info(thd->query_id);
if (! sp)
{
if (lex->spname->m_db.str)
sp_result= SP_KEY_NOT_FOUND;
else
{
my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
goto error;
}
}
else
{
if (check_routine_access(thd, ALTER_PROC_ACL, sp->m_db.str,
sp->m_name.str,
lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0))
goto error;
if (check_routine_access(thd, ALTER_PROC_ACL, lex->spname->m_db.str,
lex->spname->m_name.str,
lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0))
goto error;
memcpy(&lex->sp_chistics, &chistics, sizeof(lex->sp_chistics));
if ((sp->m_type == TYPE_ENUM_FUNCTION) &&
!trust_function_creators && mysql_bin_log.is_open() &&
!sp->m_chistics->detistic &&
(chistics.daccess == SP_CONTAINS_SQL ||
chistics.daccess == SP_MODIFIES_SQL_DATA))
{
my_message(ER_BINLOG_UNSAFE_ROUTINE,
ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0));
sp_result= SP_INTERNAL_ERROR;
}
else
{
/*
Note that if you implement the capability of ALTER FUNCTION to
alter the body of the function, this command should be made to
follow the restrictions that log-bin-trust-function-creators=0
already puts on CREATE FUNCTION.
*/
/* Conditionally writes to binlog */
int type= lex->sql_command == SQLCOM_ALTER_PROCEDURE ?
TYPE_ENUM_PROCEDURE :
TYPE_ENUM_FUNCTION;
sp_result= sp_update_routine(thd,
type,
lex->spname,
&lex->sp_chistics);
}
}
/*
Note that if you implement the capability of ALTER FUNCTION to
alter the body of the function, this command should be made to
follow the restrictions that log-bin-trust-function-creators=0
already puts on CREATE FUNCTION.
*/
/* Conditionally writes to binlog */
sp_result= sp_update_routine(thd, type, lex->spname, &lex->sp_chistics);
switch (sp_result)
{
case SP_OK:
@ -4199,6 +4162,12 @@ create_sp_error:
int type= (lex->sql_command == SQLCOM_DROP_PROCEDURE ?
TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
/*
@todo: here we break the metadata locking protocol by
looking up the information about the routine without
a metadata lock. Rewrite this piece to make sp_drop_routine
return whether the routine existed or not.
*/
sp_result= sp_routine_exists_in_table(thd, type, lex->spname);
thd->warning_info->opt_clear_warning_info(thd->query_id);
if (sp_result == SP_OK)
@ -4210,30 +4179,30 @@ create_sp_error:
lex->sql_command == SQLCOM_DROP_PROCEDURE, 0))
goto error;
if (trans_commit_implicit(thd))
goto error;
close_thread_tables(thd);
thd->mdl_context.release_transactional_locks();
/* Conditionally writes to binlog */
sp_result= sp_drop_routine(thd, type, lex->spname);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
/*
We're going to issue an implicit REVOKE statement.
It takes metadata locks and updates system tables.
Make sure that sp_create_routine() did not leave any
locks in the MDL context, so there is no risk to
deadlock.
*/
trans_commit_implicit(thd);
close_thread_tables(thd);
thd->mdl_context.release_transactional_locks();
if (sp_automatic_privileges && !opt_noacl &&
sp_revoke_privileges(thd, db, name,
sp_revoke_privileges(thd, db, name,
lex->sql_command == SQLCOM_DROP_PROCEDURE))
{
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_PROC_AUTO_REVOKE_FAIL,
ER(ER_PROC_AUTO_REVOKE_FAIL));
}
#endif
/* Conditionally writes to binlog */
int type= lex->sql_command == SQLCOM_DROP_PROCEDURE ?
TYPE_ENUM_PROCEDURE :
TYPE_ENUM_FUNCTION;
sp_result= sp_drop_routine(thd, type, lex->spname);
}
else
{
@ -4292,21 +4261,13 @@ create_sp_error:
case SQLCOM_SHOW_CREATE_PROC:
{
if (sp_show_create_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname))
{
my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
SP_COM_STRING(lex), lex->spname->m_name.str);
goto error;
}
goto error;
break;
}
case SQLCOM_SHOW_CREATE_FUNC:
{
if (sp_show_create_routine(thd, TYPE_ENUM_FUNCTION, lex->spname))
{
my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
SP_COM_STRING(lex), lex->spname->m_name.str);
goto error;
}
break;
}
case SQLCOM_SHOW_PROC_CODE:
@ -4314,13 +4275,11 @@ create_sp_error:
{
#ifndef DBUG_OFF
sp_head *sp;
int type= (lex->sql_command == SQLCOM_SHOW_PROC_CODE ?
TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
if (lex->sql_command == SQLCOM_SHOW_PROC_CODE)
sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname,
&thd->sp_proc_cache, FALSE);
else
sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname,
&thd->sp_func_cache, FALSE);
if (sp_cache_routine(thd, type, lex->spname, FALSE, &sp))
goto error;
if (!sp || sp->show_routine_code(thd))
{
/* We don't distinguish between errors for now */
@ -5579,9 +5538,6 @@ void mysql_parse(THD *thd, const char *inBuf, uint length,
{
LEX *lex= thd->lex;
sp_cache_flush_obsolete(&thd->sp_proc_cache);
sp_cache_flush_obsolete(&thd->sp_func_cache);
Parser_state parser_state(thd, inBuf, length);
bool err= parse_sql(thd, & parser_state, NULL);

View file

@ -173,8 +173,6 @@ private:
SELECT_LEX and other classes).
*/
MEM_ROOT main_mem_root;
/* Version of the stored functions cache at the time of prepare. */
ulong m_sp_cache_version;
private:
bool set_db(const char *db, uint db_length);
bool set_parameters(String *expanded_query,
@ -2138,9 +2136,6 @@ void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length)
DBUG_VOID_RETURN;
}
sp_cache_flush_obsolete(&thd->sp_proc_cache);
sp_cache_flush_obsolete(&thd->sp_func_cache);
thd->protocol= &thd->protocol_binary;
if (stmt->prepare(packet, packet_length))
@ -2419,6 +2414,13 @@ void reinit_stmt_before_use(THD *thd, LEX *lex)
{
tables->reinit_before_use(thd);
}
/* Reset MDL tickets for procedures/functions */
for (Sroutine_hash_entry *rt=
(Sroutine_hash_entry*)thd->lex->sroutines_list.first;
rt; rt= rt->next)
rt->mdl_request.ticket= NULL;
/*
Cleanup of the special case of DELETE t1, t2 FROM t1, t2, t3 ...
(multi-delete). We do a full clean up, although at the moment all we
@ -2512,9 +2514,6 @@ void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
DBUG_PRINT("exec_query", ("%s", stmt->query()));
DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt));
sp_cache_flush_obsolete(&thd->sp_proc_cache);
sp_cache_flush_obsolete(&thd->sp_func_cache);
open_cursor= test(flags & (ulong) CURSOR_TYPE_READ_ONLY);
thd->protocol= &thd->protocol_binary;
@ -2964,8 +2963,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg)
param_array(0),
param_count(0),
last_errno(0),
flags((uint) IS_IN_USE),
m_sp_cache_version(0)
flags((uint) IS_IN_USE)
{
init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size,
thd_arg->variables.query_prealloc_size);
@ -3234,20 +3232,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
init_stmt_after_parse(lex);
state= Query_arena::PREPARED;
flags&= ~ (uint) IS_IN_USE;
/*
This is for prepared statement validation purposes.
A statement looks up and pre-loads all its stored functions
at prepare. Later on, if a function is gone from the cache,
execute may fail.
Remember the cache version to be able to invalidate the prepared
statement at execute if it changes.
We only need to care about version of the stored functions cache:
if a prepared statement uses a stored procedure, it's indirect,
via a stored function. The only exception is SQLCOM_CALL,
but the latter one looks up the stored procedure each time
it's invoked, rather than once at prepare.
*/
m_sp_cache_version= sp_cache_version(&thd->sp_func_cache);
/*
Log COM_EXECUTE to the general log. Note, that in case of SQL
@ -3588,13 +3572,12 @@ Prepared_statement::swap_prepared_statement(Prepared_statement *copy)
is allocated in the old arena.
*/
swap_variables(Item_param **, param_array, copy->param_array);
/* Swap flags: this is perhaps unnecessary */
swap_variables(uint, flags, copy->flags);
/* Don't swap flags: the copy has IS_SQL_PREPARE always set. */
/* swap_variables(uint, flags, copy->flags); */
/* Swap names, the old name is allocated in the wrong memory root */
swap_variables(LEX_STRING, name, copy->name);
/* Ditto */
swap_variables(char *, db, copy->db);
swap_variables(ulong, m_sp_cache_version, copy->m_sp_cache_version);
DBUG_ASSERT(db_length == copy->db_length);
DBUG_ASSERT(param_count == copy->param_count);
@ -3653,19 +3636,6 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
return TRUE;
}
/*
Reprepare the statement if we're using stored functions
and the version of the stored routines cache has changed.
*/
if (lex->uses_stored_routines() &&
m_sp_cache_version != sp_cache_version(&thd->sp_func_cache) &&
thd->m_reprepare_observer &&
thd->m_reprepare_observer->report_error(thd))
{
return TRUE;
}
/*
For SHOW VARIABLES lex->result is NULL, as it's a non-SELECT
command. For such queries we don't return an error and don't

View file

@ -3237,7 +3237,6 @@ end_share:
end_unlock:
pthread_mutex_unlock(&LOCK_open);
end:
thd->mdl_context.release_lock(table_list.mdl_request.ticket);
thd->clear_error();
return res;

View file

@ -21,6 +21,7 @@
#include <myisam.h>
#include <my_dir.h>
#include "sp_head.h"
#include "sp.h"
#include "sql_trigger.h"
#include "sql_show.h"
#include "transaction.h"
@ -5010,6 +5011,23 @@ send_result_message:
trans_commit_implicit(thd);
close_thread_tables(thd);
table->table=0; // For query cache
/*
If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run
separate open_tables() for each CHECK TABLE argument.
Right now we do not have a separate method to reset the prelocking
state in the lex to the state after parsing, so each open will pollute
this state: add elements to lex->srotuines_list, TABLE_LISTs to
lex->query_tables. Below is a lame attempt to recover from this
pollution.
@todo: have a method to reset a prelocking context, or use separate
contexts for each open.
*/
for (Sroutine_hash_entry *rt=
(Sroutine_hash_entry*)thd->lex->sroutines_list.first;
rt; rt= rt->next)
rt->mdl_request.ticket= NULL;
if (protocol->write())
goto err;
}

View file

@ -1117,10 +1117,6 @@ reopen_tables:
while ((item= it++))
item->cleanup();
/* We have to cleanup translation tables of views. */
for (TABLE_LIST *tbl= table_list; tbl; tbl= tbl->next_global)
tbl->cleanup_items();
/*
To not to hog memory (as a result of the
unit->reinit_exec_mechanism() call below):