mariadb/sql/sql_handler.cc
unknown d46c8ce634 Fix for:
Bug #20662 "Infinite loop in CREATE TABLE IF NOT EXISTS ... SELECT
              with locked tables"
  Bug #20903 "Crash when using CREATE TABLE .. SELECT and triggers"
  Bug #24738 "CREATE TABLE ... SELECT is not isolated properly"
  Bug #24508 "Inconsistent results of CREATE TABLE ... SELECT when
              temporary table exists"

Deadlock occured when one tried to execute CREATE TABLE IF NOT
EXISTS ... SELECT statement under LOCK TABLES which held
read lock on target table.
Attempt to execute the same statement for already existing
target table with triggers caused server crashes.
Also concurrent execution of CREATE TABLE ... SELECT statement
and other statements involving target table suffered from
various races (some of which might've led to deadlocks).
Finally, attempt to execute CREATE TABLE ... SELECT in case
when a temporary table with same name was already present
led to the insertion of data into this temporary table and
creation of empty non-temporary table.
 
All above problems stemmed from the old implementation of CREATE
TABLE ... SELECT in which we created, opened and locked target
table without any special protection in a separate step and not
with the rest of tables used by this statement.
This underminded deadlock-avoidance approach used in server
and created window for races. It also excluded target table
from prelocking causing problems with trigger execution.

The patch solves these problems by implementing new approach to
handling of CREATE TABLE ... SELECT for base tables.
We try to open and lock table to be created at the same time as
the rest of tables used by this statement. If such table does not
exist at this moment we create and place in the table cache special
placeholder for it which prevents its creation or any other usage
by other threads.
We still use old approach for creation of temporary tables.

Note that we have separate fix for 5.0 since there we use slightly
different less intrusive approach.


mysql-test/r/create.result:
  Extended test coverage for CREATE TABLE ... SELECT. In particular added
  tests for bug #24508 "Inconsistent results of CREATE TABLE ... SELECT
  when temporary table exists" and bug #20662 "Infinite loop in CREATE
  TABLE IF NOT EXISTS ... SELECT with locked tables".
mysql-test/r/trigger.result:
  Added test case for bug #20903 "Crash when using CREATE TABLE .. SELECT
  and triggers"
mysql-test/t/create.test:
  Extended test coverage for CREATE TABLE ... SELECT. In particular added
  tests for bug #24508 "Inconsistent results of CREATE TABLE ... SELECT
  when temporary table exists" and bug #20662 "Infinite loop in CREATE
  TABLE IF NOT EXISTS ... SELECT with locked tables".
mysql-test/t/trigger.test:
  Added test case for bug #20903 "Crash when using CREATE TABLE .. SELECT
  and triggers"
sql/lock.cc:
  Now for creation of name-lock placeholder lock_table_name() uses
  auxiliary function table_cache_insert_placeholder().
sql/mysql_priv.h:
  Removed declaration of non-existing build_table_path() routine.
  The former mysql_create_table_internal() was renamed to
  mysql_create_table_no_lock() and now exposed to other modules to
  give them opportunity of creation of tables in cases when name-lock
  is already obtained.
  reopen_name_locked_table() now has 3rd argument which controls linking
  in of table being opened into THD::open_tables (this is useful in
  cases when placeholder used for name-locking is already linked into
  this list).
  Added declaration of auxiliary function table_cache_insert_placeholder()
  which is used for creation of table placeholders for name-locking.
  Added declaration of lock_table_name_if_not_cached() which can be
  used to take an exclusive name-lock on table if there are no records
  for it in table cache.
  Changed signature of unlink_open_table() function to simplify its use
  and make it useful for table placeholders and tables that are only open.
  Added auxiliary drop_open_table() routine.
  Moved declaration of refresh_version to table.h header to make it
  accessible from inline methods of TABLE class.
  MYSQL_OPEN_IGNORE_LOCKED_TABLES flag is no longer used. Instead
  MYSQL_OPEN_TEMPORARY_ONLY option was added.
sql/sql_base.cc:
  Added support for the new approach to the handling of CREATE TABLE
  ... SELECT for base tables.
  
  Now we try to open and lock table to be created at the same time as
  the rest of tables used by this statement. If such table does not
  exist at this moment we create and place in the table cache special
  placeholder for it which prevents its creation or any other usage
  by other threads.
  
  Note significant distinctions of this placeholder from the placeholder
  used for normal name-lock: 1) It is treated like open table by other
  name-locks so it does not allow name-lock taking operations like DROP
  TABLE or RENAME TABLE to proceed. 2) it is linked into THD::open_tables
  list and automatically removed during close_thread_tables() call
    
  open_tables():
    Implemented logic described above. To do this added
    auxiliary check_if_table_exists() function.
    Removed support for MYSQL_OPEN_IGNORE_LOCKED_TABLES option
    which is no longer used.
    Added MYSQL_OPEN_TEMPORARY_ONLY which is used to restrict
    search for temporary tables only.
  close_cached_tables()/close_thread_table()/reopen_tables()/
  close_old_data_files()/table_is_used()/remove_table_from_cache():
    Added support for open placeholders (note that we also use them
    when we need to re-open tables during flush).
  unlink_open_table():
    Changed function signature to simplify its use and to make
    useful for open placeholders and tables which are only
    open and not locked.
  Added auxiliary drop_open_table() routine.
  reopen_name_locked_table():
    Now has 3rd argument which controls linking in of table being
    opened into THD::open_tables (this is useful in cases when
    placeholder used for name-locking is already linked into
    this list).
  Added auxiliary table_cache_insert_placeholder() routine which
  simplifies creation of placeholders used for name-locking.
  Added lock_table_name_if_not_cached() which can be used to take
  an exclusive name-lock on table if there are no records for it
  in table cache.
