diff options
Diffstat (limited to 'sql/sql_error.cc')
-rw-r--r-- | sql/sql_error.cc | 355 |
1 files changed, 240 insertions, 115 deletions
diff --git a/sql/sql_error.cc b/sql/sql_error.cc index 038f4876b14..fc5beb31218 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -47,12 +47,12 @@ This file contains the implementation of error and warnings related #include "sp_rcontext.h" /* - Design notes about MYSQL_ERROR::m_message_text. + Design notes about Sql_condition::m_message_text. - The member MYSQL_ERROR::m_message_text contains the text associated with + The member Sql_condition::m_message_text contains the text associated with an error, warning or note (which are all SQL 'conditions') - Producer of MYSQL_ERROR::m_message_text: + Producer of Sql_condition::m_message_text: ---------------------------------------- (#1) the server implementation itself, when invoking functions like @@ -78,16 +78,16 @@ This file contains the implementation of error and warnings related - a RESIGNAL statement, the message text is provided by the user logic, and is expressed in UTF8. - Storage of MYSQL_ERROR::m_message_text: + Storage of Sql_condition::m_message_text: --------------------------------------- - (#4) The class MYSQL_ERROR is used to hold the message text member. + (#4) The class Sql_condition is used to hold the message text member. This class represents a single SQL condition. (#5) The class Warning_info represents a SQL condition area, and contains a collection of SQL conditions in the Warning_info::m_warn_list - Consumer of MYSQL_ERROR::m_message_text: + Consumer of Sql_condition::m_message_text: ---------------------------------------- (#6) The statements SHOW WARNINGS and SHOW ERRORS display the content of @@ -97,9 +97,9 @@ This file contains the implementation of error and warnings related also read the content of: - the top level statement condition area (when executed in a query), - a sub statement (when executed in a stored program) - and return the data stored in a MYSQL_ERROR. + and return the data stored in a Sql_condition. - (#8) The RESIGNAL statement reads the MYSQL_ERROR caught by an exception + (#8) The RESIGNAL statement reads the Sql_condition caught by an exception handler, to raise a new or modified condition (in #3). The big picture @@ -113,7 +113,7 @@ This file contains the implementation of error and warnings related ----------------------------|---------------------------- | | | V | - MYSQL_ERROR(#4) | + Sql_condition(#4) | | | | | V | @@ -151,10 +151,10 @@ This file contains the implementation of error and warnings related As a result, the design choice for (#4) and (#5) is to store data in the 'error_message_charset_info' CHARSET, to minimize impact on the code base. - This is implemented by using 'String MYSQL_ERROR::m_message_text'. + This is implemented by using 'String Sql_condition::m_message_text'. The UTF8 -> error_message_charset_info conversion is implemented in - Signal_common::eval_signal_informations() (for path #B and #C). + Sql_cmd_common_signal::eval_signal_informations() (for path #B and #C). Future work ----------- @@ -164,14 +164,14 @@ This file contains the implementation of error and warnings related - Change (#4 and #5) to store message text in UTF8 natively. In practice, this means changing the type of the message text to - '<UTF8 String 128 class> MYSQL_ERROR::m_message_text', and is a direct + '<UTF8 String 128 class> Sql_condition::m_message_text', and is a direct consequence of WL#751. - Implement (#9) (GET DIAGNOSTICS). See WL#2111 (Stored Procedures: Implement GET DIAGNOSTICS) */ -MYSQL_ERROR::MYSQL_ERROR() +Sql_condition::Sql_condition() : Sql_alloc(), m_class_origin((const char*) NULL, 0, & my_charset_utf8_bin), m_subclass_origin((const char*) NULL, 0, & my_charset_utf8_bin), @@ -185,20 +185,20 @@ MYSQL_ERROR::MYSQL_ERROR() m_cursor_name((const char*) NULL, 0, & my_charset_utf8_bin), m_message_text(), m_sql_errno(0), - m_level(MYSQL_ERROR::WARN_LEVEL_ERROR), + m_level(Sql_condition::WARN_LEVEL_ERROR), m_mem_root(NULL) { memset(m_returned_sqlstate, 0, sizeof(m_returned_sqlstate)); } -void MYSQL_ERROR::init(MEM_ROOT *mem_root) +void Sql_condition::init(MEM_ROOT *mem_root) { DBUG_ASSERT(mem_root != NULL); DBUG_ASSERT(m_mem_root == NULL); m_mem_root= mem_root; } -void MYSQL_ERROR::clear() +void Sql_condition::clear() { m_class_origin.length(0); m_subclass_origin.length(0); @@ -212,10 +212,10 @@ void MYSQL_ERROR::clear() m_cursor_name.length(0); m_message_text.length(0); m_sql_errno= 0; - m_level= MYSQL_ERROR::WARN_LEVEL_ERROR; + m_level= Sql_condition::WARN_LEVEL_ERROR; } -MYSQL_ERROR::MYSQL_ERROR(MEM_ROOT *mem_root) +Sql_condition::Sql_condition(MEM_ROOT *mem_root) : Sql_alloc(), m_class_origin((const char*) NULL, 0, & my_charset_utf8_bin), m_subclass_origin((const char*) NULL, 0, & my_charset_utf8_bin), @@ -229,7 +229,7 @@ MYSQL_ERROR::MYSQL_ERROR(MEM_ROOT *mem_root) m_cursor_name((const char*) NULL, 0, & my_charset_utf8_bin), m_message_text(), m_sql_errno(0), - m_level(MYSQL_ERROR::WARN_LEVEL_ERROR), + m_level(Sql_condition::WARN_LEVEL_ERROR), m_mem_root(mem_root) { DBUG_ASSERT(mem_root != NULL); @@ -254,7 +254,7 @@ static void copy_string(MEM_ROOT *mem_root, String* dst, const String* src) } void -MYSQL_ERROR::copy_opt_attributes(const MYSQL_ERROR *cond) +Sql_condition::copy_opt_attributes(const Sql_condition *cond) { DBUG_ASSERT(this != cond); copy_string(m_mem_root, & m_class_origin, & cond->m_class_origin); @@ -270,8 +270,8 @@ MYSQL_ERROR::copy_opt_attributes(const MYSQL_ERROR *cond) } void -MYSQL_ERROR::set(uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, const char* msg) +Sql_condition::set(uint sql_errno, const char* sqlstate, + Sql_condition::enum_warning_level level, const char* msg) { DBUG_ASSERT(sql_errno != 0); DBUG_ASSERT(sqlstate != NULL); @@ -286,11 +286,11 @@ MYSQL_ERROR::set(uint sql_errno, const char* sqlstate, } void -MYSQL_ERROR::set_builtin_message_text(const char* str) +Sql_condition::set_builtin_message_text(const char* str) { /* See the comments - "Design notes about MYSQL_ERROR::m_message_text." + "Design notes about Sql_condition::m_message_text." */ const char* copy; @@ -300,24 +300,41 @@ MYSQL_ERROR::set_builtin_message_text(const char* str) } const char* -MYSQL_ERROR::get_message_text() const +Sql_condition::get_message_text() const { return m_message_text.ptr(); } int -MYSQL_ERROR::get_message_octet_length() const +Sql_condition::get_message_octet_length() const { return m_message_text.length(); } void -MYSQL_ERROR::set_sqlstate(const char* sqlstate) +Sql_condition::set_sqlstate(const char* sqlstate) { memcpy(m_returned_sqlstate, sqlstate, SQLSTATE_LENGTH); m_returned_sqlstate[SQLSTATE_LENGTH]= '\0'; } +Diagnostics_area::Diagnostics_area(bool initialize) + : m_main_wi(0, false, initialize) +{ + push_warning_info(&m_main_wi); + + reset_diagnostics_area(); +} + +Diagnostics_area::Diagnostics_area(ulonglong warning_info_id, + bool allow_unlimited_warnings) + : m_main_wi(warning_info_id, allow_unlimited_warnings, true) +{ + push_warning_info(&m_main_wi); + + reset_diagnostics_area(); +} + /** Clear this diagnostics area. @@ -337,7 +354,8 @@ Diagnostics_area::reset_diagnostics_area() m_last_insert_id= 0; m_statement_warn_count= 0; #endif - is_sent= FALSE; + get_warning_info()->clear_error_condition(); + set_is_sent(false); /** Tiny reset in debug mode to see garbage right away */ m_status= DA_EMPTY; DBUG_VOID_RETURN; @@ -350,9 +368,9 @@ Diagnostics_area::reset_diagnostics_area() */ void -Diagnostics_area::set_ok_status(THD *thd, ulonglong affected_rows_arg, - ulonglong last_insert_id_arg, - const char *message_arg) +Diagnostics_area::set_ok_status(ulonglong affected_rows, + ulonglong last_insert_id, + const char *message) { DBUG_ENTER("set_ok_status"); DBUG_ASSERT(! is_set()); @@ -363,11 +381,11 @@ Diagnostics_area::set_ok_status(THD *thd, ulonglong affected_rows_arg, if (is_error() || is_disabled()) return; - m_statement_warn_count= thd->warning_info->statement_warn_count(); - m_affected_rows= affected_rows_arg; - m_last_insert_id= last_insert_id_arg; - if (message_arg) - strmake(m_message, message_arg, sizeof(m_message) - 1); + m_statement_warn_count= current_statement_warn_count(); + m_affected_rows= affected_rows; + m_last_insert_id= last_insert_id; + if (message) + strmake(m_message, message, sizeof(m_message) - 1); else m_message[0]= '\0'; m_status= DA_OK; @@ -398,20 +416,48 @@ Diagnostics_area::set_eof_status(THD *thd) anyway. */ m_statement_warn_count= (thd->spcont ? - 0 : thd->warning_info->statement_warn_count()); + 0 : + current_statement_warn_count()); m_status= DA_EOF; DBUG_VOID_RETURN; } /** - Set ERROR status. + Set ERROR status in the Diagnostics Area. This function should be used to + report fatal errors (such as out-of-memory errors) when no further + processing is possible. + + @param sql_errno SQL-condition error number */ void -Diagnostics_area::set_error_status(THD *thd, uint sql_errno_arg, - const char *message_arg, - const char *sqlstate) +Diagnostics_area::set_error_status(uint sql_errno) +{ + set_error_status(sql_errno, + ER(sql_errno), + mysql_errno_to_sqlstate(sql_errno), + NULL); +} + + +/** + Set ERROR status in the Diagnostics Area. + + @note error_condition may be NULL. It happens if a) OOM error is being + reported; or b) when Warning_info is full. + + @param sql_errno SQL-condition error number + @param message SQL-condition message + @param sqlstate SQL-condition state + @param error_condition SQL-condition object representing the error state +*/ + +void +Diagnostics_area::set_error_status(uint sql_errno, + const char *message, + const char *sqlstate, + const Sql_condition *error_condition) { DBUG_ENTER("set_error_status"); /* @@ -419,7 +465,14 @@ Diagnostics_area::set_error_status(THD *thd, uint sql_errno_arg, The only exception is when we flush the message to the client, an error can happen during the flush. */ - DBUG_ASSERT(! is_set() || can_overwrite_status); + DBUG_ASSERT(! is_set() || m_can_overwrite_status); + + // message must be set properly by the caller. + DBUG_ASSERT(message); + + // sqlstate must be set properly by the caller. + DBUG_ASSERT(sqlstate); + #ifdef DBUG_OFF /* In production, refuse to overwrite a custom response with an @@ -429,19 +482,17 @@ Diagnostics_area::set_error_status(THD *thd, uint sql_errno_arg, return; #endif - if (sqlstate == NULL) - sqlstate= mysql_errno_to_sqlstate(sql_errno_arg); - - m_sql_errno= sql_errno_arg; + m_sql_errno= sql_errno; memcpy(m_sqlstate, sqlstate, SQLSTATE_LENGTH); m_sqlstate[SQLSTATE_LENGTH]= '\0'; - strmake(m_message, message_arg, sizeof(m_message)-1); + strmake(m_message, message, sizeof(m_message)-1); + + get_warning_info()->set_error_condition(error_condition); m_status= DA_ERROR; DBUG_VOID_RETURN; } - /** Mark the diagnostics area as 'DISABLED'. @@ -459,15 +510,16 @@ Diagnostics_area::disable_status() Warning_info::Warning_info(ulonglong warn_id_arg, bool allow_unlimited_warnings, bool initialize) - :m_statement_warn_count(0), + :m_current_statement_warn_count(0), m_current_row_for_warning(1), m_warn_id(warn_id_arg), + m_error_condition(NULL), m_allow_unlimited_warnings(allow_unlimited_warnings), initialized(0), m_read_only(FALSE) { m_warn_list.empty(); - bzero((char*) m_warn_count, sizeof(m_warn_count)); + memset(m_warn_count, 0, sizeof(m_warn_count)); if (initialize) init(); } @@ -492,92 +544,164 @@ Warning_info::~Warning_info() } -/** - Reset the warning information of this connection. -*/ +bool Warning_info::has_sql_condition(const char *message_str, + ulong message_length) const +{ + Diagnostics_area::Sql_condition_iterator it(m_warn_list); + const Sql_condition *err; -void Warning_info::clear_warning_info(ulonglong warn_id_arg) + while ((err= it++)) + { + if (strncmp(message_str, err->get_message_text(), message_length) == 0) + return true; + } + + return false; +} + + +void Warning_info::clear(ulonglong new_id) { - m_warn_id= warn_id_arg; - free_memory(); - bzero((char*) m_warn_count, sizeof(m_warn_count)); + id(new_id); m_warn_list.empty(); - m_statement_warn_count= 0; + m_marked_sql_conditions.empty(); + free_memory(); + memset(m_warn_count, 0, sizeof(m_warn_count)); + m_current_statement_warn_count= 0; m_current_row_for_warning= 1; /* Start counting from the first row */ + clear_error_condition(); } -/** - Append warnings only if the original contents of the routine - warning info was replaced. -*/ -void Warning_info::merge_with_routine_info(THD *thd, Warning_info *source) +void Warning_info::append_warning_info(THD *thd, const Warning_info *source) { - /* - If a routine body is empty or if a routine did not - generate any warnings (thus m_warn_id didn't change), - do not duplicate our own contents by appending the - contents of the called routine. We know that the called - routine did not change its warning info. - - On the other hand, if the routine body is not empty and - some statement in the routine generates a warning or - uses tables, m_warn_id is guaranteed to have changed. - In this case we know that the routine warning info - contains only new warnings, and thus we perform a copy. - */ - if (m_warn_id != source->m_warn_id) + const Sql_condition *err; + Diagnostics_area::Sql_condition_iterator it(source->m_warn_list); + const Sql_condition *src_error_condition = source->get_error_condition(); + + while ((err= it++)) { - /* - If the invocation of the routine was a standalone statement, - rather than a sub-statement, in other words, if it's a CALL - of a procedure, rather than invocation of a function or a - trigger, we need to clear the current contents of the caller's - warning info. - - This is per MySQL rules: if a statement generates a warning, - warnings from the previous statement are flushed. Normally - it's done in push_warning(). However, here we don't use - push_warning() to avoid invocation of condition handlers or - escalation of warnings to errors. - */ - opt_clear_warning_info(thd->query_id); - append_warning_info(thd, source); + // Do not use ::push_warning() to avoid invocation of THD-internal-handlers. + Sql_condition *new_error= Warning_info::push_warning(thd, err); + + if (src_error_condition && src_error_condition == err) + set_error_condition(new_error); + + if (source->is_marked_for_removal(err)) + mark_condition_for_removal(new_error); } } + /** - Add a warning to the list of warnings. Increment the respective - counters. + Copy Sql_conditions that are not WARN_LEVEL_ERROR from the source + Warning_info to the current Warning_info. + + @param thd Thread context. + @param sp_wi Stored-program Warning_info + @param thd Thread context. + @param src_wi Warning_info to copy from. */ -MYSQL_ERROR *Warning_info::push_warning(THD *thd, - uint sql_errno, const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char *msg) +void Diagnostics_area::copy_non_errors_from_wi(THD *thd, + const Warning_info *src_wi) +{ + Sql_condition_iterator it(src_wi->m_warn_list); + const Sql_condition *cond; + Warning_info *wi= get_warning_info(); + + while ((cond= it++)) + { + if (cond->get_level() == Sql_condition::WARN_LEVEL_ERROR) + continue; + + Sql_condition *new_condition= wi->push_warning(thd, cond); + + if (src_wi->is_marked_for_removal(cond)) + wi->mark_condition_for_removal(new_condition); + } +} + + +void Warning_info::mark_sql_conditions_for_removal() +{ + Sql_condition_list::Iterator it(m_warn_list); + Sql_condition *cond; + + while ((cond= it++)) + mark_condition_for_removal(cond); +} + + +void Warning_info::remove_marked_sql_conditions() { - MYSQL_ERROR *cond= NULL; + List_iterator_fast<Sql_condition> it(m_marked_sql_conditions); + Sql_condition *cond; + + while ((cond= it++)) + { + m_warn_list.remove(cond); + m_warn_count[cond->get_level()]--; + m_current_statement_warn_count--; + if (cond == m_error_condition) + m_error_condition= NULL; + } + + m_marked_sql_conditions.empty(); +} + + +bool Warning_info::is_marked_for_removal(const Sql_condition *cond) const +{ + List_iterator_fast<Sql_condition> it( + const_cast<List<Sql_condition>&> (m_marked_sql_conditions)); + Sql_condition *c; + + while ((c= it++)) + { + if (c == cond) + return true; + } + + return false; +} + + +void Warning_info::reserve_space(THD *thd, uint count) +{ + while (m_warn_list.elements() && + (m_warn_list.elements() + count) > thd->variables.max_error_count) + m_warn_list.remove(m_warn_list.front()); +} + +Sql_condition *Warning_info::push_warning(THD *thd, + uint sql_errno, const char* sqlstate, + Sql_condition::enum_warning_level level, + const char *msg) +{ + Sql_condition *cond= NULL; if (! m_read_only) { if (m_allow_unlimited_warnings || - m_warn_list.elements < thd->variables.max_error_count) + m_warn_list.elements() < thd->variables.max_error_count) { - cond= new (& m_warn_root) MYSQL_ERROR(& m_warn_root); + cond= new (& m_warn_root) Sql_condition(& m_warn_root); if (cond) { cond->set(sql_errno, sqlstate, level, msg); - m_warn_list.push_back(cond, &m_warn_root); + m_warn_list.push_back(cond); } } m_warn_count[(uint) level]++; } - m_statement_warn_count++; + m_current_statement_warn_count++; return cond; } -MYSQL_ERROR *Warning_info::push_warning(THD *thd, const MYSQL_ERROR *sql_condition) + +Sql_condition *Warning_info::push_warning(THD *thd, const Sql_condition *sql_condition) { - MYSQL_ERROR *new_condition= push_warning(thd, + Sql_condition *new_condition= push_warning(thd, sql_condition->get_sql_errno(), sql_condition->get_sqlstate(), sql_condition->get_level(), @@ -600,7 +724,7 @@ MYSQL_ERROR *Warning_info::push_warning(THD *thd, const MYSQL_ERROR *sql_conditi msg Clear error message */ -void push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, +void push_warning(THD *thd, Sql_condition::enum_warning_level level, uint code, const char *msg) { DBUG_ENTER("push_warning"); @@ -611,15 +735,15 @@ void push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, WARN_LEVEL_ERROR *is* a bug. Either use my_printf_error(), my_error(), or WARN_LEVEL_WARN. */ - DBUG_ASSERT(level != MYSQL_ERROR::WARN_LEVEL_ERROR); + DBUG_ASSERT(level != Sql_condition::WARN_LEVEL_ERROR); - if (level == MYSQL_ERROR::WARN_LEVEL_ERROR) - level= MYSQL_ERROR::WARN_LEVEL_WARN; + if (level == Sql_condition::WARN_LEVEL_ERROR) + level= Sql_condition::WARN_LEVEL_WARN; (void) thd->raise_condition(code, NULL, level, msg); /* Make sure we also count warnings pushed after calling set_ok_status(). */ - thd->stmt_da->increment_warning(); + thd->get_stmt_da()->increment_warning(); DBUG_VOID_RETURN; } @@ -636,7 +760,7 @@ void push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, msg Clear error message */ -void push_warning_printf(THD *thd, MYSQL_ERROR::enum_warning_level level, +void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level, uint code, const char *format, ...) { va_list args; @@ -685,7 +809,7 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show) List<Item> field_list; DBUG_ENTER("mysqld_show_warnings"); - DBUG_ASSERT(thd->warning_info->is_read_only()); + DBUG_ASSERT(thd->get_stmt_da()->is_warning_info_read_only()); field_list.push_back(new Item_empty_string("Level", 7)); field_list.push_back(new Item_return_int("Code",4, MYSQL_TYPE_LONG)); @@ -695,7 +819,7 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show) Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); - MYSQL_ERROR *err; + const Sql_condition *err; SELECT_LEX *sel= &thd->lex->select_lex; SELECT_LEX_UNIT *unit= &thd->lex->unit; ulonglong idx= 0; @@ -703,7 +827,8 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show) unit->set_limit(sel); - List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); + Diagnostics_area::Sql_condition_iterator it= + thd->get_stmt_da()->sql_conditions(); while ((err= it++)) { /* Skip levels that the user is not interested in */ @@ -726,7 +851,7 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show) } my_eof(thd); - thd->warning_info->set_read_only(FALSE); + thd->get_stmt_da()->set_warning_info_read_only(FALSE); DBUG_RETURN(FALSE); } |