sql_handler.cc 24.6 KB
Newer Older
1
/* Copyright (C) 2000-2004 MySQL AB
2 3
   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
4
   the Free Software Foundation; version 2 of the License.
5 6 7 8 9 10 11 12 13 14 15 16 17

   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 */

serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
18 19
/* TODO:
  HANDLER blabla OPEN [ AS foobar ] [ (column-list) ]
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
20

serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
21 22 23
  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.
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
24

serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
25 26 27 28 29
  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; }
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
30

31
  But !!! do_command calls free_root at the end of every query and frees up
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
32
  all the sql_alloc'ed memory. It's harder to work around...
33
*/
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
34

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
/*
  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[]=
monty@mysql.com's avatar
monty@mysql.com committed
66
{ RNEXT_SAME, RNEXT, RPREV, RNEXT, RPREV, RNEXT, RPREV, RPREV };
67

68
static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags);
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84


/*
  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.
85

86 87 88
  RETURN
    Pointer to the TABLE_LIST struct.
*/
89

90
static char *mysql_ha_hash_get_key(TABLE_LIST *tables, size_t *key_len_p,
91
                                   my_bool first __attribute__((unused)))
92
{
93 94 95
  *key_len_p= strlen(tables->alias) + 1 ; /* include '\0' in comparisons */
  return tables->alias;
}
96 97


98 99
/*
  Free an hash object.
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
100

101 102 103
  SYNOPSIS
    mysql_ha_hash_free()
    tables                      Pointer to the hash object.
104

105 106 107 108 109 110 111 112 113 114
  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));
115 116
}

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
/**
  Close a HANDLER table.

  @param thd Thread identifier.
  @param tables A list of tables with the first entry to close.

  @note Though this function takes a list of tables, only the first list entry
  will be closed.
  @note Broadcasts refresh if it closed the table.
*/

