Commit 060541c0 authored by Alexander Nozdrin's avatar Alexander Nozdrin

A patch for Bug#11763166 (55847: SHOW WARNINGS returns empty

result set when SQLEXCEPTION is active.

The problem was in a hackish THD::no_warnings_for_error attribute.
When it was set, an error was not written to Warning_info -- only
Diagnostics_area state was changed. That means, Diagnostics_area
might contain error state, which is not present in Warning_info.

The user-visible problem was that in some cases SHOW WARNINGS
returned empty result set (i.e. there were no warnings) while
the previous SQL statement failed. According to the MySQL
protocol errors must be presented in warning list.

The main idea of this patch is to remove THD::no_warnings_for_error.
There were few places where it was used:
  - sql_admin.cc, handling of REPAIR TABLE USE_FRM.
  - sql_show.cc, when calling fill_schema_table_from_frm().
  - sql_show.cc, when calling fill_table().
The fix is to either use internal-error-handlers, or to use
temporary Warning_info storing warnings, which might be ignored.

This patch is needed to fix Bug 11763162 (55843).
parent 98d52459
......@@ -316,3 +316,25 @@ SHOW ERRORS;
Level Code Message
Error 1051 Unknown table 't1'
End of 5.0 tests
-- Bug#55847
DROP TABLE IF EXISTS t1;
DROP FUNCTION IF EXISTS f1;
CREATE TABLE t1(a INT UNIQUE);
CREATE FUNCTION f1(x INT) RETURNS INT
BEGIN
INSERT INTO t1 VALUES(x);
INSERT INTO t1 VALUES(x);
RETURN x;
END|
SHOW TABLES WHERE f1(11) = 11;
ERROR 23000: Duplicate entry '11' for key 'a'
SHOW WARNINGS;
Level Code Message
Error 1062 Duplicate entry '11' for key 'a'
DROP TABLE t1;
DROP FUNCTION f1;
......@@ -228,3 +228,43 @@ DROP TABLE t1;
SHOW ERRORS;
--echo End of 5.0 tests
#
# Bug#55847: SHOW WARNINGS returns empty result set when SQLEXCEPTION is active
#
--echo
--echo -- Bug#55847
--echo
--disable_warnings
DROP TABLE IF EXISTS t1;
DROP FUNCTION IF EXISTS f1;
--enable_warnings
CREATE TABLE t1(a INT UNIQUE);
delimiter |;
CREATE FUNCTION f1(x INT) RETURNS INT
BEGIN
INSERT INTO t1 VALUES(x);
INSERT INTO t1 VALUES(x);
RETURN x;
END|
delimiter ;|
--echo
--error ER_DUP_ENTRY
SHOW TABLES WHERE f1(11) = 11;
--echo
SHOW WARNINGS;
--echo
DROP TABLE t1;
DROP FUNCTION f1;
/* Copyright 2002-2008 MySQL AB, 2008-2010 Sun Microsystems, Inc.
/* Copyright (c) 2002, 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
......@@ -1217,7 +1217,8 @@ sp_head::execute(THD *thd, bool merge_da_on_success)
String old_packet;
Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer;
Object_creation_ctx *saved_creation_ctx;
Warning_info *saved_warning_info, warning_info(thd->warning_info->warn_id());
Warning_info *saved_warning_info;
Warning_info warning_info(thd->warning_info->warn_id(), false);
/*
Just reporting a stack overrun error
......
......@@ -263,7 +263,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
const char *operator_name,
thr_lock_type lock_type,
bool open_for_modify,
bool no_warnings_for_error,
bool repair_table_use_frm,
uint extra_open_options,
int (*prepare_func)(THD *, TABLE_LIST *,
HA_CHECK_OPT *),
......@@ -331,18 +331,43 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
lex->query_tables= table;
lex->query_tables_last= &table->next_global;
lex->query_tables_own_last= 0;
/*
Under locked tables, we know that the table can be opened,
so any errors opening the table are logical errors.
In these cases it makes sense to report them.
*/
if (!thd->locked_tables_mode)
thd->no_warnings_for_error= no_warnings_for_error;
if (view_operator_func == NULL)
table->required_type=FRMTYPE_TABLE;
if (!thd->locked_tables_mode && repair_table_use_frm)
{
/*
If we're not under LOCK TABLES and we're executing REPAIR TABLE
USE_FRM, we need to ignore errors from open_and_lock_tables().
REPAIR TABLE USE_FRM is a heavy weapon used when a table is
critically damaged, so open_and_lock_tables() will most likely
report errors. Those errors are not interesting for the user
because it's already known that the table is badly damaged.
*/
Warning_info wi(thd->query_id, false);
Warning_info *wi_saved= thd->warning_info;
thd->warning_info= &wi;
open_error= open_and_lock_tables(thd, table, TRUE, 0);
thd->no_warnings_for_error= 0;
thd->warning_info= wi_saved;
}
else
{
/*
It's assumed that even if it is REPAIR TABLE USE_FRM, the table
can be opened if we're under LOCK TABLES (otherwise LOCK TABLES
would fail). Thus, the only errors we could have from
open_and_lock_tables() are logical ones, like incorrect locking
mode. It does make sense for the user to see such errors.
*/
open_error= open_and_lock_tables(thd, table, TRUE, 0);
}
table->next_global= save_next_global;
table->next_local= save_next_local;
thd->open_options&= ~extra_open_options;
......
/* Copyright (C) 2000-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
/* Copyright (c) 2000, 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
......@@ -13,7 +13,6 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*****************************************************************************
**
** This file implements classes defined in sql_class.h
......@@ -522,7 +521,7 @@ THD::THD()
#if defined(ENABLED_DEBUG_SYNC)
debug_sync_control(0),
#endif /* defined(ENABLED_DEBUG_SYNC) */
main_warning_info(0)
main_warning_info(0, false)
{
ulong tmp;
......@@ -581,7 +580,7 @@ THD::THD()
client_capabilities= 0; // minimalistic client
ull=0;
system_thread= NON_SYSTEM_THREAD;
cleanup_done= abort_on_warning= no_warnings_for_error= 0;
cleanup_done= abort_on_warning= 0;
peer_port= 0; // For SHOW PROCESSLIST
transaction.m_pending_rows_event= 0;
transaction.on= 1;
......@@ -854,10 +853,6 @@ MYSQL_ERROR* THD::raise_condition(uint sql_errno,
query_cache_abort(&query_cache_tls);
/* FIXME: broken special case */
if (no_warnings_for_error && (level == MYSQL_ERROR::WARN_LEVEL_ERROR))
DBUG_RETURN(NULL);
/* When simulating OOM, skip writing to error log to avoid mtr errors */
DBUG_EXECUTE_IF("simulate_out_of_memory", DBUG_RETURN(NULL););
......
/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2000, 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
......@@ -2089,7 +2089,6 @@ class THD :public Statement,
bool enable_slow_log; /* enable slow log for current statement */
bool abort_on_warning;
bool got_warning; /* Set on call to push_warning() */
bool no_warnings_for_error; /* no warnings on call to my_error() */
/* set during loop of derived table processing */
bool derived_tables_processing;
my_bool tablespace_op; /* This is TRUE in DISCARD/IMPORT TABLESPACE */
......@@ -2807,6 +2806,7 @@ class THD :public Statement,
/** The current internal error handler for this thread, or NULL. */
Internal_error_handler *m_internal_handler;
/**
The lex to hold the parsed tree of conventional (non-prepared) queries.
Whereas for prepared and stored procedure statements we use an own lex
......
/* Copyright (C) 1995-2002 MySQL AB,
Copyright (C) 2008-2009 Sun Microsystems, Inc
/* 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
......@@ -458,10 +457,11 @@ Diagnostics_area::disable_status()
m_status= DA_DISABLED;
}
Warning_info::Warning_info(ulonglong warn_id_arg)
Warning_info::Warning_info(ulonglong warn_id_arg, bool allow_unlimited_warnings)
:m_statement_warn_count(0),
m_current_row_for_warning(1),
m_warn_id(warn_id_arg),
m_allow_unlimited_warnings(allow_unlimited_warnings),
m_read_only(FALSE)
{
/* Initialize sub structures */
......@@ -543,7 +543,8 @@ MYSQL_ERROR *Warning_info::push_warning(THD *thd,
if (! m_read_only)
{
if (m_warn_list.elements < thd->variables.max_error_count)
if (m_allow_unlimited_warnings ||
m_warn_list.elements < thd->variables.max_error_count)
{
cond= new (& m_warn_root) MYSQL_ERROR(& m_warn_root);
if (cond)
......@@ -559,6 +560,20 @@ MYSQL_ERROR *Warning_info::push_warning(THD *thd,
return cond;
}
MYSQL_ERROR *Warning_info::push_warning(THD *thd, const MYSQL_ERROR *sql_condition)
{
MYSQL_ERROR *new_condition= push_warning(thd,
sql_condition->get_sql_errno(),
sql_condition->get_sqlstate(),
sql_condition->get_level(),
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
......
/* Copyright (C) 2000-2003 MySQL AB,
Copyright (C) 2008-2009 Sun Microsystems, Inc
/* Copyright (c) 2000, 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
......@@ -323,10 +322,13 @@ class Warning_info
{
/** A memory root to allocate warnings and errors */
MEM_ROOT m_warn_root;
/** List of warnings of all severities (levels). */
List <MYSQL_ERROR> m_warn_list;
/** A break down of the number of warnings per severity (level). */
uint m_warn_count[(uint) MYSQL_ERROR::WARN_LEVEL_END];
/**
The number of warnings of the current statement. Warning_info
life cycle differs from statement life cycle -- it may span
......@@ -334,20 +336,25 @@ class Warning_info
m_statement_warn_count 0, whereas m_warn_list is not empty.
*/
uint m_statement_warn_count;
/*
Row counter, to print in errors and warnings. Not increased in
create_sort_index(); may differ from examined_row_count.
*/
ulong m_current_row_for_warning;
/** Used to optionally clear warnings only once per statement. */
ulonglong m_warn_id;
/** Indicates if push_warning() allows unlimited number of warnings. */
bool m_allow_unlimited_warnings;
private:
Warning_info(const Warning_info &rhs); /* Not implemented */
Warning_info& operator=(const Warning_info &rhs); /* Not implemented */
public:
Warning_info(ulonglong warn_id_arg);
Warning_info(ulonglong warn_id_arg, bool allow_unlimited_warnings);
~Warning_info();
/**
......@@ -384,19 +391,13 @@ class Warning_info
void append_warnings(THD *thd, List<MYSQL_ERROR> *src)
{
MYSQL_ERROR *err;
MYSQL_ERROR *copy;
List_iterator_fast<MYSQL_ERROR> it(*src);
/*
Don't use ::push_warning() to avoid invocation of condition
handlers or escalation of warnings to errors.
*/
while ((err= it++))
{
copy= Warning_info::push_warning(thd, err->get_sql_errno(), err->get_sqlstate(),
err->get_level(), err->get_message_text());
if (copy)
copy->copy_opt_attributes(err);
}
Warning_info::push_warning(thd, err);
}
/**
......@@ -462,6 +463,9 @@ class Warning_info
MYSQL_ERROR::enum_warning_level level,
const char* msg);
/** Add a new condition to the current list. */
MYSQL_ERROR *push_warning(THD *thd, const MYSQL_ERROR *sql_condition);
/**
Set the read only status for this statement area.
This is a privileged operation, reserved for the implementation of
......
/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2000, 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
......@@ -7223,10 +7223,20 @@ bool parse_sql(THD *thd,
bool mysql_parse_status= MYSQLparse(thd) != 0;
/* Check that if MYSQLparse() failed, thd->is_error() is set. */
/*
Check that if MYSQLparse() failed either thd->is_error() is set, or an
internal error handler is set.
The assert will not catch a situation where parsing fails without an
error reported if an error handler exists. The problem is that the
error handler might have intercepted the error, so thd->is_error() is
not set. However, there is no way to be 100% sure here (the error
handler might be for other errors than parsing one).
*/
DBUG_ASSERT(!mysql_parse_status ||
(mysql_parse_status && thd->is_error()));
(mysql_parse_status && thd->is_error()) ||
(mysql_parse_status && thd->get_internal_handler()));
/* Reset parser state. */
......
/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2002, 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
......@@ -2842,7 +2842,8 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length)
param= stmt->param_array[param_number];
Diagnostics_area new_stmt_da, *save_stmt_da= thd->stmt_da;
Warning_info new_warnning_info(thd->query_id), *save_warinig_info= thd->warning_info;
Warning_info new_warnning_info(thd->query_id, false);
Warning_info *save_warinig_info= thd->warning_info;
thd->stmt_da= &new_stmt_da;
thd->warning_info= &new_warnning_info;
......@@ -3900,7 +3901,7 @@ Ed_result_set::Ed_result_set(List<Ed_row> *rows_arg,
*/
Ed_connection::Ed_connection(THD *thd)
:m_warning_info(thd->query_id),
:m_warning_info(thd->query_id, false),
m_thd(thd),
m_rsets(0),
m_current_rset(0)
......
......@@ -3415,6 +3415,45 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables,
}
/**
Trigger_error_handler is intended to intercept and silence SQL conditions
that might happen during trigger loading for SHOW statements.
The potential SQL conditions are:
- ER_PARSE_ERROR -- this error is thrown if a trigger definition file
is damaged or contains invalid CREATE TRIGGER statement. That should
not happen in normal life.
- ER_TRG_NO_DEFINER -- this warning is thrown when we're loading a
trigger created/imported in/from the version of MySQL, which does not
support trigger definers.
- ER_TRG_NO_CREATION_CTX -- this warning is thrown when we're loading a
trigger created/imported in/from the version of MySQL, which does not
support trigger creation contexts.
*/
class Trigger_error_handler : public Internal_error_handler
{
public:
bool handle_condition(THD *thd,
uint sql_errno,
const char* sqlstate,
MYSQL_ERROR::enum_warning_level level,
const char* msg,
MYSQL_ERROR ** cond_hdl)
{
if (sql_errno == ER_PARSE_ERROR ||
sql_errno == ER_TRG_NO_DEFINER ||
sql_errno == ER_TRG_NO_CREATION_CTX)
return true;
return false;
}
};
/**
@brief Fill I_S tables whose data are retrieved
from frm files and storage engine
......@@ -3570,7 +3609,6 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
acl_get(sctx->host, sctx->ip, sctx->priv_user, db_name->str, 0))
#endif
{
thd->no_warnings_for_error= 1;
List<LEX_STRING> table_names;
int res= make_table_name_list(thd, &table_names, lex,
&lookup_field_vals,
......@@ -3619,9 +3657,24 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
if (!(table_open_method & ~OPEN_FRM_ONLY) &&
!with_i_schema)
{
if (!fill_schema_table_from_frm(thd, tables, schema_table, db_name,
table_name, schema_table_idx,
can_deadlock))
/*
Here we need to filter out warnings, which can happen
during loading of triggers in fill_schema_table_from_frm(),
because we don't need those warnings to pollute output of
SELECT from I_S / SHOW-statements.
*/
Trigger_error_handler err_handler;
thd->push_internal_handler(&err_handler);
int res= fill_schema_table_from_frm(thd, tables, schema_table,
db_name, table_name,
schema_table_idx,
can_deadlock);
thd->pop_internal_handler();
if (!res)
continue;
}
......@@ -3631,7 +3684,6 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
Set the parent lex of 'sel' because it is needed by
sel.init_query() which is called inside make_table_list.
*/
thd->no_warnings_for_error= 1;
sel.parent_lex= lex;
if (make_table_list(thd, &sel, db_name, table_name))
goto err;
......@@ -6675,6 +6727,92 @@ int make_schema_select(THD *thd, SELECT_LEX *sel,
}
/**
Fill INFORMATION_SCHEMA-table, leave correct Diagnostics_area /
Warning_info state after itself.
This function is a wrapper around ST_SCHEMA_TABLE::fill_table(), which
may "partially silence" some errors. The thing is that during
fill_table() many errors might be emitted. These errors stem from the
nature of fill_table().
For example, SELECT ... FROM INFORMATION_SCHEMA.xxx WHERE TABLE_NAME = 'xxx'
results in a number of 'Table <db name>.xxx does not exist' errors,
because fill_table() tries to open the 'xxx' table in every possible
database.
Those errors are cleared (the error status is cleared from
Diagnostics_area) inside fill_table(), but they remain in Warning_info
(Warning_info is not cleared because it may contain useful warnings).
This function is responsible for making sure that Warning_info does not
contain warnings corresponding to the cleared errors.
@note: THD::no_warnings_for_error used to be set before calling
fill_table(), thus those errors didn't go to Warning_info. This is not
the case now (THD::no_warnings_for_error was eliminated as a hack), so we
need to take care of those warnings here.
@param thd Thread context.
@param table_list I_S table.
@param join_table JOIN/SELECT table.
@return Error status.
@retval TRUE Error.
@retval FALSE Success.
*/
static bool do_fill_table(THD *thd,
TABLE_LIST *table_list,
JOIN_TAB *join_table)
{
// NOTE: fill_table() may generate many "useless" warnings, which will be
// ignored afterwards. On the other hand, there might be "useful"
// warnings, which should be presented to the user. Warning_info usually
// stores no more than THD::variables.max_error_count warnings.
// The problem is that "useless warnings" may occupy all the slots in the
// Warning_info, so "useful warnings" get rejected. In order to avoid
// that problem we create a Warning_info instance, which is capable of
// storing "unlimited" number of warnings.
Warning_info wi(thd->query_id, true);
Warning_info *wi_saved= thd->warning_info;
thd->warning_info= &wi;
bool res= table_list->schema_table->fill_table(
thd, table_list, join_table->select_cond);
thd->warning_info= wi_saved;
// Pass an error if any.
if (thd->stmt_da->is_error())
{
thd->warning_info->push_warning(thd,
thd->stmt_da->sql_errno(),
thd->stmt_da->get_sqlstate(),
MYSQL_ERROR::WARN_LEVEL_ERROR,
thd->stmt_da->message());
}
// Pass warnings (if any).
//
// Filter out warnings with WARN_LEVEL_ERROR level, because they
// correspond to the errors which were filtered out in fill_table().
List_iterator_fast<MYSQL_ERROR> it(wi.warn_list());
MYSQL_ERROR *err;
while ((err= it++))
{
if (err->get_level() != MYSQL_ERROR::WARN_LEVEL_ERROR)
thd->warning_info->push_warning(thd, err);
}
return res;
}
/*
Fill temporary schema tables before SELECT
......@@ -6697,7 +6835,6 @@ bool get_schema_tables_result(JOIN *join,
bool result= 0;
DBUG_ENTER("get_schema_tables_result");
thd->no_warnings_for_error= 1;
for (JOIN_TAB *tab= join->join_tab; tab < tmp_join_tab; tab++)
{
if (!tab->table || !tab->table->pos_in_table_list)
......@@ -6748,8 +6885,7 @@ bool get_schema_tables_result(JOIN *join,
else
table_list->table->file->stats.records= 0;
if (table_list->schema_table->fill_table(thd, table_list,
tab->select_cond))
if (do_fill_table(thd, table_list, tab))
{
result= 1;
join->error= 1;
......@@ -6761,7 +6897,6 @@ bool get_schema_tables_result(JOIN *join,
table_list->schema_table_state= executed_place;
}
}
thd->no_warnings_for_error= 0;
DBUG_RETURN(result);
}
......
......@@ -1226,7 +1226,6 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
DBUG_RETURN(1); // EOM
}
if (!thd->no_warnings_for_error)
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_TRG_NO_CREATION_CTX,
ER(ER_TRG_NO_CREATION_CTX),
......@@ -1362,7 +1361,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
MySQL, which does not support triggers definers. We should emit
warning here.
*/
if (!thd->no_warnings_for_error)
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER),
(const char*) db,
......
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