sql/sql_handler.cc:
  Adjusted mysql_ha_mark_tables_for_reopen() routine to properly
  handle placeholders which now can be linked into open tables
  list.
sql/sql_insert.cc:
  Introduced new approach to handling of base tables in CREATE TABLE
  ... SELECT statement.
  
  Now we try to open and lock table to be created at the same time as
  the rest of tables used by this statement. If such table does not
  exist at this moment we create and place in the table cache special
  placeholder for it which prevents its creation or any other usage
  by other threads. By doing this we avoid races which existed with
  previous approach in which we created, opened and locked target in
  separate step without any special protection.
  This also allows properly calculate prelocking set in cases when
  target table already exists and has some on insert triggers.
  
  Note that we don't employ the same approach for temporary tables
  (this is okay as such tables are unaffected by other threads).
  
  Changed create_table_from_items() and methods of select_create
  class to implement this approach.
sql/sql_parse.cc:
  The new approach to handling of CREATE TABLE ... SELECT for
  base tables assumes that all tables (including table to be
  created) are opened and (or) locked at the same time.
  So in cases when we create base table we have to pass to
  open_and_lock_tables() table list which includes target table.
sql/sql_prepare.cc:
  The new approach to handling of CREATE TABLE ... SELECT for
  base tables assumes that all tables (including table to be
  created) are opened and (or) locked at the same time.
  So in cases when we create base table we have to pass to
  open_and_lock_tables() table list which includes target table.
sql/sql_table.cc:
  Changed mysql_create_table(), mysql_create_like_table() and
  mysql_alter_table() (in rename case) to obtain exclusive name-lock
  on the non-temporary table which is going to be created (to which
  we going to rename). This ensures that not only destination table
  doesn't exist on disk but also that there are no placeholder in 
  table cache for it (i.e. there is no CREATE TABLE ... SELECT operation
  in progress for it). Note that to avoid deadlocks while taking these
  name-locks this code assumes that existence of any record for table in
  table cache (even name-lock) means that table exists. Altough such
  check can lead to false positives these should occur only in case of
  highly concurrent DDL operations on the table and should not break
  binary logging.
  
  Renamed mysql_create_table_internal() to mysql_create_table_no_lock()
  and made it accessible from other files to give them ability to create
  table in situation when name-lock is already obtained or not relevant.
  
  Adjusted calls to reopen_name_locked_table(), which now takes
  extra argument, which controls linking of open table into
  THD::open_tables list.
  
  Removed redundant setting of table's 'version' field before calls
  to close_cached_table(). This function will set it to 0 itself
  anyway.
sql/sql_trigger.cc:
  reopen_name_locked_tables() now has one more argument which controls
  linking of opened table into the THD::open_tables list.
sql/sql_yacc.yy:
  The new approach to handling of CREATE TABLE ... SELECT statement
  for base tables assumes that all tables including table to be
  created are open and (or) locked at the same time. Therefore
  we need to set correct lock for target table.
sql/table.h:
  Moved declaration of refresh_version variable from mysql_priv.h
  to make it accessible from inline methods of TABLE class.
  Renamed TABLE::locked_by_flush member to open_placeholder since
  now it is also used for taking exclusive name-lock and not only
  by flush. 
  Introduced TABLE::is_name_opened() helper method which can be used
  to distinguish TABLE instances corresponding to open tables or
  placeholders for them from closed instances (e.g. due to their old
  version). Also introduced TABLE::needs_reopen_or_name_lock() helper
  which allows to check if TABLE instance corresponds to outdated
  version of table or to name-lock placeholder.
  Introduced TABLE_LIST::create member which marks elements of
  table list corresponds to the table to be created.
  Adjusted TABLE_LIST::placeholder() method to take into account 
  name-lock placeholders for tables to be created (this, for example,
  allows to properly handle such placeholders in lock_tables()).
  Finally, moved currently unused TABLE::open_next/open_prev
  members under ifdef NOT_YET.
mysql-test/r/create_select-big.result:
  New BitKeeper file ``mysql-test/r/create_select-big.result''
mysql-test/t/create_select-big.test:
  New BitKeeper file ``mysql-test/t/create_select-big.test''
2007-05-11 21:51:03 +04:00

805 lines
24 KiB
C++