static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables)
{
  TABLE **table_ptr;

  /*
    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 != 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));
  }
}
155 156

/*
157
  Open a HANDLER table.
158 159

  SYNOPSIS
160
    mysql_ha_open()
161
    thd                         Thread identifier.
162 163
    tables                      A list of tables with the first entry to open.
    reopen                      Re-open a previously opened handler table.
164 165 166

  DESCRIPTION
    Though this function takes a list of tables, only the first list entry
167 168 169 170 171 172
    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.
173 174

  RETURN
175 176
    FALSE OK
    TRUE  Error
177 178
*/

bell@sanja.is.com.ua's avatar
merge  
bell@sanja.is.com.ua committed
179
bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
180
{
181
  TABLE_LIST    *hash_tables = NULL;
182 183
  char          *db, *name, *alias;
  uint          dblen, namelen, aliaslen, counter;
monty@mysql.com's avatar
monty@mysql.com committed
184
  int           error;
185
  TABLE         *backup_open_tables;
186
  DBUG_ENTER("mysql_ha_open");
187
  DBUG_PRINT("enter",("'%s'.'%s' as '%s'  reopen: %d",
188
                      tables->db, tables->table_name, tables->alias,
189
                      (int) reopen));
190 191

  if (! hash_inited(&thd->handler_tables_hash))
192
  {
193 194 195
    /*
      HASH entries are of type TABLE_LIST.
    */
monty@mysql.com's avatar
monty@mysql.com committed
196 197
    if (hash_init(&thd->handler_tables_hash, &my_charset_latin1,
                  HANDLER_TABLES_HASH_SIZE, 0, 0,
198 199 200 201 202
                  (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. */
203
  {
204
    if (hash_search(&thd->handler_tables_hash, (uchar*) tables->alias,
205
                    strlen(tables->alias) + 1))
206
    {
207
      DBUG_PRINT("info",("duplicate '%s'", tables->alias));
208
      if (! reopen)
209
        my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias);
210
      goto err;
211
    }
212
  }
213

214 215 216 217 218 219 220 221 222
  /*
    Save and reset the open_tables list so that open_tables() won't
    be able to access (or know about) the previous list. And on return
    from open_tables(), thd->open_tables will contain only the opened
    table.

    The thd->handler_tables list is kept as-is to avoid deadlocks if
    open_table(), called by open_tables(), needs to back-off because
    of a pending name-lock on the table being opened.
223

224 225 226
    See open_table() back-off comments for more details.
  */
  backup_open_tables= thd->open_tables;
227 228
  thd->open_tables= NULL;

229 230 231 232 233
  /*
    open_tables() will set 'tables->table' if successful.
    It must be NULL for a real open when calling open_tables().
  */
  DBUG_ASSERT(! tables->table);
monty@mysql.com's avatar
monty@mysql.com committed
234 235 236

  /* for now HANDLER can be used only for real TABLES */
  tables->required_type= FRMTYPE_TABLE;
237
  error= open_tables(thd, &tables, &counter, 0);
238
  /* restore the state and merge the opened table into handler_tables list */
239 240 241 242 243 244
  if (thd->open_tables)
  {
    thd->open_tables->next= thd->handler_tables;
    thd->handler_tables= thd->open_tables;
  }

245
  thd->open_tables= backup_open_tables;
246

monty@mysql.com's avatar
monty@mysql.com committed
247
  if (error)
248 249 250
    goto err;

  /* There can be only one table in '*tables'. */
251
  if (! (tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
252
  {
253
    if (! reopen)
254
      my_error(ER_ILLEGAL_HA, MYF(0), tables->alias);
255 256 257 258
    goto err;
  }

  if (! reopen)
259
  {
260 261
    /* copy the TABLE_LIST struct */
    dblen= strlen(tables->db) + 1;
262
    namelen= strlen(tables->table_name) + 1;
263 264
    aliaslen= strlen(tables->alias) + 1;
    if (!(my_multi_malloc(MYF(MY_WME),
265 266 267 268
                          &hash_tables, (uint) sizeof(*hash_tables),
                          &db, (uint) dblen,
                          &name, (uint) namelen,
                          &alias, (uint) aliaslen,
269 270 271 272 273
                          NullS)))
      goto err;
    /* structure copy */
    *hash_tables= *tables;
    hash_tables->db= db;
274
    hash_tables->table_name= name;
275 276
    hash_tables->alias= alias;
    memcpy(hash_tables->db, tables->db, dblen);
277
    memcpy(hash_tables->table_name, tables->table_name, namelen);
278 279 280
    memcpy(hash_tables->alias, tables->alias, aliaslen);

    /* add to hash */
281
    if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables))
282
      goto err;
283
  }
284 285

  if (! reopen)
286
    send_ok(thd);
287
  DBUG_PRINT("exit",("OK"));
bell@sanja.is.com.ua's avatar
merge  
bell@sanja.is.com.ua committed
288
  DBUG_RETURN(FALSE);
289 290

err:
291 292 293 294
  if (hash_tables)
    my_free((char*) hash_tables, MYF(0));
  if (tables->table)
    mysql_ha_close_table(thd, tables);
295
  DBUG_PRINT("exit",("ERROR"));
bell@sanja.is.com.ua's avatar
merge  
bell@sanja.is.com.ua committed
296
  DBUG_RETURN(TRUE);
297 298
}

299 300

/*
301
  Close a HANDLER table by alias or table name
302 303

  SYNOPSIS
304
    mysql_ha_close()
305
    thd                         Thread identifier.
306
    tables                      A list of tables with the first entry to close.
307 308

  DESCRIPTION
309 310
    Closes the table that is associated (on the handler tables hash) with the
    name (table->alias) of the specified table.
311 312

  RETURN
313 314
    FALSE ok
    TRUE  error
315 316
*/

317
bool mysql_ha_close(THD *thd, TABLE_LIST *tables)
318
{
319 320
  TABLE_LIST    *hash_tables;
  DBUG_ENTER("mysql_ha_close");
321
  DBUG_PRINT("enter",("'%s'.'%s' as '%s'",
322
                      tables->db, tables->table_name, tables->alias));
323

324
  if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
325
                                              (uchar*) tables->alias,
326
                                              strlen(tables->alias) + 1)))
327
  {
328
    mysql_ha_close_table(thd, hash_tables);
329
    hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
330 331
  }
  else
332
  {
333
    my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER");
334
    DBUG_PRINT("exit",("ERROR"));
335
    DBUG_RETURN(TRUE);
336
  }
337

338
  send_ok(thd);
339
  DBUG_PRINT("exit", ("OK"));
340
  DBUG_RETURN(FALSE);
341 342
}

monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
343

344 345 346 347 348 349 350 351 352 353 354 355
/*
  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
356 357
    select_limit_cnt
    offset_limit_cnt
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
358

359
  RETURN
bell@sanja.is.com.ua's avatar
merge  
bell@sanja.is.com.ua committed
360 361
    FALSE ok
    TRUE  error
362 363
*/
 
364 365 366 367
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,
368
                   ha_rows select_limit_cnt, ha_rows offset_limit_cnt)
