forked from tempesta-tech/mariadb
-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Aleksey Midenkov edited this page Aug 22, 2018
·
3 revisions
class Table_locker
{
THD *thd;
TABLE &table;
thr_lock_type saved_type;
MYSQL_LOCK *saved_lock;
enum_locked_tables_mode saved_mode;
TABLE_LIST **saved_query_tables_own_last;
TABLE_LIST table_list;
bool locked;
public:
Table_locker(THD *_thd, TABLE &_table, thr_lock_type lock_type) :
thd(_thd),
table(_table),
saved_type(table.reginfo.lock_type),
saved_lock(_thd->lock),
saved_mode(_thd->locked_tables_mode),
saved_query_tables_own_last(_thd->lex->query_tables_own_last),
table_list(&_table, lock_type),
locked(false)
{
table.reginfo.lock_type= lock_type;
}
bool lock()
{
DBUG_ASSERT(table.file);
// FIXME: check consistency with table.reginfo.lock_type
if (table.file->get_lock_type() != F_UNLCK
|| table.s->tmp_table)
{
return false;
}
thd->lock= NULL;
thd->locked_tables_mode= LTM_NONE;
thd->lex->query_tables_own_last= NULL;
bool res= lock_tables(thd, &table_list, 1, 0);
locked= !res;
return res;
}
~Table_locker()
{
if (locked)
mysql_unlock_tables(thd, thd->lock);
table.reginfo.lock_type= saved_type;
thd->lock= saved_lock;
thd->locked_tables_mode= saved_mode;
thd->lex->query_tables_own_last= saved_query_tables_own_last;
if (locked && !thd->in_sub_stmt)
{
ha_commit_trans(thd, false);
ha_commit_trans(thd, true);
}
}
};
// optionally switch to statement arena
LEX *old_lex= thd->lex;
LEX lex; // or allocate as (LEX*) new(thd->mem_root) st_lex_local
Parser_state parser_state;
parser_state.init(thd, query_ptr, query_length);
init_lex_with_single_table(thd, table, &lex); // + end_lex_with_single_table()
/* or */
{
thd->lex= &lex;
lex_start(thd); // + lex_end()
}
// optionally
lex.select_lex.select_number= ++thd->select_number;
// optionally
thd->push_internal_handler(&error_handler);
error= parse_sql(thd, &parser_state, NULL) || /* optionally */ thd->is_error();
// optionally
thd->pop_internal_handler();
// optionally
lex.number_of_selects=
(thd->select_number - lex.select_lex.select_number) + 1;
st_select_lex_unit *With_element::clone_parsed_spec(THD *thd,
TABLE_LIST *with_table)
{
LEX *lex;
st_select_lex_unit *res= NULL;
Query_arena backup;
Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup);
if (!(lex= (LEX*) new(thd->mem_root) st_lex_local))
{
if (arena)
thd->restore_active_arena(arena, &backup);
return res;
}
LEX *old_lex= thd->lex;
thd->lex= lex;
bool parse_status= false;
Parser_state parser_state;
TABLE_LIST *spec_tables;
TABLE_LIST *spec_tables_tail;
st_select_lex *with_select;
if (parser_state.init(thd, unparsed_spec.str, unparsed_spec.length))
goto err;
lex_start(thd);
with_select= &lex->select_lex;
with_select->select_number= ++thd->select_number;
parse_status= parse_sql(thd, &parser_state, 0);
if (parse_status)
goto err;
table->view= lex= thd->lex= (LEX*) new(thd->mem_root) st_lex_local;
if (!table->view)
{
result= true;
goto end;
}
{
char old_db_buf[SAFE_NAME_LEN+1];
LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) };
bool dbchanged;
Parser_state parser_state;
if (parser_state.init(thd, table->select_stmt.str,
table->select_stmt.length))
goto err;
/*
Use view db name as thread default database, in order to ensure
that the view is parsed and prepared correctly.
*/
if ((result= mysql_opt_change_db(thd, &table->view_db, &old_db, 1,
&dbchanged)))
goto end;
lex_start(thd);
view_select= &lex->select_lex;
view_select->select_number= ++thd->select_number;
sql_mode_t saved_mode= thd->variables.sql_mode;
/* switch off modes which can prevent normal parsing of VIEW
... */
thd->variables.sql_mode&= ~(MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
MODE_IGNORE_SPACE | MODE_NO_BACKSLASH_ESCAPES);
/* Parse the query. */
parse_status= parse_sql(thd, & parser_state, table->view_creation_ctx);
lex->number_of_selects=
(thd->select_number - view_select->select_number) + 1;
/* Restore environment. */
if ((old_lex->sql_command == SQLCOM_SHOW_FIELDS) ||
(old_lex->sql_command == SQLCOM_SHOW_CREATE))
lex->sql_command= old_lex->sql_command;
thd->variables.sql_mode= saved_mode;
if (dbchanged && mysql_change_db(thd, &old_db, TRUE))
goto err;
}
LEX *old_lex= thd->lex, lex;
sp_rcontext *save_spcont= thd->spcont;
sql_mode_t save_sql_mode= thd->variables.sql_mode;
thd->lex= &lex;
save_db.str= thd->db;
save_db.length= thd->db_length;
thd->reset_db((char*) db, strlen(db));
while ((trg_create_str= it++))
{
sp_head *sp;
sql_mode_t sql_mode;
LEX_STRING *trg_definer;
Trigger_creation_ctx *creation_ctx;
/*
It is old file format then sql_mode may not be filled in.
We use one mode (current) for all triggers, because we have not
information about mode in old format.
*/
sql_mode= ((trg_sql_mode= itm++) ? *trg_sql_mode :
(ulonglong) global_system_variables.sql_mode);
trg_create_time= it_create_times++; // May be NULL if old file
trg_definer= it_definer++; // May be NULL if old file
thd->variables.sql_mode= sql_mode;
Parser_state parser_state;
if (parser_state.init(thd, trg_create_str->str, trg_create_str->length))
goto err_with_lex_cleanup;
if (!trigger_list->client_cs_names.is_empty())
creation_ctx= Trigger_creation_ctx::create(thd,
db,
table_name,
it_client_cs_name++,
it_connection_cl_name++,
it_db_cl_name++);
else
{
/* Old file with not stored character sets. Use current */
creation_ctx= new
Trigger_creation_ctx(thd->variables.character_set_client,
thd->variables.collation_connection,
thd->variables.collation_database);
}
lex_start(thd);
thd->spcont= NULL;
/* The following is for catching parse errors */
lex.trg_chistics.event= TRG_EVENT_MAX;
lex.trg_chistics.action_time= TRG_ACTION_MAX;
Deprecated_trigger_syntax_handler error_handler;
thd->push_internal_handler(&error_handler);
bool parse_error= parse_sql(thd, & parser_state, creation_ctx);
thd->pop_internal_handler();
DBUG_ASSERT(!parse_error || lex.sphead == 0);
/*
Not strictly necessary to invoke this method here, since we know
that we've parsed CREATE TRIGGER and not an
UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
maintain the invariant that this method is called for each
distinct statement, in case its logic is extended with other
types of analyses in future.
*/
lex.set_trg_event_type_for_tables();
thd->reset_for_next_command();
/*
MySQL parser currently assumes that current database is either
present in THD or all names in all statements are fully specified.
And yet not fully specified names inside stored programs must be
be supported, even if the current database is not set:
CREATE PROCEDURE db1.p1() BEGIN CREATE TABLE t1; END//
-- in this example t1 should be always created in db1 and the statement
must parse even if there is no current database.
To support this feature and still address the parser limitation,
we need to set the current database here.
We don't have to call mysql_change_db, since the checks performed
in it are unnecessary for the purpose of parsing, and
mysql_change_db will be invoked anyway later, to activate the
procedure database before it's executed.
*/
thd->set_db(dbname.str, dbname.length);
lex_start(thd);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (event_sctx.change_security_context(thd,
&definer_user, &definer_host,
&dbname, &save_sctx))
{
sql_print_error("Event Scheduler: "
"[%s].[%s.%s] execution failed, "
"failed to authenticate the user.",
definer.str, dbname.str, name.str);
goto end;
}
#endif
if (check_access(thd, EVENT_ACL, dbname.str, NULL, NULL, 0, 0))
{
/*
This aspect of behavior is defined in the worklog,
and this is how triggers work too: if TRIGGER
privilege is revoked from trigger definer,
triggers are not executed.
*/
sql_print_error("Event Scheduler: "
"[%s].[%s.%s] execution failed, "
"user no longer has EVENT privilege.",
definer.str, dbname.str, name.str);
goto end;
}
if (construct_sp_sql(thd, &sp_sql))
goto end;
/*
Set up global thread attributes to reflect the properties of
this Event. We can simply reset these instead of usual
backup/restore employed in stored programs since we know that
this is a top level statement and the worker thread is
allocated exclusively to execute this event.
*/
thd->variables.sql_mode= sql_mode;
thd->variables.time_zone= time_zone;
thd->set_query(sp_sql.c_ptr_safe(), sp_sql.length());
{
Parser_state parser_state;
if (parser_state.init(thd, thd->query(), thd->query_length()))
goto end;
if (parse_sql(thd, & parser_state, creation_ctx))
{
sql_print_error("Event Scheduler: "
"%serror during compilation of %s.%s",
thd->is_fatal_error ? "fatal " : "",
(const char *) dbname.str, (const char *) name.str);
goto end;
}
}
bool
Execute_sql_statement::execute_server_code(THD *thd)
{
PSI_statement_locker *parent_locker;
bool error;
if (alloc_query(thd, m_sql_text.str, m_sql_text.length))
return TRUE;
Parser_state parser_state;
if (parser_state.init(thd, thd->query(), thd->query_length()))
return TRUE;
parser_state.m_lip.multi_statements= FALSE;
lex_start(thd);
error= parse_sql(thd, &parser_state, NULL) || thd->is_error();
if (error)
goto end;
thd->lex->set_trg_event_type_for_tables();
parent_locker= thd->m_statement_psi;
thd->m_statement_psi= NULL;
error= mysql_execute_command(thd);
thd->m_statement_psi= parent_locker;
/* report error issued during command execution */
if (error == 0 && thd->spcont == NULL)
general_log_write(thd, COM_STMT_EXECUTE,
thd->query(), thd->query_length());
end:
thd->lex->restore_set_statement_var();
lex_end(thd->lex);
return error;
}
/**************************************************************************
Common parts of mysql_[sql]_stmt_prepare, mysql_[sql]_stmt_execute.
Essentially, these functions do all the magic of preparing/executing
a statement, leaving network communication, input data handling and
global THD state management to the caller.
***************************************************************************/
/**
Parse statement text, validate the statement, and prepare it for execution.
You should not change global THD state in this function, if at all
possible: it may be called from any context, e.g. when executing
a COM_* command, and SQLCOM_* command, or a stored procedure.
@param packet statement text
@param packet_len
@note
Precondition:
The caller must ensure that thd->change_list and thd->free_list
is empty: this function will not back them up but will free
in the end of its execution.
@note
Postcondition:
thd->mem_root contains unused memory allocated during validation.
*/
bool Prepared_statement::prepare(const char *packet, uint packet_len)
{
bool error;
Statement stmt_backup;
Query_arena *old_stmt_arena;
DBUG_ENTER("Prepared_statement::prepare");
DBUG_ASSERT(m_sql_mode == thd->variables.sql_mode);
/*
If this is an SQLCOM_PREPARE, we also increase Com_prepare_sql.
However, it seems handy if com_stmt_prepare is increased always,
no matter what kind of prepare is processed.
*/
status_var_increment(thd->status_var.com_stmt_prepare);
if (! (lex= new (mem_root) st_lex_local))
DBUG_RETURN(TRUE);
if (set_db(thd->db, thd->db_length))
DBUG_RETURN(TRUE);
/*
alloc_query() uses thd->mem_root && thd->query, so we should call
both of backup_statement() and backup_query_arena() here.
*/
thd->set_n_backup_statement(this, &stmt_backup);
thd->set_n_backup_active_arena(this, &stmt_backup);
if (alloc_query(thd, packet, packet_len))
{
thd->restore_backup_statement(this, &stmt_backup);
thd->restore_active_arena(this, &stmt_backup);
DBUG_RETURN(TRUE);
}
old_stmt_arena= thd->stmt_arena;
thd->stmt_arena= this;
Parser_state parser_state;
if (parser_state.init(thd, thd->query(), thd->query_length()))
{
thd->restore_backup_statement(this, &stmt_backup);
thd->restore_active_arena(this, &stmt_backup);
thd->stmt_arena= old_stmt_arena;
DBUG_RETURN(TRUE);
}
parser_state.m_lip.stmt_prepare_mode= TRUE;
parser_state.m_lip.multi_statements= FALSE;
lex_start(thd);
lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_PREPARE;
error= parse_sql(thd, & parser_state, NULL) ||
thd->is_error() ||
init_param_array(this);
lex->set_trg_event_type_for_tables();
/*
While doing context analysis of the query (in check_prepared_statement)
we allocate a lot of additional memory: for open tables, JOINs, derived
tables, etc. Let's save a snapshot of current parse tree to the
statement and restore original THD. In cases when some tree
transformation can be reused on execute, we set again thd->mem_root from
stmt->mem_root (see setup_wild for one place where we do that).
*/
thd->restore_active_arena(this, &stmt_backup);
/*
If called from a stored procedure, ensure that we won't rollback
external changes when cleaning up after validation.
*/
DBUG_ASSERT(thd->change_list.is_empty());
/*
Marker used to release metadata locks acquired while the prepared
statement is being checked.
*/
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
/*
The only case where we should have items in the thd->free_list is
after stmt->set_params_from_vars(), which may in some cases create
Item_null objects.
*/
if (error == 0)
error= check_prepared_statement(this);
if (error)
{
/*
let the following code know we're not in PS anymore,
the won't be any EXECUTE, so we need a full cleanup
*/
lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_PREPARE;
}
/* The order is important */
lex->unit.cleanup();
/* No need to commit statement transaction, it's not started. */
DBUG_ASSERT(thd->transaction.stmt.is_empty());
close_thread_tables(thd);
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
/*
Transaction rollback was requested since MDL deadlock was discovered
while trying to open tables. Rollback transaction in all storage
engines including binary log and release all locks.
Once dynamic SQL is allowed as substatements the below if-statement
has to be adjusted to not do rollback in substatement.
*/
DBUG_ASSERT(! thd->in_sub_stmt);
if (thd->transaction_rollback_request)
{
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
select_number_after_prepare= thd->select_number;
/* Preserve CHANGE MASTER attributes */
lex_end_stage1(lex);
cleanup_stmt();
thd->restore_backup_statement(this, &stmt_backup);
thd->stmt_arena= old_stmt_arena;
if (error == 0)
{
setup_set_params();
lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_PREPARE;
state= Query_arena::STMT_PREPARED;
flags&= ~ (uint) IS_IN_USE;
/*
Log COM_EXECUTE to the general log. Note, that in case of SQL
prepared statements this causes two records to be output:
Query PREPARE stmt from @user_variable
Prepare <statement SQL text>
This is considered user-friendly, since in the
second log entry we output the actual statement text.
Do not print anything if this is an SQL prepared statement and
we're inside a stored procedure (also called Dynamic SQL) --
sub-statements inside stored procedures are not logged into
the general log.
*/
if (thd->spcont == NULL)
general_log_write(thd, COM_STMT_PREPARE, query(), query_length());
}
DBUG_RETURN(error);
}
int TABLE_SHARE::init_from_sql_statement_string(THD *thd, bool write,
const char *sql, size_t sql_length)
{
sql_mode_t saved_mode= thd->variables.sql_mode;
CHARSET_INFO *old_cs= thd->variables.character_set_client;
Parser_state parser_state;
bool error;
char *sql_copy;
handler *file;
LEX *old_lex;
Query_arena *arena, backup;
LEX tmp_lex;
KEY *unused1;
uint unused2;
handlerton *hton= plugin_hton(db_plugin);
LEX_CUSTRING frm= {0,0};
LEX_STRING db_backup= { thd->db, thd->db_length };
DBUG_ENTER("TABLE_SHARE::init_from_sql_statement_string");
/*
Ouch. Parser may *change* the string it's working on.
Currently (2013-02-26) it is used to permanently disable
conditional comments.
Anyway, let's copy the caller's string...
*/
if (!(sql_copy= thd->strmake(sql, sql_length)))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
if (parser_state.init(thd, sql_copy, sql_length))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
thd->variables.sql_mode= MODE_NO_ENGINE_SUBSTITUTION | MODE_NO_DIR_IN_CREATE;
thd->variables.character_set_client= system_charset_info;
tmp_disable_binlog(thd);
old_lex= thd->lex;
thd->lex= &tmp_lex;
arena= thd->stmt_arena;
if (arena->is_conventional())
arena= 0;
else
thd->set_n_backup_active_arena(arena, &backup);
thd->reset_db(db.str, db.length);
lex_start(thd);
if ((error= parse_sql(thd, & parser_state, NULL) ||
sql_unusable_for_discovery(thd, hton, sql_copy)))
goto ret;
thd->lex->create_info.db_type= hton;
if (tabledef_version.str)
thd->lex->create_info.tabledef_version= tabledef_version;
promote_first_timestamp_column(&thd->lex->alter_info.create_list);
file= mysql_create_frm_image(thd, db.str, table_name.str,
&thd->lex->create_info, &thd->lex->alter_info,
C_ORDINARY_CREATE, &unused1, &unused2, &frm);
error|= file == 0;
delete file;
if (frm.str)
{
option_list= 0; // cleanup existing options ...
option_struct= 0; // ... if it's an assisted discovery
error= init_from_binary_frm_image(thd, write, frm.str, frm.length);
}
static Virtual_column_info *
unpack_vcol_info_from_frm(THD *thd, MEM_ROOT *mem_root, TABLE *table,
String *expr_str, Virtual_column_info **vcol_ptr,
bool *error_reported)
{
Create_field vcol_storage; // placeholder for vcol_info
Parser_state parser_state;
Virtual_column_info *vcol= *vcol_ptr, *vcol_info= 0;
LEX *old_lex= thd->lex;
LEX lex;
bool error;
DBUG_ENTER("unpack_vcol_info_from_frm");
DBUG_ASSERT(vcol->expr == NULL);
if (parser_state.init(thd, expr_str->c_ptr_safe(), expr_str->length()))
goto end;
if (init_lex_with_single_table(thd, table, &lex))
goto end;
lex.parse_vcol_expr= true;
lex.last_field= &vcol_storage;
error= parse_sql(thd, &parser_state, NULL);
if (error)
goto end;
vcol_storage.vcol_info->stored_in_db= vcol->stored_in_db;
vcol_storage.vcol_info->name= vcol->name;
vcol_storage.vcol_info->utf8= vcol->utf8;
if (!fix_and_check_vcol_expr(thd, table, vcol_storage.vcol_info))
{
*vcol_ptr= vcol_info= vcol_storage.vcol_info; // Expression ok
DBUG_ASSERT(vcol_info->expr);
goto end;
}
*error_reported= TRUE;
end:
end_lex_with_single_table(thd, table, old_lex);
DBUG_RETURN(vcol_info);
}
static sp_head *sp_compile(THD *thd, String *defstr, sql_mode_t sql_mode,
Stored_program_creation_ctx *creation_ctx)
{
sp_head *sp;
sql_mode_t old_sql_mode= thd->variables.sql_mode;
ha_rows old_select_limit= thd->variables.select_limit;
sp_rcontext *old_spcont= thd->spcont;
Silence_deprecated_warning warning_handler;
Parser_state parser_state;
thd->variables.sql_mode= sql_mode;
thd->variables.select_limit= HA_POS_ERROR;
if (parser_state.init(thd, defstr->c_ptr_safe(), defstr->length()))
{
thd->variables.sql_mode= old_sql_mode;
thd->variables.select_limit= old_select_limit;
return NULL;
}
lex_start(thd);
thd->push_internal_handler(&warning_handler);
thd->spcont= 0;
if (parse_sql(thd, & parser_state, creation_ctx) || thd->lex == NULL)
{
sp= thd->lex->sphead;
delete sp;
sp= 0;
}
else
{
sp= thd->lex->sphead;
}
thd->pop_internal_handler();
thd->spcont= old_spcont;
thd->variables.sql_mode= old_sql_mode;
thd->variables.select_limit= old_select_limit;
return sp;
}
static int prepare_for_fill(TABLE_LIST *tables)
{
/*
Add our thd to the list, for it to be visible in SHOW PROCESSLIST.
But don't generate thread_id every time - use the saved value
(every increment of global thread_id counts as a new connection
in SHOW STATUS and we want to avoid skewing the statistics)
*/
thd->variables.pseudo_thread_id= thd->thread_id;
mysql_mutex_lock(&LOCK_thread_count);
threads.append(thd);
mysql_mutex_unlock(&LOCK_thread_count);
thd->thread_stack= (char*) &tables;
if (thd->store_globals())
return 1;
thd->mysys_var->current_cond= &sleep_condition;
thd->mysys_var->current_mutex= &sleep_mutex;
thd->proc_info="feedback";
thd->set_command(COM_SLEEP);
thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; // whatever
thd->set_time();
thd->init_for_queries();
thd->real_id= pthread_self();
thd->db= NULL;
thd->db_length= 0;
thd->security_ctx->host_or_ip= "";
thd->security_ctx->db_access= DB_ACLS;
thd->security_ctx->master_access= ~NO_ACCESS;
bzero((char*) &thd->net, sizeof(thd->net));
lex_start(thd);
mysql_init_select(thd->lex);
tables->init_one_table(INFORMATION_SCHEMA_NAME.str,
INFORMATION_SCHEMA_NAME.length,
i_s_feedback->table_name,
strlen(i_s_feedback->table_name),
0, TL_READ);
tables->schema_table= i_s_feedback;
tables->table= create_schema_table(thd, tables);
if (!tables->table)
return 1;
tables->select_lex= thd->lex->current_select;
tables->table->pos_in_table_list= tables;
return 0;
}