mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
828471cbf8
The reason for the failure is that thd->mdl_context.release_transactional_locks() was called after commit & rollback even in cases where the current transaction is still active. For 10.2, 10.3 and 10.4 the fix is simple: - Replace all calls to thd->mdl_context.release_transactional_locks() with thd->release_transactional_locks(). The thd function will only call the mdl_context function if there are no active transactional locks. In 10.6 we will better fix where we will change the return value for some trans_xxx() functions to indicate if transaction did close the transaction or not. This will avoid the need of the indirect call. Other things: - trans_xa_commit() and trans_xa_rollback() will automatically call release_transactional_locks() if the transaction is closed. - We can't do that for the other functions as the caller of many of these are doing additional work (like close_thread_tables) before calling release_transactional_locks(). - Added missing abort_result_set() and missing DBUG_RETURN in select_create::send_eof() - Fixed wrong indentation in injector::transaction::commit()
4408 lines
120 KiB
C++
4408 lines
120 KiB
C++
/*
|
|
Copyright (c) 2002, 2016, Oracle and/or its affiliates.
|
|
Copyright (c) 2011, 2017, MariaDB
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
|
|
#include "sql_priv.h"
|
|
#include "unireg.h"
|
|
#include "sql_prepare.h"
|
|
#include "sql_cache.h" // query_cache_*
|
|
#include "probes_mysql.h"
|
|
#include "sql_show.h" // append_identifier
|
|
#include "sql_db.h" // mysql_opt_change_db, mysql_change_db
|
|
#include "sql_table.h" // sp_prepare_create_field,
|
|
// prepare_create_field
|
|
#include "sql_acl.h" // *_ACL
|
|
#include "sql_array.h" // Dynamic_array
|
|
#include "log_event.h" // Query_log_event
|
|
#include "sql_derived.h" // mysql_handle_derived
|
|
#include "sql_cte.h"
|
|
|
|
#ifdef USE_PRAGMA_IMPLEMENTATION
|
|
#pragma implementation
|
|
#endif
|
|
#include "sp_head.h"
|
|
#include "sp.h"
|
|
#include "sp_pcontext.h"
|
|
#include "sp_rcontext.h"
|
|
#include "sp_cache.h"
|
|
#include "set_var.h"
|
|
#include "sql_parse.h" // cleanup_items
|
|
#include "sql_base.h" // close_thread_tables
|
|
#include "transaction.h" // trans_commit_stmt
|
|
#include "sql_audit.h"
|
|
#include "debug_sync.h"
|
|
#ifdef WITH_WSREP
|
|
#include "wsrep_thd.h"
|
|
#endif /* WITH_WSREP */
|
|
|
|
/*
|
|
Sufficient max length of printed destinations and frame offsets (all uints).
|
|
*/
|
|
#define SP_INSTR_UINT_MAXLEN 8
|
|
#define SP_STMT_PRINT_MAXLEN 40
|
|
|
|
|
|
#include <my_user.h>
|
|
|
|
extern "C" uchar *sp_table_key(const uchar *ptr, size_t *plen, my_bool first);
|
|
|
|
/**
|
|
Helper function which operates on a THD object to set the query start_time to
|
|
the current time.
|
|
|
|
@param[in, out] thd The session object
|
|
|
|
*/
|
|
|
|
static void reset_start_time_for_sp(THD *thd)
|
|
{
|
|
if (!thd->in_sub_stmt)
|
|
thd->set_start_time();
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Return a string representation of the Item value.
|
|
|
|
@param thd thread handle
|
|
@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
|
|
*/
|
|
|
|
static 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->field_type() != MYSQL_TYPE_BIT)
|
|
return item->val_str(str);
|
|
/* fall through */
|
|
case STRING_RESULT:
|
|
{
|
|
String *result= item->val_str(str);
|
|
|
|
if (!result)
|
|
return NULL;
|
|
|
|
{
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> buf(result->charset());
|
|
CHARSET_INFO *cs= thd->variables.character_set_client;
|
|
|
|
buf.append('_');
|
|
buf.append(result->charset()->csname);
|
|
if (cs->escape_with_backslash_is_dangerous)
|
|
buf.append(' ');
|
|
append_query_string(cs, &buf, result->ptr(), result->length(),
|
|
thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
|
|
buf.append(" COLLATE '");
|
|
buf.append(item->collation.collation->name);
|
|
buf.append('\'');
|
|
str->copy(buf);
|
|
|
|
return str;
|
|
}
|
|
}
|
|
|
|
case ROW_RESULT:
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
bool Item_splocal::append_for_log(THD *thd, String *str)
|
|
{
|
|
if (fix_fields(thd, NULL))
|
|
return true;
|
|
|
|
if (limit_clause_param)
|
|
return str->append_ulonglong(val_uint());
|
|
|
|
if (str->append(STRING_WITH_LEN(" NAME_CONST('")) ||
|
|
str->append(&m_name) ||
|
|
str->append(STRING_WITH_LEN("',")))
|
|
return true;
|
|
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> str_value_holder(&my_charset_latin1);
|
|
String *str_value= sp_get_item_value(thd, this_item(), &str_value_holder);
|
|
if (str_value)
|
|
return str->append(*str_value) || str->append(')');
|
|
else
|
|
return str->append(STRING_WITH_LEN("NULL)"));
|
|
}
|
|
|
|
|
|
/**
|
|
Returns 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 && !lex->analyze_stmt)
|
|
{
|
|
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_AUTHORS:
|
|
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_CONTRIBUTORS:
|
|
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_CREATE_USER:
|
|
case SQLCOM_SHOW_DATABASES:
|
|
case SQLCOM_SHOW_ERRORS:
|
|
case SQLCOM_SHOW_EXPLAIN:
|
|
case SQLCOM_SHOW_FIELDS:
|
|
case SQLCOM_SHOW_FUNC_CODE:
|
|
case SQLCOM_SHOW_GENERIC:
|
|
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:
|
|
case SQLCOM_EXECUTE_IMMEDIATE:
|
|
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->tmp_table())
|
|
flags= 0;
|
|
else
|
|
flags= sp_head::HAS_COMMIT_OR_ROLLBACK;
|
|
break;
|
|
case SQLCOM_DROP_TABLE:
|
|
if (lex->tmp_table())
|
|
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_CREATE_ROLE:
|
|
case SQLCOM_ALTER_TABLE:
|
|
case SQLCOM_ALTER_USER:
|
|
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_ROLE:
|
|
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:
|
|
flags= sp_head::HAS_COMMIT_OR_ROLLBACK;
|
|
break;
|
|
case SQLCOM_DELETE:
|
|
case SQLCOM_DELETE_MULTI:
|
|
{
|
|
/*
|
|
DELETE normally doesn't return resultset, but there are 3 exceptions:
|
|
- DELETE ... RETURNING
|
|
- EXPLAIN DELETE ...
|
|
- ANALYZE DELETE ...
|
|
*/
|
|
if (lex->select_lex.item_list.is_empty() &&
|
|
!lex->describe && !lex->analyze_stmt)
|
|
flags= 0;
|
|
else
|
|
flags= sp_head::MULTI_RESULTS;
|
|
break;
|
|
}
|
|
case SQLCOM_UPDATE:
|
|
case SQLCOM_UPDATE_MULTI:
|
|
case SQLCOM_INSERT:
|
|
case SQLCOM_REPLACE:
|
|
case SQLCOM_REPLACE_SELECT:
|
|
case SQLCOM_INSERT_SELECT:
|
|
{
|
|
if (!lex->describe && !lex->analyze_stmt)
|
|
flags= 0;
|
|
else
|
|
flags= sp_head::MULTI_RESULTS;
|
|
break;
|
|
}
|
|
default:
|
|
flags= 0;
|
|
break;
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
/**
|
|
Prepare an Item for evaluation (call of fix_fields).
|
|
|
|
@param thd thread handler
|
|
@param it_addr pointer on item refernce
|
|
|
|
@retval
|
|
NULL error
|
|
@retval
|
|
non-NULL prepared item
|
|
*/
|
|
|
|
Item *
|
|
sp_prepare_func_item(THD* thd, Item **it_addr)
|
|
{
|
|
DBUG_ENTER("sp_prepare_func_item");
|
|
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"));
|
|
DBUG_RETURN(NULL);
|
|
}
|
|
DBUG_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;
|
|
enum_check_fields save_count_cuted_fields= thd->count_cuted_fields;
|
|
bool save_abort_on_warning= thd->abort_on_warning;
|
|
bool save_stmt_modified_non_trans_table=
|
|
thd->transaction.stmt.modified_non_trans_table;
|
|
|
|
DBUG_ENTER("sp_eval_expr");
|
|
|
|
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->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL;
|
|
thd->abort_on_warning= thd->is_strict_mode();
|
|
thd->transaction.stmt.modified_non_trans_table= FALSE;
|
|
|
|
/* Save the value in the field. Convert the value if needed. */
|
|
|
|
expr_item->save_in_field(result_field, 0);
|
|
|
|
thd->count_cuted_fields= save_count_cuted_fields;
|
|
thd->abort_on_warning= save_abort_on_warning;
|
|
thd->transaction.stmt.modified_non_trans_table= save_stmt_modified_non_trans_table;
|
|
|
|
if (!thd->is_error())
|
|
DBUG_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();
|
|
DBUG_RETURN (TRUE);
|
|
}
|
|
|
|
|
|
/**
|
|
Create temporary sp_name object from MDL key.
|
|
|
|
@note The lifetime of this object is bound to the lifetime of the MDL_key.
|
|
This should be fine as sp_name objects created by this constructor
|
|
are mainly used for SP-cache lookups.
|
|
|
|
@param key MDL key containing database and routine name.
|
|
@param qname_buff Buffer to be used for storing quoted routine name
|
|
(should be at least 2*NAME_LEN+1+1 bytes).
|
|
*/
|
|
|
|
sp_name::sp_name(const MDL_key *key, char *qname_buff)
|
|
{
|
|
m_db.str= (char*)key->db_name();
|
|
m_db.length= key->db_name_length();
|
|
m_name.str= (char*)key->name();
|
|
m_name.length= key->name_length();
|
|
m_qname.str= qname_buff;
|
|
if (m_db.length)
|
|
{
|
|
strxmov(qname_buff, m_db.str, ".", m_name.str, NullS);
|
|
m_qname.length= m_db.length + 1 + m_name.length;
|
|
}
|
|
else
|
|
{
|
|
strmov(qname_buff, m_name.str);
|
|
m_qname.length= m_name.length;
|
|
}
|
|
m_explicit_name= false;
|
|
}
|
|
|
|
|
|
/**
|
|
Init the qualified name from the db and name.
|
|
*/
|
|
void
|
|
sp_name::init_qname(THD *thd)
|
|
{
|
|
const uint dot= !!m_db.length;
|
|
/* m_qname format: [database + dot] + name + '\0' */
|
|
m_qname.length= m_db.length + dot + m_name.length;
|
|
if (!(m_qname.str= (char*) thd->alloc(m_qname.length + 1)))
|
|
return;
|
|
sprintf(m_qname.str, "%.*s%.*s%.*s",
|
|
(int) m_db.length, (m_db.length ? m_db.str : ""),
|
|
dot, ".",
|
|
(int) m_name.length, m_name.str);
|
|
DBUG_ASSERT(ok_for_lower_case_names(m_db.str));
|
|
}
|
|
|
|
|
|
/**
|
|
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
|
|
check_routine_name(LEX_STRING *ident)
|
|
{
|
|
DBUG_ASSERT(ident);
|
|
DBUG_ASSERT(ident->str);
|
|
|
|
if (!ident->str[0] || ident->str[ident->length-1] == ' ')
|
|
{
|
|
my_error(ER_SP_WRONG_NAME, MYF(0), ident->str);
|
|
return TRUE;
|
|
}
|
|
if (check_ident_length(ident))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
sp_head* sp_head::create()
|
|
{
|
|
MEM_ROOT own_root;
|
|
init_sql_alloc(&own_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC, MYF(0));
|
|
sp_head *sp;
|
|
if (!(sp= new (&own_root) sp_head(&own_root)))
|
|
free_root(&own_root, MYF(0));
|
|
|
|
return sp;
|
|
}
|
|
|
|
|
|
void sp_head::destroy(sp_head *sp)
|
|
{
|
|
if (sp)
|
|
{
|
|
/* Make a copy of main_mem_root as free_root will free the sp */
|
|
MEM_ROOT own_root= sp->main_mem_root;
|
|
DBUG_PRINT("info", ("mem_root %p moved to %p",
|
|
&sp->main_mem_root, &own_root));
|
|
delete sp;
|
|
free_root(&own_root, MYF(0));
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
* sp_head
|
|
*
|
|
*/
|
|
|
|
sp_head::sp_head(MEM_ROOT *mem_root_arg)
|
|
:Query_arena(NULL, STMT_INITIALIZED_FOR_SP),
|
|
main_mem_root(*mem_root_arg), // todo: std::move operator.
|
|
m_flags(0),
|
|
m_sp_cache_version(0),
|
|
m_creation_ctx(0),
|
|
unsafe_flags(0),
|
|
m_recursion_level(0),
|
|
m_next_cached_sp(0),
|
|
m_cont_level(0)
|
|
{
|
|
mem_root= &main_mem_root;
|
|
|
|
m_first_instance= this;
|
|
m_first_free_instance= this;
|
|
m_last_cached_sp= this;
|
|
|
|
m_return_field_def.charset = NULL;
|
|
/*
|
|
FIXME: the only use case when name is NULL is events, and it should
|
|
be rewritten soon. Remove the else part and replace 'if' with
|
|
an assert when this is done.
|
|
*/
|
|
m_db= m_name= m_qname= null_lex_str;
|
|
|
|
DBUG_ENTER("sp_head::sp_head");
|
|
|
|
m_security_ctx.init();
|
|
m_backpatch.empty();
|
|
m_cont_backpatch.empty();
|
|
m_lex.empty();
|
|
my_hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0);
|
|
my_hash_init(&m_sroutines, system_charset_info, 0, 0, 0, sp_sroutine_key,
|
|
0, 0);
|
|
|
|
m_body_utf8.str= NULL;
|
|
m_body_utf8.length= 0;
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void
|
|
sp_head::init(LEX *lex)
|
|
{
|
|
DBUG_ENTER("sp_head::init");
|
|
|
|
lex->spcont= m_pcont= new sp_pcontext();
|
|
|
|
if (!lex->spcont)
|
|
DBUG_VOID_RETURN;
|
|
|
|
/*
|
|
Altough trg_table_fields list is used only in triggers we init for all
|
|
types of stored procedures to simplify reset_lex()/restore_lex() code.
|
|
*/
|
|
lex->trg_table_fields.empty();
|
|
my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8, MYF(0));
|
|
|
|
m_param_begin= NULL;
|
|
m_param_end= NULL;
|
|
|
|
m_body_begin= NULL ;
|
|
|
|
m_qname.str= NULL;
|
|
m_qname.length= 0;
|
|
|
|
m_explicit_name= false;
|
|
|
|
m_db.str= NULL;
|
|
m_db.length= 0;
|
|
|
|
m_name.str= NULL;
|
|
m_name.length= 0;
|
|
|
|
m_params.str= NULL;
|
|
m_params.length= 0;
|
|
|
|
m_body.str= NULL;
|
|
m_body.length= 0;
|
|
|
|
m_defstr.str= NULL;
|
|
m_defstr.length= 0;
|
|
|
|
m_return_field_def.charset= NULL;
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void
|
|
sp_head::init_sp_name(THD *thd, sp_name *spname)
|
|
{
|
|
DBUG_ENTER("sp_head::init_sp_name");
|
|
|
|
/* Must be initialized in the parser. */
|
|
|
|
DBUG_ASSERT(spname && spname->m_db.str && spname->m_db.length);
|
|
|
|
/* We have to copy strings to get them into the right memroot. */
|
|
|
|
m_db.length= spname->m_db.length;
|
|
m_db.str= strmake_root(thd->mem_root, spname->m_db.str, spname->m_db.length);
|
|
|
|
m_name.length= spname->m_name.length;
|
|
m_name.str= strmake_root(thd->mem_root, spname->m_name.str,
|
|
spname->m_name.length);
|
|
|
|
m_explicit_name= spname->m_explicit_name;
|
|
|
|
if (spname->m_qname.length == 0)
|
|
spname->init_qname(thd);
|
|
|
|
m_qname.length= spname->m_qname.length;
|
|
m_qname.str= (char*) memdup_root(thd->mem_root,
|
|
spname->m_qname.str,
|
|
spname->m_qname.length + 1);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void
|
|
sp_head::set_body_start(THD *thd, const char *begin_ptr)
|
|
{
|
|
m_body_begin= begin_ptr;
|
|
thd->m_parser_state->m_lip.body_utf8_start(thd, begin_ptr);
|
|
}
|
|
|
|
|
|
void
|
|
sp_head::set_stmt_end(THD *thd)
|
|
{
|
|
Lex_input_stream *lip= & thd->m_parser_state->m_lip; /* shortcut */
|
|
const char *end_ptr= lip->get_cpp_ptr(); /* shortcut */
|
|
uint not_used;
|
|
|
|
/* Make the string of parameters. */
|
|
|
|
if (m_param_begin && m_param_end)
|
|
{
|
|
m_params.length= m_param_end - m_param_begin;
|
|
m_params.str= thd->strmake(m_param_begin, m_params.length);
|
|
}
|
|
|
|
/* Remember end pointer for further dumping of whole statement. */
|
|
|
|
thd->lex->stmt_definition_end= end_ptr;
|
|
|
|
/* Make the string of body (in the original character set). */
|
|
|
|
m_body.length= end_ptr - m_body_begin;
|
|
m_body.str= thd->strmake(m_body_begin, m_body.length);
|
|
trim_whitespace(thd->charset(), &m_body, ¬_used);
|
|
|
|
/* Make the string of UTF-body. */
|
|
|
|
lip->body_utf8_append(end_ptr);
|
|
|
|
m_body_utf8.length= lip->get_body_utf8_length();
|
|
m_body_utf8.str= thd->strmake(lip->get_body_utf8_str(), m_body_utf8.length);
|
|
trim_whitespace(thd->charset(), &m_body_utf8, ¬_used);
|
|
|
|
/*
|
|
Make the string of whole stored-program-definition query (in the
|
|
original character set).
|
|
*/
|
|
|
|
m_defstr.length= end_ptr - lip->get_cpp_buf();
|
|
m_defstr.str= thd->strmake(lip->get_cpp_buf(), m_defstr.length);
|
|
trim_whitespace(thd->charset(), &m_defstr, ¬_used);
|
|
}
|
|
|
|
|
|
static TYPELIB *
|
|
create_typelib(MEM_ROOT *mem_root, Column_definition *field_def, List<String> *src)
|
|
{
|
|
TYPELIB *result= NULL;
|
|
CHARSET_INFO *cs= field_def->charset;
|
|
DBUG_ENTER("create_typelib");
|
|
|
|
if (src->elements)
|
|
{
|
|
result= (TYPELIB*) alloc_root(mem_root, sizeof(TYPELIB));
|
|
result->count= src->elements;
|
|
result->name= "";
|
|
if (!(result->type_names=(const char **)
|
|
alloc_root(mem_root,(sizeof(char *)+sizeof(int))*(result->count+1))))
|
|
DBUG_RETURN(0);
|
|
result->type_lengths= (uint*)(result->type_names + result->count+1);
|
|
List_iterator<String> it(*src);
|
|
String conv;
|
|
for (uint i=0; i < result->count; i++)
|
|
{
|
|
uint32 dummy;
|
|
uint length;
|
|
String *tmp= it++;
|
|
|
|
if (String::needs_conversion(tmp->length(), tmp->charset(),
|
|
cs, &dummy))
|
|
{
|
|
uint cnv_errs;
|
|
conv.copy(tmp->ptr(), tmp->length(), tmp->charset(), cs, &cnv_errs);
|
|
|
|
length= conv.length();
|
|
result->type_names[i]= (char*) strmake_root(mem_root, conv.ptr(),
|
|
length);
|
|
}
|
|
else
|
|
{
|
|
length= tmp->length();
|
|
result->type_names[i]= strmake_root(mem_root, tmp->ptr(), length);
|
|
}
|
|
|
|
// Strip trailing spaces.
|
|
length= cs->cset->lengthsp(cs, result->type_names[i], length);
|
|
result->type_lengths[i]= length;
|
|
((uchar *)result->type_names[i])[length]= '\0';
|
|
}
|
|
result->type_names[result->count]= 0;
|
|
result->type_lengths[result->count]= 0;
|
|
}
|
|
DBUG_RETURN(result);
|
|
}
|
|
|
|
|
|
sp_head::~sp_head()
|
|
{
|
|
LEX *lex;
|
|
sp_instr *i;
|
|
DBUG_ENTER("sp_head::~sp_head");
|
|
|
|
/* sp_head::restore_thd_mem_root() must already have been called. */
|
|
DBUG_ASSERT(m_thd == NULL);
|
|
|
|
for (uint ip = 0 ; (i = get_instr(ip)) ; ip++)
|
|
delete i;
|
|
delete_dynamic(&m_instr);
|
|
delete m_pcont;
|
|
free_items();
|
|
|
|
/*
|
|
If we have non-empty LEX stack then we just came out of parser with
|
|
error. Now we should delete all auxilary LEXes and restore original
|
|
THD::lex. It is safe to not update LEX::ptr because further query
|
|
string parsing and execution will be stopped anyway.
|
|
*/
|
|
while ((lex= (LEX *)m_lex.pop()))
|
|
{
|
|
THD *thd= lex->thd;
|
|
thd->lex->sphead= NULL;
|
|
lex_end(thd->lex);
|
|
delete thd->lex;
|
|
thd->lex= lex;
|
|
}
|
|
|
|
my_hash_free(&m_sptabs);
|
|
my_hash_free(&m_sroutines);
|
|
|
|
sp_head::destroy(m_next_cached_sp);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
This is only used for result fields from functions (both during
|
|
fix_length_and_dec() and evaluation).
|
|
*/
|
|
|
|
Field *
|
|
sp_head::create_result_field(uint field_max_length, const char *field_name,
|
|
TABLE *table)
|
|
{
|
|
Field *field;
|
|
|
|
DBUG_ENTER("sp_head::create_result_field");
|
|
|
|
/*
|
|
m_return_field_def.length is always set to the field length calculated
|
|
by the parser, according to the RETURNS clause. See prepare_create_field()
|
|
in sql_table.cc. Value examples, depending on data type:
|
|
- 11 for INT (character representation length)
|
|
- 20 for BIGINT (character representation length)
|
|
- 22 for DOUBLE (character representation length)
|
|
- N for CHAR(N) CHARACTER SET latin1 (octet length)
|
|
- 3*N for CHAR(N) CHARACTER SET utf8 (octet length)
|
|
- 8 for blob-alike data types (packed length !!!)
|
|
|
|
field_max_length is also set according to the data type in the RETURNS
|
|
clause but can have different values depending on the execution stage:
|
|
|
|
1. During direct execution:
|
|
field_max_length is 0, because Item_func_sp::fix_length_and_dec() has
|
|
not been called yet, so Item_func_sp::max_length is 0 by default.
|
|
|
|
2a. During PREPARE:
|
|
field_max_length is 0, because Item_func_sp::fix_length_and_dec()
|
|
has not been called yet. It's called after create_result_field().
|
|
|
|
2b. During EXEC:
|
|
field_max_length is set to the maximum possible octet length of the
|
|
RETURNS data type.
|
|
- N for CHAR(N) CHARACTER SET latin1 (octet length)
|
|
- 3*N for CHAR(N) CHARACTER SET utf8 (octet length)
|
|
- 255 for TINYBLOB (octet length, not packed length !!!)
|
|
|
|
Perhaps we should refactor prepare_create_field() to set
|
|
Create_field::length to maximum octet length for BLOBs,
|
|
instead of packed length).
|
|
*/
|
|
DBUG_ASSERT(field_max_length <= m_return_field_def.length ||
|
|
(current_thd->stmt_arena->is_stmt_execute() &&
|
|
m_return_field_def.length == 8 &&
|
|
(m_return_field_def.pack_flag &
|
|
(FIELDFLAG_BLOB|FIELDFLAG_GEOM))));
|
|
|
|
field= m_return_field_def.make_field(table->s, /* TABLE_SHARE ptr */
|
|
table->in_use->mem_root,
|
|
field_name ?
|
|
field_name :
|
|
(const char *) m_name.str);
|
|
|
|
field->vcol_info= m_return_field_def.vcol_info;
|
|
if (field)
|
|
field->init(table);
|
|
|
|
DBUG_RETURN(field);
|
|
}
|
|
|
|
|
|
int cmp_rqp_locations(Rewritable_query_parameter * const *a,
|
|
Rewritable_query_parameter * const *b)
|
|
{
|
|
return (int)((*a)->pos_in_query - (*b)->pos_in_query);
|
|
}
|
|
|
|
|
|
/*
|
|
StoredRoutinesBinlogging
|
|
This paragraph applies only to statement-based binlogging. Row-based
|
|
binlogging does not need anything special like this.
|
|
|
|
Top-down overview:
|
|
|
|
1. Statements
|
|
|
|
Statements that have is_update_query(stmt) == TRUE are written into the
|
|
binary log verbatim.
|
|
Examples:
|
|
UPDATE tbl SET tbl.x = spfunc_w_side_effects()
|
|
UPDATE tbl SET tbl.x=1 WHERE spfunc_w_side_effect_that_returns_false(tbl.y)
|
|
|
|
Statements that have is_update_query(stmt) == FALSE (e.g. SELECTs) are not
|
|
written into binary log. Instead we catch function calls the statement
|
|
makes and write it into binary log separately (see #3).
|
|
|
|
2. PROCEDURE calls
|
|
|
|
CALL statements are not written into binary log. Instead
|
|
* Any FUNCTION invocation (in SET, IF, WHILE, OPEN CURSOR and other SP
|
|
instructions) is written into binlog separately.
|
|
|
|
* Each statement executed in SP is binlogged separately, according to rules
|
|
in #1, with the exception that we modify query string: we replace uses
|
|
of SP local variables with NAME_CONST('spvar_name', <spvar-value>) calls.
|
|
This substitution is done in subst_spvars().
|
|
|
|
3. FUNCTION calls
|
|
|
|
In sp_head::execute_function(), we check
|
|
* If this function invocation is done from a statement that is written
|
|
into the binary log.
|
|
* If there were any attempts to write events to the binary log during
|
|
function execution (grep for start_union_events and stop_union_events)
|
|
|
|
If the answers are No and Yes, we write the function call into the binary
|
|
log as "SELECT spfunc(<param1value>, <param2value>, ...)"
|
|
|
|
|
|
4. Miscellaneous issues.
|
|
|
|
4.1 User variables.
|
|
|
|
When we call mysql_bin_log.write() for an SP statement, thd->user_var_events
|
|
must hold set<{var_name, value}> pairs for all user variables used during
|
|
the statement execution.
|
|
This set is produced by tracking user variable reads during statement
|
|
execution.
|
|
|
|
For SPs, this has the following implications:
|
|
1) thd->user_var_events may contain events from several SP statements and
|
|
needs to be valid after exection of these statements was finished. In
|
|
order to achieve that, we
|
|
* Allocate user_var_events array elements on appropriate mem_root (grep
|
|
for user_var_events_alloc).
|
|
* Use is_query_in_union() to determine if user_var_event is created.
|
|
|
|
2) We need to empty thd->user_var_events after we have wrote a function
|
|
call. This is currently done by making
|
|
reset_dynamic(&thd->user_var_events);
|
|
calls in several different places. (TODO cosider moving this into
|
|
mysql_bin_log.write() function)
|
|
|
|
4.2 Auto_increment storage in binlog
|
|
|
|
As we may write two statements to binlog from one single logical statement
|
|
(case of "SELECT func1(),func2()": it is binlogged as "SELECT func1()" and
|
|
then "SELECT func2()"), we need to reset auto_increment binlog variables
|
|
after each binlogged SELECT. Otherwise, the auto_increment value of the
|
|
first SELECT would be used for the second too.
|
|
*/
|
|
|
|
|
|
/**
|
|
Replace thd->query{_length} with a string that one can write to
|
|
the binlog.
|
|
|
|
The binlog-suitable string is produced by replacing references to SP local
|
|
variables with NAME_CONST('sp_var_name', value) calls.
|
|
|
|
@param thd Current thread.
|
|
@param instr Instruction (we look for Item_splocal instances in
|
|
instr->free_list)
|
|
@param query_str Original query string
|
|
|
|
@return
|
|
- FALSE on success.
|
|
thd->query{_length} either has been appropriately replaced or there
|
|
is no need for replacements.
|
|
- TRUE out of memory error.
|
|
*/
|
|
|
|
static bool
|
|
subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str)
|
|
{
|
|
DBUG_ENTER("subst_spvars");
|
|
|
|
Dynamic_array<Rewritable_query_parameter*> rewritables;
|
|
char *pbuf;
|
|
StringBuffer<512> qbuf;
|
|
Copy_query_with_rewrite acc(thd, query_str->str, query_str->length, &qbuf);
|
|
|
|
/* Find rewritable Items used in this statement */
|
|
for (Item *item= instr->free_list; item; item= item->next)
|
|
{
|
|
Rewritable_query_parameter *rqp= item->get_rewritable_query_parameter();
|
|
if (rqp && rqp->pos_in_query)
|
|
rewritables.append(rqp);
|
|
}
|
|
if (!rewritables.elements())
|
|
DBUG_RETURN(FALSE);
|
|
|
|
rewritables.sort(cmp_rqp_locations);
|
|
|
|
thd->query_name_consts= rewritables.elements();
|
|
|
|
for (Rewritable_query_parameter **rqp= rewritables.front();
|
|
rqp <= rewritables.back(); rqp++)
|
|
{
|
|
if (acc.append(*rqp))
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
if (acc.finalize())
|
|
DBUG_RETURN(TRUE);
|
|
|
|
/*
|
|
Allocate additional space at the end of the new query string for the
|
|
query_cache_send_result_to_client function.
|
|
|
|
The query buffer layout is:
|
|
buffer :==
|
|
<statement> The input statement(s)
|
|
'\0' Terminating null char
|
|
<length> Length of following current database name 2
|
|
<db_name> Name of current database
|
|
<flags> Flags struct
|
|
*/
|
|
int buf_len= (qbuf.length() + 1 + QUERY_CACHE_DB_LENGTH_SIZE +
|
|
thd->db_length + QUERY_CACHE_FLAGS_SIZE + 1);
|
|
if ((pbuf= (char *) alloc_root(thd->mem_root, buf_len)))
|
|
{
|
|
char *ptr= pbuf + qbuf.length();
|
|
memcpy(pbuf, qbuf.ptr(), qbuf.length());
|
|
*ptr= 0;
|
|
int2store(ptr+1, thd->db_length);
|
|
}
|
|
else
|
|
DBUG_RETURN(TRUE);
|
|
|
|
thd->set_query(pbuf, qbuf.length());
|
|
|
|
DBUG_RETURN(FALSE);
|
|
}
|
|
|
|
|
|
/**
|
|
Return appropriate error about recursion limit reaching
|
|
|
|
@param thd Thread handle
|
|
|
|
@remark For functions and triggers we return error about
|
|
prohibited recursion. For stored procedures we
|
|
return about reaching recursion limit.
|
|
*/
|
|
|
|
void sp_head::recursion_level_error(THD *thd)
|
|
{
|
|
if (m_type == TYPE_ENUM_PROCEDURE)
|
|
{
|
|
my_error(ER_SP_RECURSION_LIMIT, MYF(0),
|
|
static_cast<int>(thd->variables.max_sp_recursion_depth),
|
|
m_name.str);
|
|
}
|
|
else
|
|
my_error(ER_SP_NO_RECURSION, MYF(0));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
Execute the routine. The main instruction jump loop is there.
|
|
Assume the parameters already set.
|
|
|
|
@param thd Thread context.
|
|
@param merge_da_on_success Flag specifying if Warning Info should be
|
|
propagated to the caller on Completion
|
|
Condition or not.
|
|
|
|
@todo
|
|
- Will write this SP statement into binlog separately
|
|
(TODO: consider changing the condition to "not inside event union")
|
|
|
|
@return Error status.
|
|
@retval
|
|
FALSE on success
|
|
@retval
|
|
TRUE on error
|
|
*/
|
|
|
|
bool
|
|
sp_head::execute(THD *thd, bool merge_da_on_success)
|
|
{
|
|
DBUG_ENTER("sp_head::execute");
|
|
char saved_cur_db_name_buf[SAFE_NAME_LEN+1];
|
|
LEX_STRING saved_cur_db_name=
|
|
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
|
|
bool cur_db_changed= FALSE;
|
|
sp_rcontext *ctx= thd->spcont;
|
|
bool err_status= FALSE;
|
|
uint ip= 0;
|
|
ulonglong save_sql_mode;
|
|
bool save_abort_on_warning;
|
|
Query_arena *old_arena;
|
|
/* per-instruction arena */
|
|
MEM_ROOT execute_mem_root;
|
|
Query_arena execute_arena(&execute_mem_root, STMT_INITIALIZED_FOR_SP),
|
|
backup_arena;
|
|
query_id_t old_query_id;
|
|
TABLE *old_derived_tables;
|
|
TABLE *old_rec_tables;
|
|
LEX *old_lex;
|
|
Item_change_list old_change_list;
|
|
String old_packet;
|
|
uint old_server_status;
|
|
const uint status_backup_mask= SERVER_STATUS_CURSOR_EXISTS |
|
|
SERVER_STATUS_LAST_ROW_SENT;
|
|
MEM_ROOT *user_var_events_alloc_saved= 0;
|
|
Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer;
|
|
Object_creation_ctx *UNINIT_VAR(saved_creation_ctx);
|
|
Diagnostics_area *da= thd->get_stmt_da();
|
|
Warning_info sp_wi(da->warning_info_id(), false, true);
|
|
|
|
/* this 7*STACK_MIN_SIZE is a complex matter with a long history (see it!) */
|
|
if (check_stack_overrun(thd, 7 * STACK_MIN_SIZE, (uchar*)&old_packet))
|
|
DBUG_RETURN(TRUE);
|
|
|
|
/* init per-instruction memroot */
|
|
init_sql_alloc(&execute_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
|
|
|
|
DBUG_ASSERT(!(m_flags & IS_INVOKED));
|
|
m_flags|= IS_INVOKED;
|
|
m_first_instance->m_first_free_instance= m_next_cached_sp;
|
|
if (m_next_cached_sp)
|
|
{
|
|
DBUG_PRINT("info",
|
|
("first free for %p ++: %p->%p level: %lu flags %x",
|
|
m_first_instance, this,
|
|
m_next_cached_sp,
|
|
m_next_cached_sp->m_recursion_level,
|
|
m_next_cached_sp->m_flags));
|
|
}
|
|
/*
|
|
Check that if there are not any instances after this one then
|
|
pointer to the last instance points on this instance or if there are
|
|
some instances after this one then recursion level of next instance
|
|
greater then recursion level of current instance on 1
|
|
*/
|
|
DBUG_ASSERT((m_next_cached_sp == 0 &&
|
|
m_first_instance->m_last_cached_sp == this) ||
|
|
(m_recursion_level + 1 == m_next_cached_sp->m_recursion_level));
|
|
|
|
/*
|
|
NOTE: The SQL Standard does not specify the context that should be
|
|
preserved for stored routines. However, at SAP/Walldorf meeting it was
|
|
decided that current database should be preserved.
|
|
*/
|
|
|
|
if (m_db.length &&
|
|
(err_status= mysql_opt_change_db(thd, &m_db, &saved_cur_db_name, FALSE,
|
|
&cur_db_changed)))
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
thd->is_slave_error= 0;
|
|
old_arena= thd->stmt_arena;
|
|
|
|
/* Push a new warning information area. */
|
|
da->copy_sql_conditions_to_wi(thd, &sp_wi);
|
|
da->push_warning_info(&sp_wi);
|
|
|
|
/*
|
|
Switch query context. This has to be done early as this is sometimes
|
|
allocated on THD::mem_root
|
|
*/
|
|
if (m_creation_ctx)
|
|
saved_creation_ctx= m_creation_ctx->set_n_backup(thd);
|
|
|
|
/*
|
|
We have to save/restore this info when we are changing call level to
|
|
be able properly do close_thread_tables() in instructions.
|
|
*/
|
|
old_query_id= thd->query_id;
|
|
old_derived_tables= thd->derived_tables;
|
|
thd->derived_tables= 0;
|
|
old_rec_tables= thd->rec_tables;
|
|
thd->rec_tables= 0;
|
|
save_sql_mode= thd->variables.sql_mode;
|
|
thd->variables.sql_mode= m_sql_mode;
|
|
save_abort_on_warning= thd->abort_on_warning;
|
|
thd->abort_on_warning= 0;
|
|
/**
|
|
When inside a substatement (a stored function or trigger
|
|
statement), clear the metadata observer in THD, if any.
|
|
Remember the value of the observer here, to be able
|
|
to restore it when leaving the substatement.
|
|
|
|
We reset the observer to suppress errors when a substatement
|
|
uses temporary tables. If a temporary table does not exist
|
|
at start of the main statement, it's not prelocked
|
|
and thus is not validated with other prelocked tables.
|
|
|
|
Later on, when the temporary table is opened, metadata
|
|
versions mismatch, expectedly.
|
|
|
|
The proper solution for the problem is to re-validate tables
|
|
of substatements (Bug#12257, Bug#27011, Bug#32868, Bug#33000),
|
|
but it's not implemented yet.
|
|
*/
|
|
thd->m_reprepare_observer= 0;
|
|
|
|
/*
|
|
It is also more efficient to save/restore current thd->lex once when
|
|
do it in each instruction
|
|
*/
|
|
old_lex= thd->lex;
|
|
/*
|
|
We should also save Item tree change list to avoid rollback something
|
|
too early in the calling query.
|
|
*/
|
|
thd->Item_change_list::move_elements_to(&old_change_list);
|
|
/*
|
|
Cursors will use thd->packet, so they may corrupt data which was prepared
|
|
for sending by upper level. OTOH cursors in the same routine can share this
|
|
buffer safely so let use use routine-local packet instead of having own
|
|
packet buffer for each cursor.
|
|
|
|
It is probably safe to use same thd->convert_buff everywhere.
|
|
*/
|
|
old_packet.swap(thd->packet);
|
|
old_server_status= thd->server_status & status_backup_mask;
|
|
|
|
/*
|
|
Switch to per-instruction arena here. We can do it since we cleanup
|
|
arena after every instruction.
|
|
*/
|
|
thd->set_n_backup_active_arena(&execute_arena, &backup_arena);
|
|
|
|
/*
|
|
Save callers arena in order to store instruction results and out
|
|
parameters in it later during sp_eval_func_item()
|
|
*/
|
|
thd->spcont->callers_arena= &backup_arena;
|
|
|
|
#if defined(ENABLED_PROFILING)
|
|
/* Discard the initial part of executing routines. */
|
|
thd->profiling.discard_current_query();
|
|
#endif
|
|
DEBUG_SYNC(thd, "sp_head_execute_before_loop");
|
|
do
|
|
{
|
|
sp_instr *i;
|
|
|
|
#if defined(ENABLED_PROFILING)
|
|
/*
|
|
Treat each "instr" of a routine as discrete unit that could be profiled.
|
|
Profiling only records information for segments of code that set the
|
|
source of the query, and almost all kinds of instructions in s-p do not.
|
|
*/
|
|
thd->profiling.finish_current_query();
|
|
thd->profiling.start_new_query("continuing inside routine");
|
|
#endif
|
|
|
|
/* get_instr returns NULL when we're done. */
|
|
i = get_instr(ip);
|
|
if (i == NULL)
|
|
{
|
|
#if defined(ENABLED_PROFILING)
|
|
thd->profiling.discard_current_query();
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
/* Reset number of warnings for this query. */
|
|
thd->get_stmt_da()->reset_for_next_command();
|
|
|
|
DBUG_PRINT("execute", ("Instruction %u", ip));
|
|
|
|
/*
|
|
We need to reset start_time to allow for time to flow inside a stored
|
|
procedure. This is only done for SP since time is suppose to be constant
|
|
during execution of triggers and functions.
|
|
*/
|
|
reset_start_time_for_sp(thd);
|
|
|
|
/*
|
|
We have to set thd->stmt_arena before executing the instruction
|
|
to store in the instruction free_list all new items, created
|
|
during the first execution (for example expanding of '*' or the
|
|
items made during other permanent subquery transformations).
|
|
*/
|
|
thd->stmt_arena= i;
|
|
|
|
/*
|
|
Will write this SP statement into binlog separately.
|
|
TODO: consider changing the condition to "not inside event union".
|
|
*/
|
|
if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
|
|
{
|
|
user_var_events_alloc_saved= thd->user_var_events_alloc;
|
|
thd->user_var_events_alloc= thd->mem_root;
|
|
}
|
|
|
|
sql_digest_state *parent_digest= thd->m_digest;
|
|
thd->m_digest= NULL;
|
|
|
|
err_status= i->execute(thd, &ip);
|
|
#ifdef WITH_WSREP
|
|
if (m_type == TYPE_ENUM_PROCEDURE)
|
|
{
|
|
mysql_mutex_lock(&thd->LOCK_thd_data);
|
|
if (thd->wsrep_conflict_state == MUST_REPLAY)
|
|
{
|
|
wsrep_replay_sp_transaction(thd);
|
|
err_status= thd->get_stmt_da()->is_set();
|
|
thd->wsrep_conflict_state= NO_CONFLICT;
|
|
}
|
|
else if (thd->wsrep_conflict_state == ABORTED ||
|
|
thd->wsrep_conflict_state == CERT_FAILURE)
|
|
{
|
|
/*
|
|
If the statement execution was BF aborted or was aborted
|
|
due to certification failure, clean up transaction here
|
|
and reset conflict state to NO_CONFLICT and thd->killed
|
|
to THD::NOT_KILLED. Error handling is done based on err_status
|
|
below. Error must have been raised by wsrep hton code before
|
|
entering here.
|
|
*/
|
|
DBUG_ASSERT(err_status);
|
|
DBUG_ASSERT(thd->get_stmt_da()->is_error());
|
|
thd->wsrep_conflict_state= NO_CONFLICT;
|
|
thd->killed= NOT_KILLED;
|
|
}
|
|
mysql_mutex_unlock(&thd->LOCK_thd_data);
|
|
}
|
|
#endif /* WITH_WSREP */
|
|
#ifdef WITH_WSREP_NO
|
|
if (WSREP(thd))
|
|
{
|
|
if (((thd->wsrep_trx().state() == wsrep::transaction::s_executing) &&
|
|
(thd->is_fatal_error || thd->killed)))
|
|
{
|
|
WSREP_DEBUG("SP abort err status %d in sub %d trx state %d",
|
|
err_status, thd->in_sub_stmt, thd->wsrep_trx().state());
|
|
err_status= 1;
|
|
thd->is_fatal_error= 1;
|
|
/*
|
|
SP was killed, and it is not due to a wsrep conflict.
|
|
We skip after_command hook at this point because
|
|
otherwise it clears the error, and cleans up the
|
|
whole transaction. For now we just return and finish
|
|
our handling once we are back to mysql_parse.
|
|
*/
|
|
WSREP_DEBUG("Skipping after_command hook for killed SP");
|
|
}
|
|
else
|
|
{
|
|
const bool must_replay= wsrep_must_replay(thd);
|
|
if (must_replay)
|
|
{
|
|
WSREP_DEBUG("MUST_REPLAY set after SP, err_status %d trx state: %d",
|
|
err_status, thd->wsrep_trx().state());
|
|
}
|
|
(void) wsrep_after_statement(thd);
|
|
|
|
/*
|
|
Reset the return code to zero if the transaction was
|
|
replayed succesfully.
|
|
*/
|
|
if (must_replay && !wsrep_current_error(thd))
|
|
{
|
|
err_status= 0;
|
|
thd->get_stmt_da()->reset_diagnostics_area();
|
|
}
|
|
/*
|
|
Final wsrep error status for statement is known only after
|
|
wsrep_after_statement() call. If the error is set, override
|
|
error in thd diagnostics area and reset wsrep client_state error
|
|
so that the error does not get propagated via client-server protocol.
|
|
*/
|
|
if (wsrep_current_error(thd))
|
|
{
|
|
wsrep_override_error(thd, wsrep_current_error(thd),
|
|
wsrep_current_error_status(thd));
|
|
thd->wsrep_cs().reset_error();
|
|
/* Reset also thd->killed if it has been set during BF abort. */
|
|
if (thd->killed == KILL_QUERY)
|
|
thd->killed= NOT_KILLED;
|
|
/* if failed transaction was not replayed, must return with error from here */
|
|
if (!must_replay) err_status = 1;
|
|
}
|
|
}
|
|
}
|
|
#endif /* WITH_WSREP */
|
|
thd->m_digest= parent_digest;
|
|
|
|
if (i->free_list)
|
|
cleanup_items(i->free_list);
|
|
|
|
/*
|
|
If we've set thd->user_var_events_alloc to mem_root of this SP
|
|
statement, clean all the events allocated in it.
|
|
*/
|
|
if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
|
|
{
|
|
reset_dynamic(&thd->user_var_events);
|
|
thd->user_var_events_alloc= user_var_events_alloc_saved;
|
|
}
|
|
|
|
/* we should cleanup free_list and memroot, used by instruction */
|
|
thd->cleanup_after_query();
|
|
free_root(&execute_mem_root, MYF(0));
|
|
|
|
/*
|
|
Find and process SQL handlers unless it is a fatal error (fatal
|
|
errors are not catchable by SQL handlers) or the connection has been
|
|
killed during execution.
|
|
*/
|
|
if (!thd->is_fatal_error && !thd->killed_errno() &&
|
|
ctx->handle_sql_condition(thd, &ip, i))
|
|
{
|
|
err_status= FALSE;
|
|
}
|
|
|
|
/* Reset sp_rcontext::end_partial_result_set flag. */
|
|
ctx->end_partial_result_set= FALSE;
|
|
|
|
} while (!err_status && !thd->killed && !thd->is_fatal_error);
|
|
|
|
#if defined(ENABLED_PROFILING)
|
|
thd->profiling.finish_current_query();
|
|
thd->profiling.start_new_query("tail end of routine");
|
|
#endif
|
|
|
|
/* Restore query context. */
|
|
|
|
if (m_creation_ctx)
|
|
m_creation_ctx->restore_env(thd, saved_creation_ctx);
|
|
|
|
/* Restore arena. */
|
|
|
|
thd->restore_active_arena(&execute_arena, &backup_arena);
|
|
|
|
thd->spcont->pop_all_cursors(); // To avoid memory leaks after an error
|
|
|
|
/* Restore all saved */
|
|
thd->server_status= (thd->server_status & ~status_backup_mask) | old_server_status;
|
|
old_packet.swap(thd->packet);
|
|
DBUG_ASSERT(thd->Item_change_list::is_empty());
|
|
old_change_list.move_elements_to(thd);
|
|
thd->lex= old_lex;
|
|
thd->set_query_id(old_query_id);
|
|
DBUG_ASSERT(!thd->derived_tables);
|
|
thd->derived_tables= old_derived_tables;
|
|
thd->rec_tables= old_rec_tables;
|
|
thd->variables.sql_mode= save_sql_mode;
|
|
thd->abort_on_warning= save_abort_on_warning;
|
|
thd->m_reprepare_observer= save_reprepare_observer;
|
|
|
|
thd->stmt_arena= old_arena;
|
|
state= STMT_EXECUTED;
|
|
|
|
/*
|
|
Restore the caller's original warning information area:
|
|
- warnings generated during trigger execution should not be
|
|
propagated to the caller on success;
|
|
- if there was an exception during execution, warning info should be
|
|
propagated to the caller in any case.
|
|
*/
|
|
da->pop_warning_info();
|
|
|
|
if (err_status || merge_da_on_success)
|
|
{
|
|
/*
|
|
If a routine body is empty or if a routine did not generate any warnings,
|
|
do not duplicate our own contents by appending the contents of the called
|
|
routine. We know that the called routine did not change its warning info.
|
|
|
|
On the other hand, if the routine body is not empty and some statement in
|
|
the routine generates a warning or uses tables, warning info is guaranteed
|
|
to have changed. In this case we know that the routine warning info
|
|
contains only new warnings, and thus we perform a copy.
|
|
*/
|
|
if (da->warning_info_changed(&sp_wi))
|
|
{
|
|
/*
|
|
If the invocation of the routine was a standalone statement,
|
|
rather than a sub-statement, in other words, if it's a CALL
|
|
of a procedure, rather than invocation of a function or a
|
|
trigger, we need to clear the current contents of the caller's
|
|
warning info.
|
|
|
|
This is per MySQL rules: if a statement generates a warning,
|
|
warnings from the previous statement are flushed. Normally
|
|
it's done in push_warning(). However, here we don't use
|
|
push_warning() to avoid invocation of condition handlers or
|
|
escalation of warnings to errors.
|
|
*/
|
|
da->opt_clear_warning_info(thd->query_id);
|
|
da->copy_sql_conditions_from_wi(thd, &sp_wi);
|
|
da->remove_marked_sql_conditions();
|
|
}
|
|
}
|
|
|
|
done:
|
|
DBUG_PRINT("info", ("err_status: %d killed: %d is_slave_error: %d report_error: %d",
|
|
err_status, thd->killed, thd->is_slave_error,
|
|
thd->is_error()));
|
|
|
|
if (thd->killed)
|
|
err_status= TRUE;
|
|
/*
|
|
If the DB has changed, the pointer has changed too, but the
|
|
original thd->db will then have been freed
|
|
*/
|
|
if (cur_db_changed && thd->killed != KILL_CONNECTION)
|
|
{
|
|
/*
|
|
Force switching back to the saved current database, because it may be
|
|
NULL. In this case, mysql_change_db() would generate an error.
|
|
*/
|
|
|
|
err_status|= mysql_change_db(thd, &saved_cur_db_name, TRUE);
|
|
}
|
|
m_flags&= ~IS_INVOKED;
|
|
DBUG_PRINT("info",
|
|
("first free for %p --: %p->%p, level: %lu, flags %x",
|
|
m_first_instance,
|
|
m_first_instance->m_first_free_instance,
|
|
this, m_recursion_level, m_flags));
|
|
/*
|
|
Check that we have one of following:
|
|
|
|
1) there are not free instances which means that this instance is last
|
|
in the list of instances (pointer to the last instance point on it and
|
|
ther are not other instances after this one in the list)
|
|
|
|
2) There are some free instances which mean that first free instance
|
|
should go just after this one and recursion level of that free instance
|
|
should be on 1 more then recursion level of this instance.
|
|
*/
|
|
DBUG_ASSERT((m_first_instance->m_first_free_instance == 0 &&
|
|
this == m_first_instance->m_last_cached_sp &&
|
|
m_next_cached_sp == 0) ||
|
|
(m_first_instance->m_first_free_instance != 0 &&
|
|
m_first_instance->m_first_free_instance == m_next_cached_sp &&
|
|
m_first_instance->m_first_free_instance->m_recursion_level ==
|
|
m_recursion_level + 1));
|
|
m_first_instance->m_first_free_instance= this;
|
|
|
|
DBUG_RETURN(err_status);
|
|
}
|
|
|
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
/**
|
|
set_routine_security_ctx() changes routine security context, and
|
|
checks if there is an EXECUTE privilege in new context. If there is
|
|
no EXECUTE privilege, it changes the context back and returns a
|
|
error.
|
|
|
|
@param thd thread handle
|
|
@param sp stored routine to change the context for
|
|
@param is_proc TRUE is procedure, FALSE if function
|
|
@param save_ctx pointer to an old security context
|
|
|
|
@todo
|
|
- Cache if the definer has the right to use the object on the
|
|
first usage and only reset the cache if someone does a GRANT
|
|
statement that 'may' affect this.
|
|
|
|
@retval
|
|
TRUE if there was a error, and the context wasn't changed.
|
|
@retval
|
|
FALSE if the context was changed.
|
|
*/
|
|
|
|
bool
|
|
set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc,
|
|
Security_context **save_ctx)
|
|
{
|
|
*save_ctx= 0;
|
|
if (sp->m_chistics->suid != SP_IS_NOT_SUID &&
|
|
sp->m_security_ctx.change_security_context(thd, &sp->m_definer_user,
|
|
&sp->m_definer_host,
|
|
&sp->m_db,
|
|
save_ctx))
|
|
return TRUE;
|
|
|
|
/*
|
|
If we changed context to run as another user, we need to check the
|
|
access right for the new context again as someone may have revoked
|
|
the right to use the procedure from this user.
|
|
|
|
TODO:
|
|
Cache if the definer has the right to use the object on the
|
|
first usage and only reset the cache if someone does a GRANT
|
|
statement that 'may' affect this.
|
|
*/
|
|
if (*save_ctx &&
|
|
check_routine_access(thd, EXECUTE_ACL,
|
|
sp->m_db.str, sp->m_name.str, is_proc, FALSE))
|
|
{
|
|
sp->m_security_ctx.restore_security_context(thd, *save_ctx);
|
|
*save_ctx= 0;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
#endif // ! NO_EMBEDDED_ACCESS_CHECKS
|
|
|
|
|
|
/**
|
|
Execute trigger stored program.
|
|
|
|
- changes security context for triggers
|
|
- switch to new memroot
|
|
- call sp_head::execute
|
|
- restore old memroot
|
|
- restores security context
|
|
|
|
@param thd Thread handle
|
|
@param db database name
|
|
@param table table name
|
|
@param grant_info GRANT_INFO structure to be filled with
|
|
information about definer's privileges
|
|
on subject table
|
|
|
|
@todo
|
|
- TODO: we should create sp_rcontext once per command and reuse it
|
|
on subsequent executions of a trigger.
|
|
|
|
@retval
|
|
FALSE on success
|
|
@retval
|
|
TRUE on error
|
|
*/
|
|
|
|
bool
|
|
sp_head::execute_trigger(THD *thd,
|
|
const LEX_STRING *db_name,
|
|
const LEX_STRING *table_name,
|
|
GRANT_INFO *grant_info)
|
|
{
|
|
sp_rcontext *octx = thd->spcont;
|
|
sp_rcontext *nctx = NULL;
|
|
bool err_status= FALSE;
|
|
MEM_ROOT call_mem_root;
|
|
Query_arena call_arena(&call_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP);
|
|
Query_arena backup_arena;
|
|
|
|
DBUG_ENTER("sp_head::execute_trigger");
|
|
DBUG_PRINT("info", ("trigger %s", m_name.str));
|
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
Security_context *save_ctx= NULL;
|
|
|
|
|
|
if (m_chistics->suid != SP_IS_NOT_SUID &&
|
|
m_security_ctx.change_security_context(thd,
|
|
&m_definer_user,
|
|
&m_definer_host,
|
|
&m_db,
|
|
&save_ctx))
|
|
DBUG_RETURN(TRUE);
|
|
|
|
/*
|
|
Fetch information about table-level privileges for subject table into
|
|
GRANT_INFO instance. The access check itself will happen in
|
|
Item_trigger_field, where this information will be used along with
|
|
information about column-level privileges.
|
|
*/
|
|
|
|
fill_effective_table_privileges(thd,
|
|
grant_info,
|
|
db_name->str,
|
|
table_name->str);
|
|
|
|
/* Check that the definer has TRIGGER privilege on the subject table. */
|
|
|
|
if (!(grant_info->privilege & TRIGGER_ACL))
|
|
{
|
|
char priv_desc[128];
|
|
get_privilege_desc(priv_desc, sizeof(priv_desc), TRIGGER_ACL);
|
|
|
|
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), priv_desc,
|
|
thd->security_ctx->priv_user, thd->security_ctx->host_or_ip,
|
|
table_name->str);
|
|
|
|
m_security_ctx.restore_security_context(thd, save_ctx);
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
#endif // NO_EMBEDDED_ACCESS_CHECKS
|
|
|
|
/*
|
|
Prepare arena and memroot for objects which lifetime is whole
|
|
duration of trigger call (sp_rcontext, it's tables and items,
|
|
sp_cursor and Item_cache holders for case expressions). We can't
|
|
use caller's arena/memroot for those objects because in this case
|
|
some fixed amount of memory will be consumed for each trigger
|
|
invocation and so statements which involve lot of them will hog
|
|
memory.
|
|
|
|
TODO: we should create sp_rcontext once per command and reuse it
|
|
on subsequent executions of a trigger.
|
|
*/
|
|
init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
|
|
thd->set_n_backup_active_arena(&call_arena, &backup_arena);
|
|
|
|
if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL)))
|
|
{
|
|
err_status= TRUE;
|
|
goto err_with_cleanup;
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
nctx->sp= this;
|
|
#endif
|
|
|
|
thd->spcont= nctx;
|
|
|
|
err_status= execute(thd, FALSE);
|
|
|
|
err_with_cleanup:
|
|
thd->restore_active_arena(&call_arena, &backup_arena);
|
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
m_security_ctx.restore_security_context(thd, save_ctx);
|
|
#endif // NO_EMBEDDED_ACCESS_CHECKS
|
|
|
|
delete nctx;
|
|
call_arena.free_items();
|
|
free_root(&call_mem_root, MYF(0));
|
|
thd->spcont= octx;
|
|
|
|
if (thd->killed)
|
|
thd->send_kill_message();
|
|
|
|
DBUG_RETURN(err_status);
|
|
}
|
|
|
|
|
|
/**
|
|
Execute a function.
|
|
|
|
- evaluate parameters
|
|
- changes security context for SUID routines
|
|
- switch to new memroot
|
|
- call sp_head::execute
|
|
- restore old memroot
|
|
- evaluate the return value
|
|
- restores security context
|
|
|
|
@param thd Thread handle
|
|
@param argp Passed arguments (these are items from containing
|
|
statement?)
|
|
@param argcount Number of passed arguments. We need to check if
|
|
this is correct.
|
|
@param return_value_fld Save result here.
|
|
|
|
@todo
|
|
We should create sp_rcontext once per command and reuse
|
|
it on subsequent executions of a function/trigger.
|
|
|
|
@todo
|
|
In future we should associate call arena/mem_root with
|
|
sp_rcontext and allocate all these objects (and sp_rcontext
|
|
itself) on it directly rather than juggle with arenas.
|
|
|
|
@retval
|
|
FALSE on success
|
|
@retval
|
|
TRUE on error
|
|
*/
|
|
|
|
bool
|
|
sp_head::execute_function(THD *thd, Item **argp, uint argcount,
|
|
Field *return_value_fld)
|
|
{
|
|
ulonglong UNINIT_VAR(binlog_save_options);
|
|
bool need_binlog_call= FALSE;
|
|
uint arg_no;
|
|
sp_rcontext *octx = thd->spcont;
|
|
sp_rcontext *nctx = NULL;
|
|
char buf[STRING_BUFFER_USUAL_SIZE];
|
|
String binlog_buf(buf, sizeof(buf), &my_charset_bin);
|
|
bool err_status= FALSE;
|
|
MEM_ROOT call_mem_root;
|
|
Query_arena call_arena(&call_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP);
|
|
Query_arena backup_arena;
|
|
DBUG_ENTER("sp_head::execute_function");
|
|
DBUG_PRINT("info", ("function %s", m_name.str));
|
|
|
|
/*
|
|
Check that the function is called with all specified arguments.
|
|
|
|
If it is not, use my_error() to report an error, or it will not terminate
|
|
the invoking query properly.
|
|
*/
|
|
if (argcount != m_pcont->context_var_count())
|
|
{
|
|
/*
|
|
Need to use my_error here, or it will not terminate the
|
|
invoking query properly.
|
|
*/
|
|
my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0),
|
|
"FUNCTION", m_qname.str, m_pcont->context_var_count(), argcount);
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
/*
|
|
Prepare arena and memroot for objects which lifetime is whole
|
|
duration of function call (sp_rcontext, it's tables and items,
|
|
sp_cursor and Item_cache holders for case expressions).
|
|
We can't use caller's arena/memroot for those objects because
|
|
in this case some fixed amount of memory will be consumed for
|
|
each function/trigger invocation and so statements which involve
|
|
lot of them will hog memory.
|
|
TODO: we should create sp_rcontext once per command and reuse
|
|
it on subsequent executions of a function/trigger.
|
|
*/
|
|
init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
|
|
thd->set_n_backup_active_arena(&call_arena, &backup_arena);
|
|
|
|
if (!(nctx= sp_rcontext::create(thd, m_pcont, return_value_fld)))
|
|
{
|
|
thd->restore_active_arena(&call_arena, &backup_arena);
|
|
err_status= TRUE;
|
|
goto err_with_cleanup;
|
|
}
|
|
|
|
/*
|
|
We have to switch temporarily back to callers arena/memroot.
|
|
Function arguments belong to the caller and so the may reference
|
|
memory which they will allocate during calculation long after
|
|
this function call will be finished (e.g. in Item::cleanup()).
|
|
*/
|
|
thd->restore_active_arena(&call_arena, &backup_arena);
|
|
|
|
#ifndef DBUG_OFF
|
|
nctx->sp= this;
|
|
#endif
|
|
|
|
/* Pass arguments. */
|
|
for (arg_no= 0; arg_no < argcount; arg_no++)
|
|
{
|
|
/* Arguments must be fixed in Item_func_sp::fix_fields */
|
|
DBUG_ASSERT(argp[arg_no]->fixed);
|
|
|
|
if ((err_status= nctx->set_variable(thd, arg_no, &(argp[arg_no]))))
|
|
goto err_with_cleanup;
|
|
}
|
|
|
|
/*
|
|
If row-based binlogging, we don't need to binlog the function's call, let
|
|
each substatement be binlogged its way.
|
|
*/
|
|
need_binlog_call= mysql_bin_log.is_open() &&
|
|
(thd->variables.option_bits & OPTION_BIN_LOG) &&
|
|
!thd->is_current_stmt_binlog_format_row();
|
|
|
|
/*
|
|
Remember the original arguments for unrolled replication of functions
|
|
before they are changed by execution.
|
|
*/
|
|
if (need_binlog_call)
|
|
{
|
|
binlog_buf.length(0);
|
|
binlog_buf.append(STRING_WITH_LEN("SELECT "));
|
|
append_identifier(thd, &binlog_buf, m_db.str, m_db.length);
|
|
binlog_buf.append('.');
|
|
append_identifier(thd, &binlog_buf, m_name.str, m_name.length);
|
|
binlog_buf.append('(');
|
|
for (arg_no= 0; arg_no < argcount; arg_no++)
|
|
{
|
|
String str_value_holder;
|
|
String *str_value;
|
|
|
|
if (arg_no)
|
|
binlog_buf.append(',');
|
|
|
|
str_value= sp_get_item_value(thd, nctx->get_item(arg_no),
|
|
&str_value_holder);
|
|
|
|
if (str_value)
|
|
binlog_buf.append(*str_value);
|
|
else
|
|
binlog_buf.append(STRING_WITH_LEN("NULL"));
|
|
}
|
|
binlog_buf.append(')');
|
|
}
|
|
thd->spcont= nctx;
|
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
Security_context *save_security_ctx;
|
|
if (set_routine_security_ctx(thd, this, FALSE, &save_security_ctx))
|
|
{
|
|
err_status= TRUE;
|
|
goto err_with_cleanup;
|
|
}
|
|
#endif
|
|
|
|
if (need_binlog_call)
|
|
{
|
|
query_id_t q;
|
|
reset_dynamic(&thd->user_var_events);
|
|
/*
|
|
In case of artificially constructed events for function calls
|
|
we have separate union for each such event and hence can't use
|
|
query_id of real calling statement as the start of all these
|
|
unions (this will break logic of replication of user-defined
|
|
variables). So we use artifical value which is guaranteed to
|
|
be greater than all query_id's of all statements belonging
|
|
to previous events/unions.
|
|
Possible alternative to this is logging of all function invocations
|
|
as one select and not resetting THD::user_var_events before
|
|
each invocation.
|
|
*/
|
|
q= get_query_id();
|
|
mysql_bin_log.start_union_events(thd, q + 1);
|
|
binlog_save_options= thd->variables.option_bits;
|
|
thd->variables.option_bits&= ~OPTION_BIN_LOG;
|
|
}
|
|
|
|
/*
|
|
Switch to call arena/mem_root so objects like sp_cursor or
|
|
Item_cache holders for case expressions can be allocated on it.
|
|
|
|
TODO: In future we should associate call arena/mem_root with
|
|
sp_rcontext and allocate all these objects (and sp_rcontext
|
|
itself) on it directly rather than juggle with arenas.
|
|
*/
|
|
thd->set_n_backup_active_arena(&call_arena, &backup_arena);
|
|
|
|
err_status= execute(thd, TRUE);
|
|
|
|
thd->restore_active_arena(&call_arena, &backup_arena);
|
|
|
|
if (need_binlog_call)
|
|
{
|
|
mysql_bin_log.stop_union_events(thd);
|
|
thd->variables.option_bits= binlog_save_options;
|
|
if (thd->binlog_evt_union.unioned_events)
|
|
{
|
|
int errcode = query_error_code(thd, thd->killed == NOT_KILLED);
|
|
Query_log_event qinfo(thd, binlog_buf.ptr(), binlog_buf.length(),
|
|
thd->binlog_evt_union.unioned_events_trans, FALSE, FALSE, errcode);
|
|
if (mysql_bin_log.write(&qinfo) &&
|
|
thd->binlog_evt_union.unioned_events_trans)
|
|
{
|
|
push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
|
|
"Invoked ROUTINE modified a transactional table but MySQL "
|
|
"failed to reflect this change in the binary log");
|
|
err_status= TRUE;
|
|
}
|
|
reset_dynamic(&thd->user_var_events);
|
|
/* Forget those values, in case more function calls are binlogged: */
|
|
thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0;
|
|
thd->auto_inc_intervals_in_cur_stmt_for_binlog.empty();
|
|
}
|
|
}
|
|
|
|
if (!err_status)
|
|
{
|
|
/* We need result only in function but not in trigger */
|
|
|
|
if (!nctx->is_return_value_set())
|
|
{
|
|
my_error(ER_SP_NORETURNEND, MYF(0), m_name.str);
|
|
err_status= TRUE;
|
|
}
|
|
}
|
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
m_security_ctx.restore_security_context(thd, save_security_ctx);
|
|
#endif
|
|
|
|
err_with_cleanup:
|
|
delete nctx;
|
|
call_arena.free_items();
|
|
free_root(&call_mem_root, MYF(0));
|
|
thd->spcont= octx;
|
|
|
|
/*
|
|
If not insided a procedure and a function printing warning
|
|
messsages.
|
|
*/
|
|
if (need_binlog_call &&
|
|
thd->spcont == NULL && !thd->binlog_evt_union.do_union)
|
|
thd->issue_unsafe_warnings();
|
|
|
|
DBUG_RETURN(err_status);
|
|
}
|
|
|
|
|
|
/**
|
|
Execute a procedure.
|
|
|
|
The function does the following steps:
|
|
- Set all parameters
|
|
- changes security context for SUID routines
|
|
- call sp_head::execute
|
|
- copy back values of INOUT and OUT parameters
|
|
- restores security context
|
|
|
|
@param thd Thread handle
|
|
@param args List of values passed as arguments.
|
|
|
|
@retval
|
|
FALSE on success
|
|
@retval
|
|
TRUE on error
|
|
*/
|
|
|
|
bool
|
|
sp_head::execute_procedure(THD *thd, List<Item> *args)
|
|
{
|
|
bool err_status= FALSE;
|
|
uint params = m_pcont->context_var_count();
|
|
/* Query start time may be reset in a multi-stmt SP; keep this for later. */
|
|
ulonglong utime_before_sp_exec= thd->utime_after_lock;
|
|
sp_rcontext *save_spcont, *octx;
|
|
sp_rcontext *nctx = NULL;
|
|
bool save_enable_slow_log;
|
|
bool save_log_general= false;
|
|
DBUG_ENTER("sp_head::execute_procedure");
|
|
DBUG_PRINT("info", ("procedure %s", m_name.str));
|
|
|
|
if (args->elements != params)
|
|
{
|
|
my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "PROCEDURE",
|
|
m_qname.str, params, args->elements);
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
|
|
save_spcont= octx= thd->spcont;
|
|
if (! octx)
|
|
{
|
|
/* Create a temporary old context. */
|
|
if (!(octx= sp_rcontext::create(thd, m_pcont, NULL)))
|
|
{
|
|
DBUG_PRINT("error", ("Could not create octx"));
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
octx->sp= 0;
|
|
#endif
|
|
thd->spcont= octx;
|
|
|
|
/* set callers_arena to thd, for upper-level function to work */
|
|
thd->spcont->callers_arena= thd;
|
|
}
|
|
|
|
if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL)))
|
|
{
|
|
delete nctx; /* Delete nctx if it was init() that failed. */
|
|
thd->spcont= save_spcont;
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
#ifndef DBUG_OFF
|
|
nctx->sp= this;
|
|
#endif
|
|
|
|
if (params > 0)
|
|
{
|
|
List_iterator<Item> it_args(*args);
|
|
|
|
DBUG_PRINT("info",(" %.*s: eval args", (int) m_name.length, m_name.str));
|
|
|
|
for (uint i= 0 ; i < params ; i++)
|
|
{
|
|
Item *arg_item= it_args++;
|
|
|
|
if (!arg_item)
|
|
break;
|
|
|
|
sp_variable *spvar= m_pcont->find_variable(i);
|
|
|
|
if (!spvar)
|
|
continue;
|
|
|
|
if (spvar->mode != sp_variable::MODE_IN)
|
|
{
|
|
Settable_routine_parameter *srp=
|
|
arg_item->get_settable_routine_parameter();
|
|
|
|
if (!srp)
|
|
{
|
|
my_error(ER_SP_NOT_VAR_ARG, MYF(0), i+1, m_qname.str);
|
|
err_status= TRUE;
|
|
break;
|
|
}
|
|
|
|
srp->set_required_privilege(spvar->mode == sp_variable::MODE_INOUT);
|
|
}
|
|
|
|
if (spvar->mode == sp_variable::MODE_OUT)
|
|
{
|
|
Item_null *null_item= new (thd->mem_root) Item_null(thd);
|
|
Item *tmp_item= null_item;
|
|
|
|
if (!null_item ||
|
|
nctx->set_variable(thd, i, &tmp_item))
|
|
{
|
|
DBUG_PRINT("error", ("set variable failed"));
|
|
err_status= TRUE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nctx->set_variable(thd, i, it_args.ref()))
|
|
{
|
|
DBUG_PRINT("error", ("set variable 2 failed"));
|
|
err_status= TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
TRANSACT_TRACKER(add_trx_state_from_thd(thd));
|
|
}
|
|
|
|
/*
|
|
Okay, got values for all arguments. Close tables that might be used by
|
|
arguments evaluation. If arguments evaluation required prelocking mode,
|
|
we'll leave it here.
|
|
*/
|
|
thd->lex->unit.cleanup();
|
|
|
|
if (!thd->in_sub_stmt)
|
|
{
|
|
thd->get_stmt_da()->set_overwrite_status(true);
|
|
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
|
|
thd->get_stmt_da()->set_overwrite_status(false);
|
|
}
|
|
|
|
thd_proc_info(thd, "closing tables");
|
|
close_thread_tables(thd);
|
|
thd_proc_info(thd, 0);
|
|
|
|
if (! thd->in_sub_stmt)
|
|
{
|
|
if (thd->transaction_rollback_request)
|
|
{
|
|
trans_rollback_implicit(thd);
|
|
thd->release_transactional_locks();
|
|
}
|
|
else if (! thd->in_multi_stmt_transaction_mode())
|
|
thd->release_transactional_locks();
|
|
else
|
|
thd->mdl_context.release_statement_locks();
|
|
}
|
|
|
|
thd->rollback_item_tree_changes();
|
|
|
|
DBUG_PRINT("info",(" %.*s: eval args done", (int) m_name.length,
|
|
m_name.str));
|
|
}
|
|
save_enable_slow_log= thd->enable_slow_log;
|
|
if (!(m_flags & LOG_SLOW_STATEMENTS) && save_enable_slow_log)
|
|
{
|
|
DBUG_PRINT("info", ("Disabling slow log for the execution"));
|
|
thd->enable_slow_log= FALSE;
|
|
}
|
|
if (!(m_flags & LOG_GENERAL_LOG) && !(thd->variables.option_bits & OPTION_LOG_OFF))
|
|
{
|
|
DBUG_PRINT("info", ("Disabling general log for the execution"));
|
|
save_log_general= true;
|
|
/* disable this bit */
|
|
thd->variables.option_bits |= OPTION_LOG_OFF;
|
|
}
|
|
thd->spcont= nctx;
|
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
Security_context *save_security_ctx= 0;
|
|
if (!err_status)
|
|
err_status= set_routine_security_ctx(thd, this, TRUE, &save_security_ctx);
|
|
#endif
|
|
|
|
if (!err_status)
|
|
{
|
|
err_status= execute(thd, TRUE);
|
|
}
|
|
|
|
if (save_log_general)
|
|
thd->variables.option_bits &= ~OPTION_LOG_OFF;
|
|
thd->enable_slow_log= save_enable_slow_log;
|
|
/*
|
|
In the case when we weren't able to employ reuse mechanism for
|
|
OUT/INOUT paranmeters, we should reallocate memory. This
|
|
allocation should be done on the arena which will live through
|
|
all execution of calling routine.
|
|
*/
|
|
thd->spcont->callers_arena= octx->callers_arena;
|
|
|
|
if (!err_status && params > 0)
|
|
{
|
|
List_iterator<Item> it_args(*args);
|
|
|
|
/*
|
|
Copy back all OUT or INOUT values to the previous frame, or
|
|
set global user variables
|
|
*/
|
|
for (uint i= 0 ; i < params ; i++)
|
|
{
|
|
Item *arg_item= it_args++;
|
|
|
|
if (!arg_item)
|
|
break;
|
|
|
|
sp_variable *spvar= m_pcont->find_variable(i);
|
|
|
|
if (spvar->mode == sp_variable::MODE_IN)
|
|
continue;
|
|
|
|
Settable_routine_parameter *srp=
|
|
arg_item->get_settable_routine_parameter();
|
|
|
|
DBUG_ASSERT(srp);
|
|
|
|
if (srp->set_value(thd, octx, nctx->get_item_addr(i)))
|
|
{
|
|
DBUG_PRINT("error", ("set value failed"));
|
|
err_status= TRUE;
|
|
break;
|
|
}
|
|
|
|
Send_field *out_param_info= new (thd->mem_root) Send_field();
|
|
nctx->get_item(i)->make_field(thd, out_param_info);
|
|
out_param_info->db_name= m_db.str;
|
|
out_param_info->table_name= m_name.str;
|
|
out_param_info->org_table_name= m_name.str;
|
|
out_param_info->col_name= spvar->name.str;
|
|
out_param_info->org_col_name= spvar->name.str;
|
|
|
|
srp->set_out_param_info(out_param_info);
|
|
}
|
|
}
|
|
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
if (save_security_ctx)
|
|
m_security_ctx.restore_security_context(thd, save_security_ctx);
|
|
#endif
|
|
|
|
if (!save_spcont)
|
|
delete octx;
|
|
|
|
delete nctx;
|
|
thd->spcont= save_spcont;
|
|
thd->utime_after_lock= utime_before_sp_exec;
|
|
|
|
/*
|
|
If not insided a procedure and a function printing warning
|
|
messsages.
|
|
*/
|
|
bool need_binlog_call= mysql_bin_log.is_open() &&
|
|
(thd->variables.option_bits & OPTION_BIN_LOG) &&
|
|
!thd->is_current_stmt_binlog_format_row();
|
|
if (need_binlog_call && thd->spcont == NULL &&
|
|
!thd->binlog_evt_union.do_union)
|
|
thd->issue_unsafe_warnings();
|
|
|
|
DBUG_RETURN(err_status);
|
|
}
|
|
|
|
|
|
/**
|
|
Reset lex during parsing, before we parse a sub statement.
|
|
|
|
@param thd Thread handler.
|
|
|
|
@return Error state
|
|
@retval true An error occurred.
|
|
@retval false Success.
|
|
*/
|
|
|
|
bool
|
|
sp_head::reset_lex(THD *thd)
|
|
{
|
|
DBUG_ENTER("sp_head::reset_lex");
|
|
LEX *sublex;
|
|
LEX *oldlex= thd->lex;
|
|
|
|
sublex= new (thd->mem_root)st_lex_local;
|
|
if (sublex == 0)
|
|
DBUG_RETURN(TRUE);
|
|
|
|
thd->lex= sublex;
|
|
(void)m_lex.push_front(oldlex);
|
|
|
|
/* Reset most stuff. */
|
|
lex_start(thd);
|
|
|
|
/* And keep the SP stuff too */
|
|
sublex->sphead= oldlex->sphead;
|
|
sublex->spcont= oldlex->spcont;
|
|
/* And trigger related stuff too */
|
|
sublex->trg_chistics= oldlex->trg_chistics;
|
|
sublex->trg_table_fields.empty();
|
|
sublex->sp_lex_in_use= FALSE;
|
|
|
|
/* Reset part of parser state which needs this. */
|
|
thd->m_parser_state->m_yacc.reset_before_substatement();
|
|
|
|
DBUG_RETURN(FALSE);
|
|
}
|
|
|
|
|
|
/**
|
|
Restore lex during parsing, after we have parsed a sub statement.
|
|
|
|
@param thd Thread handle
|
|
|
|
@return
|
|
@retval TRUE failure
|
|
@retval FALSE success
|
|
*/
|
|
|
|
bool
|
|
sp_head::restore_lex(THD *thd)
|
|
{
|
|
DBUG_ENTER("sp_head::restore_lex");
|
|
LEX *sublex= thd->lex;
|
|
LEX *oldlex;
|
|
|
|
sublex->set_trg_event_type_for_tables();
|
|
|
|
oldlex= (LEX *)m_lex.pop();
|
|
if (! oldlex)
|
|
DBUG_RETURN(FALSE); // Nothing to restore
|
|
|
|
oldlex->trg_table_fields.push_back(&sublex->trg_table_fields);
|
|
|
|
/* If this substatement is unsafe, the entire routine is too. */
|
|
DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags: 0x%x",
|
|
thd->lex->get_stmt_unsafe_flags()));
|
|
unsafe_flags|= sublex->get_stmt_unsafe_flags();
|
|
|
|
/*
|
|
Add routines which are used by statement to respective set for
|
|
this routine.
|
|
*/
|
|
if (sp_update_sp_used_routines(&m_sroutines, &sublex->sroutines))
|
|
DBUG_RETURN(TRUE);
|
|
|
|
/* If this substatement is a update query, then mark MODIFIES_DATA */
|
|
if (is_update_query(sublex->sql_command))
|
|
m_flags|= MODIFIES_DATA;
|
|
|
|
/*
|
|
Merge tables used by this statement (but not by its functions or
|
|
procedures) to multiset of tables used by this routine.
|
|
*/
|
|
merge_table_list(thd, sublex->query_tables, sublex);
|
|
/* Merge lists of PS parameters. */
|
|
oldlex->param_list.append(&sublex->param_list);
|
|
|
|
if (! sublex->sp_lex_in_use)
|
|
{
|
|
sublex->sphead= NULL;
|
|
lex_end(sublex);
|
|
delete sublex;
|
|
}
|
|
thd->lex= oldlex;
|
|
DBUG_RETURN(FALSE);
|
|
}
|
|
|
|
/**
|
|
Put the instruction on the backpatch list, associated with the label.
|
|
*/
|
|
int
|
|
sp_head::push_backpatch(THD *thd, sp_instr *i, sp_label *lab)
|
|
{
|
|
bp_t *bp= (bp_t *) thd->alloc(sizeof(bp_t));
|
|
|
|
if (!bp)
|
|
return 1;
|
|
bp->lab= lab;
|
|
bp->instr= i;
|
|
return m_backpatch.push_front(bp);
|
|
}
|
|
|
|
/**
|
|
Update all instruction with this label in the backpatch list to
|
|
the current position.
|
|
*/
|
|
void
|
|
sp_head::backpatch(sp_label *lab)
|
|
{
|
|
bp_t *bp;
|
|
uint dest= instructions();
|
|
List_iterator_fast<bp_t> li(m_backpatch);
|
|
|
|
DBUG_ENTER("sp_head::backpatch");
|
|
while ((bp= li++))
|
|
{
|
|
if (bp->lab == lab)
|
|
{
|
|
DBUG_PRINT("info", ("backpatch: (m_ip %d, label %p <%s>) to dest %d",
|
|
bp->instr->m_ip, lab, lab->name.str, dest));
|
|
bp->instr->backpatch(dest, lab->ctx);
|
|
}
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Prepare an instance of Column_definition for field creation
|
|
(fill all necessary attributes).
|
|
|
|
@param[in] thd Thread handle
|
|
@param[in] lex Yacc parsing context
|
|
@param[out] field_def An instance of create_field to be filled
|
|
|
|
@retval
|
|
FALSE on success
|
|
@retval
|
|
TRUE on error
|
|
*/
|
|
|
|
bool
|
|
sp_head::fill_field_definition(THD *thd, LEX *lex,
|
|
Column_definition *field_def)
|
|
{
|
|
uint unused1= 0;
|
|
|
|
if (field_def->check(thd))
|
|
return TRUE;
|
|
|
|
if (field_def->interval_list.elements)
|
|
field_def->interval= create_typelib(mem_root, field_def,
|
|
&field_def->interval_list);
|
|
|
|
sp_prepare_create_field(thd, field_def);
|
|
|
|
if (prepare_create_field(field_def, &unused1, HA_CAN_GEOMETRY))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
int
|
|
sp_head::new_cont_backpatch(sp_instr_opt_meta *i)
|
|
{
|
|
m_cont_level+= 1;
|
|
if (i)
|
|
{
|
|
/* Use the cont. destination slot to store the level */
|
|
i->m_cont_dest= m_cont_level;
|
|
if (m_cont_backpatch.push_front(i))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sp_head::add_cont_backpatch(sp_instr_opt_meta *i)
|
|
{
|
|
i->m_cont_dest= m_cont_level;
|
|
return m_cont_backpatch.push_front(i);
|
|
}
|
|
|
|
void
|
|
sp_head::do_cont_backpatch()
|
|
{
|
|
uint dest= instructions();
|
|
uint lev= m_cont_level--;
|
|
sp_instr_opt_meta *i;
|
|
|
|
while ((i= m_cont_backpatch.head()) && i->m_cont_dest == lev)
|
|
{
|
|
i->m_cont_dest= dest;
|
|
(void)m_cont_backpatch.pop();
|
|
}
|
|
}
|
|
|
|
void
|
|
sp_head::set_info(longlong created, longlong modified,
|
|
st_sp_chistics *chistics, sql_mode_t sql_mode)
|
|
{
|
|
m_created= created;
|
|
m_modified= modified;
|
|
m_chistics= (st_sp_chistics *) memdup_root(mem_root, (char*) chistics,
|
|
sizeof(*chistics));
|
|
if (m_chistics->comment.length == 0)
|
|
m_chistics->comment.str= 0;
|
|
else
|
|
m_chistics->comment.str= strmake_root(mem_root,
|
|
m_chistics->comment.str,
|
|
m_chistics->comment.length);
|
|
m_sql_mode= sql_mode;
|
|
}
|
|
|
|
|
|
void
|
|
sp_head::set_definer(const char *definer, uint definerlen)
|
|
{
|
|
char user_name_holder[USERNAME_LENGTH + 1];
|
|
LEX_STRING user_name= { user_name_holder, USERNAME_LENGTH };
|
|
|
|
char host_name_holder[HOSTNAME_LENGTH + 1];
|
|
LEX_STRING host_name= { host_name_holder, HOSTNAME_LENGTH };
|
|
|
|
if (parse_user(definer, definerlen, user_name.str, &user_name.length,
|
|
host_name.str, &host_name.length) &&
|
|
user_name.length && !host_name.length)
|
|
{
|
|
// 'user@' -> 'user@%'
|
|
host_name= host_not_specified;
|
|
}
|
|
|
|
set_definer(&user_name, &host_name);
|
|
}
|
|
|
|
|
|
void
|
|
sp_head::set_definer(const LEX_STRING *user_name, const LEX_STRING *host_name)
|
|
{
|
|
m_definer_user.str= strmake_root(mem_root, user_name->str, user_name->length);
|
|
m_definer_user.length= user_name->length;
|
|
|
|
m_definer_host.str= strmake_root(mem_root, host_name->str, host_name->length);
|
|
m_definer_host.length= host_name->length;
|
|
}
|
|
|
|
|
|
void
|
|
sp_head::reset_thd_mem_root(THD *thd)
|
|
{
|
|
DBUG_ENTER("sp_head::reset_thd_mem_root");
|
|
m_thd_root= thd->mem_root;
|
|
thd->mem_root= &main_mem_root;
|
|
DBUG_PRINT("info", ("mem_root %p moved to thd mem root %p",
|
|
&mem_root, &thd->mem_root));
|
|
free_list= thd->free_list; // Keep the old list
|
|
thd->free_list= NULL; // Start a new one
|
|
m_thd= thd;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void
|
|
sp_head::restore_thd_mem_root(THD *thd)
|
|
{
|
|
DBUG_ENTER("sp_head::restore_thd_mem_root");
|
|
|
|
/*
|
|
In some cases our parser detects a syntax error and calls
|
|
LEX::cleanup_lex_after_parse_error() method only after
|
|
finishing parsing the whole routine. In such a situation
|
|
sp_head::restore_thd_mem_root() will be called twice - the
|
|
first time as part of normal parsing process and the second
|
|
time by cleanup_lex_after_parse_error().
|
|
To avoid ruining active arena/mem_root state in this case we
|
|
skip restoration of old arena/mem_root if this method has been
|
|
already called for this routine.
|
|
*/
|
|
if (!m_thd)
|
|
DBUG_VOID_RETURN;
|
|
|
|
Item *flist= free_list; // The old list
|
|
set_query_arena(thd); // Get new free_list and mem_root
|
|
state= STMT_INITIALIZED_FOR_SP;
|
|
|
|
DBUG_PRINT("info", ("mem_root %p returned from thd mem root %p",
|
|
&mem_root, &thd->mem_root));
|
|
thd->free_list= flist; // Restore the old one
|
|
thd->mem_root= m_thd_root;
|
|
m_thd= NULL;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
Check if a user has access right to a routine.
|
|
|
|
@param thd Thread handler
|
|
@param sp SP
|
|
@param full_access Set to 1 if the user has SELECT right to the
|
|
'mysql.proc' able or is the owner of the routine
|
|
@retval
|
|
false ok
|
|
@retval
|
|
true error
|
|
*/
|
|
|
|
bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access)
|
|
{
|
|
TABLE_LIST tables;
|
|
bzero((char*) &tables,sizeof(tables));
|
|
tables.db= (char*) "mysql";
|
|
tables.table_name= tables.alias= (char*) "proc";
|
|
*full_access= ((!check_table_access(thd, SELECT_ACL, &tables, FALSE,
|
|
1, TRUE) &&
|
|
(tables.grant.privilege & SELECT_ACL) != 0) ||
|
|
/* Check if user owns the routine. */
|
|
(!strcmp(sp->m_definer_user.str,
|
|
thd->security_ctx->priv_user) &&
|
|
!strcmp(sp->m_definer_host.str,
|
|
thd->security_ctx->priv_host)) ||
|
|
/* Check if current role or any of the sub-granted roles
|
|
own the routine. */
|
|
(sp->m_definer_host.length == 0 &&
|
|
(!strcmp(sp->m_definer_user.str,
|
|
thd->security_ctx->priv_role) ||
|
|
check_role_is_granted(thd->security_ctx->priv_role, NULL,
|
|
sp->m_definer_user.str))));
|
|
if (!*full_access)
|
|
return check_some_routine_access(thd, sp->m_db.str, sp->m_name.str,
|
|
sp->m_type == TYPE_ENUM_PROCEDURE);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Collect metadata for SHOW CREATE statement for stored routines.
|
|
|
|
@param thd Thread context.
|
|
@param type Stored routine type
|
|
@param type Stored routine type
|
|
(TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION)
|
|
|
|
@return Error status.
|
|
@retval FALSE on success
|
|
@retval TRUE on error
|
|
*/
|
|
|
|
void
|
|
sp_head::show_create_routine_get_fields(THD *thd, int type, List<Item> *fields)
|
|
{
|
|
const char *col1_caption= type == TYPE_ENUM_PROCEDURE ?
|
|
"Procedure" : "Function";
|
|
|
|
const char *col3_caption= type == TYPE_ENUM_PROCEDURE ?
|
|
"Create Procedure" : "Create Function";
|
|
|
|
MEM_ROOT *mem_root= thd->mem_root;
|
|
|
|
/* Send header. */
|
|
|
|
fields->push_back(new (mem_root)
|
|
Item_empty_string(thd, col1_caption, NAME_CHAR_LEN),
|
|
mem_root);
|
|
fields->push_back(new (mem_root)
|
|
Item_empty_string(thd, "sql_mode", 256),
|
|
mem_root);
|
|
|
|
{
|
|
/*
|
|
NOTE: SQL statement field must be not less than 1024 in order not to
|
|
confuse old clients.
|
|
*/
|
|
|
|
Item_empty_string *stmt_fld=
|
|
new (mem_root) Item_empty_string(thd, col3_caption, 1024);
|
|
stmt_fld->maybe_null= TRUE;
|
|
|
|
fields->push_back(stmt_fld, mem_root);
|
|
}
|
|
|
|
fields->push_back(new (mem_root)
|
|
Item_empty_string(thd, "character_set_client",
|
|
MY_CS_NAME_SIZE),
|
|
mem_root);
|
|
|
|
fields->push_back(new (mem_root)
|
|
Item_empty_string(thd, "collation_connection",
|
|
MY_CS_NAME_SIZE),
|
|
mem_root);
|
|
|
|
fields->push_back(new (mem_root)
|
|
Item_empty_string(thd, "Database Collation",
|
|
MY_CS_NAME_SIZE),
|
|
mem_root);
|
|
}
|
|
|
|
|
|
/**
|
|
Implement SHOW CREATE statement for stored routines.
|
|
|
|
@param thd Thread context.
|
|
@param type Stored routine type
|
|
(TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION)
|
|
|
|
@return Error status.
|
|
@retval FALSE on success
|
|
@retval TRUE on error
|
|
*/
|
|
|
|
bool
|
|
sp_head::show_create_routine(THD *thd, int type)
|
|
{
|
|
const char *col1_caption= type == TYPE_ENUM_PROCEDURE ?
|
|
"Procedure" : "Function";
|
|
|
|
const char *col3_caption= type == TYPE_ENUM_PROCEDURE ?
|
|
"Create Procedure" : "Create Function";
|
|
|
|
bool err_status;
|
|
|
|
Protocol *protocol= thd->protocol;
|
|
List<Item> fields;
|
|
|
|
LEX_STRING sql_mode;
|
|
|
|
bool full_access;
|
|
MEM_ROOT *mem_root= thd->mem_root;
|
|
|
|
DBUG_ENTER("sp_head::show_create_routine");
|
|
DBUG_PRINT("info", ("routine %s", m_name.str));
|
|
|
|
DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
|
|
type == TYPE_ENUM_FUNCTION);
|
|
|
|
if (check_show_routine_access(thd, this, &full_access))
|
|
DBUG_RETURN(TRUE);
|
|
|
|
sql_mode_string_representation(thd, m_sql_mode, &sql_mode);
|
|
|
|
/* Send header. */
|
|
|
|
fields.push_back(new (mem_root)
|
|
Item_empty_string(thd, col1_caption, NAME_CHAR_LEN),
|
|
thd->mem_root);
|
|
fields.push_back(new (mem_root)
|
|
Item_empty_string(thd, "sql_mode", sql_mode.length),
|
|
thd->mem_root);
|
|
|
|
{
|
|
/*
|
|
NOTE: SQL statement field must be not less than 1024 in order not to
|
|
confuse old clients.
|
|
*/
|
|
|
|
Item_empty_string *stmt_fld=
|
|
new (mem_root) Item_empty_string(thd, col3_caption,
|
|
MY_MAX(m_defstr.length, 1024));
|
|
|
|
stmt_fld->maybe_null= TRUE;
|
|
|
|
fields.push_back(stmt_fld, thd->mem_root);
|
|
}
|
|
|
|
fields.push_back(new (mem_root)
|
|
Item_empty_string(thd, "character_set_client",
|
|
MY_CS_NAME_SIZE),
|
|
thd->mem_root);
|
|
|
|
fields.push_back(new (mem_root)
|
|
Item_empty_string(thd, "collation_connection",
|
|
MY_CS_NAME_SIZE),
|
|
thd->mem_root);
|
|
|
|
fields.push_back(new (mem_root)
|
|
Item_empty_string(thd, "Database Collation",
|
|
MY_CS_NAME_SIZE),
|
|
thd->mem_root);
|
|
|
|
if (protocol->send_result_set_metadata(&fields,
|
|
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
|
|
{
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
|
|
/* Send data. */
|
|
|
|
protocol->prepare_for_resend();
|
|
|
|
protocol->store(m_name.str, m_name.length, system_charset_info);
|
|
protocol->store(sql_mode.str, sql_mode.length, system_charset_info);
|
|
|
|
if (full_access)
|
|
protocol->store(m_defstr.str, m_defstr.length,
|
|
m_creation_ctx->get_client_cs());
|
|
else
|
|
protocol->store_null();
|
|
|
|
|
|
protocol->store(m_creation_ctx->get_client_cs()->csname, system_charset_info);
|
|
protocol->store(m_creation_ctx->get_connection_cl()->name, system_charset_info);
|
|
protocol->store(m_creation_ctx->get_db_cl()->name, system_charset_info);
|
|
|
|
err_status= protocol->write();
|
|
|
|
if (!err_status)
|
|
my_eof(thd);
|
|
|
|
DBUG_RETURN(err_status);
|
|
}
|
|
|
|
|
|
/**
|
|
Add instruction to SP.
|
|
|
|
@param instr Instruction
|
|
*/
|
|
|
|
int sp_head::add_instr(sp_instr *instr)
|
|
{
|
|
instr->free_list= m_thd->free_list;
|
|
m_thd->free_list= 0;
|
|
/*
|
|
Memory root of every instruction is designated for permanent
|
|
transformations (optimizations) made on the parsed tree during
|
|
the first execution. It points to the memory root of the
|
|
entire stored procedure, as their life span is equal.
|
|
*/
|
|
instr->mem_root= &main_mem_root;
|
|
return insert_dynamic(&m_instr, (uchar*)&instr);
|
|
}
|
|
|
|
|
|
/**
|
|
Do some minimal optimization of the code:
|
|
-# Mark used instructions
|
|
-# While doing this, shortcut jumps to jump instructions
|
|
-# Compact the code, removing unused instructions.
|
|
|
|
This is the main mark and move loop; it relies on the following methods
|
|
in sp_instr and its subclasses:
|
|
|
|
- opt_mark() : Mark instruction as reachable
|
|
- opt_shortcut_jump(): Shortcut jumps to the final destination;
|
|
used by opt_mark().
|
|
- opt_move() : Update moved instruction
|
|
- set_destination() : Set the new destination (jump instructions only)
|
|
*/
|
|
|
|
void sp_head::optimize()
|
|
{
|
|
List<sp_instr> bp;
|
|
sp_instr *i;
|
|
uint src, dst;
|
|
|
|
opt_mark();
|
|
|
|
bp.empty();
|
|
src= dst= 0;
|
|
while ((i= get_instr(src)))
|
|
{
|
|
if (! i->marked)
|
|
{
|
|
delete i;
|
|
src+= 1;
|
|
}
|
|
else
|
|
{
|
|
if (src != dst)
|
|
{
|
|
/* Move the instruction and update prev. jumps */
|
|
sp_instr *ibp;
|
|
List_iterator_fast<sp_instr> li(bp);
|
|
|
|
set_dynamic(&m_instr, (uchar*)&i, dst);
|
|
while ((ibp= li++))
|
|
{
|
|
sp_instr_opt_meta *im= static_cast<sp_instr_opt_meta *>(ibp);
|
|
im->set_destination(src, dst);
|
|
}
|
|
}
|
|
i->opt_move(dst, &bp);
|
|
src+= 1;
|
|
dst+= 1;
|
|
}
|
|
}
|
|
m_instr.elements= dst;
|
|
bp.empty();
|
|
}
|
|
|
|
void sp_head::add_mark_lead(uint ip, List<sp_instr> *leads)
|
|
{
|
|
sp_instr *i= get_instr(ip);
|
|
|
|
if (i && ! i->marked)
|
|
leads->push_front(i);
|
|
}
|
|
|
|
void
|
|
sp_head::opt_mark()
|
|
{
|
|
uint ip;
|
|
sp_instr *i;
|
|
List<sp_instr> leads;
|
|
|
|
/*
|
|
Forward flow analysis algorithm in the instruction graph:
|
|
- first, add the entry point in the graph (the first instruction) to the
|
|
'leads' list of paths to explore.
|
|
- while there are still leads to explore:
|
|
- pick one lead, and follow the path forward. Mark instruction reached.
|
|
Stop only if the end of the routine is reached, or the path converge
|
|
to code already explored (marked).
|
|
- while following a path, collect in the 'leads' list any fork to
|
|
another path (caused by conditional jumps instructions), so that these
|
|
paths can be explored as well.
|
|
*/
|
|
|
|
/* Add the entry point */
|
|
i= get_instr(0);
|
|
leads.push_front(i);
|
|
|
|
/* For each path of code ... */
|
|
while (leads.elements != 0)
|
|
{
|
|
i= leads.pop();
|
|
|
|
/* Mark the entire path, collecting new leads. */
|
|
while (i && ! i->marked)
|
|
{
|
|
ip= i->opt_mark(this, & leads);
|
|
i= get_instr(ip);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef DBUG_OFF
|
|
/**
|
|
Return the routine instructions as a result set.
|
|
@return
|
|
0 if ok, !=0 on error.
|
|
*/
|
|
int
|
|
sp_head::show_routine_code(THD *thd)
|
|
{
|
|
Protocol *protocol= thd->protocol;
|
|
char buff[2048];
|
|
String buffer(buff, sizeof(buff), system_charset_info);
|
|
List<Item> field_list;
|
|
sp_instr *i;
|
|
bool full_access;
|
|
int res= 0;
|
|
uint ip;
|
|
DBUG_ENTER("sp_head::show_routine_code");
|
|
DBUG_PRINT("info", ("procedure: %s", m_name.str));
|
|
|
|
if (check_show_routine_access(thd, this, &full_access) || !full_access)
|
|
DBUG_RETURN(1);
|
|
|
|
field_list.push_back(new (thd->mem_root) Item_uint(thd, "Pos", 9),
|
|
thd->mem_root);
|
|
// 1024 is for not to confuse old clients
|
|
field_list.push_back(new (thd->mem_root)
|
|
Item_empty_string(thd, "Instruction",
|
|
MY_MAX(buffer.length(), 1024)),
|
|
thd->mem_root);
|
|
if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS |
|
|
Protocol::SEND_EOF))
|
|
DBUG_RETURN(1);
|
|
|
|
for (ip= 0; (i = get_instr(ip)) ; ip++)
|
|
{
|
|
/*
|
|
Consistency check. If these are different something went wrong
|
|
during optimization.
|
|
*/
|
|
if (ip != i->m_ip)
|
|
{
|
|
const char *format= "Instruction at position %u has m_ip=%u";
|
|
char tmp[sizeof(format) + 2*SP_INSTR_UINT_MAXLEN + 1];
|
|
|
|
my_snprintf(tmp, sizeof(tmp), format, ip, i->m_ip);
|
|
/*
|
|
Since this is for debugging purposes only, we don't bother to
|
|
introduce a special error code for it.
|
|
*/
|
|
push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, tmp);
|
|
}
|
|
protocol->prepare_for_resend();
|
|
protocol->store((longlong)ip);
|
|
|
|
buffer.set("", 0, system_charset_info);
|
|
i->print(&buffer);
|
|
protocol->store(buffer.ptr(), buffer.length(), system_charset_info);
|
|
if ((res= protocol->write()))
|
|
break;
|
|
}
|
|
|
|
if (!res)
|
|
my_eof(thd);
|
|
|
|
DBUG_RETURN(res);
|
|
}
|
|
#endif // ifndef DBUG_OFF
|
|
|
|
|
|
/**
|
|
Prepare LEX and thread for execution of instruction, if requested open
|
|
and lock LEX's tables, execute instruction's core function, perform
|
|
cleanup afterwards.
|
|
|
|
@param thd thread context
|
|
@param nextp out - next instruction
|
|
@param open_tables if TRUE then check read access to tables in LEX's table
|
|
list and open and lock them (used in instructions which
|
|
need to calculate some expression and don't execute
|
|
complete statement).
|
|
@param sp_instr instruction for which we prepare context, and which core
|
|
function execute by calling its exec_core() method.
|
|
|
|
@note
|
|
We are not saving/restoring some parts of THD which may need this because
|
|
we do this once for whole routine execution in sp_head::execute().
|
|
|
|
@return
|
|
0/non-0 - Success/Failure
|
|
*/
|
|
|
|
int
|
|
sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
|
|
bool open_tables, sp_instr* instr)
|
|
{
|
|
int res= 0;
|
|
DBUG_ENTER("reset_lex_and_exec_core");
|
|
|
|
/*
|
|
The flag is saved at the entry to the following substatement.
|
|
It's reset further in the common code part.
|
|
It's merged with the saved parent's value at the exit of this func.
|
|
*/
|
|
bool parent_modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table;
|
|
thd->transaction.stmt.modified_non_trans_table= FALSE;
|
|
DBUG_ASSERT(!thd->derived_tables);
|
|
DBUG_ASSERT(thd->Item_change_list::is_empty());
|
|
/*
|
|
Use our own lex.
|
|
We should not save old value since it is saved/restored in
|
|
sp_head::execute() when we are entering/leaving routine.
|
|
*/
|
|
thd->lex= m_lex;
|
|
|
|
thd->set_query_id(next_query_id());
|
|
|
|
if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
|
|
{
|
|
/*
|
|
This statement will enter/leave prelocked mode on its own.
|
|
Entering prelocked mode changes table list and related members
|
|
of LEX, so we'll need to restore them.
|
|
*/
|
|
if (lex_query_tables_own_last)
|
|
{
|
|
/*
|
|
We've already entered/left prelocked mode with this statement.
|
|
Attach the list of tables that need to be prelocked and mark m_lex
|
|
as having such list attached.
|
|
*/
|
|
*lex_query_tables_own_last= prelocking_tables;
|
|
m_lex->mark_as_requiring_prelocking(lex_query_tables_own_last);
|
|
}
|
|
}
|
|
|
|
reinit_stmt_before_use(thd, m_lex);
|
|
|
|
#ifndef EMBEDDED_LIBRARY
|
|
/*
|
|
If there was instruction which changed tracking state,
|
|
the result of changed tracking state send to client in OK packed.
|
|
So it changes result sent to client and probably can be different
|
|
independent on query text. So we can't cache such results.
|
|
*/
|
|
if ((thd->client_capabilities & CLIENT_SESSION_TRACK) &&
|
|
(thd->server_status & SERVER_SESSION_STATE_CHANGED))
|
|
thd->lex->safe_to_cache_query= 0;
|
|
#endif
|
|
|
|
if (open_tables)
|
|
res= check_dependencies_in_with_clauses(m_lex->with_clauses_list) ||
|
|
instr->exec_open_and_lock_tables(thd, m_lex->query_tables);
|
|
|
|
if (!res)
|
|
{
|
|
res= instr->exec_core(thd, nextp);
|
|
DBUG_PRINT("info",("exec_core returned: %d", res));
|
|
}
|
|
|
|
/*
|
|
Call after unit->cleanup() to close open table
|
|
key read.
|
|
*/
|
|
if (open_tables)
|
|
{
|
|
m_lex->unit.cleanup();
|
|
/* Here we also commit or rollback the current statement. */
|
|
if (! thd->in_sub_stmt)
|
|
{
|
|
thd->get_stmt_da()->set_overwrite_status(true);
|
|
thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
|
|
thd->get_stmt_da()->set_overwrite_status(false);
|
|
}
|
|
thd_proc_info(thd, "closing tables");
|
|
close_thread_tables(thd);
|
|
thd_proc_info(thd, 0);
|
|
|
|
if (! thd->in_sub_stmt)
|
|
{
|
|
if (thd->transaction_rollback_request)
|
|
{
|
|
trans_rollback_implicit(thd);
|
|
thd->release_transactional_locks();
|
|
}
|
|
else if (! thd->in_multi_stmt_transaction_mode())
|
|
thd->release_transactional_locks();
|
|
else
|
|
thd->mdl_context.release_statement_locks();
|
|
}
|
|
}
|
|
//TODO: why is this here if log_slow_query is in sp_instr_stmt_execute?
|
|
delete_explain_query(m_lex);
|
|
|
|
if (m_lex->query_tables_own_last)
|
|
{
|
|
/*
|
|
We've entered and left prelocking mode when executing statement
|
|
stored in m_lex.
|
|
m_lex->query_tables(->next_global)* list now has a 'tail' - a list
|
|
of tables that are added for prelocking. (If this is the first
|
|
execution, the 'tail' was added by open_tables(), otherwise we've
|
|
attached it above in this function).
|
|
Now we'll save the 'tail', and detach it.
|
|
*/
|
|
lex_query_tables_own_last= m_lex->query_tables_own_last;
|
|
prelocking_tables= *lex_query_tables_own_last;
|
|
*lex_query_tables_own_last= NULL;
|
|
m_lex->mark_as_requiring_prelocking(NULL);
|
|
}
|
|
thd->rollback_item_tree_changes();
|
|
/*
|
|
Update the state of the active arena if no errors on
|
|
open_tables stage.
|
|
*/
|
|
if (!res || !thd->is_error() ||
|
|
(thd->get_stmt_da()->sql_errno() != ER_CANT_REOPEN_TABLE &&
|
|
thd->get_stmt_da()->sql_errno() != ER_NO_SUCH_TABLE &&
|
|
thd->get_stmt_da()->sql_errno() != ER_NO_SUCH_TABLE_IN_ENGINE &&
|
|
thd->get_stmt_da()->sql_errno() != ER_UPDATE_TABLE_USED))
|
|
thd->stmt_arena->state= Query_arena::STMT_EXECUTED;
|
|
|
|
/*
|
|
Merge here with the saved parent's values
|
|
what is needed from the substatement gained
|
|
*/
|
|
thd->transaction.stmt.modified_non_trans_table |= parent_modified_non_trans_table;
|
|
|
|
TRANSACT_TRACKER(add_trx_state_from_thd(thd));
|
|
|
|
/*
|
|
Unlike for PS we should not call Item's destructors for newly created
|
|
items after execution of each instruction in stored routine. This is
|
|
because SP often create Item (like Item_int, Item_string etc...) when
|
|
they want to store some value in local variable, pass return value and
|
|
etc... So their life time should be longer than one instruction.
|
|
|
|
cleanup_items() is called in sp_head::execute()
|
|
*/
|
|
thd->lex->restore_set_statement_var();
|
|
DBUG_RETURN(res || thd->is_error());
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr class functions
|
|
*/
|
|
|
|
int sp_instr::exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables)
|
|
{
|
|
int result;
|
|
|
|
/*
|
|
Check whenever we have access to tables for this statement
|
|
and open and lock them before executing instructions core function.
|
|
*/
|
|
if (thd->open_temporary_tables(tables) ||
|
|
check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)
|
|
|| open_and_lock_tables(thd, tables, TRUE, 0))
|
|
result= -1;
|
|
else
|
|
result= 0;
|
|
/* Prepare all derived tables/views to catch possible errors. */
|
|
if (!result)
|
|
result= mysql_handle_derived(thd->lex, DT_PREPARE) ? -1 : 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
uint sp_instr::get_cont_dest() const
|
|
{
|
|
return (m_ip+1);
|
|
}
|
|
|
|
|
|
int sp_instr::exec_core(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ASSERT(0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
sp_instr_stmt class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_stmt::execute(THD *thd, uint *nextp)
|
|
{
|
|
int res;
|
|
DBUG_ENTER("sp_instr_stmt::execute");
|
|
DBUG_PRINT("info", ("command: %d", m_lex_keeper.sql_command()));
|
|
|
|
const CSET_STRING query_backup= thd->query_string;
|
|
#if defined(ENABLED_PROFILING)
|
|
/* This s-p instr is profilable and will be captured. */
|
|
thd->profiling.set_query_source(m_query.str, m_query.length);
|
|
#endif
|
|
if (!(res= alloc_query(thd, m_query.str, m_query.length)) &&
|
|
!(res=subst_spvars(thd, this, &m_query)))
|
|
{
|
|
/*
|
|
(the order of query cache and subst_spvars calls is irrelevant because
|
|
queries with SP vars can't be cached)
|
|
*/
|
|
general_log_write(thd, COM_QUERY, thd->query(), thd->query_length());
|
|
|
|
if (query_cache_send_result_to_client(thd, thd->query(),
|
|
thd->query_length()) <= 0)
|
|
{
|
|
res= m_lex_keeper.reset_lex_and_exec_core(thd, nextp, FALSE, this);
|
|
bool log_slow= !res && thd->enable_slow_log;
|
|
|
|
/* Finalize server status flags after executing a statement. */
|
|
if (log_slow || thd->get_stmt_da()->is_eof())
|
|
thd->update_server_status();
|
|
|
|
if (thd->get_stmt_da()->is_eof())
|
|
thd->protocol->end_statement();
|
|
|
|
query_cache_end_of_result(thd);
|
|
|
|
mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS,
|
|
thd->get_stmt_da()->is_error() ?
|
|
thd->get_stmt_da()->sql_errno() : 0,
|
|
command_name[COM_QUERY].str);
|
|
|
|
if (log_slow)
|
|
log_slow_statement(thd);
|
|
}
|
|
else
|
|
{
|
|
/* change statistics */
|
|
enum_sql_command save_sql_command= thd->lex->sql_command;
|
|
thd->lex->sql_command= SQLCOM_SELECT;
|
|
status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]);
|
|
thd->update_stats();
|
|
thd->lex->sql_command= save_sql_command;
|
|
*nextp= m_ip+1;
|
|
}
|
|
thd->set_query(query_backup);
|
|
thd->query_name_consts= 0;
|
|
|
|
if (!thd->is_error())
|
|
{
|
|
res= 0;
|
|
thd->get_stmt_da()->reset_diagnostics_area();
|
|
}
|
|
}
|
|
DBUG_RETURN(res || thd->is_error());
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_stmt::print(String *str)
|
|
{
|
|
uint i, len;
|
|
|
|
/* stmt CMD "..." */
|
|
if (str->reserve(SP_STMT_PRINT_MAXLEN+SP_INSTR_UINT_MAXLEN+8))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("stmt "));
|
|
str->qs_append((uint)m_lex_keeper.sql_command());
|
|
str->qs_append(STRING_WITH_LEN(" \""));
|
|
len= m_query.length;
|
|
/*
|
|
Print the query string (but not too much of it), just to indicate which
|
|
statement it is.
|
|
*/
|
|
if (len > SP_STMT_PRINT_MAXLEN)
|
|
len= SP_STMT_PRINT_MAXLEN-3;
|
|
/* Copy the query string and replace '\n' with ' ' in the process */
|
|
for (i= 0 ; i < len ; i++)
|
|
{
|
|
char c= m_query.str[i];
|
|
if (c == '\n')
|
|
c= ' ';
|
|
str->qs_append(c);
|
|
}
|
|
if (m_query.length > SP_STMT_PRINT_MAXLEN)
|
|
str->qs_append(STRING_WITH_LEN("...")); /* Indicate truncated string */
|
|
str->qs_append('"');
|
|
}
|
|
|
|
|
|
int
|
|
sp_instr_stmt::exec_core(THD *thd, uint *nextp)
|
|
{
|
|
MYSQL_QUERY_EXEC_START(thd->query(),
|
|
thd->thread_id,
|
|
(char *) (thd->db ? thd->db : ""),
|
|
&thd->security_ctx->priv_user[0],
|
|
(char *)thd->security_ctx->host_or_ip,
|
|
3);
|
|
int res= mysql_execute_command(thd);
|
|
MYSQL_QUERY_EXEC_DONE(res);
|
|
*nextp= m_ip+1;
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_set class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_set::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_set::execute");
|
|
DBUG_PRINT("info", ("offset: %u", m_offset));
|
|
|
|
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
|
|
}
|
|
|
|
|
|
int
|
|
sp_instr_set::exec_core(THD *thd, uint *nextp)
|
|
{
|
|
int res= thd->spcont->set_variable(thd, m_offset, &m_value);
|
|
|
|
if (res)
|
|
{
|
|
/* Failed to evaluate the value. Reset the variable to NULL. */
|
|
|
|
if (thd->spcont->set_variable(thd, m_offset, 0))
|
|
{
|
|
/* If this also failed, let's abort. */
|
|
my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
|
|
}
|
|
}
|
|
delete_explain_query(thd->lex);
|
|
|
|
*nextp = m_ip+1;
|
|
return res;
|
|
}
|
|
|
|
void
|
|
sp_instr_set::print(String *str)
|
|
{
|
|
/* set name@offset ... */
|
|
int rsrv = SP_INSTR_UINT_MAXLEN+6;
|
|
sp_variable *var = m_ctx->find_variable(m_offset);
|
|
|
|
/* 'var' should always be non-null, but just in case... */
|
|
if (var)
|
|
rsrv+= var->name.length;
|
|
if (str->reserve(rsrv))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("set "));
|
|
if (var)
|
|
{
|
|
str->qs_append(var->name.str, var->name.length);
|
|
str->qs_append('@');
|
|
}
|
|
str->qs_append(m_offset);
|
|
str->qs_append(' ');
|
|
m_value->print(str, enum_query_type(QT_ORDINARY |
|
|
QT_ITEM_ORIGINAL_FUNC_NULLIF));
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_set_trigger_field class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_set_trigger_field::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_set_trigger_field::execute");
|
|
thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL;
|
|
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
|
|
}
|
|
|
|
|
|
int
|
|
sp_instr_set_trigger_field::exec_core(THD *thd, uint *nextp)
|
|
{
|
|
bool sav_abort_on_warning= thd->abort_on_warning;
|
|
thd->abort_on_warning= thd->is_strict_mode() && !thd->lex->ignore;
|
|
const int res= (trigger_field->set_value(thd, &value) ? -1 : 0);
|
|
thd->abort_on_warning= sav_abort_on_warning;
|
|
*nextp = m_ip+1;
|
|
return res;
|
|
}
|
|
|
|
void
|
|
sp_instr_set_trigger_field::print(String *str)
|
|
{
|
|
str->append(STRING_WITH_LEN("set_trigger_field "));
|
|
trigger_field->print(str, enum_query_type(QT_ORDINARY |
|
|
QT_ITEM_ORIGINAL_FUNC_NULLIF));
|
|
str->append(STRING_WITH_LEN(":="));
|
|
value->print(str, enum_query_type(QT_ORDINARY |
|
|
QT_ITEM_ORIGINAL_FUNC_NULLIF));
|
|
}
|
|
|
|
/*
|
|
sp_instr_opt_meta
|
|
*/
|
|
|
|
uint sp_instr_opt_meta::get_cont_dest() const
|
|
{
|
|
return m_cont_dest;
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_jump class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_jump::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_jump::execute");
|
|
DBUG_PRINT("info", ("destination: %u", m_dest));
|
|
|
|
*nextp= m_dest;
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
void
|
|
sp_instr_jump::print(String *str)
|
|
{
|
|
/* jump dest */
|
|
if (str->reserve(SP_INSTR_UINT_MAXLEN+5))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("jump "));
|
|
str->qs_append(m_dest);
|
|
}
|
|
|
|
uint
|
|
sp_instr_jump::opt_mark(sp_head *sp, List<sp_instr> *leads)
|
|
{
|
|
m_dest= opt_shortcut_jump(sp, this);
|
|
if (m_dest != m_ip+1) /* Jumping to following instruction? */
|
|
marked= 1;
|
|
m_optdest= sp->get_instr(m_dest);
|
|
return m_dest;
|
|
}
|
|
|
|
uint
|
|
sp_instr_jump::opt_shortcut_jump(sp_head *sp, sp_instr *start)
|
|
{
|
|
uint dest= m_dest;
|
|
sp_instr *i;
|
|
|
|
while ((i= sp->get_instr(dest)))
|
|
{
|
|
uint ndest;
|
|
|
|
if (start == i || this == i)
|
|
break;
|
|
ndest= i->opt_shortcut_jump(sp, start);
|
|
if (ndest == dest)
|
|
break;
|
|
dest= ndest;
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
void
|
|
sp_instr_jump::opt_move(uint dst, List<sp_instr> *bp)
|
|
{
|
|
if (m_dest > m_ip)
|
|
bp->push_back(this); // Forward
|
|
else if (m_optdest)
|
|
m_dest= m_optdest->m_ip; // Backward
|
|
m_ip= dst;
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_jump_if_not class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_jump_if_not::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_jump_if_not::execute");
|
|
DBUG_PRINT("info", ("destination: %u", m_dest));
|
|
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
|
|
}
|
|
|
|
|
|
int
|
|
sp_instr_jump_if_not::exec_core(THD *thd, uint *nextp)
|
|
{
|
|
Item *it;
|
|
int res;
|
|
|
|
it= sp_prepare_func_item(thd, &m_expr);
|
|
if (! it)
|
|
{
|
|
res= -1;
|
|
}
|
|
else
|
|
{
|
|
res= 0;
|
|
if (! it->val_bool())
|
|
*nextp = m_dest;
|
|
else
|
|
*nextp = m_ip+1;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_jump_if_not::print(String *str)
|
|
{
|
|
/* jump_if_not dest(cont) ... */
|
|
if (str->reserve(2*SP_INSTR_UINT_MAXLEN+14+32)) // Add some for the expr. too
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("jump_if_not "));
|
|
str->qs_append(m_dest);
|
|
str->qs_append('(');
|
|
str->qs_append(m_cont_dest);
|
|
str->qs_append(STRING_WITH_LEN(") "));
|
|
m_expr->print(str, enum_query_type(QT_ORDINARY |
|
|
QT_ITEM_ORIGINAL_FUNC_NULLIF));
|
|
}
|
|
|
|
|
|
uint
|
|
sp_instr_jump_if_not::opt_mark(sp_head *sp, List<sp_instr> *leads)
|
|
{
|
|
sp_instr *i;
|
|
|
|
marked= 1;
|
|
if ((i= sp->get_instr(m_dest)))
|
|
{
|
|
m_dest= i->opt_shortcut_jump(sp, this);
|
|
m_optdest= sp->get_instr(m_dest);
|
|
}
|
|
sp->add_mark_lead(m_dest, leads);
|
|
if ((i= sp->get_instr(m_cont_dest)))
|
|
{
|
|
m_cont_dest= i->opt_shortcut_jump(sp, this);
|
|
m_cont_optdest= sp->get_instr(m_cont_dest);
|
|
}
|
|
sp->add_mark_lead(m_cont_dest, leads);
|
|
return m_ip+1;
|
|
}
|
|
|
|
void
|
|
sp_instr_jump_if_not::opt_move(uint dst, List<sp_instr> *bp)
|
|
{
|
|
/*
|
|
cont. destinations may point backwards after shortcutting jumps
|
|
during the mark phase. If it's still pointing forwards, only
|
|
push this for backpatching if sp_instr_jump::opt_move() will not
|
|
do it (i.e. if the m_dest points backwards).
|
|
*/
|
|
if (m_cont_dest > m_ip)
|
|
{ // Forward
|
|
if (m_dest < m_ip)
|
|
bp->push_back(this);
|
|
}
|
|
else if (m_cont_optdest)
|
|
m_cont_dest= m_cont_optdest->m_ip; // Backward
|
|
/* This will take care of m_dest and m_ip */
|
|
sp_instr_jump::opt_move(dst, bp);
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_freturn class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_freturn::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_freturn::execute");
|
|
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
|
|
}
|
|
|
|
|
|
int
|
|
sp_instr_freturn::exec_core(THD *thd, uint *nextp)
|
|
{
|
|
/*
|
|
RETURN is a "procedure statement" (in terms of the SQL standard).
|
|
That means, Diagnostics Area should be clean before its execution.
|
|
*/
|
|
|
|
Diagnostics_area *da= thd->get_stmt_da();
|
|
da->clear_warning_info(da->warning_info_id());
|
|
|
|
/*
|
|
Change <next instruction pointer>, so that this will be the last
|
|
instruction in the stored function.
|
|
*/
|
|
|
|
*nextp= UINT_MAX;
|
|
|
|
/*
|
|
Evaluate the value of return expression and store it in current runtime
|
|
context.
|
|
|
|
NOTE: It's necessary to evaluate result item right here, because we must
|
|
do it in scope of execution the current context/block.
|
|
*/
|
|
|
|
return thd->spcont->set_return_value(thd, &m_value);
|
|
}
|
|
|
|
void
|
|
sp_instr_freturn::print(String *str)
|
|
{
|
|
/* freturn type expr... */
|
|
if (str->reserve(1024+8+32)) // Add some for the expr. too
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("freturn "));
|
|
str->qs_append((uint)m_type);
|
|
str->qs_append(' ');
|
|
m_value->print(str, enum_query_type(QT_ORDINARY |
|
|
QT_ITEM_ORIGINAL_FUNC_NULLIF));
|
|
}
|
|
|
|
/*
|
|
sp_instr_hpush_jump class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_hpush_jump::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_hpush_jump::execute");
|
|
|
|
int ret= thd->spcont->push_handler(m_handler, m_ip + 1);
|
|
|
|
*nextp= m_dest;
|
|
|
|
DBUG_RETURN(ret);
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_hpush_jump::print(String *str)
|
|
{
|
|
/* hpush_jump dest fsize type */
|
|
if (str->reserve(SP_INSTR_UINT_MAXLEN*2 + 21))
|
|
return;
|
|
|
|
str->qs_append(STRING_WITH_LEN("hpush_jump "));
|
|
str->qs_append(m_dest);
|
|
str->qs_append(' ');
|
|
str->qs_append(m_frame);
|
|
|
|
switch (m_handler->type) {
|
|
case sp_handler::EXIT:
|
|
str->qs_append(STRING_WITH_LEN(" EXIT"));
|
|
break;
|
|
case sp_handler::CONTINUE:
|
|
str->qs_append(STRING_WITH_LEN(" CONTINUE"));
|
|
break;
|
|
default:
|
|
// The handler type must be either CONTINUE or EXIT.
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
|
|
uint
|
|
sp_instr_hpush_jump::opt_mark(sp_head *sp, List<sp_instr> *leads)
|
|
{
|
|
sp_instr *i;
|
|
|
|
marked= 1;
|
|
if ((i= sp->get_instr(m_dest)))
|
|
{
|
|
m_dest= i->opt_shortcut_jump(sp, this);
|
|
m_optdest= sp->get_instr(m_dest);
|
|
}
|
|
sp->add_mark_lead(m_dest, leads);
|
|
|
|
/*
|
|
For continue handlers, all instructions in the scope of the handler
|
|
are possible leads. For example, the instruction after freturn might
|
|
be executed if the freturn triggers the condition handled by the
|
|
continue handler.
|
|
|
|
m_dest marks the start of the handler scope. It's added as a lead
|
|
above, so we start on m_dest+1 here.
|
|
m_opt_hpop is the hpop marking the end of the handler scope.
|
|
*/
|
|
if (m_handler->type == sp_handler::CONTINUE)
|
|
{
|
|
for (uint scope_ip= m_dest+1; scope_ip <= m_opt_hpop; scope_ip++)
|
|
sp->add_mark_lead(scope_ip, leads);
|
|
}
|
|
|
|
return m_ip+1;
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_hpop class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_hpop::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_hpop::execute");
|
|
thd->spcont->pop_handlers(m_count);
|
|
*nextp= m_ip+1;
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
void
|
|
sp_instr_hpop::print(String *str)
|
|
{
|
|
/* hpop count */
|
|
if (str->reserve(SP_INSTR_UINT_MAXLEN+5))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("hpop "));
|
|
str->qs_append(m_count);
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_hreturn class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_hreturn::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_hreturn::execute");
|
|
|
|
uint continue_ip= thd->spcont->exit_handler(thd->get_stmt_da());
|
|
|
|
*nextp= m_dest ? m_dest : continue_ip;
|
|
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_hreturn::print(String *str)
|
|
{
|
|
/* hreturn framesize dest */
|
|
if (str->reserve(SP_INSTR_UINT_MAXLEN*2 + 9))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("hreturn "));
|
|
if (m_dest)
|
|
{
|
|
// NOTE: this is legacy: hreturn instruction for EXIT handler
|
|
// should print out 0 as frame index.
|
|
str->qs_append(STRING_WITH_LEN("0 "));
|
|
str->qs_append(m_dest);
|
|
}
|
|
else
|
|
{
|
|
str->qs_append(m_frame);
|
|
}
|
|
}
|
|
|
|
|
|
uint
|
|
sp_instr_hreturn::opt_mark(sp_head *sp, List<sp_instr> *leads)
|
|
{
|
|
marked= 1;
|
|
|
|
if (m_dest)
|
|
{
|
|
/*
|
|
This is an EXIT handler; next instruction step is in m_dest.
|
|
*/
|
|
return m_dest;
|
|
}
|
|
|
|
/*
|
|
This is a CONTINUE handler; next instruction step will come from
|
|
the handler stack and not from opt_mark.
|
|
*/
|
|
return UINT_MAX;
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_cpush class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_cpush::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_cpush::execute");
|
|
|
|
int ret= thd->spcont->push_cursor(thd, &m_lex_keeper, this);
|
|
|
|
*nextp= m_ip+1;
|
|
|
|
DBUG_RETURN(ret);
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_cpush::print(String *str)
|
|
{
|
|
const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor);
|
|
|
|
/* cpush name@offset */
|
|
uint rsrv= SP_INSTR_UINT_MAXLEN+7;
|
|
|
|
if (cursor_name)
|
|
rsrv+= cursor_name->length;
|
|
if (str->reserve(rsrv))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("cpush "));
|
|
if (cursor_name)
|
|
{
|
|
str->qs_append(cursor_name->str, cursor_name->length);
|
|
str->qs_append('@');
|
|
}
|
|
str->qs_append(m_cursor);
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_cpop class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_cpop::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_cpop::execute");
|
|
thd->spcont->pop_cursors(m_count);
|
|
*nextp= m_ip+1;
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_cpop::print(String *str)
|
|
{
|
|
/* cpop count */
|
|
if (str->reserve(SP_INSTR_UINT_MAXLEN+5))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("cpop "));
|
|
str->qs_append(m_count);
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_copen class functions
|
|
*/
|
|
|
|
/**
|
|
@todo
|
|
Assert that we either have an error or a cursor
|
|
*/
|
|
|
|
int
|
|
sp_instr_copen::execute(THD *thd, uint *nextp)
|
|
{
|
|
/*
|
|
We don't store a pointer to the cursor in the instruction to be
|
|
able to reuse the same instruction among different threads in future.
|
|
*/
|
|
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
|
|
int res;
|
|
DBUG_ENTER("sp_instr_copen::execute");
|
|
|
|
if (! c)
|
|
res= -1;
|
|
else
|
|
{
|
|
sp_lex_keeper *lex_keeper= c->get_lex_keeper();
|
|
Query_arena *old_arena= thd->stmt_arena;
|
|
|
|
/*
|
|
Get the Query_arena from the cpush instruction, which contains
|
|
the free_list of the query, so new items (if any) are stored in
|
|
the right free_list, and we can cleanup after each open.
|
|
*/
|
|
thd->stmt_arena= c->get_instr();
|
|
res= lex_keeper->reset_lex_and_exec_core(thd, nextp, FALSE, this);
|
|
/* Cleanup the query's items */
|
|
if (thd->stmt_arena->free_list)
|
|
cleanup_items(thd->stmt_arena->free_list);
|
|
thd->stmt_arena= old_arena;
|
|
/* TODO: Assert here that we either have an error or a cursor */
|
|
}
|
|
DBUG_RETURN(res);
|
|
}
|
|
|
|
|
|
int
|
|
sp_instr_copen::exec_core(THD *thd, uint *nextp)
|
|
{
|
|
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
|
|
int res= c->open(thd);
|
|
*nextp= m_ip+1;
|
|
return res;
|
|
}
|
|
|
|
void
|
|
sp_instr_copen::print(String *str)
|
|
{
|
|
const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor);
|
|
|
|
/* copen name@offset */
|
|
uint rsrv= SP_INSTR_UINT_MAXLEN+7;
|
|
|
|
if (cursor_name)
|
|
rsrv+= cursor_name->length;
|
|
if (str->reserve(rsrv))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("copen "));
|
|
if (cursor_name)
|
|
{
|
|
str->qs_append(cursor_name->str, cursor_name->length);
|
|
str->qs_append('@');
|
|
}
|
|
str->qs_append(m_cursor);
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_cclose class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_cclose::execute(THD *thd, uint *nextp)
|
|
{
|
|
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
|
|
int res;
|
|
DBUG_ENTER("sp_instr_cclose::execute");
|
|
|
|
if (! c)
|
|
res= -1;
|
|
else
|
|
res= c->close(thd);
|
|
*nextp= m_ip+1;
|
|
DBUG_RETURN(res);
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_cclose::print(String *str)
|
|
{
|
|
const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor);
|
|
|
|
/* cclose name@offset */
|
|
uint rsrv= SP_INSTR_UINT_MAXLEN+8;
|
|
|
|
if (cursor_name)
|
|
rsrv+= cursor_name->length;
|
|
if (str->reserve(rsrv))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("cclose "));
|
|
if (cursor_name)
|
|
{
|
|
str->qs_append(cursor_name->str, cursor_name->length);
|
|
str->qs_append('@');
|
|
}
|
|
str->qs_append(m_cursor);
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_cfetch class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_cfetch::execute(THD *thd, uint *nextp)
|
|
{
|
|
sp_cursor *c= thd->spcont->get_cursor(m_cursor);
|
|
int res;
|
|
Query_arena backup_arena;
|
|
DBUG_ENTER("sp_instr_cfetch::execute");
|
|
|
|
res= c ? c->fetch(thd, &m_varlist) : -1;
|
|
|
|
*nextp= m_ip+1;
|
|
DBUG_RETURN(res);
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_cfetch::print(String *str)
|
|
{
|
|
List_iterator_fast<sp_variable> li(m_varlist);
|
|
sp_variable *pv;
|
|
const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor);
|
|
|
|
/* cfetch name@offset vars... */
|
|
uint rsrv= SP_INSTR_UINT_MAXLEN+8;
|
|
|
|
if (cursor_name)
|
|
rsrv+= cursor_name->length;
|
|
if (str->reserve(rsrv))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("cfetch "));
|
|
if (cursor_name)
|
|
{
|
|
str->qs_append(cursor_name->str, cursor_name->length);
|
|
str->qs_append('@');
|
|
}
|
|
str->qs_append(m_cursor);
|
|
while ((pv= li++))
|
|
{
|
|
if (str->reserve(pv->name.length+SP_INSTR_UINT_MAXLEN+2))
|
|
return;
|
|
str->qs_append(' ');
|
|
str->qs_append(pv->name.str, pv->name.length);
|
|
str->qs_append('@');
|
|
str->qs_append(pv->offset);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
sp_instr_error class functions
|
|
*/
|
|
|
|
int
|
|
sp_instr_error::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_error::execute");
|
|
|
|
my_message(m_errcode, ER_THD(thd, m_errcode), MYF(0));
|
|
*nextp= m_ip+1;
|
|
DBUG_RETURN(-1);
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_error::print(String *str)
|
|
{
|
|
/* error code */
|
|
if (str->reserve(SP_INSTR_UINT_MAXLEN+6))
|
|
return;
|
|
str->qs_append(STRING_WITH_LEN("error "));
|
|
str->qs_append(m_errcode);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
sp_instr_set_case_expr class implementation
|
|
**************************************************************************/
|
|
|
|
int
|
|
sp_instr_set_case_expr::execute(THD *thd, uint *nextp)
|
|
{
|
|
DBUG_ENTER("sp_instr_set_case_expr::execute");
|
|
|
|
DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this));
|
|
}
|
|
|
|
|
|
int
|
|
sp_instr_set_case_expr::exec_core(THD *thd, uint *nextp)
|
|
{
|
|
int res= thd->spcont->set_case_expr(thd, m_case_expr_id, &m_case_expr);
|
|
|
|
if (res && !thd->spcont->get_case_expr(m_case_expr_id))
|
|
{
|
|
/*
|
|
Failed to evaluate the value, the case expression is still not
|
|
initialized. Set to NULL so we can continue.
|
|
*/
|
|
|
|
Item *null_item= new (thd->mem_root) Item_null(thd);
|
|
|
|
if (!null_item ||
|
|
thd->spcont->set_case_expr(thd, m_case_expr_id, &null_item))
|
|
{
|
|
/* If this also failed, we have to abort. */
|
|
my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
|
|
}
|
|
}
|
|
else
|
|
*nextp= m_ip+1;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
void
|
|
sp_instr_set_case_expr::print(String *str)
|
|
{
|
|
/* set_case_expr (cont) id ... */
|
|
str->reserve(2*SP_INSTR_UINT_MAXLEN+18+32); // Add some extra for expr too
|
|
str->qs_append(STRING_WITH_LEN("set_case_expr ("));
|
|
str->qs_append(m_cont_dest);
|
|
str->qs_append(STRING_WITH_LEN(") "));
|
|
str->qs_append(m_case_expr_id);
|
|
str->qs_append(' ');
|
|
m_case_expr->print(str, enum_query_type(QT_ORDINARY |
|
|
QT_ITEM_ORIGINAL_FUNC_NULLIF));
|
|
}
|
|
|
|
uint
|
|
sp_instr_set_case_expr::opt_mark(sp_head *sp, List<sp_instr> *leads)
|
|
{
|
|
sp_instr *i;
|
|
|
|
marked= 1;
|
|
if ((i= sp->get_instr(m_cont_dest)))
|
|
{
|
|
m_cont_dest= i->opt_shortcut_jump(sp, this);
|
|
m_cont_optdest= sp->get_instr(m_cont_dest);
|
|
}
|
|
sp->add_mark_lead(m_cont_dest, leads);
|
|
return m_ip+1;
|
|
}
|
|
|
|
void
|
|
sp_instr_set_case_expr::opt_move(uint dst, List<sp_instr> *bp)
|
|
{
|
|
if (m_cont_dest > m_ip)
|
|
bp->push_back(this); // Forward
|
|
else if (m_cont_optdest)
|
|
m_cont_dest= m_cont_optdest->m_ip; // Backward
|
|
m_ip= dst;
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
|
/*
|
|
Structure that represent all instances of one table
|
|
in optimized multi-set of tables used by routine.
|
|
*/
|
|
|
|
typedef struct st_sp_table
|
|
{
|
|
/*
|
|
Multi-set key:
|
|
db_name\0table_name\0alias\0 - for normal tables
|
|
db_name\0table_name\0 - for temporary tables
|
|
*/
|
|
LEX_STRING qname;
|
|
uint db_length, table_name_length;
|
|
bool temp; /* true if corresponds to a temporary table */
|
|
thr_lock_type lock_type; /* lock type used for prelocking */
|
|
uint lock_count;
|
|
uint query_lock_count;
|
|
uint8 trg_event_map;
|
|
} SP_TABLE;
|
|
|
|
|
|
uchar *sp_table_key(const uchar *ptr, size_t *plen, my_bool first)
|
|
{
|
|
SP_TABLE *tab= (SP_TABLE *)ptr;
|
|
*plen= tab->qname.length;
|
|
return (uchar *)tab->qname.str;
|
|
}
|
|
|
|
|
|
/**
|
|
Merge the list of tables used by some query into the multi-set of
|
|
tables used by routine.
|
|
|
|
@param thd thread context
|
|
@param table table list
|
|
@param lex_for_tmp_check LEX of the query for which we are merging
|
|
table list.
|
|
|
|
@note
|
|
This method will use LEX provided to check whenever we are creating
|
|
temporary table and mark it as such in target multi-set.
|
|
|
|
@retval
|
|
TRUE Success
|
|
@retval
|
|
FALSE Error
|
|
*/
|
|
|
|
bool
|
|
sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check)
|
|
{
|
|
SP_TABLE *tab;
|
|
|
|
if (lex_for_tmp_check->sql_command == SQLCOM_DROP_TABLE &&
|
|
lex_for_tmp_check->tmp_table())
|
|
return TRUE;
|
|
|
|
for (uint i= 0 ; i < m_sptabs.records ; i++)
|
|
{
|
|
tab= (SP_TABLE*) my_hash_element(&m_sptabs, i);
|
|
tab->query_lock_count= 0;
|
|
}
|
|
|
|
for (; table ; table= table->next_global)
|
|
if (!table->derived && !table->schema_table)
|
|
{
|
|
/*
|
|
Structure of key for the multi-set is "db\0table\0alias\0".
|
|
Since "alias" part can have arbitrary length we use String
|
|
object to construct the key. By default String will use
|
|
buffer allocated on stack with NAME_LEN bytes reserved for
|
|
alias, since in most cases it is going to be smaller than
|
|
NAME_LEN bytes.
|
|
*/
|
|
char tname_buff[(SAFE_NAME_LEN + 1) * 3];
|
|
String tname(tname_buff, sizeof(tname_buff), &my_charset_bin);
|
|
uint temp_table_key_length;
|
|
|
|
tname.length(0);
|
|
tname.append(table->db, table->db_length);
|
|
tname.append('\0');
|
|
tname.append(table->table_name, table->table_name_length);
|
|
tname.append('\0');
|
|
temp_table_key_length= tname.length();
|
|
tname.append(table->alias);
|
|
tname.append('\0');
|
|
|
|
/*
|
|
Upgrade the lock type because this table list will be used
|
|
only in pre-locked mode, in which DELAYED inserts are always
|
|
converted to normal inserts.
|
|
*/
|
|
if (table->lock_type == TL_WRITE_DELAYED)
|
|
table->lock_type= TL_WRITE;
|
|
|
|
/*
|
|
We ignore alias when we check if table was already marked as temporary
|
|
(and therefore should not be prelocked). Otherwise we will erroneously
|
|
treat table with same name but with different alias as non-temporary.
|
|
*/
|
|
if ((tab= (SP_TABLE*) my_hash_search(&m_sptabs, (uchar *)tname.ptr(),
|
|
tname.length())) ||
|
|
((tab= (SP_TABLE*) my_hash_search(&m_sptabs, (uchar *)tname.ptr(),
|
|
temp_table_key_length)) &&
|
|
tab->temp))
|
|
{
|
|
if (tab->lock_type < table->lock_type)
|
|
tab->lock_type= table->lock_type; // Use the table with the highest lock type
|
|
tab->query_lock_count++;
|
|
if (tab->query_lock_count > tab->lock_count)
|
|
tab->lock_count++;
|
|
tab->trg_event_map|= table->trg_event_map;
|
|
}
|
|
else
|
|
{
|
|
if (!(tab= (SP_TABLE *)thd->calloc(sizeof(SP_TABLE))))
|
|
return FALSE;
|
|
if (lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE &&
|
|
lex_for_tmp_check->query_tables == table &&
|
|
lex_for_tmp_check->tmp_table())
|
|
{
|
|
tab->temp= TRUE;
|
|
tab->qname.length= temp_table_key_length;
|
|
}
|
|
else
|
|
tab->qname.length= tname.length();
|
|
tab->qname.str= (char*) thd->memdup(tname.ptr(), tab->qname.length);
|
|
if (!tab->qname.str)
|
|
return FALSE;
|
|
tab->table_name_length= table->table_name_length;
|
|
tab->db_length= table->db_length;
|
|
tab->lock_type= table->lock_type;
|
|
tab->lock_count= tab->query_lock_count= 1;
|
|
tab->trg_event_map= table->trg_event_map;
|
|
if (my_hash_insert(&m_sptabs, (uchar *)tab))
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
Add tables used by routine to the table list.
|
|
|
|
Converts multi-set of tables used by this routine to table list and adds
|
|
this list to the end of table list specified by 'query_tables_last_ptr'.
|
|
|
|
Elements of list will be allocated in PS memroot, so this list will be
|
|
persistent between PS executions.
|
|
|
|
@param[in] thd Thread context
|
|
@param[in,out] query_tables_last_ptr Pointer to the next_global member of
|
|
last element of the list where tables
|
|
will be added (or to its root).
|
|
@param[in] belong_to_view Uppermost view which uses this routine,
|
|
0 if none.
|
|
|
|
@retval
|
|
TRUE if some elements were added
|
|
@retval
|
|
FALSE otherwise.
|
|
*/
|
|
|
|
bool
|
|
sp_head::add_used_tables_to_table_list(THD *thd,
|
|
TABLE_LIST ***query_tables_last_ptr,
|
|
TABLE_LIST *belong_to_view)
|
|
{
|
|
uint i;
|
|
Query_arena *arena, backup;
|
|
bool result= FALSE;
|
|
DBUG_ENTER("sp_head::add_used_tables_to_table_list");
|
|
|
|
/*
|
|
Use persistent arena for table list allocation to be PS/SP friendly.
|
|
Note that we also have to copy database/table names and alias to PS/SP
|
|
memory since current instance of sp_head object can pass away before
|
|
next execution of PS/SP for which tables are added to prelocking list.
|
|
This will be fixed by introducing of proper invalidation mechanism
|
|
once new TDC is ready.
|
|
*/
|
|
arena= thd->activate_stmt_arena_if_needed(&backup);
|
|
|
|
for (i=0 ; i < m_sptabs.records ; i++)
|
|
{
|
|
char *tab_buff, *key_buff;
|
|
TABLE_LIST *table;
|
|
SP_TABLE *stab= (SP_TABLE*) my_hash_element(&m_sptabs, i);
|
|
if (stab->temp)
|
|
continue;
|
|
|
|
if (!(tab_buff= (char *)thd->alloc(ALIGN_SIZE(sizeof(TABLE_LIST)) *
|
|
stab->lock_count)) ||
|
|
!(key_buff= (char*)thd->memdup(stab->qname.str,
|
|
stab->qname.length)))
|
|
DBUG_RETURN(FALSE);
|
|
|
|
for (uint j= 0; j < stab->lock_count; j++)
|
|
{
|
|
table= (TABLE_LIST *)tab_buff;
|
|
table->init_one_table_for_prelocking(key_buff, stab->db_length,
|
|
key_buff + stab->db_length + 1, stab->table_name_length,
|
|
key_buff + stab->db_length + stab->table_name_length + 2,
|
|
stab->lock_type, true, belong_to_view, stab->trg_event_map,
|
|
query_tables_last_ptr);
|
|
|
|
tab_buff+= ALIGN_SIZE(sizeof(TABLE_LIST));
|
|
result= TRUE;
|
|
}
|
|
}
|
|
|
|
if (arena)
|
|
thd->restore_active_arena(arena, &backup);
|
|
|
|
DBUG_RETURN(result);
|
|
}
|
|
|
|
|
|
/**
|
|
Simple function for adding an explicitly named (systems) table to
|
|
the global table list, e.g. "mysql", "proc".
|
|
*/
|
|
|
|
TABLE_LIST *
|
|
sp_add_to_query_tables(THD *thd, LEX *lex,
|
|
const char *db, const char *name,
|
|
thr_lock_type locktype,
|
|
enum_mdl_type mdl_type)
|
|
{
|
|
TABLE_LIST *table;
|
|
|
|
if (!(table= (TABLE_LIST *)thd->calloc(sizeof(TABLE_LIST))))
|
|
return NULL;
|
|
table->db_length= strlen(db);
|
|
table->db= thd->strmake(db, table->db_length);
|
|
table->table_name_length= strlen(name);
|
|
table->table_name= thd->strmake(name, table->table_name_length);
|
|
table->alias= thd->strdup(name);
|
|
table->lock_type= locktype;
|
|
table->select_lex= lex->current_select;
|
|
table->cacheable_table= 1;
|
|
table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
|
|
mdl_type, MDL_TRANSACTION);
|
|
|
|
lex->add_to_query_tables(table);
|
|
return table;
|
|
}
|