diff --git a/mysql-test/r/information_schema_db.result b/mysql-test/r/information_schema_db.result
index db14b7b6600..2a6a3e6e0fb 100644
--- a/mysql-test/r/information_schema_db.result
+++ b/mysql-test/r/information_schema_db.result
@@ -98,13 +98,13 @@ where table_schema='test';
table_name table_type table_comment
t1 BASE TABLE
v1 VIEW VIEW
-v2 VIEW View 'test.v2' references invalid table(s) or column(s) or function(s) or define
+v2 VIEW VIEW
drop table t1;
select table_name, table_type, table_comment from information_schema.tables
where table_schema='test';
table_name table_type table_comment
-v1 VIEW View 'test.v1' references invalid table(s) or column(s) or function(s) or define
-v2 VIEW View 'test.v2' references invalid table(s) or column(s) or function(s) or define
+v1 VIEW VIEW
+v2 VIEW VIEW
drop function f1;
drop function f2;
drop view v1, v2;
diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result
index 5e81df99d18..30043e066db 100644
--- a/mysql-test/r/view.result
+++ b/mysql-test/r/view.result
@@ -1935,11 +1935,11 @@ create function f1 () returns int return (select max(col1) from t1);
DROP TABLE t1;
CHECK TABLE v1, v2, v3, v4, v5, v6;
Table Op Msg_type Msg_text
-test.v1 check error View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
+test.v1 check status OK
test.v2 check status OK
-test.v3 check error View 'test.v3' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
+test.v3 check status OK
test.v4 check status OK
-test.v5 check error View 'test.v5' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
+test.v5 check status OK
test.v6 check status OK
drop function f1;
drop function f2;
diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test
index 38753ed3ac0..6c7fde71e78 100644
--- a/mysql-test/t/sp.test
+++ b/mysql-test/t/sp.test
@@ -1369,7 +1369,7 @@ end|
select f11()|
--error ER_CANT_REOPEN_TABLE
select f11() from t1|
-# We don't handle temporary tables used by nested functions well
+# Test that using a single table instance at a time works
create function f12_1() returns int
begin
drop temporary table if exists t3;
@@ -1379,11 +1379,9 @@ begin
end|
create function f12_2() returns int
return (select count(*) from t3)|
-# We need clean start to get error
+
drop temporary table t3|
---error ER_NO_SUCH_TABLE
select f12_1()|
---error ER_NO_SUCH_TABLE
select f12_1() from t1 limit 1|
# Cleanup
@@ -6802,6 +6800,53 @@ CALL bug24117()|
DROP PROCEDURE bug24117|
DROP TABLE t3|
+#
+# Bug#8407(Stored functions/triggers ignore exception handler)
+#
+
+--disable_warnings
+drop function if exists func_8407_a|
+drop function if exists func_8407_b|
+--enable_warnings
+
+create function func_8407_a() returns int
+begin
+ declare x int;
+
+ declare continue handler for sqlexception
+ begin
+ end;
+
+ select 1 from no_such_view limit 1 into x;
+
+ return x;
+end|
+
+create function func_8407_b() returns int
+begin
+ declare x int default 0;
+
+ declare continue handler for sqlstate '42S02'
+ begin
+ set x:= x+1000;
+ end;
+
+ case (select 1 from no_such_view limit 1)
+ when 1 then set x:= x+1;
+ when 2 then set x:= x+2;
+ else set x:= x+100;
+ end case;
+ set x:=x + 500;
+
+ return x;
+end|
+
+select func_8407_a()|
+select func_8407_b()|
+
+drop function func_8407_a|
+drop function func_8407_b|
+
#
# NOTE: The delimiter is `|`, and not `;`. It is changed to `;`
# at the end of the file!
diff --git a/sql/lock.cc b/sql/lock.cc
index edef3b3b67f..a93033bfdd0 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -604,7 +604,7 @@ TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle,
for (; haystack; haystack= haystack->next_global)
{
- if (haystack->placeholder() || haystack->schema_table)
+ if (haystack->placeholder())
continue;
table2= haystack->table;
if (table2->s->tmp_table == TMP_TABLE)
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 425088f622c..8a6d00e44a1 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -2553,6 +2553,14 @@ static int my_message_sql(uint error, const char *str, myf MyFlags)
*/
if ((thd= current_thd))
{
+ /*
+ TODO: There are two exceptions mechanism (THD and sp_rcontext),
+ this could be improved by having a common stack of handlers.
+ */
+ if (thd->handle_error(error,
+ MYSQL_ERROR::WARN_LEVEL_ERROR))
+ DBUG_RETURN(0);
+
if (thd->spcont &&
thd->spcont->handle_error(error, MYSQL_ERROR::WARN_LEVEL_ERROR, thd))
{
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index 20d45af7415..1e0986f6e82 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -2444,16 +2444,11 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
m_lex->mark_as_requiring_prelocking(lex_query_tables_own_last);
}
}
-
+
reinit_stmt_before_use(thd, m_lex);
- /*
- If requested check whenever we have access to tables in LEX's table list
- and open and lock them before executing instructtions core function.
- */
- if (open_tables &&
- (check_table_access(thd, SELECT_ACL, m_lex->query_tables, 0) ||
- open_and_lock_tables(thd, m_lex->query_tables)))
- res= -1;
+
+ if (open_tables)
+ res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables, nextp);
if (!res)
{
@@ -2505,6 +2500,33 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
sp_instr class functions
*/
+int sp_instr::exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables,
+ uint *nextp)
+{
+ int result;
+
+ /*
+ Check whenever we have access to tables for this statement
+ and open and lock them before executing instructions core function.
+ */
+ if (check_table_access(thd, SELECT_ACL, tables, 0)
+ || open_and_lock_tables(thd, tables))
+ {
+ get_cont_dest(nextp);
+ result= -1;
+ }
+ else
+ result= 0;
+
+ return result;
+}
+
+void sp_instr::get_cont_dest(uint *nextp)
+{
+ *nextp= m_ip+1;
+}
+
+
int sp_instr::exec_core(THD *thd, uint *nextp)
{
DBUG_ASSERT(0);
@@ -2690,6 +2712,15 @@ sp_instr_set_trigger_field::print(String *str)
value->print(str);
}
+/*
+ sp_instr_opt_meta
+*/
+
+void sp_instr_opt_meta::get_cont_dest(uint *nextp)
+{
+ *nextp= m_cont_dest;
+}
+
/*
sp_instr_jump class functions
diff --git a/sql/sp_head.h b/sql/sp_head.h
index be6eefa2ea4..46ad3dd96d8 100644
--- a/sql/sp_head.h
+++ b/sql/sp_head.h
@@ -477,6 +477,28 @@ public:
virtual int execute(THD *thd, uint *nextp) = 0;
+ /**
+ Execute open_and_lock_tables()
for this statement.
+ Open and lock the tables used by this statement, as a pre-requisite
+ to execute the core logic of this instruction with
+ exec_core()
.
+ If this statement fails, the next instruction to execute is also returned.
+ This is useful when a user defined SQL continue handler needs to be
+ executed.
+ @param thd the current thread
+ @param tables the list of tables to open and lock
+ @param nextp the continuation instruction, returned to the caller if this
+ method fails.
+ @return zero on success, non zero on failure.
+ */
+ int exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables, uint *nextp);
+
+ /**
+ Get the continuation destination of this instruction.
+ @param nextp the continuation destination (output)
+ */
+ virtual void get_cont_dest(uint *nextp);
+
/*
Execute core function of instruction after all preparations (e.g.
setting of proper LEX, saving part of the thread context have been
@@ -741,6 +763,8 @@ public:
virtual void set_destination(uint old_dest, uint new_dest)
= 0;
+ virtual void get_cont_dest(uint *nextp);
+
protected:
sp_instr *m_optdest; // Used during optimization
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index ac1222e6bd6..6d831952df5 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -28,6 +28,59 @@
#include
#endif
+/**
+ This internal handler is used to trap internally
+ errors that can occur when executing open table
+ during the prelocking phase.
+*/
+class Prelock_error_handler : public Internal_error_handler
+{
+public:
+ Prelock_error_handler()
+ : m_handled_errors(0), m_unhandled_errors(0)
+ {}
+
+ virtual ~Prelock_error_handler() {}
+
+ virtual bool handle_error(uint sql_errno,
+ MYSQL_ERROR::enum_warning_level level,
+ THD *thd);
+
+ bool safely_trapped_errors();
+
+private:
+ int m_handled_errors;
+ int m_unhandled_errors;
+};
+
+
+bool
+Prelock_error_handler::handle_error(uint sql_errno,
+ MYSQL_ERROR::enum_warning_level /* level */,
+ THD * /* thd */)
+{
+ if (sql_errno == ER_NO_SUCH_TABLE)
+ {
+ m_handled_errors++;
+ return TRUE; // 'TRUE', as per coding style
+ }
+
+ m_unhandled_errors++;
+ return FALSE; // 'FALSE', as per coding style
+}
+
+
+bool Prelock_error_handler::safely_trapped_errors()
+{
+ /*
+ If m_unhandled_errors != 0, something else, unanticipated, happened,
+ so the error is not trapped but returned to the caller.
+ Multiple ER_NO_SUCH_TABLE can be raised in case of views.
+ */
+ return ((m_handled_errors > 0) && (m_unhandled_errors == 0));
+}
+
+
TABLE *unused_tables; /* Used by mysql_test */
HASH open_cache; /* Used by mysql_test */
static HASH table_def_cache;
@@ -1977,7 +2030,10 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
VOID(pthread_mutex_unlock(&LOCK_open));
}
}
- my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias);
+ if ((thd->locked_tables) && (thd->locked_tables->lock_count > 0))
+ my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias);
+ else
+ my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
DBUG_RETURN(0);
}
@@ -2860,6 +2916,8 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
MEM_ROOT new_frm_mem;
/* Also used for indicating that prelocking is need */
TABLE_LIST **query_tables_last_own;
+ bool safe_to_ignore_table;
+
DBUG_ENTER("open_tables");
/*
temporary mem_root for new .frm parsing.
@@ -2908,6 +2966,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
for (tables= *start; tables ;tables= tables->next_global)
{
+ safe_to_ignore_table= FALSE; // 'FALSE', as per coding style
/*
Ignore placeholders for derived tables. After derived tables
processing, link to created temporary table will be put here.
@@ -2927,9 +2986,28 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
DBUG_RETURN(-1);
}
(*counter)++;
-
- if (!tables->table &&
- !(tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags)))
+
+ if (!tables->table)
+ {
+ if (tables->prelocking_placeholder)
+ {
+ /*
+ For the tables added by the pre-locking code, attempt to open
+ the table but fail silently if the table does not exist.
+ The real failure will occur when/if a statement attempts to use
+ that table.
+ */
+ Prelock_error_handler prelock_handler;
+ thd->push_internal_handler(& prelock_handler);
+ tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+ thd->pop_internal_handler();
+ safe_to_ignore_table= prelock_handler.safely_trapped_errors();
+ }
+ else
+ tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+ }
+
+ if (!tables->table)
{
free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC));
@@ -2980,6 +3058,14 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
close_tables_for_reopen(thd, start);
goto restart;
}
+
+ if (safe_to_ignore_table)
+ {
+ DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'",
+ tables->db, tables->alias));
+ continue;
+ }
+
result= -1; // Fatal error
break;
}
@@ -3283,7 +3369,7 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags)
static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table)
{
for (; table; table= table->next_global)
- if (!table->placeholder() && !table->schema_table)
+ if (!table->placeholder())
table->table->query_id= 0;
}
@@ -3356,7 +3442,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
DBUG_RETURN(-1);
for (table= tables; table; table= table->next_global)
{
- if (!table->placeholder() && !table->schema_table)
+ if (!table->placeholder())
*(ptr++)= table->table;
}
@@ -3409,7 +3495,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
for (table= tables; table != first_not_own; table= table->next_global)
{
- if (!table->placeholder() && !table->schema_table)
+ if (!table->placeholder())
{
table->table->query_id= thd->query_id;
if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
@@ -3436,7 +3522,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
for (table= tables; table != first_not_own; table= table->next_global)
{
- if (!table->placeholder() && !table->schema_table &&
+ if (!table->placeholder() &&
check_lock_and_start_stmt(thd, table->table, table->lock_type))
{
ha_rollback_stmt(thd);
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 6673ed1ddf0..cdb75e763b6 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -309,6 +309,38 @@ THD::THD()
substitute_null_with_insert_id = FALSE;
thr_lock_info_init(&lock_info); /* safety: will be reset after start */
thr_lock_owner_init(&main_lock_id, &lock_info);
+
+ m_internal_handler= NULL;
+}
+
+
+void THD::push_internal_handler(Internal_error_handler *handler)
+{
+ /*
+ TODO: The current implementation is limited to 1 handler at a time only.
+ THD and sp_rcontext need to be modified to use a common handler stack.
+ */
+ DBUG_ASSERT(m_internal_handler == NULL);
+ m_internal_handler= handler;
+}
+
+
+bool THD::handle_error(uint sql_errno,
+ MYSQL_ERROR::enum_warning_level level)
+{
+ if (m_internal_handler)
+ {
+ return m_internal_handler->handle_error(sql_errno, level, this);
+ }
+
+ return FALSE; // 'FALSE', as per coding style
+}
+
+
+void THD::pop_internal_handler()
+{
+ DBUG_ASSERT(m_internal_handler != NULL);
+ m_internal_handler= NULL;
}
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index 02a90b942bf..27a58a295ff 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -907,7 +907,7 @@ reopen_tables:
tl->lock_type= using_update_log ? TL_READ_NO_INSERT : TL_READ;
tl->updating= 0;
/* Update TABLE::lock_type accordingly. */
- if (!tl->placeholder() && !tl->schema_table && !using_lock_tables)
+ if (!tl->placeholder() && !using_lock_tables)
tl->table->reginfo.lock_type= tl->lock_type;
}
}
diff --git a/sql/table.cc b/sql/table.cc
index 0fe1f37e9b1..560f53bae26 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -2890,7 +2890,9 @@ void st_table_list::hide_view_error(THD *thd)
thd->net.last_errno == ER_SP_DOES_NOT_EXIST ||
thd->net.last_errno == ER_PROCACCESS_DENIED_ERROR ||
thd->net.last_errno == ER_COLUMNACCESS_DENIED_ERROR ||
- thd->net.last_errno == ER_TABLEACCESS_DENIED_ERROR)
+ thd->net.last_errno == ER_TABLEACCESS_DENIED_ERROR ||
+ thd->net.last_errno == ER_TABLE_NOT_LOCKED ||
+ thd->net.last_errno == ER_NO_SUCH_TABLE)
{
TABLE_LIST *top= top_table();
thd->clear_error();
diff --git a/sql/table.h b/sql/table.h
index b055bfb74e6..54c820d391c 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -856,7 +856,7 @@ typedef struct st_table_list
int view_check_option(THD *thd, bool ignore_failure);
bool setup_underlying(THD *thd);
void cleanup_items();
- bool placeholder() {return derived || view; }
+ bool placeholder() {return derived || view || schema_table || !table; }
void print(THD *thd, String *str);
bool check_single_table(st_table_list **table, table_map map,
st_table_list *view);