diff options
Diffstat (limited to 'sql/sql_handler.cc')
-rw-r--r-- | sql/sql_handler.cc | 1292 |
1 files changed, 1292 insertions, 0 deletions
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc new file mode 100644 index 00000000..7235dc64 --- /dev/null +++ b/sql/sql_handler.cc @@ -0,0 +1,1292 @@ +/* Copyright (c) 2001, 2015, Oracle and/or its affiliates. + Copyright (c) 2011, 2016, MariaDB Corporation + + 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */ + + +/* HANDLER ... commands - direct access to ISAM */ + +/* TODO: + HANDLER blabla OPEN [ AS foobar ] [ (column-list) ] + + the most natural (easiest, fastest) way to do it is to + compute List<Item> field_list not in mysql_ha_read + but in mysql_ha_open, and then store it in TABLE structure. + + The problem here is that mysql_parse calls free_item to free all the + items allocated at the end of every query. The workaround would to + keep two item lists per THD - normal free_list and handler_items. + The second is to be freeed only on thread end. mysql_ha_open should + then do { handler_items=concat(handler_items, free_list); free_list=0; } + + But !!! do_command calls free_root at the end of every query and frees up + all the memory allocated on THD::mem_root. It's harder to work around... +*/ + +/* + The information about open HANDLER objects is stored in a HASH. + It holds objects of type TABLE_LIST, which are indexed by table + name/alias, and allows us to quickly find a HANDLER table for any + operation at hand - be it HANDLER READ or HANDLER CLOSE. + + It also allows us to maintain an "open" HANDLER even in cases + when there is no physically open cursor. E.g. a FLUSH TABLE + statement in this or some other connection demands that all open + HANDLERs against the flushed table are closed. In order to + preserve the information about an open HANDLER, we don't perform + a complete HANDLER CLOSE, but only close the TABLE object. The + corresponding TABLE_LIST is kept in the cache with 'table' + pointer set to NULL. The table will be reopened on next access + (this, however, leads to loss of cursor position, unless the + cursor points at the first record). +*/ + +#include "mariadb.h" +#include "sql_priv.h" +#include "sql_handler.h" +#include "sql_base.h" // close_thread_tables +#include "lock.h" // mysql_unlock_tables +#include "key.h" // key_copy +#include "sql_base.h" // insert_fields +#include "sql_select.h" +#include "transaction.h" + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#define HANDLER_TABLES_HASH_SIZE 120 + +static enum enum_ha_read_modes rkey_to_rnext[]= +{ RNEXT_SAME, RNEXT, RPREV, RNEXT, RPREV, RNEXT, RPREV, RPREV }; + +/* + Set handler to state after create, but keep base information about + which table is used +*/ + +void SQL_HANDLER::reset() +{ + fields.empty(); + arena.free_items(); + free_root(&mem_root, MYF(0)); + my_free(lock); + init(); +} + +/* Free all allocated data */ + +SQL_HANDLER::~SQL_HANDLER() +{ + reset(); + my_free(base_data); +} + +/* + Get hash key and hash key length. + + SYNOPSIS + mysql_ha_hash_get_key() + tables Pointer to the hash object. + key_len_p (out) Pointer to the result for key length. + first Unused. + + DESCRIPTION + The hash object is an TABLE_LIST struct. + The hash key is the alias name. + The hash key length is the alias name length plus one for the + terminateing NUL character. + + RETURN + Pointer to the TABLE_LIST struct. +*/ + +static char *mysql_ha_hash_get_key(SQL_HANDLER *table, size_t *key_len, + my_bool first __attribute__((unused))) +{ + *key_len= table->handler_name.length + 1 ; /* include '\0' in comparisons */ + return (char*) table->handler_name.str; +} + + +/* + Free an hash object. + + SYNOPSIS + mysql_ha_hash_free() + tables Pointer to the hash object. + + DESCRIPTION + The hash object is an TABLE_LIST struct. + + RETURN + Nothing +*/ + +static void mysql_ha_hash_free(SQL_HANDLER *table) +{ + delete table; +} + +static void mysql_ha_close_childs(THD *thd, TABLE_LIST *current_table_list, + TABLE_LIST **next_global) +{ + TABLE_LIST *table_list; + DBUG_ENTER("mysql_ha_close_childs"); + DBUG_PRINT("info",("current_table_list: %p", current_table_list)); + DBUG_PRINT("info",("next_global: %p", *next_global)); + for (table_list = *next_global; table_list; table_list = *next_global) + { + *next_global = table_list->next_global; + DBUG_PRINT("info",("table_name: %s.%s", table_list->table->s->db.str, + table_list->table->s->table_name.str)); + DBUG_PRINT("info",("parent_l: %p", table_list->parent_l)); + if (table_list->parent_l == current_table_list) + { + DBUG_PRINT("info",("found child")); + TABLE *table = table_list->table; + if (table) + { + table->open_by_handler= 0; + if (!table->s->tmp_table) + { + (void) close_thread_table(thd, &table); + thd->mdl_context.release_lock(table_list->mdl_request.ticket); + } + else + { + thd->mark_tmp_table_as_free_for_reuse(table); + } + } + mysql_ha_close_childs(thd, table_list, next_global); + } + else + { + /* the end of child tables */ + *next_global = table_list; + break; + } + } + DBUG_VOID_RETURN; +} + +/** + Close a HANDLER table. + + @param thd Thread identifier. + @param tables A list of tables with the first entry to close. + + @note Though this function takes a list of tables, only the first list entry + will be closed. + @mote handler_object is not deleted! + @note Broadcasts refresh if it closed a table with old version. +*/ + +static void mysql_ha_close_table(SQL_HANDLER *handler) +{ + DBUG_ENTER("mysql_ha_close_table"); + THD *thd= handler->thd; + TABLE *table= handler->table; + TABLE_LIST *current_table_list= NULL, *next_global; + + /* check if table was already closed */ + if (!table) + DBUG_VOID_RETURN; + + if ((next_global= table->file->get_next_global_for_child())) + current_table_list= next_global->parent_l; + + table->open_by_handler= 0; + if (!table->s->tmp_table) + { + /* Non temporary table. */ + if (handler->lock) + { + // Mark it unlocked, like in reset_lock_data() + reset_lock_data(handler->lock, 1); + } + + table->file->ha_index_or_rnd_end(); + close_thread_table(thd, &table); + if (current_table_list) + mysql_ha_close_childs(thd, current_table_list, &next_global); + thd->mdl_context.release_lock(handler->mdl_request.ticket); + } + else + { + /* Must be a temporary table */ + table->file->ha_index_or_rnd_end(); + if (current_table_list) + mysql_ha_close_childs(thd, current_table_list, &next_global); + thd->mark_tmp_table_as_free_for_reuse(table); + } + my_free(handler->lock); + handler->init(); + DBUG_VOID_RETURN; +} + +/* + Open a HANDLER table. + + SYNOPSIS + mysql_ha_open() + thd Thread identifier. + tables A list of tables with the first entry to open. + reopen Re-open a previously opened handler table. + + DESCRIPTION + Though this function takes a list of tables, only the first list entry + will be opened. + 'reopen' is set when a handler table is to be re-opened. In this case, + 'tables' is the pointer to the hashed SQL_HANDLER object which has been + saved on the original open. + 'reopen' is also used to suppress the sending of an 'ok' message. + + RETURN + FALSE OK + TRUE Error +*/ + +bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) +{ + SQL_HANDLER *sql_handler= 0; + uint counter; + bool error; + TABLE *table, *backup_open_tables; + MDL_savepoint mdl_savepoint; + Query_arena backup_arena; + DBUG_ENTER("mysql_ha_open"); + DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", + tables->db.str, tables->table_name.str, tables->alias.str, + reopen != 0)); + + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + if (tables->schema_table) + { + my_error(ER_WRONG_USAGE, MYF(0), "HANDLER OPEN", + INFORMATION_SCHEMA_NAME.str); + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + + if (! my_hash_inited(&thd->handler_tables_hash)) + { + /* + HASH entries are of type SQL_HANDLER + */ + if (my_hash_init(key_memory_THD_handler_tables_hash, + &thd->handler_tables_hash, &my_charset_latin1, + HANDLER_TABLES_HASH_SIZE, 0, 0, (my_hash_get_key) + mysql_ha_hash_get_key, (my_hash_free_key) + mysql_ha_hash_free, 0)) + { + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + } + else if (! reopen) /* Otherwise we have 'tables' already. */ + { + if (my_hash_search(&thd->handler_tables_hash, (uchar*) tables->alias.str, + tables->alias.length + 1)) + { + DBUG_PRINT("info",("duplicate '%s'", tables->alias.str)); + DBUG_PRINT("exit",("ERROR")); + my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias.str); + DBUG_RETURN(TRUE); + } + } + + /* + Save and reset the open_tables list so that open_tables() won't + be able to access (or know about) the previous list. And on return + from open_tables(), thd->open_tables will contain only the opened + table. + + See open_table() back-off comments for more details. + */ + backup_open_tables= thd->open_tables; + thd->set_open_tables(NULL); + + /* + open_tables() will set 'tables->table' if successful. + It must be NULL for a real open when calling open_tables(). + */ + DBUG_ASSERT(! tables->table); + + /* + We can't request lock with explicit duration for this table + right from the start as open_tables() can't handle properly + back-off for such locks. + */ + MDL_REQUEST_INIT(&tables->mdl_request, MDL_key::TABLE, tables->db.str, + tables->table_name.str, MDL_SHARED_READ, MDL_TRANSACTION); + mdl_savepoint= thd->mdl_context.mdl_savepoint(); + + /* for now HANDLER can be used only for real TABLES */ + tables->required_type= TABLE_TYPE_NORMAL; + + /* + We use open_tables() here, rather than, say, + open_ltable() or open_table() because we would like to be able + to open a temporary table. + */ + error= (thd->open_temporary_tables(tables) || + open_tables(thd, &tables, &counter, 0)); + + if (unlikely(error)) + goto err; + + table= tables->table; + + /* There can be only one table in '*tables'. */ + if (! (table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) + { + my_error(ER_ILLEGAL_HA, MYF(0), table->file->table_type(), + table->s->db.str, table->s->table_name.str); + goto err; + } + + DBUG_PRINT("info",("clone_tickets start")); + for (TABLE_LIST *table_list= tables; table_list; + table_list= table_list->next_global) + { + DBUG_PRINT("info",("table_list %s.%s", table_list->table->s->db.str, + table_list->table->s->table_name.str)); + if (table_list->mdl_request.ticket && + thd->mdl_context.has_lock(mdl_savepoint, table_list->mdl_request.ticket)) + { + DBUG_PRINT("info",("clone_tickets")); + /* The ticket returned is within a savepoint. Make a copy. */ + error= thd->mdl_context.clone_ticket(&table_list->mdl_request); + table_list->table->mdl_ticket= table_list->mdl_request.ticket; + if (unlikely(error)) + goto err; + } + } + DBUG_PRINT("info",("clone_tickets end")); + + if (! reopen) + { + /* copy data to sql_handler */ + if (!(sql_handler= new SQL_HANDLER(thd))) + goto err; + init_alloc_root(PSI_INSTRUMENT_ME, &sql_handler->mem_root, 1024, 0, + MYF(MY_THREAD_SPECIFIC)); + + sql_handler->db.length= tables->db.length; + sql_handler->table_name.length= tables->table_name.length; + sql_handler->handler_name.length= tables->alias.length; + + if (!(my_multi_malloc(PSI_INSTRUMENT_ME, MYF(MY_WME), + &sql_handler->base_data, + (uint) sql_handler->db.length + 1, + &sql_handler->table_name.str, + (uint) sql_handler->table_name.length + 1, + &sql_handler->handler_name.str, + (uint) sql_handler->handler_name.length + 1, + NullS))) + goto err; + sql_handler->db.str= sql_handler->base_data; + memcpy((char*) sql_handler->db.str, tables->db.str, tables->db.length +1); + memcpy((char*) sql_handler->table_name.str, tables->table_name.str, + tables->table_name.length+1); + memcpy((char*) sql_handler->handler_name.str, tables->alias.str, + tables->alias.length +1); + + /* add to hash */ + if (my_hash_insert(&thd->handler_tables_hash, (uchar*) sql_handler)) + goto err; + } + else + { + sql_handler= reopen; + sql_handler->reset(); + } + sql_handler->table= table; + + if (!(sql_handler->lock= get_lock_data(thd, &sql_handler->table, 1, + GET_LOCK_STORE_LOCKS))) + goto err; + + /* Get a list of all fields for send_fields */ + thd->set_n_backup_active_arena(&sql_handler->arena, &backup_arena); + error= table->fill_item_list(&sql_handler->fields); + thd->restore_active_arena(&sql_handler->arena, &backup_arena); + if (unlikely(error)) + goto err; + + sql_handler->mdl_request.move_from(tables->mdl_request); + + /* Always read all columns */ + table->read_set= &table->s->all_set; + + /* Restore the state. */ + thd->set_open_tables(backup_open_tables); + DBUG_PRINT("info",("set_lock_duration start")); + if (sql_handler->mdl_request.ticket) + { + thd->mdl_context.set_lock_duration(sql_handler->mdl_request.ticket, + MDL_EXPLICIT); + thd->mdl_context.set_needs_thr_lock_abort(TRUE); + } + for (TABLE_LIST *table_list= tables->next_global; table_list; + table_list= table_list->next_global) + { + DBUG_PRINT("info",("table_list %s.%s", table_list->table->s->db.str, + table_list->table->s->table_name.str)); + if (table_list->mdl_request.ticket) + { + thd->mdl_context.set_lock_duration(table_list->mdl_request.ticket, + MDL_EXPLICIT); + thd->mdl_context.set_needs_thr_lock_abort(TRUE); + } + } + DBUG_PRINT("info",("set_lock_duration end")); + + /* + If it's a temp table, don't reset table->query_id as the table is + being used by this handler. For non-temp tables we use this flag + in asserts. + */ + for (TABLE_LIST *table_list= tables; table_list; + table_list= table_list->next_global) + { + table_list->table->open_by_handler= 1; + } + + if (! reopen) + my_ok(thd); + DBUG_PRINT("exit",("OK")); + DBUG_RETURN(FALSE); + +err: + /* + No need to rollback statement transaction, it's not started. + If called with reopen flag, no need to rollback either, + it will be done at statement end. + */ + DBUG_ASSERT(thd->transaction->stmt.is_empty()); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + thd->set_open_tables(backup_open_tables); + if (sql_handler) + { + if (!reopen) + my_hash_delete(&thd->handler_tables_hash, (uchar*) sql_handler); + else + sql_handler->reset(); // or should it be init() ? + } + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); +} + + +/* + Close a HANDLER table by alias or table name + + SYNOPSIS + mysql_ha_close() + thd Thread identifier. + tables A list of tables with the first entry to close. + + DESCRIPTION + Closes the table that is associated (on the handler tables hash) with the + name (table->alias) of the specified table. + + RETURN + FALSE ok + TRUE error +*/ + +bool mysql_ha_close(THD *thd, TABLE_LIST *tables) +{ + SQL_HANDLER *handler; + DBUG_ENTER("mysql_ha_close"); + DBUG_PRINT("enter",("'%s'.'%s' as '%s'", + tables->db.str, tables->table_name.str, tables->alias.str)); + + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + if ((my_hash_inited(&thd->handler_tables_hash)) && + (handler= (SQL_HANDLER*) my_hash_search(&thd->handler_tables_hash, + (const uchar*) tables->alias.str, + tables->alias.length + 1))) + { + mysql_ha_close_table(handler); + my_hash_delete(&thd->handler_tables_hash, (uchar*) handler); + } + else + { + my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias.str, "HANDLER"); + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + + /* + Mark MDL_context as no longer breaking protocol if we have + closed last HANDLER. + */ + if (! thd->handler_tables_hash.records) + thd->mdl_context.set_needs_thr_lock_abort(FALSE); + + my_ok(thd); + DBUG_PRINT("exit", ("OK")); + DBUG_RETURN(FALSE); +} + + +/** + Finds an open HANDLER table. + + @params name Name of handler to open + + @return 0 failure + @return handler +*/ + +static SQL_HANDLER *mysql_ha_find_handler(THD *thd, const LEX_CSTRING *name) +{ + SQL_HANDLER *handler; + if ((my_hash_inited(&thd->handler_tables_hash)) && + (handler= (SQL_HANDLER*) my_hash_search(&thd->handler_tables_hash, + (const uchar*) name->str, + name->length + 1))) + { + DBUG_PRINT("info-in-hash",("'%s'.'%s' as '%s' table: %p", + handler->db.str, + handler->table_name.str, + handler->handler_name.str, handler->table)); + if (!handler->table) + { + /* The handler table has been closed. Re-open it. */ + TABLE_LIST tmp; + tmp.init_one_table(&handler->db, &handler->table_name, + &handler->handler_name, TL_READ); + + if (mysql_ha_open(thd, &tmp, handler)) + { + DBUG_PRINT("exit",("reopen failed")); + return 0; + } + } + } + else + { + my_error(ER_UNKNOWN_TABLE, MYF(0), name->str, "HANDLER"); + return 0; + } + return handler; +} + + +/** + Check that condition and key name are ok + + @param handler + @param mode Read mode (RFIRST, RNEXT etc...) + @param keyname Key to use. + @param key_expr List of key column values + @param cond Where clause + @param in_prepare If we are in prepare phase (we can't evalute items yet) + + @return 0 ok + @return 1 error + + In ok, then values of used key and mode is stored in sql_handler +*/ + +static bool +mysql_ha_fix_cond_and_key(SQL_HANDLER *handler, + enum enum_ha_read_modes mode, const char *keyname, + List<Item> *key_expr, + enum ha_rkey_function ha_rkey_mode, + Item *cond, bool in_prepare) +{ + THD *thd= handler->thd; + TABLE *table= handler->table; + if (cond) + { + bool ret; + Item::vcol_func_processor_result res; + + /* This can only be true for temp tables */ + if (table->query_id != thd->query_id) + cond->cleanup(); // File was reopened + + ret= cond->walk(&Item::check_handler_func_processor, 0, &res); + if (ret || res.errors) + { + my_error(ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED, MYF(0), res.name, + "WHERE", "HANDLER"); + return 1; // ROWNUM() used + } + if (cond->fix_fields_if_needed_for_bool(thd, &cond)) + return 1; + } + + if (keyname) + { + /* Check if same as last keyname. If not, do a full lookup */ + if (handler->keyno < 0 || + my_strcasecmp(&my_charset_latin1, + keyname, + table->s->key_info[handler->keyno].name.str)) + { + if ((handler->keyno= find_type(keyname, &table->s->keynames, + FIND_TYPE_NO_PREFIX) - 1) < 0) + { + my_error(ER_KEY_DOES_NOT_EXISTS, MYF(0), keyname, + handler->handler_name.str); + return 1; + } + } + + /* Check key parts */ + if (mode == RKEY) + { + TABLE *table= handler->table; + KEY *keyinfo= table->key_info + handler->keyno; + KEY_PART_INFO *key_part= keyinfo->key_part; + List_iterator<Item> it_ke(*key_expr); + Item *item; + key_part_map keypart_map; + uint key_len; + const KEY *c_key= table->s->key_info + handler->keyno; + + if ((c_key->flags & HA_SPATIAL) || + c_key->algorithm == HA_KEY_ALG_FULLTEXT || + (ha_rkey_mode != HA_READ_KEY_EXACT && + (table->file->index_flags(handler->keyno, 0, TRUE) & + (HA_READ_NEXT | HA_READ_PREV | HA_READ_RANGE)) == 0)) + { + my_error(ER_KEY_DOESNT_SUPPORT, MYF(0), + table->file->index_type(handler->keyno), keyinfo->name.str); + return 1; + } + + if (key_expr->elements > keyinfo->user_defined_key_parts) + { + my_error(ER_TOO_MANY_KEY_PARTS, MYF(0), + keyinfo->user_defined_key_parts); + return 1; + } + + if (key_expr->elements < keyinfo->user_defined_key_parts && + (table->file->index_flags(handler->keyno, 0, TRUE) & + HA_ONLY_WHOLE_INDEX)) + { + my_error(ER_KEY_DOESNT_SUPPORT, MYF(0), + table->file->index_type(handler->keyno), keyinfo->name.str); + return 1; + } + + for (keypart_map= key_len=0 ; (item=it_ke++) ; key_part++) + { + /* note that 'item' can be changed by fix_fields() call */ + if (item->fix_fields_if_needed_for_scalar(thd, it_ke.ref())) + return 1; + item= *it_ke.ref(); + if (item->used_tables() & ~(RAND_TABLE_BIT | PARAM_TABLE_BIT)) + { + my_error(ER_WRONG_ARGUMENTS,MYF(0),"HANDLER ... READ"); + return 1; + } + if (!in_prepare) + { + MY_BITMAP *old_map= dbug_tmp_use_all_columns(table, &table->write_set); + int res= item->save_in_field(key_part->field, 1); + dbug_tmp_restore_column_map(&table->write_set, old_map); + if (res) + return 1; + } + key_len+= key_part->store_length; + keypart_map= (keypart_map << 1) | 1; + } + handler->keypart_map= keypart_map; + handler->key_len= key_len; + } + else + { + /* + Check if the same index involved. + We need to always do this check because we may not have yet + called the handler since the last keyno change. + */ + if ((uint) handler->keyno != table->file->get_index()) + { + if (mode == RNEXT) + mode= RFIRST; + else if (mode == RPREV) + mode= RLAST; + } + } + } + else if (table->file->inited != handler::RND) + { + /* Convert RNEXT to RFIRST if we haven't started row scan */ + if (mode == RNEXT) + mode= RFIRST; + } + handler->mode= mode; // Store adjusted mode + return 0; +} + +/* + Read from a HANDLER table. + + SYNOPSIS + mysql_ha_read() + thd Thread identifier. + tables A list of tables with the first entry to read. + mode + keyname + key_expr + ha_rkey_mode + cond + select_limit_cnt + offset_limit_cnt + + RETURN + FALSE ok + TRUE error +*/ + +bool mysql_ha_read(THD *thd, TABLE_LIST *tables, + enum enum_ha_read_modes mode, const char *keyname, + List<Item> *key_expr, + enum ha_rkey_function ha_rkey_mode, Item *cond, + ha_rows select_limit_cnt, ha_rows offset_limit_cnt) +{ + SQL_HANDLER *handler; + TABLE *table; + Protocol *protocol= thd->protocol; + char buff[MAX_FIELD_WIDTH]; + String buffer(buff, sizeof(buff), system_charset_info); + int error, keyno; + uint num_rows; + uchar *UNINIT_VAR(key); + MDL_deadlock_and_lock_abort_error_handler sql_handler_lock_error; + DBUG_ENTER("mysql_ha_read"); + DBUG_PRINT("enter",("'%s'.'%s' as '%s'", + tables->db.str, tables->table_name.str, tables->alias.str)); + + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + +retry: + if (!(handler= mysql_ha_find_handler(thd, &tables->alias))) + goto err0; + + if (thd->transaction->xid_state.check_has_uncommitted_xa()) + goto err0; + + table= handler->table; + tables->table= table; // This is used by fix_fields + table->pos_in_table_list= tables; + + if (handler->lock->table_count > 0) + { + int lock_error; + + THR_LOCK_DATA **pos,**end; + for (pos= handler->lock->locks, + end= handler->lock->locks + handler->lock->lock_count; + pos < end; + pos++) + { + pos[0]->type= pos[0]->org_type; + } + + /* save open_tables state */ + TABLE* backup_open_tables= thd->open_tables; + /* Always a one-element list, see mysql_ha_open(). */ + DBUG_ASSERT(table->next == NULL || table->s->tmp_table); + /* + mysql_lock_tables() needs thd->open_tables to be set correctly to + be able to handle aborts properly. + */ + thd->set_open_tables(table); + + sql_handler_lock_error.init(); + thd->push_internal_handler(&sql_handler_lock_error); + + lock_error= mysql_lock_tables(thd, handler->lock, + (table->s->tmp_table == NO_TMP_TABLE ? + MYSQL_LOCK_NOT_TEMPORARY : 0)); + + thd->pop_internal_handler(); + + /* + In 5.1 and earlier, mysql_lock_tables() could replace the TABLE + object with another one (reopen it). This is no longer the case + with new MDL. + */ + DBUG_ASSERT(table == thd->open_tables); + /* Restore previous context. */ + thd->set_open_tables(backup_open_tables); + + if (sql_handler_lock_error.need_reopen()) + { + DBUG_ASSERT(lock_error && !thd->is_error()); + /* + Always close statement transaction explicitly, + so that the engine doesn't have to count locks. + There should be no need to perform transaction + rollback due to deadlock. + */ + DBUG_ASSERT(! thd->transaction_rollback_request); + trans_rollback_stmt(thd); + mysql_ha_close_table(handler); + if (thd->stmt_arena->is_stmt_execute()) + { + /* + As we have already sent field list and types to the client, we can't + handle any changes in the table format for prepared statements. + Better to force a reprepare. + */ + my_error(ER_NEED_REPREPARE, MYF(0)); + goto err0; + } + goto retry; + } + + if (unlikely(lock_error)) + goto err0; // mysql_lock_tables() printed error message already + } + + if (mysql_ha_fix_cond_and_key(handler, mode, keyname, key_expr, + ha_rkey_mode, cond, 0)) + goto err; + mode= handler->mode; + keyno= handler->keyno; + + protocol->send_result_set_metadata(&handler->fields, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF); + + /* + In ::external_lock InnoDB resets the fields which tell it that + the handle is used in the HANDLER interface. Tell it again that + we are using it for HANDLER. + */ + + table->file->init_table_handle_for_HANDLER(); + + for (num_rows=0; num_rows < select_limit_cnt; ) + { + switch (mode) { + case RNEXT: + if (table->file->inited != handler::NONE) + { + if ((error= table->file->can_continue_handler_scan())) + break; + if (keyname) + { + /* Check if we read from the same index. */ + DBUG_ASSERT((uint) keyno == table->file->get_index()); + error= table->file->ha_index_next(table->record[0]); + } + else + error= table->file->ha_rnd_next(table->record[0]); + break; + } + /* else fall through */ + case RFIRST: + if (keyname) + { + if (likely(!(error= table->file->ha_index_or_rnd_end())) && + likely(!(error= table->file->ha_index_init(keyno, 1)))) + error= table->file->ha_index_first(table->record[0]); + } + else + { + if (likely(!(error= table->file->ha_index_or_rnd_end())) && + likely(!(error= table->file->ha_rnd_init(1)))) + error= table->file->ha_rnd_next(table->record[0]); + } + mode= RNEXT; + break; + case RPREV: + DBUG_ASSERT(keyname != 0); + /* Check if we read from the same index. */ + DBUG_ASSERT((uint) keyno == table->file->get_index()); + if (table->file->inited != handler::NONE) + { + if ((error= table->file->can_continue_handler_scan())) + break; + error= table->file->ha_index_prev(table->record[0]); + break; + } + /* else fall through */ + case RLAST: + DBUG_ASSERT(keyname != 0); + if (likely(!(error= table->file->ha_index_or_rnd_end())) && + likely(!(error= table->file->ha_index_init(keyno, 1)))) + error= table->file->ha_index_last(table->record[0]); + mode=RPREV; + break; + case RNEXT_SAME: + /* Continue scan on "(keypart1,keypart2,...)=(c1, c2, ...) */ + DBUG_ASSERT(keyname != 0); + error= table->file->ha_index_next_same(table->record[0], key, + handler->key_len); + break; + case RKEY: + { + DBUG_ASSERT(keyname != 0); + + if (unlikely(!(key= (uchar*) thd->calloc(ALIGN_SIZE(handler->key_len))))) + goto err; + if (unlikely((error= table->file->ha_index_or_rnd_end()))) + break; + key_copy(key, table->record[0], table->key_info + keyno, + handler->key_len); + if (unlikely(!(error= table->file->ha_index_init(keyno, 1)))) + error= table->file->ha_index_read_map(table->record[0], + key, handler->keypart_map, + ha_rkey_mode); + mode= rkey_to_rnext[(int)ha_rkey_mode]; + break; + } + default: + my_error(ER_ILLEGAL_HA, MYF(0), table->file->table_type(), + table->s->db.str, table->s->table_name.str); + goto err; + } + + if (unlikely(error)) + { + if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE) + { + /* Don't give error in the log file for some expected problems */ + if (error != HA_ERR_RECORD_CHANGED && error != HA_ERR_WRONG_COMMAND) + sql_print_error("mysql_ha_read: Got error %d when reading " + "table '%s'", + error, tables->table_name.str); + table->file->print_error(error,MYF(0)); + table->file->ha_index_or_rnd_end(); + goto err; + } + goto ok; + } + if (cond && !cond->val_int()) + { + if (thd->is_error()) + goto err; + continue; + } + if (num_rows >= offset_limit_cnt) + { + protocol->prepare_for_resend(); + + if (protocol->send_result_set_row(&handler->fields)) + goto err; + + protocol->write(); + } + num_rows++; + } +ok: + /* + Always close statement transaction explicitly, + so that the engine doesn't have to count locks. + */ + trans_commit_stmt(thd); + mysql_unlock_tables(thd, handler->lock, 0); + my_eof(thd); + DBUG_PRINT("exit",("OK")); + DBUG_RETURN(FALSE); + +err: + trans_rollback_stmt(thd); + mysql_unlock_tables(thd, handler->lock, 0); +err0: + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); +} + + +/** + Prepare for handler read + + For parameters, see mysql_ha_read() +*/ + +SQL_HANDLER *mysql_ha_read_prepare(THD *thd, TABLE_LIST *tables, + enum enum_ha_read_modes mode, + const char *keyname, + List<Item> *key_expr, + enum ha_rkey_function ha_rkey_mode, + Item *cond) +{ + SQL_HANDLER *handler; + DBUG_ENTER("mysql_ha_read_prepare"); + if (!(handler= mysql_ha_find_handler(thd, &tables->alias))) + DBUG_RETURN(0); + tables->table= handler->table; // This is used by fix_fields + handler->table->pos_in_table_list= tables; + if (mysql_ha_fix_cond_and_key(handler, mode, keyname, key_expr, + ha_rkey_mode, cond, 1)) + DBUG_RETURN(0); + DBUG_RETURN(handler); +} + + + +/** + Scan the handler tables hash for matching tables. + + @param thd Thread identifier. + @param tables The list of tables to remove. + + @return Pointer to head of linked list (TABLE_LIST::next_local) of matching + TABLE_LIST elements from handler_tables_hash. Otherwise, NULL if no + table was matched. +*/ + +static SQL_HANDLER *mysql_ha_find_match(THD *thd, TABLE_LIST *tables) +{ + SQL_HANDLER *hash_tables, *head= NULL; + TABLE_LIST *first= tables; + DBUG_ENTER("mysql_ha_find_match"); + + /* search for all handlers with matching table names */ + for (uint i= 0; i < thd->handler_tables_hash.records; i++) + { + hash_tables= (SQL_HANDLER*) my_hash_element(&thd->handler_tables_hash, i); + + for (tables= first; tables; tables= tables->next_local) + { + if (tables->is_anonymous_derived_table()) + continue; + if ((! tables->db.str[0] || + ! my_strcasecmp(&my_charset_latin1, hash_tables->db.str, + tables->get_db_name())) && + ! my_strcasecmp(&my_charset_latin1, hash_tables->table_name.str, + tables->get_table_name())) + { + /* Link into hash_tables list */ + hash_tables->next= head; + head= hash_tables; + break; + } + } + } + DBUG_RETURN(head); +} + + +/** + Remove matching tables from the HANDLER's hash table. + + @param thd Thread identifier. + @param tables The list of tables to remove. + + @note Broadcasts refresh if it closed a table with old version. +*/ + +void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables) +{ + SQL_HANDLER *hash_tables, *next; + DBUG_ENTER("mysql_ha_rm_tables"); + + DBUG_ASSERT(tables); + + hash_tables= mysql_ha_find_match(thd, tables); + + while (hash_tables) + { + next= hash_tables->next; + if (hash_tables->table) + mysql_ha_close_table(hash_tables); + my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); + hash_tables= next; + } + + /* + Mark MDL_context as no longer breaking protocol if we have + closed last HANDLER. + */ + if (! thd->handler_tables_hash.records) + thd->mdl_context.set_needs_thr_lock_abort(FALSE); + + DBUG_VOID_RETURN; +} + + +/** + Close cursors of matching tables from the HANDLER's hash table. + + @param thd Thread identifier. + @param tables The list of tables to flush. +*/ + +void mysql_ha_flush_tables(THD *thd, TABLE_LIST *all_tables) +{ + DBUG_ENTER("mysql_ha_flush_tables"); + + for (TABLE_LIST *table_list= all_tables; table_list; + table_list= table_list->next_global) + { + SQL_HANDLER *hash_tables= mysql_ha_find_match(thd, table_list); + /* Close all aliases of the same table. */ + while (hash_tables) + { + SQL_HANDLER *next_local= hash_tables->next; + if (hash_tables->table) + mysql_ha_close_table(hash_tables); + hash_tables= next_local; + } + } + + DBUG_VOID_RETURN; +} + + +/** + Flush (close and mark for re-open) all tables that should be should + be reopen. + + @param thd Thread identifier. + + @note Broadcasts refresh if it closed a table with old version. +*/ + +void mysql_ha_flush(THD *thd) +{ + SQL_HANDLER *hash_tables; + DBUG_ENTER("mysql_ha_flush"); + + /* + Don't try to flush open HANDLERs when we're working with + system tables. The main MDL context is backed up and we can't + properly release HANDLER locks stored there. + */ + if (thd->state_flags & Open_tables_state::BACKUPS_AVAIL) + DBUG_VOID_RETURN; + + for (uint i= 0; i < thd->handler_tables_hash.records; i++) + { + hash_tables= (SQL_HANDLER*) my_hash_element(&thd->handler_tables_hash, i); + /* + TABLE::mdl_ticket is 0 for temporary tables so we need extra check. + */ + if (hash_tables->table && + ((hash_tables->table->mdl_ticket && + hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) || + (!hash_tables->table->s->tmp_table && + hash_tables->table->s->tdc->flushed))) + mysql_ha_close_table(hash_tables); + } + + DBUG_VOID_RETURN; +} + + +/** + Close all HANDLER's tables. + + @param thd Thread identifier. + + @note Broadcasts refresh if it closed a table with old version. +*/ + +void mysql_ha_cleanup_no_free(THD *thd) +{ + SQL_HANDLER *hash_tables; + DBUG_ENTER("mysql_ha_cleanup_no_free"); + + for (uint i= 0; i < thd->handler_tables_hash.records; i++) + { + hash_tables= (SQL_HANDLER*) my_hash_element(&thd->handler_tables_hash, i); + if (hash_tables->table) + mysql_ha_close_table(hash_tables); + } + DBUG_VOID_RETURN; +} + + +void mysql_ha_cleanup(THD *thd) +{ + DBUG_ENTER("mysql_ha_cleanup"); + mysql_ha_cleanup_no_free(thd); + my_hash_free(&thd->handler_tables_hash); + DBUG_VOID_RETURN; +} + + +/** + Set explicit duration for metadata locks corresponding to open HANDLERs + to protect them from being released at the end of transaction. + + @param thd Thread identifier. +*/ + +void mysql_ha_set_explicit_lock_duration(THD *thd) +{ + SQL_HANDLER *hash_tables; + DBUG_ENTER("mysql_ha_set_explicit_lock_duration"); + + for (uint i= 0; i < thd->handler_tables_hash.records; i++) + { + hash_tables= (SQL_HANDLER*) my_hash_element(&thd->handler_tables_hash, i); + if (hash_tables->table && hash_tables->table->mdl_ticket) + thd->mdl_context.set_lock_duration(hash_tables->table->mdl_ticket, + MDL_EXPLICIT); + } + DBUG_VOID_RETURN; +} + + +/** + Remove temporary tables from the HANDLER's hash table. The reason + for having a separate function, rather than calling + mysql_ha_rm_tables() is that it is not always feasible (e.g. in + THD::close_temporary_tables) to obtain a TABLE_LIST containing the + temporary tables. + + @See THD::close_temporary_tables() + @param thd Thread identifier. +*/ +void mysql_ha_rm_temporary_tables(THD *thd) +{ + DBUG_ENTER("mysql_ha_rm_temporary_tables"); + + TABLE_LIST *tmp_handler_tables= NULL; + for (uint i= 0; i < thd->handler_tables_hash.records; i++) + { + TABLE_LIST *handler_table= reinterpret_cast<TABLE_LIST*> + (my_hash_element(&thd->handler_tables_hash, i)); + + if (handler_table->table && handler_table->table->s->tmp_table) + { + handler_table->next_local= tmp_handler_tables; + tmp_handler_tables= handler_table; + } + } + + if (tmp_handler_tables) + mysql_ha_rm_tables(thd, tmp_handler_tables); + + DBUG_VOID_RETURN; +} |