Commit 7b8bfb2a authored by Konstantin Osipov's avatar Konstantin Osipov

A fix for

Bug#12093 "SP not found on second PS execution if another thread 
drops other SP in between" and
Bug#21294 "executing a prepared statement that executes a stored 
function which was recreat"

Stored functions are resolved at prepared statement prepare only.
If someone flushes the stored functions cache between prepare and
execute, execution fails.

The fix is to detect the situation of the cache flush and automatically
reprepare the prepared statement after it.

mysql-test/r/ps_ddl.result:
  Update results (Bug#12093 and Bug#21294, the test cases are already
  in the source tree).
mysql-test/r/ps_ddl1.result:
  Update results (Bug#12093 and Bug#21294, the test cases are already
  in the source tree).
mysql-test/r/sp-error.result:
  Update results (Bug#12093 and Bug#21294, the test cases are already
  in the source tree).
mysql-test/t/ps_ddl.test:
  Modify the test to not expect an error where there is no error
  any more (Bug#12093, Bug#21294).
mysql-test/t/ps_ddl1.test:
  Modify the test to not expect an error where there is no error
  any more (Bug#12093, Bug#21294).
mysql-test/t/sp-error.test:
  Modify the test to not expect an error where there is no error
  any more (Bug#12093, Bug#21294).
sql/sp_cache.cc:
  Implement sp_cache_version() -- returns the current version of 
  a stored routines cache.
sql/sp_cache.h:
  Declare sp_cache_version().
sql/sql_prepare.cc:
  Keep track of stored functions cache version, and invalidate
  the statement if it changed between prepared statement
  prepare and execute (and the statement actually uses stored routines).
parent b6b6d98f
...@@ -290,7 +290,7 @@ SUCCESS ...@@ -290,7 +290,7 @@ SUCCESS
# Test 7-b: dependent FUNCTION has changed # Test 7-b: dependent FUNCTION has changed
# #
# Note, this scenario is not supported, subject of Bug#12093 # Note, this scenario is supported, subject of Bug#12093
# #
drop trigger t1_ai; drop trigger t1_ai;
create trigger t1_ai after insert on t1 for each row create trigger t1_ai after insert on t1 for each row
...@@ -305,8 +305,7 @@ select @var; ...@@ -305,8 +305,7 @@ select @var;
drop function f1; drop function f1;
create function f1 (a int) returns int return 0; create function f1 (a int) returns int return 0;
execute stmt using @var; execute stmt using @var;
ERROR 42000: FUNCTION test.f1 does not exist call p_verify_reprepare_count(1);
call p_verify_reprepare_count(0);
SUCCESS SUCCESS
drop function f1; drop function f1;
...@@ -359,8 +358,14 @@ a ...@@ -359,8 +358,14 @@ a
drop view v1; drop view v1;
create view v1 as select a from t2; create view v1 as select a from t2;
set @var=8; 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.
# Repreparation of the main statement doesn't cause repreparation
# of trigger statements.
execute stmt using @var; execute stmt using @var;
call p_verify_reprepare_count(0); ERROR 42S02: Table 'test.t3' doesn't exist
call p_verify_reprepare_count(1);
SUCCESS SUCCESS
# #
...@@ -377,7 +382,6 @@ select * from t3; ...@@ -377,7 +382,6 @@ select * from t3;
a a
6 6
7 7
8
flush table t1; flush table t1;
set @var=9; set @var=9;
execute stmt using @var; execute stmt using @var;
...@@ -392,7 +396,6 @@ select * from t3; ...@@ -392,7 +396,6 @@ select * from t3;
a a
6 6
7 7
8
drop view v1; drop view v1;
drop table t1,t2,t3; drop table t1,t2,t3;
# Test 7-d: dependent TABLE has changed # Test 7-d: dependent TABLE has changed
...@@ -798,14 +801,17 @@ SUCCESS ...@@ -798,14 +801,17 @@ SUCCESS
drop function f1; drop function f1;
create function f1() returns int return 2; create function f1() returns int return 2;
# XXX: Bug#12093. We only get a different error # XXX: Used to be another manifestation of Bug#12093.
# We only used to get a different error
# message because the non-existing procedure error is masked # message because the non-existing procedure error is masked
# by the view. # by the view.
execute stmt; execute stmt;
ERROR HY000: View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them f1()
2
execute stmt; execute stmt;
ERROR HY000: View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them f1()
call p_verify_reprepare_count(0); 2
call p_verify_reprepare_count(1);
SUCCESS SUCCESS
# Part 18b: dependent procedure has changed (referred to via a function) # Part 18b: dependent procedure has changed (referred to via a function)
...@@ -831,19 +837,20 @@ SUCCESS ...@@ -831,19 +837,20 @@ SUCCESS
drop procedure p1; drop procedure p1;
create procedure p1(out x int) select max(a) from t2 into x; create procedure p1(out x int) select max(a) from t2 into x;
# XXX: bug. The prelocked list is not invalidated # XXX: used to be a bug. The prelocked list was not invalidated
# and we keep opening table t1, whereas the procedure # and we kept opening table t1, whereas the procedure
# is now referring to table t2 # is now referring to table t2
execute stmt; execute stmt;
ERROR HY000: View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them f1()
call p_verify_reprepare_count(0); 6
call p_verify_reprepare_count(1);
SUCCESS SUCCESS
flush table t1; flush table t1;
execute stmt; execute stmt;
f1() f1()
6 6
call p_verify_reprepare_count(1); call p_verify_reprepare_count(0);
SUCCESS SUCCESS
execute stmt; execute stmt;
...@@ -1528,7 +1535,6 @@ drop view v_27690_1; ...@@ -1528,7 +1535,6 @@ drop view v_27690_1;
drop table v_27690_2; drop table v_27690_2;
deallocate prepare stmt; deallocate prepare stmt;
#===================================================================== #=====================================================================
# TODO: fix the below two bugs and modify their tests
# #
# Bug#21294 Executing a prepared statement that executes # Bug#21294 Executing a prepared statement that executes
# a stored function which was recreat # a stored function which was recreat
...@@ -1541,12 +1547,14 @@ f1() ...@@ -1541,12 +1547,14 @@ f1()
drop function f1; drop function f1;
create function f1() returns int return 10; create function f1() returns int return 10;
execute stmt; execute stmt;
ERROR 42000: FUNCTION test.f1 does not exist f1()
10
drop function f1; drop function f1;
create function f1() returns int return 20; create function f1() returns int return 20;
execute stmt; execute stmt;
ERROR 42000: FUNCTION test.f1 does not exist f1()
call p_verify_reprepare_count(0); 20
call p_verify_reprepare_count(2);
SUCCESS SUCCESS
drop function f1; drop function f1;
...@@ -1573,19 +1581,21 @@ execute stmt_sp; ...@@ -1573,19 +1581,21 @@ execute stmt_sp;
a a
drop function f_12093_unrelated; drop function f_12093_unrelated;
drop procedure p_12093_unrelated; drop procedure p_12093_unrelated;
# XXX: bug # XXX: used to be a bug
execute stmt_sf; execute stmt_sf;
ERROR 42000: FUNCTION test.f_12093 does not exist f_12093()
# XXX: bug 0
# XXX: used to be a bug
execute stmt_sp; execute stmt_sp;
ERROR 42000: PROCEDURE test.p_12093 does not exist a
# XXX: bug # XXX: used to be a bug
execute stmt_sf; execute stmt_sf;
ERROR 42000: FUNCTION test.f_12093 does not exist f_12093()
# XXX: bug 0
# XXX: used to be a bug
execute stmt_sp; execute stmt_sp;
ERROR 42000: PROCEDURE test.p_12093 does not exist a
call p_verify_reprepare_count(0); call p_verify_reprepare_count(2);
SUCCESS SUCCESS
drop table t_12093; drop table t_12093;
......
...@@ -460,7 +460,7 @@ create schema mysqltest; ...@@ -460,7 +460,7 @@ create schema mysqltest;
end| end|
execute stmt; execute stmt;
ERROR 42000: PROCEDURE test.p1 does not exist ERROR 42000: PROCEDURE test.p1 does not exist
call p_verify_reprepare_count(0); call p_verify_reprepare_count(1);
SUCCESS SUCCESS
execute stmt; execute stmt;
......
...@@ -796,7 +796,7 @@ bug11834_2() ...@@ -796,7 +796,7 @@ bug11834_2()
10 10
drop function bug11834_1; drop function bug11834_1;
execute stmt; execute stmt;
ERROR 42000: FUNCTION test.bug11834_2 does not exist ERROR 42000: FUNCTION test.bug11834_1 does not exist
deallocate prepare stmt; deallocate prepare stmt;
drop function bug11834_2; drop function bug11834_2;
DROP FUNCTION IF EXISTS bug12953| DROP FUNCTION IF EXISTS bug12953|
...@@ -1045,7 +1045,8 @@ select bug12329(); ...@@ -1045,7 +1045,8 @@ select bug12329();
bug12329() bug12329()
101 101
execute stmt1; execute stmt1;
ERROR 42S02: Table 'test.t2' doesn't exist bug12329()
101
deallocate prepare stmt1; deallocate prepare stmt1;
drop function bug12329; drop function bug12329;
drop table t1, t2; drop table t1, t2;
......
...@@ -299,7 +299,7 @@ call p_verify_reprepare_count(0); ...@@ -299,7 +299,7 @@ call p_verify_reprepare_count(0);
--echo # Test 7-b: dependent FUNCTION has changed --echo # Test 7-b: dependent FUNCTION has changed
--echo # --echo #
--echo # Note, this scenario is not supported, subject of Bug#12093 --echo # Note, this scenario is supported, subject of Bug#12093
--echo # --echo #
drop trigger t1_ai; drop trigger t1_ai;
create trigger t1_ai after insert on t1 for each row create trigger t1_ai after insert on t1 for each row
...@@ -311,9 +311,8 @@ execute stmt using @var; ...@@ -311,9 +311,8 @@ execute stmt using @var;
select @var; select @var;
drop function f1; drop function f1;
create function f1 (a int) returns int return 0; create function f1 (a int) returns int return 0;
--error ER_SP_DOES_NOT_EXIST
execute stmt using @var; execute stmt using @var;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(1);
drop function f1; drop function f1;
deallocate prepare stmt; deallocate prepare stmt;
...@@ -353,8 +352,14 @@ select * from t2; ...@@ -353,8 +352,14 @@ select * from t2;
drop view v1; drop view v1;
create view v1 as select a from t2; create view v1 as select a from t2;
set @var=8; 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 # Repreparation of the main statement doesn't cause repreparation
--echo # of trigger statements.
--error ER_NO_SUCH_TABLE
execute stmt using @var; execute stmt using @var;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(1);
--echo # --echo #
--echo # Sic: the insert went into t3, even though the view now --echo # Sic: the insert went into t3, even though the view now
--echo # points at t2. This is because neither the merged view --echo # points at t2. This is because neither the merged view
...@@ -703,14 +708,13 @@ execute stmt; ...@@ -703,14 +708,13 @@ execute stmt;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(0);
drop function f1; drop function f1;
create function f1() returns int return 2; create function f1() returns int return 2;
--echo # XXX: Bug#12093. We only get a different error --echo # XXX: Used to be another manifestation of Bug#12093.
--echo # We only used to get a different error
--echo # message because the non-existing procedure error is masked --echo # message because the non-existing procedure error is masked
--echo # by the view. --echo # by the view.
--error ER_VIEW_INVALID
execute stmt; execute stmt;
--error ER_VIEW_INVALID
execute stmt; execute stmt;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(1);
--echo # Part 18b: dependent procedure has changed (referred to via a function) --echo # Part 18b: dependent procedure has changed (referred to via a function)
...@@ -734,15 +738,14 @@ execute stmt; ...@@ -734,15 +738,14 @@ execute stmt;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(0);
drop procedure p1; drop procedure p1;
create procedure p1(out x int) select max(a) from t2 into x; create procedure p1(out x int) select max(a) from t2 into x;
--echo # XXX: bug. The prelocked list is not invalidated --echo # XXX: used to be a bug. The prelocked list was not invalidated
--echo # and we keep opening table t1, whereas the procedure --echo # and we kept opening table t1, whereas the procedure
--echo # is now referring to table t2 --echo # is now referring to table t2
--error ER_VIEW_INVALID
execute stmt; execute stmt;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(1);
flush table t1; flush table t1;
execute stmt; execute stmt;
call p_verify_reprepare_count(1); call p_verify_reprepare_count(0);
execute stmt; execute stmt;
--echo # Test 18-c: dependent VIEW has changed --echo # Test 18-c: dependent VIEW has changed
...@@ -1326,7 +1329,6 @@ drop table v_27690_2; ...@@ -1326,7 +1329,6 @@ drop table v_27690_2;
deallocate prepare stmt; deallocate prepare stmt;
--echo #===================================================================== --echo #=====================================================================
--echo # TODO: fix the below two bugs and modify their tests
--echo # --echo #
--echo # Bug#21294 Executing a prepared statement that executes --echo # Bug#21294 Executing a prepared statement that executes
--echo # a stored function which was recreat --echo # a stored function which was recreat
...@@ -1341,15 +1343,13 @@ drop function f1; ...@@ -1341,15 +1343,13 @@ drop function f1;
create function f1() returns int return 10; create function f1() returns int return 10;
# might pass or fail, implementation dependent # might pass or fail, implementation dependent
--error ER_SP_DOES_NOT_EXIST
execute stmt; execute stmt;
drop function f1; drop function f1;
create function f1() returns int return 20; create function f1() returns int return 20;
--error ER_SP_DOES_NOT_EXIST
execute stmt; execute stmt;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(2);
drop function f1; drop function f1;
deallocate prepare stmt; deallocate prepare stmt;
...@@ -1388,20 +1388,16 @@ drop procedure p_12093_unrelated; ...@@ -1388,20 +1388,16 @@ drop procedure p_12093_unrelated;
connection default; connection default;
--echo # XXX: bug --echo # XXX: used to be a bug
--error ER_SP_DOES_NOT_EXIST
execute stmt_sf; execute stmt_sf;
--echo # XXX: bug --echo # XXX: used to be a bug
--error ER_SP_DOES_NOT_EXIST
execute stmt_sp; execute stmt_sp;
--echo # XXX: bug --echo # XXX: used to be a bug
--error ER_SP_DOES_NOT_EXIST
execute stmt_sf; execute stmt_sf;
--echo # XXX: bug --echo # XXX: used to be a bug
--error ER_SP_DOES_NOT_EXIST
execute stmt_sp; execute stmt_sp;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(2);
disconnect con1; disconnect con1;
......
...@@ -363,7 +363,7 @@ end| ...@@ -363,7 +363,7 @@ end|
delimiter ;| delimiter ;|
--error ER_SP_DOES_NOT_EXIST --error ER_SP_DOES_NOT_EXIST
execute stmt; execute stmt;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(1);
--error ER_SP_DOES_NOT_EXIST --error ER_SP_DOES_NOT_EXIST
execute stmt; execute stmt;
call p_verify_reprepare_count(0); call p_verify_reprepare_count(0);
......
...@@ -1470,10 +1470,6 @@ execute stmt1; ...@@ -1470,10 +1470,6 @@ execute stmt1;
drop function bug12329; drop function bug12329;
create function bug12329() returns int return (select a+100 from t2); create function bug12329() returns int return (select a+100 from t2);
select bug12329(); select bug12329();
# Until we implement proper mechanism for invalidation of PS/SP when table
# or SP's are changed the following statement will fail with 'Table ... was
# not locked' error (this mechanism should be based on the new TDC).
--error ER_NO_SUCH_TABLE
execute stmt1; execute stmt1;
deallocate prepare stmt1; deallocate prepare stmt1;
drop function bug12329; drop function bug12329;
......
...@@ -210,6 +210,19 @@ void sp_cache_flush_obsolete(sp_cache **cp) ...@@ -210,6 +210,19 @@ void sp_cache_flush_obsolete(sp_cache **cp)
} }
/**
Return the current version of the cache.
*/
ulong sp_cache_version(sp_cache **cp)
{
sp_cache *c= *cp;
if (c)
return c->version;
return 0;
}
/************************************************************************* /*************************************************************************
Internal functions Internal functions
*************************************************************************/ *************************************************************************/
......
...@@ -58,5 +58,6 @@ void sp_cache_insert(sp_cache **cp, sp_head *sp); ...@@ -58,5 +58,6 @@ void sp_cache_insert(sp_cache **cp, sp_head *sp);
sp_head *sp_cache_lookup(sp_cache **cp, sp_name *name); sp_head *sp_cache_lookup(sp_cache **cp, sp_name *name);
void sp_cache_invalidate(); void sp_cache_invalidate();
void sp_cache_flush_obsolete(sp_cache **cp); void sp_cache_flush_obsolete(sp_cache **cp);
ulong sp_cache_version(sp_cache **cp);
#endif /* _SP_CACHE_H_ */ #endif /* _SP_CACHE_H_ */
...@@ -169,6 +169,8 @@ class Prepared_statement: public Statement ...@@ -169,6 +169,8 @@ class Prepared_statement: public Statement
SELECT_LEX and other classes). SELECT_LEX and other classes).
*/ */
MEM_ROOT main_mem_root; MEM_ROOT main_mem_root;
/* Version of the stored functions cache at the time of prepare. */
ulong m_sp_cache_version;
private: private:
bool set_db(const char *db, uint db_length); bool set_db(const char *db, uint db_length);
bool set_parameters(String *expanded_query, bool set_parameters(String *expanded_query,
...@@ -2819,7 +2821,8 @@ Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg) ...@@ -2819,7 +2821,8 @@ Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg)
param_array(0), param_array(0),
param_count(0), param_count(0),
last_errno(0), last_errno(0),
flags((uint) IS_IN_USE) flags((uint) IS_IN_USE),
m_sp_cache_version(0)
{ {
init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size, init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size,
thd_arg->variables.query_prealloc_size); thd_arg->variables.query_prealloc_size);
...@@ -3072,6 +3075,20 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) ...@@ -3072,6 +3075,20 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
init_stmt_after_parse(lex); init_stmt_after_parse(lex);
state= Query_arena::PREPARED; state= Query_arena::PREPARED;
flags&= ~ (uint) IS_IN_USE; 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 Log COM_EXECUTE to the general log. Note, that in case of SQL
...@@ -3383,6 +3400,7 @@ Prepared_statement::swap_prepared_statement(Prepared_statement *copy) ...@@ -3383,6 +3400,7 @@ Prepared_statement::swap_prepared_statement(Prepared_statement *copy)
swap_variables(LEX_STRING, name, copy->name); swap_variables(LEX_STRING, name, copy->name);
/* Ditto */ /* Ditto */
swap_variables(char *, db, copy->db); 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(db_length == copy->db_length);
DBUG_ASSERT(param_count == copy->param_count); DBUG_ASSERT(param_count == copy->param_count);
...@@ -3442,6 +3460,19 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) ...@@ -3442,6 +3460,19 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
return TRUE; 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 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 command. For such queries we don't return an error and don't
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment