/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Basic functions neaded by many modules */ #include "mysql_priv.h" #include "sql_acl.h" #include <thr_alarm.h> #include <m_ctype.h> #include <my_dir.h> #include <hash.h> #include <nisam.h> #include <assert.h> #ifdef __WIN__ #include <io.h> #endif 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); static bool insert_fields(THD *thd,TABLE_LIST *tables, const char *db_name, const char *table_name, List_iterator<Item> *it); static void free_cache_entry(TABLE *entry); static void mysql_rm_tmp_tables(void); static key_map get_key_map_from_key_list(TABLE *table, List<String> *index_list); static byte *cache_key(const byte *record,uint *length, my_bool not_used __attribute__((unused))) { TABLE *entry=(TABLE*) record; *length=entry->key_length; return (byte*) entry->table_cache_key; } void table_cache_init(void) { VOID(hash_init(&open_cache,table_cache_size+16,0,0,cache_key, (void (*)(void*)) free_cache_entry,0)); mysql_rm_tmp_tables(); } 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); DBUG_VOID_RETURN; } uint cached_tables(void) { return open_cache.records; } #ifdef EXTRA_DEBUG static void check_unused(void) { uint count=0,idx=0; TABLE *cur_link,*start_link; if ((start_link=cur_link=unused_tables)) { do { if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next) { DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */ return; /* purecov: inspected */ } } while (count++ < open_cache.records && (cur_link=cur_link->next) != start_link); if (cur_link != start_link) { DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */ } } for (idx=0 ; idx < open_cache.records ; idx++) { TABLE *entry=(TABLE*) hash_element(&open_cache,idx); if (!entry->in_use) count--; } if (count != 0) { DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */ count)); /* purecov: inspected */ } } #else #define check_unused() #endif int list_open_tables(THD *thd,List<char> *tables, const char *db, const char *wild) { int result = 0; uint col_access=thd->col_access; TABLE_LIST table_list; DBUG_ENTER("list_open_tables"); VOID(pthread_mutex_lock(&LOCK_open)); bzero((char*) &table_list,sizeof(table_list)); for (uint idx=0 ; idx < open_cache.records; idx++) { TABLE *entry=(TABLE*) hash_element(&open_cache,idx); if ((!entry->real_name) || strcmp(entry->table_cache_key,db)) continue; if (wild && wild[0] && wild_compare(entry->real_name,wild)) continue; if (db && !(col_access & TABLE_ACLS)) { table_list.db= (char*) db; table_list.real_name= entry->real_name;/*real name*/ table_list.grant.privilege=col_access; if (check_grant(thd,TABLE_ACLS,&table_list,1,1)) continue; } /* need to check if he have't already listed it */ List_iterator<char> it(*tables); char *table_name; int check = 0; while (check == 0 && (table_name=it++)) { if (!strcmp(table_name,entry->real_name)) check++; } if (check) continue; if (tables->push_back(thd->strdup(entry->real_name))) { result = -1; break; } } VOID(pthread_mutex_unlock(&LOCK_open)); DBUG_RETURN(result); } char* query_table_status(THD *thd,const char *db,const char *table_name) { int cached = 0, in_use = 0; char info[256]; for (uint idx=0 ; idx < open_cache.records; idx++) { TABLE *entry=(TABLE*) hash_element(&open_cache,idx); if (strcmp(entry->table_cache_key,db) || strcmp(entry->real_name,table_name)) continue; cached++; if (entry->in_use) in_use++; } sprintf(info, "cached=%d, in_use=%d", cached, in_use); return thd->strdup(info); } /****************************************************************************** ** Send name and type of result to client. ** Sum fields has table name empty and field_name. ** flag is a bit mask with the following functions: ** 1 send number of rows ** 2 send default values ** 4 Don't convert field names ******************************************************************************/ bool send_fields(THD *thd,List<Item> &list,uint flag) { List_iterator<Item> it(list); Item *item; char buff[80]; CONVERT *convert= (flag & 4) ? (CONVERT*) 0 : thd->convert_set; String tmp((char*) buff,sizeof(buff)),*res,*packet= &thd->packet; if (thd->fatal_error) // We have got an error goto err; if (flag & 1) { // Packet with number of elements char *pos=net_store_length(buff,(uint) list.elements); (void) my_net_write(&thd->net, buff,(uint) (pos-buff)); } while ((item=it++)) { char *pos; Send_field field; item->make_field(&field); packet->length(0); if (convert) { if (convert->store(packet,field.table_name, (uint) strlen(field.table_name)) || convert->store(packet,field.col_name, (uint) strlen(field.col_name)) || packet->realloc(packet->length()+10)) goto err; } else if (net_store_data(packet,field.table_name) || net_store_data(packet,field.col_name) || packet->realloc(packet->length()+10)) goto err; /* purecov: inspected */ pos= (char*) packet->ptr()+packet->length(); if (!(thd->client_capabilities & CLIENT_LONG_FLAG)) { packet->length(packet->length()+9); pos[0]=3; int3store(pos+1,field.length); pos[4]=1; pos[5]=field.type; pos[6]=2; pos[7]=(char) field.flags; pos[8]= (char) field.decimals; } else { packet->length(packet->length()+10); pos[0]=3; int3store(pos+1,field.length); pos[4]=1; pos[5]=field.type; pos[6]=3; int2store(pos+7,field.flags); pos[9]= (char) field.decimals; } if (flag & 2) { // Send default value if (!(res=item->val_str(&tmp))) { if (net_store_null(packet)) goto err; } else if (net_store_data(packet,res->ptr(),res->length())) goto err; } if (my_net_write(&thd->net, (char*) packet->ptr(),packet->length())) break; /* purecov: inspected */ } send_eof(&thd->net,(test_flags & TEST_MIT_THREAD) ? 0: 1); return 0; err: send_error(&thd->net,ER_OUT_OF_RESOURCES); /* purecov: inspected */ return 1; /* purecov: inspected */ } /***************************************************************************** * Functions to free open table cache ****************************************************************************/ void intern_close_table(TABLE *table) { // Free all structures free_io_cache(table); if (table->file) VOID(closefrm(table)); // close file } /* Remove table from the open table cache SYNOPSIS free_cache_entry() table Table to remove NOTE We need to have a lock on LOCK_open when calling this */ static void free_cache_entry(TABLE *table) { DBUG_ENTER("free_cache_entry"); intern_close_table(table); if (!table->in_use) { table->next->prev=table->prev; /* remove from used chain */ table->prev->next=table->next; if (table == unused_tables) { unused_tables=unused_tables->next; if (table == unused_tables) unused_tables=0; } check_unused(); // consisty check } my_free((gptr) table,MYF(0)); DBUG_VOID_RETURN; } void free_io_cache(TABLE *table) { if (table->io_cache) { close_cached_file(table->io_cache); my_free((gptr) table->io_cache,MYF(0)); table->io_cache=0; } if (table->record_pointers) { my_free((gptr) table->record_pointers,MYF(0)); table->record_pointers=0; } } /* Close all tables which aren't in use by any thread */ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, TABLE_LIST *tables) { bool result=0; DBUG_ENTER("close_cached_tables"); VOID(pthread_mutex_lock(&LOCK_open)); if (!tables) { while (unused_tables) { #ifdef EXTRA_DEBUG if (hash_delete(&open_cache,(byte*) unused_tables)) printf("Warning: Couldn't delete open table from hash\n"); #else VOID(hash_delete(&open_cache,(byte*) unused_tables)); #endif } refresh_version++; // Force close of open tables } else { bool found=0; for (TABLE_LIST *table=tables ; table ; table=table->next) { if (remove_table_from_cache(thd, table->db, table->real_name, 1)) found=1; } if (!found) if_wait_for_refresh=0; // Nothing to wait for } if (if_wait_for_refresh) { /* If there is any table that has a lower refresh_version, wait until this is closed (or this thread is killed) before returning */ if (!tables) kill_delayed_threads(); thd->mysys_var->current_mutex= &LOCK_open; thd->mysys_var->current_cond= &COND_refresh; thd->proc_info="Flushing tables"; close_old_data_files(thd,thd->open_tables,1,1); bool found=1; /* Wait until all threads has closed all the tables we had locked */ DBUG_PRINT("info", ("Waiting for others threads to close their open tables")); while (found && ! thd->killed) { found=0; for (uint idx=0 ; idx < open_cache.records ; idx++) { TABLE *table=(TABLE*) hash_element(&open_cache,idx); if ((table->version) < refresh_version && table->db_stat) { found=1; pthread_cond_wait(&COND_refresh,&LOCK_open); break; } } } /* No other thread has the locked tables open; reopen them and get the old locks. This should always succeed (unless some external process has removed the tables) */ thd->in_lock_tables=1; result=reopen_tables(thd,1,1); thd->in_lock_tables=0; /* Set version for table */ for (TABLE *table=thd->open_tables; table ; table=table->next) table->version=refresh_version; } VOID(pthread_mutex_unlock(&LOCK_open)); if (if_wait_for_refresh) { THD *thd=current_thd; 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); } /* Put all tables used by thread in free list */ void close_thread_tables(THD *thd, bool locked) { DBUG_ENTER("close_thread_tables"); if (thd->locked_tables) { ha_commit_stmt(thd); // If select statement DBUG_VOID_RETURN; // LOCK TABLES in use } bool found_old_table=0; if (thd->lock) { mysql_unlock_tables(thd, thd->lock); thd->lock=0; } /* VOID(pthread_sigmask(SIG_SETMASK,&thd->block_signals,NULL)); */ if (!locked) VOID(pthread_mutex_lock(&LOCK_open)); DBUG_PRINT("info", ("thd->open_tables=%p", thd->open_tables)); while (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 */ while (open_cache.records > table_cache_size && unused_tables) VOID(hash_delete(&open_cache,(byte*) unused_tables)); /* purecov: tested */ check_unused(); if (found_old_table) { /* Tell threads waiting for refresh that something has happened */ VOID(pthread_cond_broadcast(&COND_refresh)); } if (!locked) VOID(pthread_mutex_unlock(&LOCK_open)); /* VOID(pthread_sigmask(SIG_SETMASK,&thd->signals,NULL)); */ DBUG_VOID_RETURN; } /* move one table to free list */ bool close_thread_table(THD *thd, TABLE **table_ptr) { DBUG_ENTER("close_thread_table"); bool found_old_table=0; TABLE *table=*table_ptr; *table_ptr=table->next; if (table->version != refresh_version || thd->version != refresh_version || !table->db_stat) { VOID(hash_delete(&open_cache,(byte*) table)); found_old_table=1; } else { if (table->flush_version != flush_version) { table->flush_version=flush_version; table->file->extra(HA_EXTRA_FLUSH); } else { // Free memory and reset for next loop table->file->extra(HA_EXTRA_RESET); } table->in_use=0; if (unused_tables) { table->next=unused_tables; /* Link in last */ table->prev=unused_tables->prev; unused_tables->prev=table; table->prev->next=table; } else unused_tables=table->next=table->prev=table; } 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->db_type; strmov(path,table->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; } void close_temporary_tables(THD *thd) { TABLE *table,*next; char *query, *end; uint query_buf_size; bool found_user_tables = 0; LINT_INIT(end); if (!thd->temporary_tables) return; // no tables to close query_buf_size= 11; // "drop table " for (table=thd->temporary_tables ; table ; table=table->next) query_buf_size+= table->key_length +1; if ((query = alloc_root(&thd->mem_root, query_buf_size))) end= strmov(query, "drop table "); for (table=thd->temporary_tables ; table ; table=next) { if (query) // we might be out of memory, but this is not fatal { // skip temporary tables not created directly by the user if (table->real_name[0] != '#') { end = strxmov(end,table->table_cache_key,".", table->real_name,",", NullS); // here we assume table_cache_key always starts // with \0 terminated db name found_user_tables = 1; } } next=table->next; close_temporary(table); } if (query && found_user_tables && mysql_bin_log.is_open()) { uint save_query_len = thd->query_length; *--end = 0; // Remove last ',' thd->query_length = (uint)(end-query); Query_log_event qinfo(thd, query); qinfo.error_code=0; mysql_bin_log.write(&qinfo); thd->query_length = save_query_len; } thd->temporary_tables=0; } 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; int4store(key+key_length,thd->slave_proxy_id); key_length += 4; prev= &thd->temporary_tables; for (table=thd->temporary_tables ; table ; table=table->next) { if (table->key_length == key_length && !memcmp(table->table_cache_key,key,key_length)) return prev; prev= &table->next; } return 0; // Not a temporary table } bool close_temporary_table(THD *thd, const char *db, const char *table_name) { TABLE *table,**prev; if (!(prev=find_temporary_table(thd,db,table_name))) return 1; table= *prev; *prev= table->next; close_temporary(table); if(thd->slave_thread) --slave_open_temp_tables; return 0; } bool rename_temporary_table(THD* thd, TABLE *table, const char *db, const char *table_name) { char *key; if (!(key=(char*) alloc_root(&table->mem_root, (uint) strlen(db)+ (uint) strlen(table_name)+6))) return 1; /* purecov: inspected */ table->key_length=(uint) (strmov((table->real_name=strmov(table->table_cache_key=key, db)+1), table_name) - table->table_cache_key)+1; int4store(key+table->key_length,thd->slave_proxy_id); table->key_length += 4; return 0; } /* move table first in unused links */ static void relink_unused(TABLE *table) { if (table != unused_tables) { table->prev->next=table->next; /* Remove from unused list */ table->next->prev=table->prev; table->next=unused_tables; /* Link in unused tables */ table->prev=unused_tables->prev; unused_tables->prev->next=table; unused_tables->prev=table; unused_tables=table; check_unused(); } } /* Remove all instances of table from the current open list Free all locks on tables that are done with LOCK TABLES */ TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find) { char key[MAX_DBKEY_LENGTH]; uint key_length=find->key_length; TABLE *start=list,**prev,*next; prev= &start; memcpy(key,find->table_cache_key,key_length); for (; list ; list=next) { next=list->next; if (list->key_length == key_length && !memcmp(list->table_cache_key,key,key_length)) { if (thd->locked_tables) mysql_lock_remove(thd, thd->locked_tables,list); VOID(hash_delete(&open_cache,(byte*) list)); // Close table } else { *prev=list; // put in use list prev= &list->next; } } *prev=0; // Notify any 'refresh' threads pthread_cond_broadcast(&COND_refresh); return start; } /* When we call the following function we must have a lock on LOCK_OPEN ; This lock will be unlocked on return. */ void wait_for_refresh(THD *thd) { /* 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; proc_info=thd->proc_info; thd->proc_info="Waiting for table"; if (!thd->killed) (void) pthread_cond_wait(&COND_refresh,&LOCK_open); pthread_mutex_unlock(&LOCK_open); // Must be unlocked first pthread_mutex_lock(&thd->mysys_var->mutex); thd->mysys_var->current_mutex= 0; thd->mysys_var->current_cond= 0; thd->proc_info= proc_info; pthread_mutex_unlock(&thd->mysys_var->mutex); } TABLE *reopen_name_locked_table(THD* thd, TABLE_LIST* table_list) { DBUG_ENTER("reopen_name_locked_table"); if (thd->killed) DBUG_RETURN(0); TABLE* table; if(!(table = table_list->table)) DBUG_RETURN(0); char* db = thd->db ? thd->db : table_list->db; char* table_name = table_list->real_name; char key[MAX_DBKEY_LENGTH]; uint key_length; key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; pthread_mutex_lock(&LOCK_open); if (open_unireg_entry(thd, table, db, table_name, table_name) || !(table->table_cache_key =memdup_root(&table->mem_root,(char*) key, key_length))) { closefrm(table); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } table->key_length=key_length; table->version=0; table->flush_version=0; table->in_use = thd; check_unused(); pthread_mutex_unlock(&LOCK_open); table->next = thd->open_tables; thd->open_tables = table; table->tablenr=thd->current_tablenr++; table->used_fields=0; table->const_table=0; table->outer_join=table->null_row=table->maybe_null=0; table->status=STATUS_NO_RECORD; table->keys_in_use_for_query=table->used_keys= table->keys_in_use; DBUG_RETURN(table); } /****************************************************************************** ** open a table ** Uses a cache of open tables to find a table not in use. ** If refresh is a NULL pointer, then the is no version number checking and ** the table is not put in the thread-open-list ** If the return value is NULL and refresh is set then one must close ** all tables and retry the open ******************************************************************************/ TABLE *open_table(THD *thd,const char *db,const char *table_name, const char *alias,bool *refresh) { reg1 TABLE *table; char key[MAX_DBKEY_LENGTH]; uint key_length; DBUG_ENTER("open_table"); /* find a unused table in the open table cache */ if (refresh) *refresh=0; if (thd->killed) DBUG_RETURN(0); key_length= (uint) (strmov(strmov(key,db)+1,table_name)-key)+1; int4store(key + key_length, thd->slave_proxy_id); for (table=thd->temporary_tables; table ; table=table->next) { if (table->key_length == key_length+4 && !memcmp(table->table_cache_key,key,key_length+4)) { if (table->query_id == thd->query_id) { my_printf_error(ER_CANT_REOPEN_TABLE, ER(ER_CANT_REOPEN_TABLE),MYF(0),table->table_name); DBUG_RETURN(0); } table->query_id=thd->query_id; goto reset; } } if (thd->locked_tables) { // Using table locks for (table=thd->open_tables; table ; table=table->next) { if (table->key_length == key_length && !memcmp(table->table_cache_key,key,key_length) && !my_strcasecmp(table->table_name,alias)) goto reset; } my_printf_error(ER_TABLE_NOT_LOCKED,ER(ER_TABLE_NOT_LOCKED),MYF(0),alias); DBUG_RETURN(0); } VOID(pthread_mutex_lock(&LOCK_open)); if (!thd->open_tables) thd->version=refresh_version; else if (thd->version != refresh_version && refresh) { /* Someone did a refresh while thread was opening tables */ *refresh=1; VOID(pthread_mutex_unlock(&LOCK_open)); DBUG_RETURN(0); } for (table=(TABLE*) hash_search(&open_cache,(byte*) key,key_length) ; table && table->in_use ; table = (TABLE*) hash_next(&open_cache,(byte*) key,key_length)) { if (table->version != refresh_version) { /* ** There is a refresh in progress for this table ** Wait until the table is freed or the thread is killed. */ close_old_data_files(thd,thd->open_tables,0,0); if (table->in_use != thd) wait_for_refresh(thd); else VOID(pthread_mutex_unlock(&LOCK_open)); if (refresh) *refresh=1; DBUG_RETURN(0); } } if (table) { if (table == unused_tables) { // First unused unused_tables=unused_tables->next; // Remove from link if (table == unused_tables) unused_tables=0; } table->prev->next=table->next; /* Remove from unused list */ table->next->prev=table->prev; } else { /* Free cache if too big */ while (open_cache.records > table_cache_size && unused_tables) VOID(hash_delete(&open_cache,(byte*) unused_tables)); /* purecov: tested */ /* make a new table */ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) { VOID(pthread_mutex_unlock(&LOCK_open)); DBUG_RETURN(NULL); } if (open_unireg_entry(thd, table,db,table_name,alias) || !(table->table_cache_key=memdup_root(&table->mem_root,(char*) key, key_length))) { MEM_ROOT* glob_alloc; LINT_INIT(glob_alloc); if (errno == ENOENT && (glob_alloc = my_pthread_getspecific_ptr(MEM_ROOT*,THR_MALLOC))) // Sasha: needed for replication // remember the name of the non-existent table // so we can try to download it from the master { int table_name_len = (uint) strlen(table_name); int db_len = (uint) strlen(db); thd->last_nx_db = alloc_root(glob_alloc,db_len + table_name_len + 2); if(thd->last_nx_db) { thd->last_nx_table = thd->last_nx_db + db_len + 1; memcpy(thd->last_nx_table, table_name, table_name_len + 1); memcpy(thd->last_nx_db, db, db_len + 1); } } table->next=table->prev=table; free_cache_entry(table); VOID(pthread_mutex_unlock(&LOCK_open)); DBUG_RETURN(NULL); } table->key_length=key_length; table->version=refresh_version; table->flush_version=flush_version; DBUG_PRINT("info", ("inserting table %p into the cache", table)); VOID(hash_insert(&open_cache,(byte*) table)); } table->in_use=thd; check_unused(); VOID(pthread_mutex_unlock(&LOCK_open)); if (refresh) { table->next=thd->open_tables; /* Link into simple list */ thd->open_tables=table; } table->reginfo.lock_type=TL_READ; /* Assume read */ reset: /* Fix alias if table name changes */ if (strcmp(table->table_name,alias)) { uint length=(uint) strlen(alias)+1; table->table_name= (char*) my_realloc(table->table_name,length, MYF(MY_WME)); memcpy(table->table_name,alias,length); for (uint i=0 ; i < table->fields ; i++) table->field[i]->table_name=table->table_name; } /* These variables are also set in reopen_table() */ table->tablenr=thd->current_tablenr++; table->used_fields=0; table->const_table=0; table->outer_join=table->null_row=table->maybe_null=0; table->status=STATUS_NO_RECORD; table->keys_in_use_for_query=table->used_keys= table->keys_in_use; dbug_assert(table->key_read == 0); DBUG_RETURN(table); } TABLE *find_locked_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; for (TABLE *table=thd->open_tables; table ; table=table->next) { if (table->key_length == key_length && !memcmp(table->table_cache_key,key,key_length)) return table; } return(0); } /**************************************************************************** ** Reopen an table because the definition has changed. The date file for the ** table is already closed. ** Returns 0 if ok. ** If table can't be reopened, the entry is unchanged. ****************************************************************************/ bool reopen_table(TABLE *table,bool locked) { TABLE tmp; char *db=table->table_cache_key; char *table_name=table->real_name; bool error=1; Field **field; uint key,part; DBUG_ENTER("reopen_table"); #ifdef EXTRA_DEBUG if (table->db_stat) sql_print_error("Table %s had a open data handler in reopen_table", table->table_name); #endif if (!locked) VOID(pthread_mutex_lock(&LOCK_open)); if (open_unireg_entry(current_thd,&tmp,db,table_name,table->table_name)) goto end; free_io_cache(table); if (!(tmp.table_cache_key= memdup_root(&tmp.mem_root,db, table->key_length))) { closefrm(&tmp); // End of memory goto end; } /* This list copies variables set by open_table */ tmp.tablenr= table->tablenr; tmp.used_fields= table->used_fields; tmp.const_table= table->const_table; tmp.outer_join= table->outer_join; tmp.null_row= table->null_row; tmp.maybe_null= table->maybe_null; tmp.status= table->status; tmp.keys_in_use_for_query=tmp.used_keys=tmp.keys_in_use; /* Get state */ tmp.key_length= table->key_length; tmp.in_use= table->in_use; tmp.reginfo.lock_type=table->reginfo.lock_type; tmp.version= refresh_version; tmp.tmp_table= table->tmp_table; tmp.grant= table->grant; /* Replace table in open list */ tmp.next=table->next; tmp.prev=table->prev; if (table->file) VOID(closefrm(table)); // close file, free everything *table=tmp; table->file->change_table_ptr(table); for (field=table->field ; *field ; field++) { (*field)->table=table; (*field)->table_name=table->table_name; } for (key=0 ; key < table->keys ; key++) for (part=0 ; part < table->key_info[key].usable_key_parts ; part++) table->key_info[key].key_part[part].field->table=table; VOID(pthread_cond_broadcast(&COND_refresh)); 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 */ bool close_data_tables(THD *thd,const char *db, const char *table_name) { TABLE *table; for (table=thd->open_tables; table ; table=table->next) { if (!strcmp(table->real_name,table_name) && !strcmp(table->table_cache_key,db)) { mysql_lock_remove(thd, thd->locked_tables,table); table->file->close(); table->db_stat=0; } } return 0; // For the future } /* Reopen all tables with closed data files One should have lock on LOCK_open when calling this */ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh) { DBUG_ENTER("reopen_tables"); if (!thd->open_tables) DBUG_RETURN(0); TABLE *table,*next,**prev; TABLE **tables,**tables_ptr; // For locks bool error=0; if (get_locks) { /* The ptr is checked later */ uint opens=0; for (table=thd->open_tables; table ; table=table->next) opens++; tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens); } else tables= &thd->open_tables; tables_ptr =tables; prev= &thd->open_tables; for (table=thd->open_tables; table ; table=next) { uint db_stat=table->db_stat; next=table->next; if (!tables || (!db_stat && reopen_table(table,1))) { my_error(ER_CANT_REOPEN_TABLE,MYF(0),table->table_name); VOID(hash_delete(&open_cache,(byte*) table)); error=1; } else { *prev= table; prev= &table->next; if (get_locks && !db_stat) *tables_ptr++= table; // need new lock on this if (in_refresh) { table->version=0; table->locked_by_flush=0; } } } if (tables != tables_ptr) // Should we get back old locks { MYSQL_LOCK *lock; /* We should always get these locks */ thd->some_tables_deleted=0; if ((lock=mysql_lock_tables(thd,tables,(uint) (tables_ptr-tables)))) { thd->locked_tables=mysql_lock_merge(thd->locked_tables,lock); } else error=1; } if (get_locks && tables) { my_afree((gptr) tables); } VOID(pthread_cond_broadcast(&COND_refresh)); // Signal to refresh *prev=0; DBUG_RETURN(error); } /* Close handlers for tables in list, but leave the TABLE structure intact so that we can re-open these quickly abort_locks is set if called from flush_tables. */ void close_old_data_files(THD *thd, TABLE *table, bool abort_locks, bool send_refresh) { DBUG_ENTER("close_old_data_files"); bool found=send_refresh; for (; table ; table=table->next) { if (table->version != refresh_version) { found=1; if (!abort_locks) // If not from flush tables table->version = refresh_version; // Let other threads use table if (table->db_stat) { if (abort_locks) { mysql_lock_abort(thd,table); // Close waiting threads mysql_lock_remove(thd, thd->locked_tables,table); table->locked_by_flush=1; // Will be reopened with locks } table->file->close(); table->db_stat=0; } } } if (found) VOID(pthread_cond_broadcast(&COND_refresh)); // Signal to refresh DBUG_VOID_RETURN; } /* 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 */ bool table_is_used(TABLE *table, bool wait_for_name_lock) { do { char *key= table->table_cache_key; uint key_length=table->key_length; for (TABLE *search=(TABLE*) hash_search(&open_cache, (byte*) key,key_length) ; search ; search = (TABLE*) hash_next(&open_cache,(byte*) key,key_length)) { if (search->locked_by_flush || search->locked_by_name && wait_for_name_lock || search->db_stat && search->version < refresh_version) return 1; // Table is used } } while ((table=table->next)); return 0; } /* Wait until all used tables are refreshed */ bool wait_for_tables(THD *thd) { bool result; DBUG_ENTER("wait_for_tables"); thd->proc_info="Waiting for tables"; pthread_mutex_lock(&LOCK_open); while (!thd->killed) { thd->some_tables_deleted=0; close_old_data_files(thd,thd->open_tables,0,dropping_tables != 0); if (!table_is_used(thd->open_tables,1)) break; (void) pthread_cond_wait(&COND_refresh,&LOCK_open); } if (thd->killed) result= 1; // aborted else { /* Now we can open all tables without any interference */ thd->proc_info="Reopen tables"; result=reopen_tables(thd,0,0); } pthread_mutex_unlock(&LOCK_open); thd->proc_info=0; DBUG_RETURN(result); } /* drop tables from locked list */ bool drop_locked_tables(THD *thd,const char *db, const char *table_name) { TABLE *table,*next,**prev; bool found=0; prev= &thd->open_tables; for (table=thd->open_tables; table ; table=next) { next=table->next; if (!strcmp(table->real_name,table_name) && !strcmp(table->table_cache_key,db)) { mysql_lock_remove(thd, thd->locked_tables,table); VOID(hash_delete(&open_cache,(byte*) table)); found=1; } else { *prev=table; prev= &table->next; } } *prev=0; if (found) VOID(pthread_cond_broadcast(&COND_refresh)); // Signal to refresh if (thd->locked_tables && thd->locked_tables->table_count == 0) { my_free((gptr) thd->locked_tables,MYF(0)); thd->locked_tables=0; } return found; } /* lock table to force abort of any threads trying to use table */ 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->real_name,table_name) && !strcmp(table->table_cache_key,db)) mysql_lock_abort(thd,table); } } /**************************************************************************** ** open_unireg_entry ** Purpose : Load a table definition from file and open unireg table ** Args : entry with DB and table given ** Returns : 0 if ok ** Note that the extra argument for open is taken from thd->open_options */ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db, const char *name, const char *alias) { char path[FN_REFLEN]; int error; DBUG_ENTER("open_unireg_entry"); (void) sprintf(path,"%s/%s/%s",mysql_data_home,db,name); if (openfrm(path,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)) { if (!entry->crashed) goto err; // Can't repair the table TABLE_LIST table_list; table_list.db=(char*) db; table_list.real_name=(char*) name; table_list.next=0; if ((error=lock_table_name(thd,&table_list))) { 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->net.last_error[0]=0; // Clear error message thd->net.last_errno=0; error=0; if (openfrm(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->net.last_error[0]=0; thd->net.last_errno=0; my_error(ER_NOT_KEYFILE, MYF(0), name, my_errno); sql_print_error("Error: Couldn't repair table: %s.%s",db,name); if (entry->file) closefrm(entry); error=1; } else { thd->net.last_error[0]=0; // Clear error message thd->net.last_errno=0; } pthread_mutex_lock(&LOCK_open); unlock_table_name(thd,&table_list); if (error) goto err; } (void) entry->file->extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL DBUG_RETURN(0); err: DBUG_RETURN(1); } /***************************************************************************** ** open all tables in list *****************************************************************************/ int open_tables(THD *thd,TABLE_LIST *start) { TABLE_LIST *tables; bool refresh; int result=0; DBUG_ENTER("open_tables"); restart: thd->proc_info="Opening tables"; for (tables=start ; tables ; tables=tables->next) { if (!tables->table && !(tables->table=open_table(thd, tables->db ? tables->db : thd->db, tables->real_name, tables->alias, &refresh))) { if (refresh) // Refresh in progress { /* close all 'old' tables used by this thread */ pthread_mutex_lock(&LOCK_open); // if query_id is not reset, we will get an error // re-opening a temp table thd->version=refresh_version; TABLE **prev_table= &thd->open_tables; bool found=0; for (TABLE_LIST *tmp=start ; tmp ; tmp=tmp->next) { /* Close normal (not temporary) changed tables */ if (tmp->table && ! tmp->table->tmp_table) { if (tmp->table->version != refresh_version || ! tmp->table->db_stat) { VOID(hash_delete(&open_cache,(byte*) tmp->table)); tmp->table=0; found=1; } else { *prev_table= tmp->table; // Relink open list prev_table= &tmp->table->next; } } } *prev_table=0; pthread_mutex_unlock(&LOCK_open); if (found) VOID(pthread_cond_broadcast(&COND_refresh)); // Signal to refresh goto restart; } result= -1; // Fatal error break; } if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables) tables->table->reginfo.lock_type=tables->lock_type; tables->table->grant= tables->grant; } thd->proc_info=0; DBUG_RETURN(result); } TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type) { TABLE *table; bool refresh; DBUG_ENTER("open_ltable"); thd->proc_info="Opening table"; while (!(table=open_table(thd,table_list->db ? table_list->db : thd->db, table_list->real_name, table_list->alias, &refresh)) && refresh) ; if (table) { int error; #if defined( __WIN__) || defined(OS2) /* Win32 can't drop a file that is open */ if (lock_type == TL_WRITE_ALLOW_READ #ifdef HAVE_GEMINI_DB && table->db_type != DB_TYPE_GEMINI #endif /* HAVE_GEMINI_DB */ ) { lock_type= TL_WRITE; } #endif /* __WIN__ || OS2 */ table_list->table=table; table->grant= table_list->grant; if (thd->locked_tables) { thd->proc_info=0; if ((int) lock_type >= (int) TL_WRITE_ALLOW_READ && (int) table->reginfo.lock_type < (int) TL_WRITE_ALLOW_READ) { my_printf_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, ER(ER_TABLE_NOT_LOCKED_FOR_WRITE), MYF(0),table_list->alias); table=0; } else if ((error=table->file->start_stmt(thd))) { table->file->print_error(error,MYF(0)); table=0; } thd->proc_info=0; DBUG_RETURN(table); } if ((table->reginfo.lock_type=lock_type) != TL_UNLOCK) if (!(thd->lock=mysql_lock_tables(thd,&table_list->table,1))) DBUG_RETURN(0); } thd->proc_info=0; DBUG_RETURN(table); } /* ** Open all tables in list and locks them for read. ** The lock will automaticly be freed by the close_thread_tables */ int open_and_lock_tables(THD *thd,TABLE_LIST *tables) { if (open_tables(thd,tables) || lock_tables(thd,tables)) return -1; /* purecov: inspected */ return 0; } int lock_tables(THD *thd,TABLE_LIST *tables) { TABLE_LIST *table; if (tables && !thd->locked_tables) { uint count=0; for (table = tables ; table ; table=table->next) count++; TABLE **start,**ptr; if (!(ptr=start=(TABLE**) sql_alloc(sizeof(TABLE*)*count))) return -1; for (table = tables ; table ; table=table->next) *(ptr++)= table->table; if (!(thd->lock=mysql_lock_tables(thd,start,count))) return -1; /* purecov: inspected */ } else { for (table = tables ; table ; table=table->next) { int error; if ((error=table->table->file->start_stmt(thd))) { table->table->file->print_error(error,MYF(0)); return -1; } } } return 0; } /* ** 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 ... */ TABLE *open_temporary_table(THD *thd, const char *path, const char *db, const char *table_name, bool link_in_list) { TABLE *tmp_table; 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, MYF(MY_WME)))) DBUG_RETURN(0); /* purecov: inspected */ if (openfrm(path, table_name, (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | HA_GET_INDEX), READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, ha_open_options, tmp_table)) { my_free((char*) tmp_table,MYF(0)); DBUG_RETURN(0); } tmp_table->file->extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL tmp_table->reginfo.lock_type=TL_WRITE; // Simulate locked tmp_table->tmp_table = (tmp_table->file->has_transactions() ? TRANSACTIONAL_TMP_TABLE : TMP_TABLE); tmp_table->table_cache_key=(char*) (tmp_table+1); tmp_table->key_length= (uint) (strmov((tmp_table->real_name= strmov(tmp_table->table_cache_key,db) +1), table_name) - tmp_table->table_cache_key)+1; int4store(tmp_table->table_cache_key + tmp_table->key_length, thd->slave_proxy_id); tmp_table->key_length += 4; if (link_in_list) { tmp_table->next=thd->temporary_tables; thd->temporary_tables=tmp_table; if(thd->slave_thread) ++slave_open_temp_tables; } DBUG_RETURN(tmp_table); } bool rm_temporary_table(enum db_type base, char *path) { bool error=0; fn_format(path, path,"",reg_ext,4); unpack_filename(path,path); if (my_delete(path,MYF(0))) error=1; /* purecov: inspected */ *fn_ext(path)='\0'; // remove extension handler *file=get_new_handler((TABLE*) 0, base); if (file && file->delete_table(path)) error=1; delete file; return error; } /***************************************************************************** ** find field in list or tables. if field is unqualifed and unique, ** return unique field ******************************************************************************/ #define WRONG_GRANT (Field*) -1 Field *find_field_in_table(THD *thd,TABLE *table,const char *name,uint length, bool check_grants, bool allow_rowid) { Field *field; if (table->name_hash.records) { if ((field=(Field*) hash_search(&table->name_hash,(byte*) name, length))) goto found; } else { Field **ptr=table->field; while ((field = *ptr++)) { if (!my_strcasecmp(field->field_name, name)) goto found; } } if (allow_rowid && !my_strcasecmp(name,"_rowid") && (field=table->rowid_field)) goto found; return (Field*) 0; found: if (thd->set_query_id) { if (field->query_id != thd->query_id) { field->query_id=thd->query_id; table->used_fields++; if (field->part_of_key) { if (!(field->part_of_key & table->ref_primary_key)) table->used_keys&=field->part_of_key; } else table->used_keys=0; } else thd->dupp_field=field; } if (check_grants && check_grant_column(thd,table,name,length)) return WRONG_GRANT; return field; } Field * find_field_in_tables(THD *thd,Item_field *item,TABLE_LIST *tables) { Field *found=0; const char *db=item->db_name; const char *table_name=item->table_name; const char *name=item->field_name; uint length=(uint) strlen(name); if (table_name) { /* Qualified field */ bool found_table=0; for (; tables ; tables=tables->next) { if (!strcmp(tables->alias,table_name) && (!db || (tables->db && !strcmp(db,tables->db)) || (!tables->db && !strcmp(db,thd->db)))) { found_table=1; Field *find=find_field_in_table(thd,tables->table,name,length, grant_option && tables->table->grant.want_privilege, 1); if (find) { if (find == WRONG_GRANT) return (Field*) 0; if (db || !thd->where) return find; if (found) { my_printf_error(ER_NON_UNIQ_ERROR,ER(ER_NON_UNIQ_ERROR),MYF(0), item->full_name(),thd->where); return (Field*) 0; } found=find; } } } if (found) return found; if (!found_table) { char buff[NAME_LEN*2+1]; if (db) { strxnmov(buff,sizeof(buff)-1,db,".",table_name,NullS); table_name=buff; } my_printf_error(ER_UNKNOWN_TABLE,ER(ER_UNKNOWN_TABLE),MYF(0),table_name, thd->where); } else my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR),MYF(0), item->full_name(),thd->where); return (Field*) 0; } bool allow_rowid= tables && !tables->next; // Only one table for (; tables ; tables=tables->next) { Field *field=find_field_in_table(thd,tables->table,name,length, grant_option && tables->table->grant.want_privilege, allow_rowid); if (field) { if (field == WRONG_GRANT) return (Field*) 0; if (found) { if (!thd->where) // Returns first found break; my_printf_error(ER_NON_UNIQ_ERROR,ER(ER_NON_UNIQ_ERROR),MYF(0), name,thd->where); return (Field*) 0; } found=field; } } if (found) return found; my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR), MYF(0),item->full_name(),thd->where); return (Field*) 0; } Item ** find_item_in_list(Item *find,List<Item> &items) { List_iterator<Item> li(items); Item **found=0,*item; const char *field_name=0; const char *table_name=0; if (find->type() == Item::FIELD_ITEM || find->type() == Item::REF_ITEM) { field_name= ((Item_ident*) find)->field_name; table_name= ((Item_ident*) find)->table_name; } while ((item=li++)) { if (field_name && item->type() == Item::FIELD_ITEM) { if (!my_strcasecmp(((Item_field*) item)->name,field_name)) { if (!table_name) { if (found) { if ((*found)->eq(item,0)) continue; // Same field twice (Access?) if (current_thd->where) my_printf_error(ER_NON_UNIQ_ERROR,ER(ER_NON_UNIQ_ERROR),MYF(0), find->full_name(), current_thd->where); return (Item**) 0; } found=li.ref(); } else if (!strcmp(((Item_field*) item)->table_name,table_name)) { found=li.ref(); break; } } } else if (!table_name && (item->eq(find,0) || find->name && !my_strcasecmp(item->name,find->name))) { found=li.ref(); break; } } if (!found && current_thd->where) my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR),MYF(0), find->full_name(),current_thd->where); return found; } /**************************************************************************** ** Check that all given fields exists and fill struct with current data ****************************************************************************/ int setup_fields(THD *thd, TABLE_LIST *tables, List<Item> &fields, bool set_query_id, List<Item> *sum_func_list) { reg2 Item *item; List_iterator<Item> it(fields); DBUG_ENTER("setup_fields"); thd->set_query_id=set_query_id; thd->allow_sum_func= test(sum_func_list); thd->where="field list"; while ((item=it++)) { if (item->type() == Item::FIELD_ITEM && ((Item_field*) item)->field_name[0] == '*') { if (insert_fields(thd,tables,((Item_field*) item)->db_name, ((Item_field*) item)->table_name,&it)) DBUG_RETURN(-1); /* purecov: inspected */ } else { if (item->fix_fields(thd,tables)) DBUG_RETURN(-1); /* purecov: inspected */ if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM) item->split_sum_func(*sum_func_list); thd->used_tables|=item->used_tables(); } } DBUG_RETURN(test(thd->fatal_error)); } /* Remap table numbers if INSERT ... SELECT Check also that the 'used keys' and 'ignored keys' exists and set up the table structure accordingly */ bool setup_tables(TABLE_LIST *tables) { DBUG_ENTER("setup_tables"); uint tablenr=0; for (TABLE_LIST *table=tables ; table ; table=table->next,tablenr++) { table->table->tablenr=tablenr; table->table->map= (table_map) 1 << tablenr; if ((table->table->outer_join=table->outer_join)) table->table->maybe_null=1; // LEFT OUTER JOIN ... if (table->use_index) { key_map map= get_key_map_from_key_list(table->table, table->use_index); if (map == ~(key_map) 0) DBUG_RETURN(1); table->table->keys_in_use_for_query=map; } if (table->ignore_index) { key_map map= get_key_map_from_key_list(table->table, table->ignore_index); if (map == ~(key_map) 0) DBUG_RETURN(1); table->table->keys_in_use_for_query &= ~map; } table->table->used_keys &= table->table->keys_in_use_for_query; } if (tablenr > MAX_TABLES) { my_error(ER_TOO_MANY_TABLES,MYF(0),MAX_TABLES); DBUG_RETURN(1); } DBUG_RETURN(0); } static key_map get_key_map_from_key_list(TABLE *table, List<String> *index_list) { key_map map=0; List_iterator<String> it(*index_list); String *name; uint pos; while ((name=it++)) { if ((pos=find_type(name->c_ptr(), &table->keynames, 1+2)) <= 0) { my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), name->c_ptr(), table->real_name); return (~ (key_map) 0); } map|= ((key_map) 1) << (pos-1); } return map; } /**************************************************************************** ** This just drops in all fields instead of current '*' field ** Returns pointer to last inserted field if ok ****************************************************************************/ static bool insert_fields(THD *thd,TABLE_LIST *tables, const char *db_name, const char *table_name, List_iterator<Item> *it) { uint found; DBUG_ENTER("insert_fields"); found=0; for (; tables ; tables=tables->next) { TABLE *table=tables->table; if (grant_option && !thd->master_access && check_grant_all_columns(thd,SELECT_ACL,table) ) DBUG_RETURN(-1); if (!table_name || (!strcmp(table_name,tables->alias) && (!db_name || !tables->db || !strcmp(tables->db,db_name)))) { Field **ptr=table->field,*field; thd->used_tables|=table->map; while ((field = *ptr++)) { Item_field *item= new Item_field(field); if (!found++) (void) it->replace(item); else it->after(item); if (field->query_id == thd->query_id) thd->dupp_field=field; field->query_id=thd->query_id; if (field->part_of_key) { if (!(field->part_of_key & table->ref_primary_key)) table->used_keys&=field->part_of_key; } else table->used_keys=0; } /* All fields are used */ table->used_fields=table->fields; } } if (!found) { if (!table_name) my_error(ER_NO_TABLES_USED,MYF(0)); else my_error(ER_BAD_TABLE_ERROR,MYF(0),table_name); } DBUG_RETURN(!found); } /* ** Fix all conditions and outer join expressions */ int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds) { DBUG_ENTER("setup_conds"); thd->set_query_id=1; thd->cond_count=0; thd->allow_sum_func=0; if (*conds) { thd->where="where clause"; if ((*conds)->fix_fields(thd,tables)) DBUG_RETURN(1); } /* Check if we are using outer joins */ for (TABLE_LIST *table=tables ; table ; table=table->next) { if (table->on_expr) { /* Make a join an a expression */ thd->where="on clause"; if (table->on_expr->fix_fields(thd,tables)) DBUG_RETURN(1); thd->cond_count++; /* If it's a normal join, add the ON/USING expression to the WHERE */ if (!table->outer_join) { if (!(*conds=and_conds(*conds, table->on_expr))) DBUG_RETURN(1); table->on_expr=0; } } if (table->natural_join) { /* Make a join of all fields with have the same name */ TABLE *t1=table->table; TABLE *t2=table->natural_join->table; Item_cond_and *cond_and=new Item_cond_and(); if (!cond_and) // If not out of memory DBUG_RETURN(1); uint i,j; for (i=0 ; i < t1->fields ; i++) { // TODO: This could be optimized to use hashed names if t2 had a hash for (j=0 ; j < t2->fields ; j++) { key_map tmp_map; if (!my_strcasecmp(t1->field[i]->field_name, t2->field[j]->field_name)) { Item_func_eq *tmp=new Item_func_eq(new Item_field(t1->field[i]), new Item_field(t2->field[j])); if (!tmp) DBUG_RETURN(1); tmp->fix_length_and_dec(); // Update cmp_type tmp->const_item_cache=0; /* Mark field used for table cache */ t1->field[i]->query_id=t2->field[j]->query_id=thd->query_id; cond_and->list.push_back(tmp); if ((tmp_map=t1->field[i]->part_of_key)) { if (!(tmp_map & t1->ref_primary_key)) t1->used_keys&=tmp_map; } else t1->used_keys=0; if ((tmp_map=t2->field[j]->part_of_key)) { if (!(tmp_map & t2->ref_primary_key)) t2->used_keys&=tmp_map; } else t2->used_keys=0; break; } } } cond_and->used_tables_cache= t1->map | t2->map; thd->cond_count+=cond_and->list.elements; if (!table->outer_join) // Not left join { if (!(*conds=and_conds(*conds, cond_and))) DBUG_RETURN(1); } else table->on_expr=and_conds(table->on_expr,cond_and); } } DBUG_RETURN(test(thd->fatal_error)); } /****************************************************************************** ** Fill a record with data (for INSERT or UPDATE) ** Returns : 1 if some field has wrong type ******************************************************************************/ int fill_record(List<Item> &fields,List<Item> &values) { List_iterator<Item> f(fields),v(values); Item *value; Item_field *field; DBUG_ENTER("fill_record"); while ((field=(Item_field*) f++)) { value=v++; if (value->save_in_field(field->field)) DBUG_RETURN(1); } DBUG_RETURN(0); } int fill_record(Field **ptr,List<Item> &values) { List_iterator<Item> v(values); Item *value; DBUG_ENTER("fill_record"); Field *field; while ((field = *ptr++)) { value=v++; if (value->save_in_field(field)) DBUG_RETURN(1); } DBUG_RETURN(0); } static void mysql_rm_tmp_tables(void) { uint idx; char filePath[FN_REFLEN]; MY_DIR *dirp; FILEINFO *file; DBUG_ENTER("mysql_rm_tmp_tables"); /* See if the directory exists */ if (!(dirp = my_dir(mysql_tmpdir,MYF(MY_WME | MY_DONT_SORT)))) DBUG_VOID_RETURN; /* purecov: inspected */ /* ** Remove all SQLxxx tables from directory */ for (idx=2 ; idx < (uint) dirp->number_off_files ; idx++) { file=dirp->dir_entry+idx; if (!bcmp(file->name,tmp_file_prefix,tmp_file_prefix_length)) { sprintf(filePath,"%s%s",mysql_tmpdir,file->name); /* purecov: inspected */ VOID(my_delete(filePath,MYF(MY_WME))); /* purecov: inspected */ } } my_dirend(dirp); DBUG_VOID_RETURN; } /* ** CREATE INDEX and DROP INDEX are implemented by calling ALTER TABLE with ** the proper arguments. This isn't very fast but it should work for most ** cases. ** One should normally create all indexes with CREATE TABLE or ALTER TABLE. */ int mysql_create_index(THD *thd, TABLE_LIST *table_list, List<Key> &keys) { List<create_field> fields; List<Alter_drop> drop; List<Alter_column> alter; HA_CREATE_INFO create_info; DBUG_ENTER("mysql_create_index"); bzero((char*) &create_info,sizeof(create_info)); create_info.db_type=DB_TYPE_DEFAULT; DBUG_RETURN(mysql_alter_table(thd,table_list->db,table_list->real_name, &create_info, table_list, fields, keys, drop, alter, (ORDER*)0, FALSE, DUP_ERROR)); } int mysql_drop_index(THD *thd, TABLE_LIST *table_list, List<Alter_drop> &drop) { List<create_field> fields; List<Key> keys; List<Alter_column> alter; HA_CREATE_INFO create_info; DBUG_ENTER("mysql_drop_index"); bzero((char*) &create_info,sizeof(create_info)); create_info.db_type=DB_TYPE_DEFAULT; DBUG_RETURN(mysql_alter_table(thd,table_list->db,table_list->real_name, &create_info, table_list, fields, keys, drop, alter, (ORDER*)0, FALSE, DUP_ERROR)); } /***************************************************************************** unireg support functions *****************************************************************************/ /* ** Invalidate any cache entries that are for some DB ** We can't use hash_delete when looping hash_elements. We mark them first ** and afterwards delete those marked unused. */ void remove_db_from_cache(const my_string db) { for (uint idx=0 ; idx < open_cache.records ; idx++) { TABLE *table=(TABLE*) hash_element(&open_cache,idx); if (!strcmp(table->table_cache_key,db)) { table->version=0L; /* Free when thread is ready */ if (!table->in_use) relink_unused(table); } } while (unused_tables && !unused_tables->version) VOID(hash_delete(&open_cache,(byte*) unused_tables)); } /* ** free all unused tables */ void flush_tables() { (void) pthread_mutex_lock(&LOCK_open); while (unused_tables) hash_delete(&open_cache,(byte*) unused_tables); (void) pthread_mutex_unlock(&LOCK_open); } /* ** Mark all entries with the table as deleted to force an reopen of the table ** Returns true if the table is in use by another thread */ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, bool return_if_owned_by_thd) { char key[MAX_DBKEY_LENGTH]; uint key_length; TABLE *table; bool result=0; DBUG_ENTER("remove_table_from_cache"); key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; for (table=(TABLE*) hash_search(&open_cache,(byte*) key,key_length) ; table; table = (TABLE*) hash_next(&open_cache,(byte*) key,key_length)) { THD *in_use; table->version=0L; /* Free when thread is ready */ if (!(in_use=table->in_use)) relink_unused(table); else if (in_use != thd) { in_use->some_tables_deleted=1; if (table->db_stat) result=1; /* Kill delayed insert threads */ if (in_use->system_thread && ! in_use->killed) { in_use->killed=1; 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); } } else result= result || return_if_owned_by_thd; } while (unused_tables && !unused_tables->version) VOID(hash_delete(&open_cache,(byte*) unused_tables)); DBUG_RETURN(result); } int setup_ftfuncs(THD *thd) { List_iterator<Item_func_match> li(thd->lex.ftfunc_list), lj(thd->lex.ftfunc_list); Item_func_match *ftf, *ftf2; while ((ftf=li++)) { if (ftf->fix_index()) return 1; lj.rewind(); while ((ftf2=lj++) != ftf) { if (ftf->eq(ftf2,1) && !ftf2->master) ftf2->master=ftf; } } return 0; } int init_ftfuncs(THD *thd, bool no_order) { if (thd->lex.ftfunc_list.elements) { List_iterator<Item_func_match> li(thd->lex.ftfunc_list); Item_func_match *ifm; DBUG_PRINT("info",("Performing FULLTEXT search")); thd->proc_info="FULLTEXT initialization"; while ((ifm=li++)) ifm->init_search(no_order); } return 0; }