369
{
370
  TABLE_LIST    *hash_tables;
371
  TABLE         *table, *backup_open_tables;
372 373 374 375 376
  MYSQL_LOCK    *lock;
  List<Item>	list;
  Protocol	*protocol= thd->protocol;
  char		buff[MAX_FIELD_WIDTH];
  String	buffer(buff, sizeof(buff), system_charset_info);
monty@mysql.com's avatar
monty@mysql.com committed
377
  int           error, keyno= -1;
378
  uint          num_rows;
379
  uchar		*key;
380
  uint		key_len;
381
  bool          need_reopen;
382
  DBUG_ENTER("mysql_ha_read");
383
  DBUG_PRINT("enter",("'%s'.'%s' as '%s'",
384
                      tables->db, tables->table_name, tables->alias));
385

386 387 388
  LINT_INIT(key);
  LINT_INIT(key_len);

389 390 391
  thd->lex->select_lex.context.resolve_in_table_list_only(tables);
  list.push_front(new Item_field(&thd->lex->select_lex.context,
                                 NULL, NULL, "*"));
392 393 394
  List_iterator<Item> it(list);
  it++;

395
retry:
396
  if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
397
                                              (uchar*) tables->alias,
398 399 400
                                              strlen(tables->alias) + 1)))
  {
    table= hash_tables->table;
401
    DBUG_PRINT("info-in-hash",("'%s'.'%s' as '%s' table: 0x%lx",
402
                               hash_tables->db, hash_tables->table_name,
403
                               hash_tables->alias, (long) table));
404 405 406 407 408 409 410
    if (!table)
    {
      /*
        The handler table has been closed. Re-open it.
      */
      if (mysql_ha_open(thd, hash_tables, 1))
      {
411
        DBUG_PRINT("exit",("reopen failed"));
412 413 414 415
        goto err0;
      }

      table= hash_tables->table;
416
      DBUG_PRINT("info",("re-opened '%s'.'%s' as '%s' tab %p",
417
                         hash_tables->db, hash_tables->table_name,
418 419 420 421 422 423
                         hash_tables->alias, table));
    }

#if MYSQL_VERSION_ID < 40100
    if (*tables->db && strcmp(table->table_cache_key, tables->db))
    {
424
      DBUG_PRINT("info",("wrong db"));
425 426 427 428 429 430 431
      table= NULL;
    }
#endif
  }
  else
    table= NULL;

432 433
  if (!table)
  {
434 435 436
#if MYSQL_VERSION_ID < 40100
    char buff[MAX_DBKEY_LENGTH];
    if (*tables->db)
437 438
      strxnmov(buff, sizeof(buff)-1, tables->db, ".", tables->table_name,
               NullS);
439 440
    else
      strncpy(buff, tables->alias, sizeof(buff));
441
    my_error(ER_UNKNOWN_TABLE, MYF(0), buff, "HANDLER");
442
#else
443
    my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER");
444 445
#endif
    goto err0;
446 447
  }
  tables->table=table;
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
448

449
  /* save open_tables state */
450
  backup_open_tables= thd->open_tables;
451 452 453 454 455 456 457
  /*
    mysql_lock_tables() needs thd->open_tables to be set correctly to
    be able to handle aborts properly. When the abort happens, it's
    safe to not protect thd->handler_tables because it won't close any
    tables.
  */
  thd->open_tables= thd->handler_tables;
458 459 460 461 462 463 464 465 466 467 468

  lock= mysql_lock_tables(thd, &tables->table, 1,
                          MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN, &need_reopen);

  /* restore previous context */
  thd->open_tables= backup_open_tables;

  if (need_reopen)
  {
    mysql_ha_close_table(thd, tables);
    hash_tables->table= NULL;
469 470 471 472 473 474
    /*
      The lock might have been aborted, we need to manually reset
      thd->some_tables_deleted because handler's tables are closed
      in a non-standard way. Otherwise we might loop indefinitely.
    */
    thd->some_tables_deleted= 0;
475 476
    goto retry;
  }
477 478 479 480

  if (!lock)
    goto err0; // mysql_lock_tables() printed error message already

481 482 483
  // Always read all columns
  tables->table->read_set= &tables->table->s->all_set;

484
  if (cond)
485 486 487
  {
    if (table->query_id != thd->query_id)
      cond->cleanup();                          // File was reopened
488 489
    if ((!cond->fixed &&
	 cond->fix_fields(thd, &cond)) || cond->check_cols(1))
490
      goto err;
491
  }
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
492

serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
493
  if (keyname)
494
  {
495
    if ((keyno=find_type(keyname, &table->s->keynames, 1+2)-1)<0)
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
496
    {
497
      my_error(ER_KEY_DOES_NOT_EXITS, MYF(0), keyname, tables->alias);
498
      goto err;
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
499
    }
500 501
  }

502 503
  if (insert_fields(thd, &thd->lex->select_lex.context,
                    tables->db, tables->alias, &it, 0))
504
    goto err;
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
505

506
  protocol->send_fields(&list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
507

508 509 510 511 512
  /*
    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.
  */
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
513 514 515

  table->file->init_table_handle_for_HANDLER();

516
  for (num_rows=0; num_rows < select_limit_cnt; )
517
  {
518
    switch (mode) {
519 520 521
    case RNEXT:
      if (table->file->inited != handler::NONE)
      {
acurtis@xiphis.org's avatar
Merge  
acurtis@xiphis.org committed
522
        error=keyname ?
523 524 525 526 527
	  table->file->index_next(table->record[0]) :
	  table->file->rnd_next(table->record[0]);
        break;
      }
      /* else fall through */
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
528
    case RFIRST:
529
      if (keyname)
530 531
      {
        table->file->ha_index_or_rnd_end();
532
        table->file->ha_index_init(keyno, 1);
monty@mysql.com's avatar
monty@mysql.com committed
533
        error= table->file->index_first(table->record[0]);
534
      }
535 536
      else
      {
537
        table->file->ha_index_or_rnd_end();
monty@mysql.com's avatar
monty@mysql.com committed
538 539
	if (!(error= table->file->ha_rnd_init(1)))
          error= table->file->rnd_next(table->record[0]);
540
      }
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
541 542
      mode=RNEXT;
      break;
543 544 545 546
    case RPREV:
      DBUG_ASSERT(keyname != 0);
      if (table->file->inited != handler::NONE)
      {
acurtis@xiphis.org's avatar
Merge  
acurtis@xiphis.org committed
547
        error=table->file->index_prev(table->record[0]);
548 549 550
        break;
      }
      /* else fall through */
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
551 552
    case RLAST:
      DBUG_ASSERT(keyname != 0);
553
      table->file->ha_index_or_rnd_end();
554
      table->file->ha_index_init(keyno, 1);
monty@mysql.com's avatar
monty@mysql.com committed
555
      error= table->file->index_last(table->record[0]);
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
556 557
      mode=RPREV;
      break;
558 559 560
    case RNEXT_SAME:
      /* Continue scan on "(keypart1,keypart2,...)=(c1, c2, ...)  */
      DBUG_ASSERT(keyname != 0);
monty@mysql.com's avatar
monty@mysql.com committed
561
      error= table->file->index_next_same(table->record[0], key, key_len);
562
      break;
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
563
    case RKEY:
564
    {
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
565 566 567 568 569
      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)
      {
570
	my_error(ER_TOO_MANY_KEY_PARTS, MYF(0), keyinfo->key_parts);
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
571 572
	goto err;
      }
573
      List_iterator<Item> it_ke(*key_expr);
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
574
      Item *item;
575
      key_part_map keypart_map;
576
      for (keypart_map= key_len=0 ; (item=it_ke++) ; key_part++)
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
577
      {
578
        my_bitmap_map *old_map;
579
	// 'item' can be changed by fix_fields() call
580
        if ((!item->fixed &&
581
             item->fix_fields(thd, it_ke.ref())) ||
582
	    (item= *it_ke.ref())->check_cols(1))
583 584 585 586 587 588
	  goto err;
	if (item->used_tables() & ~RAND_TABLE_BIT)
        {
          my_error(ER_WRONG_ARGUMENTS,MYF(0),"HANDLER ... READ");
	  goto err;
        }
589
        old_map= dbug_tmp_use_all_columns(table, table->write_set);
monty@mashka.mysql.fi's avatar
monty@mashka.mysql.fi committed
590
	(void) item->save_in_field(key_part->field, 1);
591
        dbug_tmp_restore_column_map(table->write_set, old_map);
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
592
	key_len+=key_part->store_length;
593
        keypart_map= (keypart_map << 1) | 1;
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
594
      }
595

596
      if (!(key= (uchar*) thd->calloc(ALIGN_SIZE(key_len))))
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
597
	goto err;
598
      table->file->ha_index_or_rnd_end();
599
      table->file->ha_index_init(keyno, 1);
600
      key_copy(key, table->record[0], table->key_info + keyno, key_len);
601 602
      error= table->file->index_read_map(table->record[0],
                                         key, keypart_map, ha_rkey_mode);
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
603 604 605 606
      mode=rkey_to_rnext[(int)ha_rkey_mode];
      break;
    }
    default:
bell@sanja.is.com.ua's avatar
bell@sanja.is.com.ua committed
607
      my_message(ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), MYF(0));
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
608
      goto err;
609 610
    }

