mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 03:52:35 +01:00
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:
parent
dfdbc84585
commit
bf9c1b7353
25 changed files with 2243 additions and 307 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
697
mysql-test/r/sp-lock.result
Normal 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
|
||||
#
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
876
mysql-test/t/sp-lock.test
Normal 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 #
|
51
sql/lock.cc
51
sql/lock.cc
|
@ -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;
|
||||
|
|
|
@ -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
168
sql/sp.cc
|
@ -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);
|
||||
}
|
||||
|
|
18
sql/sp.h
18
sql/sp.h
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
239
sql/sql_base.cc
239
sql/sql_base.cc
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
166
sql/sql_parse.cc
166
sql/sql_parse.cc
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue