mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-30 04:26:45 +03:00 
			
		
		
		
	updated manual about table lock counter fixed coredump in DROP DATABASE with long bogus name by non-root user fixed bug in handling STOP immediately after ROTATE added test case for buffer overrun on DROP DATABASE by non-root user added test case for the STOP bug in replication Docs/manual.texi: updates for Table_locks_immediate and Table_locks_waited include/thr_lock.h: fixes suggested by Monty to lock counts mysql-test/r/status.result: clean up of result for changes in the test case mysql-test/t/status.test: make it produce the same results if the had been some activity on the server already mysys/thr_lock.c: fixes suggested by Monty sql/mysqld.cc: fixes suggested by Monty sql/slave.cc: fixed bug in processing STOP event after rotate sql/sql_base.cc: fixes suggested by Monty sql/sql_parse.cc: fixed coredump in drop database with long name by a non-root user sql/sql_repl.cc: better status message
		
			
				
	
	
		
			2168 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2168 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* 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, bool locked);
 | |
| 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 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 ; result == 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))
 | |
|         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;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   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
 | |
| }
 | |
| 
 | |
| 
 | |
| 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
 | |
|     }
 | |
|     if (!open_cache.records && ! locked_in_memory)
 | |
|     {
 | |
|       end_key_cache();				/* No tables in memory */
 | |
|     }
 | |
|     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->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();
 | |
|     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);
 | |
| 
 | |
|     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;
 | |
|   }
 | |
|   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;
 | |
|   uint init_query_buf_size = 11, query_buf_size; // "drop table "
 | |
|   char* query, *p;
 | |
|   LINT_INIT(p);
 | |
|   query_buf_size = init_query_buf_size;
 | |
| 
 | |
|   for (table=thd->temporary_tables ; table ; table=table->next)
 | |
|   {
 | |
|     query_buf_size += table->key_length;
 | |
| 
 | |
|   }
 | |
| 
 | |
|   if(query_buf_size == init_query_buf_size)
 | |
|     return; // no tables to close
 | |
| 
 | |
|   if((query = alloc_root(&thd->mem_root, query_buf_size)))
 | |
|     {
 | |
|       memcpy(query, "drop table ", init_query_buf_size);
 | |
|       p = query + init_query_buf_size;
 | |
|     }
 | |
| 
 | |
|   for (table=thd->temporary_tables ; table ; table=next)
 | |
|   {
 | |
|     if(query) // we might be out of memory, but this is not fatal
 | |
|       {
 | |
| 	p = strxmov(p,table->table_cache_key,".",
 | |
| 		    table->table_name,",", NullS);
 | |
| 	// here we assume table_cache_key always starts
 | |
| 	// with \0 terminated db name
 | |
|       }
 | |
|     next=table->next;
 | |
|     close_temporary(table);
 | |
|   }
 | |
|   if (query && mysql_bin_log.is_open())
 | |
|   {
 | |
|     uint save_query_len = thd->query_length;
 | |
|     *--p = 0;
 | |
|     thd->query_length = (uint)(p-query);
 | |
|     Query_log_event qinfo(thd, query);
 | |
|     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;
 | |
|   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);
 | |
| }
 | |
| 
 | |
| 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->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,0) ||
 | |
|       !(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;
 | |
|   if (!key_cache_inited)
 | |
|     ha_key_cache();
 | |
|   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))))
 | |
|       DBUG_RETURN(NULL);
 | |
|     if (open_unireg_entry(thd, table,db,table_name,alias,1) ||
 | |
| 	!(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;
 | |
|     if (!key_cache_inited)
 | |
|       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=(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,
 | |
| 			locked))
 | |
|     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 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, bool locked)
 | |
| {
 | |
|   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.name=(char*) name;
 | |
|     table_list.next=0;
 | |
|     if (!locked)
 | |
|       pthread_mutex_lock(&LOCK_open);
 | |
|     if ((error=lock_table_name(thd,&table_list)))
 | |
|     {
 | |
|       if (error < 0)
 | |
|       {
 | |
| 	if (!locked)
 | |
| 	  pthread_mutex_lock(&LOCK_open);
 | |
| 	goto err;
 | |
|       }
 | |
|       if (wait_for_locked_table_names(thd,&table_list))
 | |
|       {
 | |
| 	unlock_table_name(thd,&table_list);
 | |
| 	if (!locked)
 | |
| 	  pthread_mutex_lock(&LOCK_open);
 | |
| 	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;
 | |
|     }
 | |
|     if (locked)
 | |
|       pthread_mutex_lock(&LOCK_open);      // Get back original lock
 | |
|     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->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");
 | |
| 
 | |
|   // 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 |
 | |
| 		      HA_TRY_READ_ONLY),
 | |
| 	      READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
 | |
| 	      ha_open_options,
 | |
| 	      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;
 | |
|   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 && !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=(uint) 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)
 | |
|       {
 | |
| 	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 && !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);
 | |
|       thd->used_tables|=item->used_tables();
 | |
|     }
 | |
|   }
 | |
|   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)
 | |
| {
 | |
|   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->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_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);
 | |
|       }
 | |
|     }
 | |
|     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,TABLE_LIST *tables, List<Item_func_match> &ftfuncs)
 | |
| {
 | |
|   List_iterator<Item_func_match> li(ftfuncs), li2(ftfuncs);
 | |
|   Item_func_match *ftf, *ftf2;
 | |
| 
 | |
|   while ((ftf=li++))
 | |
|   {
 | |
|     if (ftf->fix_index())
 | |
|       return 1;
 | |
|     li2.rewind();
 | |
|     while ((ftf2=li2++) != ftf)
 | |
|     {
 | |
|       if (ftf->eq(ftf2) && !ftf2->master)
 | |
|         ftf2->master=ftf;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 |