/* Copyright (C) 2000-2004 MySQL 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; version 2 of the License.
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 */
/* HANDLER ... commands - direct access to ISAM */
/* TODO:
HANDLER blabla OPEN [ AS foobar ] [ (column-list) ]
the most natural (easiest, fastest) way to do it is to
compute List<Item> field_list not in mysql_ha_read
but in mysql_ha_open, and then store it in TABLE structure.
The problem here is that mysql_parse calls free_item to free all the
items allocated at the end of every query. The workaround would to
keep two item lists per THD - normal free_list and handler_items.
The second is to be freeed only on thread end. mysql_ha_open should
then do { handler_items=concat(handler_items, free_list); free_list=0; }
But !!! do_command calls free_root at the end of every query and frees up
all the sql_alloc'ed memory. It's harder to work around...
*/
/*
There are two containers holding information about open handler tables.
The first is 'thd->handler_tables'. It is a linked list of TABLE objects.
It is used like 'thd->open_tables' in the table cache. The trick is to
exchange these two lists during open and lock of tables. Thus the normal
table cache code can be used.
The second container is a HASH. It holds objects of the type TABLE_LIST.
Despite its name, no lists of tables but only single structs are hashed
(the 'next' pointer is always NULL). The reason for theis second container
is, that we want handler tables to survive FLUSH TABLE commands. A table
affected by FLUSH TABLE must be closed so that other threads are not
blocked by handler tables still in use. Since we use the normal table cache
functions with 'thd->handler_tables', the closed tables are removed from
this list. Hence we need the original open information for the handler
table in the case that it is used again. This information is handed over
to mysql_ha_open() as a TABLE_LIST. So we store this information in the
second container, where it is not affected by FLUSH TABLE. The second
container is implemented as a hash for performance reasons. Consequently,
we use it not only for re-opening a handler table, but also for the
HANDLER ... READ commands. For this purpose, we store a pointer to the
TABLE structure (in the first container) in the TBALE_LIST object in the
second container. When the table is flushed, the pointer is cleared.
*/
#include "mysql_priv.h"
#include "sql_select.h"
#include <assert.h>
#define HANDLER_TABLES_HASH_SIZE 120
static enum enum_ha_read_modes rkey_to_rnext[]=
{ RNEXT_SAME, RNEXT, RPREV, RNEXT, RPREV, RNEXT, RPREV, RPREV };
#define HANDLER_TABLES_HACK(thd) { \
TABLE *tmp=thd->open_tables; \
thd->open_tables=thd->handler_tables; \
thd->handler_tables=tmp; }
static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags);
/*
Get hash key and hash key length.
SYNOPSIS
mysql_ha_hash_get_key()
tables Pointer to the hash object.
key_len_p (out) Pointer to the result for key length.
first Unused.
DESCRIPTION
The hash object is an TABLE_LIST struct.
The hash key is the alias name.
The hash key length is the alias name length plus one for the
terminateing NUL character.
RETURN
Pointer to the TABLE_LIST struct.
*/
static char *mysql_ha_hash_get_key(TABLE_LIST *tables, uint *key_len_p,
my_bool first __attribute__((unused)))
{
*key_len_p= strlen(tables->alias) + 1 ; /* include '\0' in comparisons */
return tables->alias;
}
/*
Free an hash object.
SYNOPSIS
mysql_ha_hash_free()
tables Pointer to the hash object.
DESCRIPTION
The hash object is an TABLE_LIST struct.
RETURN
Nothing
*/
static void mysql_ha_hash_free(TABLE_LIST *tables)
{
my_free((char*) tables, MYF(0));
}
/*
Open a HANDLER table.
SYNOPSIS
mysql_ha_open()
thd Thread identifier.
tables A list of tables with the first entry to open.
reopen Re-open a previously opened handler table.
DESCRIPTION
Though this function takes a list of tables, only the first list entry
will be opened.
'reopen' is set when a handler table is to be re-opened. In this case,
'tables' is the pointer to the hashed TABLE_LIST object which has been
saved on the original open.
'reopen' is also used to suppress the sending of an 'ok' message or
error messages.
RETURN
FALSE OK
TRUE Error
*/
bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
{
TABLE_LIST *hash_tables;
char *db, *name, *alias;
uint dblen, namelen, aliaslen, counter;
int error;
DBUG_ENTER("mysql_ha_open");
DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d",
tables->db, tables->table_name, tables->alias,
(int) reopen));
if (! hash_inited(&thd->handler_tables_hash))
{
/*
HASH entries are of type TABLE_LIST.
*/
if (hash_init(&thd->handler_tables_hash, &my_charset_latin1,
HANDLER_TABLES_HASH_SIZE, 0, 0,
(hash_get_key) mysql_ha_hash_get_key,
(hash_free_key) mysql_ha_hash_free, 0))
goto err;
}
else if (! reopen) /* Otherwise we have 'tables' already. */
{
if (hash_search(&thd->handler_tables_hash, (byte*) tables->alias,
strlen(tables->alias) + 1))
{
DBUG_PRINT("info",("duplicate '%s'", tables->alias));
if (! reopen)
my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias);
goto err;
}
}
/*
open_tables() will set 'tables->table' if successful.
It must be NULL for a real open when calling open_tables().
*/
DBUG_ASSERT(! tables->table);
HANDLER_TABLES_HACK(thd);
/* for now HANDLER can be used only for real TABLES */
tables->required_type= FRMTYPE_TABLE;
error= open_tables(thd, &tables, &counter, 0);
HANDLER_TABLES_HACK(thd);
if (error)
goto err;
/* There can be only one table in '*tables'. */
if (! (tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
{
if (! reopen)
my_error(ER_ILLEGAL_HA, MYF(0), tables->alias);
mysql_ha_close(thd, tables);
goto err;
}
if (! reopen)
{
/* copy the TABLE_LIST struct */
dblen= strlen(tables->db) + 1;
namelen= strlen(tables->table_name) + 1;
aliaslen= strlen(tables->alias) + 1;
if (!(my_multi_malloc(MYF(MY_WME),
&hash_tables, sizeof(*hash_tables),
&db, dblen,
&name, namelen,
&alias, aliaslen,
NullS)))
goto err;
/* structure copy */
*hash_tables= *tables;
hash_tables->db= db;
hash_tables->table_name= name;
hash_tables->alias= alias;
memcpy(hash_tables->db, tables->db, dblen);
memcpy(hash_tables->table_name, tables->table_name, namelen);
memcpy(hash_tables->alias, tables->alias, aliaslen);
/* add to hash */
if (my_hash_insert(&thd->handler_tables_hash, (byte*) hash_tables))
{
my_free((char*) hash_tables, MYF(0));
mysql_ha_close(thd, tables);
goto err;
}
}
if (! reopen)
send_ok(thd);
DBUG_PRINT("exit",("OK"));
DBUG_RETURN(FALSE);
err:
DBUG_PRINT("exit",("ERROR"));
DBUG_RETURN(TRUE);
}
/*
Close a HANDLER table.
SYNOPSIS
mysql_ha_close()
thd Thread identifier.
tables A list of tables with the first entry to close.
DESCRIPTION
Though this function takes a list of tables, only the first list entry
will be closed.
Broadcasts refresh if it closed the table.
RETURN
FALSE ok
TRUE error
*/
bool mysql_ha_close(THD *thd, TABLE_LIST *tables)
{
TABLE_LIST *hash_tables;
TABLE **table_ptr;
DBUG_ENTER("mysql_ha_close");
DBUG_PRINT("enter",("'%s'.'%s' as '%s'",
tables->db, tables->table_name, tables->alias));
if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
(byte*) tables->alias,
strlen(tables->alias) + 1)))
{
/*
Though we could take the table pointer from hash_tables->table,
we must follow the thd->handler_tables chain anyway, as we need the
address of the 'next' pointer referencing this table
for close_thread_table().
*/
for (table_ptr= &(thd->handler_tables);
*table_ptr && (*table_ptr != hash_tables->table);
table_ptr= &(*table_ptr)->next)
;
if (*table_ptr)
{
(*table_ptr)->file->ha_index_or_rnd_end();
VOID(pthread_mutex_lock(&LOCK_open));
if (close_thread_table(thd, table_ptr))
{
/* Tell threads waiting for refresh that something has happened */
broadcast_refresh();
}
VOID(pthread_mutex_unlock(&LOCK_open));
}
hash_delete(&thd->handler_tables_hash, (byte*) hash_tables);
}
else
{
my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER");
DBUG_PRINT("exit",("ERROR"));
DBUG_RETURN(TRUE);
}
send_ok(thd);
DBUG_PRINT("exit", ("OK"));
DBUG_RETURN(FALSE);
}
/*
Read from a HANDLER table.
SYNOPSIS
mysql_ha_read()
thd Thread identifier.
tables A list of tables with the first entry to read.
mode
keyname
key_expr
ha_rkey_mode
cond
select_limit_cnt
offset_limit_cnt
RETURN
FALSE ok
TRUE error
*/
bool mysql_ha_read(THD *thd, TABLE_LIST *tables,
enum enum_ha_read_modes mode, char *keyname,
List<Item> *key_expr,
enum ha_rkey_function ha_rkey_mode, Item *cond,
ha_rows select_limit_cnt, ha_rows offset_limit_cnt)
{
TABLE_LIST *hash_tables;
TABLE *table;
MYSQL_LOCK *lock;
List<Item> list;
Protocol *protocol= thd->protocol;
char buff[MAX_FIELD_WIDTH];
String buffer(buff, sizeof(buff), system_charset_info);
int error, keyno= -1;
uint num_rows;
byte *key;
uint key_len;
bool not_used;
DBUG_ENTER("mysql_ha_read");
DBUG_PRINT("enter",("'%s'.'%s' as '%s'",
tables->db, tables->table_name, tables->alias));
LINT_INIT(key);
LINT_INIT(key_len);
thd->lex->select_lex.context.resolve_in_table_list_only(tables);
list.push_front(new Item_field(&thd->lex->select_lex.context,
NULL, NULL, "*"));
List_iterator<Item> it(list);
it++;
if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
(byte*) tables->alias,
strlen(tables->alias) + 1)))
{
table= hash_tables->table;
DBUG_PRINT("info-in-hash",("'%s'.'%s' as '%s' table: 0x%lx",
hash_tables->db, hash_tables->table_name,
hash_tables->alias, (long) table));
if (!table)
{
/*
The handler table has been closed. Re-open it.
*/
if (mysql_ha_open(thd, hash_tables, 1))
{
DBUG_PRINT("exit",("reopen failed"));
goto err0;
}
table= hash_tables->table;
DBUG_PRINT("info",("re-opened '%s'.'%s' as '%s' tab %p",
hash_tables->db, hash_tables->table_name,
hash_tables->alias, table));
}
#if MYSQL_VERSION_ID < 40100
if (*tables->db && strcmp(table->table_cache_key, tables->db))
{
DBUG_PRINT("info",("wrong db"));
table= NULL;
}
#endif
}
else
table= NULL;
if (!table)
{
#if MYSQL_VERSION_ID < 40100
char buff[MAX_DBKEY_LENGTH];
if (*tables->db)
strxnmov(buff, sizeof(buff)-1, tables->db, ".", tables->table_name,
NullS);
else
strncpy(buff, tables->alias, sizeof(buff));
my_error(ER_UNKNOWN_TABLE, MYF(0), buff, "HANDLER");
#else
my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER");
#endif
goto err0;
}
tables->table=table;
HANDLER_TABLES_HACK(thd);
lock= mysql_lock_tables(thd, &tables->table, 1, 0, &not_used);
HANDLER_TABLES_HACK(thd);
if (!lock)
goto err0; // mysql_lock_tables() printed error message already
// Always read all columns
tables->table->read_set= &tables->table->s->all_set;
if (cond)
{
if (table->query_id != thd->query_id)
cond->cleanup(); // File was reopened
if ((!cond->fixed &&
cond->fix_fields(thd, &cond)) || cond->check_cols(1))
goto err0;
}
if (keyname)
{
if ((keyno=find_type(keyname, &table->s->keynames, 1+2)-1)<0)
{
my_error(ER_KEY_DOES_NOT_EXITS, MYF(0), keyname, tables->alias);
goto err0;
}
}
if (insert_fields(thd, &thd->lex->select_lex.context,
tables->db, tables->alias, &it, 0))
goto err0;
protocol->send_fields(&list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
/*
In ::external_lock InnoDB resets the fields which tell it that
the handle is used in the HANDLER interface. Tell it again that
we are using it for HANDLER.
*/
table->file->init_table_handle_for_HANDLER();
for (num_rows=0; num_rows < select_limit_cnt; )
{
switch (mode) {
case RNEXT:
if (table->file->inited != handler::NONE)
{
error=keyname ?
table->file->index_next(table->record[0]) :
table->file->rnd_next(table->record[0]);
break;
}
/* else fall through */
case RFIRST:
if (keyname)
{
table->file->ha_index_or_rnd_end();
table->file->ha_index_init(keyno, 1);
error= table->file->index_first(table->record[0]);
}
else
{
table->file->ha_index_or_rnd_end();
if (!(error= table->file->ha_rnd_init(1)))
error= table->file->rnd_next(table->record[0]);
}
mode=RNEXT;
break;
case RPREV:
DBUG_ASSERT(keyname != 0);
if (table->file->inited != handler::NONE)
{
error=table->file->index_prev(table->record[0]);
break;
}
/* else fall through */
case RLAST:
DBUG_ASSERT(keyname != 0);
table->file->ha_index_or_rnd_end();
table->file->ha_index_init(keyno, 1);
error= table->file->index_last(table->record[0]);
mode=RPREV;
break;
case RNEXT_SAME:
/* Continue scan on "(keypart1,keypart2,...)=(c1, c2, ...) */
DBUG_ASSERT(keyname != 0);
error= table->file->index_next_same(table->record[0], key, key_len);
break;
case RKEY:
{
DBUG_ASSERT(keyname != 0);
KEY *keyinfo=table->key_info+keyno;
KEY_PART_INFO *key_part=keyinfo->key_part;
if (key_expr->elements > keyinfo->key_parts)
{
my_error(ER_TOO_MANY_KEY_PARTS, MYF(0), keyinfo->key_parts);
goto err;
}
List_iterator<Item> it_ke(*key_expr);
Item *item;
key_part_map keypart_map;
for (keypart_map= key_len=0 ; (item=it_ke++) ; key_part++)
{
my_bitmap_map *old_map;
// 'item' can be changed by fix_fields() call
if ((!item->fixed &&
item->fix_fields(thd, it_ke.ref())) ||
(item= *it_ke.ref())->check_cols(1))
goto err;
if (item->used_tables() & ~RAND_TABLE_BIT)
{
my_error(ER_WRONG_ARGUMENTS,MYF(0),"HANDLER ... READ");
goto err;
}
old_map= dbug_tmp_use_all_columns(table, table->write_set);
(void) item->save_in_field(key_part->field, 1);
dbug_tmp_restore_column_map(table->write_set, old_map);
key_len+=key_part->store_length;
keypart_map= (keypart_map << 1) | 1;
}
if (!(key= (byte*) thd->calloc(ALIGN_SIZE(key_len))))
goto err;
table->file->ha_index_or_rnd_end();
table->file->ha_index_init(keyno, 1);
key_copy(key, table->record[0], table->key_info + keyno, key_len);
error= table->file->index_read(table->record[0],
key, keypart_map, ha_rkey_mode);
mode=rkey_to_rnext[(int)ha_rkey_mode];
break;
}
default:
my_message(ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), MYF(0));
goto err;
}
if (error)
{
if (error == HA_ERR_RECORD_DELETED)
continue;
if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
{
sql_print_error("mysql_ha_read: Got error %d when reading table '%s'",
error, tables->table_name);
table->file->print_error(error,MYF(0));
goto err;
}
goto ok;
}
if (cond && !cond->val_int())
continue;
if (num_rows >= offset_limit_cnt)
{
Item *item;
protocol->prepare_for_resend();
it.rewind();
while ((item=it++))
{
if (item->send(thd->protocol, &buffer))
{
protocol->free(); // Free used
my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
goto err;
}
}
protocol->write();
}
num_rows++;
}
ok:
mysql_unlock_tables(thd,lock);
send_eof(thd);
DBUG_PRINT("exit",("OK"));
DBUG_RETURN(FALSE);
err:
mysql_unlock_tables(thd,lock);
err0:
DBUG_PRINT("exit",("ERROR"));
DBUG_RETURN(TRUE);
}
/*
Flush (close) a list of HANDLER tables.
SYNOPSIS
mysql_ha_flush()
thd Thread identifier.
tables The list of tables to close. If NULL,
close all HANDLER tables [marked as flushed].
mode_flags MYSQL_HA_CLOSE_FINAL finally close the table.
MYSQL_HA_REOPEN_ON_USAGE mark for reopen.
MYSQL_HA_FLUSH_ALL flush all tables, not only
those marked for flush.
is_locked If LOCK_open is locked.
DESCRIPTION
The list of HANDLER tables may be NULL, in which case all HANDLER
tables are closed (if MYSQL_HA_FLUSH_ALL) is set.
If 'tables' is NULL and MYSQL_HA_FLUSH_ALL is not set,
all HANDLER tables marked for flush are closed.
Broadcasts refresh for every table closed.
NOTE
Since mysql_ha_flush() is called when the base table has to be closed,
we compare real table names, not aliases. Hence, database names matter.
RETURN
0 ok
*/
int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags,
bool is_locked)
{
TABLE_LIST *tmp_tables;
TABLE **table_ptr;
bool did_lock= FALSE;
DBUG_ENTER("mysql_ha_flush");
DBUG_PRINT("enter", ("tables: 0x%lx mode_flags: 0x%02x",
(long) tables, mode_flags));
if (tables)
{
/* Close all tables in the list. */
for (tmp_tables= tables ; tmp_tables; tmp_tables= tmp_tables->next_local)
{
DBUG_PRINT("info-in-tables-list",("'%s'.'%s' as '%s'",
tmp_tables->db, tmp_tables->table_name,
tmp_tables->alias));
/* Close all currently open handler tables with the same base table. */
table_ptr= &(thd->handler_tables);
while (*table_ptr)
{
if ((!*tmp_tables->db ||
!my_strcasecmp(&my_charset_latin1, (*table_ptr)->s->db.str,
tmp_tables->db)) &&
! my_strcasecmp(&my_charset_latin1,
(*table_ptr)->s->table_name.str,
tmp_tables->table_name))
{
DBUG_PRINT("info",("*table_ptr '%s'.'%s' as '%s'",
(*table_ptr)->s->db.str,
(*table_ptr)->s->table_name.str,
(*table_ptr)->alias));
/* The first time it is required, lock for close_thread_table(). */
if (! did_lock && ! is_locked)
{
VOID(pthread_mutex_lock(&LOCK_open));
did_lock= TRUE;
}
mysql_ha_flush_table(thd, table_ptr, mode_flags);
continue;
}
table_ptr= &(*table_ptr)->next;
}
/* end of handler_tables list */
}
/* end of flush tables list */
}
else
{
/* Close all currently open tables [which are marked for flush]. */
table_ptr= &(thd->handler_tables);
while (*table_ptr)
{
if ((mode_flags & MYSQL_HA_FLUSH_ALL) ||
(*table_ptr)->needs_reopen_or_name_lock())
{
/* The first time it is required, lock for close_thread_table(). */
if (! did_lock && ! is_locked)
{
VOID(pthread_mutex_lock(&LOCK_open));
did_lock= TRUE;
}
mysql_ha_flush_table(thd, table_ptr, mode_flags);
continue;
}
table_ptr= &(*table_ptr)->next;
}
}
/* Release the lock if it was taken by this function. */
if (did_lock)
VOID(pthread_mutex_unlock(&LOCK_open));
DBUG_RETURN(0);
}
/*
Flush (close) a table.
SYNOPSIS
mysql_ha_flush_table()
thd Thread identifier.
table The table to close.
mode_flags MYSQL_HA_CLOSE_FINAL finally close the table.
MYSQL_HA_REOPEN_ON_USAGE mark for reopen.
DESCRIPTION
Broadcasts refresh if it closed the table.
The caller must lock LOCK_open.
RETURN
0 ok
*/
static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags)
{
TABLE_LIST *hash_tables;
TABLE *table= *table_ptr;
DBUG_ENTER("mysql_ha_flush_table");
DBUG_PRINT("enter",("'%s'.'%s' as '%s' flags: 0x%02x",
table->s->db.str, table->s->table_name.str,
table->alias, mode_flags));
if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
(byte*) table->alias,
strlen(table->alias) + 1)))
{
if (! (mode_flags & MYSQL_HA_REOPEN_ON_USAGE))
{
/* This is a final close. Remove from hash. */
hash_delete(&thd->handler_tables_hash, (byte*) hash_tables);
}
else
{
/* Mark table as closed, ready for re-open. */
hash_tables->table= NULL;
}
}
safe_mutex_assert_owner(&LOCK_open);
(*table_ptr)->file->ha_index_or_rnd_end();
safe_mutex_assert_owner(&LOCK_open);
if (close_thread_table(thd, table_ptr))
{
/* Tell threads waiting for refresh that something has happened */
broadcast_refresh();
}
DBUG_RETURN(0);
}
/*
Mark tables for reopen.
SYNOPSIS
mysql_ha_mark_tables_for_reopen()
thd Thread identifier.
table Table list to mark for reopen.
DESCRIPTION
For each table found in the handler hash mark it as closed
(ready for reopen) and end all index/table scans.
NOTE
The caller must lock LOCK_open.
*/
void mysql_ha_mark_tables_for_reopen(THD *thd, TABLE *table)
{
DBUG_ENTER("mysql_ha_mark_tables_for_reopen");
safe_mutex_assert_owner(&LOCK_open);
for (; table; table= table->next)
{
/*
Some elements in open table list, for example placeholders used for
name-locking, can have alias set to 0.
*/
if (table->alias)
{
TABLE_LIST *hash_tables;
if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
(byte*) table->alias,
strlen(table->alias) + 1)))
{
/* Mark table as ready for reopen. */
hash_tables->table= NULL;
/* End open index/table scans. */
table->file->ha_index_or_rnd_end();
}
}
}
DBUG_VOID_RETURN;
}