diff options
Diffstat (limited to 'sql/sql_cache.cc')
-rw-r--r-- | sql/sql_cache.cc | 2530 |
1 files changed, 2476 insertions, 54 deletions
diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 09d436c0c9c..38db67ba38f 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1,15 +1,15 @@ /* 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 */ @@ -18,81 +18,2503 @@ #include <m_ctype.h> #include <my_dir.h> #include <hash.h> +#include "sql_acl.h" +#include "ha_myisammrg.h" +#ifndef MASTER +#include "../srclib/myisammrg/myrg_def.h" +#else +#include "../myisammrg/myrg_def.h" +#endif + +#ifdef EXTRA_DEBUG +#define MUTEX_LOCK(M) { DBUG_PRINT("info", ("mutex lock 0x%lx", (ulong)(M))); \ + pthread_mutex_lock(M);} +#define SEM_LOCK(M) { int val = 0; sem_getvalue (M, &val); \ + DBUG_PRINT("info", ("sem lock 0x%lx (%d)", (ulong)(M), val)); \ + sem_wait(M); DBUG_PRINT("info", ("sem lock ok")); } +#define MUTEX_UNLOCK(M) {DBUG_PRINT("info", ("mutex unlock 0x%lx",\ + (ulong)(M))); pthread_mutex_unlock(M);} +#define SEM_UNLOCK(M) {DBUG_PRINT("info", ("sem unlock 0x%lx", (ulong)(M))); \ + sem_post(M); DBUG_PRINT("info", ("sem unlock ok")); } +#define STRUCT_LOCK(M) {DBUG_PRINT("info", ("%d struct lock...",__LINE__)); \ + pthread_mutex_lock(M);DBUG_PRINT("info", ("struct lock OK"));} +#define STRUCT_UNLOCK(M) { \ + DBUG_PRINT("info", ("%d struct unlock...",__LINE__)); \ + pthread_mutex_unlock(M);DBUG_PRINT("info", ("struct unlock OK"));} +#define BLOCK_LOCK_WR(B) {DBUG_PRINT("info", ("%d LOCK_WR 0x%lx",\ + __LINE__,(ulong)(B))); \ + B->query()->lock_writing();} +#define BLOCK_LOCK_RD(B) {DBUG_PRINT("info", ("%d LOCK_RD 0x%lx",\ + __LINE__,(ulong)(B))); \ + B->query()->lock_reading();} +#define BLOCK_UNLOCK_WR(B) { \ + DBUG_PRINT("info", ("%d UNLOCK_WR 0x%lx",\ + __LINE__,(ulong)(B)));B->query()->unlock_writing();} +#define BLOCK_UNLOCK_RD(B) { \ + DBUG_PRINT("info", ("%d UNLOCK_RD 0x%lx",\ + __LINE__,(ulong)(B)));B->query()->unlock_reading();} +#define DUMP(C) {C->bins_dump();C->cache_dump();\ + C->queries_dump();C->tables_dump();} +#else +#define MUTEX_LOCK(M) pthread_mutex_lock(M) +#define SEM_LOCK(M) sem_wait(M) +#define MUTEX_UNLOCK(M) pthread_mutex_unlock(M) +#define SEM_UNLOCK(M) sem_post(M) +#define STRUCT_LOCK(M) pthread_mutex_lock(M) +#define STRUCT_UNLOCK(M) pthread_mutex_unlock(M) +#define BLOCK_LOCK_WR(B) B->query()->lock_writing() +#define BLOCK_LOCK_RD(B) B->query()->lock_reading() +#define BLOCK_UNLOCK_WR(B) B->query()->unlock_writing() +#define BLOCK_UNLOCK_RD(B) B->query()->unlock_reading() +#define DUMP(C) +#endif + +/***************************************************************************** + * Query_cache_block_table method(s) + *****************************************************************************/ + +inline Query_cache_block * Query_cache_block_table::block() +{ + return (Query_cache_block *)( ((byte*)this) - + ALIGN_SIZE(sizeof(Query_cache_block_table))*n - + ALIGN_SIZE(sizeof(Query_cache_block))); +}; + +/***************************************************************************** + * Query_cache_block method(s) + *****************************************************************************/ + +void Query_cache_block::init(ulong block_length) +{ + DBUG_ENTER("Query_cache_block::init"); + DBUG_PRINT("info", ("init block 0x%lx", (ulong) this)); + length = block_length; + used = 0; + type = Query_cache_block::FREE; + n_tables = 0; + DBUG_VOID_RETURN; +} + +void Query_cache_block::destroy() +{ + DBUG_ENTER("Query_cache_block::destroy"); + DBUG_PRINT("info", ("destroy block 0x%lx, type %d", + (ulong)this, type)); + type = INCOMPLETE; + DBUG_VOID_RETURN; +} + +inline uint Query_cache_block::headers_len() +{ + return (ALIGN_SIZE(sizeof(Query_cache_block_table))*n_tables + + ALIGN_SIZE(sizeof(Query_cache_block))); +} + +inline gptr Query_cache_block::data(void) +{ + return (gptr)( ((byte*)this) + headers_len() ); +} + +inline Query_cache_query * Query_cache_block::query() +{ +#ifndef DBUG_OFF + if (type != QUERY) + query_cache.wreck(__LINE__, "incorrect block type"); +#endif + return (Query_cache_query *) data(); +} + +inline Query_cache_table * Query_cache_block::table() +{ +#ifndef DBUG_OFF + if (type != TABLE) + query_cache.wreck(__LINE__, "incorrect block type"); +#endif + return (Query_cache_table *) data(); +} + +inline Query_cache_result * Query_cache_block::result() +{ +#ifndef DBUG_OFF + if (type != RESULT && type != RES_CONT && type != RES_BEG && + type != RES_INCOMPLETE) + query_cache.wreck(__LINE__, "incorrect block type"); +#endif + return (Query_cache_result *) data(); +} + +inline Query_cache_block_table * Query_cache_block::table(TABLE_COUNTER_TYPE n) +{ + return ((Query_cache_block_table *) + (((byte*)this)+ALIGN_SIZE(sizeof(Query_cache_block)) + + n*ALIGN_SIZE(sizeof(Query_cache_block_table)))); +} + + +/***************************************************************************** + * Query_cache_table method(s) + *****************************************************************************/ + +byte * Query_cache_table::cache_key(const byte *record, uint *length, + my_bool not_used __attribute__((unused))) +{ + Query_cache_block* table_block = (Query_cache_block*) record; + *length = (table_block->used - table_block->headers_len() - + ALIGN_SIZE(sizeof(Query_cache_table))); + return (((byte *) table_block->data()) + + ALIGN_SIZE(sizeof(Query_cache_table))); +} + +void Query_cache_table::free_cache(void *entry) +{ + //NOP +} + +/***************************************************************************** + * Query_cache_query methods + *****************************************************************************/ + +void Query_cache_query::init_n_lock() +{ + DBUG_ENTER("Query_cache_query::init_n_lock"); + res=0; wri = 0; len = 0; + sem_init(&lock, 0, 1); + pthread_mutex_init(&clients_guard,MY_MUTEX_INIT_FAST); + clients = 0; + lock_writing(); + DBUG_PRINT("info", ("inited & locked query for block 0x%lx", + ((byte*) this)-ALIGN_SIZE(sizeof(Query_cache_block)))); + DBUG_VOID_RETURN; +} + +void Query_cache_query::unlock_n_destroy() +{ + DBUG_ENTER("Query_cache_query::unlock_n_destroy"); + this->unlock_writing(); + DBUG_PRINT("info", ("destroyed & unlocked query for block 0x%lx", + ((byte*)this)-ALIGN_SIZE(sizeof(Query_cache_block)))); + sem_destroy(&lock); + pthread_mutex_destroy(&clients_guard); + DBUG_VOID_RETURN; +} + + +/* + Following methods work for block rwad/write locking only in this + particular case and in interaction with structure_guard_mutex. + + Lock for write prevents any other locking. + Lock for read prevents only locking for write. +*/ + +void Query_cache_query::lock_writing() +{ + SEM_LOCK(&lock); +} + + +/* + Needed for finding queries, that we may delete from cache. + We don't want wait while block become unlocked, in addition + block locking mean that query now used and we not need to + remove it +*/ + +my_bool Query_cache_query::try_lock_writing() +{ + DBUG_ENTER("Query_cache_block::try_lock_writing"); + if (sem_trywait(&lock)!=0 || clients != 0) + { + DBUG_PRINT("info", ("can't lock mutex")); + DBUG_RETURN(0); + } + DBUG_PRINT("info", ("mutex 'lock' 0x%lx locked", (ulong) &lock)); + DBUG_RETURN(1); +} + + +void Query_cache_query::lock_reading() +{ + MUTEX_LOCK(&clients_guard); + clients++; + if (clients == 1) SEM_LOCK(&lock); + MUTEX_UNLOCK(&clients_guard); +} + + +void Query_cache_query::unlock_writing() +{ + SEM_UNLOCK(&lock); +} + + +void Query_cache_query::unlock_reading() +{ + MUTEX_LOCK(&clients_guard); + clients--; + if (clients == 0) SEM_UNLOCK(&lock); + MUTEX_UNLOCK(&clients_guard); + +} + + +byte * Query_cache_query::cache_key( const byte *record, uint *length, + my_bool not_used) +{ + Query_cache_block * query_block = (Query_cache_block *) record; + *length = (query_block->used - query_block->headers_len() - + ALIGN_SIZE(sizeof(Query_cache_query))); + return (((byte *)query_block->data()) + + ALIGN_SIZE(sizeof(Query_cache_query))); +} + + +void Query_cache_query::free_cache(void *entry) +{ + //NOP +} + +/***************************************************************************** + * Query cache store functions + *****************************************************************************/ + +void query_cache_insert(NET *net, const char *packet, ulong length) +{ + DBUG_ENTER("query_cache_insert"); + +#ifndef DBUG_OFF + // Debugging method wreck may cause this + if (query_cache.query_cache_size == 0) + DBUG_VOID_RETURN; +#endif + + // Quick check on unlocked structure + if (net->query_cache_query != 0) + { + STRUCT_LOCK(&query_cache.structure_guard_mutex); + if (net->query_cache_query != 0) + { + Query_cache_block * query_block = (Query_cache_block*) + net->query_cache_query; + DUMP((&query_cache)); + BLOCK_LOCK_WR(query_block); + DBUG_PRINT("info", ("insert packet %lu bytes long",length)); + Query_cache_query * header = query_block->query(); + + Query_cache_block * result = header->result(); + /* + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); will be done by + query_cache.append_result_data if success (if no success we need + query_cache.structure_guard_mutex locked to free query) + */ + if (!query_cache.append_result_data( result, length, (gptr) packet, + query_block, result)) + { + DBUG_PRINT("warning", ("Can't append data")); + header->result(result); + DBUG_PRINT("info", ("free query 0x%lx", (ulong)query_block)); + query_cache.free_query(query_block); + // append_result_data no success => we need unlock + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + DBUG_VOID_RETURN; + } + header->result(result); + BLOCK_UNLOCK_WR(query_block); + } + else + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + } + DBUG_VOID_RETURN; +} + +void query_cache_abort(NET *net) +{ + DBUG_ENTER("query_cache_abort"); + +#ifndef DBUG_OFF + // debuging method wreck may cause this + if (query_cache.query_cache_size == 0) + DBUG_VOID_RETURN; +#endif + + // quick check on unlocked structure + if (net->query_cache_query != 0) + { + STRUCT_LOCK(&query_cache.structure_guard_mutex); + DUMP((&query_cache)); + Query_cache_block * query_block = ((Query_cache_block*) + net->query_cache_query); + if (query_block) + { + BLOCK_LOCK_WR(query_block); + query_cache.free_query(query_block); + net->query_cache_query=0; + } + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + } + DBUG_VOID_RETURN; +} + +void query_cache_end_of_result(NET * net) +{ + DBUG_ENTER("query_cache_end_of_result"); + +#ifndef DBUG_OFF + // debuging method wreck may couse this + if (query_cache.query_cache_size == 0) DBUG_VOID_RETURN; +#endif + + //quick check on unlocked structure + if (net->query_cache_query != 0) + { + STRUCT_LOCK(&query_cache.structure_guard_mutex); + if (net->query_cache_query != 0) + { + Query_cache_block * query_block = (Query_cache_block*) + net->query_cache_query; + DUMP((&query_cache)); + BLOCK_LOCK_WR(query_block); + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + + Query_cache_query * header = query_block->query(); +#ifndef DBUG_OFF + if (header->result() != 0) + { +#endif + header->found_rows(current_thd->limit_found_rows); + header->result()->type = Query_cache_block::RESULT; +#ifndef DBUG_OFF + } + else + { + DBUG_PRINT("error", ("end of data whith no result. query '%s'", + header->query())); + query_cache.wreck(__LINE__, ""); + DBUG_VOID_RETURN; + } +#endif + net->query_cache_query=0; + header->writer(0); + BLOCK_UNLOCK_WR(query_block); + } + else + { + //cache was flushed or resized and query was deleted => do nothing + STRUCT_UNLOCK(&query_cache.structure_guard_mutex); + } + net->query_cache_query=0; + } + DBUG_VOID_RETURN; +} + +void query_cache_invalidate_by_MyISAM_filename(char * filename) +{ + query_cache.invalidate_by_MyISAM_filename(filename); +} + +/***************************************************************************** + * Query_cache methods + *****************************************************************************/ + +/***************************************************************************** + * interface methods + *****************************************************************************/ + +Query_cache::Query_cache( + ulong query_cache_limit, + ulong min_allocation_unit, + ulong min_result_data_size, + uint def_query_hash_size , + uint def_table_hash_size): + + query_cache_size(0), + query_cache_limit(query_cache_limit), + min_allocation_unit(min_allocation_unit), + min_result_data_size(min_result_data_size), + def_query_hash_size(def_query_hash_size), + def_table_hash_size(def_table_hash_size), + queries_in_cache(0), hits(0), inserts(0), refused(0), + initialized(0) +{ + if (min_allocation_unit < ALIGN_SIZE(sizeof(Query_cache_block)) + + ALIGN_SIZE(sizeof(Query_cache_block_table)) + + ALIGN_SIZE(sizeof(Query_cache_query)) + 3) + { + min_allocation_unit=ALIGN_SIZE(sizeof(Query_cache_block)) + + ALIGN_SIZE(sizeof(Query_cache_block_table)) + + ALIGN_SIZE(sizeof(Query_cache_query)) + 3; + } + this->min_allocation_unit = min_allocation_unit; + if (min_result_data_size < min_allocation_unit) + this->min_result_data_size = min_allocation_unit; +} + +ulong Query_cache::resize(ulong query_cache_size) +{ + /* + TODO: when will be realized pack() optimize case when + query_cache_size < this->query_cache_size + */ + /* + TODO: try to copy old cache in new mamory + */ + DBUG_ENTER("Query_cache::resize"); + DBUG_PRINT("info", ("from %lu to %lu",this->query_cache_size,\ + query_cache_size)); + free_cache(0); + this->query_cache_size=query_cache_size; + DBUG_RETURN(init_cache()); +} + +void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) +{ + /* + TODO may be better convert keywords to upper case when query + stored/compared + */ + DBUG_ENTER("Query_cache::store_query"); + if (query_cache_size == 0) + DBUG_VOID_RETURN; + + LEX * lex = &thd->lex; + NET * net = &thd->net; + + TABLE_COUNTER_TYPE tables = 0; + + if ((tables = is_cachable(thd, thd->query_length, + thd->query, lex, tables_used))){ + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size == 0) + DBUG_VOID_RETURN; -#define SQL_CACHE_LENGTH 30 // 300 crashes apple gcc. + DUMP(this); -HASH sql_cache; -static LEX lex_array_static[SQL_CACHE_LENGTH]; -LEX * lex_array = lex_array_static; -int last_lex_array_item = SQL_CACHE_LENGTH - 1; + /* + prepare flags: + most significant bit - CLIENT_LONG_FLAG, + other - charset number (0 no charset convertion) + */ + byte flags = (thd->client_capabilities & CLIENT_LONG_FLAG ? 0x80 : 0); + if (thd->convert_set != 0) + { + flags |= (byte) thd->convert_set->number(); +#ifndef DBUG_OFF + if ( (thd->convert_set->number() & QUERY_CACHE_CHARSET_CONVERT_MASK) != + thd->convert_set->number()) + { + wreck(__LINE__, + "charset number bigger than QUERY_CACHE_CHARSET_CONVERT_MASK"); + } +#endif + } + + /* check: Is it another thread who process same query? */ + thd->query[thd->query_length] = (char)flags; + Query_cache_block *competitor = (Query_cache_block *) + hash_search(&queries, thd->query, thd->query_length+1); + thd->query[thd->query_length] = '\0'; + DBUG_PRINT("info", ("competitor 0x%lx, flags %x", (ulong)competitor, + flags)); + + if (competitor == 0) + { + thd->query[thd->query_length] = (char)flags; + Query_cache_block * query_block = + write_block_data(thd->query_length+1, + (gptr) thd->query, + ALIGN_SIZE(sizeof(Query_cache_query)), + Query_cache_block::QUERY, tables, 1); + thd->query[thd->query_length] = '\0'; + if (query_block != 0) + { + DBUG_PRINT("info", ("query block 0x%lx allocated, %lu", + (ulong)query_block, query_block->used)); + + Query_cache_query * header = query_block->query(); + header->init_n_lock(); + if (hash_insert(&queries, (byte*)query_block)) + { + refused++; + DBUG_PRINT("info", ("insertion in query hash")); + header->unlock_n_destroy(); + free_memory_block(query_block); + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_VOID_RETURN; + } + if (!register_all_tables(query_block, tables_used, tables)) + { + refused++; + DBUG_PRINT("warning", ("tables list incliding filed")); + hash_delete(&queries, (char *) query_block); + header->unlock_n_destroy(); + free_memory_block(query_block); + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_VOID_RETURN; + } + double_linked_list_simple_include(query_block, queries_blocks); + inserts++; + queries_in_cache++; + STRUCT_UNLOCK(&structure_guard_mutex); + + net->query_cache_query = (gptr) query_block; + header->writer(net); + // init_n_lock make query block locked + BLOCK_UNLOCK_WR(query_block); + } + else + { + refused++; + // We have not enough memory to store query => do nothing + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_PRINT("warning", ("Can't allocate query")); + } + } + else + { + refused++; + // Another thread already procass same query => do nothing + DBUG_PRINT("info", ("Another thread process same query")); + STRUCT_UNLOCK(&structure_guard_mutex); + } + } + else + refused++; + DBUG_VOID_RETURN; +} -/* Function to return a text string from a LEX struct */ -static byte *cache_key(const byte *record, uint *length, my_bool not_used) +my_bool Query_cache::send_result_to_client( + THD *thd, char *sql, uint query_length) { -#ifdef QQ - LEX *lex=(LEX*) record; - *length = lex->sql_query_length; - // *length = strlen(lex->ptr); - return (byte*) lex->sql_query_text; - // return (byte*) lex->ptr; + + DBUG_ENTER("Query_cache::send_result_to_client"); + + if (query_cache_size == 0 || + /* + it is not possible to check has_transactions() function of handler + because tables not opened yet + */ + (thd->options & (OPTION_NOT_AUTO_COMMIT | OPTION_BEGIN)) || + thd->query_cache_type == 0) + + { + DBUG_PRINT("info", ("query cache disabled on not in autocommit mode")); + DBUG_RETURN(1); + } + + char *begin = sql; + while(*begin == ' ' || *begin == '\t') begin++; + if ( toupper(begin[0])!='S' || + toupper(begin[1])!='E' || + toupper(begin[2])!='L') + { + DBUG_PRINT("info", ("Not look like SELECT")); + DBUG_RETURN(1); + } + if (thd->temporary_tables != 0 || !thd->safe_to_cache_query ) + { + DBUG_PRINT("info", ("SELECT is non-cachable")); + DBUG_RETURN(1); + } + + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size == 0) + { + DBUG_PRINT("info", ("query cache disabled on not in autocommit mode")); + DBUG_RETURN(1); + } + DBUG_PRINT("info", (" sql %u '%s'", query_length, sql)); + Query_cache_block *query_block = 0; + + /* + prepare flags: + most significant bit - CLIENT_LONG_FLAG, + other - charset number (0 no charset convertion) + */ + byte flags = (thd->client_capabilities & CLIENT_LONG_FLAG ? 0x80 : 0); + if (thd->convert_set != 0) + { + flags |= (byte) thd->convert_set->number(); +#ifndef DBUG_OFF + if ( (thd->convert_set->number() & QUERY_CACHE_CHARSET_CONVERT_MASK) != + thd->convert_set->number()) + { + wreck(__LINE__, + "charset number bigger than QUERY_CACHE_CHARSET_CONVERT_MASK"); + } #endif - return 0; + } + + sql[query_length] = (char) flags; + query_block = (Query_cache_block *) + hash_search(&queries, sql, query_length+1); + sql[query_length] = '\0'; + + /*quick abort on unlocked data*/ + if (query_block == 0 || + query_block->query()->result() == 0 || + query_block->query()->result()->type != Query_cache_block::RESULT) + { + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_PRINT("info", ("No query in query hash or no results")); + DBUG_RETURN(1); + } + DBUG_PRINT("info", ("Query in query hash 0x%lx", (ulong)query_block)); + + /* now lock and test that nothing changed while blocks was unlocked */ + BLOCK_LOCK_RD(query_block); + STRUCT_UNLOCK(&structure_guard_mutex); + + Query_cache_query * query = query_block->query(); + Query_cache_block * first_result_block = query->result(); + Query_cache_block * result_block = first_result_block; + if (result_block == 0 || + result_block->type != Query_cache_block::RESULT) + { + DBUG_PRINT("info", ("query found, but no data or data incomplete")); + DBUG_RETURN(1); //no data in query + } + DBUG_PRINT("info", ("Query have result 0x%lx", (ulong)query)); + + //check access; + TABLE_COUNTER_TYPE t = 0; + for(; t < query_block->n_tables; t++) + { + TABLE_LIST table_list; + table_list.next = 0; + table_list.use_index = table_list.ignore_index = 0; + table_list.table = 0; + table_list.grant.grant_table = 0; + table_list.grant.version = table_list.grant.privilege = + table_list.grant.want_privilege = 0; + table_list.outer_join = 0; + table_list.straight = 0; + table_list.updating = 0; + table_list.shared = 0; + + Query_cache_table * table = query_block->table(t)->parent; + table_list.db = table->db(); + table_list.name = table_list.real_name = table->table(); + if (check_table_access(thd,SELECT_ACL,&table_list)) + { + DBUG_PRINT("info", + ("probably no SELECT access to %s.%s =>\ + return to normal processing", + table_list.db, table_list.name)); + DBUG_RETURN(1); //no access + } + } + + move_to_query_list_end(query_block); + + hits++; + do + { + DBUG_PRINT("info", ("Results (len %lu, used %lu, headers %lu)", + result_block->length, result_block->used, + result_block->headers_len()+ + ALIGN_SIZE(sizeof(Query_cache_result)))); + + Query_cache_result * result = result_block->result(); + net_real_write(&thd->net, result->data(), + result_block->used - + result_block->headers_len() - + ALIGN_SIZE(sizeof(Query_cache_result))); + result_block = result_block->next; + } while (result_block != first_result_block); + + thd->limit_found_rows = query->found_rows(); + + BLOCK_UNLOCK_RD(query_block); + + DBUG_RETURN(0); +} + +void Query_cache::invalidate(TABLE_LIST *tables_used) +{ + DBUG_ENTER("Query_cache::invalidate (table list)"); + if (query_cache_size > 0) + { + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0) + { + DUMP(this); + for ( ; tables_used; tables_used=tables_used->next) + invalidate_table(tables_used); + } + STRUCT_UNLOCK(&structure_guard_mutex); + } + DBUG_VOID_RETURN; } -/* At the moment we do not really want to do anything upon delete */ -static void free_cache_entry(void *entry) +void Query_cache::invalidate(TABLE *table) { + DBUG_ENTER("Query_cache::invalidate (table)"); + if (query_cache_size > 0) + { + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0) + invalidate_table(table); + STRUCT_UNLOCK(&structure_guard_mutex); + } + DBUG_VOID_RETURN; } -/* Initialization of the SQL cache hash -- should be called during - the bootstrap stage */ -bool sql_cache_init(void) +void Query_cache::invalidate(Query_cache_table::query_cache_table_type type) { - if (query_buff_size) + DBUG_ENTER("Query_cache::invalidate (type)"); + if (query_cache_size > 0) { - VOID(hash_init(&sql_cache, 4096, 0, 0, - cache_key, - (void (*)(void*)) free_cache_entry, - 0)); + STRUCT_LOCK(&structure_guard_mutex); + DUMP(this); + if (query_cache_size > 0 && tables_blocks[type] != 0) + { + Query_cache_block *table_block = tables_blocks[type]; + do + { + /* + store next block address defore deletetion of current block + */ + Query_cache_block *next = table_block->next; + + invalidate_table(table_block); + + if (next == table_block) + break; + + table_block = next; + } while (table_block != tables_blocks[type]); + + } + STRUCT_UNLOCK(&structure_guard_mutex); } - return 0; + DBUG_VOID_RETURN; } -/* Clearing the SQL cache hash -- during shutdown */ -void sql_cache_free(void) +void Query_cache::invalidate(char *db) +{ + DBUG_ENTER("Query_cache::invalidate (db)"); + if (query_cache_size > 0) + { + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0) + { + DUMP(this); + int i = 0; + for(; i < (int) Query_cache_table::TYPES_NUMBER; i++) + { + if (tables_blocks[i] != 0) //cache not empty + { + Query_cache_block *table_block = tables_blocks[i]; + do + { + /* + store next block address defore deletetion of current block + */ + Query_cache_block *next = table_block->next; + + invalidate_table_in_db(table_block, db); + + if (table_block == next) + break; + + table_block = next; + } while (table_block != tables_blocks[i]); + } + } + } + STRUCT_UNLOCK(&structure_guard_mutex); + } + DBUG_VOID_RETURN; +} + +void Query_cache::invalidate_by_MyISAM_filename(char * filename) +{ + DBUG_ENTER("Query_cache::invalidate_by_MyISAM_filename"); + if (query_cache_size > 0) + { + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size > 0) + { + char key[MAX_DBKEY_LENGTH]; + uint key_length = + Query_cache::filename_2_table_key(key, filename); + + Query_cache_block *table_block; + if ((table_block = (Query_cache_block*) + hash_search(&tables, key, key_length))) + invalidate_table(table_block); + } + STRUCT_UNLOCK(&structure_guard_mutex); + } + DBUG_VOID_RETURN; +} +void Query_cache::flush() { - hash_free(&sql_cache); + DBUG_ENTER("Query_cache::flush"); + if (query_cache_size > 0) + { + DUMP(this); + STRUCT_LOCK(&structure_guard_mutex); + flush_cache(); + DUMP(this); + STRUCT_UNLOCK(&structure_guard_mutex); + } + DBUG_VOID_RETURN; } -/* Finds whether the SQL command is already in the cache, at any case - establishes correct LEX structure in the THD (either from - cache or a new one) */ +void Query_cache::pack(ulong join_limit, uint iteration_limit) +{ + DBUG_ENTER("Query_cache::pack"); + uint i = 0; + do + { + pack_cache(); + } while ((++i < iteration_limit) && join_results(join_limit)); + DBUG_VOID_RETURN; +} -int sql_cache_hit(THD *thd, char *sql, uint length) +void Query_cache::destroy() { -#ifdef QQ - LEX *ptr; - ptr = (LEX *)hash_search(&sql_cache, sql, length); - if (ptr) { - fprintf(stderr, "Query `%s' -- hit in the cache (%p)\n", ptr->sql_query_text, ptr); - thd->lex_ptr = ptr; - ptr->thd = thd; - } else { - thd->lex_ptr = ptr = lex_array + last_lex_array_item--; + DBUG_ENTER("Query_cache::destroy"); + free_cache(1); + pthread_mutex_destroy(&structure_guard_mutex); + initialized = 0; + DBUG_VOID_RETURN; +} + +#ifndef DBUG_OFF - lex_start(thd, (uchar *)sql, length); +void Query_cache::wreck(uint line, const char * message) +{ + DBUG_ENTER("Query_cache::wreck"); + query_cache_size = 0; + DBUG_PRINT("error", (" %s", message)); + DBUG_PRINT("warning", ("==================================")); + DBUG_PRINT("warning", ("%5d QUERY CACHE WRECK => DISABLED",line)); + DBUG_PRINT("warning", ("==================================")); + current_thd->killed = 1; + bins_dump(); + cache_dump(); + DBUG_VOID_RETURN; +} - if (hash_insert(&sql_cache, (const byte *)ptr)) { - fprintf(stderr, "Out of memory during hash_insert?\n"); +void Query_cache::bins_dump() +{ + DBUG_PRINT("info", ("mem_bin_num=%u, mem_bin_steps=%u", + mem_bin_num, mem_bin_steps)); + DBUG_PRINT("info", ("-------------------------")); + DBUG_PRINT("info", (" size idx step")); + DBUG_PRINT("info", ("-------------------------")); + uint i = 0; + for(; i < mem_bin_steps; i++) + { + DBUG_PRINT("info", ("%10lu %3d %10lu", steps[i].size, steps[i].idx, + steps[i].increment)); + } + DBUG_PRINT("info", ("-------------------------")); + DBUG_PRINT("info", (" size num")); + DBUG_PRINT("info", ("-------------------------")); + i = 0; + for(; i < mem_bin_num; i++) + { + DBUG_PRINT("info", ("%10lu %3d 0x%lx", bins[i].size, bins[i].number, + (ulong)&(bins[i]))); + if (bins[i].free_blocks) + { + Query_cache_block * block = bins[i].free_blocks; + do{ + DBUG_PRINT("info", ("\\-- %lu 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx", + block->length, (ulong)block, + (ulong)block->next, (ulong)block->prev, + (ulong)block->pnext, (ulong)block->pprev)); + block = block->next; + } while ( block != bins[i].free_blocks ); } - fprintf(stderr, "Query `%s' not found in the cache -- insert %p from slot %d\n", thd->lex_ptr->ptr, ptr, last_lex_array_item+1); - if (!hash_search(&sql_cache, sql, length)) { - fprintf(stderr, "I just enterred a hash key but it's not where -- what's that?\n"); - } else { - fprintf(stderr, "Inserted to cache\n"); + } + DBUG_PRINT("info", ("-------------------------")); +} + +void Query_cache::cache_dump() +{ + DBUG_PRINT("info", ("-------------------------------------")); + DBUG_PRINT("info", (" length used t nt")); + DBUG_PRINT("info", ("-------------------------------------")); + Query_cache_block * i = first_block; + do + { + DBUG_PRINT("info", + ("%10lu %10lu %1d %2d 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx", + i->length, i->used, (int)i->type, + i->n_tables, (ulong)i, + (ulong)i->next, (ulong)i->prev, (ulong)i->pnext, + (ulong)i->pprev)); + i = i->pnext; + } while ( i != first_block ); + DBUG_PRINT("info", ("-------------------------------------")); +} +void Query_cache::queries_dump() +{ + DBUG_PRINT("info", ("------------------")); + DBUG_PRINT("info", (" QUERIES")); + DBUG_PRINT("info", ("------------------")); + if (queries_blocks != 0) + { + Query_cache_block * i = queries_blocks; + do + { + uint len; + char * str = (char*) Query_cache_query::cache_key((byte*) i, &len, 0); + byte flags = (byte) str[len-1]; + str[len-1] = 0; //safe only under structure_guard_mutex locked + DBUG_PRINT("info", ("%u (%u,%u) %s",len, + ((flags & QUERY_CACHE_CLIENT_LONG_FLAG_MASK)?1:0), + (flags & QUERY_CACHE_CHARSET_CONVERT_MASK), str)); + str[len-1] = (char)flags; + DBUG_PRINT("info", ("-b- 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx", (ulong)i, + (ulong)i->next, (ulong)i->prev, (ulong)i->pnext, + (ulong)i->pprev)); + TABLE_COUNTER_TYPE t = 0; + for (; t < i->n_tables; t++) + { + Query_cache_table * table = i->table(t)->parent; + DBUG_PRINT("info", ("-t- '%s' '%s'", table->db(), table->table())); + } + Query_cache_query * header = i->query(); + if (header->result()) + { + Query_cache_block * result_block = header->result(); + Query_cache_block * result_beg = result_block; + do + { + DBUG_PRINT("info", ("-r- %u %lu/%lu 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx", + (uint) result_block->type, + result_block->length, result_block->used, + (ulong) result_block, + (ulong) result_block->next, + (ulong) result_block->prev, + (ulong) result_block->pnext, + (ulong) result_block->pprev)); + result_block = result_block->next; + } while ( result_block != result_beg ); + } + i = i->next; + } while ( i != queries_blocks ); + } + else + { + DBUG_PRINT("info", ("no queries in list")); + } + DBUG_PRINT("info", ("------------------")); +} + +void Query_cache::tables_dump() +{ + DBUG_PRINT("info", ("--------------------")); + DBUG_PRINT("info", ("TABLES")); + DBUG_PRINT("info", ("--------------------")); + int i = 0; + for(; i < (int) Query_cache_table::TYPES_NUMBER; i++) + { + DBUG_PRINT("info", ("--- type %u", i)); + if (tables_blocks[i] != 0) + { + Query_cache_block * table_block = tables_blocks[i]; + do + { + Query_cache_table * table = table_block->table(); + DBUG_PRINT("info", ("'%s' '%s'", table->db(), table->table())); + table_block = table_block->next; + } while ( table_block != tables_blocks[i]); } - return 0; + else + DBUG_PRINT("info", ("no tables in list")); } + DBUG_PRINT("info", ("--------------------")); +} + #endif - return 1; + +/***************************************************************************** + * init/destroy + *****************************************************************************/ + +void Query_cache::init() +{ + DBUG_ENTER("Query_cache::init"); + pthread_mutex_init(&structure_guard_mutex,MY_MUTEX_INIT_FAST); + initialized = 1; + DBUG_VOID_RETURN; +} + +ulong Query_cache::init_cache() +{ + DBUG_ENTER("Query_cache::init_cache"); + if (!initialized) + { + DBUG_PRINT("info", ("first time init")); + init(); + } + ulong additional_data_size = 0, + approx_additional_data_size = sizeof(Query_cache) + + sizeof(gptr)*(def_query_hash_size+def_query_hash_size); + + ulong max_mem_bin_size = 0; + if (query_cache_size < approx_additional_data_size) + { + additional_data_size = 0; + approx_additional_data_size = 0; + make_disabled(); + } + else + { + query_cache_size -= approx_additional_data_size; + + //Count memory bins number. + + /* + The idea is inherited from GNU malloc with some add-ons. + Free memory blocks are stored in bins according to their sizes. + The bins are stored in size-descending order. + The bins are approximately logarithmically separated by size. + + Opposite to GNU malloc bin splitting is not fixed but calculated + depending on cache size. Spliting calculating stored in cache and + then used for bin finding. + + For example: + query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 = 100, + min_allocation_unit = 17, + QUERY_CACHE_MEM_BIN_STEP_PWR2 = 1, + QUERY_CACHE_MEM_BIN_PARTS_INC = 1, + QUERY_CACHE_MEM_BIN_PARTS_MUL = 1 + (in followed picture showed right (low) bound of bin): + + | 100>>1 50>>1 |25>>1| + | | | | | | + | 100 75 50 41 33 25 21 18 15| 12 | - bins right (low) bounds + |\---/\-----/\--------/\--------|---/ | + | 0 1 2 3 | | - steps + \-----------------------------/ \---/ + bins that we store in cache this bin showed for example only + */ + max_mem_bin_size = + query_cache_size >> QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2; + uint mem_bin_count = 1 + QUERY_CACHE_MEM_BIN_PARTS_INC; + mem_bin_count = (uint) (mem_bin_count * QUERY_CACHE_MEM_BIN_PARTS_MUL); + mem_bin_num = 1; + mem_bin_steps = 1; + ulong mem_bin_size = max_mem_bin_size >> QUERY_CACHE_MEM_BIN_STEP_PWR2; + ulong prev_size = 0; + while (mem_bin_size > min_allocation_unit) + { + mem_bin_num += mem_bin_count; + prev_size = mem_bin_size; + mem_bin_size >>= QUERY_CACHE_MEM_BIN_STEP_PWR2; + mem_bin_steps++; + mem_bin_count += QUERY_CACHE_MEM_BIN_PARTS_INC; + mem_bin_count = (uint) (mem_bin_count * QUERY_CACHE_MEM_BIN_PARTS_MUL); + + //prevent too small bins spacing + if (mem_bin_count > (mem_bin_size>>QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2)) + mem_bin_count=(mem_bin_size>>QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2); + } + ulong inc = (prev_size - mem_bin_size) / mem_bin_count; + mem_bin_num += (mem_bin_count - (min_allocation_unit - mem_bin_size)/inc); + mem_bin_steps++; + additional_data_size = mem_bin_num * + ALIGN_SIZE(sizeof(Query_cache_memory_bin))+ + mem_bin_steps * ALIGN_SIZE(sizeof(Query_cache_memory_bin_step)); + } + if (query_cache_size < additional_data_size) + { + additional_data_size = 0; + approx_additional_data_size = 0; + make_disabled(); + } + else + query_cache_size -= additional_data_size; + + STRUCT_LOCK(&structure_guard_mutex); + if (query_cache_size <= min_allocation_unit) + { + DBUG_PRINT("info", + (" query_cache_size <= min_allocation_unit => cache disabled")); + make_disabled(); + } + else + { + if ( (cache = (byte *) + my_malloc_lock(query_cache_size+additional_data_size, MYF(0)))==0 ) + { + DBUG_PRINT("warning", + ("can't allocate query cache memory => cache disabled")); + make_disabled(); + } + else + { + DBUG_PRINT("info", + ("cache length %lu, min unit %lu, %u bins", + query_cache_size, min_allocation_unit, mem_bin_num)); + + steps = (Query_cache_memory_bin_step *) cache; + bins = (Query_cache_memory_bin *) + (cache + + mem_bin_steps * ALIGN_SIZE(sizeof(Query_cache_memory_bin_step))); + + first_block = (Query_cache_block *) (cache + additional_data_size); + first_block->init(query_cache_size); + first_block->pnext=first_block->pprev=first_block; + first_block->next=first_block->prev=first_block; + + free_memory = query_cache_size; + + /* prepare bins */ + + bins[0].init(max_mem_bin_size); + steps[0].init(max_mem_bin_size,0,0); + uint mem_bin_count = 1 + QUERY_CACHE_MEM_BIN_PARTS_INC; + mem_bin_count = (uint) (mem_bin_count * QUERY_CACHE_MEM_BIN_PARTS_MUL); + uint num = 1; + ulong mem_bin_size = max_mem_bin_size >> QUERY_CACHE_MEM_BIN_STEP_PWR2; + uint step = 1; + while (mem_bin_size > min_allocation_unit) + { + ulong inc = (steps[step-1].size - mem_bin_size) / mem_bin_count; + + unsigned long size = mem_bin_size; + uint i = mem_bin_count; + for(; i > 0; i--) + { + bins[num+i-1].init(size); + size += inc; + } + num += mem_bin_count; + steps[step].init(mem_bin_size, num-1, inc); + mem_bin_size >>= QUERY_CACHE_MEM_BIN_STEP_PWR2; + step++; + mem_bin_count += QUERY_CACHE_MEM_BIN_PARTS_INC; + mem_bin_count = (uint) (mem_bin_count * QUERY_CACHE_MEM_BIN_PARTS_MUL); + if (mem_bin_count > (mem_bin_size>>QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2)) + mem_bin_count=(mem_bin_size>>QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2); + } + ulong inc = (steps[step-1].size - mem_bin_size) / mem_bin_count; + /* + num + mem_bin_count > mem_bin_num, but index never be > mem_bin_num + because block with size < min_allocated_unit never will be requested + */ + steps[step].init(mem_bin_size, num + mem_bin_count - 1, inc); + uint skiped = (min_allocation_unit - mem_bin_size)/inc; + ulong size = mem_bin_size + inc*skiped; + uint i = mem_bin_count - skiped; + for(; i > 0; i--) + { + bins[num+i-1].init(size); + size += inc; + } + + insert_into_free_memory_list(first_block); + + DUMP(this); + + VOID(hash_init(&queries,def_query_hash_size, 0, 0, + Query_cache_query::cache_key, + (void (*)(void*))Query_cache_query::free_cache, + 0)); + VOID(hash_init(&tables,def_table_hash_size, 0, 0, + Query_cache_table::cache_key, + (void (*)(void*))Query_cache_table::free_cache, + 0)); + } + } + queries_in_cache = 0; + queries_blocks = 0; + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_RETURN(query_cache_size + + additional_data_size + approx_additional_data_size); +} + +void Query_cache::make_disabled() +{ + DBUG_ENTER("Query_cache::make_disabled"); + query_cache_size = 0; + free_memory = 0; + bins = 0; + steps = 0; + cache = 0; + mem_bin_num = mem_bin_steps = 0; + DBUG_VOID_RETURN; +} + +void Query_cache::free_cache(my_bool destruction) +{ + DBUG_ENTER("Query_cache::free_cache"); + if (query_cache_size > 0) + { + if (!destruction) + STRUCT_LOCK(&structure_guard_mutex); + + flush_cache(); +#ifndef DBUG_OFF + if (bins[0].free_blocks == 0) + { + wreck(__LINE__,"no free memory found in (bins[0].free_blocks"); + } +#endif + + bins[0].free_blocks->destroy(); /* all cache memory must be + in one this block */ + DBUG_PRINT("info", ("free memory %lu (should be %lu)", + free_memory , query_cache_size)); + free_memory = 0; + + my_free((gptr)cache, MYF(MY_ALLOW_ZERO_PTR)); + first_block = 0; + bins = 0; + cache = 0; + query_cache_size = 0; + hash_free(&queries); + hash_free(&tables); + queries_in_cache = 0; + if (!destruction) + STRUCT_UNLOCK(&structure_guard_mutex); + } + DBUG_VOID_RETURN; +} + +/***************************************************************************** + * free block data + *****************************************************************************/ + +void Query_cache::flush_cache() +{ + while(queries_blocks != 0){ + BLOCK_LOCK_WR(queries_blocks); + free_query(queries_blocks); + } +} + +my_bool Query_cache::free_old_query() +{ + DBUG_ENTER("Query_cache::free_old_query"); + if (queries_blocks == 0) + { + DBUG_RETURN(0); + } + /* + try_lock_writing used to prevent clinch because + here lock sequence is breached, also we don't need remove + locked queries at this point + */ + Query_cache_block *query_block = 0; + if (queries_blocks != 0) + { + Query_cache_block *i = queries_blocks; + do + { + Query_cache_query * header = i->query(); + if (header->result() != 0 && + header->result()->type == Query_cache_block::RESULT && + i->query()->try_lock_writing()) + { + query_block = i; + break; + } + i = i->next; + } while ( i != queries_blocks ); + } + + if (query_block != 0) + { + free_query(query_block); + DBUG_RETURN(1); + } + else + DBUG_RETURN(0); +} + +/* query_block must be lock_writing() */ +void Query_cache::free_query(Query_cache_block * query_block) +{ + DBUG_ENTER("Query_cache::free_query"); + DBUG_PRINT("info", ("free query 0x%lx %lu bytes result", + (ulong)query_block, + query_block->query()->length() )); + queries_in_cache--; + hash_delete(&queries,(byte *) query_block); + + Query_cache_query * query = query_block->query(); + if (query->writer() != 0) + { + query->writer()->query_cache_query = 0; + query->writer(0); + } + double_linked_list_exclude(query_block, queries_blocks); + TABLE_COUNTER_TYPE i = 0; + for(; i < query_block->n_tables; i++) + { + unlink_table(query_block->table(i)); + } + Query_cache_block *result_block = query->result(); + if (result_block != 0) + { + Query_cache_block * block = result_block; + do + { + Query_cache_block * current = block; + block = block->next; + free_memory_block(current); + } while (block != result_block); + } + + query->unlock_n_destroy(); + free_memory_block(query_block); + + DBUG_VOID_RETURN; +} + +/***************************************************************************** + * query data creation + *****************************************************************************/ + +Query_cache_block * +Query_cache::write_block_data(ulong data_len, gptr data, + ulong header_len, + Query_cache_block::block_type type, + TABLE_COUNTER_TYPE ntab, + my_bool under_guard) +{ + DBUG_ENTER("Query_cache::write_block_data"); + ulong all_headers_len = ALIGN_SIZE(sizeof(Query_cache_block)) + + ntab*ALIGN_SIZE(sizeof(Query_cache_block_table)) + + header_len; + DBUG_PRINT("info", ("data: %ld, header: %ld, all header: %ld", + data_len, header_len, all_headers_len)); + ulong len = data_len + all_headers_len; + Query_cache_block * block = allocate_block(max(len, min_allocation_unit), + 1, 0, under_guard); + if (block != 0) + { + block->type = type; + block->n_tables = ntab; + block->used = len; + + memcpy((void*)(((byte *) block)+ + all_headers_len), + (void*)data, + data_len); + } + DBUG_RETURN(block); +} + +my_bool +Query_cache::append_result_data(Query_cache_block * &result, + ulong data_len, gptr data, + Query_cache_block * query_block, + Query_cache_block * first_data_block) +{ + DBUG_ENTER("Query_cache::uppend_result_data"); + DBUG_PRINT("info", ("append %lu bytes to 0x%lx query")); + if (query_block->query()->add(data_len) > query_cache_limit) + { + DBUG_PRINT("info", ("size limit reached %lu > %lu", + query_block->query()->length(), + query_cache_limit)); + result=0; + DBUG_RETURN(0); + } + if (first_data_block == 0) + { + DBUG_PRINT("info", ("allocated first result data block 0x%xl", data_len)); + /* + STRUCT_UNLOCK(&structure_guard_mutex); will be done by + query_cache.append_result_data if success; + */ + DBUG_RETURN(write_result_data(result, data_len, data, query_block, + Query_cache_block::RES_BEG)); + } + else + { + result = first_data_block; + } + Query_cache_block * last_block = first_data_block->prev; + + DBUG_PRINT("info", ("lastblock 0x%lx len %lu used %lu", + (ulong)last_block, last_block->length, + last_block->used)); + my_bool success = 1; + + ulong last_block_free_space = last_block->length - last_block->used; + + //write 'tail' of data, that can't be appended to last block + + //try join blocks if physicaly next block is free... + if (last_block_free_space < data_len && + append_next_free_block(last_block, + max(data_len - last_block_free_space, + QUERY_CACHE_MIN_RESULT_DATA_SIZE))) + last_block_free_space = last_block->length - last_block->used; + //if no space in last block (even after join) allocate new block + if (last_block_free_space < data_len) + { + //TODO try get memory from next free block (if exist) (is it needed?) + DBUG_PRINT("info", ("allocate new block for %lu bytes", + data_len-last_block_free_space)); + Query_cache_block *new_block = 0; + /* + STRUCT_UNLOCK(&structure_guard_mutex); will be done by + query_cache.append_result_data + */ + success = write_result_data(new_block, data_len-last_block_free_space, + (gptr)(((byte*)data)+last_block_free_space), + query_block, + Query_cache_block::RES_CONT); + /* + new_block may be not 0 even !success (if write_result_data + allocate small block but filed allocate continue + */ + if (new_block != 0) + double_linked_list_join(last_block, new_block); + } + else + //it is success (nobody can prevent us write data) + STRUCT_UNLOCK(&structure_guard_mutex); + + // append last block (if it is possible) + if (last_block_free_space > 0) + { + ulong to_copy = min(data_len,last_block_free_space); + DBUG_PRINT("info", ("use free space %lub at block 0x%lx to copy %lub", + last_block_free_space, (ulong)last_block, to_copy)); + memcpy((void*)(((byte*)last_block)+last_block->used), + (void*)data,to_copy); + last_block->used+=to_copy; + } + + DBUG_RETURN(success); +} + +my_bool Query_cache::write_result_data(Query_cache_block * &result_block, + ulong data_len, gptr data, + Query_cache_block * query_block, + Query_cache_block::block_type type) +{ + DBUG_ENTER("Query_cache::write_result_data"); + DBUG_PRINT("info", ("data_len %lu",data_len)); + + //reserve block(s) for filling + my_bool success = allocate_data_chain(result_block, data_len, query_block); + if (success) + { + //it is success (nobody can prevent us write data) + STRUCT_UNLOCK(&structure_guard_mutex); + byte * rest = (byte*) data; + Query_cache_block * block = result_block; + uint headers_len = ALIGN_SIZE(sizeof(Query_cache_block)) + + ALIGN_SIZE(sizeof(Query_cache_result)); + // now fill list of blocks that created by allocate_data_chain + do + { + block->type = type; + ulong length = block->used - headers_len; + DBUG_PRINT("info", ("write %lu byte in block 0x%lx",length, + (ulong)block)); + memcpy((void*)(((byte*)block)+headers_len), + (void*)rest,length); + + rest += length; + block = block->next; + type = Query_cache_block::RES_CONT; + } while (block != result_block); + } + else + { + if (result_block != 0) + { + // destroy list of blocks that created & locked by lock_result_data + Query_cache_block * block = result_block; + do + { + Query_cache_block * current = block; + block = block->next; + free_memory_block(current); + } while (block != result_block); + result_block = 0; + /* + it is not success => not unlock structure_guard_mutex (we need it to + free query) + */ + } + } + DBUG_PRINT("info", ("success %d", (int) success)); + DBUG_RETURN(success); +} + +my_bool Query_cache::allocate_data_chain(Query_cache_block * &result_block, + ulong data_len, + Query_cache_block * query_block) +{ + DBUG_ENTER("Query_cache::allocate_data_chain"); + + ulong all_headers_len = ALIGN_SIZE(sizeof(Query_cache_block)) + + ALIGN_SIZE(sizeof(Query_cache_result)); + ulong len = data_len + all_headers_len; + DBUG_PRINT("info", ("data_len %lu, all_headers_len %lu", + data_len, all_headers_len)); + + result_block = allocate_block(max(min_result_data_size,len), + min_result_data_size == 0, + all_headers_len + min_result_data_size, + 1); + my_bool success = (result_block != 0); + if (success) + { + result_block->n_tables = 0; + result_block->used = 0; + result_block->type = Query_cache_block::RES_INCOMPLETE; + result_block->next = result_block->prev = result_block; + Query_cache_result * header = result_block->result(); + header->parent(query_block); + + Query_cache_block * next_block = 0; + if (result_block->length < len) + { + /* + allocated less memory then we need (no big memory blocks) => + to be continue + */ + Query_cache_block * next_block; + if ((success = allocate_data_chain(next_block, + len - result_block->length, + query_block))) + double_linked_list_join(result_block, next_block); + } + if (success) + { + result_block->used = min(len, result_block->length); + + DBUG_PRINT("info", ("Block len %lu used %lu", + result_block->length, result_block->used)); + } + else + DBUG_PRINT("warning", ("Can't allocate block for continue")); + } + else + DBUG_PRINT("warning", ("Can't allocate block for results")); + DBUG_RETURN(success); +} + +/***************************************************************************** + * tables management + *****************************************************************************/ + +void Query_cache::invalidate_table(TABLE_LIST *table_list) +{ + if (table_list->table != 0) + invalidate_table(table_list->table); + else + { + char key[MAX_DBKEY_LENGTH], *key_ptr; + uint key_length; + Query_cache_block *table_block; + key_length=(uint) (strmov(strmov(key,table_list->db)+1, + table_list->real_name) -key)+ 1; + key_ptr = key; + + // we dont store temporary tables => no key_length+=4 ... + if ((table_block = (Query_cache_block*) + hash_search(&tables,key_ptr,key_length))) + invalidate_table(table_block); + } +} + +void Query_cache::invalidate_table(TABLE *table) +{ + Query_cache_block *table_block; + if ((table_block = (Query_cache_block*) + hash_search(&tables,table->table_cache_key,table->key_length))) + invalidate_table(table_block); +} + +void Query_cache::invalidate_table_in_db(Query_cache_block *table_block, + char * db) +{ + /* + table key consist of data_base_name + '\0' + table_name +'\0'... + => we may use strcmp. + */ + if (strcmp(db, (char*)(table_block->table()->db()))==0) + { + invalidate_table(table_block); + } +} + +void Query_cache::invalidate_table(Query_cache_block *table_block) +{ + Query_cache_block_table * list_root = table_block->table(0); + while(list_root->next != list_root) + { + Query_cache_block * query_block = list_root->next->block(); + BLOCK_LOCK_WR(query_block); + free_query(query_block); + } +} + +my_bool Query_cache::register_all_tables(Query_cache_block * block, + TABLE_LIST * tables_used, + TABLE_COUNTER_TYPE tables) +{ + DBUG_PRINT("info", ("register tables block 0x%lx, n %d, header %x", + (ulong)block, (int) tables, + (int)ALIGN_SIZE(sizeof(Query_cache_block)) )); + + TABLE_COUNTER_TYPE n = 0; + TABLE_LIST * i = tables_used; + for(; i != 0; i=i->next, n++) + { + DBUG_PRINT("info", + ("table %s, db %s, openinfo at 0x%lx, keylen %u, key at 0x%lx", + i->real_name, i->db, (ulong) i->table, + i->table->key_length, + (ulong) i->table->table_cache_key)); + Query_cache_block_table * block_table = block->table(n); + block_table->n=n; + if (!insert_table(i->table->key_length, + i->table->table_cache_key, block_table, + Query_cache_table::type_convertion(i->table->db_type))) + break; + if (i->table->db_type == DB_TYPE_MRG_MYISAM) + { + ha_myisammrg * handler = (ha_myisammrg *)i->table->file; + MYRG_INFO *file = handler->myrg_info(); + MYRG_TABLE *table = file->open_tables; + for(;table != file->end_table ; table++) + { + char key[MAX_DBKEY_LENGTH]; + uint key_length = + Query_cache::filename_2_table_key(key, table->table->filename); + n++; + Query_cache_block_table * block_table = block->table(n); + block_table->n=n; + if (!insert_table(key_length, key, block_table, + Query_cache_table::type_convertion(DB_TYPE_MYISAM))) + goto err; + } + } + } +err: + if (i != 0) + { + n--; + DBUG_PRINT("info", ("filed at table %d", (int)n)); + TABLE_COUNTER_TYPE idx = 0; + for(i = tables_used; + idx < n; + idx++) + { + unlink_table(block->table(n)); + } + } + return(i == 0); +} + +my_bool Query_cache::insert_table(uint key_len, char * key, + Query_cache_block_table * node, + Query_cache_table::query_cache_table_type + type) +{ + DBUG_ENTER("Query_cache::insert_table"); + DBUG_PRINT("info", ("insert table node 0x%lx, len %d", + (ulong)node, key_len)); + Query_cache_block *table_block = (Query_cache_block *) + hash_search(&tables, key, key_len); + + if (table_block == 0) + { + DBUG_PRINT("info", ("new table block from 0x%lx (%u)", + (ulong)key, (int) key_len)); + table_block = write_block_data(key_len, (gptr) key, + ALIGN_SIZE(sizeof(Query_cache_table)), + Query_cache_block::TABLE, + 1, 1); + if (table_block == 0) + { + DBUG_PRINT("info", ("Can't write table name to cache")); + DBUG_RETURN(0); + } + Query_cache_table * header = table_block->table(); + header->type(type); + double_linked_list_simple_include(table_block, + tables_blocks[type]); + Query_cache_block_table * list_root = table_block->table(0); + list_root->n = 0; + list_root->next = list_root->prev = list_root; + if (hash_insert(&tables, (const byte *)table_block)) + { + DBUG_PRINT("info", ("Can't insert table to hash")); + // write_block_data return locked block + free_memory_block(table_block); + DBUG_RETURN(0); + } + char * db = header->db(); + header->table(db + strlen(db) + 1); + } + + Query_cache_block_table * list_root = table_block->table(0); + node->next = list_root->next; list_root->next = node; + node->next->prev = node; node->prev = list_root; + node->parent = table_block->table(); + DBUG_RETURN(1); +} + +void Query_cache::unlink_table(Query_cache_block_table * node) +{ + node->prev->next = node->next; node->next->prev = node->prev; + Query_cache_block_table * neighbour = node->next; + if (neighbour->next == neighbour) + { + // list is empty (neighbour is root of list) + Query_cache_block * table_block = neighbour->block(); + double_linked_list_exclude(table_block, + tables_blocks[table_block->table()->type()]); + hash_delete(&tables,(byte *) table_block); + free_memory_block(table_block); + } +} + +/***************************************************************************** + * free memory management + *****************************************************************************/ + +Query_cache_block * Query_cache::allocate_block(ulong len, + my_bool not_less, + ulong min, + my_bool under_guard) +{ + DBUG_ENTER("Query_cache::allocate_n_lock_block"); + DBUG_PRINT("info", ("len %lu, not less %d, min %lu, uder_guard %d", + len, not_less,min,under_guard)); + + if (len >= min(query_cache_size, query_cache_limit)) + { + DBUG_PRINT("info", ("Query cache hase only %lu memory and limit %lu", + query_cache_size, query_cache_limit)); + DBUG_RETURN(0); // in any case we don't have such piece of memory + } + + if (!under_guard){STRUCT_LOCK(&structure_guard_mutex);}; + + Query_cache_block *block = get_free_block(len, not_less, min); + while( block == 0 && free_old_query()) + { + block = get_free_block(len, not_less, min); + } + + if (block!=0) + { + if (block->length > ALIGN_SIZE(len) + min_allocation_unit) + split_block(block,ALIGN_SIZE(len)); + } + + if (!under_guard){STRUCT_UNLOCK(&structure_guard_mutex);}; + DBUG_RETURN(block); +} + +Query_cache_block * Query_cache::get_free_block (ulong len, + my_bool not_less, + ulong min) +{ + DBUG_ENTER("Query_cache::get_free_block"); + Query_cache_block *block = 0, *first = 0; + DBUG_PRINT("info",("length %lu, not_less %d, min %lu", len, + (int)not_less, min)); + + /* find block with minimal size > len */ + + uint start = find_bin(len); + // try matching bin + if (bins[start].number != 0) + { + Query_cache_block * list = bins[start].free_blocks; + first = list; + while(first->next != list && first->length < len) + { + first=first->next; + } + if (first->length >= len) + block=first; + } + if (block == 0 && start > 0) + { + DBUG_PRINT("info",("try bins whith more bigger blocks")); + //try more big bins + int i = start - 1; + while(i > 0 && bins[i].number == 0) + i--; + if (bins[i].number > 0) + block = bins[i].free_blocks; + } + // if no big blocks => try less size (if it is possible) + if (block == 0 && ! not_less) + { + DBUG_PRINT("info",("try smaller blocks")); + if (first != 0 && first->length > min) + block = first; + else + { + uint i = start + 1; + while( i < mem_bin_num && bins[i].number == 0) + i++; + if (i < mem_bin_num && bins[i].number > 0 && + bins[i].free_blocks->prev->length >= min) + block = bins[i].free_blocks->prev; + } + } + if (block != 0) + exclude_from_free_memory_list(block); + + DBUG_PRINT("info",("getting block 0x%lx", (ulong) block)); + DBUG_RETURN(block); +} + +void Query_cache::free_memory_block(Query_cache_block * block) +{ + DBUG_ENTER("Query_cache::free_n_unlock_memory_block"); + block->used=0; + DBUG_PRINT("info",("first_block 0x%lx, block 0x%lx, pnext 0x%lx pprev 0x%lx", + (ulong) first_block, (ulong) block,block->pnext, + (ulong) block->pprev)); + if (block->pnext != first_block && block->pnext->is_free()) + { + block = join_free_blocks(block, block->pnext); + } + if (block != first_block && block->pprev->is_free()) + { + block = join_free_blocks(block->pprev, block->pprev); + } + insert_into_free_memory_list(block); + DBUG_VOID_RETURN; +} + +void Query_cache::split_block(Query_cache_block * block,ulong len) +{ + DBUG_ENTER("Query_cache::split_block"); + Query_cache_block * new_block = (Query_cache_block *)(((byte*)block)+len); + + new_block->init(block->length - len); + block->length=len; + new_block->pnext = block->pnext; block->pnext = new_block; + new_block->pprev = block; new_block->pnext->pprev = new_block; + + insert_into_free_memory_list(new_block); + + DBUG_PRINT("info", ("split 0x%lx (%lu) new 0x%lx", + (ulong) block, len, (ulong) new_block)); + DBUG_VOID_RETURN; +} + +Query_cache_block * +Query_cache::join_free_blocks(Query_cache_block *first_block, + Query_cache_block * block_in_list) +{ + DBUG_ENTER("Query_cache::join_free_blocks"); + DBUG_PRINT("info", + ("join first 0x%lx, pnext 0x%lx, in list 0x%lx", + (ulong) first_block, (ulong) first_block->pnext, + (ulong) block_in_list)); + exclude_from_free_memory_list(block_in_list); + + Query_cache_block * second_block = first_block->pnext; + // may be was not free block + second_block->type = Query_cache_block::FREE;second_block->used=0; + second_block->destroy(); + + first_block->length += second_block->length; + first_block->pnext = second_block->pnext; + second_block->pnext->pprev = first_block; + + DBUG_RETURN(first_block); +} + +my_bool Query_cache::append_next_free_block(Query_cache_block * block, + ulong add_size) +{ + DBUG_ENTER("Query_cache::append_next_free_block"); + DBUG_PRINT("enter", ("block 0x%lx, add_size %lu", (ulong) block, + add_size)); + Query_cache_block * next_block = block->pnext; + if (next_block->is_free()) + { + exclude_from_free_memory_list(next_block); + ulong old_len = block->length; + + next_block->destroy(); + + block->length += next_block->length; + block->pnext = next_block->pnext; + next_block->pnext->pprev = block; + + if (block->length > + ALIGN_SIZE(old_len + add_size) + min_allocation_unit) + split_block(block,ALIGN_SIZE(old_len + add_size)); + DBUG_PRINT("exit", ("block was appended")); + DBUG_RETURN(1); + } + else + { + DBUG_PRINT("exit", ("block was not appended")); + DBUG_RETURN(0); + } +} + +void Query_cache::exclude_from_free_memory_list(Query_cache_block * free_block) +{ + DBUG_ENTER("Query_cache::exclude_from_free_memory_list"); + Query_cache_memory_bin * bin = *((Query_cache_memory_bin **) + free_block->data()); + double_linked_list_exclude(free_block ,bin->free_blocks); + bin->number--; + free_memory-=free_block->length; + DBUG_PRINT("info",("exclude block 0x%lx, bin 0x%lx", (ulong) free_block, + (ulong)bin)); + DBUG_VOID_RETURN; +} + +void Query_cache::insert_into_free_memory_list(Query_cache_block * free_block) +{ + DBUG_ENTER("Query_cache::insert_into_free_memory_list"); + uint idx = find_bin(free_block->length); + insert_into_free_memory_sorted_list(free_block, bins[idx].free_blocks); + /* + we have enough memory in block for storing bin reference due to + min_allocation_unit choice + */ + Query_cache_memory_bin * *bin_ptr = (Query_cache_memory_bin**) + free_block->data(); + *bin_ptr = &(bins[idx]); + bins[idx].number++; + DBUG_PRINT("info",("insert block 0x%lx, bin[%d] 0x%lx", + (ulong) free_block, idx, + (ulong) &(bins[idx]))); + DBUG_VOID_RETURN; +} + +uint Query_cache::find_bin(ulong size) +{ + DBUG_ENTER("Query_cache::find_bin"); + //begin small blocks to big (small blocks frequently asked) + int i = mem_bin_steps - 1; + for(; i > 0 && steps[i-1].size < size; i--); + if (i == 0) + { + // first bin not subordinate of common rules + DBUG_PRINT("info", ("first bin (# 0), size %lu",size)); + DBUG_RETURN(0); + } + uint bin = steps[i].idx - (uint)((size - steps[i].size)/steps[i].increment); + DBUG_PRINT("info", ("bin %u step %u, size %lu", bin, i, size)); + DBUG_RETURN(bin); +} + +/***************************************************************************** + * lists management + *****************************************************************************/ + +void Query_cache::move_to_query_list_end(Query_cache_block * query_block) +{ + DBUG_ENTER("Query_cache::move_to_query_list_end"); + double_linked_list_exclude(query_block, queries_blocks); + double_linked_list_simple_include(query_block, queries_blocks); + DBUG_VOID_RETURN; +} + +void Query_cache::insert_into_free_memory_sorted_list(Query_cache_block * + new_block, + Query_cache_block * + &list) +{ + DBUG_ENTER("Query_cache::insert_into_free_memory_sorted_list"); + /* + list sorted by size in ascendant order, because we need small blocks + frequently than big one + */ + + new_block->used = 0; + new_block->n_tables = 0; + new_block->type = Query_cache_block::FREE; + + if (list == 0) + { + list = new_block->next=new_block->prev=new_block; + DBUG_PRINT("info", ("inserted into empty list")); + } + else + { + Query_cache_block *point = list; + if (point->length >= new_block->length) + { + point = point->prev; + list = new_block; + } + else + { + while(point->next != list && + point->next->length < new_block->length) + { + point=point->next; + } + } + new_block->prev = point; new_block->next = point->next; + new_block->next->prev = new_block; point->next = new_block; + } + free_memory+=new_block->length; + DBUG_VOID_RETURN; +} + + +void +Query_cache::double_linked_list_simple_include(Query_cache_block * point, + Query_cache_block * + &list_pointer) +{ + DBUG_ENTER("Query_cache::double_linked_list_simple_include"); + DBUG_PRINT("info", ("including block 0x%lx", (ulong) point)); + if (list_pointer == 0) + list_pointer=point->next=point->prev=point; + else + { + point->next = list_pointer; + point->prev = list_pointer->prev; + point->prev->next = point; + list_pointer->prev = point; + list_pointer = point; + } + DBUG_VOID_RETURN; +} + +void +Query_cache::double_linked_list_exclude(Query_cache_block *point, + Query_cache_block * &list_pointer) +{ + DBUG_ENTER("Query_cache::double_linked_list_exclude"); + DBUG_PRINT("info", ("excluding block 0x%lx, list 0x%lx", + (ulong) point, (ulong) list_pointer)); + if (point->next == point) + list_pointer = 0; // empty list + else + { + point->next->prev = point->prev; + point->prev->next = point->next; + if (point == list_pointer) list_pointer = point->next; + } + DBUG_VOID_RETURN; +} + +void Query_cache::double_linked_list_join(Query_cache_block *head_tail, + Query_cache_block *tail_head) +{ + Query_cache_block *head_head = head_tail->next, + *tail_tail = tail_head->prev; + head_head->prev = tail_tail; + head_tail->next = tail_head; + tail_head->prev = head_tail; + tail_tail->next = head_head; +} + +/***************************************************************************** + * query + *****************************************************************************/ + +/* + if query is cachable return numder tables in query + (query whithout tables tot chached) +*/ +TABLE_COUNTER_TYPE Query_cache::is_cachable(THD *thd, + uint query_len, char *query, + LEX *lex, TABLE_LIST* tables_used) +{ + TABLE_COUNTER_TYPE tables = 0; + DBUG_ENTER("Query_cache::is_cachable"); + if (lex->sql_command == SQLCOM_SELECT && + thd->temporary_tables == 0 && + (thd->query_cache_type == 1 || (thd->query_cache_type == 2 && + (lex->select->options & + OPTION_TO_QUERY_CACHE))) && + thd->safe_to_cache_query) + { + DBUG_PRINT("info", ("options %lx %lx, type %u", + OPTION_TO_QUERY_CACHE, + lex->select->options, + (int) thd->query_cache_type)); + my_bool has_transactions = 0; + TABLE_LIST * i = tables_used; + for(; i != 0; i=i->next) + { + tables++; + DBUG_PRINT("info", ("table %s, db %s, type %u", i->real_name, + i->db, i->table->db_type)); + has_transactions = (has_transactions || + i->table->file->has_transactions()); + if (i->table->db_type == DB_TYPE_MRG_ISAM) + { + DBUG_PRINT("info", ("select not cachable: used MRG_ISAM table(s)")); + DBUG_RETURN(0); + } + if (i->table->db_type == DB_TYPE_MRG_MYISAM) + { + ha_myisammrg * handler = (ha_myisammrg *)i->table->file; + MYRG_INFO *file = handler->myrg_info(); + MYRG_TABLE *table = file->open_tables; + for(;table != file->end_table ; table++) + { + tables++; + DBUG_PRINT("loop", (" + 1 table (mrg_MyISAM)")); + } + } + } + + if ((thd->options & (OPTION_NOT_AUTO_COMMIT | OPTION_BEGIN)) && + has_transactions) + { + DBUG_PRINT("info", ("not in autocommin mode")); + DBUG_RETURN(0); + } + else + { + DBUG_PRINT("info", ("select have %d tables", tables)); + DBUG_RETURN(tables); + } + } + DBUG_PRINT("info", + ("not interest query: %d or not cachable, \ +options %lx %lx, type %u", + (int) lex->sql_command, + OPTION_TO_QUERY_CACHE, + lex->select->options, + (int) thd->query_cache_type)); + DBUG_RETURN(0); +} + +/***************************************************************************** + * packing + *****************************************************************************/ + +void Query_cache::pack_cache() +{ + DBUG_ENTER("Query_cache::pack_cache"); + STRUCT_LOCK(&structure_guard_mutex); + DUMP(this); + byte * border = 0; + Query_cache_block * before = 0; + ulong gap = 0; + my_bool ok = 1; + Query_cache_block * i = first_block; + do + { + ok = move_by_type(border, before, gap, i); + i = i->pnext; + } while (ok && i != first_block); + if (border != 0) + { + Query_cache_block * new_block = (Query_cache_block *) border; + new_block->init(gap); + new_block->pnext = before->pnext; before->pnext = new_block; + new_block->pprev = before; new_block->pnext->pprev = new_block; + insert_into_free_memory_list(new_block); + } + DUMP(this); + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_VOID_RETURN; +} + +my_bool Query_cache::move_by_type(byte * &border, + Query_cache_block * &before, ulong &gap, Query_cache_block * i) +{ + DBUG_ENTER("Query_cache::move_by_type"); + my_bool ok = 1; + switch(i->type) + { + case Query_cache_block::FREE: + { + DBUG_PRINT("info", ("block 0x%lx FREE", (ulong) i)); + if (border == 0) + { + border = (byte *) i; + before = i->pprev; + DBUG_PRINT("info", ("gap begining here")); + } + exclude_from_free_memory_list(i); + gap +=i->length; + i->pprev->pnext=i->pnext; + i->pnext->pprev=i->pprev; + i->destroy(); + DBUG_PRINT("info", ("added to gap (%lu)", gap)); + break; + } + case Query_cache_block::TABLE: + { + DBUG_PRINT("info", ("block 0x%lx TABLE", (ulong) i)); + if (border == 0) break; + ulong len = i->length, + used = i->used; + Query_cache_block_table * list_root = i->table(0); + Query_cache_block_table + *tprev = list_root->prev, + *tnext = list_root->next; + Query_cache_block + *prev = i->prev, + *next = i->next, + *pprev = i->pprev, + *pnext = i->pnext, + *new_block =(Query_cache_block *) border; + char * data = (char*)i->data(); + hash_delete(&tables, (byte *)i); + i->destroy(); + new_block->init(len); + new_block->type=Query_cache_block::TABLE; + new_block->used=used; + new_block->n_tables=1; + memcpy((char*)new_block->data(), data, + len-new_block->headers_len()); + relink(i, new_block, next, prev, pnext, pprev); + if (tables_blocks[new_block->table()->type()] == i) + { + tables_blocks[new_block->table()->type()] = new_block; + } + Query_cache_block_table * nlist_root = new_block->table(0); + nlist_root->n = 0; + nlist_root->next = (tnext==list_root?nlist_root:tnext); + nlist_root->prev = (tprev==list_root?nlist_root:tnext); + tnext->prev = list_root; + tprev->next = list_root; + border += len; + before = new_block; + hash_insert(&tables, (const byte *)new_block); + DBUG_PRINT("info", ("moved %lu bytes to 0x%lx, new gap at 0x%lx", + len, (ulong) new_block, (ulong) border)); + break; + } + case Query_cache_block::QUERY: + { + DBUG_PRINT("info", ("block 0x%lx QUERY", (ulong) i)); + if (border == 0) break; + BLOCK_LOCK_WR(i); + ulong len = i->length, + used = i->used; + TABLE_COUNTER_TYPE n_tables = i->n_tables; + Query_cache_block + *prev = i->prev, + *next = i->next, + *pprev = i->pprev, + *pnext = i->pnext, + *new_block =(Query_cache_block *) border; + char * data = (char*)i->data(); + Query_cache_block * first_result_block = ((Query_cache_query *) + i->data())->result(); + hash_delete(&queries, (byte *)i); + memcpy((char*)new_block->table(0), (char*)i->table(0), + n_tables * ALIGN_SIZE(sizeof(Query_cache_block_table))); + i->query()->unlock_n_destroy(); + i->destroy(); + new_block->init(len); + new_block->type=Query_cache_block::QUERY; + new_block->used=used; + new_block->n_tables=n_tables; + memcpy((char*)new_block->data(), data, + len - new_block->headers_len()); + relink(i, new_block, next, prev, pnext, pprev); + if (queries_blocks == i) + queries_blocks = new_block; + TABLE_COUNTER_TYPE j=0; + for(; j< n_tables; j++) + { + Query_cache_block_table * block_table = new_block->table(j); + block_table->next->prev = block_table; + block_table->prev->next = block_table; + } + DBUG_PRINT("info", ("after cicle tt")); + border += len; + before = new_block; + new_block->query()->result(first_result_block); + if (first_result_block != 0) + { + Query_cache_block * result_block = first_result_block; + do + { + result_block->result()->parent(new_block); + result_block = result_block->next; + } while ( result_block != first_result_block ); + } + NET * net = new_block->query()->writer(); + if (net != 0) + { + net->query_cache_query = (gptr) new_block; + } + hash_insert(&queries, (const byte *)new_block); + DBUG_PRINT("info", ("moved %lu bytes to 0x%lx, new gap at 0x%lx", + len, (ulong) new_block, (ulong) border)); + break; + } + case Query_cache_block::RES_INCOMPLETE: + case Query_cache_block::RES_BEG: + case Query_cache_block::RES_CONT: + case Query_cache_block::RESULT: + { + DBUG_PRINT("info", ("block 0x%lx RES* (%d)", (ulong) i, (int)i->type)); + if (border == 0) break; + Query_cache_block + *query_block = i->result()->parent(), + *next = i->next, + *prev = i->prev; + Query_cache_block::block_type type = i->type; + BLOCK_LOCK_WR(query_block); + ulong len = i->length, + used = i->used; + Query_cache_block + *pprev = i->pprev, + *pnext = i->pnext, + *new_block =(Query_cache_block *) border; + char * data = (char*)i->data(); + i->destroy(); + new_block->init(len); + new_block->type=type; + new_block->used=used; + memcpy((char*)new_block->data(), data, + len - new_block->headers_len()); + relink(i, new_block, next, prev, pnext, pprev); + new_block->result()->parent(query_block); + Query_cache_query * query = query_block->query(); + if (query->result() == i) + query->result(new_block); + border += len; + before = new_block; + /* if result writing complete && we have free space in block */ + ulong free_space = new_block->length - new_block->used; + if (query->result()->type == Query_cache_block::RESULT && + new_block->length > new_block->used && + gap + free_space > min_allocation_unit && + new_block->length - free_space > min_allocation_unit) + { + border -= free_space; + gap += free_space; + new_block->length -= free_space; + } + BLOCK_UNLOCK_WR(query_block); + DBUG_PRINT("info", ("moved %lu bytes to 0x%lx, new gap at 0x%lx", + len, (ulong) new_block, (ulong) border)); + break; + } + default: + DBUG_PRINT("error", ("unexpectet block type %d, block 0x%lx", + (int)i->type, (ulong) i)); + ok = 0; + } + DBUG_RETURN(ok); +} + +void Query_cache::relink(Query_cache_block * oblock, + Query_cache_block * nblock, + Query_cache_block * next, Query_cache_block * prev, + Query_cache_block * pnext, Query_cache_block * pprev) +{ + nblock->prev = (prev==oblock?nblock:prev); //check pointer to himself + nblock->next = (next==oblock?nblock:next); + prev->next=nblock; + next->prev=nblock; + nblock->pprev = pprev; //physical pointer to himself have only 1 free block + nblock->pnext = pnext; + pprev->pnext=nblock; + pnext->pprev=nblock; +} + +my_bool Query_cache::join_results(ulong join_limit) +{ + //TODO + DBUG_ENTER("Query_cache::join_results"); + my_bool has_moving = 0; + Query_cache_block *query_block = 0; + STRUCT_LOCK(&structure_guard_mutex); + if (queries_blocks != 0) + { + Query_cache_block *i = queries_blocks; + do + { + Query_cache_query * header = i->query(); + if (header->result() != 0 && + header->result()->type == Query_cache_block::RESULT && + header->length() > join_limit) + { + Query_cache_block *new_result_block = + get_free_block(header->length() + + ALIGN_SIZE(sizeof(Query_cache_block)) + + ALIGN_SIZE(sizeof(Query_cache_result)), 1, 0); + if (new_result_block != 0) + { + has_moving = 1; + Query_cache_block *first_result = header->result(); + ulong new_len = header->length() + + ALIGN_SIZE(sizeof(Query_cache_block)) + + ALIGN_SIZE(sizeof(Query_cache_result)); + if (new_result_block->length > + ALIGN_SIZE(new_len) + min_allocation_unit) + split_block(new_result_block,ALIGN_SIZE(new_len)); + BLOCK_LOCK_WR(i); + header->result(new_result_block); + new_result_block->type = Query_cache_block::RESULT; + new_result_block->n_tables = 0; + new_result_block->used = new_len; + + new_result_block->next = new_result_block->prev = new_result_block; + DBUG_PRINT("info", ("new block %lu/%lu (%lu)", + new_result_block->length, + new_result_block->used, + header->length())); + + Query_cache_result *new_result = new_result_block->result(); + new_result->parent(i); + byte *write_to = (byte*) new_result->data(); + Query_cache_block *result_block = first_result; + do + { + ulong len = result_block->used - result_block->headers_len() - + ALIGN_SIZE(sizeof(Query_cache_result)); + DBUG_PRINT("loop", ("add block %lu/%lu (%lu)", + result_block->length, + result_block->used, + len)); + memcpy((char *) write_to, + (char*) result_block->result()->data(), + len); + write_to += len; + Query_cache_block * old_result_block = result_block; + result_block = result_block->next; + free_memory_block(old_result_block); + } while (result_block != first_result); + BLOCK_UNLOCK_WR(i); + } + } + i = i->next; + } while ( i != queries_blocks ); + } + STRUCT_UNLOCK(&structure_guard_mutex); + DBUG_RETURN(has_moving); +} + +uint Query_cache::filename_2_table_key (char * key, char *filename) +{ + DBUG_ENTER("Query_cache::filename_2_table_key"); + char tablename[FN_REFLEN]; + char dbbuff[FN_REFLEN]; + char *dbname; + Query_cache_block *table_block; + fn_format(tablename,filename,"","",3); + + int path_len = (strrchr(filename, '/') - filename); + strncpy(dbbuff, filename, path_len); + dbbuff[path_len] = '\0'; + dbname = strrchr(dbbuff, '/') + 1; + + DBUG_PRINT("info", ("table %s.%s", dbname, tablename)); + + DBUG_RETURN( (uint) (strmov(strmov(key, dbname) + 1, + tablename) -key) + 1); } |