diff options
Diffstat (limited to 'sql/sql_base.cc')
-rw-r--r-- | sql/sql_base.cc | 2667 |
1 files changed, 2007 insertions, 660 deletions
diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 98e1f2dacd5..a97d285810e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -24,7 +24,7 @@ #include <m_ctype.h> #include <my_dir.h> #include <hash.h> -#ifdef __WIN__ +#ifdef __WIN__ #include <io.h> #endif @@ -83,28 +83,35 @@ bool Prelock_error_handler::safely_trapped_errors() TABLE *unused_tables; /* Used by mysql_test */ HASH open_cache; /* Used by mysql_test */ - -static int open_unireg_entry(THD *thd, TABLE *entry, const char *db, - const char *name, const char *alias, - TABLE_LIST *table_list, MEM_ROOT *mem_root, - uint flags); +static HASH table_def_cache; +static TABLE_SHARE *oldest_unused_share, end_of_unused_share; +static pthread_mutex_t LOCK_table_share; +static bool table_def_inited= 0; + +static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, + const char *alias, + char *cache_key, uint cache_key_length, + MEM_ROOT *mem_root, uint flags); static void free_cache_entry(TABLE *entry); -static bool open_new_frm(THD *thd, const char *path, const char *alias, - const char *db, const char *table_name, +static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, bool send_refresh); +static bool +has_two_write_locked_tables_with_auto_increment(TABLE_LIST *tables); + extern "C" byte *table_cache_key(const byte *record,uint *length, my_bool not_used __attribute__((unused))) { TABLE *entry=(TABLE*) record; - *length= entry->s->key_length; - return (byte*) entry->s->table_cache_key; + *length= entry->s->table_cache_key.length; + return (byte*) entry->s->table_cache_key.str; } + bool table_cache_init(void) { return hash_init(&open_cache, &my_charset_bin, table_cache_size+16, @@ -115,21 +122,25 @@ bool table_cache_init(void) void table_cache_free(void) { DBUG_ENTER("table_cache_free"); - close_cached_tables((THD*) 0,0,(TABLE_LIST*) 0); - if (!open_cache.records) // Safety first - hash_free(&open_cache); + if (table_def_inited) + { + close_cached_tables((THD*) 0,0,(TABLE_LIST*) 0); + if (!open_cache.records) // Safety first + hash_free(&open_cache); + } DBUG_VOID_RETURN; } -uint cached_tables(void) +uint cached_open_tables(void) { return open_cache.records; } + #ifdef EXTRA_DEBUG static void check_unused(void) { - uint count=0,idx=0; + uint count= 0, open_files= 0, idx= 0; TABLE *cur_link,*start_link; if ((start_link=cur_link=unused_tables)) @@ -153,17 +164,557 @@ static void check_unused(void) TABLE *entry=(TABLE*) hash_element(&open_cache,idx); if (!entry->in_use) count--; + if (entry->file) + open_files++; } if (count != 0) { DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */ count)); /* purecov: inspected */ } + +#ifdef NOT_SAFE_FOR_REPAIR + /* + check that open cache and table definition cache has same number of + aktive tables + */ + count= 0; + for (idx=0 ; idx < table_def_cache.records ; idx++) + { + TABLE_SHARE *entry= (TABLE_SHARE*) hash_element(&table_def_cache,idx); + count+= entry->ref_count; + } + if (count != open_files) + { + DBUG_PRINT("error", ("table_def ref_count: %u open_cache: %u", + count, open_files)); + DBUG_ASSERT(count == open_files); + } +#endif } #else #define check_unused() #endif + +/* + Create a table cache key + + SYNOPSIS + create_table_def_key() + thd Thread handler + key Create key here (must be of size MAX_DBKEY_LENGTH) + table_list Table definition + tmp_table Set if table is a tmp table + + IMPLEMENTATION + The table cache_key is created from: + db_name + \0 + table_name + \0 + + if the table is a tmp table, we add the following to make each tmp table + unique on the slave: + + 4 bytes for master thread id + 4 bytes pseudo thread id + + RETURN + Length of key +*/ + +uint create_table_def_key(THD *thd, char *key, TABLE_LIST *table_list, + bool tmp_table) +{ + uint key_length= (uint) (strmov(strmov(key, table_list->db)+1, + table_list->table_name)-key)+1; + if (tmp_table) + { + int4store(key + key_length, thd->server_id); + int4store(key + key_length + 4, thd->variables.pseudo_thread_id); + key_length+= TMP_TABLE_KEY_EXTRA; + } + return key_length; +} + + + +/***************************************************************************** + Functions to handle table definition cach (TABLE_SHARE) +*****************************************************************************/ + +extern "C" byte *table_def_key(const byte *record, uint *length, + my_bool not_used __attribute__((unused))) +{ + TABLE_SHARE *entry=(TABLE_SHARE*) record; + *length= entry->table_cache_key.length; + return (byte*) entry->table_cache_key.str; +} + + +static void table_def_free_entry(TABLE_SHARE *share) +{ + DBUG_ENTER("table_def_free_entry"); + if (share->prev) + { + /* remove from old_unused_share list */ + pthread_mutex_lock(&LOCK_table_share); + *share->prev= share->next; + share->next->prev= share->prev; + pthread_mutex_unlock(&LOCK_table_share); + } + free_table_share(share); + DBUG_VOID_RETURN; +} + + +bool table_def_init(void) +{ + table_def_inited= 1; + pthread_mutex_init(&LOCK_table_share, MY_MUTEX_INIT_FAST); + oldest_unused_share= &end_of_unused_share; + end_of_unused_share.prev= &oldest_unused_share; + + return hash_init(&table_def_cache, &my_charset_bin, table_def_size, + 0, 0, table_def_key, + (hash_free_key) table_def_free_entry, 0) != 0; +} + + +void table_def_free(void) +{ + DBUG_ENTER("table_def_free"); + if (table_def_inited) + { + table_def_inited= 0; + pthread_mutex_destroy(&LOCK_table_share); + hash_free(&table_def_cache); + } + DBUG_VOID_RETURN; +} + + +uint cached_table_definitions(void) +{ + return table_def_cache.records; +} + + +/* + Get TABLE_SHARE for a table. + + get_table_share() + thd Thread handle + table_list Table that should be opened + key Table cache key + key_length Length of key + db_flags Flags to open_table_def(): + OPEN_VIEW + error out: Error code from open_table_def() + + IMPLEMENTATION + Get a table definition from the table definition cache. + If it doesn't exist, create a new from the table definition file. + + NOTES + We must have wrlock on LOCK_open when we come here + (To be changed later) + + RETURN + 0 Error + # Share for table +*/ + +TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, + uint key_length, uint db_flags, int *error) +{ + TABLE_SHARE *share; + DBUG_ENTER("get_table_share"); + + *error= 0; + + /* Read table definition from cache */ + if ((share= (TABLE_SHARE*) hash_search(&table_def_cache,(byte*) key, + key_length))) + goto found; + + if (!(share= alloc_table_share(table_list, key, key_length))) + { +#ifdef WAITING_FOR_TABLE_DEF_CACHE_STAGE_3 + pthread_mutex_unlock(&LOCK_open); +#endif + DBUG_RETURN(0); + } + +#ifdef WAITING_FOR_TABLE_DEF_CACHE_STAGE_3 + // We need a write lock to be able to add a new entry + pthread_mutex_unlock(&LOCK_open); + pthread_mutex_lock(&LOCK_open); + /* Check that another thread didn't insert the same table in between */ + if ((old_share= hash_search(&table_def_cache, (byte*) key, key_length))) + { + (void) pthread_mutex_lock(&share->mutex); + free_table_share(share); + share= old_share; + goto found; + } +#endif + + /* + Lock mutex to be able to read table definition from file without + conflicts + */ + (void) pthread_mutex_lock(&share->mutex); + + /* + We assign a new table id under the protection of the LOCK_open and + the share's own mutex. We do this insted of creating a new mutex + and using it for the sole purpose of serializing accesses to a + static variable, we assign the table id here. We assign it to the + share before inserting it into the table_def_cache to be really + sure that it cannot be read from the cache without having a table + id assigned. + + CAVEAT. This means that the table cannot be used for + binlogging/replication purposes, unless get_table_share() has been + called directly or indirectly. + */ + assign_new_table_id(share); + + if (my_hash_insert(&table_def_cache, (byte*) share)) + { +#ifdef WAITING_FOR_TABLE_DEF_CACHE_STAGE_3 + pthread_mutex_unlock(&LOCK_open); + (void) pthread_mutex_unlock(&share->mutex); +#endif + free_table_share(share); + DBUG_RETURN(0); // return error + } +#ifdef WAITING_FOR_TABLE_DEF_CACHE_STAGE_3 + pthread_mutex_unlock(&LOCK_open); +#endif + if (open_table_def(thd, share, db_flags)) + { +#ifdef WAITING_FOR_TABLE_DEF_CACHE_STAGE_3 + /* + No such table or wrong table definition file + Lock first the table cache and then the mutex. + This will ensure that no other thread is using the share + structure. + */ + (void) pthread_mutex_unlock(&share->mutex); + (void) pthread_mutex_lock(&LOCK_open); + (void) pthread_mutex_lock(&share->mutex); +#endif + *error= share->error; + (void) hash_delete(&table_def_cache, (byte*) share); + DBUG_RETURN(0); + } + share->ref_count++; // Mark in use + DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", + (ulong) share, share->ref_count)); + (void) pthread_mutex_unlock(&share->mutex); + DBUG_RETURN(share); + +found: + /* + We found an existing table definition. Return it if we didn't get + an error when reading the table definition from file. + */ + + /* We must do a lock to ensure that the structure is initialized */ + (void) pthread_mutex_lock(&share->mutex); +#ifdef WAITING_FOR_TABLE_DEF_CACHE_STAGE_3 + pthread_mutex_unlock(&LOCK_open); +#endif + if (share->error) + { + /* Table definition contained an error */ + open_table_error(share, share->error, share->open_errno, share->errarg); + (void) pthread_mutex_unlock(&share->mutex); + DBUG_RETURN(0); + } + if (share->is_view && !(db_flags & OPEN_VIEW)) + { + open_table_error(share, 1, ENOENT, 0); + (void) pthread_mutex_unlock(&share->mutex); + DBUG_RETURN(0); + } + + if (!share->ref_count++ && share->prev) + { + /* + Share was not used before and it was in the old_unused_share list + Unlink share from this list + */ + DBUG_PRINT("info", ("Unlinking from not used list")); + pthread_mutex_lock(&LOCK_table_share); + *share->prev= share->next; + share->next->prev= share->prev; + share->next= 0; + share->prev= 0; + pthread_mutex_unlock(&LOCK_table_share); + } + (void) pthread_mutex_unlock(&share->mutex); + + /* Free cache if too big */ + while (table_def_cache.records > table_def_size && + oldest_unused_share->next) + { + pthread_mutex_lock(&oldest_unused_share->mutex); + VOID(hash_delete(&table_def_cache, (byte*) oldest_unused_share)); + } + + DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", + (ulong) share, share->ref_count)); + DBUG_RETURN(share); +} + + +/* + Get a table share. If it didn't exist, try creating it from engine + + For arguments and return values, see get_table_from_share() +*/ + +static TABLE_SHARE +*get_table_share_with_create(THD *thd, TABLE_LIST *table_list, + char *key, uint key_length, + uint db_flags, int *error) +{ + TABLE_SHARE *share; + int tmp; + DBUG_ENTER("get_table_share_with_create"); + + if ((share= get_table_share(thd, table_list, key, key_length, + db_flags, error)) || + thd->net.last_errno != ER_NO_SUCH_TABLE) + DBUG_RETURN(share); + + /* Table didn't exist. Check if some engine can provide it */ + if ((tmp= ha_create_table_from_engine(thd, table_list->db, + table_list->table_name)) < 0) + { + /* + No such table in any engine. + Hide "Table doesn't exist" errors if table belong to view + */ + if (table_list->belong_to_view) + { + TABLE_LIST *view= table_list->belong_to_view; + thd->clear_error(); + my_error(ER_VIEW_INVALID, MYF(0), + view->view_db.str, view->view_name.str); + } + DBUG_RETURN(0); + } + if (tmp) + { + /* Give right error message */ + thd->clear_error(); + DBUG_PRINT("error", ("Discovery of %s/%s failed", table_list->db, + table_list->table_name)); + my_printf_error(ER_UNKNOWN_ERROR, + "Failed to open '%-.64s', error while " + "unpacking from engine", + MYF(0), table_list->table_name); + DBUG_RETURN(0); + } + /* Table existed in engine. Let's open it */ + mysql_reset_errors(thd, 1); // Clear warnings + thd->clear_error(); // Clear error message + DBUG_RETURN(get_table_share(thd, table_list, key, key_length, + db_flags, error)); +} + + +/* + Mark that we are not using table share anymore. + + SYNOPSIS + release_table_share() + share Table share + release_type How the release should be done: + RELEASE_NORMAL + - Release without checking + RELEASE_WAIT_FOR_DROP + - Don't return until we get a signal that the + table is deleted or the thread is killed. + + IMPLEMENTATION + If ref_count goes to zero and (we have done a refresh or if we have + already too many open table shares) then delete the definition. + + If type == RELEASE_WAIT_FOR_DROP then don't return until we get a signal + that the table is deleted or the thread is killed. +*/ + +void release_table_share(TABLE_SHARE *share, enum release_type type) +{ + bool to_be_deleted= 0; + DBUG_ENTER("release_table_share"); + DBUG_PRINT("enter", + ("share: 0x%lx table: %s.%s ref_count: %u version: %lu", + (ulong) share, share->db.str, share->table_name.str, + share->ref_count, share->version)); + + safe_mutex_assert_owner(&LOCK_open); + + pthread_mutex_lock(&share->mutex); + if (!--share->ref_count) + { + if (share->version != refresh_version) + to_be_deleted=1; + else + { + /* Link share last in used_table_share list */ + DBUG_PRINT("info",("moving share to unused list")); + + DBUG_ASSERT(share->next == 0); + pthread_mutex_lock(&LOCK_table_share); + share->prev= end_of_unused_share.prev; + *end_of_unused_share.prev= share; + end_of_unused_share.prev= &share->next; + share->next= &end_of_unused_share; + pthread_mutex_unlock(&LOCK_table_share); + + to_be_deleted= (table_def_cache.records > table_def_size); + } + } + + if (to_be_deleted) + { + DBUG_PRINT("info", ("Deleting share")); + hash_delete(&table_def_cache, (byte*) share); + DBUG_VOID_RETURN; + } + pthread_mutex_unlock(&share->mutex); + DBUG_VOID_RETURN; + + +#ifdef WAITING_FOR_TABLE_DEF_CACHE_STAGE_3 + if (to_be_deleted) + { + /* + We must try again with new locks as we must get LOCK_open + before share->mutex + */ + pthread_mutex_unlock(&share->mutex); + pthread_mutex_lock(&LOCK_open); + pthread_mutex_lock(&share->mutex); + if (!share->ref_count) + { // No one is using this now + TABLE_SHARE *name_lock; + if (share->replace_with_name_lock && (name_lock=get_name_lock(share))) + { + /* + This code is execured when someone does FLUSH TABLES while on has + locked tables. + */ + (void) hash_search(&def_cache,(byte*) key,key_length); + hash_replace(&def_cache, def_cache.current_record,(byte*) name_lock); + } + else + { + /* Remove table definition */ + hash_delete(&def_cache,(byte*) share); + } + pthread_mutex_unlock(&LOCK_open); + free_table_share(share); + } + else + { + pthread_mutex_unlock(&LOCK_open); + if (type == RELEASE_WAIT_FOR_DROP) + wait_for_table(share, "Waiting for close"); + else + pthread_mutex_unlock(&share->mutex); + } + } + else if (type == RELEASE_WAIT_FOR_DROP) + wait_for_table(share, "Waiting for close"); + else + pthread_mutex_unlock(&share->mutex); +#endif +} + + +/* + Check if table definition exits in cache + + SYNOPSIS + get_cached_table_share() + db Database name + table_name Table name + + RETURN + 0 Not cached + # TABLE_SHARE for table +*/ + +TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name) +{ + char key[NAME_LEN*2+2]; + TABLE_LIST table_list; + uint key_length; + safe_mutex_assert_owner(&LOCK_open); + + table_list.db= (char*) db; + table_list.table_name= (char*) table_name; + key_length= create_table_def_key((THD*) 0, key, &table_list, 0); + return (TABLE_SHARE*) hash_search(&table_def_cache,(byte*) key, key_length); +} + + +/* + Close file handle, but leave the table in the table cache + + SYNOPSIS + close_handle_and_leave_table_as_lock() + table Table handler + + NOTES + By leaving the table in the table cache, it disallows any other thread + to open the table + + thd->killed will be set if we run out of memory +*/ + + +void close_handle_and_leave_table_as_lock(TABLE *table) +{ + TABLE_SHARE *share, *old_share= table->s; + char *key_buff; + MEM_ROOT *mem_root= &table->mem_root; + DBUG_ENTER("close_handle_and_leave_table_as_lock"); + + DBUG_ASSERT(table->db_stat); + + /* + Make a local copy of the table share and free the current one. + This has to be done to ensure that the table share is removed from + the table defintion cache as soon as the last instance is removed + */ + if (multi_alloc_root(mem_root, + &share, sizeof(*share), + &key_buff, old_share->table_cache_key.length, + NULL)) + { + bzero((char*) share, sizeof(*share)); + share->set_table_cache_key(key_buff, old_share->table_cache_key.str, + old_share->table_cache_key.length); + share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table() + } + + table->file->close(); + table->db_stat= 0; // Mark file closed + release_table_share(table->s, RELEASE_NORMAL); + table->s= share; + + DBUG_VOID_RETURN; +} + + + /* Create a list for all open tables matching SQL expression @@ -200,17 +751,14 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) TABLE *entry=(TABLE*) hash_element(&open_cache,idx); TABLE_SHARE *share= entry->s; - DBUG_ASSERT(share->table_name != 0); - if ((!share->table_name)) // To be removed - continue; // Shouldn't happen - if (db && my_strcasecmp(system_charset_info, db, share->db)) + if (db && my_strcasecmp(system_charset_info, db, share->db.str)) continue; - if (wild && wild_compare(share->table_name,wild,0)) + if (wild && wild_compare(share->table_name.str, wild, 0)) continue; /* Check if user has SELECT privilege for any column in the table */ - table_list.db= (char*) share->db; - table_list.table_name= (char*) share->table_name; + table_list.db= share->db.str; + table_list.table_name= share->table_name.str; table_list.grant.privilege=0; if (check_table_access(thd,SELECT_ACL | EXTRA_ACL,&table_list,1)) @@ -218,8 +766,8 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) /* need to check if we haven't already listed it */ for (table= open_list ; table ; table=table->next) { - if (!strcmp(table->table,share->table_name) && - !strcmp(table->db,entry->s->db)) + if (!strcmp(table->table, share->table_name.str) && + !strcmp(table->db, share->db.str)) { if (entry->in_use) table->in_use++; @@ -231,15 +779,15 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) if (table) continue; if (!(*start_list = (OPEN_TABLE_LIST *) - sql_alloc(sizeof(**start_list)+share->key_length))) + sql_alloc(sizeof(**start_list)+share->table_cache_key.length))) { open_list=0; // Out of memory break; } strmov((*start_list)->table= strmov(((*start_list)->db= (char*) ((*start_list)+1)), - entry->s->db)+1, - entry->s->table_name); + share->db.str)+1, + share->table_name.str); (*start_list)->in_use= entry->in_use ? 1 : 0; (*start_list)->locked= entry->locked_by_name ? 1 : 0; start_list= &(*start_list)->next; @@ -256,10 +804,13 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) void intern_close_table(TABLE *table) { // Free all structures + DBUG_ENTER("intern_close_table"); + free_io_cache(table); delete table->triggers; - if (table->file) - VOID(closefrm(table)); // close file + if (table->file) // Not true if name lock + VOID(closefrm(table, 1)); // close file + DBUG_VOID_RETURN; } /* @@ -276,7 +827,6 @@ void intern_close_table(TABLE *table) static void free_cache_entry(TABLE *table) { DBUG_ENTER("free_cache_entry"); - safe_mutex_assert_owner(&LOCK_open); intern_close_table(table); if (!table->in_use) @@ -309,6 +859,7 @@ void free_io_cache(TABLE *table) DBUG_VOID_RETURN; } + /* Close all tables which aren't in use by any thread @@ -317,15 +868,17 @@ void free_io_cache(TABLE *table) */ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, - TABLE_LIST *tables) + TABLE_LIST *tables, bool have_lock) { bool result=0; DBUG_ENTER("close_cached_tables"); DBUG_ASSERT(thd || (!if_wait_for_refresh && !tables)); - VOID(pthread_mutex_lock(&LOCK_open)); + if (!have_lock) + VOID(pthread_mutex_lock(&LOCK_open)); if (!tables) { + refresh_version++; // Force close of open tables while (unused_tables) { #ifdef EXTRA_DEBUG @@ -335,14 +888,20 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, VOID(hash_delete(&open_cache,(byte*) unused_tables)); #endif } - refresh_version++; // Force close of open tables + /* Free table shares */ + while (oldest_unused_share->next) + { + pthread_mutex_lock(&oldest_unused_share->mutex); + VOID(hash_delete(&table_def_cache, (byte*) oldest_unused_share)); + } } else { bool found=0; for (TABLE_LIST *table= tables; table; table= table->next_local) { - if (remove_table_from_cache(thd, table->db, table->table_name, + if ((!table->table || !table->table->s->log_table) && + remove_table_from_cache(thd, table->db, table->table_name, RTFC_OWNED_BY_THD_FLAG)) found=1; } @@ -390,7 +949,8 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, are employed by CREATE TABLE as in this case table simply does not exist yet. */ - if (table->needs_reopen_or_name_lock() && table->db_stat) + if (!table->s->log_table && + (table->needs_reopen_or_name_lock() && table->db_stat)) { found=1; DBUG_PRINT("signal", ("Waiting for COND_refresh")); @@ -411,7 +971,8 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, for (TABLE *table=thd->open_tables; table ; table= table->next) table->s->version= refresh_version; } - VOID(pthread_mutex_unlock(&LOCK_open)); + if (!have_lock) + VOID(pthread_mutex_unlock(&LOCK_open)); if (if_wait_for_refresh) { pthread_mutex_lock(&thd->mysys_var->mutex); @@ -425,6 +986,71 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, /* + Close all tables which match specified connection string or + if specified string is NULL, then any table with a connection string. +*/ + +bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, + LEX_STRING *connection, bool have_lock) +{ + uint idx; + TABLE_LIST tmp, *tables= NULL; + bool result= FALSE; + DBUG_ENTER("close_cached_connections"); + DBUG_ASSERT(thd); + + bzero(&tmp, sizeof(TABLE_LIST)); + + if (!have_lock) + VOID(pthread_mutex_lock(&LOCK_open)); + + for (idx= 0; idx < table_def_cache.records; idx++) + { + TABLE_SHARE *share= (TABLE_SHARE *) hash_element(&table_def_cache, idx); + + /* Ignore if table is not open or does not have a connect_string */ + if (!share->connect_string.length || !share->ref_count) + continue; + + /* Compare the connection string */ + if (connection && + (connection->length > share->connect_string.length || + (connection->length < share->connect_string.length && + (share->connect_string.str[connection->length] != '/' && + share->connect_string.str[connection->length] != '\\')) || + strncasecmp(connection->str, share->connect_string.str, + connection->length))) + continue; + + /* close_cached_tables() only uses these elements */ + tmp.db= share->db.str; + tmp.table_name= share->table_name.str; + tmp.next_local= tables; + + tables= (TABLE_LIST *) memdup_root(thd->mem_root, (char*)&tmp, + sizeof(TABLE_LIST)); + } + + if (tables) + result= close_cached_tables(thd, FALSE, tables, TRUE); + + if (!have_lock) + VOID(pthread_mutex_unlock(&LOCK_open)); + + if (if_wait_for_refresh) + { + pthread_mutex_lock(&thd->mysys_var->mutex); + thd->mysys_var->current_mutex= 0; + thd->mysys_var->current_cond= 0; + thd->proc_info=0; + pthread_mutex_unlock(&thd->mysys_var->mutex); + } + + DBUG_RETURN(result); +} + + +/* Mark all tables in the list which were used by current substatement as free for reuse. @@ -450,8 +1076,13 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table) { for (; table ; table= table->next) + { if (table->query_id == thd->query_id) + { table->query_id= 0; + table->file->ha_reset(); + } + } } @@ -531,21 +1162,13 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived) */ ha_commit_stmt(thd); + /* Ensure we are calling ha_reset() for all used tables */ + mark_used_tables_as_free_for_reuse(thd, thd->open_tables); + /* We are under simple LOCK TABLES so should not do anything else. */ - if (!prelocked_mode) + if (!prelocked_mode || !thd->lex->requires_prelocking()) DBUG_VOID_RETURN; - if (!thd->lex->requires_prelocking()) - { - /* - If we are executing one of substatements we have to mark - all tables which it used as free for reuse. - */ - mark_used_tables_as_free_for_reuse(thd, thd->open_tables); - DBUG_VOID_RETURN; - } - - DBUG_ASSERT(prelocked_mode); /* We are in prelocked mode, so we have to leave it now with doing implicit UNLOCK TABLES if need. @@ -563,6 +1186,16 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived) if (thd->lock) { + /* + For RBR we flush the pending event just before we unlock all the + tables. This means that we are at the end of a topmost + statement, so we ensure that the STMT_END_F flag is set on the + pending event. For statements that are *inside* stored + functions, the pending event will not be flushed: that will be + handled either before writing a query log event (inside + binlog_query()) or when preparing a pending event. + */ + thd->binlog_flush_pending_rows_event(TRUE); mysql_unlock_tables(thd, thd->lock); thd->lock=0; } @@ -574,18 +1207,16 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived) saves some work in 2pc too) see also sql_parse.cc - dispatch_command() */ - bzero(&thd->transaction.stmt, sizeof(thd->transaction.stmt)); + if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) + bzero(&thd->transaction.stmt, sizeof(thd->transaction.stmt)); if (!thd->active_transaction()) thd->transaction.xid_state.xid.null(); - /* VOID(pthread_sigmask(SIG_SETMASK,&thd->block_signals,NULL)); */ if (!lock_in_use) VOID(pthread_mutex_lock(&LOCK_open)); - safe_mutex_assert_owner(&LOCK_open); - DBUG_PRINT("info", ("thd->open_tables: %p", thd->open_tables)); + DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables)); - /* End open index scans and table scans and remove references to the tables from the handler tables hash. After this preparation it is safe to close @@ -595,7 +1226,7 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived) found_old_table= 0; while (thd->open_tables) - found_old_table|=close_thread_table(thd, &thd->open_tables); + found_old_table|= close_thread_table(thd, &thd->open_tables); thd->some_tables_deleted=0; /* Free tables to hold down open files */ @@ -624,6 +1255,7 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived) DBUG_VOID_RETURN; } + /* move one table to free list */ bool close_thread_table(THD *thd, TABLE **table_ptr) @@ -649,16 +1281,8 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) */ DBUG_ASSERT(!table->open_placeholder); - if (table->s->flush_version != flush_version) - { - table->s->flush_version= flush_version; - table->file->extra(HA_EXTRA_FLUSH); - } - else - { - // Free memory and reset for next loop - table->file->reset(); - } + /* Free memory and reset for next loop */ + table->file->ha_reset(); table->in_use=0; if (unused_tables) { @@ -673,64 +1297,62 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) DBUG_RETURN(found_old_table); } - /* Close and delete temporary tables */ - -void close_temporary(TABLE *table,bool delete_table) -{ - DBUG_ENTER("close_temporary"); - char path[FN_REFLEN]; - db_type table_type=table->s->db_type; - strmov(path,table->s->path); - free_io_cache(table); - closefrm(table); - my_free((char*) table,MYF(0)); - if (delete_table) - rm_temporary_table(table_type, path); - DBUG_VOID_RETURN; -} /* close_temporary_tables' internal, 4 is due to uint4korr definition */ static inline uint tmpkeyval(THD *thd, TABLE *table) { - return uint4korr(table->s->table_cache_key + table->s->key_length - 4); + return uint4korr(table->s->table_cache_key.str + table->s->table_cache_key.length - 4); } -/* Creates one DROP TEMPORARY TABLE binlog event for each pseudo-thread */ + +/* + Close all temporary tables created by 'CREATE TEMPORARY TABLE' for thread + creates one DROP TEMPORARY TABLE binlog event for each pseudo-thread +*/ void close_temporary_tables(THD *thd) { TABLE *table; + TABLE *next; + /* + TODO: 5.1 maintains prev link in temporary_tables + double-linked list so we could fix it. But it is not necessary + at this time when the list is being destroyed + */ + TABLE *prev_table; + /* Assume thd->options has OPTION_QUOTE_SHOW_CREATE */ + bool was_quote_show= TRUE; + if (!thd->temporary_tables) return; - if (!mysql_bin_log.is_open()) + if (!mysql_bin_log.is_open() || thd->current_stmt_binlog_row_based) { TABLE *next; for (table= thd->temporary_tables; table; table= next) { - next= table->next; - close_temporary(table, 1); + next=table->next; + close_temporary(table, 1, 1); } thd->temporary_tables= 0; return; } - TABLE *next, - *prev_table /* prev link is not maintained in TABLE's double-linked list */; - bool was_quote_show= true; /* to assume thd->options has OPTION_QUOTE_SHOW_CREATE */ - // Better add "if exists", in case a RESET MASTER has been done + /* Better add "if exists", in case a RESET MASTER has been done */ const char stub[]= "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS "; uint stub_len= sizeof(stub) - 1; char buf[256]; - memcpy(buf, stub, stub_len); String s_query= String(buf, sizeof(buf), system_charset_info); - bool found_user_tables= false; + bool found_user_tables= FALSE; LINT_INIT(next); + memcpy(buf, stub, stub_len); + /* insertion sort of temp tables by pseudo_thread_id to build ordered list of sublists of equal pseudo_thread_id */ + for (prev_table= thd->temporary_tables, table= prev_table->next; table; prev_table= table, table= table->next) @@ -776,10 +1398,13 @@ void close_temporary_tables(THD *thd) { if (is_user_table(table)) { + my_thread_id save_pseudo_thread_id= thd->variables.pseudo_thread_id; /* Set pseudo_thread_id to be that of the processed table */ thd->variables.pseudo_thread_id= tmpkeyval(thd, table); - /* Loop forward through all tables within the sublist of - common pseudo_thread_id to create single DROP query */ + /* + Loop forward through all tables within the sublist of + common pseudo_thread_id to create single DROP query. + */ for (s_query.length(stub_len); table && is_user_table(table) && tmpkeyval(thd, table) == thd->variables.pseudo_thread_id; @@ -789,13 +1414,13 @@ void close_temporary_tables(THD *thd) We are going to add 4 ` around the db/table names and possible more due to special characters in the names */ - append_identifier(thd, &s_query, table->s->db, strlen(table->s->db)); - s_query.q_append('.'); - append_identifier(thd, &s_query, table->s->table_name, - strlen(table->s->table_name)); - s_query.q_append(','); + append_identifier(thd, &s_query, table->s->db.str, strlen(table->s->db.str)); + s_query.append('.'); + append_identifier(thd, &s_query, table->s->table_name.str, + strlen(table->s->table_name.str)); + s_query.append(','); next= table->next; - close_temporary(table, 1); + close_temporary(table, 1, 1); } thd->clear_error(); CHARSET_INFO *cs_save= thd->variables.character_set_client; @@ -805,29 +1430,30 @@ void close_temporary_tables(THD *thd) 0, FALSE); thd->variables.character_set_client= cs_save; /* - Imagine the thread had created a temp table, then was doing a SELECT, and - the SELECT was killed. Then it's not clever to mark the statement above as - "killed", because it's not really a statement updating data, and there - are 99.99% chances it will succeed on slave. - If a real update (one updating a persistent table) was killed on the - master, then this real update will be logged with error_code=killed, - rightfully causing the slave to stop. + Imagine the thread had created a temp table, then was doing a + SELECT, and the SELECT was killed. Then it's not clever to + mark the statement above as "killed", because it's not really + a statement updating data, and there are 99.99% chances it + will succeed on slave. If a real update (one updating a + persistent table) was killed on the master, then this real + update will be logged with error_code=killed, rightfully + causing the slave to stop. */ qinfo.error_code= 0; mysql_bin_log.write(&qinfo); + thd->variables.pseudo_thread_id= save_pseudo_thread_id; } else { next= table->next; - close_temporary(table, 1); + close_temporary(table, 1, 1); } } if (!was_quote_show) - thd->options &= ~OPTION_QUOTE_SHOW_CREATE; /* restore option */ + thd->options&= ~OPTION_QUOTE_SHOW_CREATE; /* restore option */ thd->temporary_tables=0; } - /* Find table in list. @@ -1015,43 +1641,119 @@ void update_non_unique_table_error(TABLE_LIST *update, } -TABLE **find_temporary_table(THD *thd, const char *db, const char *table_name) +TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name) { - char key[MAX_DBKEY_LENGTH]; - uint key_length= (uint) (strmov(strmov(key,db)+1,table_name)-key)+1; - TABLE *table,**prev; + TABLE_LIST table_list; + + table_list.db= (char*) db; + table_list.table_name= (char*) table_name; + return find_temporary_table(thd, &table_list); +} - int4store(key+key_length,thd->server_id); - key_length += 4; - int4store(key+key_length,thd->variables.pseudo_thread_id); - key_length += 4; - prev= &thd->temporary_tables; - for (table=thd->temporary_tables ; table ; table=table->next) +TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + TABLE *table; + DBUG_ENTER("find_temporary_table"); + DBUG_PRINT("enter", ("table: '%s'.'%s'", + table_list->db, table_list->table_name)); + + key_length= create_table_def_key(thd, key, table_list, 1); + for (table=thd->temporary_tables ; table ; table= table->next) { - if (table->s->key_length == key_length && - !memcmp(table->s->table_cache_key,key,key_length)) - return prev; - prev= &table->next; + if (table->s->table_cache_key.length == key_length && + !memcmp(table->s->table_cache_key.str, key, key_length)) + { + DBUG_PRINT("info", + ("Found table. server_id: %u pseudo_thread_id: %lu", + (uint) thd->server_id, + (ulong) thd->variables.pseudo_thread_id)); + DBUG_RETURN(table); + } } - return 0; // Not a temporary table + DBUG_RETURN(0); // Not a temporary table } -bool close_temporary_table(THD *thd, const char *db, const char *table_name) + +/* + Close temporary table and unlink from thd->temporary tables +*/ + +bool close_temporary_table(THD *thd, TABLE_LIST *table_list) { - TABLE *table,**prev; + TABLE *table; - if (!(prev=find_temporary_table(thd,db,table_name))) + if (!(table= find_temporary_table(thd, table_list))) return 1; - table= *prev; - *prev= table->next; - close_temporary(table, 1); - if (thd->slave_thread) - --slave_open_temp_tables; + close_temporary_table(thd, table, 1, 1); return 0; } /* + unlink from thd->temporary tables and close temporary table +*/ + +void close_temporary_table(THD *thd, TABLE *table, + bool free_share, bool delete_table) +{ + if (table->prev) + { + table->prev->next= table->next; + if (table->prev->next) + table->next->prev= table->prev; + } + else + { + /* removing the item from the list */ + DBUG_ASSERT(table == thd->temporary_tables); + /* + slave must reset its temporary list pointer to zero to exclude + passing non-zero value to end_slave via rli->save_temporary_tables + when no temp tables opened, see an invariant below. + */ + thd->temporary_tables= table->next; + if (thd->temporary_tables) + table->next->prev= 0; + } + if (thd->slave_thread) + { + /* natural invariant of temporary_tables */ + DBUG_ASSERT(slave_open_temp_tables || !thd->temporary_tables); + slave_open_temp_tables--; + } + close_temporary(table, free_share, delete_table); +} + + +/* + Close and delete a temporary table + + NOTE + This dosn't unlink table from thd->temporary + If this is needed, use close_temporary_table() +*/ + +void close_temporary(TABLE *table, bool free_share, bool delete_table) +{ + handlerton *table_type= table->s->db_type(); + DBUG_ENTER("close_temporary"); + + free_io_cache(table); + closefrm(table, 0); + if (delete_table) + rm_temporary_table(table_type, table->s->path.str); + if (free_share) + { + free_table_share(table->s); + my_free((char*) table,MYF(0)); + } + DBUG_VOID_RETURN; +} + + +/* Used by ALTER TABLE when the table is a temporary one. It changes something only if the ALTER contained a RENAME clause (otherwise, table_name is the old name). @@ -1063,22 +1765,19 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db, const char *table_name) { char *key; + uint key_length; TABLE_SHARE *share= table->s; + TABLE_LIST table_list; + DBUG_ENTER("rename_temporary_table"); - if (!(key=(char*) alloc_root(&table->mem_root, - (uint) strlen(db)+ - (uint) strlen(table_name)+6+4))) - return 1; /* purecov: inspected */ - share->key_length= (uint) - (strmov((char*) (share->table_name= strmov(share->table_cache_key= key, - db)+1), - table_name) - share->table_cache_key)+1; - share->db= share->table_cache_key; - int4store(key+share->key_length, thd->server_id); - share->key_length+= 4; - int4store(key+share->key_length, thd->variables.pseudo_thread_id); - share->key_length+= 4; - return 0; + if (!(key=(char*) alloc_root(&share->mem_root, MAX_DBKEY_LENGTH))) + DBUG_RETURN(1); /* purecov: inspected */ + + table_list.db= (char*) db; + table_list.table_name= (char*) table_name; + key_length= create_table_def_key(thd, key, &table_list, 1); + share->set_table_cache_key(key, key_length); + DBUG_RETURN(0); } @@ -1100,26 +1799,40 @@ static void relink_unused(TABLE *table) } -/* - Remove all instances of table from the current open list - Free all locks on tables that are done with LOCK TABLES - */ +/** + @brief Remove all instances of table from thread's open list and + table cache. + + @param thd Thread context + @param find Table to remove + @param unlock TRUE - free all locks on tables removed that are + done with LOCK TABLES + FALSE - otherwise + + @note When unlock parameter is FALSE or current thread doesn't have + any tables locked with LOCK TABLES tables are assumed to be + not locked (for example already unlocked). +*/ -TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find) +void unlink_open_table(THD *thd, TABLE *find, bool unlock) { char key[MAX_DBKEY_LENGTH]; - uint key_length= find->s->key_length; - TABLE *start=list,**prev,*next; - prev= &start; + uint key_length= find->s->table_cache_key.length; + TABLE *list, **prev, *next; + DBUG_ENTER("unlink_open_table"); + + safe_mutex_assert_owner(&LOCK_open); - memcpy(key, find->s->table_cache_key, key_length); + list= thd->open_tables; + prev= &thd->open_tables; + memcpy(key, find->s->table_cache_key.str, key_length); for (; list ; list=next) { next=list->next; - if (list->s->key_length == key_length && - !memcmp(list->s->table_cache_key, key, key_length)) + if (list->s->table_cache_key.length == key_length && + !memcmp(list->s->table_cache_key.str, key, key_length)) { - if (thd->locked_tables) + if (unlock && thd->locked_tables) mysql_lock_remove(thd, thd->locked_tables,list); VOID(hash_delete(&open_cache,(byte*) list)); // Close table } @@ -1132,7 +1845,7 @@ TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find) *prev=0; // Notify any 'refresh' threads broadcast_refresh(); - return start; + DBUG_VOID_RETURN; } @@ -1146,53 +1859,65 @@ TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find) @note This routine assumes that table to be closed is open only by calling thread so we needn't wait until other threads - will close the table. Also unless called under implicit or - explicit LOCK TABLES mode it assumes that table to be - dropped is already unlocked. In the former case it will - also remove lock on the table. But one should not rely on - this behaviour as it may change in future. + will close the table. It also assumes that table to be + dropped is already unlocked. */ void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name) { if (table->s->tmp_table) - close_temporary_table(thd, db_name, table_name); + close_temporary_table(thd, table, 1, 1); else { - enum db_type table_type= table->s->db_type; + handlerton *table_type= table->s->db_type(); VOID(pthread_mutex_lock(&LOCK_open)); /* unlink_open_table() also tells threads waiting for refresh or close that something has happened. */ - thd->open_tables= unlink_open_table(thd, thd->open_tables, table); - quick_rm_table(table_type, db_name, table_name); + unlink_open_table(thd, table, FALSE); + quick_rm_table(table_type, db_name, table_name, 0); VOID(pthread_mutex_unlock(&LOCK_open)); } } /* - When we call the following function we must have a lock on - LOCK_open ; This lock will be unlocked on return. + Wait for condition but allow the user to send a kill to mysqld + + SYNOPSIS + wait_for_condition() + thd Thread handler + mutex mutex that is currently hold that is associated with condition + Will be unlocked on return + cond Condition to wait for */ -void wait_for_refresh(THD *thd) +void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond) { - DBUG_ENTER("wait_for_refresh"); - safe_mutex_assert_owner(&LOCK_open); - /* Wait until the current table is up to date */ const char *proc_info; - thd->mysys_var->current_mutex= &LOCK_open; - thd->mysys_var->current_cond= &COND_refresh; + thd->mysys_var->current_mutex= mutex; + thd->mysys_var->current_cond= cond; proc_info=thd->proc_info; thd->proc_info="Waiting for table"; + DBUG_ENTER("wait_for_condition"); if (!thd->killed) - (void) pthread_cond_wait(&COND_refresh,&LOCK_open); + (void) pthread_cond_wait(cond, mutex); - pthread_mutex_unlock(&LOCK_open); // Must be unlocked first + /* + We must unlock mutex first to avoid deadlock becasue conditions are + sent to this thread by doing locks in the following order: + lock(mysys_var->mutex) + lock(mysys_var->current_mutex) + + One by effect of this that one can only use wait_for_condition with + condition variables that are guranteed to not disapper (freed) even if this + mutex is unlocked + */ + + pthread_mutex_unlock(mutex); pthread_mutex_lock(&thd->mysys_var->mutex); thd->mysys_var->current_mutex= 0; thd->mysys_var->current_cond= 0; @@ -1229,10 +1954,7 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) { TABLE *table= table_list->table; TABLE_SHARE *share; - char *db= table_list->db; char *table_name= table_list->table_name; - char key[MAX_DBKEY_LENGTH]; - uint key_length; TABLE orig_table; DBUG_ENTER("reopen_name_locked_table"); @@ -1242,12 +1964,10 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) DBUG_RETURN(TRUE); orig_table= *table; - key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; - if (open_unireg_entry(thd, table, db, table_name, table_name, 0, - thd->mem_root, 0) || - !(table->s->table_cache_key= memdup_root(&table->mem_root, (char*) key, - key_length))) + if (open_unireg_entry(thd, table, table_list, table_name, + table->s->table_cache_key.str, + table->s->table_cache_key.length, thd->mem_root, 0)) { intern_close_table(table); /* @@ -1261,8 +1981,6 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) } share= table->s; - share->db= share->table_cache_key; - share->key_length=key_length; /* We want to prevent other connections from opening this table until end of statement as it is likely that modifications of table's metadata are @@ -1272,7 +1990,6 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) before we will get table-level lock on this table. */ share->version=0; - share->flush_version=0; table->in_use = thd; check_unused(); @@ -1295,8 +2012,6 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) table->const_table=0; table->null_row= table->maybe_null= table->force_index= 0; table->status=STATUS_NO_RECORD; - table->keys_in_use_for_query= share->keys_in_use; - table->used_keys= share->keys_for_keyread; DBUG_RETURN(FALSE); } @@ -1306,9 +2021,9 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) which will prevent its opening (or creation) (a.k.a lock table name). - @param thd Thread context - @param key Table cache key for name to be locked - @param key_length Table cache key length + @param thd Thread context + @param key Table cache key for name to be locked + @param key_length Table cache key length @return Pointer to TABLE object used for name locking or 0 in case of failure. @@ -1318,6 +2033,7 @@ TABLE *table_cache_insert_placeholder(THD *thd, const char *key, uint key_length) { TABLE *table; + TABLE_SHARE *share; char *key_buff; DBUG_ENTER("table_cache_insert_placeholder"); @@ -1330,18 +2046,16 @@ TABLE *table_cache_insert_placeholder(THD *thd, const char *key, */ if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL), &table, sizeof(*table), + &share, sizeof(*share), &key_buff, key_length, NULL)) DBUG_RETURN(NULL); - table->s= &table->share_not_to_be_used; - memcpy(key_buff, key, key_length); - table->s->table_cache_key= key_buff; - table->s->db= table->s->table_cache_key; - table->s->table_name= table->s->table_cache_key + strlen(table->s->db) + 1; - table->s->key_length= key_length; + table->s= share; + share->set_table_cache_key(key_buff, key, key_length); + share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table table->in_use= thd; - table->locked_by_name= 1; + table->locked_by_name=1; if (my_hash_insert(&open_cache, (byte*)table)) { @@ -1354,57 +2068,60 @@ TABLE *table_cache_insert_placeholder(THD *thd, const char *key, /** - @brief Check if table cache contains an open placeholder for the - table and if this placeholder was created by another thread. - - @param thd Thread context - @param db Name of database for table in question - @param table_name Table name - - @note The presence of open placeholder indicates that either some - other thread is trying to create table in question and obtained - an exclusive name-lock on it or that this table already exists - and is being flushed at the moment. - - @note One should acquire LOCK_open mutex before calling this function. - - @note This function is a hack which was introduced in 5.0 only to - minimize code changes. It doesn't present in 5.1. - - @retval TRUE Table cache contains open placeholder for the table - which was created by some other thread. - @retval FALSE Otherwise. + @brief Obtain an exclusive name lock on the table if it is not cached + in the table cache. + + @param thd Thread context + @param db Name of database + @param table_name Name of table + @param[out] table Out parameter which is either: + - set to NULL if table cache contains record for + the table or + - set to point to the TABLE instance used for + name-locking. + + @note This function takes into account all records for table in table + cache, even placeholders used for name-locking. This means that + 'table' parameter can be set to NULL for some situations when + table does not really exist. + + @retval TRUE Error occured (OOM) + @retval FALSE Success. 'table' parameter set according to above rules. */ -bool table_cache_has_open_placeholder(THD *thd, const char *db, - const char *table_name) +bool lock_table_name_if_not_cached(THD *thd, const char *db, + const char *table_name, TABLE **table) { char key[MAX_DBKEY_LENGTH]; uint key_length; - HASH_SEARCH_STATE state; - TABLE *search; - DBUG_ENTER("table_cache_has_open_placeholder"); + DBUG_ENTER("lock_table_name_if_not_cached"); - safe_mutex_assert_owner(&LOCK_open); + key_length= (uint)(strmov(strmov(key, db) + 1, table_name) - key) + 1; + VOID(pthread_mutex_lock(&LOCK_open)); - key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; - for (search= (TABLE*) hash_first(&open_cache, (byte*) key, key_length, - &state); - search ; - search= (TABLE*) hash_next(&open_cache, (byte*) key, key_length, - &state)) + if (hash_search(&open_cache, (byte *)key, key_length)) { - if (search->in_use == thd) - continue; - if (search->open_placeholder) - DBUG_RETURN(1); + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_PRINT("info", ("Table is cached, name-lock is not obtained")); + *table= 0; + DBUG_RETURN(FALSE); } - DBUG_RETURN(0); + if (!(*table= table_cache_insert_placeholder(thd, key, key_length))) + { + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_RETURN(TRUE); + } + (*table)->open_placeholder= 1; + (*table)->next= thd->open_tables; + thd->open_tables= *table; + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_RETURN(FALSE); } /** - @brief Check that table exists on disk or in some storage engine. + @brief Check that table exists in table definition cache, on disk + or in some storage engine. @param thd Thread context @param table Table list element @@ -1433,7 +2150,11 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) *exists= TRUE; - build_table_path(path, sizeof(path), table->db, table->table_name, reg_ext); + if (get_cached_table_share(table->db, table->table_name)) + DBUG_RETURN(FALSE); + + build_table_filename(path, sizeof(path) - 1, table->db, table->table_name, + reg_ext, 0); if (!access(path, F_OK)) DBUG_RETURN(FALSE); @@ -1515,10 +2236,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (thd->killed) DBUG_RETURN(0); - key_length= (uint) (strmov(strmov(key, table_list->db)+1, - table_list->table_name)-key)+1; - int4store(key + key_length, thd->server_id); - int4store(key + key_length + 4, thd->variables.pseudo_thread_id); + + key_length= (create_table_def_key(thd, key, table_list, 1) - + TMP_TABLE_KEY_EXTRA); /* Unless requested otherwise, try to resolve this table in the list @@ -1531,8 +2251,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { for (table= thd->temporary_tables; table ; table=table->next) { - if (table->s->key_length == key_length + TMP_TABLE_KEY_EXTRA && - !memcmp(table->s->table_cache_key, key, + if (table->s->table_cache_key.length == key_length + + TMP_TABLE_KEY_EXTRA && + !memcmp(table->s->table_cache_key.str, key, key_length + TMP_TABLE_KEY_EXTRA)) { /* @@ -1544,6 +2265,10 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (table->query_id == thd->query_id || thd->prelocked_mode && table->query_id) { + DBUG_PRINT("error", + ("query_id: %lu server_id: %u pseudo_thread_id: %lu", + (ulong) table->query_id, (uint) thd->server_id, + (ulong) thd->variables.pseudo_thread_id)); my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); DBUG_RETURN(0); } @@ -1578,8 +2303,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, (int) TL_WRITE_ALLOW_WRITE); for (table=thd->open_tables; table ; table=table->next) { - if (table->s->key_length == key_length && - !memcmp(table->s->table_cache_key, key, key_length)) + if (table->s->table_cache_key.length == key_length && + !memcmp(table->s->table_cache_key.str, key, key_length)) { if (check_if_used && table->query_id && table->query_id != thd->query_id) @@ -1591,7 +2316,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, is not already open by some calling stamement. */ my_error(ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG, MYF(0), - table->s->table_name); + table->s->table_name.str); DBUG_RETURN(0); } if (!my_strcasecmp(system_charset_info, table->alias, alias) && @@ -1645,10 +2370,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ { char path[FN_REFLEN]; - db_type not_used; - strxnmov(path, FN_REFLEN, mysql_data_home, "/", table_list->db, "/", - table_list->table_name, reg_ext, NullS); - (void) unpack_filename(path, path); + enum legacy_db_type not_used; + build_table_filename(path, sizeof(path) - 1, + table_list->db, table_list->table_name, reg_ext, 0); if (mysql_frm_type(thd, path, ¬_used) == FRMTYPE_VIEW) { /* @@ -1658,9 +2382,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, TABLE tab; table= &tab; VOID(pthread_mutex_lock(&LOCK_open)); - if (!open_unireg_entry(thd, table, table_list->db, - table_list->table_name, - alias, table_list, mem_root, 0)) + if (!open_unireg_entry(thd, table, table_list, alias, + key, key_length, mem_root, 0)) { DBUG_ASSERT(table_list->view != 0); VOID(pthread_mutex_unlock(&LOCK_open)); @@ -1742,6 +2465,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, &state)) { /* + Here we flush tables marked for flush. However we never flush log + tables here. They are flushed only on FLUSH LOGS. Normally, table->s->version contains the value of refresh_version from the moment when this table was (re-)opened and added to the cache. @@ -1758,11 +2483,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, c1: name lock t2; -- blocks c2: open t1; -- blocks */ - if (table->needs_reopen_or_name_lock()) + if (table->needs_reopen_or_name_lock() && !table->s->log_table) { DBUG_PRINT("note", ("Found table '%s.%s' with different refresh version", table_list->db, table_list->table_name)); + if (flags & MYSQL_LOCK_IGNORE_FLUSH) { /* Force close at once after usage */ @@ -1774,7 +2500,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (table->open_placeholder && table->in_use == thd) { VOID(pthread_mutex_unlock(&LOCK_open)); - my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name); + my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str); DBUG_RETURN(0); } @@ -1805,8 +2531,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ if (table->in_use != thd) { - wait_for_refresh(thd); - /* wait_for_refresh will unlock LOCK_open for us */ + /* wait_for_conditionwill unlock LOCK_open for us */ + wait_for_condition(thd, &LOCK_open, &COND_refresh); } else { @@ -1837,7 +2563,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, else { /* Insert a new TABLE instance into the open cache */ - TABLE_SHARE *share; int error; /* Free cache if too big */ while (open_cache.records > table_cache_size && unused_tables) @@ -1883,18 +2608,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, VOID(pthread_mutex_unlock(&LOCK_open)); DBUG_RETURN(NULL); } - error= open_unireg_entry(thd, table, table_list->db, - table_list->table_name, - alias, table_list, mem_root, - (flags & OPEN_VIEW_NO_PARSE)); - if ((error > 0) || - (!table_list->view && !error && - !(table->s->table_cache_key= memdup_root(&table->mem_root, - (char*) key, - key_length)))) + + error= open_unireg_entry(thd, table, table_list, alias, key, key_length, + mem_root, (flags & OPEN_VIEW_NO_PARSE)); + if (error > 0) { - table->next=table->prev=table; - free_cache_entry(table); + my_free((gptr)table, MYF(0)); VOID(pthread_mutex_unlock(&LOCK_open)); DBUG_RETURN(NULL); } @@ -1911,12 +2630,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, VOID(pthread_mutex_unlock(&LOCK_open)); DBUG_RETURN(0); // VIEW } - share= table->s; - share->db= share->table_cache_key; - share->key_length= key_length; - share->version= refresh_version; - share->flush_version= flush_version; - DBUG_PRINT("info", ("inserting table %p into the cache", table)); + DBUG_PRINT("info", ("inserting table 0x%lx into the cache", (long) table)); VOID(my_hash_insert(&open_cache,(byte*) table)); } @@ -1931,9 +2645,11 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->reginfo.lock_type=TL_READ; /* Assume read */ reset: + DBUG_ASSERT(table->s->ref_count > 0 || table->s->tmp_table != NO_TMP_TABLE); + if (thd->lex->need_correct_ident()) table->alias_name_used= my_strcasecmp(table_alias_charset, - table->s->table_name, alias); + table->s->table_name.str, alias); /* Fix alias if table name changes */ if (strcmp(table->alias, alias)) { @@ -1948,9 +2664,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->const_table=0; table->null_row= table->maybe_null= table->force_index= 0; table->status=STATUS_NO_RECORD; - table->keys_in_use_for_query= table->s->keys_in_use; table->insert_values= 0; - table->used_keys= table->s->keys_for_keyread; table->fulltext_searched= 0; table->file->ft_handler= 0; /* Catch wrong handling of the auto_increment_field_not_null. */ @@ -1960,6 +2674,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->timestamp_field_type= table->timestamp_field->get_auto_set_type(); table->pos_in_table_list= table_list; table_list->updatable= 1; // It is not derived table nor non-updatable VIEW + table->clear_column_bitmaps(); DBUG_ASSERT(table->key_read == 0); DBUG_RETURN(table); } @@ -1972,63 +2687,63 @@ TABLE *find_locked_table(THD *thd, const char *db,const char *table_name) for (TABLE *table=thd->open_tables; table ; table=table->next) { - if (table->s->key_length == key_length && - !memcmp(table->s->table_cache_key,key,key_length)) + if (table->s->table_cache_key.length == key_length && + !memcmp(table->s->table_cache_key.str, key, key_length)) return table; } return(0); } -/**************************************************************************** - Reopen an table because the definition has changed. The date file for the - table is already closed. +/* + Reopen an table because the definition has changed. SYNOPSIS reopen_table() - table Table to be opened - locked 1 if we have already a lock on LOCK_open + table Table object NOTES - table->query_id will be 0 if table was reopened + The data file for the table is already closed and the share is released + The table has a 'dummy' share that mainly contains database and table name. - RETURN - 0 ok - 1 error ('table' is unchanged if table couldn't be reopened) -****************************************************************************/ + RETURN + 0 ok + 1 error. The old table object is not changed. +*/ -bool reopen_table(TABLE *table,bool locked) +bool reopen_table(TABLE *table) { TABLE tmp; - char *db= table->s->table_cache_key; - const char *table_name= table->s->table_name; bool error= 1; Field **field; uint key,part; + TABLE_LIST table_list; + THD *thd= table->in_use; DBUG_ENTER("reopen_table"); + DBUG_ASSERT(table->s->ref_count == 0); + DBUG_ASSERT(!table->sort.io_cache); + #ifdef EXTRA_DEBUG if (table->db_stat) sql_print_error("Table %s had a open data handler in reopen_table", table->alias); #endif - if (!locked) - VOID(pthread_mutex_lock(&LOCK_open)); - safe_mutex_assert_owner(&LOCK_open); - - if (open_unireg_entry(table->in_use, &tmp, db, table_name, - table->alias, 0, table->in_use->mem_root, 0)) + table_list.db= table->s->db.str; + table_list.table_name= table->s->table_name.str; + table_list.table= table; + table_list.belong_to_view= 0; + table_list.next_local= 0; + + if (wait_for_locked_table_names(thd, &table_list)) + DBUG_RETURN(1); // Thread was killed + + if (open_unireg_entry(thd, &tmp, &table_list, + table->alias, + table->s->table_cache_key.str, + table->s->table_cache_key.length, + thd->mem_root, 0)) goto end; - free_io_cache(table); - - if (!(tmp.s->table_cache_key= memdup_root(&tmp.mem_root,db, - table->s->key_length))) - { - delete tmp.triggers; - closefrm(&tmp); // End of memory - goto end; - } - tmp.s->db= tmp.s->table_cache_key; /* This list copies variables set by open_table */ tmp.tablenr= table->tablenr; @@ -2037,15 +2752,12 @@ bool reopen_table(TABLE *table,bool locked) tmp.null_row= table->null_row; tmp.maybe_null= table->maybe_null; tmp.status= table->status; - tmp.keys_in_use_for_query= tmp.s->keys_in_use; - tmp.used_keys= tmp.s->keys_for_keyread; + + tmp.s->table_map_id= table->s->table_map_id; /* Get state */ - tmp.s->key_length= table->s->key_length; - tmp.in_use= table->in_use; + tmp.in_use= thd; tmp.reginfo.lock_type=table->reginfo.lock_type; - tmp.s->version= refresh_version; - tmp.s->tmp_table= table->s->tmp_table; tmp.grant= table->grant; /* Replace table in open list */ @@ -2054,11 +2766,11 @@ bool reopen_table(TABLE *table,bool locked) delete table->triggers; if (table->file) - VOID(closefrm(table)); // close file, free everything + VOID(closefrm(table, 1)); // close file, free everything *table= tmp; - table->s= &table->share_not_to_be_used; - table->file->change_table_ptr(table); + table->default_column_bitmaps(); + table->file->change_table_ptr(table, table->s); DBUG_ASSERT(table->alias != 0); for (field=table->field ; *field ; field++) @@ -2078,32 +2790,59 @@ bool reopen_table(TABLE *table,bool locked) error=0; end: - if (!locked) - VOID(pthread_mutex_unlock(&LOCK_open)); DBUG_RETURN(error); } -/* - Used with ALTER TABLE: - Close all instanses of table when LOCK TABLES is in used; - Close first all instances of table and then reopen them - */ +/** + @brief Close all instances of a table open by this thread and replace + them with exclusive name-locks. + + @param thd Thread context + @param db Database name for the table to be closed + @param table_name Name of the table to be closed + + @note This function assumes that if we are not under LOCK TABLES, + then there is only one table open and locked. This means that + the function probably has to be adjusted before it can be used + anywhere outside ALTER TABLE. +*/ -bool close_data_tables(THD *thd,const char *db, const char *table_name) +void close_data_files_and_morph_locks(THD *thd, const char *db, + const char *table_name) { TABLE *table; + DBUG_ENTER("close_data_files_and_morph_locks"); + + safe_mutex_assert_owner(&LOCK_open); + + if (thd->lock) + { + /* + If we are not under LOCK TABLES we should have only one table + open and locked so it makes sense to remove the lock at once. + */ + mysql_unlock_tables(thd, thd->lock); + thd->lock= 0; + } + + /* + Note that open table list may contain a name-lock placeholder + for target table name if we process ALTER TABLE ... RENAME. + So loop below makes sense even if we are not under LOCK TABLES. + */ for (table=thd->open_tables; table ; table=table->next) { - if (!strcmp(table->s->table_name, table_name) && - !strcmp(table->s->db, db)) + if (!strcmp(table->s->table_name.str, table_name) && + !strcmp(table->s->db.str, db)) { - mysql_lock_remove(thd, thd->locked_tables,table); - table->file->close(); - table->db_stat=0; + if (thd->locked_tables) + mysql_lock_remove(thd, thd->locked_tables, table); + table->open_placeholder= 1; + close_handle_and_leave_table_as_lock(table); } } - return 0; // For the future + DBUG_VOID_RETURN; } @@ -2129,20 +2868,21 @@ bool close_data_tables(THD *thd,const char *db, const char *table_name) bool reopen_tables(THD *thd,bool get_locks,bool in_refresh) { + TABLE *table,*next,**prev; + TABLE **tables,**tables_ptr; // For locks + bool error=0, not_used; DBUG_ENTER("reopen_tables"); - safe_mutex_assert_owner(&LOCK_open); if (!thd->open_tables) DBUG_RETURN(0); - TABLE *table,*next,**prev; - TABLE **tables,**tables_ptr; // For locks - bool error=0, not_used; + safe_mutex_assert_owner(&LOCK_open); if (get_locks) { /* The ptr is checked later */ uint opens=0; - for (table=thd->open_tables; table ; table=table->next) opens++; + for (table= thd->open_tables; table ; table=table->next) + opens++; tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens); } else @@ -2154,7 +2894,7 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh) { uint db_stat=table->db_stat; next=table->next; - if (!tables || (!db_stat && reopen_table(table,1))) + if (!tables || (!db_stat && reopen_table(table))) { my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); VOID(hash_delete(&open_cache,(byte*) table)); @@ -2213,40 +2953,37 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh) void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, bool send_refresh) { + bool found= send_refresh; DBUG_ENTER("close_old_data_files"); - bool found=send_refresh; + for (; table ; table=table->next) { - if (table->needs_reopen_or_name_lock()) + /* + Reopen marked for flush. But close log tables. They are flushed only + explicitly on FLUSH LOGS + */ + if (table->needs_reopen_or_name_lock() && !table->s->log_table) { found=1; - /* - Note that it is safe to update version even for open placeholders - as later in this function we reset TABLE::open_placeholder and thus - effectively remove them from the table cache. - */ - if (!morph_locks) // If not from flush tables - table->s->version= refresh_version; // Let other threads use table if (table->db_stat) { - if (morph_locks) - { + if (morph_locks) + { /* Wake up threads waiting for table-level lock on this table so they won't sneak in when we will temporarily remove our lock on it. This will also give them a chance to close their instances of this table. */ - mysql_lock_abort(thd, table); + mysql_lock_abort(thd, table, TRUE); mysql_lock_remove(thd, thd->locked_tables, table); /* We want to protect the table from concurrent DDL operations (like RENAME TABLE) until we will re-open and re-lock it. */ - table->open_placeholder= 1; - } - table->file->close(); - table->db_stat=0; + table->open_placeholder= 1; + } + close_handle_and_leave_table_as_lock(table); } else if (table->open_placeholder) { @@ -2272,14 +3009,21 @@ void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, Wait until all threads has closed the tables in the list We have also to wait if there is thread that has a lock on this table even if the table is closed + NOTE: log tables are handled differently by the logging routines. + E.g. general_log is always opened and locked by the logger + and the table handler used by the logger, will be skipped by + this check. */ bool table_is_used(TABLE *table, bool wait_for_name_lock) { + DBUG_ENTER("table_is_used"); do { - char *key= table->s->table_cache_key; - uint key_length= table->s->key_length; + char *key= table->s->table_cache_key.str; + uint key_length= table->s->table_cache_key.length; + + DBUG_PRINT("loop", ("table_name: %s", table->alias)); HASH_SEARCH_STATE state; for (TABLE *search= (TABLE*) hash_first(&open_cache, (byte*) key, key_length, &state); @@ -2287,12 +3031,31 @@ bool table_is_used(TABLE *table, bool wait_for_name_lock) search= (TABLE*) hash_next(&open_cache, (byte*) key, key_length, &state)) { - if (search->locked_by_name && wait_for_name_lock || - search->is_name_opened() && search->needs_reopen_or_name_lock()) - return 1; // Table is used + DBUG_PRINT("info", ("share: 0x%lx locked_by_logger: %d " + "open_placeholder: %d locked_by_name: %d " + "db_stat: %u version: %lu", + (ulong) search->s, search->locked_by_logger, + search->open_placeholder, search->locked_by_name, + search->db_stat, + search->s->version)); + if (search->in_use == table->in_use) + continue; // Name locked by this thread + /* + We can't use the table under any of the following conditions: + - There is an name lock on it (Table is to be deleted or altered) + - If we are in flush table and we didn't execute the flush + - If the table engine is open and it's an old version + (We must wait until all engines are shut down to use the table) + However we fo not wait if we encountered a table, locked by the logger. + Log tables are managed separately by logging routines. + */ + if (!search->locked_by_logger && + (search->locked_by_name && wait_for_name_lock || + (search->is_name_opened() && search->needs_reopen_or_name_lock()))) + DBUG_RETURN(1); } } while ((table=table->next)); - return 0; + DBUG_RETURN(0); } @@ -2329,22 +3092,58 @@ bool wait_for_tables(THD *thd) } -/* drop tables from locked list */ +/* + drop tables from locked list + + SYNOPSIS + drop_locked_tables() + thd Thread thandler + db Database + table_name Table name + + INFORMATION + This is only called on drop tables -bool drop_locked_tables(THD *thd,const char *db, const char *table_name) + The TABLE object for the dropped table is unlocked but still kept around + as a name lock, which means that the table will be available for other + thread as soon as we call unlock_table_names(). + If there is multiple copies of the table locked, all copies except + the first, which acts as a name lock, is removed. + + RETURN + # If table existed, return table + 0 Table was not locked +*/ + + +TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name) { - TABLE *table,*next,**prev; - bool found=0; + TABLE *table,*next,**prev, *found= 0; prev= &thd->open_tables; + DBUG_ENTER("drop_locked_tables"); + for (table= thd->open_tables; table ; table=next) { next=table->next; - if (!strcmp(table->s->table_name, table_name) && - !strcmp(table->s->db, db)) + if (!strcmp(table->s->table_name.str, table_name) && + !strcmp(table->s->db.str, db)) { mysql_lock_remove(thd, thd->locked_tables,table); - VOID(hash_delete(&open_cache,(byte*) table)); - found=1; + if (!found) + { + found= table; + /* Close engine table, but keep object around as a name lock */ + if (table->db_stat) + { + table->db_stat= 0; + table->file->close(); + } + } + else + { + /* We already have a name lock, remove copy */ + VOID(hash_delete(&open_cache,(byte*) table)); + } } else { @@ -2360,7 +3159,7 @@ bool drop_locked_tables(THD *thd,const char *db, const char *table_name) my_free((gptr) thd->locked_tables,MYF(0)); thd->locked_tables=0; } - return found; + DBUG_RETURN(found); } @@ -2375,10 +3174,10 @@ void abort_locked_tables(THD *thd,const char *db, const char *table_name) TABLE *table; for (table= thd->open_tables; table ; table= table->next) { - if (!strcmp(table->s->table_name,table_name) && - !strcmp(table->s->db, db)) + if (!strcmp(table->s->table_name.str, table_name) && + !strcmp(table->s->db.str, db)) { - mysql_lock_abort(thd,table); + mysql_lock_abort(thd,table, TRUE); break; } } @@ -2386,146 +3185,216 @@ void abort_locked_tables(THD *thd,const char *db, const char *table_name) /* + Function to assign a new table map id to a table share. + + PARAMETERS + + share - Pointer to table share structure + + DESCRIPTION + + We are intentionally not checking that share->mutex is locked + since this function should only be called when opening a table + share and before it is entered into the table_def_cache (meaning + that it cannot be fetched by another thread, even accidentally). + + PRE-CONDITION(S) + + share is non-NULL + The LOCK_open mutex is locked + + POST-CONDITION(S) + + share->table_map_id is given a value that with a high certainty is + not used by any other table (the only case where a table id can be + reused is on wrap-around, which means more than 4 billion table + shares open at the same time). + + share->table_map_id is not ~0UL. + */ +void assign_new_table_id(TABLE_SHARE *share) +{ + static ulong last_table_id= ~0UL; + + DBUG_ENTER("assign_new_table_id"); + + /* Preconditions */ + DBUG_ASSERT(share != NULL); + safe_mutex_assert_owner(&LOCK_open); + + ulong tid= ++last_table_id; /* get next id */ + /* + There is one reserved number that cannot be used. Remember to + change this when 6-byte global table id's are introduced. + */ + if (unlikely(tid == ~0UL)) + tid= ++last_table_id; + share->table_map_id= tid; + DBUG_PRINT("info", ("table_id=%lu", tid)); + + /* Post conditions */ + DBUG_ASSERT(share->table_map_id != ~0UL); + + DBUG_VOID_RETURN; +} + +/* Load a table definition from file and open unireg table SYNOPSIS open_unireg_entry() thd Thread handle entry Store open table definition here - db Database name - name Table name + table_list TABLE_LIST with db, table_name & belong_to_view alias Alias name - table_desc TABLE_LIST descriptor (used with views) + cache_key Key for share_cache + cache_key_length length of cache_key mem_root temporary mem_root for parsing flags the OPEN_VIEW_NO_PARSE flag to be passed to openfrm()/open_new_frm() NOTES Extra argument for open is taken from thd->open_options + One must have a lock on LOCK_open when calling this function RETURN 0 ok # Error */ -static int open_unireg_entry(THD *thd, TABLE *entry, const char *db, - const char *name, const char *alias, - TABLE_LIST *table_desc, MEM_ROOT *mem_root, - uint flags) + +static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, + const char *alias, + char *cache_key, uint cache_key_length, + MEM_ROOT *mem_root, uint flags) { - char path[FN_REFLEN]; int error; + TABLE_SHARE *share; uint discover_retry_count= 0; DBUG_ENTER("open_unireg_entry"); - strxmov(path, mysql_data_home, "/", db, "/", name, NullS); - while ((error= openfrm(thd, path, alias, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | HA_TRY_READ_ONLY | - NO_ERR_ON_NEW_FRM), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | - (flags & OPEN_VIEW_NO_PARSE), - thd->open_options, entry)) && - (error != 5 || - (fn_format(path, path, 0, reg_ext, MY_UNPACK_FILENAME), - open_new_frm(thd, path, alias, db, name, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | - (flags & OPEN_VIEW_NO_PARSE), - thd->open_options, entry, table_desc, mem_root)))) + safe_mutex_assert_owner(&LOCK_open); + +retry: + if (!(share= get_table_share_with_create(thd, table_list, cache_key, + cache_key_length, + OPEN_VIEW, &error))) + DBUG_RETURN(1); + if (share->is_view) { - if (!entry->s || !entry->s->crashed) + /* Open view */ + error= (int) open_new_frm(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | + (flags & OPEN_VIEW_NO_PARSE), + thd->open_options, entry, table_list, + mem_root); + if (error) + goto err; + /* TODO: Don't free this */ + release_table_share(share, RELEASE_NORMAL); + DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0); + } + + while ((error= open_table_from_share(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | + HA_OPEN_RNDFILE | + HA_GET_INDEX | + HA_TRY_READ_ONLY), + (READ_KEYINFO | COMPUTE_TYPES | + EXTRA_RECORD), + thd->open_options, entry, FALSE))) + { + if (error == 7) // Table def changed { + share->version= 0; // Mark share as old + if (discover_retry_count++) // Retry once + goto err; + /* - Frm file could not be found on disk - Since it does not exist, no one can be using it - LOCK_open has been locked to protect from someone else - trying to discover the table at the same time. + TODO: + Here we should wait until all threads has released the table. + For now we do one retry. This may cause a deadlock if there + is other threads waiting for other tables used by this thread. + + Proper fix would be to if the second retry failed: + - Mark that table def changed + - Return from open table + - Close all tables used by this thread + - Start waiting that the share is released + - Retry by opening all tables again */ - if (discover_retry_count++ != 0) + if (ha_create_table_from_engine(thd, table_list->db, + table_list->table_name)) goto err; - if (ha_create_table_from_engine(thd, db, name) > 0) - { - /* Give right error message */ - thd->clear_error(); - DBUG_PRINT("error", ("Discovery of %s/%s failed", db, name)); - my_printf_error(ER_UNKNOWN_ERROR, - "Failed to open '%-.64s', error while " - "unpacking from engine", - MYF(0), name); - + /* + TO BE FIXED + To avoid deadlock, only wait for release if no one else is + using the share. + */ + if (share->ref_count != 1) goto err; - } - - mysql_reset_errors(thd, 1); // Clear warnings - thd->clear_error(); // Clear error message - continue; - } - - // Code below is for repairing a crashed file - TABLE_LIST table_list; - bzero((char*) &table_list, sizeof(table_list)); // just for safe - table_list.db=(char*) db; - table_list.table_name=(char*) name; - - safe_mutex_assert_owner(&LOCK_open); - - if ((error=lock_table_name(thd,&table_list))) - { - if (error < 0) + /* Free share and wait until it's released by all threads */ + release_table_share(share, RELEASE_WAIT_FOR_DROP); + if (!thd->killed) { - goto err; + mysql_reset_errors(thd, 1); // Clear warnings + thd->clear_error(); // Clear error message + goto retry; } - if (wait_for_locked_table_names(thd,&table_list)) - { - unlock_table_name(thd,&table_list); - goto err; - } - } - pthread_mutex_unlock(&LOCK_open); - thd->clear_error(); // Clear error message - error= 0; - if (openfrm(thd, path, alias, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | HA_GET_INDEX | - HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, - ha_open_options | HA_OPEN_FOR_REPAIR, - entry) || ! entry->file || - (entry->file->is_crashed() && entry->file->check_and_repair(thd))) - { - /* Give right error message */ - thd->clear_error(); - my_error(ER_NOT_KEYFILE, MYF(0), name, my_errno); - sql_print_error("Couldn't repair table: %s.%s",db,name); - if (entry->file) - closefrm(entry); - error=1; + DBUG_RETURN(1); } - else - thd->clear_error(); // Clear error message - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd,&table_list); - - if (error) + if (!entry->s || !entry->s->crashed) goto err; - break; - } - - if (error == 5) - DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0); // we have just opened VIEW - - /* - We can't mark all tables in 'mysql' database as system since we don't - allow to lock such tables for writing with any other tables (even with - other system tables) and some privilege tables need this. - */ - if (!my_strcasecmp(system_charset_info, db, "mysql") && - !my_strcasecmp(system_charset_info, name, "proc")) - entry->s->system_table= 1; + // Code below is for repairing a crashed file + if ((error= lock_table_name(thd, table_list, TRUE))) + { + if (error < 0) + goto err; + if (wait_for_locked_table_names(thd, table_list)) + { + unlock_table_name(thd, table_list); + goto err; + } + } + pthread_mutex_unlock(&LOCK_open); + thd->clear_error(); // Clear error message + error= 0; + if (open_table_from_share(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | + HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, + ha_open_options | HA_OPEN_FOR_REPAIR, + entry, FALSE) || ! entry->file || + (entry->file->is_crashed() && entry->file->check_and_repair(thd))) + { + /* Give right error message */ + thd->clear_error(); + my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno); + sql_print_error("Couldn't repair table: %s.%s", share->db.str, + share->table_name.str); + if (entry->file) + closefrm(entry, 0); + error=1; + } + else + thd->clear_error(); // Clear error message + pthread_mutex_lock(&LOCK_open); + unlock_table_name(thd, table_list); + + if (error) + goto err; + break; + } - if (Table_triggers_list::check_n_load(thd, db, name, entry, 0)) + if (Table_triggers_list::check_n_load(thd, share->db.str, + share->table_name.str, entry, 0)) + { + closefrm(entry, 0); goto err; + } /* If we are here, there was no fatal error (but error may be still @@ -2537,13 +3406,14 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db, if (mysql_bin_log.is_open()) { char *query, *end; - uint query_buf_size= 20 + 2*NAME_LEN + 1; - if ((query= (char*)my_malloc(query_buf_size,MYF(MY_WME)))) + uint query_buf_size= 20 + share->db.length + share->table_name.length +1; + if ((query= (char*) my_malloc(query_buf_size,MYF(MY_WME)))) { + /* this DELETE FROM is needed even with row-based binlogging */ end = strxmov(strmov(query, "DELETE FROM `"), - db,"`.`",name,"`", NullS); - Query_log_event qinfo(thd, query, (ulong)(end-query), 0, FALSE); - mysql_bin_log.write(&qinfo); + share->db.str,"`.`",share->table_name.str,"`", NullS); + thd->binlog_query(THD::STMT_QUERY_TYPE, + query, (ulong)(end-query), FALSE, FALSE); my_free(query, MYF(0)); } else @@ -2553,25 +3423,19 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db, DBA on top of warning the client (which will automatically be done because of MYF(MY_WME) in my_malloc() above). */ - sql_print_error("When opening HEAP table, could not allocate \ -memory to write 'DELETE FROM `%s`.`%s`' to the binary log",db,name); + sql_print_error("When opening HEAP table, could not allocate memory " + "to write 'DELETE FROM `%s`.`%s`' to the binary log", + table_list->db, table_list->table_name); delete entry->triggers; - if (entry->file) - closefrm(entry); + closefrm(entry, 0); goto err; } } } DBUG_RETURN(0); + err: - /* Hide "Table doesn't exist" errors if table belong to view */ - if (thd->net.last_errno == ER_NO_SUCH_TABLE && - table_desc && table_desc->belong_to_view) - { - TABLE_LIST *view= table_desc->belong_to_view; - thd->clear_error(); - my_error(ER_VIEW_INVALID, MYF(0), view->view_db.str, view->view_name.str); - } + release_table_share(share, RELEASE_NORMAL); DBUG_RETURN(1); } @@ -2632,25 +3496,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) statement for which table list for prelocking is already built, let us cache routines and try to build such table list. - NOTE: We will mark statement as requiring prelocking only if we will - have non empty table list. But this does not guarantee that in prelocked - mode we will have some locked tables, because queries which use only - derived/information schema tables and views possible. Thus "counter" - may be still zero for prelocked statement... */ if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && thd->lex->uses_stored_routines()) { - bool first_no_prelocking, need_prelocking, tabs_changed; + bool first_no_prelocking, need_prelocking; TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; DBUG_ASSERT(thd->lex->query_tables == *start); sp_get_prelocking_info(thd, &need_prelocking, &first_no_prelocking); - if (sp_cache_routines_and_add_tables(thd, thd->lex, - first_no_prelocking, - &tabs_changed)) + if (sp_cache_routines_and_add_tables(thd, thd->lex, first_no_prelocking)) { /* Serious error during reading stored routines from mysql.proc table. @@ -2660,7 +3517,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) result= -1; goto err; } - else if ((tabs_changed || *start) && need_prelocking) + else if (need_prelocking) { query_tables_last_own= save_query_tables_last; *start= thd->lex->query_tables; @@ -2931,13 +3788,13 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type) if (table) { -#if defined( __WIN__) || defined(OS2) +#if defined( __WIN__) /* Win32 can't drop a file that is open */ if (lock_type == TL_WRITE_ALLOW_READ) { lock_type= TL_WRITE; } -#endif /* __WIN__ || OS2 */ +#endif /* __WIN__ */ table_list->lock_type= lock_type; table_list->table= table; table->grant= table_list->grant; @@ -3129,15 +3986,16 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) in prelocked mode. */ DBUG_ASSERT(!thd->prelocked_mode || !thd->lex->requires_prelocking()); - /* - If statement requires prelocking then it has non-empty table list. - So it is safe to shortcut. - */ - DBUG_ASSERT(!thd->lex->requires_prelocking() || tables); *need_reopen= FALSE; - if (!tables) + /* + CREATE ... SELECT UUID() locks no tables, we have to test here. + */ + if (thd->lex->binlog_row_based_if_mixed) + thd->set_current_stmt_binlog_row_based_if_mixed(); + + if (!tables && !thd->lex->requires_prelocking()) DBUG_RETURN(0); /* @@ -3167,6 +4025,17 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) { thd->in_lock_tables=1; thd->options|= OPTION_TABLE_LOCK; + /* + If we have >= 2 different tables to update with auto_inc columns, + statement-based binlogging won't work. We can solve this problem in + mixed mode by switching to row-based binlogging: + */ + if (thd->variables.binlog_format == BINLOG_FORMAT_MIXED && + has_two_write_locked_tables_with_auto_increment(tables)) + { + thd->lex->binlog_row_based_if_mixed= TRUE; + thd->set_current_stmt_binlog_row_based_if_mixed(); + } } if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start), @@ -3280,8 +4149,22 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) /* Open a single table without table caching and don't set it in open_list - Used by alter_table to open a temporary table and when creating - a temporary table with CREATE TEMPORARY ... + + SYNPOSIS + open_temporary_table() + thd Thread object + path Path (without .frm) + db database + table_name Table name + link_in_list 1 if table should be linked into thd->temporary_tables + + NOTES: + Used by alter_table to open a temporary table and when creating + a temporary table with CREATE TEMPORARY ... + + RETURN + 0 Error + # TABLE object */ TABLE *open_temporary_table(THD *thd, const char *path, const char *db, @@ -3289,51 +4172,60 @@ TABLE *open_temporary_table(THD *thd, const char *path, const char *db, { TABLE *tmp_table; TABLE_SHARE *share; + char cache_key[MAX_DBKEY_LENGTH], *saved_cache_key, *tmp_path; + uint key_length; + TABLE_LIST table_list; DBUG_ENTER("open_temporary_table"); - - /* - The extra size in my_malloc() is for table_cache_key - 4 bytes for master thread id if we are in the slave - 1 byte to terminate db - 1 byte to terminate table_name - total of 6 extra bytes in my_malloc in addition to table/db stuff - */ - if (!(tmp_table=(TABLE*) my_malloc(sizeof(*tmp_table)+(uint) strlen(db)+ - (uint) strlen(table_name)+6+4, - MYF(MY_WME)))) + DBUG_PRINT("enter", + ("table: '%s'.'%s' path: '%s' server_id: %u " + "pseudo_thread_id: %lu", + db, table_name, path, + (uint) thd->server_id, (ulong) thd->variables.pseudo_thread_id)); + + table_list.db= (char*) db; + table_list.table_name= (char*) table_name; + /* Create the cache_key for temporary tables */ + key_length= create_table_def_key(thd, cache_key, &table_list, 1); + + if (!(tmp_table= (TABLE*) my_malloc(sizeof(*tmp_table) + sizeof(*share) + + strlen(path)+1 + key_length, + MYF(MY_WME)))) DBUG_RETURN(0); /* purecov: inspected */ - if (openfrm(thd, path, table_name, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | HA_GET_INDEX), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, - ha_open_options, - tmp_table)) + share= (TABLE_SHARE*) (tmp_table+1); + tmp_path= (char*) (share+1); + saved_cache_key= strmov(tmp_path, path)+1; + memcpy(saved_cache_key, cache_key, key_length); + + init_tmp_table_share(share, saved_cache_key, key_length, + strend(saved_cache_key)+1, tmp_path); + + if (open_table_def(thd, share, 0) || + open_table_from_share(thd, share, table_name, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, + ha_open_options, + tmp_table, FALSE)) { + /* No need to lock share->mutex as this is not needed for tmp tables */ + free_table_share(share); my_free((char*) tmp_table,MYF(0)); DBUG_RETURN(0); } - share= tmp_table->s; - tmp_table->reginfo.lock_type=TL_WRITE; // Simulate locked + tmp_table->reginfo.lock_type= TL_WRITE; // Simulate locked share->tmp_table= (tmp_table->file->has_transactions() ? TRANSACTIONAL_TMP_TABLE : NON_TRANSACTIONAL_TMP_TABLE); - share->table_cache_key= (char*) (tmp_table+1); - share->db= share->table_cache_key; - share->key_length= (uint) (strmov(((char*) (share->table_name= - strmov(share->table_cache_key, - db)+1)), - table_name) - - share->table_cache_key) +1; - int4store(share->table_cache_key + share->key_length, thd->server_id); - share->key_length+= 4; - int4store(share->table_cache_key + share->key_length, - thd->variables.pseudo_thread_id); - share->key_length+= 4; if (link_in_list) { - tmp_table->next=thd->temporary_tables; - thd->temporary_tables=tmp_table; + /* growing temp list at the head */ + tmp_table->next= thd->temporary_tables; + if (tmp_table->next) + tmp_table->next->prev= tmp_table; + thd->temporary_tables= tmp_table; + thd->temporary_tables->prev= 0; if (thd->slave_thread) slave_open_temp_tables++; } @@ -3342,21 +4234,22 @@ TABLE *open_temporary_table(THD *thd, const char *path, const char *db, } -bool rm_temporary_table(enum db_type base, char *path) +bool rm_temporary_table(handlerton *base, char *path) { bool error=0; + handler *file; + char *ext; DBUG_ENTER("rm_temporary_table"); - fn_format(path, path,"",reg_ext,4); - unpack_filename(path,path); + strmov(ext= strend(path), reg_ext); if (my_delete(path,MYF(0))) error=1; /* purecov: inspected */ - *fn_ext(path)='\0'; // remove extension - handler *file= get_new_handler((TABLE*) 0, current_thd->mem_root, base); + *ext= 0; // remove extension + file= get_new_handler((TABLE_SHARE*) 0, current_thd->mem_root, base); if (file && file->delete_table(path)) { error=1; - sql_print_warning("Could not remove tmp table: '%s', error: %d", + sql_print_warning("Could not remove temporary table: '%s', error: %d", path, my_errno); } delete file; @@ -3381,17 +4274,50 @@ Field *view_ref_found= (Field*) 0x2; static void update_field_dependencies(THD *thd, Field *field, TABLE *table) { - if (thd->set_query_id) + DBUG_ENTER("update_field_dependencies"); + if (thd->mark_used_columns != MARK_COLUMNS_NONE) { - if (field->query_id != thd->query_id) + MY_BITMAP *current_bitmap, *other_bitmap; + + /* + We always want to register the used keys, as the column bitmap may have + been set for all fields (for example for view). + */ + + table->covering_keys.intersect(field->part_of_key); + table->merge_keys.merge(field->part_of_key); + + if (thd->mark_used_columns == MARK_COLUMNS_READ) { - field->query_id= thd->query_id; - table->used_fields++; - table->used_keys.intersect(field->part_of_key); + current_bitmap= table->read_set; + other_bitmap= table->write_set; } else - thd->dupp_field= field; + { + current_bitmap= table->write_set; + other_bitmap= table->read_set; + } + + if (bitmap_fast_test_and_set(current_bitmap, field->field_index)) + { + if (thd->mark_used_columns == MARK_COLUMNS_WRITE) + { + DBUG_PRINT("warning", ("Found duplicated field")); + thd->dup_field= field; + } + else + { + DBUG_PRINT("note", ("Field found before")); + } + DBUG_VOID_RETURN; + } + if (table->get_fields_in_item_tree) + field->flags|= GET_FIXED_FIELDS_FLAG; + table->used_fields++; } + else if (table->get_fields_in_item_tree) + field->flags|= GET_FIXED_FIELDS_FLAG; + DBUG_VOID_RETURN; } @@ -3429,6 +4355,7 @@ find_field_in_view(THD *thd, TABLE_LIST *table_list, Field_iterator_view field_it; field_it.set(table_list); Query_arena *arena, backup; + LINT_INIT(arena); DBUG_ASSERT(table_list->schema_table_reformed || (ref != 0 && table_list->view != 0)); @@ -3518,6 +4445,7 @@ find_field_in_natural_join(THD *thd, TABLE_LIST *table_ref, const char *name, DBUG_ASSERT(table_ref->is_natural_join && table_ref->join_columns); DBUG_ASSERT(*actual_table == NULL); + LINT_INIT(arena); LINT_INIT(found_field); for (nj_col= NULL, curr_nj_col= field_it++; curr_nj_col; @@ -3629,8 +4557,18 @@ find_field_in_table(THD *thd, TABLE *table, const char *name, uint length, table->field[cached_field_index]->field_name, name)) field_ptr= table->field + cached_field_index; else if (table->s->name_hash.records) + { field_ptr= (Field**) hash_search(&table->s->name_hash, (byte*) name, length); + if (field_ptr) + { + /* + field_ptr points to field in TABLE_SHARE. Convert it to the matching + field in table + */ + field_ptr= (table->field + (field_ptr - table->s->field)); + } + } else { if (!(field_ptr= table->field)) @@ -3649,8 +4587,9 @@ find_field_in_table(THD *thd, TABLE *table, const char *name, uint length, { if (!allow_rowid || my_strcasecmp(system_charset_info, name, "_rowid") || - !(field=table->rowid_field)) + table->s->rowid_field_offset == 0) DBUG_RETURN((Field*) 0); + field= table->field[table->s->rowid_field_offset-1]; } update_field_dependencies(thd, field, table); @@ -3712,6 +4651,9 @@ find_field_in_table_ref(THD *thd, TABLE_LIST *table_list, { Field *fld; DBUG_ENTER("find_field_in_table_ref"); + DBUG_ASSERT(table_list->alias); + DBUG_ASSERT(name); + DBUG_ASSERT(item_name); DBUG_PRINT("enter", ("table: '%s' field name: '%s' item name: '%s' ref 0x%lx", table_list->alias, name, item_name, (ulong) ref)); @@ -3799,18 +4741,97 @@ find_field_in_table_ref(THD *thd, TABLE_LIST *table_list, register_tree_change, actual_table); } + if (fld) + { #ifndef NO_EMBEDDED_ACCESS_CHECKS - /* Check if there are sufficient access rights to the found field. */ - if (fld && check_privileges && - check_column_grant_in_table_ref(thd, *actual_table, name, length)) - fld= WRONG_GRANT; + /* Check if there are sufficient access rights to the found field. */ + if (check_privileges && + check_column_grant_in_table_ref(thd, *actual_table, name, length)) + fld= WRONG_GRANT; + else #endif - + if (thd->mark_used_columns != MARK_COLUMNS_NONE) + { + /* + Get rw_set correct for this field so that the handler + knows that this field is involved in the query and gets + retrieved/updated + */ + Field *field_to_set= NULL; + if (fld == view_ref_found) + { + Item *it= (*ref)->real_item(); + if (it->type() == Item::FIELD_ITEM) + field_to_set= ((Item_field*)it)->field; + else + { + if (thd->mark_used_columns == MARK_COLUMNS_READ) + it->walk(&Item::register_field_in_read_map, 1, (byte *) 0); + } + } + else + field_to_set= fld; + if (field_to_set) + { + TABLE *table= field_to_set->table; + if (thd->mark_used_columns == MARK_COLUMNS_READ) + bitmap_set_bit(table->read_set, field_to_set->field_index); + else + bitmap_set_bit(table->write_set, field_to_set->field_index); + } + } + } DBUG_RETURN(fld); } /* + Find field in table, no side effects, only purpose is to check for field + in table object and get reference to the field if found. + + SYNOPSIS + find_field_in_table_sef() + + table table where to find + name Name of field searched for + + RETURN + 0 field is not found + # pointer to field +*/ + +Field *find_field_in_table_sef(TABLE *table, const char *name) +{ + Field **field_ptr; + if (table->s->name_hash.records) + { + field_ptr= (Field**)hash_search(&table->s->name_hash,(byte*) name, + strlen(name)); + if (field_ptr) + { + /* + field_ptr points to field in TABLE_SHARE. Convert it to the matching + field in table + */ + field_ptr= (table->field + (field_ptr - table->s->field)); + } + } + else + { + if (!(field_ptr= table->field)) + return (Field *)0; + for (; *field_ptr; ++field_ptr) + if (!my_strcasecmp(system_charset_info, (*field_ptr)->field_name, name)) + break; + } + if (field_ptr) + return *field_ptr; + else + return (Field *)0; +} + + +/* Find field in table list. SYNOPSIS @@ -3963,8 +4984,8 @@ find_field_in_tables(THD *thd, Item_ident *item, { Field *nf=new Field_null(NULL,0,Field::NONE, cur_field->field_name, - cur_field->table, &my_charset_bin); + nf->init(cur_table->table); cur_field= nf; } } @@ -4521,15 +5542,19 @@ mark_common_columns(THD *thd, TABLE_LIST *table_ref_1, TABLE_LIST *table_ref_2, if (field_1) { + TABLE *table_1= nj_col_1->table_ref->table; /* Mark field_1 used for table cache. */ - field_1->query_id= thd->query_id; - nj_col_1->table_ref->table->used_keys.intersect(field_1->part_of_key); + bitmap_set_bit(table_1->read_set, field_1->field_index); + table_1->covering_keys.intersect(field_1->part_of_key); + table_1->merge_keys.merge(field_1->part_of_key); } if (field_2) { + TABLE *table_2= nj_col_2->table_ref->table; /* Mark field_2 used for table cache. */ - field_2->query_id= thd->query_id; - nj_col_2->table_ref->table->used_keys.intersect(field_2->part_of_key); + bitmap_set_bit(table_2->read_set, field_2->field_index); + table_2->covering_keys.intersect(field_2->part_of_key); + table_2->merge_keys.merge(field_2->part_of_key); } if (using_fields != NULL) @@ -5014,17 +6039,18 @@ int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields, ****************************************************************************/ bool setup_fields(THD *thd, Item **ref_pointer_array, - List<Item> &fields, bool set_query_id, + List<Item> &fields, enum_mark_columns mark_used_columns, List<Item> *sum_func_list, bool allow_sum_func) { reg2 Item *item; - bool save_set_query_id= thd->set_query_id; + enum_mark_columns save_mark_used_columns= thd->mark_used_columns; nesting_map save_allow_sum_func= thd->lex->allow_sum_func; List_iterator<Item> it(fields); bool save_is_item_list_lookup; DBUG_ENTER("setup_fields"); - thd->set_query_id=set_query_id; + thd->mark_used_columns= mark_used_columns; + DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns)); if (allow_sum_func) thd->lex->allow_sum_func|= 1 << thd->lex->current_select->nest_level; thd->where= THD::DEFAULT_WHERE; @@ -5054,7 +6080,8 @@ bool setup_fields(THD *thd, Item **ref_pointer_array, { thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup; thd->lex->allow_sum_func= save_allow_sum_func; - thd->set_query_id= save_set_query_id; + thd->mark_used_columns= save_mark_used_columns; + DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns)); DBUG_RETURN(TRUE); /* purecov: inspected */ } if (ref) @@ -5069,7 +6096,8 @@ bool setup_fields(THD *thd, Item **ref_pointer_array, thd->lex->current_select->cur_pos_in_select_list= UNDEF_POS; thd->lex->allow_sum_func= save_allow_sum_func; - thd->set_query_id= save_set_query_id; + thd->mark_used_columns= save_mark_used_columns; + DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns)); DBUG_RETURN(test(thd->net.report_error)); } @@ -5113,7 +6141,6 @@ TABLE_LIST **make_leaves_list(TABLE_LIST **list, TABLE_LIST *tables) context name resolution contest to setup table list there from_clause Top-level list of table references in the FROM clause tables Table list (select_lex->table_list) - conds Condition of current SELECT (can be changed by VIEW) leaves List of join table leaves list (select_lex->leaf_tables) refresh It is onle refresh for subquery select_insert It is SELECT ... INSERT command @@ -5135,7 +6162,7 @@ TABLE_LIST **make_leaves_list(TABLE_LIST **list, TABLE_LIST *tables) bool setup_tables(THD *thd, Name_resolution_context *context, List<TABLE_LIST> *from_clause, TABLE_LIST *tables, - Item **conds, TABLE_LIST **leaves, bool select_insert) + TABLE_LIST **leaves, bool select_insert) { uint tablenr= 0; DBUG_ENTER("setup_tables"); @@ -5167,24 +6194,8 @@ bool setup_tables(THD *thd, Name_resolution_context *context, tablenr= 0; } setup_table_map(table, table_list, tablenr); - table->used_keys= table->s->keys_for_keyread; - if (table_list->use_index) - { - key_map map; - get_key_map_from_key_list(&map, table, table_list->use_index); - if (map.is_set_all()) - DBUG_RETURN(1); - table->keys_in_use_for_query=map; - } - if (table_list->ignore_index) - { - key_map map; - get_key_map_from_key_list(&map, table, table_list->ignore_index); - if (map.is_set_all()) - DBUG_RETURN(1); - table->keys_in_use_for_query.subtract(map); - } - table->used_keys.intersect(table->keys_in_use_for_query); + if (table_list->process_index_hints(table)) + DBUG_RETURN(1); } if (tablenr > MAX_TABLES) { @@ -5248,31 +6259,31 @@ bool setup_tables_and_check_access(THD *thd, Name_resolution_context *context, List<TABLE_LIST> *from_clause, TABLE_LIST *tables, - Item **conds, TABLE_LIST **leaves, + TABLE_LIST **leaves, bool select_insert, ulong want_access_first, ulong want_access) { - TABLE_LIST *leaves_tmp = NULL; + TABLE_LIST *leaves_tmp= NULL; bool first_table= true; - if (setup_tables (thd, context, from_clause, tables, conds, - &leaves_tmp, select_insert)) + if (setup_tables(thd, context, from_clause, tables, + &leaves_tmp, select_insert)) return TRUE; if (leaves) - *leaves = leaves_tmp; + *leaves= leaves_tmp; for (; leaves_tmp; leaves_tmp= leaves_tmp->next_leaf) { if (leaves_tmp->belong_to_view && check_single_table_access(thd, first_table ? want_access_first : - want_access, leaves_tmp)) + want_access, leaves_tmp, FALSE)) { tables->hide_view_error(thd); return TRUE; } - first_table= false; + first_table= 0; } return FALSE; } @@ -5398,7 +6409,6 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name, } #endif - /* Update the tables used in the query based on the referenced fields. For views and natural joins this update is performed inside the loop below. @@ -5464,17 +6474,13 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name, if ((field= field_iterator.field())) { - /* - Mark if field used before in this select. - Used by 'insert' to verify if a field name is used twice. - */ - if (field->query_id == thd->query_id) - thd->dupp_field= field; - field->query_id= thd->query_id; - + /* Mark fields as used to allow storage engine to optimze access */ + bitmap_set_bit(field->table->read_set, field->field_index); if (table) - table->used_keys.intersect(field->part_of_key); - + { + table->covering_keys.intersect(field->part_of_key); + table->merge_keys.merge(field->part_of_key); + } if (tables->is_natural_join) { TABLE *field_table; @@ -5490,17 +6496,14 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name, if (field_table) { thd->used_tables|= field_table->map; - field_table->used_keys.intersect(field->part_of_key); + field_table->covering_keys.intersect(field->part_of_key); + field_table->merge_keys.merge(field->part_of_key); field_table->used_fields++; } } } else - { thd->used_tables|= item->used_tables(); - item->walk(&Item::reset_query_id_processor, - (byte *)(&thd->query_id)); - } thd->lex->current_select->cur_pos_in_select_list++; } /* @@ -5571,7 +6574,8 @@ int setup_conds(THD *thd, TABLE_LIST *tables, TABLE_LIST *leaves, arena->is_conventional()) arena= 0; // For easier test - thd->set_query_id=1; + thd->mark_used_columns= MARK_COLUMNS_READ; + DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns)); select_lex->cond_count= 0; select_lex->between_count= 0; @@ -5866,7 +6870,7 @@ my_bool mysql_rm_tmp_tables(void) char filePath[FN_REFLEN], *tmpdir, filePathCopy[FN_REFLEN]; MY_DIR *dirp; FILEINFO *file; - TABLE tmp_table; + TABLE_SHARE share; THD *thd; DBUG_ENTER("mysql_rm_tmp_tables"); @@ -5878,50 +6882,53 @@ my_bool mysql_rm_tmp_tables(void) for (i=0; i<=mysql_tmpdir_list.max; i++) { tmpdir=mysql_tmpdir_list.list[i]; - /* See if the directory exists */ + /* See if the directory exists */ if (!(dirp = my_dir(tmpdir,MYF(MY_WME | MY_DONT_SORT)))) continue; /* Remove all SQLxxx tables from directory */ - for (idx=0 ; idx < (uint) dirp->number_off_files ; idx++) - { - file=dirp->dir_entry+idx; + for (idx=0 ; idx < (uint) dirp->number_off_files ; idx++) + { + file=dirp->dir_entry+idx; - /* skiping . and .. */ - if (file->name[0] == '.' && (!file->name[1] || - (file->name[1] == '.' && !file->name[2]))) - continue; + /* skiping . and .. */ + if (file->name[0] == '.' && (!file->name[1] || + (file->name[1] == '.' && !file->name[2]))) + continue; - if (!bcmp(file->name,tmp_file_prefix,tmp_file_prefix_length)) - { - char *ext= fn_ext(file->name); - uint ext_len= strlen(ext); - uint filePath_len= my_snprintf(filePath, sizeof(filePath), - "%s%s", tmpdir, file->name); - if (!bcmp(reg_ext, ext, ext_len)) + if (!bcmp(file->name,tmp_file_prefix,tmp_file_prefix_length)) { - TABLE tmp_table; - if (!openfrm(thd, filePath, "tmp_table", (uint) 0, - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, - 0, &tmp_table)) + char *ext= fn_ext(file->name); + uint ext_len= strlen(ext); + uint filePath_len= my_snprintf(filePath, sizeof(filePath), + "%s%c%s", tmpdir, FN_LIBCHAR, + file->name); + if (!bcmp(reg_ext, ext, ext_len)) { + handler *handler_file= 0; /* We should cut file extention before deleting of table */ memcpy(filePathCopy, filePath, filePath_len - ext_len); filePathCopy[filePath_len - ext_len]= 0; - tmp_table.file->delete_table(filePathCopy); - closefrm(&tmp_table); + init_tmp_table_share(&share, "", 0, "", filePathCopy); + if (!open_table_def(thd, &share, 0) && + ((handler_file= get_new_handler(&share, thd->mem_root, + share.db_type())))) + { + handler_file->delete_table(filePathCopy); + delete handler_file; + } + free_table_share(&share); } + /* + File can be already deleted by tmp_table.file->delete_table(). + So we hide error messages which happnes during deleting of these + files(MYF(0)). + */ + VOID(my_delete(filePath, MYF(0))); } - /* - File can be already deleted by tmp_table.file->delete_table(). - So we hide error messages which happnes during deleting of these - files(MYF(0)). - */ - VOID(my_delete(filePath, MYF(0))); } - } - my_dirend(dirp); + my_dirend(dirp); } delete thd; my_pthread_setspecific_ptr(THR_THD, 0); @@ -5952,7 +6959,7 @@ void remove_db_from_cache(const char *db) for (uint idx=0 ; idx < open_cache.records ; idx++) { TABLE *table=(TABLE*) hash_element(&open_cache,idx); - if (!strcmp(table->s->db, db)) + if (!strcmp(table->s->db.str, db)) { table->s->version= 0L; /* Free when thread is ready */ if (!table->in_use) @@ -5965,7 +6972,11 @@ void remove_db_from_cache(const char *db) /* -** free all unused tables + free all unused tables + + NOTE + This is called by 'handle_manager' when one wants to periodicly flush + all not used tables. */ void flush_tables() @@ -5998,7 +7009,8 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, char key[MAX_DBKEY_LENGTH]; uint key_length; TABLE *table; - bool result=0, signalled= 0; + TABLE_SHARE *share; + bool result= 0, signalled= 0; DBUG_ENTER("remove_table_from_cache"); DBUG_PRINT("enter", ("Table: '%s.%s' flags: %u", db, table_name, flags)); @@ -6015,6 +7027,7 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, &state)) { THD *in_use; + table->s->version=0L; /* Free when thread is ready */ if (!(in_use=table->in_use)) { @@ -6023,6 +7036,7 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, } else if (in_use != thd) { + DBUG_PRINT("info", ("Table was in use by other thread")); in_use->some_tables_deleted=1; if (table->is_name_opened()) { @@ -6057,10 +7071,30 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, } } else + { + DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u", + table->db_stat)); result= result || (flags & RTFC_OWNED_BY_THD_FLAG); + } } while (unused_tables && !unused_tables->s->version) VOID(hash_delete(&open_cache,(byte*) unused_tables)); + + DBUG_PRINT("info", ("Removing table from table_def_cache")); + /* Remove table from table definition cache if it's not in use */ + if ((share= (TABLE_SHARE*) hash_search(&table_def_cache,(byte*) key, + key_length))) + { + DBUG_PRINT("info", ("share version: %lu ref_count: %u", + share->version, share->ref_count)); + share->version= 0; // Mark for delete + if (share->ref_count == 0) + { + pthread_mutex_lock(&share->mutex); + VOID(hash_delete(&table_def_cache, (byte*) share)); + } + } + if (result && (flags & RTFC_WAIT_OTHER_THREAD_FLAG)) { /* @@ -6099,6 +7133,7 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, DBUG_RETURN(result); } + int setup_ftfuncs(SELECT_LEX *select_lex) { List_iterator<Item_func_match> li(*(select_lex->ftfunc_list)), @@ -6143,11 +7178,11 @@ int init_ftfuncs(THD *thd, SELECT_LEX *select_lex, bool no_order) SYNOPSIS open_new_frm() THD thread handler - path path to .frm + path path to .frm file (without extension) alias alias for table db database table_name name of table - db_stat open flags (for example HA_OPEN_KEYFILE|HA_OPEN_RNDFILE..) + db_stat open flags (for example ->OPEN_KEYFILE|HA_OPEN_RNDFILE..) can be 0 (example in ha_example_table) prgflag READ_ALL etc.. ha_open_flags HA_OPEN_ABORT_IF_LOCKED etc.. @@ -6157,18 +7192,20 @@ int init_ftfuncs(THD *thd, SELECT_LEX *select_lex, bool no_order) */ static bool -open_new_frm(THD *thd, const char *path, const char *alias, - const char *db, const char *table_name, +open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root) { LEX_STRING pathstr; File_parser *parser; + char path[FN_REFLEN]; DBUG_ENTER("open_new_frm"); - pathstr.str= (char*) path; - pathstr.length= strlen(path); + /* Create path with extension */ + pathstr.length= (uint) (strxmov(path, share->normalized_path.str, reg_ext, + NullS)- path); + pathstr.str= path; if ((parser= sql_parse_prepare(&pathstr, mem_root, 1))) { @@ -6176,7 +7213,8 @@ open_new_frm(THD *thd, const char *path, const char *alias, { if (table_desc == 0 || table_desc->required_type == FRMTYPE_TABLE) { - my_error(ER_WRONG_OBJECT, MYF(0), db, table_name, "BASE TABLE"); + my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, + "BASE TABLE"); goto err; } if (mysql_make_view(thd, parser, table_desc, @@ -6186,7 +7224,7 @@ open_new_frm(THD *thd, const char *path, const char *alias, else { /* only VIEWs are supported now */ - my_error(ER_FRM_UNKNOWN_TYPE, MYF(0), path, parser->type()->str); + my_error(ER_FRM_UNKNOWN_TYPE, MYF(0), share->path, parser->type()->str); goto err; } DBUG_RETURN(0); @@ -6202,3 +7240,312 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b) { return a->length == b->length && !strncmp(a->str, b->str, a->length); } + + +/* + SYNOPSIS + abort_and_upgrade_lock() + lpt Parameter passing struct + All parameters passed through the ALTER_PARTITION_PARAM_TYPE object + RETURN VALUE + 0 + DESCRIPTION + Remember old lock level (for possible downgrade later on), abort all + waiting threads and ensure that all keeping locks currently are + completed such that we own the lock exclusively and no other interaction + is ongoing. + + thd Thread object + table Table object + db Database name + table_name Table name + old_lock_level Old lock level +*/ + +int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + uint flags= RTFC_WAIT_OTHER_THREAD_FLAG | RTFC_CHECK_KILLED_FLAG; + DBUG_ENTER("abort_and_upgrade_locks"); + + lpt->old_lock_type= lpt->table->reginfo.lock_type; + VOID(pthread_mutex_lock(&LOCK_open)); + mysql_lock_abort(lpt->thd, lpt->table, TRUE); + VOID(remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, flags)); + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_RETURN(0); +} + + +/* + SYNOPSIS + close_open_tables_and_downgrade() + RESULT VALUES + NONE + DESCRIPTION + We need to ensure that any thread that has managed to open the table + but not yet encountered our lock on the table is also thrown out to + ensure that no threads see our frm changes premature to the final + version. The intermediate versions are only meant for use after a + crash and later REPAIR TABLE. + We also downgrade locks after the upgrade to WRITE_ONLY +*/ + +void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + VOID(pthread_mutex_lock(&LOCK_open)); + remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, + RTFC_WAIT_OTHER_THREAD_FLAG); + VOID(pthread_mutex_unlock(&LOCK_open)); + mysql_lock_downgrade_write(lpt->thd, lpt->table, lpt->old_lock_type); +} + + +/* + SYNOPSIS + mysql_wait_completed_table() + lpt Parameter passing struct + my_table My table object + All parameters passed through the ALTER_PARTITION_PARAM object + RETURN VALUES + TRUE Failure + FALSE Success + DESCRIPTION + We have changed the frm file and now we want to wait for all users of + the old frm to complete before proceeding to ensure that no one + remains that uses the old frm definition. + Start by ensuring that all users of the table will be removed from cache + once they are done. Then abort all that have stumbled on locks and + haven't been started yet. + + thd Thread object + table Table object + db Database name + table_name Table name +*/ + +void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length; + TABLE *table; + DBUG_ENTER("mysql_wait_completed_table"); + + key_length=(uint) (strmov(strmov(key,lpt->db)+1,lpt->table_name)-key)+1; + VOID(pthread_mutex_lock(&LOCK_open)); + HASH_SEARCH_STATE state; + for (table= (TABLE*) hash_first(&open_cache,(byte*) key,key_length, + &state) ; + table; + table= (TABLE*) hash_next(&open_cache,(byte*) key,key_length, + &state)) + { + THD *in_use= table->in_use; + table->s->version= 0L; + if (!in_use) + { + relink_unused(table); + } + else + { + /* Kill delayed insert threads */ + if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && + ! in_use->killed) + { + in_use->killed= THD::KILL_CONNECTION; + pthread_mutex_lock(&in_use->mysys_var->mutex); + if (in_use->mysys_var->current_cond) + { + pthread_mutex_lock(in_use->mysys_var->current_mutex); + pthread_cond_broadcast(in_use->mysys_var->current_cond); + pthread_mutex_unlock(in_use->mysys_var->current_mutex); + } + pthread_mutex_unlock(&in_use->mysys_var->mutex); + } + /* + Now we must abort all tables locks used by this thread + as the thread may be waiting to get a lock for another table + */ + for (TABLE *thd_table= in_use->open_tables; + thd_table ; + thd_table= thd_table->next) + { + if (thd_table->db_stat) // If table is open + mysql_lock_abort_for_thread(lpt->thd, thd_table); + } + } + } + /* + We start by removing all unused objects from the cache and marking + those in use for removal after completion. Now we also need to abort + all that are locked and are not progressing due to being locked + by our lock. We don't upgrade our lock here. + */ + mysql_lock_abort(lpt->thd, my_table, FALSE); + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_VOID_RETURN; +} + + +/* + Tells if two (or more) tables have auto_increment columns and we want to + lock those tables with a write lock. + + SYNOPSIS + has_two_write_locked_tables_with_auto_increment + tables Table list + + NOTES: + Call this function only when you have established the list of all tables + which you'll want to update (including stored functions, triggers, views + inside your statement). + + RETURN + 0 No + 1 Yes +*/ + +static bool +has_two_write_locked_tables_with_auto_increment(TABLE_LIST *tables) +{ + char *first_table_name= NULL, *first_db; + LINT_INIT(first_db); + + for (TABLE_LIST *table= tables; table; table= table->next_global) + { + /* we must do preliminary checks as table->table may be NULL */ + if (!table->placeholder() && + table->table->found_next_number_field && + (table->lock_type >= TL_WRITE_ALLOW_WRITE)) + { + if (first_table_name == NULL) + { + first_table_name= table->table_name; + first_db= table->db; + DBUG_ASSERT(first_db); + } + else if (strcmp(first_db, table->db) || + strcmp(first_table_name, table->table_name)) + return 1; + } + } + return 0; +} + + +/* + Open and lock system tables for read. + + SYNOPSIS + open_system_tables_for_read() + thd Thread context. + table_list List of tables to open. + backup Pointer to Open_tables_state instance where + information about currently open tables will be + saved, and from which will be restored when we will + end work with system tables. + + NOTES + Thanks to restrictions which we put on opening and locking of + system tables for writing, we can open and lock them for reading + even when we already have some other tables open and locked. One + must call close_system_tables() to close systems tables opened + with this call. + + RETURN + FALSE Success + TRUE Error +*/ + +bool +open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, + Open_tables_state *backup) +{ + DBUG_ENTER("open_system_tables_for_read"); + + thd->reset_n_backup_open_tables_state(backup); + + uint count= 0; + bool not_used; + for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global) + { + TABLE *table= open_table(thd, tables, thd->mem_root, ¬_used, + MYSQL_LOCK_IGNORE_FLUSH); + if (!table) + goto error; + + DBUG_ASSERT(table->s->system_table); + + table->use_all_columns(); + table->reginfo.lock_type= tables->lock_type; + tables->table= table; + count++; + } + + { + TABLE **list= (TABLE**) thd->alloc(sizeof(TABLE*) * count); + TABLE **ptr= list; + for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global) + *(ptr++)= tables->table; + + thd->lock= mysql_lock_tables(thd, list, count, + MYSQL_LOCK_IGNORE_FLUSH, ¬_used); + } + if (thd->lock) + DBUG_RETURN(FALSE); + +error: + close_system_tables(thd, backup); + + DBUG_RETURN(TRUE); +} + + +/* + Close system tables, opened with open_system_tables_for_read(). + + SYNOPSIS + close_system_tables() + thd Thread context + backup Pointer to Open_tables_state instance which holds + information about tables which were open before we + decided to access system tables. +*/ + +void +close_system_tables(THD *thd, Open_tables_state *backup) +{ + close_thread_tables(thd); + thd->restore_backup_open_tables_state(backup); +} + + +/* + Open and lock one system table for update. + + SYNOPSIS + open_system_table_for_update() + thd Thread context. + one_table Table to open. + + NOTES + Table opened with this call should closed using close_thread_tables(). + + RETURN + 0 Error + # Pointer to TABLE object of system table +*/ + +TABLE * +open_system_table_for_update(THD *thd, TABLE_LIST *one_table) +{ + DBUG_ENTER("open_system_table_for_update"); + + TABLE *table= open_ltable(thd, one_table, one_table->lock_type); + if (table) + { + DBUG_ASSERT(table->s->system_table); + table->use_all_columns(); + } + + DBUG_RETURN(table); +} |