diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
commit | a175314c3e5827eb193872241446f2f8f5c9d33c (patch) | |
tree | cd3d60ca99ae00829c52a6ca79150a5b6e62528b /sql/sql_error.cc | |
parent | Initial commit. (diff) | |
download | mariadb-10.5-upstream.tar.xz mariadb-10.5-upstream.zip |
Adding upstream version 1:10.5.12.upstream/1%10.5.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sql/sql_error.cc')
-rw-r--r-- | sql/sql_error.cc | 1012 |
1 files changed, 1012 insertions, 0 deletions
diff --git a/sql/sql_error.cc b/sql/sql_error.cc new file mode 100644 index 00000000..80cdc0bc --- /dev/null +++ b/sql/sql_error.cc @@ -0,0 +1,1012 @@ +/* Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved. + + 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +/********************************************************************** +This file contains the implementation of error and warnings related + + - Whenever an error or warning occurred, it pushes it to a warning list + that the user can retrieve with SHOW WARNINGS or SHOW ERRORS. + + - For each statement, we return the number of warnings generated from this + command. Note that this can be different from @@warning_count as + we reset the warning list only for questions that uses a table. + This is done to allow on to do: + INSERT ...; + SELECT @@warning_count; + SHOW WARNINGS; + (If we would reset after each command, we could not retrieve the number + of warnings) + + - When client requests the information using SHOW command, then + server processes from this list and returns back in the form of + resultset. + + Supported syntaxes: + + SHOW [COUNT(*)] ERRORS [LIMIT [offset,] rows] + SHOW [COUNT(*)] WARNINGS [LIMIT [offset,] rows] + SELECT @@warning_count, @@error_count; + +***********************************************************************/ + +#include "mariadb.h" +#include "sql_priv.h" +#include "unireg.h" +#include "sql_error.h" +#include "sp_rcontext.h" + +/* + Design notes about Sql_condition::m_message_text. + + The member Sql_condition::m_message_text contains the text associated with + an error, warning or note (which are all SQL 'conditions') + + Producer of Sql_condition::m_message_text: + ---------------------------------------- + + (#1) the server implementation itself, when invoking functions like + my_error() or push_warning() + + (#2) user code in stored programs, when using the SIGNAL statement. + + (#3) user code in stored programs, when using the RESIGNAL statement. + + When invoking my_error(), the error number and message is typically + provided like this: + - my_error(ER_WRONG_DB_NAME, MYF(0), ...); + - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); + + In both cases, the message is retrieved from ER(ER_XXX), which in turn + is read from the resource file errmsg.sys at server startup. + The strings stored in the errmsg.sys file are expressed in the character set + that corresponds to the server --language start option + (see error_message_charset_info). + + When executing: + - a SIGNAL statement, + - a RESIGNAL statement, + the message text is provided by the user logic, and is expressed in UTF8. + + Storage of Sql_condition::m_message_text: + --------------------------------------- + + (#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 Sql_condition::m_message_text: + ---------------------------------------- + + (#6) The statements SHOW WARNINGS and SHOW ERRORS display the content of + the warning list. + + (#7) The GET DIAGNOSTICS statement (planned, not implemented yet) will + 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 Sql_condition. + + (#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 + --------------- + -------------- + | ^ + V | + my_error(#1) SIGNAL(#2) RESIGNAL(#3) | + |(#A) |(#B) |(#C) | + | | | | + ----------------------------|---------------------------- | + | | + V | + Sql_condition(#4) | + | | + | | + V | + Warning_info(#5) | + | | + ----------------------------------------------------- | + | | | | + | | | | + | | | | + V V V | + SHOW WARNINGS(#6) GET DIAGNOSTICS(#7) RESIGNAL(#8) | + | | | | | + | -------- | V | + | | | -------------- + V | | + Connectors | | + | | | + ------------------------- + | + V + Client application + + Current implementation status + ----------------------------- + + (#1) (my_error) produces data in the 'error_message_charset_info' CHARSET + + (#2) and (#3) (SIGNAL, RESIGNAL) produces data internally in UTF8 + + (#6) (SHOW WARNINGS) produces data in the 'error_message_charset_info' CHARSET + + (#7) (GET DIAGNOSTICS) is not implemented. + + (#8) (RESIGNAL) produces data internally in UTF8 (see #3) + + 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 Sql_condition::m_message_text'. + + The UTF8 -> error_message_charset_info conversion is implemented in + Sql_cmd_common_signal::eval_signal_informations() (for path #B and #C). + + Future work + ----------- + + - Change (#1) (my_error) to generate errors in UTF8. + See WL#751 (Recoding of error messages) + + - 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> 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) +*/ + + +static void copy_string(MEM_ROOT *mem_root, String* dst, const String* src) +{ + size_t len= src->length(); + if (len) + { + char* copy= (char*) alloc_root(mem_root, len + 1); + if (copy) + { + memcpy(copy, src->ptr(), len); + copy[len]= '\0'; + dst->set(copy, len, src->charset()); + } + } + else + dst->length(0); +} + +void +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); + copy_string(m_mem_root, & m_subclass_origin, & cond->m_subclass_origin); + copy_string(m_mem_root, & m_constraint_catalog, & cond->m_constraint_catalog); + copy_string(m_mem_root, & m_constraint_schema, & cond->m_constraint_schema); + copy_string(m_mem_root, & m_constraint_name, & cond->m_constraint_name); + copy_string(m_mem_root, & m_catalog_name, & cond->m_catalog_name); + copy_string(m_mem_root, & m_schema_name, & cond->m_schema_name); + copy_string(m_mem_root, & m_table_name, & cond->m_table_name); + copy_string(m_mem_root, & m_column_name, & cond->m_column_name); + copy_string(m_mem_root, & m_cursor_name, & cond->m_cursor_name); +} + + +void +Sql_condition::set_builtin_message_text(const char* str) +{ + /* + See the comments + "Design notes about Sql_condition::m_message_text." + */ + const char* copy; + + copy= strdup_root(m_mem_root, str); + m_message_text.set(copy, strlen(copy), error_message_charset_info); + DBUG_ASSERT(! m_message_text.is_alloced()); +} + +const char* +Sql_condition::get_message_text() const +{ + return m_message_text.ptr(); +} + +int +Sql_condition::get_message_octet_length() const +{ + return m_message_text.length(); +} + + +void Sql_state_errno_level::assign_defaults(const Sql_state_errno *from) +{ + DBUG_ASSERT(from); + int sqlerrno= from->get_sql_errno(); + /* + SIGNAL is restricted in sql_yacc.yy to only signal SQLSTATE conditions. + */ + DBUG_ASSERT(from->has_sql_state()); + set_sqlstate(from); + /* SQLSTATE class "00": illegal, rejected in the parser. */ + DBUG_ASSERT(m_sqlstate[0] != '0' || get_sqlstate()[1] != '0'); + + if (Sql_state::is_warning()) /* SQLSTATE class "01": warning. */ + { + m_level= Sql_condition::WARN_LEVEL_WARN; + m_sql_errno= sqlerrno ? sqlerrno : ER_SIGNAL_WARN; + } + else if (Sql_state::is_not_found()) /* SQLSTATE class "02": not found. */ + { + m_level= Sql_condition::WARN_LEVEL_ERROR; + m_sql_errno= sqlerrno ? sqlerrno : ER_SIGNAL_NOT_FOUND; + } + else /* other SQLSTATE classes : error. */ + { + m_level= Sql_condition::WARN_LEVEL_ERROR; + m_sql_errno= sqlerrno ? sqlerrno : ER_SIGNAL_EXCEPTION; + } +} + + +void Sql_condition::assign_defaults(THD *thd, const Sql_state_errno *from) +{ + if (from) + Sql_state_errno_level::assign_defaults(from); + if (!get_message_text()) + set_builtin_message_text(ER(get_sql_errno())); +} + + +Diagnostics_area::Diagnostics_area(bool initialize) + : is_bulk_execution(0), 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, + bool initialize) + : is_bulk_execution(0), + m_main_wi(warning_info_id, allow_unlimited_warnings, initialize) +{ + push_warning_info(&m_main_wi); + + reset_diagnostics_area(); +} + +/** + Clear this diagnostics area. + + Normally called at the end of a statement. +*/ + +void +Diagnostics_area::reset_diagnostics_area() +{ + DBUG_ENTER("reset_diagnostics_area"); + m_skip_flush= FALSE; +#ifdef DBUG_OFF + m_can_overwrite_status= FALSE; + /** Don't take chances in production */ + m_message[0]= '\0'; + Sql_state_errno::clear(); + Sql_user_condition_identity::clear(); + m_affected_rows= 0; + m_last_insert_id= 0; + m_statement_warn_count= 0; +#endif + 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; +} + + +/** + Set OK status -- ends commands that do not return a + result set, e.g. INSERT/UPDATE/DELETE. +*/ + +void +Diagnostics_area::set_ok_status(ulonglong affected_rows, + ulonglong last_insert_id, + const char *message) +{ + DBUG_ENTER("set_ok_status"); + DBUG_ASSERT(!is_set() || (m_status == DA_OK_BULK && is_bulk_op())); + /* + In production, refuse to overwrite an error or a custom response + with an OK packet. + */ + if (unlikely(is_error() || is_disabled())) + return; + /* + When running a bulk operation, m_status will be DA_OK for the first + operation and set to DA_OK_BULK for all following operations. + */ + if (m_status == DA_OK_BULK) + { + m_statement_warn_count+= current_statement_warn_count(); + m_affected_rows+= affected_rows; + } + else + { + m_statement_warn_count= current_statement_warn_count(); + m_affected_rows= affected_rows; + m_status= (is_bulk_op() ? DA_OK_BULK : DA_OK); + } + m_last_insert_id= last_insert_id; + if (message) + strmake_buf(m_message, message); + else + m_message[0]= '\0'; + DBUG_VOID_RETURN; +} + + +/** + Set EOF status. +*/ + +void +Diagnostics_area::set_eof_status(THD *thd) +{ + DBUG_ENTER("set_eof_status"); + /* Only allowed to report eof if has not yet reported an error */ + DBUG_ASSERT(!is_set() || (m_status == DA_EOF_BULK && is_bulk_op())); + /* + In production, refuse to overwrite an error or a custom response + with an EOF packet. + */ + if (unlikely(is_error() || is_disabled())) + return; + + /* + If inside a stored procedure, do not return the total + number of warnings, since they are not available to the client + anyway. + */ + if (m_status == DA_EOF_BULK) + { + if (!thd->spcont) + m_statement_warn_count+= current_statement_warn_count(); + } + else + { + if (thd->spcont) + { + m_statement_warn_count= 0; + m_affected_rows= 0; + } + else + m_statement_warn_count= current_statement_warn_count(); + m_status= (is_bulk_op() ? DA_EOF_BULK : DA_EOF); + } + + DBUG_VOID_RETURN; +} + +/** + 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(uint sql_errno) +{ + set_error_status(sql_errno, + ER(sql_errno), + mysql_errno_to_sqlstate(sql_errno), + Sql_user_condition_identity(), + 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 ucid User defined condition identity + @param error_condition SQL-condition object representing the error state + + @note Note, that error_condition may be NULL. It happens if a) OOM error is + being reported; or b) when Warning_info is full. +*/ + +void +Diagnostics_area::set_error_status(uint sql_errno, + const char *message, + const char *sqlstate, + const Sql_user_condition_identity &ucid, + const Sql_condition *error_condition) +{ + DBUG_ENTER("set_error_status"); + DBUG_PRINT("enter", ("error: %d", sql_errno)); + /* + Only allowed to report error if has not yet reported a success + The only exception is when we flush the message to the client, + an error can happen during the flush. + */ + 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 + ERROR packet. + */ + if (is_disabled()) + return; +#endif + + Sql_state_errno::set(sql_errno, sqlstate); + Sql_user_condition_identity::set(ucid); + strmake_buf(m_message, message); + + get_warning_info()->set_error_condition(error_condition); + + m_status= DA_ERROR; + DBUG_VOID_RETURN; +} + +/** + Mark the diagnostics area as 'DISABLED'. + + This is used in rare cases when the COM_ command at hand sends a response + in a custom format. One example is the query cache, another is + COM_STMT_PREPARE. +*/ + +void +Diagnostics_area::disable_status() +{ + DBUG_ENTER("disable_status"); + DBUG_ASSERT(! is_set()); + m_status= DA_DISABLED; + DBUG_VOID_RETURN; +} + +Warning_info::Warning_info(ulonglong warn_id_arg, + bool allow_unlimited_warnings, bool initialize) + :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(); + memset(m_warn_count, 0, sizeof(m_warn_count)); + if (initialize) + init(); +} + +void Warning_info::init() +{ + /* Initialize sub structures */ + DBUG_ASSERT(initialized == 0); + init_sql_alloc(PSI_INSTRUMENT_ME, &m_warn_root, WARN_ALLOC_BLOCK_SIZE, + WARN_ALLOC_PREALLOC_SIZE, MYF(MY_THREAD_SPECIFIC)); + initialized= 1; +} + +void Warning_info::free_memory() +{ + if (initialized) + free_root(&m_warn_root,MYF(0)); +} + +Warning_info::~Warning_info() +{ + free_memory(); +} + + +bool Warning_info::has_sql_condition(const char *message_str, size_t message_length) const +{ + Diagnostics_area::Sql_condition_iterator it(m_warn_list); + const Sql_condition *err; + + 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) +{ + id(new_id); + m_warn_list.empty(); + 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(); +} + +void Warning_info::append_warning_info(THD *thd, const Warning_info *source) +{ + 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++)) + { + // 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); + } +} + + +/** + 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. +*/ +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() +{ + 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, + const Sql_condition_identity *value, + 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) + { + cond= new (& m_warn_root) Sql_condition(& m_warn_root, *value, msg); + if (cond) + m_warn_list.push_back(cond); + } + m_warn_count[(uint) value->get_level()]++; + } + + m_current_statement_warn_count++; + return cond; +} + + +Sql_condition *Warning_info::push_warning(THD *thd, + const Sql_condition *sql_condition) +{ + Sql_condition *new_condition= push_warning(thd, sql_condition, + sql_condition->get_message_text()); + + if (new_condition) + new_condition->copy_opt_attributes(sql_condition); + + return new_condition; +} + +/* + Push the warning to error list if there is still room in the list + + SYNOPSIS + push_warning() + thd Thread handle + level Severity of warning (note, warning) + code Error number + msg Clear error message +*/ + +void push_warning(THD *thd, Sql_condition::enum_warning_level level, + uint code, const char *msg) +{ + DBUG_ENTER("push_warning"); + DBUG_PRINT("enter", ("code: %d, msg: %s", code, msg)); + + /* + Calling push_warning/push_warning_printf with a level of + WARN_LEVEL_ERROR *is* a bug. Either use my_printf_error(), + my_error(), or WARN_LEVEL_WARN. + */ + DBUG_ASSERT(level != Sql_condition::WARN_LEVEL_ERROR); + + 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->get_stmt_da()->increment_warning(); + + DBUG_VOID_RETURN; +} + + +/* + Push the warning to error list if there is still room in the list + + SYNOPSIS + push_warning_printf() + thd Thread handle + level Severity of warning (note, warning) + code Error number + msg Clear error message +*/ + +void push_warning_printf(THD *thd, Sql_condition::enum_warning_level level, + uint code, const char *format, ...) +{ + va_list args; + char warning[MYSQL_ERRMSG_SIZE]; + DBUG_ENTER("push_warning_printf"); + DBUG_PRINT("enter",("warning: %u", code)); + + DBUG_ASSERT(code != 0); + DBUG_ASSERT(format != NULL); + + va_start(args,format); + my_vsnprintf_ex(&my_charset_utf8mb3_general_ci, warning, + sizeof(warning), format, args); + va_end(args); + push_warning(thd, level, code, warning); + DBUG_VOID_RETURN; +} + + +/* + Send all notes, errors or warnings to the client in a result set + + SYNOPSIS + mysqld_show_warnings() + thd Thread handler + levels_to_show Bitmap for which levels to show + + DESCRIPTION + Takes into account the current LIMIT + + RETURN VALUES + FALSE ok + TRUE Error sending data to client +*/ + +const LEX_CSTRING warning_level_names[]= +{ + { STRING_WITH_LEN("Note") }, + { STRING_WITH_LEN("Warning") }, + { STRING_WITH_LEN("Error") }, + { STRING_WITH_LEN("?") } +}; + +bool mysqld_show_warnings(THD *thd, ulong levels_to_show) +{ + List<Item> field_list; + MEM_ROOT *mem_root= thd->mem_root; + const Sql_condition *err; + SELECT_LEX *sel= thd->lex->first_select_lex(); + SELECT_LEX_UNIT *unit= &thd->lex->unit; + ha_rows idx; + Protocol *protocol=thd->protocol; + DBUG_ENTER("mysqld_show_warnings"); + + DBUG_ASSERT(thd->get_stmt_da()->is_warning_info_read_only()); + + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Level", 7), + mem_root); + field_list.push_back(new (mem_root) + Item_return_int(thd, "Code", 4, MYSQL_TYPE_LONG), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Message", MYSQL_ERRMSG_SIZE), + mem_root); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + unit->set_limit(sel); + + Diagnostics_area::Sql_condition_iterator it= + thd->get_stmt_da()->sql_conditions(); + for (idx= 0; (err= it++) ; idx++) + { + /* Skip levels that the user is not interested in */ + if (!(levels_to_show & ((ulong) 1 << err->get_level()))) + continue; + if (unit->lim.check_offset(idx)) + continue; // using limit offset,count + if (idx >= unit->lim.get_select_limit()) + break; + protocol->prepare_for_resend(); + protocol->store(warning_level_names[err->get_level()].str, + warning_level_names[err->get_level()].length, + system_charset_info); + protocol->store((uint32) err->get_sql_errno()); + protocol->store_warning(err->get_message_text(), + err->get_message_octet_length()); + if (protocol->write()) + DBUG_RETURN(TRUE); + } + my_eof(thd); + + thd->get_stmt_da()->set_warning_info_read_only(FALSE); + + DBUG_RETURN(FALSE); +} + + +/** + This replaces U+0000 to '\0000', so the result error message string: + - is a good null-terminated string + - presents the entire data + For example: + SELECT CAST(_latin1 0x610062 AS SIGNED); + returns a warning: + Truncated incorrect INTEGER value: 'a\0000b' + Notice, the 0x00 byte is replaced to a 5-byte long string '\0000', + while 'a' and 'b' are printed as is. +*/ +extern "C" int my_wc_mb_utf8_null_terminated(CHARSET_INFO *cs, + my_wc_t wc, uchar *r, uchar *e) +{ + return wc == '\0' ? + cs->wc_to_printable(wc, r, e) : + my_charset_utf8mb3_handler.wc_mb(cs, wc, r, e); +} + + +/** + Convert value for dispatch to error message(see WL#751). + + @param to buffer for converted string + @param to_length size of the buffer + @param from string which should be converted + @param from_length string length + @param from_cs charset from convert + + @retval + result string +*/ + +char *err_conv(char *buff, uint to_length, const char *from, + uint from_length, CHARSET_INFO *from_cs) +{ + char *to= buff; + const char *from_start= from; + size_t res; + + DBUG_ASSERT(to_length > 0); + to_length--; + if (from_cs == &my_charset_bin) + { + uchar char_code; + res= 0; + while (1) + { + if ((uint)(from - from_start) >= from_length || + res >= to_length) + { + *to= 0; + break; + } + + char_code= ((uchar) *from); + if (char_code >= 0x20 && char_code <= 0x7E) + { + *to++= char_code; + from++; + res++; + } + else + { + if (res + 4 >= to_length) + { + *to= 0; + break; + } + res+= my_snprintf(to, 5, "\\x%02X", (uint) char_code); + to+=4; + from++; + } + } + } + else + { + uint errors; + res= my_convert_using_func(to, to_length, system_charset_info, + my_wc_mb_utf8_null_terminated, + from, from_length, from_cs, + from_cs->cset->mb_wc, + &errors); + to[res]= 0; + } + return buff; +} + + +/** + Convert string for dispatch to client(see WL#751). + + @param to buffer to convert + @param to_length buffer length + @param to_cs chraset to convert + @param from string from convert + @param from_length string length + @param from_cs charset from convert + @param errors count of errors during convertion + + @retval + length of converted string +*/ + +size_t convert_error_message(char *to, size_t to_length, CHARSET_INFO *to_cs, + const char *from, size_t from_length, + CHARSET_INFO *from_cs, uint *errors) +{ + DBUG_ASSERT(to_length > 0); + /* Make room for the null terminator. */ + to_length--; + + if (!to_cs || to_cs == &my_charset_bin) + to_cs= system_charset_info; + uint32 cnv_length= my_convert_using_func(to, to_length, + to_cs, + to_cs->cset->wc_to_printable, + from, from_length, + from_cs, from_cs->cset->mb_wc, + errors); + DBUG_ASSERT(to_length >= cnv_length); + to[cnv_length]= '\0'; + return cnv_length; +} + + +/** + Sanity check for SQLSTATEs. The function does not check if it's really an + existing SQL-state (there are just too many), it just checks string length and + looks for bad characters. + + @param sqlstate the condition SQLSTATE. + + @retval true if it's ok. + @retval false if it's bad. +*/ + +bool is_sqlstate_valid(const LEX_CSTRING *sqlstate) +{ + if (sqlstate->length != 5) + return false; + + for (int i= 0 ; i < 5 ; ++i) + { + char c = sqlstate->str[i]; + + if ((c < '0' || '9' < c) && + (c < 'A' || 'Z' < c)) + return false; + } + + return true; +} + + +void convert_error_to_warning(THD *thd) +{ + DBUG_ASSERT(thd->is_error()); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); +} |