diff options
Diffstat (limited to 'sql/sql_base.cc')
-rw-r--r-- | sql/sql_base.cc | 2174 |
1 files changed, 2174 insertions, 0 deletions
diff --git a/sql/sql_base.cc b/sql/sql_base.cc new file mode 100644 index 00000000000..4b245b29e83 --- /dev/null +++ b/sql/sql_base.cc @@ -0,0 +1,2174 @@ +/* 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 + +#define MAX_DBKEY_LENGTH (FN_LEN*2+2) + +static int key_cache_used=0; +TABLE *unused_tables; /* Used by mysql_test */ +HASH open_cache; /* Used by mysql_test */ + + +static int open_unireg_entry(TABLE *entry,const char *db,const char *name, + const char *alias); +static bool insert_fields(THD *thd,TABLE_LIST *tables, 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(THD *thd, TABLE *table, + List<String> *index_list); + +static int send_file(THD *thd) +{ + NET* net = &thd->net; + int fd = -1,bytes, error = 1; + uint packet_len; + char fname[FN_REFLEN+1]; + char buf[IO_SIZE*15]; + const char *errmsg = 0; + int old_timeout; + DBUG_ENTER("send_file"); + + // the client might be slow loading the data, give him wait_timeout to do + // the job + old_timeout = thd->net.timeout; + thd->net.timeout = thd->inactive_timeout; + + // we need net_flush here because the client will not know it needs to send + // us the file name until it has processed the load event entry + if (net_flush(net) || (packet_len = my_net_read(net)) == packet_error) + { + errmsg = "Failed reading file name"; + goto err; + } + + fn_format(fname, (char*)net->read_pos + 1, "", "", 4); + if(!strcmp(fname,"/dev/null")) goto end; // this is needed to make replicate-ignore-db + // work on the well-known system that does not have a /dev/null :-) + + if ((fd = my_open(fname, O_RDONLY, MYF(MY_WME))) < 0) + { + errmsg = "Failed on my_open()"; + goto err; + } + + while ((bytes = (int) my_read(fd, (byte*) buf, sizeof(buf), + MYF(MY_WME))) > 0) + { + if (my_net_write(net, buf, bytes)) + { + errmsg = "Failed on my_net_write()"; + goto err; + } + } + + end: + if (my_net_write(net, "", 0) || net_flush(net) || + (my_net_read(net) == packet_error)) + { + errmsg = "failed negotiating file transfer close"; + goto err; + } + error = 0; + + err: + thd->net.timeout = old_timeout; + if(fd >= 0) + (void) my_close(fd, MYF(MY_WME)); + if (errmsg) + { + sql_print_error("failed in send_file() : %s", errmsg); + DBUG_PRINT("error", (errmsg)); + } + DBUG_RETURN(error); +} + +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,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(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 dosen't match open_cache: diff: %d", /* purecov: inspected */ + count)); /* purecov: inspected */ + } +} +#else +#define check_unused() +#endif + +void mysql_binlog_send(THD* thd, char* log_ident, ulong pos, ushort flags) +{ + LOG_INFO linfo; + char *log_file_name = linfo.log_file_name; + char search_file_name[FN_REFLEN]; + FILE* log = NULL; + String* packet = &thd->packet; + int error; + const char *errmsg = "Unknown error"; + NET* net = &thd->net; + + DBUG_ENTER("mysql_binlog_send"); + + if(!mysql_bin_log.is_open()) + { + errmsg = "Binary log is not open"; + goto err; + } + + if(log_ident[0]) + mysql_bin_log.make_log_name(search_file_name, log_ident); + else + search_file_name[0] = 0; + + if(mysql_bin_log.find_first_log(&linfo, search_file_name)) + { + errmsg = "Could not find first log"; + goto err; + } + log = my_fopen(log_file_name, O_RDONLY, MYF(MY_WME)); + + if(!log) + { + errmsg = "Could not open log file"; + goto err; + } + + if(my_fseek(log, pos, MY_SEEK_SET, MYF(MY_WME)) == MY_FILEPOS_ERROR ) + { + errmsg = "Error on fseek()"; + goto err; + } + + + packet->length(0); + packet->append("\0", 1); // we need to start a packet with something other than 255 + // to distiquish it from error + + while(!net->error && net->vio != 0 && !thd->killed) + { + while(!(error = Log_event::read_log_event(log, packet))) + { + if(my_net_write(net, (char*)packet->ptr(), packet->length()) ) + { + errmsg = "Failed on my_net_write()"; + goto err; + } + DBUG_PRINT("info", ("log event code %d",(*packet)[LOG_EVENT_OFFSET+1] )); + if((*packet)[LOG_EVENT_OFFSET+1] == LOAD_EVENT) + { + if(send_file(thd)) + { + errmsg = "failed in send_file()"; + goto err; + } + } + packet->length(0); + packet->append("\0",1); + } + if(error != LOG_READ_EOF) + { + errmsg = "error reading log event"; + goto err; + } + + if(!(flags & BINLOG_DUMP_NON_BLOCK) && mysql_bin_log.is_active(log_file_name)) + // block until there is more data in the log + // unless non-blocking mode requested + { + if(net_flush(net)) + { + errmsg = "failed on net_flush()"; + goto err; + } + + // we may have missed the update broadcast from the log + // that has just happened, let's try to catch it if it did + // if we did not miss anything, we just wait for other threads + // to signal us + { + pthread_mutex_t *log_lock = mysql_bin_log.get_log_lock(); + clearerr(log); + + // tell the kill thread how to wake us up + pthread_mutex_lock(&thd->mysys_var->mutex); + thd->mysys_var->current_mutex = log_lock; + thd->mysys_var->current_cond = &COND_binlog_update; + const char* proc_info = thd->proc_info; + thd->proc_info = "Waiting for update"; + pthread_mutex_unlock(&thd->mysys_var->mutex); + + bool read_packet = 0, fatal_error = 0; + + pthread_mutex_lock(log_lock); // no one will update the log while we are reading + // now, but we'll be quick and just read one record + + + switch(Log_event::read_log_event(log, packet)) + { + case 0: + read_packet = 1; // we read successfully, so we'll need to send it to the + // slave + break; + case LOG_READ_EOF: + pthread_cond_wait(&COND_binlog_update, log_lock); + break; + + default: + fatal_error = 1; + break; + } + + pthread_mutex_unlock(log_lock); + + 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); + + if(read_packet) + { + if(my_net_write(net, (char*)packet->ptr(), packet->length()) ) + { + errmsg = "Failed on my_net_write()"; + goto err; + } + + if((*packet)[LOG_EVENT_OFFSET+1] == LOAD_EVENT) + { + if(send_file(thd)) + { + errmsg = "failed in send_file()"; + goto err; + } + } + packet->length(0); + packet->append("\0",1); + // no need to net_flush because we will get to flush later when + // we hit EOF pretty quick + } + + if(fatal_error) + { + errmsg = "error reading log entry"; + goto err; + } + + clearerr(log); + } + } + else + { + bool loop_breaker = 0; // need this to break out of the for loop from switch + + switch(mysql_bin_log.find_next_log(&linfo)) + { + case LOG_INFO_EOF: + loop_breaker = (flags & BINLOG_DUMP_NON_BLOCK); + break; + case 0: + break; + default: + errmsg = "could not find next log"; + goto err; + } + + if(loop_breaker) + break; + + (void) my_fclose(log, MYF(MY_WME)); + log = my_fopen(log_file_name, O_RDONLY, MYF(MY_WME)); + if(!log) + goto err; + // fake Rotate_log event just in case it did not make it to the log + // otherwise the slave make get confused about the offset + { + char header[9]; + memset(header, 0, 4); // when does not matter + header[4] = ROTATE_EVENT; + char* p = strrchr(log_file_name, FN_LIBCHAR); // find the last slash + if(p) + p++; + else + p = log_file_name; + + uint ident_len = strlen(p); + ulong event_len = ident_len + sizeof(header); + int4store(header + 5, event_len); + packet->append(header, sizeof(header)); + packet->append(p,ident_len); + if(my_net_write(net, (char*)packet->ptr(), packet->length())) + { + errmsg = "failed on my_net_write()"; + goto err; + } + packet->length(0); + packet->append("\0",1); + } + } + } + + (void)my_fclose(log, MYF(MY_WME)); + + send_eof(&thd->net); + DBUG_VOID_RETURN; + err: + if(log) + (void) my_fclose(log, MYF(MY_WME)); + send_error(&thd->net, 0, errmsg); + DBUG_VOID_RETURN; +} + + +/****************************************************************************** +** Send name and type of result to client. +** Sum fields has table name empty and field_name. +******************************************************************************/ + +bool +send_fields(THD *thd,List<Item> &list,uint flag) +{ + List_iterator<Item> it(list); + Item *item; + char buff[80]; + CONVERT *convert=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,strlen(field.table_name)) || + convert->store(packet,field.col_name, 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 +} + + +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(bool if_wait_for_refresh) +{ + bool result=0; + DBUG_ENTER("close_cached_tables"); + + VOID(pthread_mutex_lock(&LOCK_open)); + 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 + } + if (!open_cache.records) + { + end_key_cache(); /* No tables in memory */ + key_cache_used=0; + } + refresh_version++; // Force close of open tables + 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 + */ + kill_delayed_threads(); + THD *thd=current_thd; + pthread_mutex_lock(&thd->mysys_var->mutex); + thd->mysys_var->current_mutex= &LOCK_open; + thd->mysys_var->current_cond= &COND_refresh; + thd->proc_info="Flushing tables"; + pthread_mutex_unlock(&thd->mysys_var->mutex); + VOID(pthread_cond_broadcast(&COND_refresh)); // If one flush is locked + + close_old_data_files(thd,thd->open_tables,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; + } + 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) + DBUG_VOID_RETURN; // LOCK TABLES in use + + TABLE *table,*next; + 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)); + + for (table=thd->open_tables ; table ; table=next) + { + next=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); + } + 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; + } + } + thd->open_tables=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; +} + + /* 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; + for (table=thd->temporary_tables ; table ; table=next) + { + next=table->next; + close_temporary(table); + } + 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; + + 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); + return 0; +} + +bool rename_temporary_table(TABLE *table, const char *db, + const char *table_name) +{ + char *key; + if (!(key=(char*) alloc_root(&table->mem_root, + strlen(db)+ strlen(table_name)+2))) + 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; + 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 freed on return +*/ + +void wait_for_refresh(THD *thd) +{ + /* Wait until the current table is up to date */ + const char *proc_info; + pthread_mutex_lock(&thd->mysys_var->mutex); + 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"; + pthread_mutex_unlock(&thd->mysys_var->mutex); + (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); +} + + +/****************************************************************************** +** 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; + + for (table=thd->temporary_tables; table ; table=table->next) + { + if (table->key_length == key_length && + !memcmp(table->table_cache_key,key,key_length)) + { + 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); + 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)))) + DBUG_RETURN(NULL); + if (open_unireg_entry(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 = strlen(table_name); + int db_len = 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; + if (!key_cache_used) + { + key_cache_used=1; + ha_key_cache(); + } + 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=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(&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; + } + + tmp.key_length=table->key_length; + tmp.in_use=table->in_use; + tmp.used_keys=tmp.keys_in_use; + tmp.reginfo.lock_type=table->reginfo.lock_type; + tmp.version=refresh_version; + tmp.next=table->next; + tmp.prev=table->prev; + + /* This list copies varibles set by open_table */ + tmp.tablenr= table->tablenr; + tmp.tmp_table= table->tmp_table; + 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.status= table->status; + tmp.grant= table->grant; + + 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 found=0; + 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 +} + + +/* + 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 +*/ + +static bool table_is_used(TABLE *table) +{ + 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->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); + thd->some_tables_deleted=0; + close_old_data_files(thd,thd->open_tables,0); + if (dropping_tables) + { + (void) pthread_cond_broadcast(&COND_refresh); // Signal to refresh/delete + (void) pthread_cond_wait(&COND_refresh,&LOCK_open); + } + + while (table_is_used(thd->open_tables) && ! thd->killed) + { + (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 +*/ + +static int open_unireg_entry(TABLE *entry,const char *db,const char *name, + const char *alias) +{ + char path[FN_REFLEN]; + 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, + entry)) + { + DBUG_RETURN(1); + } + (void) entry->file->extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL + DBUG_RETURN(0); +} + + +/***************************************************************************** +** 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->name, &refresh))) + { + if (refresh) // Refresh in progress + { + /* close all 'old' tables used by this thread */ + pthread_mutex_lock(&LOCK_open); + thd->version=refresh_version; + TABLE **prev_table= &thd->open_tables; + bool found=0; + for (TABLE_LIST *tmp=start ; tmp ; tmp=tmp->next) + { + if (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; + if (found) + VOID(pthread_cond_broadcast(&COND_refresh)); // Signal to refresh + pthread_mutex_unlock(&LOCK_open); + goto restart; + } + result= -1; // Fatal error + break; + } + if (tables->lock_type != TL_UNLOCK) + 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"); + +#ifdef __WIN__ + /* Win32 can't drop a file that is open */ + if (lock_type == TL_WRITE_ALLOW_READ) + lock_type= TL_WRITE; +#endif + thd->proc_info="Opening table"; + while (!(table=open_table(thd,table_list->db ? table_list->db : thd->db, + table_list->real_name,table_list->name, + &refresh)) && refresh) ; + if (table) + { + 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->name); + DBUG_RETURN(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) +{ + if (tables && !thd->locked_tables) + { + uint count=0; + TABLE_LIST *table; + 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 */ + } + 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"); + if (!(tmp_table=(TABLE*) my_malloc(sizeof(*tmp_table)+strlen(db)+ + strlen(table_name)+2, + MYF(MY_WME)))) + DBUG_RETURN(0); /* purecov: inspected */ + + if (openfrm(path, table_name, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | HA_GET_INDEX | + HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, + tmp_table)) + { + 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 = 1; + tmp_table->table_cache_key=(char*) (tmp_table+1); + tmp_table->key_length= (uint) (strmov(strmov(tmp_table->table_cache_key,db) + +1, table_name) + - tmp_table->table_cache_key)+1; + if (link_in_list) + { + tmp_table->next=thd->temporary_tables; + thd->temporary_tables=tmp_table; + } + 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; + field->table->used_fields++; + } + else + thd->dupp_field=field; + field->table->used_keys&=field->part_of_key; + } + if (check_grants && !thd->master_access && 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=strlen(name); + + if (table_name) + { /* Qualified field */ + bool found_table=0; + for (; tables ; tables=tables->next) + { + if (!strcmp(tables->name,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 && !thd->master_access,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) + { + strxmov(buff,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 && !thd->master_access, 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)) + 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) || + 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 +** Check also that the 'used keys' and 'ignored keys' exists and set up the +** table structure accordingly +****************************************************************************/ + +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"; + + /* Remap table numbers if INSERT ... SELECT */ + 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(thd,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(thd,table->table, + table->ignore_index); + if (map == ~(key_map) 0) + DBUG_RETURN(-1); + table->table->keys_in_use_for_query &= ~map; + } + } + if (tablenr > MAX_TABLES) + { + my_error(ER_TOO_MANY_TABLES,MYF(0),MAX_TABLES); + DBUG_RETURN(-1); + } + while ((item=it++)) + { + if (item->type() == Item::FIELD_ITEM && + ((Item_field*) item)->field_name[0] == '*') + { + if (insert_fields(thd,tables,((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); + } + } + DBUG_RETURN(test(thd->fatal_error)); +} + + +static key_map get_key_map_from_key_list(THD *thd, 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)) <= 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 *table_name, + List_iterator<Item> *it) +{ + TABLE_LIST *table; + uint found; + DBUG_ENTER("insert_fields"); + + found=0; + for (table=tables ; table ; table=table->next) + { + if (grant_option && !thd->master_access && + check_grant_all_columns(thd,SELECT_ACL,table->table) ) + DBUG_RETURN(-1); + if (!table_name || !strcmp(table_name,table->name)) + { + Field **ptr=table->table->field,*field; + 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; + field->table->used_keys&=field->part_of_key; + } + /* All fields are used */ + table->table->used_fields=table->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->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++) + { + 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; + cond_and->list.push_back(tmp); + t1->used_keys&= t1->field[i]->part_of_key; + t2->used_keys&= t2->field[j]->part_of_key; + 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=cond_and; + } + else 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; + } + } + } + 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, 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, 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) +{ + 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)) + { + table->version=0L; /* Free when thread is ready */ + if (!table->in_use) + relink_unused(table); + else if (table->in_use != thd) + { + THD *in_use=table->in_use; + + 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_mutex) + { + 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); + } + } + } + while (unused_tables && !unused_tables->version) + VOID(hash_delete(&open_cache,(byte*) unused_tables)); + DBUG_RETURN(result); +} + +/* + Will be used for ft-query optimization someday. + SerG. + */ +int setup_ftfuncs(THD *thd,TABLE_LIST *tables, List<Item_func_match> &ftfuncs) +{ + List_iterator<Item_func_match> li(ftfuncs); + Item_func_match *ftf; + + while ((ftf=li++)) + if (ftf->fix_index()) + return 1; + + return 0; +} + |