用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2552 lines
84 KiB

5 months ago
/*
Copyright (c) 2002, 2019, 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, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "sql/sp.h"
#include <string.h>
#include <algorithm>
#include <atomic>
#include <memory>
#include <new>
#include <utility>
#include <vector>
#include "m_ctype.h"
#include "m_string.h"
#include "my_alloc.h"
#include "my_base.h"
#include "my_dbug.h"
#include "my_loglevel.h"
#include "my_psi_config.h"
#include "my_sqlcommand.h"
#include "my_sys.h"
#include "mysql/components/services/log_builtins.h"
#include "mysql/components/services/log_shared.h"
#include "mysql/components/services/psi_statement_bits.h"
#include "mysql/psi/mysql_sp.h"
#include "mysql/psi/psi_base.h"
#include "mysql_com.h"
#include "mysqld_error.h"
#include "sql/auth/auth_acls.h"
#include "sql/auth/auth_common.h" // check_some_routine_access
#include "sql/auth/sql_security_ctx.h"
#include "sql/binlog.h" // mysql_bin_log
#include "sql/dd/cache/dictionary_client.h" // dd::cache::Dictionary_client
#include "sql/dd/dd_routine.h" // dd routine methods.
#include "sql/dd/dd_utility.h" // dd::normalize_string
#include "sql/dd/string_type.h"
#include "sql/dd/types/function.h"
#include "sql/dd/types/procedure.h"
#include "sql/dd/types/routine.h"
#include "sql/dd/types/schema.h"
#include "sql/dd/types/trigger.h" //name_collation
#include "sql/dd_sp.h" // prepare_sp_chistics_from_dd_routine
#include "sql/dd_sql_view.h" // update_referencing_views_metadata
#include "sql/dd_table_share.h" // dd_get_mysql_charset
#include "sql/debug_sync.h" // DEBUG_SYNC
#include "sql/error_handler.h" // Internal_error_handler
#include "sql/field.h"
#include "sql/handler.h"
#include "sql/lock.h" // lock_object_name
#include "sql/log.h"
#include "sql/log_event.h" // append_query_string
#include "sql/mdl.h"
#include "sql/mysqld.h" // trust_function_creators
#include "sql/protocol.h"
#include "sql/psi_memory_key.h" // key_memory_sp_head_main_root
#include "sql/set_var.h"
#include "sql/sp_cache.h" // sp_cache_invalidate
#include "sql/sp_head.h" // Stored_program_creation_ctx
#include "sql/sp_pcontext.h" // sp_pcontext
#include "sql/sql_class.h"
#include "sql/sql_const.h"
#include "sql/sql_db.h" // get_default_db_collation
#include "sql/sql_digest_stream.h"
#include "sql/sql_error.h"
#include "sql/sql_list.h"
#include "sql/sql_parse.h" // parse_sql
#include "sql/sql_show.h" // append_identifier
#include "sql/sql_table.h" // write_bin_log
#include "sql/strfunc.h" // lex_string_strmake
#include "sql/system_variables.h"
#include "sql/table.h"
#include "sql/thd_raii.h"
#include "sql/thr_malloc.h"
#include "sql/transaction.h"
#include "sql/transaction_info.h"
#include "sql_string.h"
#include "template_utils.h"
#include "thr_lock.h"
class sp_rcontext;
/* Used in error handling only */
#define SP_TYPE_STRING(type) \
(type == enum_sp_type::FUNCTION ? "FUNCTION" : "PROCEDURE")
static bool create_string(THD *thd, String *buf, enum_sp_type sp_type,
const char *db, size_t dblen, const char *name,
size_t namelen, const char *params, size_t paramslen,
const char *returns, size_t returnslen,
const char *body, size_t bodylen,
st_sp_chistics *chistics,
const LEX_CSTRING &definer_user,
const LEX_CSTRING &definer_host, sql_mode_t sql_mode);
/**************************************************************************
Fetch stored routines and events creation_ctx for upgrade.
**************************************************************************/
bool load_charset(MEM_ROOT *mem_root, Field *field, const CHARSET_INFO *dflt_cs,
const CHARSET_INFO **cs) {
String cs_name;
if (get_field(mem_root, field, &cs_name)) {
*cs = dflt_cs;
return true;
}
*cs = get_charset_by_csname(cs_name.c_ptr(), MY_CS_PRIMARY, MYF(0));
if (*cs == NULL) {
*cs = dflt_cs;
return true;
}
return false;
}
/*************************************************************************/
bool load_collation(MEM_ROOT *mem_root, Field *field,
const CHARSET_INFO *dflt_cl, const CHARSET_INFO **cl) {
String cl_name;
if (get_field(mem_root, field, &cl_name)) {
*cl = dflt_cl;
return true;
}
*cl = get_charset_by_name(cl_name.c_ptr(), MYF(0));
if (*cl == NULL) {
*cl = dflt_cl;
return true;
}
return false;
}
/**************************************************************************
Stored_routine_creation_ctx implementation.
**************************************************************************/
Stored_routine_creation_ctx *
Stored_routine_creation_ctx::create_routine_creation_ctx(
const dd::Routine *routine) {
/* Load character set/collation attributes. */
const CHARSET_INFO *client_cs =
dd_get_mysql_charset(routine->client_collation_id());
const CHARSET_INFO *connection_cl =
dd_get_mysql_charset(routine->connection_collation_id());
const CHARSET_INFO *db_cl =
dd_get_mysql_charset(routine->schema_collation_id());
DBUG_ASSERT(client_cs != NULL);
DBUG_ASSERT(connection_cl != NULL);
DBUG_ASSERT(db_cl != NULL);
// Create the context.
return new (*THR_MALLOC)
Stored_routine_creation_ctx(client_cs, connection_cl, db_cl);
}
/*************************************************************************/
Stored_routine_creation_ctx *Stored_routine_creation_ctx::load_from_db(
THD *thd, const sp_name *name, TABLE *proc_tbl) {
// Load character set/collation attributes.
const CHARSET_INFO *client_cs;
const CHARSET_INFO *connection_cl;
const CHARSET_INFO *db_cl;
const char *db_name = thd->strmake(name->m_db.str, name->m_db.length);
const char *sr_name = thd->strmake(name->m_name.str, name->m_name.length);
bool invalid_creation_ctx = false;
if (load_charset(thd->mem_root,
proc_tbl->field[MYSQL_PROC_FIELD_CHARACTER_SET_CLIENT],
thd->variables.character_set_client, &client_cs)) {
LogErr(WARNING_LEVEL, ER_SR_BOGUS_VALUE, db_name, sr_name,
"mysql.proc.character_set_client");
invalid_creation_ctx = true;
}
if (load_collation(thd->mem_root,
proc_tbl->field[MYSQL_PROC_FIELD_COLLATION_CONNECTION],
thd->variables.collation_connection, &connection_cl)) {
LogErr(WARNING_LEVEL, ER_SR_BOGUS_VALUE, db_name, sr_name,
"mysql.proc.collation_connection.");
invalid_creation_ctx = true;
}
if (load_collation(thd->mem_root,
proc_tbl->field[MYSQL_PROC_FIELD_DB_COLLATION], NULL,
&db_cl)) {
LogErr(WARNING_LEVEL, ER_SR_BOGUS_VALUE, db_name, sr_name,
"mysql.proc.db_collation.");
invalid_creation_ctx = true;
}
if (invalid_creation_ctx) {
LogErr(WARNING_LEVEL, ER_SR_INVALID_CONTEXT, db_name, sr_name);
}
/*
If we failed to retrieve the database collation, load the default one
from the disk.
*/
if (!db_cl) get_default_db_collation(thd, name->m_db.str, &db_cl);
/* Create the context. */
return new (thd->mem_root)
Stored_routine_creation_ctx(client_cs, connection_cl, db_cl);
}
/**
Acquire Shared MDL lock on the routine object.
@param thd Thread context
@param type Type of the routine (enum_sp_type::PROCEDURE/...)
@param name Name of the routine
@param mdl_lock_type Type of MDL lock be acquired on the routine.
@retval false Success
@retval true Error
*/
static bool lock_routine_name(THD *thd, enum_sp_type type, sp_name *name,
enum_mdl_type mdl_lock_type) {
DBUG_TRACE;
DBUG_ASSERT(mdl_lock_type == MDL_SHARED_HIGH_PRIO ||
mdl_lock_type == MDL_SHARED);
MDL_key mdl_key;
if (type == enum_sp_type::FUNCTION)
dd::Function::create_mdl_key(name->m_db.str, name->m_name.str, &mdl_key);
else
dd::Procedure::create_mdl_key(name->m_db.str, name->m_name.str, &mdl_key);
// MDL Lock request on the routine.
MDL_request routine_request;
MDL_REQUEST_INIT_BY_KEY(&routine_request, &mdl_key, mdl_lock_type,
MDL_TRANSACTION);
// Acquire MDL locks
if (thd->mdl_context.acquire_lock(&routine_request,
thd->variables.lock_wait_timeout))
return true;
return false;
}
/**
Return appropriate error about recursion limit reaching
@param thd Thread handle
@param sp The stored procedure executed
@remark For functions and triggers we return error about
prohibited recursion. For stored procedures we
return about reaching recursion limit.
*/
static void recursion_level_error(THD *thd, sp_head *sp) {
if (sp->m_type == enum_sp_type::PROCEDURE) {
my_error(ER_SP_RECURSION_LIMIT, MYF(0),
static_cast<int>(thd->variables.max_sp_recursion_depth),
sp->m_name.str);
} else
my_error(ER_SP_NO_RECURSION, MYF(0));
}
/**
Find routine definition in data dictionary table and create corresponding
sp_head object for it.
@param thd Thread context
@param type Type of routine (PROCEDURE/...)
@param name Name of routine
@param sphp Out parameter in which pointer to created sp_head
object is returned (0 in case of error).
@note
This function may damage current LEX during execution, so it is good
idea to create temporary LEX and make it active before calling it.
@retval
SP_OK Success
@retval
non-SP_OK Error (one of special codes like SP_DOES_NOT_EXISTS)
*/
static enum_sp_return_code db_find_routine(THD *thd, enum_sp_type type,
sp_name *name, sp_head **sphp) {
DBUG_TRACE;
DBUG_PRINT("enter",
("type: %d name: %.*s", static_cast<int>(type),
static_cast<int>(name->m_name.length), name->m_name.str));
*sphp = NULL; // In case of errors
// Grab shared MDL lock on routine object.
if (lock_routine_name(thd, type, name, MDL_SHARED)) return SP_INTERNAL_ERROR;
// Find routine in the data dictionary.
enum_sp_return_code ret;
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
const dd::Routine *routine = NULL;
bool error;
if (type == enum_sp_type::FUNCTION)
error = thd->dd_client()->acquire<dd::Function>(name->m_db.str,
name->m_name.str, &routine);
else
error = thd->dd_client()->acquire<dd::Procedure>(
name->m_db.str, name->m_name.str, &routine);
if (error) return SP_INTERNAL_ERROR;
if (routine == nullptr) return SP_DOES_NOT_EXISTS;
// prepare sp_chistics from the dd::routine object.
st_sp_chistics sp_chistics;
prepare_sp_chistics_from_dd_routine(routine, &sp_chistics);
// prepare stored routine's return type string.
dd::String_type return_type_str;
prepare_return_type_string_from_dd_routine(thd, routine, &return_type_str);
// prepare stored routine's parameters string.
dd::String_type params_str;
prepare_params_string_from_dd_routine(thd, routine, &params_str);
// Create stored routine creation context from the dd::Routine object.
Stored_program_creation_ctx *creation_ctx =
Stored_routine_creation_ctx::create_routine_creation_ctx(routine);
if (creation_ctx == NULL) return SP_INTERNAL_ERROR;
DBUG_EXECUTE_IF("fail_stored_routine_load", return SP_PARSE_ERROR;);
/*
Create sp_head object for the stored routine from the information obtained
from the dd::Routine object.
*/
ret = db_load_routine(
thd, type, name->m_db.str, name->m_db.length, routine->name().c_str(),
routine->name().length(), sphp, routine->sql_mode(), params_str.c_str(),
return_type_str.c_str(), routine->definition().c_str(), &sp_chistics,
routine->definer_user().c_str(), routine->definer_host().c_str(),
routine->created(true), routine->last_altered(true), creation_ctx);
return ret;
}
namespace {
/**
Silence DEPRECATED SYNTAX warnings when loading a stored procedure
into the cache.
*/
class Silence_deprecated_warning final : public Internal_error_handler {
public:
bool handle_condition(THD *, uint sql_errno, const char *,
Sql_condition::enum_severity_level *level,
const char *) override {
if ((sql_errno == ER_WARN_DEPRECATED_SYNTAX ||
sql_errno == ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT) &&
(*level) == Sql_condition::SL_WARNING)
return true;
return false;
}
};
} // namespace
/**
The function parses input strings and returns SP structure.
@param[in] thd Thread handler
@param[in] defstr CREATE... string
@param[in] sql_mode SQL mode
@param[in] creation_ctx Creation context of stored routines
@retval Pointer on sp_head struct Success
@retval NULL error
*/
static sp_head *sp_compile(THD *thd, String *defstr, sql_mode_t sql_mode,
Stored_program_creation_ctx *creation_ctx) {
sp_head *sp;
sql_mode_t old_sql_mode = thd->variables.sql_mode;
ha_rows old_select_limit = thd->variables.select_limit;
sp_rcontext *sp_runtime_ctx_saved = thd->sp_runtime_ctx;
Silence_deprecated_warning warning_handler;
Parser_state parser_state;
sql_digest_state *parent_digest = thd->m_digest;
PSI_statement_locker *parent_locker = thd->m_statement_psi;
thd->variables.sql_mode = sql_mode;
thd->variables.select_limit = HA_POS_ERROR;
if (parser_state.init(thd, defstr->c_ptr(), defstr->length())) {
thd->variables.sql_mode = old_sql_mode;
thd->variables.select_limit = old_select_limit;
return NULL;
}
lex_start(thd);
thd->push_internal_handler(&warning_handler);
thd->sp_runtime_ctx = NULL;
thd->m_digest = NULL;
thd->m_statement_psi = NULL;
if (parse_sql(thd, &parser_state, creation_ctx) || thd->lex == NULL) {
sp = thd->lex->sphead;
sp_head::destroy(sp);
sp = 0;
} else {
sp = thd->lex->sphead;
}
thd->m_digest = parent_digest;
thd->m_statement_psi = parent_locker;
thd->pop_internal_handler();
thd->sp_runtime_ctx = sp_runtime_ctx_saved;
thd->variables.sql_mode = old_sql_mode;
thd->variables.select_limit = old_select_limit;
#ifdef HAVE_PSI_SP_INTERFACE
if (sp != NULL)
sp->m_sp_share =
MYSQL_GET_SP_SHARE(static_cast<uint>(sp->m_type), sp->m_db.str,
sp->m_db.length, sp->m_name.str, sp->m_name.length);
#endif
return sp;
}
class Bad_db_error_handler : public Internal_error_handler {
public:
Bad_db_error_handler() : m_error_caught(false) {}
virtual bool handle_condition(THD *, uint sql_errno, const char *,
Sql_condition::enum_severity_level *,
const char *) {
if (sql_errno == ER_BAD_DB_ERROR) {
m_error_caught = true;
return true;
}
return false;
}
bool error_caught() const { return m_error_caught; }
private:
bool m_error_caught;
};
enum_sp_return_code db_load_routine(
THD *thd, enum_sp_type type, const char *sp_db, size_t sp_db_len,
const char *sp_name, size_t sp_name_len, sp_head **sphp,
sql_mode_t sql_mode, const char *params, const char *returns,
const char *body, st_sp_chistics *sp_chistics, const char *definer_user,
const char *definer_host, longlong created, longlong modified,
Stored_program_creation_ctx *creation_ctx) {
LEX *old_lex = thd->lex, newlex;
char saved_cur_db_name_buf[NAME_LEN + 1];
LEX_STRING saved_cur_db_name = {saved_cur_db_name_buf,
sizeof(saved_cur_db_name_buf)};
bool cur_db_changed;
Bad_db_error_handler db_not_exists_handler;
enum_sp_return_code ret = SP_OK;
thd->lex = &newlex;
newlex.thd = thd;
newlex.set_current_select(NULL);
String defstr;
defstr.set_charset(creation_ctx->get_client_cs());
LEX_CSTRING user = {definer_user, strlen(definer_user)};
LEX_CSTRING host = {definer_host, strlen(definer_host)};
if (!create_string(thd, &defstr, type, NULL, 0, sp_name, sp_name_len, params,
strlen(params), returns, strlen(returns), body,
strlen(body), sp_chistics, user, host, sql_mode)) {
ret = SP_INTERNAL_ERROR;
goto end;
}
thd->push_internal_handler(&db_not_exists_handler);
/*
Change the current database (if needed).
TODO: why do we force switch here?
*/
if (mysql_opt_change_db(thd, {sp_db, sp_db_len}, &saved_cur_db_name, true,
&cur_db_changed)) {
ret = SP_INTERNAL_ERROR;
thd->pop_internal_handler();
goto end;
}
thd->pop_internal_handler();
if (db_not_exists_handler.error_caught()) {
ret = SP_INTERNAL_ERROR;
my_error(ER_BAD_DB_ERROR, MYF(0), sp_db);
goto end;
}
{
*sphp = sp_compile(thd, &defstr, sql_mode, creation_ctx);
/*
Force switching back to the saved current database (if changed),
because it may be NULL. In this case, mysql_change_db() would
generate an error.
*/
if (cur_db_changed &&
mysql_change_db(thd, to_lex_cstring(saved_cur_db_name), true)) {
sp_head::destroy(*sphp);
*sphp = NULL;
ret = SP_INTERNAL_ERROR;
goto end;
}
if (!*sphp) {
ret = SP_PARSE_ERROR;
goto end;
}
(*sphp)->set_definer(user, host);
(*sphp)->set_info(created, modified, sp_chistics, sql_mode);
(*sphp)->set_creation_ctx(creation_ctx);
(*sphp)->optimize();
/*
Not strictly necessary to invoke this method here, since we know
that we've parsed CREATE PROCEDURE/FUNCTION and not an
UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
maintain the invariant that this method is called for each
distinct statement, in case its logic is extended with other
types of analyses in future.
*/
newlex.set_trg_event_type_for_tables();
}
end:
thd->lex->sphead = NULL;
lex_end(thd->lex);
thd->lex = old_lex;
return ret;
}
static void sp_returns_type(THD *thd, String &result, sp_head *sp) {
TABLE table;
TABLE_SHARE share;
Field *field;
table.in_use = thd;
table.s = &share;
field = sp->create_result_field(0, nullptr, &table);
field->sql_type(result);
if (field->has_charset()) {
result.append(STRING_WITH_LEN(" CHARSET "));
result.append(field->charset()->csname);
if (!(field->charset()->state & MY_CS_PRIMARY)) {
result.append(STRING_WITH_LEN(" COLLATE "));
result.append(field->charset()->name);
}
}
destroy(field);
}
/**
Precheck for create routine statement.
@param thd Thread context.
@param sp Stored routine object to store.
@retval false Success.
@retval true Error.
*/
static bool create_routine_precheck(THD *thd, sp_head *sp) {
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
// Check if routine with same name exists.
bool error;
const dd::Routine *sr;
if (sp->m_type == enum_sp_type::FUNCTION)
error = thd->dd_client()->acquire<dd::Function>(sp->m_db.str,
sp->m_name.str, &sr);
else
error = thd->dd_client()->acquire<dd::Procedure>(sp->m_db.str,
sp->m_name.str, &sr);
if (error) {
// Error is reported by DD API framework.
return true;
}
if (sr != nullptr) {
my_error(ER_SP_ALREADY_EXISTS, MYF(0), SP_TYPE_STRING(sp->m_type),
sp->m_name.str);
return true;
}
/*
Check if stored function creation is allowed only to the users having SUPER
privileges.
*/
if (mysql_bin_log.is_open() && (sp->m_type == enum_sp_type::FUNCTION) &&
!trust_function_creators) {
if (!sp->m_chistics->detistic) {
/*
Note that this test is not perfect; one could use
a non-deterministic read-only function in an update statement.
*/
enum enum_sp_data_access access =
(sp->m_chistics->daccess == SP_DEFAULT_ACCESS)
? static_cast<enum_sp_data_access>(SP_DEFAULT_ACCESS_MAPPING)
: sp->m_chistics->daccess;
if (access == SP_CONTAINS_SQL || access == SP_MODIFIES_SQL_DATA) {
my_error(ER_BINLOG_UNSAFE_ROUTINE, MYF(0));
return true;
}
}
if (!(thd->security_context()->check_access(SUPER_ACL))) {
my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, MYF(0));
return true;
}
}
/*
Check routine body length.
Note: Length of routine name and parameters name is already verified in
parsing phase.
*/
if (sp->m_body.length > MYSQL_STORED_ROUTINE_BODY_LENGTH ||
DBUG_EVALUATE_IF("simulate_routine_length_error", 1, 0)) {
my_error(ER_TOO_LONG_BODY, MYF(0), sp->m_name.str);
return true;
}
// Validate body definition to avoid invalid UTF8 characters.
if (is_invalid_string(sp->m_body_utf8, system_charset_info)) return true;
// Validate routine comment.
if (sp->m_chistics->comment.length) {
// validate comment string to avoid invalid utf8 characters.
if (is_invalid_string(LEX_CSTRING{sp->m_chistics->comment.str,
sp->m_chistics->comment.length},
system_charset_info))
return true;
// Check comment string length.
if (check_string_char_length(
{sp->m_chistics->comment.str, sp->m_chistics->comment.length}, "",
MYSQL_STORED_ROUTINE_COMMENT_LENGTH, system_charset_info, true)) {
my_error(ER_TOO_LONG_ROUTINE_COMMENT, MYF(0), sp->m_chistics->comment.str,
MYSQL_STORED_ROUTINE_COMMENT_LENGTH);
return true;
}
}
return false;
}
/**
Creates a stored routine.
Atomicity:
The operation to create a stored routine is atomic/crash-safe.
Changes to the Data-dictionary and writing event to binlog are
part of the same transaction. All the changes are done as part
of the same transaction or do not have any side effects on the
operation failure. Data-dictionary, stored routines and table
definition caches are in sync with operation state. Cache do
not contain any stale/incorrect data in case of failure.
In case of crash, there won't be any discrepancy between
the data-dictionary table and the binary log.
@param thd Thread context.
@param sp Stored routine object to store.
@param definer Definer of the SP.
@retval false success
@retval true error
*/
bool sp_create_routine(THD *thd, sp_head *sp, const LEX_USER *definer) {
DBUG_TRACE;
DBUG_PRINT("enter", ("type: %d name: %.*s", static_cast<int>(sp->m_type),
static_cast<int>(sp->m_name.length), sp->m_name.str));
DBUG_ASSERT(sp->m_type == enum_sp_type::PROCEDURE ||
sp->m_type == enum_sp_type::FUNCTION);
/* Grab an exclusive MDL lock. */
MDL_key::enum_mdl_namespace mdl_type = (sp->m_type == enum_sp_type::FUNCTION)
? MDL_key::FUNCTION
: MDL_key::PROCEDURE;
if (lock_object_name(thd, mdl_type, sp->m_db.str, sp->m_name.str)) {
my_error(ER_SP_STORE_FAILED, MYF(0), SP_TYPE_STRING(sp->m_type),
sp->m_name.str);
return true;
}
DEBUG_SYNC(thd, "after_acquiring_mdl_lock_on_routine");
if (create_routine_precheck(thd, sp)) {
/* If this happens, an error should have been reported. */
return true;
}
DBUG_EXECUTE_IF("fail_while_acquiring_routine_schema_obj",
DBUG_SET("+d,fail_while_acquiring_dd_object"););
// Check that a database with this name exists.
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
const dd::Schema *schema = nullptr;
if (thd->dd_client()->acquire(sp->m_db.str, &schema)) {
DBUG_EXECUTE_IF("fail_while_acquiring_routine_schema_obj",
DBUG_SET("-d,fail_while_acquiring_dd_object"););
// Error is reported by DD API framework.
return true;
}
if (schema == nullptr) {
my_error(ER_BAD_DB_ERROR, MYF(0), sp->m_db.str);
return true;
}
// Create a stored routine.
if (dd::create_routine(thd, *schema, sp, definer))
goto err_report_with_rollback;
// Update referencing views metadata.
{
sp_name spname({sp->m_db.str, sp->m_db.length}, sp->m_name, false);
if (sp->m_type == enum_sp_type::FUNCTION &&
update_referencing_views_metadata(thd, &spname)) {
/* If this happens, an error should have been reported. */
goto err_with_rollback;
}
}
// Log stored routine create event.
if (mysql_bin_log.is_open()) {
String log_query;
log_query.set_charset(system_charset_info);
String retstr(64);
retstr.set_charset(system_charset_info);
if (sp->m_type == enum_sp_type::FUNCTION) sp_returns_type(thd, retstr, sp);
if (!create_string(thd, &log_query, sp->m_type,
(sp->m_explicit_name ? sp->m_db.str : NULL),
(sp->m_explicit_name ? sp->m_db.length : 0),
sp->m_name.str, sp->m_name.length, sp->m_params.str,
sp->m_params.length, retstr.c_ptr(), retstr.length(),
sp->m_body.str, sp->m_body.length, sp->m_chistics,
definer->user, definer->host, thd->variables.sql_mode))
goto err_report_with_rollback;
thd->add_to_binlog_accessed_dbs(sp->m_db.str);
/*
This statement will be replicated as a statement, even when using
row-based replication.
*/
Save_and_Restore_binlog_format_state binlog_format_state(thd);
if (write_bin_log(thd, true, log_query.c_ptr(), log_query.length(), true))
goto err_report_with_rollback;
}
// Commit changes to the data-dictionary and binary log.
if (DBUG_EVALUATE_IF("simulate_create_routine_failure", true, false) ||
trans_commit_stmt(thd) || trans_commit(thd))
goto err_report_with_rollback;
// Invalidate stored routine cache.
sp_cache_invalidate();
return false;
err_report_with_rollback:
my_error(ER_SP_STORE_FAILED, MYF(0), SP_TYPE_STRING(sp->m_type),
sp->m_name.str);
err_with_rollback:
trans_rollback_stmt(thd);
/*
Full rollback in case we have THD::transaction_rollback_request
and to synchronize DD state in cache and on disk (as statement
rollback doesn't clear DD cache of modified uncommitted objects).
*/
trans_rollback(thd);
return true;
}
/**
Drops a stored routine.
Atomicity:
The operation to drop a stored routine is atomic/crash-safe.
Changes to the Data-dictionary and writing event to binlog are
part of the same transaction. All the changes are done as part
of the same transaction or do not have any side effects on the
operation failure. Data-dictionary, stored routines and table
definition caches are in sync with operation state. Cache do
not contain any stale/incorrect data in case of failure.
In case of crash, there won't be any discrepancy between
the data-dictionary table and the binary log.
@param thd Thread context.
@param type Stored routine type
(PROCEDURE or FUNCTION)
@param name Stored routine name.
@return Error code. SP_OK is returned on success. Other SP_ constants are
used to indicate about errors.
*/
enum_sp_return_code sp_drop_routine(THD *thd, enum_sp_type type,
sp_name *name) {
DBUG_TRACE;
DBUG_PRINT("enter",
("type: %d name: %.*s", static_cast<int>(type),
static_cast<int>(name->m_name.length), name->m_name.str));
DBUG_ASSERT(type == enum_sp_type::PROCEDURE ||
type == enum_sp_type::FUNCTION);
/* Grab an exclusive MDL lock. */
MDL_key::enum_mdl_namespace mdl_type =
(type == enum_sp_type::FUNCTION) ? MDL_key::FUNCTION : MDL_key::PROCEDURE;
if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str))
return SP_DROP_FAILED;
DEBUG_SYNC(thd, "after_acquiring_mdl_lock_on_routine");
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
const dd::Routine *routine = NULL;
bool error;
if (type == enum_sp_type::FUNCTION)
error = thd->dd_client()->acquire<dd::Function>(name->m_db.str,
name->m_name.str, &routine);
else
error = thd->dd_client()->acquire<dd::Procedure>(
name->m_db.str, name->m_name.str, &routine);
if (error) return SP_INTERNAL_ERROR;
if (routine == nullptr) return SP_DOES_NOT_EXISTS;
/*
If definer has the SYSTEM_USER privilege then invoker can drop procedure
only if latter also has same privilege.
*/
Auth_id definer(routine->definer_user().c_str(),
routine->definer_host().c_str());
Security_context *sctx = thd->security_context();
if (sctx->can_operate_with(definer, consts::system_user, true))
return SP_INTERNAL_ERROR;
// Drop routine.
if (thd->dd_client()->drop(routine)) goto err_with_rollback;
// Update referencing views metadata.
if (mdl_type == MDL_key::FUNCTION &&
update_referencing_views_metadata(thd, name)) {
/* If this happens, an error should have been reported. */
goto err_with_rollback;
}
// Log drop routine event.
if (mysql_bin_log.is_open()) {
thd->add_to_binlog_accessed_dbs(name->m_db.str);
/*
This statement will be replicated as a statement, even when using
row-based replication.
*/
Save_and_Restore_binlog_format_state binlog_format_state(thd);
if (write_bin_log(thd, true, thd->query().str, thd->query().length, true))
goto err_with_rollback;
}
// Commit changes to the data-dictionary and binary log.
if (DBUG_EVALUATE_IF("simulate_drop_routine_failure", true, false) ||
trans_commit_stmt(thd) || trans_commit(thd))
goto err_with_rollback;
#ifdef HAVE_PSI_SP_INTERFACE
/* Drop statistics for this stored program from performance schema. */
MYSQL_DROP_SP(static_cast<uint>(type), name->m_db.str, name->m_db.length,
name->m_name.str, name->m_name.length);
#endif
// Invalidate routine cache.
{
sp_cache_invalidate();
/*
A lame workaround for lack of cache flush:
make sure the routine is at least gone from the
local cache.
*/
{
sp_head *sp;
sp_cache **spc = (type == enum_sp_type::FUNCTION ? &thd->sp_func_cache
: &thd->sp_proc_cache);
sp = sp_cache_lookup(spc, name);
if (sp) sp_cache_flush_obsolete(spc, &sp);
}
}
return SP_OK;
err_with_rollback:
trans_rollback_stmt(thd);
/*
Full rollback in case we have THD::transaction_rollback_request
and to synchronize DD state in cache and on disk (as statement
rollback doesn't clear DD cache of modified uncommitted objects).
*/
trans_rollback(thd);
return SP_DROP_FAILED;
}
/**
Updates(Alter) a stored routine.
Atomicity:
The operation to Update(Alter) a stored routine is atomic/crash-safe.
Changes to the Data-dictionary and writing event to binlog are
part of the same transaction. All the changes are done as part
of the same transaction or do not have any side effects on the
operation failure. Data-dictionary and stored routines caches
caches are in sync with operation state. Cache do not contain any
stale/incorrect data in case of failure.
In case of crash, there won't be any discrepancy between
the data-dictionary table and the binary log.
@param thd Thread context.
@param type Stored routine type
(PROCEDURE or FUNCTION)
@param name Stored routine name.
@param chistics New values of stored routine attributes to write.
@retval false Success.
@retval true Error.
*/
bool sp_update_routine(THD *thd, enum_sp_type type, sp_name *name,
st_sp_chistics *chistics) {
DBUG_TRACE;
DBUG_PRINT("enter",
("type: %d name: %.*s", static_cast<int>(type),
static_cast<int>(name->m_name.length), name->m_name.str));
DBUG_ASSERT(type == enum_sp_type::PROCEDURE ||
type == enum_sp_type::FUNCTION);
/* Grab an exclusive MDL lock. */
MDL_key::enum_mdl_namespace mdl_type =
(type == enum_sp_type::FUNCTION) ? MDL_key::FUNCTION : MDL_key::PROCEDURE;
if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str)) {
my_error(ER_SP_CANT_ALTER, MYF(0), SP_TYPE_STRING(type), name->m_name.str);
return true;
}
// Check if routine exists.
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
dd::Routine *routine = nullptr;
bool error;
if (type == enum_sp_type::FUNCTION)
error = thd->dd_client()->acquire_for_modification<dd::Function>(
name->m_db.str, name->m_name.str, &routine);
else
error = thd->dd_client()->acquire_for_modification<dd::Procedure>(
name->m_db.str, name->m_name.str, &routine);
if (error) {
// Error is reported by DD API framework.
return true;
}
if (routine == nullptr) {
my_error(ER_SP_DOES_NOT_EXIST, MYF(0), SP_TYPE_STRING(type),
thd->lex->spname->m_qname.str);
return true;
}
/*
If definer has the SYSTEM_USER privilege then invoker can alter procedure
only if latter also has same privilege.
*/
Auth_id definer(routine->definer_user().c_str(),
routine->definer_host().c_str());
Security_context *sctx = thd->security_context();
if (sctx->can_operate_with(definer, consts::system_user, true)) return true;
if (mysql_bin_log.is_open() && type == enum_sp_type::FUNCTION &&
!trust_function_creators &&
(chistics->daccess == SP_CONTAINS_SQL ||
chistics->daccess == SP_MODIFIES_SQL_DATA)) {
if (!routine->is_deterministic()) {
my_error(ER_BINLOG_UNSAFE_ROUTINE, MYF(0));
return true;
}
}
// Validate routine comment.
if (chistics->comment.str) {
// validate comment string to invalid utf8 characters.
if (is_invalid_string(chistics->comment, system_charset_info)) return true;
// Check comment string length.
if (check_string_char_length(
{chistics->comment.str, chistics->comment.length}, "",
MYSQL_STORED_ROUTINE_COMMENT_LENGTH, system_charset_info, true)) {
my_error(ER_TOO_LONG_ROUTINE_COMMENT, MYF(0), chistics->comment.str,
MYSQL_STORED_ROUTINE_COMMENT_LENGTH);
return true;
}
}
// Alter stored routine.
if (DBUG_EVALUATE_IF("simulate_alter_routine_failure", true, false) ||
dd::alter_routine(thd, routine, chistics))
goto err_report_with_rollback;
// Log update statement.
if (mysql_bin_log.is_open()) {
/*
This statement will be replicated as a statement, even when using
row-based replication.
*/
Save_and_Restore_binlog_format_state binlog_format_state(thd);
if (write_bin_log(thd, true, thd->query().str, thd->query().length, true))
goto err_report_with_rollback;
}
// Commit changes to the data-dictionary and binary log.
if (DBUG_EVALUATE_IF("simulate_alter_routine_xcommit_failure", true, false) ||
trans_commit_stmt(thd) || trans_commit(thd))
goto err_report_with_rollback;
sp_cache_invalidate();
return false;
err_report_with_rollback:
my_error(ER_SP_CANT_ALTER, MYF(0), SP_TYPE_STRING(type),
thd->lex->spname->m_qname.str);
trans_rollback_stmt(thd);
/*
Full rollback in case we have THD::transaction_rollback_request
and to synchronize DD state in cache and on disk (as statement
rollback doesn't clear DD cache of modified uncommitted objects).
*/
trans_rollback(thd);
return true;
}
bool lock_db_routines(THD *thd, const dd::Schema &schema) {
DBUG_TRACE;
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
// Vector for the stored routines of the schema.
std::vector<const dd::Routine *> routines;
// Fetch stored routines of the schema.
if (thd->dd_client()->fetch_schema_components(&schema, &routines))
return true;
/*
If lower_case_table_names == 2 then schema names should be lower cased for
proper hash key comparisons.
*/
const char *schema_name = schema.name().c_str();
char schema_name_buf[NAME_LEN + 1];
if (lower_case_table_names == 2) {
my_stpcpy(schema_name_buf, schema_name);
my_casedn_str(system_charset_info, schema_name_buf);
schema_name = schema_name_buf;
}
MDL_request_list mdl_requests;
for (const dd::Routine *routine : routines) {
MDL_key mdl_key;
if (is_dd_routine_type_function(routine))
dd::Function::create_mdl_key(dd::String_type(schema_name),
routine->name(), &mdl_key);
else
dd::Procedure::create_mdl_key(dd::String_type(schema_name),
routine->name(), &mdl_key);
// Add MDL_request for routine to mdl_requests list.
MDL_request *mdl_request = new (thd->mem_root) MDL_request;
MDL_REQUEST_INIT_BY_KEY(mdl_request, &mdl_key, MDL_EXCLUSIVE,
MDL_TRANSACTION);
mdl_requests.push_front(mdl_request);
}
return thd->mdl_context.acquire_locks(&mdl_requests,
thd->variables.lock_wait_timeout);
}
/**
Drop all routines in database 'db'
@param thd Thread context.
@param schema Schema object.
@retval SP_OK Success
@retval non-SP_OK Error (Other constants are used to indicate errors)
*/
enum_sp_return_code sp_drop_db_routines(THD *thd, const dd::Schema &schema) {
DBUG_TRACE;
bool is_routine_dropped = false;
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
// Vector for the stored routines of the schema.
std::vector<const dd::Routine *> routines;
// Fetch stored routines of the schema.
if (thd->dd_client()->fetch_schema_components(&schema, &routines))
return SP_INTERNAL_ERROR;
enum_sp_return_code ret_code = SP_OK;
for (const dd::Routine *routine : routines) {
sp_name name(
{schema.name().c_str(), schema.name().length()},
{const_cast<char *>(routine->name().c_str()), routine->name().length()},
false);
enum_sp_type type = is_dd_routine_type_function(routine)
? enum_sp_type::FUNCTION
: enum_sp_type::PROCEDURE;
DBUG_EXECUTE_IF("fail_drop_db_routines", {
my_error(ER_SP_DROP_FAILED, MYF(0), "ROUTINE", "");
return SP_DROP_FAILED;
});
if (thd->dd_client()->drop(routine)) {
ret_code = SP_DROP_FAILED;
my_error(ER_SP_DROP_FAILED, MYF(0),
is_dd_routine_type_function(routine) ? "FUNCTION" : "PROCEDURE",
routine->name().c_str());
break;
}
if (type == enum_sp_type::FUNCTION &&
update_referencing_views_metadata(thd, &name))
return SP_INTERNAL_ERROR;
is_routine_dropped = true;
#ifdef HAVE_PSI_SP_INTERFACE
/* Drop statistics for this stored routine from performance schema. */
MYSQL_DROP_SP(to_uint(type), schema.name().c_str(), schema.name().length(),
routine->name().c_str(), routine->name().length());
#endif
}
// Invalidate the sp cache.
if (is_routine_dropped) sp_cache_invalidate();
return ret_code;
}
/**
Prepare show create routine output from the DD routine object.
@param[in] thd Thread handle.
@param[in] type Stored routine type.
@param[in] sp Stored routine name.
@param[in] routine Routine object read for the routine.
@retval false on success
@retval true on error
*/
static bool show_create_routine_from_dd_routine(THD *thd, enum_sp_type type,
sp_name *sp,
const dd::Routine *routine) {
DBUG_TRACE;
/*
Before WL#7897 changes, full access to routine information is provided to
the definer of routine and to the user having SELECT privilege on
mysql.proc. But as part of WL#7897, mysql.proc table is removed. Now, non
definer user can not have full access on the routine. So backup of routine
or getting exact create string of stored routine is not possible with this
change.
So as workaround for this issue, currently full access on stored routine
is provided to any user having global SELECT privilege.
Correct solution to this issue will be provided with the WL#8131 and
WL#9049.
*/
Security_context *sctx = thd->security_context();
bool full_access =
(sctx->check_access(SELECT_ACL, sp->m_db.str) ||
(!strcmp(routine->definer_user().c_str(), sctx->priv_user().str) &&
!strcmp(routine->definer_host().c_str(), sctx->priv_host().str)));
if (!full_access &&
check_some_routine_access(thd, sp->m_db.str, sp->m_name.str,
type == enum_sp_type::PROCEDURE))
return true;
// prepare st_sp_chistics object from the dd::Routine.
st_sp_chistics sp_chistics;
prepare_sp_chistics_from_dd_routine(routine, &sp_chistics);
// prepare stored routine return type string.
dd::String_type return_type_str;
prepare_return_type_string_from_dd_routine(thd, routine, &return_type_str);
// Prepare stored routine definition string.
String defstr;
defstr.set_charset(system_charset_info);
if (!create_string(
thd, &defstr, type, NULL, 0, routine->name().c_str(),
routine->name().length(), routine->parameter_str().c_str(),
routine->parameter_str().length(), return_type_str.c_str(),
return_type_str.length(), routine->definition().c_str(),
routine->definition().length(), &sp_chistics,
{routine->definer_user().c_str(), routine->definer_user().length()},
{routine->definer_host().c_str(), routine->definer_host().length()},
routine->sql_mode()))
return true;
// Prepare sql_mode string representation.
LEX_STRING sql_mode;
sql_mode_string_representation(thd, routine->sql_mode(), &sql_mode);
/* Send header. */
List<Item> fields;
// Column type
const char *col1_caption =
(type == enum_sp_type::PROCEDURE) ? "Procedure" : "Function";
fields.push_back(new Item_empty_string(col1_caption, NAME_CHAR_LEN));
// Column sql_mode
fields.push_back(new Item_empty_string("sql_mode", sql_mode.length));
// Column Create Procedure/Function.
{
const char *col3_caption = (type == enum_sp_type::PROCEDURE)
? "Create Procedure"
: "Create Function";
/*
NOTE: SQL statement field must be not less than 1024 in order not to
confuse old clients.
*/
Item_empty_string *stmt_fld = new Item_empty_string(
col3_caption, std::max<size_t>(defstr.length(), 1024U));
stmt_fld->maybe_null = true;
fields.push_back(stmt_fld);
}
// Column character_set_client.
fields.push_back(
new Item_empty_string("character_set_client", MY_CS_NAME_SIZE));
// Column collation collation.
fields.push_back(
new Item_empty_string("collation_connection", MY_CS_NAME_SIZE));
// Column database collection.
fields.push_back(
new Item_empty_string("Database Collation", MY_CS_NAME_SIZE));
if (thd->send_result_metadata(&fields,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
return true;
/* Send data. */
Protocol *protocol = thd->get_protocol();
protocol->start_row();
// Routine Name
protocol->store_string(routine->name().c_str(), routine->name().length(),
system_charset_info);
// sql mode.
protocol->store_string(sql_mode.str, sql_mode.length, system_charset_info);
// Routine definition.
const CHARSET_INFO *cs_info =
dd_get_mysql_charset(routine->client_collation_id());
if (full_access)
protocol->store_string(defstr.c_ptr(), defstr.length(), cs_info);
else
protocol->store_null();
// character_set_client
protocol->store(cs_info->csname, system_charset_info);
// connection_collation
cs_info = dd_get_mysql_charset(routine->connection_collation_id());
protocol->store(cs_info->name, system_charset_info);
// database_collation
cs_info = dd_get_mysql_charset(routine->schema_collation_id());
protocol->store(cs_info->name, system_charset_info);
bool err_status = protocol->end_row();
if (!err_status) my_eof(thd);
return err_status;
}
/**
Implement SHOW CREATE statement for stored routines.
The operation finds the stored routine object specified by name and
then calls show_create_routine_from_dd_routine().
@param thd Thread context.
@param type Stored routine type
(PROCEDURE or FUNCTION)
@param name Stored routine name.
@retval false on success
@retval true on error
*/
bool sp_show_create_routine(THD *thd, enum_sp_type type, sp_name *name) {
DBUG_TRACE;
DBUG_PRINT("enter",
("name: %.*s", (int)name->m_name.length, name->m_name.str));
DBUG_ASSERT(type == enum_sp_type::PROCEDURE ||
type == enum_sp_type::FUNCTION);
// Lock routine for read.
if (lock_routine_name(thd, type, name, MDL_SHARED_HIGH_PRIO)) {
my_error(ER_SP_LOAD_FAILED, MYF(0), name->m_name.str);
return true;
}
// Find routine in data dictionary.
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
const dd::Routine *routine = NULL;
bool error;
if (type == enum_sp_type::FUNCTION)
error = thd->dd_client()->acquire<dd::Function>(name->m_db.str,
name->m_name.str, &routine);
else
error = thd->dd_client()->acquire<dd::Procedure>(
name->m_db.str, name->m_name.str, &routine);
if (error) {
my_error(ER_SP_LOAD_FAILED, MYF(0), name->m_name.str);
return true;
}
// show create routine.
if (routine == nullptr ||
show_create_routine_from_dd_routine(thd, type, name, routine)) {
/*
If we have insufficient privileges, pretend the routine
does not exist.
*/
my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
type == enum_sp_type::FUNCTION ? "FUNCTION" : "PROCEDURE",
name->m_name.str);
return true;
}
return false;
}
/**
Obtain object representing stored procedure/function by its name from
stored procedures cache and looking into data dictionary if needed.
@param thd thread context
@param type type of object (FUNCTION or PROCEDURE)
@param name name of procedure
@param cp hash to look routine in
@param cache_only if true perform cache-only lookup
(Don't look in data dictionary)
@retval
NonNULL pointer to sp_head object for the procedure
@retval
NULL in case of error.
*/
sp_head *sp_find_routine(THD *thd, enum_sp_type type, sp_name *name,
sp_cache **cp, bool cache_only) {
DBUG_TRACE;
DBUG_PRINT("enter", ("name: %.*s.%.*s type: %d cache only %d",
static_cast<int>(name->m_db.length), name->m_db.str,
static_cast<int>(name->m_name.length), name->m_name.str,
static_cast<int>(type), cache_only));
sp_head *sp = sp_cache_lookup(cp, name);
if (sp != NULL) return sp;
if (!cache_only) {
if (db_find_routine(thd, type, name, &sp) == SP_OK) {
sp_cache_insert(cp, sp);
DBUG_PRINT("info", ("added new: 0x%lx, level: %lu, flags %x", (ulong)sp,
sp->m_recursion_level, sp->m_flags));
}
}
return sp;
}
/**
Setup a cached routine for execution
@param thd thread context
@param type type of object (FUNCTION or PROCEDURE)
@param name name of procedure
@param cp hash to look routine in
@retval
NonNULL pointer to sp_head object for the procedure
@retval
NULL in case of error.
*/
sp_head *sp_setup_routine(THD *thd, enum_sp_type type, sp_name *name,
sp_cache **cp) {
DBUG_TRACE;
DBUG_PRINT("enter", ("name: %.*s.%.*s type: %d ",
static_cast<int>(name->m_db.length), name->m_db.str,
static_cast<int>(name->m_name.length), name->m_name.str,
static_cast<int>(type)));
sp_head *sp = sp_cache_lookup(cp, name);
if (sp == NULL) return NULL;
DBUG_PRINT("info", ("found: 0x%lx", (ulong)sp));
const ulong depth = type == enum_sp_type::PROCEDURE
? thd->variables.max_sp_recursion_depth
: 0;
if (sp->m_first_free_instance) {
DBUG_PRINT("info", ("first free: 0x%lx level: %lu flags %x",
(ulong)sp->m_first_free_instance,
sp->m_first_free_instance->m_recursion_level,
sp->m_first_free_instance->m_flags));
DBUG_ASSERT(!(sp->m_first_free_instance->m_flags & sp_head::IS_INVOKED));
if (sp->m_first_free_instance->m_recursion_level > depth) {
recursion_level_error(thd, sp);
return NULL;
}
return sp->m_first_free_instance;
}
/*
Actually depth could be +1 than the actual value in case a SP calls
SHOW CREATE PROCEDURE. Hence, the linked list could hold up to one more
instance.
*/
ulong level = sp->m_last_cached_sp->m_recursion_level + 1;
if (level > depth) {
recursion_level_error(thd, sp);
return NULL;
}
const char *returns = "";
String retstr(64);
retstr.set_charset(sp->get_creation_ctx()->get_client_cs());
if (type == enum_sp_type::FUNCTION) {
sp_returns_type(thd, retstr, sp);
returns = retstr.ptr();
}
sp_head *new_sp;
if (db_load_routine(thd, type, name->m_db.str, name->m_db.length,
name->m_name.str, name->m_name.length, &new_sp,
sp->m_sql_mode, sp->m_params.str, returns, sp->m_body.str,
sp->m_chistics, sp->m_definer_user.str,
sp->m_definer_host.str, sp->m_created, sp->m_modified,
sp->get_creation_ctx()) != SP_OK)
return NULL;
sp->m_last_cached_sp->m_next_cached_sp = new_sp;
new_sp->m_recursion_level = level;
new_sp->m_first_instance = sp;
sp->m_last_cached_sp = sp->m_first_free_instance = new_sp;
DBUG_PRINT("info", ("added level: 0x%lx, level: %lu, flags %x", (ulong)new_sp,
new_sp->m_recursion_level, new_sp->m_flags));
return new_sp;
}
/**
This is used by sql_acl.cc:mysql_routine_grant() and is used to find
the routines in 'routines'.
@param thd Thread handler
@param routines List of needles in the hay stack
@param is_proc Indicates whether routines in the list are procedures
or functions.
@return
@retval false Found.
@retval true Not found
*/
bool sp_exist_routines(THD *thd, TABLE_LIST *routines, bool is_proc) {
TABLE_LIST *routine;
bool sp_object_found;
DBUG_TRACE;
for (routine = routines; routine; routine = routine->next_global) {
sp_name *name;
LEX_CSTRING lex_db;
LEX_STRING lex_name;
lex_db.length = strlen(routine->db);
lex_name.length = strlen(routine->table_name);
lex_db.str = thd->strmake(routine->db, lex_db.length);
lex_name.str = thd->strmake(routine->table_name, lex_name.length);
name = new (thd->mem_root) sp_name(lex_db, lex_name, true);
name->init_qname(thd);
sp_object_found = is_proc
? sp_find_routine(thd, enum_sp_type::PROCEDURE, name,
&thd->sp_proc_cache, false) != NULL
: sp_find_routine(thd, enum_sp_type::FUNCTION, name,
&thd->sp_func_cache, false) != NULL;
thd->get_stmt_da()->reset_condition_info(thd);
if (!sp_object_found) {
my_error(ER_SP_DOES_NOT_EXIST, MYF(0), is_proc ? "PROCEDURE" : "FUNCTION",
routine->table_name);
return true;
}
}
return false;
}
/**
Auxilary function that adds new element to the set of stored routines
used by statement.
The elements of Query_tables_list::sroutines set are accessed on prepared
statement re-execution. Because of this we have to allocate memory for both
hash element and copy of its key in persistent arena.
@param prelocking_ctx Prelocking context of the statement
@param arena Arena in which memory for new element will be
allocated
@param key Key for the hash representing set
@param key_length Key length.
@param db_length Length of db name component in the key.
@param name Name of the routine.
@param name_length Length of the routine name.
@param belong_to_view Uppermost view which uses this routine
(0 if routine is not used by view)
@note
Will also add element to end of 'Query_tables_list::sroutines_list' list.
@todo
When we will got rid of these accesses on re-executions we will be
able to allocate memory for hash elements in non-persitent arena
and directly use key values from sp_head::m_sroutines sets instead
of making their copies.
@retval
true new element was added.
@retval
false element was not added (because it is already present in
the set).
*/
static bool sp_add_used_routine(Query_tables_list *prelocking_ctx,
Query_arena *arena, const uchar *key,
size_t key_length, size_t db_length,
const char *name, size_t name_length,
TABLE_LIST *belong_to_view) {
if (prelocking_ctx->sroutines == nullptr) {
prelocking_ctx->sroutines.reset(
new malloc_unordered_map<std::string, Sroutine_hash_entry *>(
PSI_INSTRUMENT_ME));
}
std::string key_str(pointer_cast<const char *>(key), key_length);
if (prelocking_ctx->sroutines->count(key_str) == 0) {
Sroutine_hash_entry *rn =
(Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry));
if (!rn) // OOM. Error will be reported using fatal_error().
return false;
rn->m_key = (char *)arena->alloc(key_length);
if (!rn->m_key) // Ditto.
return false;
rn->m_key_length = key_length;
rn->m_db_length = db_length;
memcpy(rn->m_key, key, key_length);
rn->m_object_name = {nullptr, 0};
if (rn->use_normalized_key() &&
lex_string_strmake(arena->mem_root, &(rn->m_object_name), name,
name_length))
return false; // OOM, Error will be reported using fatal_error().
prelocking_ctx->sroutines->emplace(key_str, rn);
prelocking_ctx->sroutines_list.link_in_list(rn, &rn->next);
rn->belong_to_view = belong_to_view;
rn->m_cache_version = 0;
return true;
}
return false;
}
/**
Add routine or trigger which is used by statement to the set of
stored routines used by this statement.
To be friendly towards prepared statements one should pass
persistent arena as second argument.
@param prelocking_ctx Prelocking context of the statement
@param arena Arena in which memory for new element of
the set will be allocated
@param type Routine type (one of FUNCTION/PROCEDURE/
TRIGGER ...)
@param db Database name
@param db_length Database name length
@param name Routine name
@param name_length Routine name length
@param lowercase_db Indicates whether db needs to be
lowercased when constructing key.
@param name_normalize_type Indicates if names needs to be
normalized (lowercased / accent needs to
be removed).
@param own_routine Indicates whether routine is explicitly
or implicitly used.
@param belong_to_view Uppermost view which uses this routine
(nullptr if routine is not used by view)
@note
Will also add element to end of 'Query_tables_list::sroutines_list' list
(and will take into account if this is an explicitly used routine).
@retval True - new element was added.
@retval False - element was not added (because it is already present
in the set).
*/
bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
Sroutine_hash_entry::entry_type type, const char *db,
size_t db_length, const char *name, size_t name_length,
bool lowercase_db,
Sp_name_normalize_type name_normalize_type,
bool own_routine, TABLE_LIST *belong_to_view) {
// Length of routine name components needs to be checked earlier.
DBUG_ASSERT(db_length <= NAME_LEN && name_length <= NAME_LEN);
uchar key[1 + NAME_LEN + 1 + NAME_LEN + 1];
size_t key_length = 0;
key[key_length++] = static_cast<uchar>(type);
memcpy(key + key_length, db, db_length + 1);
if (lowercase_db) {
/*
In lower-case-table-names > 0 modes db name will be already in
lower case here in most cases. However db names associated with
FKs come here in original form in lower-case-table-names == 2
mode. So for the proper hash key comparison db name needs to be
converted to lower case while preparing the key.
*/
key_length +=
my_casedn_str(system_charset_info, (char *)(key) + key_length) + 1;
} else
key_length += db_length + 1;
switch (name_normalize_type) {
case Sp_name_normalize_type::LEAVE_AS_IS:
memcpy(key + key_length, name, name_length + 1);
key_length += name_length + 1;
break;
case Sp_name_normalize_type::LOWERCASE_NAME:
memcpy(key + key_length, name, name_length + 1);
/*
Object names are case-insensitive. So for the proper hash key
comparison, object name is converted to the lower case while preparing
the Sroutine_hash_entry key.
*/
key_length +=
my_casedn_str(system_charset_info, (char *)(key) + key_length) + 1;
break;
case Sp_name_normalize_type::UNACCENT_AND_LOWERCASE_NAME: {
const CHARSET_INFO *cs = nullptr;
switch (type) {
case Sroutine_hash_entry::FUNCTION:
case Sroutine_hash_entry::PROCEDURE:
cs = dd::Routine::name_collation();
break;
case Sroutine_hash_entry::TRIGGER:
cs = dd::Trigger::name_collation();
break;
default:
DBUG_ASSERT(false);
break;
}
/*
Stored routine names are case and accent insensitive. So for the proper
hash key comparision, case and accent is stripped off by replacing the
characters with their sort weight when preparing the Sroutine_hash_entry
key.
*/
key_length += dd::normalize_string(cs, name, (char *)(key) + key_length,
NAME_CHAR_LEN * 2);
*(key + key_length++) = 0;
break;
}
default:
DBUG_ASSERT(false);
break;
}
if (sp_add_used_routine(prelocking_ctx, arena, key, key_length, db_length,
name, name_length, belong_to_view)) {
if (own_routine) {
prelocking_ctx->sroutines_list_own_last =
prelocking_ctx->sroutines_list.next;
prelocking_ctx->sroutines_list_own_elements =
prelocking_ctx->sroutines_list.elements;
}
return true;
}
return false;
}
/**
Remove routines which are only indirectly used by statement from
the set of routines used by this statement.
@param prelocking_ctx Prelocking context of the statement
*/
void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx) {
Sroutine_hash_entry *not_own_rt, *next_rt;
for (not_own_rt = *prelocking_ctx->sroutines_list_own_last; not_own_rt;
not_own_rt = next_rt) {
/*
It is safe to obtain not_own_rt->next after calling hash_delete() now
but we want to be more future-proof.
*/
next_rt = not_own_rt->next;
prelocking_ctx->sroutines->erase(
std::string(not_own_rt->m_key, not_own_rt->m_key_length));
}
*prelocking_ctx->sroutines_list_own_last = NULL;
prelocking_ctx->sroutines_list.next = prelocking_ctx->sroutines_list_own_last;
prelocking_ctx->sroutines_list.elements =
prelocking_ctx->sroutines_list_own_elements;
}
/**
Add contents of hash representing set of routines to the set of
routines used by statement.
@param thd Thread context
@param prelocking_ctx Prelocking context of the statement
@param src Hash representing set from which routines will
be added
@param belong_to_view Uppermost view which uses these routines, 0 if none
@note It will also add elements to end of
'Query_tables_list::sroutines_list' list.
*/
void sp_update_stmt_used_routines(
THD *thd, Query_tables_list *prelocking_ctx,
malloc_unordered_map<std::string, Sroutine_hash_entry *> *src,
TABLE_LIST *belong_to_view) {
for (const auto &key_and_value : *src) {
Sroutine_hash_entry *rt = key_and_value.second;
(void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
pointer_cast<const uchar *>(rt->m_key),
rt->m_key_length, rt->m_db_length, rt->name(),
rt->name_length(), belong_to_view);
}
}
/**
Add contents of list representing set of routines to the set of
routines used by statement.
@param thd Thread context
@param prelocking_ctx Prelocking context of the statement
@param src List representing set from which routines will
be added
@param belong_to_view Uppermost view which uses these routines, 0 if none
@note It will also add elements to end of
'Query_tables_list::sroutines_list' list.
*/
void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
SQL_I_List<Sroutine_hash_entry> *src,
TABLE_LIST *belong_to_view) {
for (Sroutine_hash_entry *rt = src->first; rt; rt = rt->next)
(void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
pointer_cast<const uchar *>(rt->m_key),
rt->m_key_length, rt->m_db_length, rt->name(),
rt->name_length(), belong_to_view);
}
/**
A helper wrapper around sp_cache_routine() to use from
prelocking until 'sp_name' is eradicated as a class.
*/
enum_sp_return_code sp_cache_routine(THD *thd, Sroutine_hash_entry *rt,
bool lookup_only, sp_head **sp) {
char qname_buff[NAME_LEN * 2 + 1 + 1];
sp_name name(rt, qname_buff);
enum_sp_type type = (rt->type() == Sroutine_hash_entry::FUNCTION)
? enum_sp_type::FUNCTION
: enum_sp_type::PROCEDURE;
#ifndef DBUG_OFF
MDL_key mdl_key;
if (rt->type() == Sroutine_hash_entry::FUNCTION)
dd::Function::create_mdl_key(rt->db(), rt->name(), &mdl_key);
else
dd::Procedure::create_mdl_key(rt->db(), rt->name(), &mdl_key);
/*
Check that we have an MDL lock on this routine, unless it's a top-level
CALL. The assert below should be unambiguous: the first element
in sroutines_list has an MDL lock unless it's a top-level call, or a
trigger, but triggers can't occur here (see the preceding assert).
*/
DBUG_ASSERT(
thd->mdl_context.owns_equal_or_stronger_lock(&mdl_key, MDL_SHARED) ||
rt == thd->lex->sroutines_list.first);
#endif
return sp_cache_routine(thd, type, &name, lookup_only, sp);
}
/**
Ensure that routine is present in cache by loading it from the data
dictionary if needed. If the routine is present but old, reload it.
Emit an appropriate error if there was a problem during
loading.
@param[in] thd Thread context.
@param[in] type Type of object (FUNCTION or PROCEDURE).
@param[in] name Name of routine.
@param[in] lookup_only Only check that the routine is in the cache.
If it's not, don't try to load. If it is present,
but old, don't try to reload.
@param[out] sp Pointer to sp_head object for routine, NULL if routine was
not found.
@retval SP_OK Either routine is found and was successfully loaded into
cache or it does not exist.
@retval non-SP_OK Error while loading routine from DD table.
*/
enum_sp_return_code sp_cache_routine(THD *thd, enum_sp_type type, sp_name *name,
bool lookup_only, sp_head **sp) {
enum_sp_return_code ret = SP_OK;
sp_cache **spc = (type == enum_sp_type::FUNCTION) ? &thd->sp_func_cache
: &thd->sp_proc_cache;
DBUG_TRACE;
DBUG_ASSERT(type == enum_sp_type::FUNCTION ||
type == enum_sp_type::PROCEDURE);
*sp = sp_cache_lookup(spc, name);
if (lookup_only) return SP_OK;
if (*sp) {
sp_cache_flush_obsolete(spc, sp);
if (*sp) return SP_OK;
}
switch ((ret = db_find_routine(thd, type, name, sp))) {
case SP_OK:
sp_cache_insert(spc, *sp);
break;
case SP_DOES_NOT_EXISTS:
ret = SP_OK;
break;
default:
/* Query might have been killed, don't set error. */
if (thd->killed) break;
/*
Any error when loading an existing routine is either some problem
with the DD table, or a parse error because the contents
has been tampered with (in which case we clear that error).
*/
if (ret == SP_PARSE_ERROR) thd->clear_error();
/*
If we cleared the parse error, or when db_find_routine() flagged
an error with it's return value without calling my_error(), we
set the generic "mysql.proc table corrupt" error here.
*/
if (!thd->is_error()) {
/*
SP allows full NAME_LEN chars thus he have to allocate enough
size in bytes. Otherwise there is stack overrun could happen
if multibyte sequence is `name`. `db` is still safe because the
rest of the server checks agains NAME_LEN bytes and not chars.
Hence, the overrun happens only if the name is in length > 32 and
uses multibyte (cyrillic, greek, etc.)
*/
char n[NAME_LEN * 2 + 2];
/* m_qname.str is not always \0 terminated */
memcpy(n, name->m_qname.str, name->m_qname.length);
n[name->m_qname.length] = '\0';
my_error(ER_SP_LOAD_FAILED, MYF(0), n);
}
break;
}
return ret;
}
/**
Generates the CREATE... string from the table information.
@return
Returns true on success, false on (alloc) failure.
*/
static bool create_string(
THD *thd, String *buf, enum_sp_type type, const char *db, size_t dblen,
const char *name, size_t namelen, const char *params, size_t paramslen,
const char *returns, size_t returnslen, const char *body, size_t bodylen,
st_sp_chistics *chistics, const LEX_CSTRING &definer_user,
const LEX_CSTRING &definer_host, sql_mode_t sql_mode) {
sql_mode_t old_sql_mode = thd->variables.sql_mode;
/* Make some room to begin with */
if (buf->alloc(100 + dblen + 1 + namelen + paramslen + returnslen + bodylen +
chistics->comment.length + 10 /* length of " DEFINER= "*/ +
USER_HOST_BUFF_SIZE))
return false;
thd->variables.sql_mode = sql_mode;
buf->append(STRING_WITH_LEN("CREATE "));
append_definer(thd, buf, definer_user, definer_host);
if (type == enum_sp_type::FUNCTION)
buf->append(STRING_WITH_LEN("FUNCTION "));
else
buf->append(STRING_WITH_LEN("PROCEDURE "));
if (dblen > 0) {
append_identifier(thd, buf, db, dblen);
buf->append('.');
}
append_identifier(thd, buf, name, namelen);
buf->append('(');
buf->append(params, paramslen, system_charset_info);
buf->append(')');
if (type == enum_sp_type::FUNCTION) {
buf->append(STRING_WITH_LEN(" RETURNS "));
buf->append(returns, returnslen);
}
buf->append('\n');
switch (chistics->daccess) {
case SP_NO_SQL:
buf->append(STRING_WITH_LEN(" NO SQL\n"));
break;
case SP_READS_SQL_DATA:
buf->append(STRING_WITH_LEN(" READS SQL DATA\n"));
break;
case SP_MODIFIES_SQL_DATA:
buf->append(STRING_WITH_LEN(" MODIFIES SQL DATA\n"));
break;
case SP_DEFAULT_ACCESS:
case SP_CONTAINS_SQL:
/* Do nothing */
break;
}
if (chistics->detistic) buf->append(STRING_WITH_LEN(" DETERMINISTIC\n"));
if (chistics->suid == SP_IS_NOT_SUID)
buf->append(STRING_WITH_LEN(" SQL SECURITY INVOKER\n"));
if (chistics->comment.length) {
buf->append(STRING_WITH_LEN(" COMMENT "));
append_unescaped(buf, chistics->comment.str, chistics->comment.length);
buf->append('\n');
}
buf->append(body, bodylen);
thd->variables.sql_mode = old_sql_mode;
return true;
}
/**
The function loads sp_head struct for information schema purposes
(used for I_S ROUTINES & PARAMETERS tables).
@param[in] thd thread handler
@param[in] db_name DB name.
@param[in] routine dd::Routine object.
@param[out] free_sp_head returns 1 if we need to free sp_head struct
otherwise returns 0
@return Pointer on sp_head struct
@retval NULL error
*/
sp_head *sp_load_for_information_schema(THD *thd, LEX_CSTRING db_name,
const dd::Routine *routine,
bool *free_sp_head) {
sp_head *sp;
enum_sp_type type = is_dd_routine_type_function(routine)
? enum_sp_type::FUNCTION
: enum_sp_type::PROCEDURE;
*free_sp_head = 0;
sp_cache **spc = (type == enum_sp_type::FUNCTION) ? &thd->sp_func_cache
: &thd->sp_proc_cache;
sp_name sp_name_obj(
db_name,
{const_cast<char *>(routine->name().c_str()), routine->name().length()},
true);
sp_name_obj.init_qname(thd);
if ((sp = sp_cache_lookup(spc, &sp_name_obj))) {
return sp;
}
// Create stored program creation context from routine object.
Stored_program_creation_ctx *creation_ctx =
Stored_routine_creation_ctx::create_routine_creation_ctx(routine);
if (creation_ctx == NULL) return NULL;
// Prepare stored routine return type string.
dd::String_type return_type_str;
prepare_return_type_string_from_dd_routine(thd, routine, &return_type_str);
// Prepare stored routine parameter's string.
dd::String_type params_str;
prepare_params_string_from_dd_routine(thd, routine, &params_str);
// Dummy Routine body.
LEX_CSTRING sr_body;
if (type == enum_sp_type::FUNCTION)
sr_body = {STRING_WITH_LEN("RETURN NULL")};
else
sr_body = {STRING_WITH_LEN("BEGIN END")};
// Dummy stored routine definer.
const LEX_CSTRING definer_user = EMPTY_CSTR;
const LEX_CSTRING definer_host = EMPTY_CSTR;
// Dummy st_sp_chistics object.
struct st_sp_chistics sp_chistics;
memset(&sp_chistics, 0, sizeof(st_sp_chistics));
String defstr;
defstr.set_charset(creation_ctx->get_client_cs());
if (!create_string(thd, &defstr, type, db_name.str, db_name.length,
routine->name().c_str(), routine->name().length(),
params_str.c_str(), params_str.length(),
return_type_str.c_str(), return_type_str.length(),
sr_body.str, sr_body.length, &sp_chistics, definer_user,
definer_host, routine->sql_mode()))
return 0;
LEX *old_lex = thd->lex, newlex;
thd->lex = &newlex;
newlex.thd = thd;
newlex.set_current_select(NULL);
sp = sp_compile(thd, &defstr, routine->sql_mode(), creation_ctx);
*free_sp_head = 1;
thd->lex->sphead = NULL;
lex_end(thd->lex);
thd->lex = old_lex;
return sp;
}
/**
Start parsing of a stored program.
This function encapsulates all the steps necessary to initialize sp_head to
start parsing SP.
Every successful call of sp_start_parsing() must finish with
sp_finish_parsing().
@param thd Thread context.
@param sp_type The stored program type
@param sp_name The stored progam name
@return properly initialized sp_head-instance in case of success, or NULL is
case of out-of-memory error.
*/
sp_head *sp_start_parsing(THD *thd, enum_sp_type sp_type, sp_name *sp_name) {
// The order is important:
// 1. new sp_head()
MEM_ROOT own_root;
init_sql_alloc(key_memory_sp_head_main_root, &own_root, MEM_ROOT_BLOCK_SIZE,
MEM_ROOT_PREALLOC);
void *rawmem = own_root.Alloc(sizeof(sp_head));
if (!rawmem) return NULL;
sp_head *sp = new (rawmem) sp_head(std::move(own_root), sp_type);
// 2. start_parsing_sp_body()
sp->m_parser_data.start_parsing_sp_body(thd, sp);
// 3. finish initialization.
sp->m_root_parsing_ctx = new (thd->mem_root) sp_pcontext(thd);
if (!sp->m_root_parsing_ctx) return NULL;
thd->lex->set_sp_current_parsing_ctx(sp->m_root_parsing_ctx);
// 4. set name.
sp->init_sp_name(thd, sp_name);
return sp;
}
/**
Finish parsing of a stored program.
This is a counterpart of sp_start_parsing().
@param thd Thread context.
*/
void sp_finish_parsing(THD *thd) {
sp_head *sp = thd->lex->sphead;
DBUG_ASSERT(sp);
sp->set_body_end(thd);
sp->m_parser_data.finish_parsing_sp_body(thd);
}
/// @return Item_result code corresponding to the RETURN-field type code.
Item_result sp_map_result_type(enum enum_field_types type) {
switch (type) {
case MYSQL_TYPE_BIT:
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_INT24:
return INT_RESULT;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return DECIMAL_RESULT;
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
return REAL_RESULT;
default:
return STRING_RESULT;
}
}
/// @return Item::Type code corresponding to the RETURN-field type code.
Item::Type sp_map_item_type(enum enum_field_types type) {
switch (type) {
case MYSQL_TYPE_BIT:
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_INT24:
return Item::INT_ITEM;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return Item::DECIMAL_ITEM;
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
return Item::REAL_ITEM;
default:
return Item::STRING_ITEM;
}
}
/**
@param lex LEX-object, representing an SQL-statement inside SP.
@return a combination of:
- sp_head::MULTI_RESULTS: added if the 'cmd' is a command that might
result in multiple result sets being sent back.
- sp_head::CONTAINS_DYNAMIC_SQL: added if 'cmd' is one of PREPARE,
EXECUTE, DEALLOCATE.
*/
uint sp_get_flags_for_command(LEX *lex) {
uint flags;
switch (lex->sql_command) {
case SQLCOM_SELECT:
if (lex->result) {
flags = 0; /* This is a SELECT with INTO clause */
break;
}
/* fallthrough */
case SQLCOM_ANALYZE:
case SQLCOM_OPTIMIZE:
case SQLCOM_PRELOAD_KEYS:
case SQLCOM_ASSIGN_TO_KEYCACHE:
case SQLCOM_CHECKSUM:
case SQLCOM_CHECK:
case SQLCOM_HA_READ:
case SQLCOM_SHOW_BINLOGS:
case SQLCOM_SHOW_BINLOG_EVENTS:
case SQLCOM_SHOW_RELAYLOG_EVENTS:
case SQLCOM_SHOW_CHARSETS:
case SQLCOM_SHOW_COLLATIONS:
case SQLCOM_SHOW_CREATE:
case SQLCOM_SHOW_CREATE_DB:
case SQLCOM_SHOW_CREATE_FUNC:
case SQLCOM_SHOW_CREATE_PROC:
case SQLCOM_SHOW_CREATE_EVENT:
case SQLCOM_SHOW_CREATE_TRIGGER:
case SQLCOM_SHOW_DATABASES:
case SQLCOM_SHOW_ERRORS:
case SQLCOM_SHOW_FIELDS:
case SQLCOM_SHOW_FUNC_CODE:
case SQLCOM_SHOW_GRANTS:
case SQLCOM_SHOW_ENGINE_STATUS:
case SQLCOM_SHOW_ENGINE_LOGS:
case SQLCOM_SHOW_ENGINE_MUTEX:
case SQLCOM_SHOW_EVENTS:
case SQLCOM_SHOW_KEYS:
case SQLCOM_SHOW_MASTER_STAT:
case SQLCOM_SHOW_OPEN_TABLES:
case SQLCOM_SHOW_PRIVILEGES:
case SQLCOM_SHOW_PROCESSLIST:
case SQLCOM_SHOW_PROC_CODE:
case SQLCOM_SHOW_SLAVE_HOSTS:
case SQLCOM_SHOW_SLAVE_STAT:
case SQLCOM_SHOW_STATUS:
case SQLCOM_SHOW_STATUS_FUNC:
case SQLCOM_SHOW_STATUS_PROC:
case SQLCOM_SHOW_STORAGE_ENGINES:
case SQLCOM_SHOW_TABLES:
case SQLCOM_SHOW_TABLE_STATUS:
case SQLCOM_SHOW_VARIABLES:
case SQLCOM_SHOW_WARNS:
case SQLCOM_REPAIR:
flags = sp_head::MULTI_RESULTS;
break;
/*
EXECUTE statement may return a result set, but doesn't have to.
We can't, however, know it in advance, and therefore must add
this statement here. This is ok, as is equivalent to a result-set
statement within an IF condition.
*/
case SQLCOM_EXECUTE:
flags = sp_head::MULTI_RESULTS | sp_head::CONTAINS_DYNAMIC_SQL;
break;
case SQLCOM_PREPARE:
case SQLCOM_DEALLOCATE_PREPARE:
flags = sp_head::CONTAINS_DYNAMIC_SQL;
break;
case SQLCOM_CREATE_TABLE:
if (lex->create_info->options & HA_LEX_CREATE_TMP_TABLE)
flags = 0;
else
flags = sp_head::HAS_COMMIT_OR_ROLLBACK;
break;
case SQLCOM_DROP_TABLE:
if (lex->drop_temporary)
flags = 0;
else
flags = sp_head::HAS_COMMIT_OR_ROLLBACK;
break;
case SQLCOM_FLUSH:
flags = sp_head::HAS_SQLCOM_FLUSH;
break;
case SQLCOM_RESET:
flags = sp_head::HAS_SQLCOM_RESET;
break;
case SQLCOM_CREATE_INDEX:
case SQLCOM_CREATE_DB:
case SQLCOM_CREATE_VIEW:
case SQLCOM_CREATE_TRIGGER:
case SQLCOM_CREATE_USER:
case SQLCOM_ALTER_TABLE:
case SQLCOM_GRANT:
case SQLCOM_GRANT_ROLE:
case SQLCOM_REVOKE:
case SQLCOM_REVOKE_ROLE:
case SQLCOM_BEGIN:
case SQLCOM_RENAME_TABLE:
case SQLCOM_RENAME_USER:
case SQLCOM_DROP_INDEX:
case SQLCOM_DROP_DB:
case SQLCOM_REVOKE_ALL:
case SQLCOM_DROP_USER:
case SQLCOM_DROP_VIEW:
case SQLCOM_DROP_TRIGGER:
case SQLCOM_TRUNCATE:
case SQLCOM_COMMIT:
case SQLCOM_ROLLBACK:
case SQLCOM_LOAD:
case SQLCOM_LOCK_TABLES:
case SQLCOM_CREATE_PROCEDURE:
case SQLCOM_CREATE_SPFUNCTION:
case SQLCOM_ALTER_PROCEDURE:
case SQLCOM_ALTER_FUNCTION:
case SQLCOM_DROP_PROCEDURE:
case SQLCOM_DROP_FUNCTION:
case SQLCOM_CREATE_EVENT:
case SQLCOM_ALTER_EVENT:
case SQLCOM_DROP_EVENT:
case SQLCOM_INSTALL_PLUGIN:
case SQLCOM_UNINSTALL_PLUGIN:
case SQLCOM_ALTER_DB:
case SQLCOM_ALTER_USER:
case SQLCOM_CREATE_SERVER:
case SQLCOM_ALTER_SERVER:
case SQLCOM_DROP_SERVER:
case SQLCOM_CHANGE_MASTER:
case SQLCOM_CHANGE_REPLICATION_FILTER:
case SQLCOM_SLAVE_START:
case SQLCOM_SLAVE_STOP:
case SQLCOM_ALTER_INSTANCE:
case SQLCOM_CREATE_ROLE:
case SQLCOM_DROP_ROLE:
case SQLCOM_CREATE_SRS:
case SQLCOM_DROP_SRS:
case SQLCOM_CREATE_RESOURCE_GROUP:
case SQLCOM_ALTER_RESOURCE_GROUP:
case SQLCOM_DROP_RESOURCE_GROUP:
flags = sp_head::HAS_COMMIT_OR_ROLLBACK;
break;
default:
flags = lex->is_explain() ? sp_head::MULTI_RESULTS : 0;
break;
}
return flags;
}
/**
Check that the name 'ident' is ok. It's assumed to be an 'ident'
from the parser, so we only have to check length and trailing spaces.
The former is a standard requirement (and 'show status' assumes a
non-empty name), the latter is a mysql:ism as trailing spaces are
removed by get_field().
@retval true bad name
@retval false name is ok
*/
bool sp_check_name(LEX_STRING *ident) {
DBUG_ASSERT(ident != NULL && ident->str != NULL);
if (!ident->str[0] || ident->str[ident->length - 1] == ' ') {
my_error(ER_SP_WRONG_NAME, MYF(0), ident->str);
return true;
}
LEX_CSTRING ident_cstr = {ident->str, ident->length};
if (check_string_char_length(ident_cstr, "", NAME_CHAR_LEN,
system_charset_info, 1)) {
my_error(ER_TOO_LONG_IDENT, MYF(0), ident->str);
return true;
}
return false;
}
/**
Prepare an Item for evaluation (call of fix_fields).
@param thd thread handler
@param it_addr pointer on item reference
@retval
NULL error
@retval
non-NULL prepared item
*/
Item *sp_prepare_func_item(THD *thd, Item **it_addr) {
it_addr = (*it_addr)->this_item_addr(thd, it_addr);
if (!(*it_addr)->fixed &&
((*it_addr)->fix_fields(thd, it_addr) || (*it_addr)->check_cols(1))) {
DBUG_PRINT("info", ("fix_fields() failed"));
return NULL;
}
thd->lex->set_exec_started();
return *it_addr;
}
/**
Evaluate an expression and store the result in the field.
@param thd current thread object
@param result_field the field to store the result
@param expr_item_ptr the root item of the expression
@retval
false on success
@retval
true on error
*/
bool sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr) {
Item *expr_item;
Strict_error_handler strict_handler(
Strict_error_handler::ENABLE_SET_SELECT_STRICT_ERROR_HANDLER);
enum_check_fields save_check_for_truncated_fields =
thd->check_for_truncated_fields;
unsigned int stmt_unsafe_rollback_flags =
thd->get_transaction()->get_unsafe_rollback_flags(Transaction_ctx::STMT);
if (!*expr_item_ptr) goto error;
if (!(expr_item = sp_prepare_func_item(thd, expr_item_ptr))) goto error;
/*
Set THD flags to emit warnings/errors in case of overflow/type errors
during saving the item into the field.
Save original values and restore them after save.
*/
thd->check_for_truncated_fields = CHECK_FIELD_ERROR_FOR_NULL;
thd->get_transaction()->reset_unsafe_rollback_flags(Transaction_ctx::STMT);
/*
Variables declared within SP/SF with DECLARE keyword like
DECLARE var INTEGER;
will follow the rules of assignment corresponding to the data type column
in a table. So, STRICT mode gives error if an invalid value is assigned
to the variable here.
*/
if (thd->is_strict_mode() && !thd->lex->is_ignore())
thd->push_internal_handler(&strict_handler);
// Save the value in the field. Convert the value if needed.
expr_item->save_in_field(result_field, false);
if (thd->is_strict_mode() && !thd->lex->is_ignore())
thd->pop_internal_handler();
thd->check_for_truncated_fields = save_check_for_truncated_fields;
thd->get_transaction()->set_unsafe_rollback_flags(Transaction_ctx::STMT,
stmt_unsafe_rollback_flags);
if (!thd->is_error()) return false;
error:
/*
In case of error during evaluation, leave the result field set to NULL.
Sic: we can't do it in the beginning of the function because the
result field might be needed for its own re-evaluation, e.g. case of
set x = x + 1;
*/
result_field->set_null();
return true;
}
/**
Return a string representation of the Item value.
@param thd Thread context.
@param item The item to evaluate
@param str String buffer for representation of the value.
@note
If the item has a string result type, the string is escaped
according to its character set.
@retval NULL on error
@retval non-NULL a pointer to valid a valid string on success
*/
String *sp_get_item_value(THD *thd, Item *item, String *str) {
switch (item->result_type()) {
case REAL_RESULT:
case INT_RESULT:
case DECIMAL_RESULT:
if (item->data_type() != MYSQL_TYPE_BIT)
return item->val_str(str);
else { /* Bit type is handled as binary string */
}
// Fall through
case STRING_RESULT: {
String *result = item->val_str(str);
if (!result) return NULL;
{
char buf_holder[STRING_BUFFER_USUAL_SIZE];
String buf(buf_holder, sizeof(buf_holder), result->charset());
const CHARSET_INFO *cs = thd->variables.character_set_client;
/* We must reset length of the buffer, because of String specificity. */
buf.length(0);
buf.append('_');
buf.append(result->charset()->csname);
if (cs->escape_with_backslash_is_dangerous) buf.append(' ');
append_query_string(thd, cs, result, &buf);
buf.append(" COLLATE '");
buf.append(item->collation.collation->name);
buf.append('\'');
str->copy(buf);
return str;
}
}
case ROW_RESULT:
default:
return NULL;
}
}