Skip to content
Aleksey Midenkov edited this page Aug 22, 2018 · 3 revisions

MySQL Paradigms

Table_locker example

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);
    }
  }
};

Local SELECT_LEX usage

Usage schema

// 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;

Examples

sql_cte.cc:728
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;
sql_view.cc:1469

top

  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;
  }
sql_trigger.cc:1355

top

      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();
event_data_objects.cc:1340

top

 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;
    }
  }
sql_prepare.cc:3551

top

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;
}
sql_prepare.cc:3750

top

/**************************************************************************
  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);
}
table.cc:2652

top

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);
  }
table.cc:2937

top

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);
}
sp.cc:729

top

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;
}

THD and LEX initialized

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;
}