monty@mysql.com's avatar
monty@mysql.com committed
611
    if (error)
612
    {
monty@mysql.com's avatar
monty@mysql.com committed
613 614
      if (error == HA_ERR_RECORD_DELETED)
        continue;
monty@mysql.com's avatar
monty@mysql.com committed
615
      if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
616
      {
617
        sql_print_error("mysql_ha_read: Got error %d when reading table '%s'",
618
                        error, tables->table_name);
monty@mysql.com's avatar
monty@mysql.com committed
619
        table->file->print_error(error,MYF(0));
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
620 621 622 623
        goto err;
      }
      goto ok;
    }
624 625
    if (cond && !cond->val_int())
      continue;
626
    if (num_rows >= offset_limit_cnt)
627
    {
628 629 630 631
      Item *item;
      protocol->prepare_for_resend();
      it.rewind();
      while ((item=it++))
632
      {
633 634 635
	if (item->send(thd->protocol, &buffer))
	{
	  protocol->free();                             // Free used
bell@sanja.is.com.ua's avatar
bell@sanja.is.com.ua committed
636
	  my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
637 638
	  goto err;
	}
639
      }
640
      protocol->write();
641
    }
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
642
    num_rows++;
643
  }
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
644 645
ok:
  mysql_unlock_tables(thd,lock);
646
  send_eof(thd);
647
  DBUG_PRINT("exit",("OK"));
bell@sanja.is.com.ua's avatar
merge  
bell@sanja.is.com.ua committed
648
  DBUG_RETURN(FALSE);
649

serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
650 651
err:
  mysql_unlock_tables(thd,lock);
serg@serg.mysql.com's avatar
serg@serg.mysql.com committed
652
err0:
653
  DBUG_PRINT("exit",("ERROR"));
bell@sanja.is.com.ua's avatar
merge  
bell@sanja.is.com.ua committed
654
  DBUG_RETURN(TRUE);
655 656
}

657

658
/*
659
  Flush (close) a list of HANDLER tables.
660 661

  SYNOPSIS
662
    mysql_ha_flush()
663
    thd                         Thread identifier.
664 665 666 667 668 669
    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.
670
    is_locked                   If LOCK_open is locked.
671 672

  DESCRIPTION
673 674 675 676
    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.
677
    Broadcasts refresh for every table closed.
678 679 680 681

  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.
682 683

  RETURN
684
    0  ok
685 686
*/

