Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "my_global.h"
#include "sql_priv.h"
#include "unireg.h"
#include "sql_prepare.h"
#include "probes_mysql.h"
#include "sql_show.h"
#include "sql_db.h"
#include "sql_table.h"
#include "sql_acl.h"
#include "sql_array.h"
#include "sp_head.h"
#include "sp_instr.h"
#include "sp.h"
#include "sp_pcontext.h"
#include "set_var.h"
#include "sql_parse.h"
#include "sql_base.h"
#include "global_threads.h"
#include <my_user.h>
SP_TABLE represents all instances of one table in an optimized multi-set of
tables used by a stored program.
*/
struct SP_TABLE
{
Multi-set key:
db_name\0table_name\0alias\0 - for normal tables
db_name\0table_name\0 - for temporary tables
Note that in both cases we don't take last '\0' into account when
we count length of key.
*/
LEX_STRING qname;
uint db_length, table_name_length;
bool temp;
thr_lock_type lock_type;
uint lock_count;
uint query_lock_count;
uint8 trg_event_map;
};
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;
}
Init the qualified name from the db and name.
*/
void sp_name::init_qname(THD *thd)
{
const uint dot= !!m_db.length;
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);
}
void *sp_head::operator new(size_t size) throw()
{
MEM_ROOT own_root;
init_sql_alloc(&own_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
sp_head *sp= (sp_head *) alloc_root(&own_root, size);
if (!sp)
return NULL;
sp->main_mem_root= own_root;
DBUG_PRINT("info", ("mem_root 0x%lx", (ulong) &sp->mem_root));
return sp;
}
void sp_head::operator delete(void *ptr, size_t size) throw()
{
if (!ptr)
return;
sp_head *sp= (sp_head *) ptr;
MEM_ROOT own_root= sp->main_mem_root;
DBUG_PRINT("info", ("mem_root 0x%lx moved to 0x%lx",
(ulong) &sp->mem_root, (ulong) &own_root));
free_root(&own_root, MYF(0));
}
sp_head::sp_head(enum_sp_type type)
:Query_arena(&main_mem_root, STMT_INITIALIZED_FOR_SP),
m_type(type),
m_flags(0),
m_chistics(NULL),
m_sql_mode(0),
m_explicit_name(false),
m_created(0),
m_modified(0),
m_recursion_level(0),
m_next_cached_sp(NULL),
m_first_instance(NULL),
m_first_free_instance(NULL),
m_last_cached_sp(NULL),
m_trg_list(NULL),
m_root_parsing_ctx(NULL),
m_sp_cache_version(0),
m_creation_ctx(NULL),
unsafe_flags(0)
{
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= NULL_STR;
m_name= NULL_STR;
m_qname= NULL_STR;
m_params= NULL_STR;
m_defstr= NULL_STR;
m_body= NULL_STR;
m_body_utf8= NULL_STR;
my_hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0);
}
void sp_head::init_sp_name(THD *thd, sp_name *spname)
{
DBUG_ASSERT(spname && spname->m_db.str && spname->m_db.length);
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);
}
void sp_head::set_body_start(THD *thd, const char *begin_ptr)
{
m_parser_data.set_body_start_ptr(begin_ptr);
thd->m_parser_state->m_lip.body_utf8_start(thd, begin_ptr);
}
void sp_head::set_body_end(THD *thd)
{
Lex_input_stream *lip= & thd->m_parser_state->m_lip;
const char *end_ptr= lip->get_cpp_ptr();
{
const char *p_start= m_parser_data.get_parameter_start_ptr();
const char *p_end= m_parser_data.get_parameter_end_ptr();
if (p_start && p_end)
{
m_params.length= p_end - p_start;
m_params.str= thd->strmake(p_start, m_params.length);
}
}
thd->lex->stmt_definition_end= end_ptr;
m_body.length= end_ptr - m_parser_data.get_body_start_ptr();
m_body.str= thd->strmake(m_parser_data.get_body_start_ptr(), m_body.length);
trim_whitespace(thd->charset(), & m_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);
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);
}
sp_head::~sp_head()
{
LEX *lex;
sp_instr *i;
DBUG_ASSERT(!m_parser_data.is_parsing_sp_body());
for (uint ip = 0 ; (i = get_instr(ip)) ; ip++)
delete i;
delete m_root_parsing_ctx;
free_items();
If we have non-empty LEX stack then we just came out of parser with
error. Now we should delete all auxiliary 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_parser_data.pop_lex()))
{
THD *thd= lex->thd;
thd->lex->sphead= NULL;
lex_end(thd->lex);
delete thd->lex;
thd->lex= lex;
}
my_hash_free(&m_sptabs);
delete m_next_cached_sp;
}
bool sp_head::reset_lex(THD *thd)
{
LEX *oldlex= thd->lex;
LEX *sublex= new (thd->mem_root)st_lex_local;
if (!sublex)
return true;
thd->lex= sublex;
m_parser_data.push_lex(oldlex);
lex_start(thd);
sublex->sphead= oldlex->sphead;
sublex->set_sp_current_parsing_ctx(oldlex->get_sp_current_parsing_ctx());
sublex->sp_lex_in_use= FALSE;
sublex->charset= NULL;
sublex->length= NULL;
sublex->dec= NULL;
sublex->interval_list.empty();
sublex->type= 0;
thd->m_parser_state->m_yacc.reset_before_substatement();
return false;
}
bool sp_head::restore_lex(THD *thd)
{
LEX *sublex= thd->lex;
sublex->set_trg_event_type_for_tables();
LEX *oldlex= (LEX *) m_parser_data.pop_lex();
if (!oldlex)
return false;
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 (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);
if (!sublex->sp_lex_in_use)
{
sublex->sphead= NULL;
lex_end(sublex);
delete sublex;
}
thd->lex= oldlex;
return false;
}
bool sp_head::add_instr(THD *thd, sp_instr *instr)
{
m_parser_data.process_new_sp_instr(thd, instr);
if (m_type == SP_TYPE_TRIGGER && m_cur_instr_trig_field_items.elements)
{
SQL_I_List<Item_trigger_field> *instr_trig_fld_list;
Move all the Item_trigger_field from "sp_head::
m_cur_instr_trig_field_items" to the per instruction Item_trigger_field
list "sp_lex_instr::m_trig_field_list" and clear "sp_head::
m_cur_instr_trig_field_items".
*/
if ((instr_trig_fld_list= instr->get_instr_trig_field_list()) != NULL)
{
m_cur_instr_trig_field_items.save_and_clear(instr_trig_fld_list);
m_list_of_trig_fields_item_lists.link_in_list(instr_trig_fld_list,
&instr_trig_fld_list->first->next_trig_field_list);
}
}
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= get_persistent_mem_root();
return m_instructions.append(instr);
}
bool sp_head::merge_table_list(THD *thd,
TABLE_LIST *table,
LEX *lex_for_tmp_check)
{
if (lex_for_tmp_check->sql_command == SQLCOM_DROP_TABLE &&
lex_for_tmp_check->drop_temporary)
return true;
for (uint i= 0 ; i < m_sptabs.records ; i++)
{
SP_TABLE *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[(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.
*/
SP_TABLE *tab;
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;
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->create_info.options & HA_LEX_CREATE_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;
}
bool sp_head::add_used_tables_to_table_list(THD *thd,
TABLE_LIST ***query_tables_last_ptr,
TABLE_LIST *belong_to_view)
{
bool result= false;
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.
*/
Prepared_stmt_arena_holder ps_arena_holder(thd);
for (uint i= 0; i < m_sptabs.records; i++)
{
char *tab_buff, *key_buff;
SP_TABLE *stab= (SP_TABLE*) my_hash_element(&m_sptabs, i);
if (stab->temp)
continue;
if (!(tab_buff= (char *)thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST)) *
stab->lock_count)) ||
!(key_buff= (char*)thd->memdup(stab->qname.str,
stab->qname.length)))
return false;
for (uint j= 0; j < stab->lock_count; j++)
{
TABLE_LIST *table= (TABLE_LIST *)tab_buff;
table->db= key_buff;
table->db_length= stab->db_length;
table->table_name= table->db + table->db_length + 1;
table->table_name_length= stab->table_name_length;
table->alias= table->table_name + table->table_name_length + 1;
table->lock_type= stab->lock_type;
table->cacheable_table= 1;
table->prelocking_placeholder= 1;
table->belong_to_view= belong_to_view;
table->trg_event_map= stab->trg_event_map;
**query_tables_last_ptr= table;
table->prev_global= *query_tables_last_ptr;
*query_tables_last_ptr= &table->next_global;
tab_buff+= ALIGN_SIZE(sizeof(TABLE_LIST));
result= true;
}
}
return result;
}
void sp_parser_data::start_parsing_sp_body(THD *thd, sp_head *sp)
{
m_saved_memroot= thd->mem_root;
m_saved_free_list= thd->free_list;
thd->mem_root= sp->get_persistent_mem_root();
thd->free_list= NULL;
}
bool sp_parser_data::add_backpatch_entry(sp_branch_instr *i,
sp_label *label)
{
Backpatch_info *bp= (Backpatch_info *)sql_alloc(sizeof(Backpatch_info));
if (!bp)
return true;
bp->label= label;
bp->instr= i;
return m_backpatch.push_front(bp);
}
void sp_parser_data::do_backpatch(sp_label *label, uint dest)
{
Backpatch_info *bp;
List_iterator_fast<Backpatch_info> li(m_backpatch);
while ((bp= li++))
{
if (bp->label == label)
bp->instr->backpatch(dest);
}
}
bool sp_parser_data::add_cont_backpatch_entry(sp_lex_branch_instr *i)
{
i->set_cont_dest(m_cont_level);
return m_cont_backpatch.push_front(i);
}
void sp_parser_data::do_cont_backpatch(uint dest)
{
sp_lex_branch_instr *i;
while ((i= m_cont_backpatch.head()) && i->get_cont_dest() == m_cont_level)
{
i->set_cont_dest(dest);
m_cont_backpatch.pop();
}
--m_cont_level;
}
void sp_parser_data::process_new_sp_instr(THD* thd, sp_instr *i)
{
thd->free_list should be cleaned here because it's implicitly expected
that that process_new_sp_instr() (called from sp_head::add_instr) is
called as the last action after parsing the SP-instruction's SQL query.
Thus, at this point thd->free_list contains all Item-objects, created for
this SP-instruction.
Next SP-instruction should start its own free-list from the scratch.
*/
i->free_list= thd->free_list;
thd->free_list= NULL;
}