mariadb/mysql-test/r/ps_ddl1.result
Konstantin Osipov bf9c1b7353 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.
2009-12-29 15:19:05 +03:00

482 lines
11 KiB
Text

drop temporary table if exists t1;
drop table if exists t1, t2;
drop procedure if exists p_verify_reprepare_count;
drop procedure if exists p1;
drop function if exists f1;
drop view if exists t1;
drop schema if exists mysqltest;
create procedure p_verify_reprepare_count(expected int)
begin
declare old_reprepare_count int default @reprepare_count;
select variable_value from
information_schema.session_status where
variable_name='com_stmt_reprepare'
into @reprepare_count;
if old_reprepare_count + expected <> @reprepare_count then
select concat("Expected: ", expected,
", actual: ", @reprepare_count - old_reprepare_count)
as "ERROR";
else
select '' as "SUCCESS";
end if;
end|
set @reprepare_count= 0;
flush status;
drop table if exists t1;
# Column added or dropped is not within the list of selected columns
# or table comment has changed.
# A reprepare is probably not needed.
create table t1 (a int, b int);
prepare stmt from "select a from t1";
execute stmt;
a
call p_verify_reprepare_count(0);
SUCCESS
alter table t1 add column c int;
execute stmt;
a
call p_verify_reprepare_count(1);
SUCCESS
execute stmt;
a
call p_verify_reprepare_count(0);
SUCCESS
alter table t1 drop column b;
execute stmt;
a
call p_verify_reprepare_count(1);
SUCCESS
execute stmt;
a
call p_verify_reprepare_count(0);
SUCCESS
alter table t1 comment "My best table";
execute stmt;
a
call p_verify_reprepare_count(1);
SUCCESS
execute stmt;
a
call p_verify_reprepare_count(0);
SUCCESS
drop table t1;
deallocate prepare stmt;
# Selects using the table at various positions, inser,update ...
# + the table disappears
create table t1 (a int);
prepare stmt1 from "truncate t1";
prepare stmt2 from "select 1 as my_column from t1";
prepare stmt3 from "select 1 as my_column from (select * from t1) as t2";
prepare stmt4 from
"select 1 as my_column from (select 1) as t2 where exists (select 1 from t1)";
prepare stmt5 from "select * from (select 1 as b) as t2, t1";
prepare stmt6 from "select * from t1 union all select 1.5";
prepare stmt7 from "select 1 as my_column union all select 1 from t1";
prepare stmt8 from "insert into t1 values(1),(2)";
prepare stmt9 from "update t1 set a = 3 where a = 2";
prepare stmt10 from "delete from t1 where a = 1";
# Attention: Result logging is disabled.
execute stmt10;
execute stmt9;
execute stmt8;
execute stmt7;
execute stmt6;
execute stmt5;
execute stmt4;
execute stmt3;
execute stmt2;
execute stmt1;
call p_verify_reprepare_count(0);
SUCCESS
drop table t1;
execute stmt10;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt9;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt8;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt7;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt6;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt5;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt4;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt3;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt2;
ERROR 42S02: Table 'test.t1' doesn't exist
execute stmt1;
ERROR 42S02: Table 'test.t1' doesn't exist
call p_verify_reprepare_count(0);
SUCCESS
deallocate prepare stmt10;
deallocate prepare stmt9;
deallocate prepare stmt8;
deallocate prepare stmt7;
deallocate prepare stmt6;
deallocate prepare stmt5;
deallocate prepare stmt4;
deallocate prepare stmt3;
deallocate prepare stmt2;
deallocate prepare stmt1;
# Selects using the table at various positions, inser,update ...
# + layout change (drop column) which must cause a reprepare
create table t1 (a int, b int);
insert into t1 values(1,1),(2,2),(3,3);
create table t2 like t1;
insert into t1 values(2,2);
prepare stmt1 from "select a,b from t1";
prepare stmt2 from "select a,b from (select * from t1) as t1";
prepare stmt3 from "select * from t1 where a = 2 and b = 2";
prepare stmt4 from "select * from t2 where (a,b) in (select * from t1)";
prepare stmt5 from "select * from t1 union select * from t2";
prepare stmt6 from "select * from t1 union all select * from t2";
prepare stmt7 from "insert into t1 set a = 4, b = 4";
prepare stmt8 from "insert into t1 select * from t2";
# Attention: Result logging is disabled.
execute stmt8;
execute stmt7;
execute stmt6;
execute stmt5;
execute stmt4;
execute stmt3;
execute stmt2;
execute stmt1;
call p_verify_reprepare_count(0);
SUCCESS
alter table t1 drop column b;
execute stmt8;
ERROR 21S01: Column count doesn't match value count at row 1
call p_verify_reprepare_count(1);
SUCCESS
execute stmt7;
ERROR 42S22: Unknown column 'b' in 'field list'
call p_verify_reprepare_count(1);
SUCCESS
execute stmt6;
ERROR 21000: The used SELECT statements have a different number of columns
call p_verify_reprepare_count(1);
SUCCESS
execute stmt5;
ERROR 21000: The used SELECT statements have a different number of columns
call p_verify_reprepare_count(1);
SUCCESS
execute stmt4;
ERROR 21000: Operand should contain 2 column(s)
call p_verify_reprepare_count(1);
SUCCESS
execute stmt3;
ERROR 42S22: Unknown column 'b' in 'where clause'
call p_verify_reprepare_count(1);
SUCCESS
execute stmt2;
ERROR 42S22: Unknown column 'b' in 'field list'
call p_verify_reprepare_count(1);
SUCCESS
execute stmt1;
ERROR 42S22: Unknown column 'b' in 'field list'
call p_verify_reprepare_count(1);
SUCCESS
execute stmt8;
ERROR 21S01: Column count doesn't match value count at row 1
call p_verify_reprepare_count(1);
ERROR
Expected: 1, actual: 0
execute stmt7;
ERROR 42S22: Unknown column 'b' in 'field list'
call p_verify_reprepare_count(1);
SUCCESS
execute stmt6;
ERROR 21000: The used SELECT statements have a different number of columns
call p_verify_reprepare_count(1);
SUCCESS
execute stmt5;
ERROR 21000: The used SELECT statements have a different number of columns
call p_verify_reprepare_count(1);
SUCCESS
execute stmt4;
ERROR 21000: Operand should contain 2 column(s)
call p_verify_reprepare_count(1);
SUCCESS
execute stmt3;
ERROR 42S22: Unknown column 'b' in 'where clause'
call p_verify_reprepare_count(1);
SUCCESS
execute stmt2;
ERROR 42S22: Unknown column 'b' in 'field list'
call p_verify_reprepare_count(1);
SUCCESS
execute stmt1;
ERROR 42S22: Unknown column 'b' in 'field list'
call p_verify_reprepare_count(1);
SUCCESS
# Why does the INSERT ... SELECT does not get a reprepare or is
# only the counter not incremented?
execute stmt8;
ERROR 21S01: Column count doesn't match value count at row 1
call p_verify_reprepare_count(1);
ERROR
Expected: 1, actual: 0
alter table t2 add column c int;
execute stmt8;
ERROR 21S01: Column count doesn't match value count at row 1
call p_verify_reprepare_count(1);
SUCCESS
deallocate prepare stmt8;
deallocate prepare stmt7;
deallocate prepare stmt6;
deallocate prepare stmt5;
deallocate prepare stmt4;
deallocate prepare stmt3;
deallocate prepare stmt2;
deallocate prepare stmt1;
drop table t1;
drop table t2;
# select AVG(<col>) + optimizer uses index meets loss of the index
create table t1 (a int, b int, primary key(b),unique index t1_unq_idx(a));
insert into t1 set a = 0, b = 0;
insert into t1 select a + 1, b + 1 from t1;
insert into t1 select a + 2, b + 2 from t1;
insert into t1 select a + 4, b + 4 from t1;
insert into t1 select a + 8, b + 8 from t1;
# Optimizer strategy: Possible keys = NULL , Extra = Using index
prepare stmt from "select avg(a) from t1";
execute stmt;
avg(a)
7.5000
call p_verify_reprepare_count(0);
SUCCESS
execute stmt;
avg(a)
7.5000
call p_verify_reprepare_count(0);
SUCCESS
alter table t1 drop index t1_unq_idx;
# Optimizer strategy: Possible keys = NULL , Extra =
execute stmt;
avg(a)
7.5000
call p_verify_reprepare_count(1);
SUCCESS
execute stmt;
avg(a)
7.5000
call p_verify_reprepare_count(0);
SUCCESS
# select AVG(<col>) + optimizer uses table scan meets a new index
alter table t1 add unique index t1_unq_idx(a);
# Optimizer strategy: Possible keys = NULL , Extra = Using index
execute stmt;
avg(a)
7.5000
call p_verify_reprepare_count(1);
SUCCESS
execute stmt;
avg(a)
7.5000
call p_verify_reprepare_count(0);
SUCCESS
deallocate prepare stmt;
drop table t1;
# table replaced by not updatable view - Insert
create table t1 (a int);
prepare stmt from "insert into t1 values(1)";
execute stmt;
call p_verify_reprepare_count(0);
SUCCESS
drop table t1;
create view t1 as select 1;
execute stmt;
ERROR HY000: The target table t1 of the INSERT is not insertable-into
call p_verify_reprepare_count(1);
SUCCESS
drop view t1;
create table t2 (a int);
create view t1 as select * from t2 with check option;
execute stmt;
call p_verify_reprepare_count(1);
SUCCESS
execute stmt;
call p_verify_reprepare_count(0);
SUCCESS
select * from t1;
a
1
1
deallocate prepare stmt;
drop view t1;
drop table t2;
=====================================================================
Some freestyle tests
=====================================================================
create temporary table t1 as select 1 as a;
create procedure p1()
begin
drop temporary table t1;
end|
create function f1() returns int
begin
call p1();
return 1;
end|
prepare stmt from "select f1() as my_column, a from t1";
execute stmt;
ERROR HY000: Can't reopen table: 't1'
call p_verify_reprepare_count(0);
SUCCESS
select * from t1;
a
1
prepare stmt from "select a, f1() as my_column from t1";
execute stmt;
ERROR HY000: Can't reopen table: 't1'
call p_verify_reprepare_count(0);
SUCCESS
select * from t1;
a
1
prepare stmt from "select f1() as my_column, count(*) from t1";
execute stmt;
ERROR HY000: Can't reopen table: 't1'
call p_verify_reprepare_count(0);
SUCCESS
select * from t1;
a
1
prepare stmt from "select count(*), f1() as my_column from t1";
execute stmt;
ERROR HY000: Can't reopen table: 't1'
call p_verify_reprepare_count(0);
SUCCESS
select * from t1;
a
1
# Execute fails, no drop of temporary table
prepare stmt from "select 1 as my_column from (select 1) as t2
where exists (select f1() from t1)";
execute stmt;
my_column
1
call p_verify_reprepare_count(0);
SUCCESS
execute stmt;
my_column
1
call p_verify_reprepare_count(0);
SUCCESS
select * from t1;
a
1
# Execute drops temporary table
prepare stmt from "select f1()";
execute stmt;
f1()
1
call p_verify_reprepare_count(0);
SUCCESS
execute stmt;
ERROR 42S02: Unknown table 't1'
call p_verify_reprepare_count(0);
SUCCESS
drop function f1;
drop procedure p1;
deallocate prepare stmt;
# Execute fails, temporary table is not replaced by another
create temporary table t1 as select 1 as a;
create procedure p1()
begin
drop temporary table t1;
create temporary table t1 as select 'abc' as a;
end|
create function f1() returns int
begin
call p1();
return 1;
end|
prepare stmt from "select count(*), f1() as my_column from t1";
execute stmt;
ERROR HY000: Can't reopen table: 't1'
call p_verify_reprepare_count(0);
SUCCESS
select * from t1;
a
1
deallocate prepare stmt;
prepare stmt from "call p1";
execute stmt;
drop procedure p1;
create schema mysqltest;
create procedure mysqltest.p1()
begin
drop schema mysqltest;
create schema mysqltest;
end|
execute stmt;
ERROR 42000: PROCEDURE test.p1 does not exist
call p_verify_reprepare_count(0);
SUCCESS
execute stmt;
ERROR 42000: PROCEDURE test.p1 does not exist
call p_verify_reprepare_count(0);
SUCCESS
deallocate prepare stmt;
drop schema mysqltest;
drop temporary table t1;
# Cleanup
#
drop temporary table if exists t1;
drop table if exists t1, t2;
drop procedure if exists p_verify_reprepare_count;
drop procedure if exists p1;
drop function if exists f1;
drop view if exists t1;
drop schema if exists mysqltest;