687 688
int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags,
                   bool is_locked)
689
{
690 691
  TABLE_LIST    *tmp_tables;
  TABLE         **table_ptr;
692
  bool          did_lock= FALSE;
693
  DBUG_ENTER("mysql_ha_flush");
694 695
  DBUG_PRINT("enter", ("tables: 0x%lx  mode_flags: 0x%02x",
                       (long) tables, mode_flags));
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
696

697
  if (tables)
698
  {
699
    /* Close all tables in the list. */
monty@mysql.com's avatar
monty@mysql.com committed
700
    for (tmp_tables= tables ; tmp_tables; tmp_tables= tmp_tables->next_local)
701
    {
702
      DBUG_PRINT("info-in-tables-list",("'%s'.'%s' as '%s'",
703
                                        tmp_tables->db, tmp_tables->table_name,
704
                                        tmp_tables->alias));
705 706 707
      /* Close all currently open handler tables with the same base table. */
      table_ptr= &(thd->handler_tables);
      while (*table_ptr)
708
      {
709
        if ((!*tmp_tables->db ||
710
             !my_strcasecmp(&my_charset_latin1, (*table_ptr)->s->db.str,
monty@mysql.com's avatar
monty@mysql.com committed
711
                             tmp_tables->db)) &&
712 713
            ! my_strcasecmp(&my_charset_latin1,
                            (*table_ptr)->s->table_name.str,
714
                            tmp_tables->table_name))
715
        {
716
          DBUG_PRINT("info",("*table_ptr '%s'.'%s' as '%s'",
717 718
                             (*table_ptr)->s->db.str,
                             (*table_ptr)->s->table_name.str,
719
                             (*table_ptr)->alias));
720 721 722 723 724 725
          /* 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;
          }
726 727
          mysql_ha_flush_table(thd, table_ptr, mode_flags);
          continue;
728
        }
729 730 731 732 733 734 735 736 737 738 739 740 741
        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) ||
dlenev@mockturtle.local's avatar
dlenev@mockturtle.local committed
742
          (*table_ptr)->needs_reopen_or_name_lock())
743
      {
744 745 746 747 748 749
        /* 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;
        }
750
        mysql_ha_flush_table(thd, table_ptr, mode_flags);
751 752
        continue;
      }
753 754 755 756
      table_ptr= &(*table_ptr)->next;
    }
  }

757 758 759 760
  /* Release the lock if it was taken by this function. */
  if (did_lock)
    VOID(pthread_mutex_unlock(&LOCK_open));

761 762 763 764 765 766 767 768 769 770 771 772 773 774
  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
775
    Broadcasts refresh if it closed the table.
776 777 778 779 780 781
    The caller must lock LOCK_open.

  RETURN
    0  ok
*/

782
static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags)
783 784 785 786
{
  TABLE_LIST    *hash_tables;
  TABLE         *table= *table_ptr;
  DBUG_ENTER("mysql_ha_flush_table");
787
  DBUG_PRINT("enter",("'%s'.'%s' as '%s'  flags: 0x%02x",
788
                      table->s->db.str, table->s->table_name.str,
789
                      table->alias, mode_flags));
790 791

  if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
792
                                              (uchar*) table->alias,
793
                                              strlen(table->alias) + 1)))
794 795 796 797
  {
    if (! (mode_flags & MYSQL_HA_REOPEN_ON_USAGE))
    {
      /* This is a final close. Remove from hash. */
798
      hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
799
    }
800 801 802 803
    else
    {
      /* Mark table as closed, ready for re-open. */
      hash_tables->table= NULL;
804
    }
805 806
  }    

807
  safe_mutex_assert_owner(&LOCK_open);
monty@mysql.com's avatar
monty@mysql.com committed
808
  (*table_ptr)->file->ha_index_or_rnd_end();
809
  safe_mutex_assert_owner(&LOCK_open);
810 811 812
  if (close_thread_table(thd, table_ptr))
  {
    /* Tell threads waiting for refresh that something has happened */
813
    broadcast_refresh();
814
  }
815 816

  DBUG_RETURN(0);
817
}