handler.cc 116 KB
Newer Older
1
/* Copyright (C) 2000-2006 MySQL AB
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
2

bk@work.mysql.com's avatar
bk@work.mysql.com committed
3 4
   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
5
   the Free Software Foundation; version 2 of the License.
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
6

bk@work.mysql.com's avatar
bk@work.mysql.com committed
7 8 9 10
   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.
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
11

bk@work.mysql.com's avatar
bk@work.mysql.com committed
12 13 14 15
   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 */

16
/** @file handler.cc
bk@work.mysql.com's avatar
bk@work.mysql.com committed
17

18 19 20
    @brief
  Handler-calling-functions
*/
bk@work.mysql.com's avatar
bk@work.mysql.com committed
21

22
#ifdef USE_PRAGMA_IMPLEMENTATION
bk@work.mysql.com's avatar
bk@work.mysql.com committed
23 24 25 26
#pragma implementation				// gcc: Class implementation
#endif

#include "mysql_priv.h"
27
#include "rpl_filter.h"
28 29
#include <myisampack.h>
#include <errno.h>
30

31 32
#ifdef WITH_PARTITION_STORAGE_ENGINE
#include "ha_partition.h"
33
#endif
34

35 36 37 38 39
/*
  While we have legacy_db_type, we have this array to
  check for dups and to find handlerton from legacy_db_type.
  Remove when legacy_db_type is finally gone
*/
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
40
st_plugin_int *hton2plugin[MAX_HA];
41

42
static handlerton *installed_htons[128];
43

lars@mysql.com's avatar
lars@mysql.com committed
44 45
#define BITMAP_STACKBUF_SIZE (128/8)

46
KEY_CREATE_INFO default_key_create_info= { HA_KEY_ALG_UNDEF, 0, {NullS,0} };
47

48
/* number of entries in handlertons[] */
49
ulong total_ha= 0;
50
/* number of storage engines (from handlertons[]) that support 2pc */
51
ulong total_ha_2pc= 0;
52
/* size of savepoint storage area (see ha_init) */
53
ulong savepoint_alloc_size= 0;
54

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
55
static const LEX_STRING sys_table_aliases[]=
56
{
andrey@example.com's avatar
andrey@example.com committed
57 58 59
  { C_STRING_WITH_LEN("INNOBASE") },  { C_STRING_WITH_LEN("INNODB") },
  { C_STRING_WITH_LEN("NDB") },       { C_STRING_WITH_LEN("NDBCLUSTER") },
  { C_STRING_WITH_LEN("HEAP") },      { C_STRING_WITH_LEN("MEMORY") },
60
  { C_STRING_WITH_LEN("MERGE") },     { C_STRING_WITH_LEN("MRG_MYISAM") },
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
61
  {NullS, 0}
62
};
63

bk@work.mysql.com's avatar
bk@work.mysql.com committed
64
const char *ha_row_type[] = {
65
  "", "FIXED", "DYNAMIC", "COMPRESSED", "REDUNDANT", "COMPACT", "PAGE", "?","?","?"
bk@work.mysql.com's avatar
bk@work.mysql.com committed
66 67
};

monty@tik.mysql.fi's avatar
monty@tik.mysql.fi committed
68
const char *tx_isolation_names[] =
69 70 71
{ "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ", "SERIALIZABLE",
  NullS};
TYPELIB tx_isolation_typelib= {array_elements(tx_isolation_names)-1,"",
72
			       tx_isolation_names, NULL};
bk@work.mysql.com's avatar
bk@work.mysql.com committed
73

74
static TYPELIB known_extensions= {0,"known_exts", NULL, NULL};
75
uint known_extensions_id= 0;
76

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
77

antony@ppcg5.local's avatar
antony@ppcg5.local committed
78 79 80 81 82 83 84 85 86

static plugin_ref ha_default_plugin(THD *thd)
{
  if (thd->variables.table_plugin)
    return thd->variables.table_plugin;
  return my_plugin_lock(thd, &global_system_variables.table_plugin);
}


87
/** @brief
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
88
  Return the default storage engine handlerton for thread
89

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
90 91 92
  SYNOPSIS
    ha_default_handlerton(thd)
    thd         current thread
93

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
94 95 96 97
  RETURN
    pointer to handlerton
*/
handlerton *ha_default_handlerton(THD *thd)
98
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
99 100 101 102 103
  plugin_ref plugin= ha_default_plugin(thd);
  DBUG_ASSERT(plugin);
  handlerton *hton= plugin_data(plugin, handlerton*);
  DBUG_ASSERT(hton);
  return hton;
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
104 105 106
}


107
/** @brief
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
108 109 110 111 112 113 114 115
  Return the storage engine handlerton for the supplied name
  
  SYNOPSIS
    ha_resolve_by_name(thd, name)
    thd         current thread
    name        name of storage engine
  
  RETURN
antony@ppcg5.local's avatar
antony@ppcg5.local committed
116
    pointer to storage engine plugin handle
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
117
*/
antony@ppcg5.local's avatar
antony@ppcg5.local committed
118
plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name)
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
119 120
{
  const LEX_STRING *table_alias;
antony@ppcg5.local's avatar
antony@ppcg5.local committed
121
  plugin_ref plugin;
122

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
123 124 125
redo:
  /* my_strnncoll is a macro and gcc doesn't do early expansion of macro */
  if (thd && !my_charset_latin1.coll->strnncoll(&my_charset_latin1,
126
                           (const uchar *)name->str, name->length,
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
127
                           (const uchar *)STRING_WITH_LEN("DEFAULT"), 0))
antony@ppcg5.local's avatar
antony@ppcg5.local committed
128
    return ha_default_plugin(thd);
129

antony@ppcg5.local's avatar
antony@ppcg5.local committed
130
  if ((plugin= my_plugin_lock_by_name(thd, name, MYSQL_STORAGE_ENGINE_PLUGIN)))
131
  {
antony@ppcg5.local's avatar
antony@ppcg5.local committed
132
    handlerton *hton= plugin_data(plugin, handlerton *);
133
    if (!(hton->flags & HTON_NOT_USER_SELECTABLE))
antony@ppcg5.local's avatar
antony@ppcg5.local committed
134 135 136 137 138 139
      return plugin;
      
    /*
      unlocking plugin immediately after locking is relatively low cost.
    */
    plugin_unlock(thd, plugin);
140
  }
141

142
  /*
143
    We check for the historical aliases.
144
  */
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
145
  for (table_alias= sys_table_aliases; table_alias->str; table_alias+= 2)
146
  {
147
    if (!my_strnncoll(&my_charset_latin1,
148
                      (const uchar *)name->str, name->length,
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
149 150 151 152 153
                      (const uchar *)table_alias->str, table_alias->length))
    {
      name= table_alias + 1;
      goto redo;
    }
154
  }
155

156
  return NULL;
157
}
monty@mysql.com's avatar
monty@mysql.com committed
158 159


antony@ppcg5.local's avatar
antony@ppcg5.local committed
160
plugin_ref ha_lock_engine(THD *thd, handlerton *hton)
161
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
162 163 164 165
  if (hton)
  {
    st_plugin_int **plugin= hton2plugin + hton->slot;
    
antony@ppcg5.local's avatar
antony@ppcg5.local committed
166
#ifdef DBUG_OFF
antony@ppcg5.local's avatar
antony@ppcg5.local committed
167 168 169 170
    return my_plugin_lock(thd, plugin);
#else
    return my_plugin_lock(thd, &plugin);
#endif
171
  }
antony@ppcg5.local's avatar
antony@ppcg5.local committed
172
  return NULL;
173 174
}

bk@work.mysql.com's avatar
bk@work.mysql.com committed
175

176
#ifdef NOT_USED
177
static handler *create_default(TABLE_SHARE *table, MEM_ROOT *mem_root)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
178
{
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
179
  handlerton *hton= ha_default_handlerton(current_thd);
180
  return (hton && hton->create) ? hton->create(hton, table, mem_root) : NULL;
181
}
182
#endif
183 184 185 186


handlerton *ha_resolve_by_legacy_type(THD *thd, enum legacy_db_type db_type)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
187
  plugin_ref plugin;
188
  switch (db_type) {
189
  case DB_TYPE_DEFAULT:
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
190
    return ha_default_handlerton(thd);
191
  default:
antony@ppcg5.local's avatar
antony@ppcg5.local committed
192 193 194 195 196
    if (db_type > DB_TYPE_UNKNOWN && db_type < DB_TYPE_DEFAULT &&
        (plugin= ha_lock_engine(thd, installed_htons[db_type])))
      return plugin_data(plugin, handlerton*);
    /* fall through */
  case DB_TYPE_UNKNOWN:
197
    return NULL;
198
  }
199 200 201
}


202 203
/**
  Use other database handler if databasehandler is not compiled in.
204
*/
205
handlerton *ha_checktype(THD *thd, enum legacy_db_type database_type,
206
                          bool no_substitute, bool report_error)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
207
{
208 209 210 211
  handlerton *hton= ha_resolve_by_legacy_type(thd, database_type);
  if (ha_storage_engine_is_enabled(hton))
    return hton;

212 213 214 215
  if (no_substitute)
  {
    if (report_error)
    {
antony@ppcg5.local's avatar
antony@ppcg5.local committed
216
      const char *engine_name= ha_resolve_storage_engine_name(hton);
217 218
      my_error(ER_FEATURE_DISABLED,MYF(0),engine_name,engine_name);
    }
219
    return NULL;
220 221
  }

bk@work.mysql.com's avatar
bk@work.mysql.com committed
222 223 224
  switch (database_type) {
#ifndef NO_HASH
  case DB_TYPE_HASH:
225
    return ha_resolve_by_legacy_type(thd, DB_TYPE_HASH);
226
#endif
227
  case DB_TYPE_MRG_ISAM:
228
    return ha_resolve_by_legacy_type(thd, DB_TYPE_MRG_MYISAM);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
229 230 231
  default:
    break;
  }
232

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
233
  return ha_default_handlerton(thd);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
234 235 236
} /* ha_checktype */


237
handler *get_new_handler(TABLE_SHARE *share, MEM_ROOT *alloc,
238
                         handlerton *db_type)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
239
{
240 241 242
  handler *file;
  DBUG_ENTER("get_new_handler");
  DBUG_PRINT("enter", ("alloc: 0x%lx", (long) alloc));
243

244
  if (db_type && db_type->state == SHOW_OPTION_YES && db_type->create)
245
  {
246
    if ((file= db_type->create(db_type, share, alloc)))
247 248
      file->init();
    DBUG_RETURN(file);
249
  }
250 251 252 253 254
  /*
    Try the default table type
    Here the call to current_thd() is ok as we call this function a lot of
    times but we enter this branch very seldom.
  */
antony@ppcg5.local's avatar
antony@ppcg5.local committed
255
  DBUG_RETURN(get_new_handler(share, alloc, ha_default_handlerton(current_thd)));
bk@work.mysql.com's avatar
bk@work.mysql.com committed
256 257
}

258

259
#ifdef WITH_PARTITION_STORAGE_ENGINE
260 261 262 263
handler *get_ha_partition(partition_info *part_info)
{
  ha_partition *partition;
  DBUG_ENTER("get_ha_partition");
264
  if ((partition= new ha_partition(partition_hton, part_info)))
265
  {
266
    if (partition->initialise_partition(current_thd->mem_root))
267 268 269 270
    {
      delete partition;
      partition= 0;
    }
271 272
    else
      partition->init();
273 274 275 276 277 278 279 280 281 282
  }
  else
  {
    my_error(ER_OUTOFMEMORY, MYF(0), sizeof(ha_partition));
  }
  DBUG_RETURN(((handler*) partition));
}
#endif


283
/**
284 285
  Register handler error messages for use with my_error().

286
  @retval
287
    0           OK
288 289
  @retval
    !=0         Error
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
*/
static int ha_init_errors(void)
{
#define SETMSG(nr, msg) errmsgs[(nr) - HA_ERR_FIRST]= (msg)
  const char    **errmsgs;

  /* Allocate a pointer array for the error message strings. */
  /* Zerofill it to avoid uninitialized gaps. */
  if (! (errmsgs= (const char**) my_malloc(HA_ERR_ERRORS * sizeof(char*),
                                           MYF(MY_WME | MY_ZEROFILL))))
    return 1;

  /* Set the dedicated error messages. */
  SETMSG(HA_ERR_KEY_NOT_FOUND,          ER(ER_KEY_NOT_FOUND));
  SETMSG(HA_ERR_FOUND_DUPP_KEY,         ER(ER_DUP_KEY));
  SETMSG(HA_ERR_RECORD_CHANGED,         "Update wich is recoverable");
  SETMSG(HA_ERR_WRONG_INDEX,            "Wrong index given to function");
  SETMSG(HA_ERR_CRASHED,                ER(ER_NOT_KEYFILE));
  SETMSG(HA_ERR_WRONG_IN_RECORD,        ER(ER_CRASHED_ON_USAGE));
  SETMSG(HA_ERR_OUT_OF_MEM,             "Table handler out of memory");
  SETMSG(HA_ERR_NOT_A_TABLE,            "Incorrect file format '%.64s'");
  SETMSG(HA_ERR_WRONG_COMMAND,          "Command not supported");
  SETMSG(HA_ERR_OLD_FILE,               ER(ER_OLD_KEYFILE));
  SETMSG(HA_ERR_NO_ACTIVE_RECORD,       "No record read in update");
  SETMSG(HA_ERR_RECORD_DELETED,         "Intern record deleted");
  SETMSG(HA_ERR_RECORD_FILE_FULL,       ER(ER_RECORD_FILE_FULL));
  SETMSG(HA_ERR_INDEX_FILE_FULL,        "No more room in index file '%.64s'");
  SETMSG(HA_ERR_END_OF_FILE,            "End in next/prev/first/last");
  SETMSG(HA_ERR_UNSUPPORTED,            ER(ER_ILLEGAL_HA));
  SETMSG(HA_ERR_TO_BIG_ROW,             "Too big row");
  SETMSG(HA_WRONG_CREATE_OPTION,        "Wrong create option");
  SETMSG(HA_ERR_FOUND_DUPP_UNIQUE,      ER(ER_DUP_UNIQUE));
  SETMSG(HA_ERR_UNKNOWN_CHARSET,        "Can't open charset");
  SETMSG(HA_ERR_WRONG_MRG_TABLE_DEF,    ER(ER_WRONG_MRG_TABLE));
  SETMSG(HA_ERR_CRASHED_ON_REPAIR,      ER(ER_CRASHED_ON_REPAIR));
  SETMSG(HA_ERR_CRASHED_ON_USAGE,       ER(ER_CRASHED_ON_USAGE));
  SETMSG(HA_ERR_LOCK_WAIT_TIMEOUT,      ER(ER_LOCK_WAIT_TIMEOUT));
  SETMSG(HA_ERR_LOCK_TABLE_FULL,        ER(ER_LOCK_TABLE_FULL));
  SETMSG(HA_ERR_READ_ONLY_TRANSACTION,  ER(ER_READ_ONLY_TRANSACTION));
  SETMSG(HA_ERR_LOCK_DEADLOCK,          ER(ER_LOCK_DEADLOCK));
  SETMSG(HA_ERR_CANNOT_ADD_FOREIGN,     ER(ER_CANNOT_ADD_FOREIGN));
331 332
  SETMSG(HA_ERR_NO_REFERENCED_ROW,      ER(ER_NO_REFERENCED_ROW_2));
  SETMSG(HA_ERR_ROW_IS_REFERENCED,      ER(ER_ROW_IS_REFERENCED_2));
333 334 335 336 337
  SETMSG(HA_ERR_NO_SAVEPOINT,           "No savepoint with that name");
  SETMSG(HA_ERR_NON_UNIQUE_BLOCK_SIZE,  "Non unique key block size");
  SETMSG(HA_ERR_NO_SUCH_TABLE,          "No such table: '%.64s'");
  SETMSG(HA_ERR_TABLE_EXIST,            ER(ER_TABLE_EXISTS_ERROR));
  SETMSG(HA_ERR_NO_CONNECTION,          "Could not connect to storage engine");
338
  SETMSG(HA_ERR_TABLE_DEF_CHANGED,      ER(ER_TABLE_DEF_CHANGED));
339
  SETMSG(HA_ERR_FOREIGN_DUPLICATE_KEY,  "FK constraint would lead to duplicate key");
340
  SETMSG(HA_ERR_TABLE_NEEDS_UPGRADE,    ER(ER_TABLE_NEEDS_UPGRADE));
341
  SETMSG(HA_ERR_TABLE_READONLY,         ER(ER_OPEN_AS_READONLY));
342 343
  SETMSG(HA_ERR_AUTOINC_READ_FAILED,    ER(ER_AUTOINC_READ_FAILED));
  SETMSG(HA_ERR_AUTOINC_ERANGE,         ER(ER_WARN_DATA_OUT_OF_RANGE));
344 345 346 347 348 349

  /* Register the error messages for use with my_error(). */
  return my_error_register(errmsgs, HA_ERR_FIRST, HA_ERR_LAST);
}


350
/**
351 352
  Unregister handler error messages.

353
  @retval
354
    0           OK
355 356
  @retval
    !=0         Error
357 358 359 360 361 362 363 364
*/
static int ha_finish_errors(void)
{
  const char    **errmsgs;

  /* Allocate a pointer array for the error message strings. */
  if (! (errmsgs= my_error_unregister(HA_ERR_FIRST, HA_ERR_LAST)))
    return 1;
365
  my_free((uchar*) errmsgs, MYF(0));
366 367
  return 0;
}
serg@serg.mylan's avatar
serg@serg.mylan committed
368

369

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
370
int ha_finalize_handlerton(st_plugin_int *plugin)
371
{
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
372
  handlerton *hton= (handlerton *)plugin->data;
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
373 374 375 376 377 378 379 380 381 382 383 384
  DBUG_ENTER("ha_finalize_handlerton");

  switch (hton->state)
  {
  case SHOW_OPTION_NO:
  case SHOW_OPTION_DISABLED:
    break;
  case SHOW_OPTION_YES:
    if (installed_htons[hton->db_type] == hton)
      installed_htons[hton->db_type]= NULL;
    break;
  };
385

386 387 388
  if (hton->panic)
    hton->panic(hton, HA_PANIC_CLOSE);

389 390 391 392 393 394 395 396 397 398 399 400 401 402
  if (plugin->plugin->deinit)
  {
    /*
      Today we have no defined/special behavior for uninstalling
      engine plugins.
    */
    DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str));
    if (plugin->plugin->deinit(NULL))
    {
      DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
                             plugin->name.str));
    }
  }

403
  my_free((uchar*)hton, MYF(0));
404

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
405
  DBUG_RETURN(0);
406
}
407

408

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
409
int ha_initialize_handlerton(st_plugin_int *plugin)
410
{
411
  handlerton *hton;
412
  DBUG_ENTER("ha_initialize_handlerton");
413
  DBUG_PRINT("plugin", ("initialize plugin: '%s'", plugin->name.str));
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
414

415 416 417
  hton= (handlerton *)my_malloc(sizeof(handlerton),
                                MYF(MY_WME | MY_ZEROFILL));
  /* Historical Requirement */
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
418
  plugin->data= hton; // shortcut for the future
419 420 421 422 423 424 425 426 427
  if (plugin->plugin->init)
  {
    if (plugin->plugin->init(hton))
    {
      sql_print_error("Plugin '%s' init function returned error.",
                      plugin->name.str);
      goto err;
    }
  }
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
428

429 430 431 432
  /*
    the switch below and hton->state should be removed when
    command-line options for plugins will be implemented
  */
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
433
  switch (hton->state) {
434 435 436 437
  case SHOW_OPTION_NO:
    break;
  case SHOW_OPTION_YES:
    {
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
438
      uint tmp;
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
439
      /* now check the db_type for conflict */
440
      if (hton->db_type <= DB_TYPE_UNKNOWN ||
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
441 442 443 444
          hton->db_type >= DB_TYPE_DEFAULT ||
          installed_htons[hton->db_type])
      {
        int idx= (int) DB_TYPE_FIRST_DYNAMIC;
445

acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
446 447 448 449 450 451 452 453 454 455
        while (idx < (int) DB_TYPE_DEFAULT && installed_htons[idx])
          idx++;

        if (idx == (int) DB_TYPE_DEFAULT)
        {
          sql_print_warning("Too many storage engines!");
          DBUG_RETURN(1);
        }
        if (hton->db_type != DB_TYPE_UNKNOWN)
          sql_print_warning("Storage engine '%s' has conflicting typecode. "
456
                            "Assigning value %d.", plugin->plugin->name, idx);
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
457 458 459
        hton->db_type= (enum legacy_db_type) idx;
      }
      installed_htons[hton->db_type]= hton;
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
460
      tmp= hton->savepoint_offset;
461 462 463
      hton->savepoint_offset= savepoint_alloc_size;
      savepoint_alloc_size+= tmp;
      hton->slot= total_ha++;
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
464
      hton2plugin[hton->slot]=plugin;
465 466
      if (hton->prepare)
        total_ha_2pc++;
467 468 469 470 471 472 473
      break;
    }
    /* fall through */
  default:
    hton->state= SHOW_OPTION_DISABLED;
    break;
  }
474 475
  
  /* 
476 477 478
    This is entirely for legacy. We will create a new "disk based" hton and a 
    "memory" hton which will be configurable longterm. We should be able to 
    remove partition and myisammrg.
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
  */
  switch (hton->db_type) {
  case DB_TYPE_HEAP:
    heap_hton= hton;
    break;
  case DB_TYPE_MYISAM:
    myisam_hton= hton;
    break;
  case DB_TYPE_PARTITION_DB:
    partition_hton= hton;
    break;
  default:
    break;
  };

494
  DBUG_RETURN(0);
495 496
err:
  DBUG_RETURN(1);
497 498
}

bk@work.mysql.com's avatar
bk@work.mysql.com committed
499 500
int ha_init()
{
501
  int error= 0;
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
502 503
  DBUG_ENTER("ha_init");

504
  if (ha_init_errors())
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
505
    DBUG_RETURN(1);
serg@serg.mylan's avatar
serg@serg.mylan committed
506

507
  DBUG_ASSERT(total_ha < MAX_HA);
508 509 510 511 512 513
  /*
    Check if there is a transaction-capable storage engine besides the
    binary log (which is considered a transaction-capable storage engine in
    counting total_ha)
  */
  opt_using_transactions= total_ha>(ulong)opt_bin_log;
514
  savepoint_alloc_size+= sizeof(SAVEPOINT);
acurtis@xiphis.org's avatar
acurtis@xiphis.org committed
515
  DBUG_RETURN(error);
516 517
}

518
int ha_end()
bk@work.mysql.com's avatar
bk@work.mysql.com committed
519
{
520 521
  int error= 0;
  DBUG_ENTER("ha_end");
522

523

524 525 526 527 528 529 530
  /* 
    This should be eventualy based  on the graceful shutdown flag.
    So if flag is equal to HA_PANIC_CLOSE, the deallocate
    the errors.
  */
  if (ha_finish_errors())
    error= 1;
531

532 533
  DBUG_RETURN(error);
}
bk@work.mysql.com's avatar
bk@work.mysql.com committed
534

antony@ppcg5.local's avatar
antony@ppcg5.local committed
535
static my_bool dropdb_handlerton(THD *unused1, plugin_ref plugin,
536 537
                                 void *path)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
538
  handlerton *hton= plugin_data(plugin, handlerton *);
539
  if (hton->state == SHOW_OPTION_YES && hton->drop_database)
540
    hton->drop_database(hton, (char *)path);
541 542 543 544
  return FALSE;
}


545 546
void ha_drop_database(char* path)
{
547 548
  plugin_foreach(NULL, dropdb_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, path);
}
549

550

antony@ppcg5.local's avatar
antony@ppcg5.local committed
551
static my_bool closecon_handlerton(THD *thd, plugin_ref plugin,
552 553
                                   void *unused)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
554
  handlerton *hton= plugin_data(plugin, handlerton *);
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
555 556 557 558
  /*
    there's no need to rollback here as all transactions must
    be rolled back already
  */
559
  if (hton->state == SHOW_OPTION_YES && hton->close_connection &&
560
      thd_get_ha_data(thd, hton))
561
    hton->close_connection(hton, thd);
562
  return FALSE;
563
}
bk@work.mysql.com's avatar
bk@work.mysql.com committed
564

565

566 567 568
/**
  @note
    don't bother to rollback here, it's done already
569
*/
570 571
void ha_close_connection(THD* thd)
{
572
  plugin_foreach(thd, closecon_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, 0);
573 574 575 576 577
}

/* ========================================================================
 ======================= TRANSACTIONS ===================================*/

578 579
/**
  Register a storage engine for a transaction.
serg@serg.mylan's avatar
serg@serg.mylan committed
580

581 582 583 584 585
  Every storage engine MUST call this function when it starts
  a transaction or a statement (that is it must be called both for the
  "beginning of transaction" and "beginning of statement").
  Only storage engines registered for the transaction/statement
  will know when to commit/rollback it.
586

587
  @note
588 589 590
    trans_register_ha is idempotent - storage engine may register many
    times per transaction.

serg@serg.mylan's avatar
serg@serg.mylan committed
591
*/
592 593 594
void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
{
  THD_TRANS *trans;
595
  handlerton **ht;
serg@serg.mylan's avatar
serg@serg.mylan committed
596 597 598
  DBUG_ENTER("trans_register_ha");
  DBUG_PRINT("enter",("%s", all ? "all" : "stmt"));

599 600 601 602 603 604 605 606
  if (all)
  {
    trans= &thd->transaction.all;
    thd->server_status|= SERVER_STATUS_IN_TRANS;
  }
  else
    trans= &thd->transaction.stmt;

607
  for (ht=trans->ht; *ht; ht++)
608 609 610
    if (*ht == ht_arg)
      DBUG_VOID_RETURN;  /* already registered, return */

611
  trans->ht[trans->nht++]=ht_arg;
612
  DBUG_ASSERT(*ht == ht_arg);
613
  trans->no_2pc|=(ht_arg->prepare==0);
614 615
  if (thd->transaction.xid_state.xid.is_null())
    thd->transaction.xid_state.xid.set(thd->query_id);
serg@serg.mylan's avatar
serg@serg.mylan committed
616
  DBUG_VOID_RETURN;
617 618
}

619 620 621 622 623
/**
  @retval
    0   ok
  @retval
    1   error, transaction was rolled back
624 625 626 627 628 629 630 631 632 633 634 635 636
*/
int ha_prepare(THD *thd)
{
  int error=0, all=1;
  THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
  handlerton **ht=trans->ht;
  DBUG_ENTER("ha_prepare");
#ifdef USING_TRANSACTIONS
  if (trans->nht)
  {
    for (; *ht; ht++)
    {
      int err;
637
      status_var_increment(thd->status_var.ha_prepare_count);
638
      if ((*ht)->prepare)
639
      {
640
        if ((err= (*(*ht)->prepare)(*ht, thd, all)))
641 642 643 644 645 646 647 648 649 650
        {
          my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
          ha_rollback_trans(thd, all);
          error=1;
          break;
        }
      }
      else
      {
        push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
651
                            ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
antony@ppcg5.local's avatar
antony@ppcg5.local committed
652
                            ha_resolve_storage_engine_name(*ht));
653 654 655 656 657 658 659
      }
    }
  }
#endif /* USING_TRANSACTIONS */
  DBUG_RETURN(error);
}

660 661 662 663 664 665 666 667 668 669 670 671 672
/**
  @retval
    0   ok
  @retval
    1   transaction was rolled back
  @retval
    2   error during commit, data may be inconsistent

  @todo
    Since we don't support nested statement transactions in 5.0,
    we can't commit or rollback stmt transactions while we are inside
    stored functions or triggers. So we simply do nothing now.
    TODO: This should be fixed in later ( >= 5.1) releases.
673 674 675 676 677 678 679
*/
int ha_commit_trans(THD *thd, bool all)
{
  int error= 0, cookie= 0;
  THD_TRANS *trans= all ? &thd->transaction.all : &thd->transaction.stmt;
  bool is_real_trans= all || thd->transaction.all.nht == 0;
  handlerton **ht= trans->ht;
680
  my_xid xid= thd->transaction.xid_state.xid.get_my_xid();
681
  DBUG_ENTER("ha_commit_trans");
682

683
  if (thd->in_sub_stmt)
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702
  {
    /*
      Since we don't support nested statement transactions in 5.0,
      we can't commit or rollback stmt transactions while we are inside
      stored functions or triggers. So we simply do nothing now.
      TODO: This should be fixed in later ( >= 5.1) releases.
    */
    if (!all)
      DBUG_RETURN(0);
    /*
      We assume that all statements which commit or rollback main transaction
      are prohibited inside of stored functions or triggers. So they should
      bail out with error even before ha_commit_trans() call. To be 100% safe
      let us throw error in non-debug builds.
    */
    DBUG_ASSERT(0);
    my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
    DBUG_RETURN(2);
  }
703 704 705
#ifdef USING_TRANSACTIONS
  if (trans->nht)
  {
706 707 708 709 710
    if (is_real_trans && wait_if_global_read_lock(thd, 0, 0))
    {
      ha_rollback_trans(thd, all);
      DBUG_RETURN(1);
    }
711

malff/marcsql@weblab.(none)'s avatar
malff/marcsql@weblab.(none) committed
712 713 714 715 716
    if (   is_real_trans
        && opt_readonly
        && ! (thd->security_ctx->master_access & SUPER_ACL)
        && ! thd->slave_thread
       )
717 718 719 720 721 722 723
    {
      my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
      ha_rollback_trans(thd, all);
      error= 1;
      goto end;
    }

724
    DBUG_EXECUTE_IF("crash_commit_before", abort(););
725 726 727 728 729

    /* Close all cursors that can not survive COMMIT */
    if (is_real_trans)                          /* not a statement commit */
      thd->stmt_map.close_transient_cursors();

730 731 732 733 734
    if (!trans->no_2pc && trans->nht > 1)
    {
      for (; *ht && !error; ht++)
      {
        int err;
735
        if ((err= (*(*ht)->prepare)(*ht, thd, all)))
736 737
        {
          my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
738
          error= 1;
739
        }
740
        status_var_increment(thd->status_var.ha_prepare_count);
741
      }
742
      DBUG_EXECUTE_IF("crash_commit_after_prepare", abort(););
743
      if (error || (is_real_trans && xid &&
744
                    (error= !(cookie= tc_log->log_xid(thd, xid)))))
745 746
      {
        ha_rollback_trans(thd, all);
747 748
        error= 1;
        goto end;
749
      }
750
      DBUG_EXECUTE_IF("crash_commit_after_log", abort(););
751
    }
752
    error=ha_commit_one_phase(thd, all) ? (cookie ? 2 : 1) : 0;
753
    DBUG_EXECUTE_IF("crash_commit_before_unlog", abort(););
754
    if (cookie)
755
      tc_log->unlog(cookie, xid);
756
    DBUG_EXECUTE_IF("crash_commit_after", abort(););
757 758 759
end:
    if (is_real_trans)
      start_waiting_global_read_lock(thd);
760 761 762 763 764
  }
#endif /* USING_TRANSACTIONS */
  DBUG_RETURN(error);
}

765 766 767
/**
  @note
  This function does not care about global read lock. A caller should.
768
*/
769 770 771 772 773 774 775 776 777 778 779 780 781
int ha_commit_one_phase(THD *thd, bool all)
{
  int error=0;
  THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
  bool is_real_trans=all || thd->transaction.all.nht == 0;
  handlerton **ht=trans->ht;
  DBUG_ENTER("ha_commit_one_phase");
#ifdef USING_TRANSACTIONS
  if (trans->nht)
  {
    for (ht=trans->ht; *ht; ht++)
    {
      int err;
782
      if ((err= (*(*ht)->commit)(*ht, thd, all)))
783 784 785 786
      {
        my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
        error=1;
      }
787
      status_var_increment(thd->status_var.ha_commit_count);
788 789 790 791 792
      *ht= 0;
    }
    trans->nht=0;
    trans->no_2pc=0;
    if (is_real_trans)
793
      thd->transaction.xid_state.xid.null();
794 795 796 797 798
    if (all)
    {
#ifdef HAVE_QUERY_CACHE
      if (thd->transaction.changed_tables)
        query_cache.invalidate(thd->transaction.changed_tables);
799
#endif
800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
      thd->variables.tx_isolation=thd->session_tx_isolation;
      thd->transaction.cleanup();
    }
  }
#endif /* USING_TRANSACTIONS */
  DBUG_RETURN(error);
}


int ha_rollback_trans(THD *thd, bool all)
{
  int error=0;
  THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
  bool is_real_trans=all || thd->transaction.all.nht == 0;
  DBUG_ENTER("ha_rollback_trans");
815
  if (thd->in_sub_stmt)
816 817 818 819 820 821 822 823 824 825 826 827
  {
    /*
      If we are inside stored function or trigger we should not commit or
      rollback current statement transaction. See comment in ha_commit_trans()
      call for more information.
    */
    if (!all)
      DBUG_RETURN(0);
    DBUG_ASSERT(0);
    my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
    DBUG_RETURN(1);
  }
828 829 830
#ifdef USING_TRANSACTIONS
  if (trans->nht)
  {
831 832 833 834
    /* Close all cursors that can not survive ROLLBACK */
    if (is_real_trans)                          /* not a statement commit */
      thd->stmt_map.close_transient_cursors();

835 836 837
    for (handlerton **ht=trans->ht; *ht; ht++)
    {
      int err;
838
      if ((err= (*(*ht)->rollback)(*ht, thd, all)))
839 840 841 842
      { // cannot happen
        my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
        error=1;
      }
843
      status_var_increment(thd->status_var.ha_rollback_count);
844 845 846 847 848
      *ht= 0;
    }
    trans->nht=0;
    trans->no_2pc=0;
    if (is_real_trans)
849
      thd->transaction.xid_state.xid.null();
850 851 852 853 854 855 856
    if (all)
    {
      thd->variables.tx_isolation=thd->session_tx_isolation;
      thd->transaction.cleanup();
    }
  }
#endif /* USING_TRANSACTIONS */
857 858 859
  if (all)
    thd->transaction_rollback_request= FALSE;

860 861 862 863 864 865 866 867 868
  /*
    If a non-transactional table was updated, warn; don't warn if this is a
    slave thread (because when a slave thread executes a ROLLBACK, it has
    been read from the binary log, so it's 100% sure and normal to produce
    error ER_WARNING_NOT_COMPLETE_ROLLBACK. If we sent the warning to the
    slave SQL thread, it would not stop the thread but just be printed in
    the error log; but we don't want users to wonder why they have this
    message in the error log, so we don't send it.
  */
869
  if (is_real_trans && thd->transaction.all.modified_non_trans_table &&
870
      !thd->slave_thread && thd->killed != THD::KILL_CONNECTION)
871 872 873 874
    push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
                 ER_WARNING_NOT_COMPLETE_ROLLBACK,
                 ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));
  DBUG_RETURN(error);
875 876
}

877 878 879 880 881 882 883 884 885 886
/**
  This is used to commit or rollback a single statement depending on
  the value of error.

  @note
    Note that if the autocommit is on, then the following call inside
    InnoDB will commit or rollback the whole transaction (= the statement). The
    autocommit mechanism built into InnoDB is based on counting locks, but if
    the user has used LOCK TABLES then that mechanism does not know to do the
    commit.
887
*/
bk@work.mysql.com's avatar
bk@work.mysql.com committed
888 889 890
int ha_autocommit_or_rollback(THD *thd, int error)
{
  DBUG_ENTER("ha_autocommit_or_rollback");
891
#ifdef USING_TRANSACTIONS
892
  if (thd->transaction.stmt.nht)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
893
  {
894 895 896 897 898
    if (!error)
    {
      if (ha_commit_stmt(thd))
	error=1;
    }
899 900
    else if (thd->transaction_rollback_request && !thd->in_sub_stmt)
      (void) ha_rollback(thd);
901 902
    else
      (void) ha_rollback_stmt(thd);
monty@mashka.mysql.fi's avatar
monty@mashka.mysql.fi committed
903

904
    thd->variables.tx_isolation=thd->session_tx_isolation;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
905 906 907 908 909
  }
#endif
  DBUG_RETURN(error);
}

monty@mysql.com's avatar
monty@mysql.com committed
910

911 912 913 914 915
struct xahton_st {
  XID *xid;
  int result;
};

antony@ppcg5.local's avatar
antony@ppcg5.local committed
916
static my_bool xacommit_handlerton(THD *unused1, plugin_ref plugin,
917
                                   void *arg)
918
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
919
  handlerton *hton= plugin_data(plugin, handlerton *);
920 921
  if (hton->state == SHOW_OPTION_YES && hton->recover)
  {
922
    hton->commit_by_xid(hton, ((struct xahton_st *)arg)->xid);
923 924 925 926
    ((struct xahton_st *)arg)->result= 0;
  }
  return FALSE;
}
927

antony@ppcg5.local's avatar
antony@ppcg5.local committed
928
static my_bool xarollback_handlerton(THD *unused1, plugin_ref plugin,
929 930
                                     void *arg)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
931
  handlerton *hton= plugin_data(plugin, handlerton *);
932
  if (hton->state == SHOW_OPTION_YES && hton->recover)
monty@mysql.com's avatar
monty@mysql.com committed
933
  {
934
    hton->rollback_by_xid(hton, ((struct xahton_st *)arg)->xid);
935
    ((struct xahton_st *)arg)->result= 0;
monty@mysql.com's avatar
monty@mysql.com committed
936
  }
937 938 939 940 941 942 943 944 945
  return FALSE;
}


int ha_commit_or_rollback_by_xid(XID *xid, bool commit)
{
  struct xahton_st xaop;
  xaop.xid= xid;
  xaop.result= 1;
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
946

947 948 949 950
  plugin_foreach(NULL, commit ? xacommit_handlerton : xarollback_handlerton,
                 MYSQL_STORAGE_ENGINE_PLUGIN, &xaop);

  return xaop.result;
951
}
952

monty@mysql.com's avatar
monty@mysql.com committed
953

serg@serg.mylan's avatar
serg@serg.mylan committed
954
#ifndef DBUG_OFF
955 956 957 958
/**
  @note
    This does not need to be multi-byte safe or anything
*/
serg@serg.mylan's avatar
serg@serg.mylan committed
959 960 961 962 963 964 965 966
static char* xid_to_str(char *buf, XID *xid)
{
  int i;
  char *s=buf;
  *s++='\'';
  for (i=0; i < xid->gtrid_length+xid->bqual_length; i++)
  {
    uchar c=(uchar)xid->data[i];
967 968
    /* is_next_dig is set if next character is a number */
    bool is_next_dig= FALSE;
serg@serg.mylan's avatar
serg@serg.mylan committed
969 970
    if (i < XIDDATASIZE)
    {
971 972
      char ch= xid->data[i+1];
      is_next_dig= (ch >= '0' && ch <='9');
serg@serg.mylan's avatar
serg@serg.mylan committed
973
    }
serg@serg.mylan's avatar
serg@serg.mylan committed
974 975 976 977 978 979 980 981 982 983 984 985
    if (i == xid->gtrid_length)
    {
      *s++='\'';
      if (xid->bqual_length)
      {
        *s++='.';
        *s++='\'';
      }
    }
    if (c < 32 || c > 126)
    {
      *s++='\\';
986 987 988 989 990
      /*
        If next character is a number, write current character with
        3 octal numbers to ensure that the next number is not seen
        as part of the octal number
      */
serg@serg.mylan's avatar
serg@serg.mylan committed
991 992 993 994 995
      if (c > 077 || is_next_dig)
        *s++=_dig_vec_lower[c >> 6];
      if (c > 007 || is_next_dig)
        *s++=_dig_vec_lower[(c >> 3) & 7];
      *s++=_dig_vec_lower[c & 7];
serg@serg.mylan's avatar
serg@serg.mylan committed
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
    }
    else
    {
      if (c == '\'' || c == '\\')
        *s++='\\';
      *s++=c;
    }
  }
  *s++='\'';
  *s=0;
  return buf;
}
#endif

1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
/**
  recover() step of xa.

  @note
    there are three modes of operation:
    - automatic recover after a crash
    in this case commit_list != 0, tc_heuristic_recover==0
    all xids from commit_list are committed, others are rolled back
    - manual (heuristic) recover
    in this case commit_list==0, tc_heuristic_recover != 0
    DBA has explicitly specified that all prepared transactions should
    be committed (or rolled back).
    - no recovery (MySQL did not detect a crash)
    in this case commit_list==0, tc_heuristic_recover == 0
    there should be no prepared transactions in this case.
1025
*/
1026 1027 1028 1029 1030 1031 1032
struct xarecover_st
{
  int len, found_foreign_xids, found_my_xids;
  XID *list;
  HASH *commit_list;
  bool dry_run;
};
1033

antony@ppcg5.local's avatar
antony@ppcg5.local committed
1034
static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin,
1035 1036
                                    void *arg)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1037
  handlerton *hton= plugin_data(plugin, handlerton *);
1038 1039
  struct xarecover_st *info= (struct xarecover_st *) arg;
  int got;
1040

1041
  if (hton->state == SHOW_OPTION_YES && hton->recover)
1042
  {
1043
    while ((got= hton->recover(hton, info->list, info->len)) > 0 )
1044
    {
serg@serg.mylan's avatar
serg@serg.mylan committed
1045
      sql_print_information("Found %d prepared transaction(s) in %s",
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1046
                            got, ha_resolve_storage_engine_name(hton));
1047 1048
      for (int i=0; i < got; i ++)
      {
1049
        my_xid x=info->list[i].get_my_xid();
1050
        if (!x) // not "mine" - that is generated by external TM
1051
        {
serg@serg.mylan's avatar
serg@serg.mylan committed
1052 1053
#ifndef DBUG_OFF
          char buf[XIDDATASIZE*4+6]; // see xid_to_str
1054
          sql_print_information("ignore xid %s", xid_to_str(buf, info->list+i));
serg@serg.mylan's avatar
serg@serg.mylan committed
1055
#endif
1056 1057
          xid_cache_insert(info->list+i, XA_PREPARED);
          info->found_foreign_xids++;
1058 1059
          continue;
        }
1060
        if (info->dry_run)
1061
        {
1062
          info->found_my_xids++;
1063
          continue;
1064 1065
        }
        // recovery mode
1066
        if (info->commit_list ?
1067
            hash_search(info->commit_list, (uchar *)&x, sizeof(x)) != 0 :
1068
            tc_heuristic_recover == TC_HEURISTIC_RECOVER_COMMIT)
serg@serg.mylan's avatar
serg@serg.mylan committed
1069 1070 1071
        {
#ifndef DBUG_OFF
          char buf[XIDDATASIZE*4+6]; // see xid_to_str
1072
          sql_print_information("commit xid %s", xid_to_str(buf, info->list+i));
serg@serg.mylan's avatar
serg@serg.mylan committed
1073
#endif
1074
          hton->commit_by_xid(hton, info->list+i);
serg@serg.mylan's avatar
serg@serg.mylan committed
1075
        }
1076
        else
serg@serg.mylan's avatar
serg@serg.mylan committed
1077 1078 1079
        {
#ifndef DBUG_OFF
          char buf[XIDDATASIZE*4+6]; // see xid_to_str
1080 1081
          sql_print_information("rollback xid %s",
                                xid_to_str(buf, info->list+i));
serg@serg.mylan's avatar
serg@serg.mylan committed
1082
#endif
1083
          hton->rollback_by_xid(hton, info->list+i);
serg@serg.mylan's avatar
serg@serg.mylan committed
1084
        }
1085
      }
1086
      if (got < info->len)
1087
        break;
1088 1089
    }
  }
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136
  return FALSE;
}

int ha_recover(HASH *commit_list)
{
  struct xarecover_st info;
  DBUG_ENTER("ha_recover");
  info.found_foreign_xids= info.found_my_xids= 0;
  info.commit_list= commit_list;
  info.dry_run= (info.commit_list==0 && tc_heuristic_recover==0);
  info.list= NULL;

  /* commit_list and tc_heuristic_recover cannot be set both */
  DBUG_ASSERT(info.commit_list==0 || tc_heuristic_recover==0);
  /* if either is set, total_ha_2pc must be set too */
  DBUG_ASSERT(info.dry_run || total_ha_2pc>(ulong)opt_bin_log);

  if (total_ha_2pc <= (ulong)opt_bin_log)
    DBUG_RETURN(0);

  if (info.commit_list)
    sql_print_information("Starting crash recovery...");

#ifndef WILL_BE_DELETED_LATER
  /*
    for now, only InnoDB supports 2pc. It means we can always safely
    rollback all pending transactions, without risking inconsistent data
  */
  DBUG_ASSERT(total_ha_2pc == (ulong) opt_bin_log+1); // only InnoDB and binlog
  tc_heuristic_recover= TC_HEURISTIC_RECOVER_ROLLBACK; // forcing ROLLBACK
  info.dry_run=FALSE;
#endif

  for (info.len= MAX_XID_LIST_SIZE ; 
       info.list==0 && info.len > MIN_XID_LIST_SIZE; info.len/=2)
  {
    info.list=(XID *)my_malloc(info.len*sizeof(XID), MYF(0));
  }
  if (!info.list)
  {
    sql_print_error(ER(ER_OUTOFMEMORY), info.len*sizeof(XID));
    DBUG_RETURN(1);
  }

  plugin_foreach(NULL, xarecover_handlerton, 
                 MYSQL_STORAGE_ENGINE_PLUGIN, &info);

1137
  my_free((uchar*)info.list, MYF(0));
1138 1139 1140 1141
  if (info.found_foreign_xids)
    sql_print_warning("Found %d prepared XA transactions", 
                      info.found_foreign_xids);
  if (info.dry_run && info.found_my_xids)
1142 1143 1144 1145 1146 1147 1148
  {
    sql_print_error("Found %d prepared transactions! It means that mysqld was "
                    "not shut down properly last time and critical recovery "
                    "information (last binlog or %s file) was manually deleted "
                    "after a crash. You have to start mysqld with "
                    "--tc-heuristic-recover switch to commit or rollback "
                    "pending transactions.",
1149
                    info.found_my_xids, opt_tc_log_file);
1150 1151
    DBUG_RETURN(1);
  }
1152
  if (info.commit_list)
serg@serg.mylan's avatar
serg@serg.mylan committed
1153
    sql_print_information("Crash recovery finished.");
1154
  DBUG_RETURN(0);
1155
}
1156

1157 1158
/**
  return the list of XID's to a client, the same way SHOW commands do.
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1159

1160
  @note
1161 1162 1163
    I didn't find in XA specs that an RM cannot return the same XID twice,
    so mysql_xa_recover does not filter XID's to ensure uniqueness.
    It can be easily fixed later, if necessary.
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1164
*/
1165
bool mysql_xa_recover(THD *thd)
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1166
{
1167 1168
  List<Item> field_list;
  Protocol *protocol= thd->protocol;
1169 1170
  int i=0;
  XID_STATE *xs;
1171 1172
  DBUG_ENTER("mysql_xa_recover");

1173 1174 1175
  field_list.push_back(new Item_int("formatID", 0, MY_INT32_NUM_DECIMAL_DIGITS));
  field_list.push_back(new Item_int("gtrid_length", 0, MY_INT32_NUM_DECIMAL_DIGITS));
  field_list.push_back(new Item_int("bqual_length", 0, MY_INT32_NUM_DECIMAL_DIGITS));
1176 1177 1178 1179 1180
  field_list.push_back(new Item_empty_string("data",XIDDATASIZE));

  if (protocol->send_fields(&field_list,
                            Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
    DBUG_RETURN(1);
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1181

1182
  pthread_mutex_lock(&LOCK_xid_cache);
monty@mishka.local's avatar
monty@mishka.local committed
1183
  while ((xs= (XID_STATE*)hash_element(&xid_cache, i++)))
1184
  {
1185
    if (xs->xa_state==XA_PREPARED)
1186
    {
1187 1188 1189 1190 1191 1192 1193
      protocol->prepare_for_resend();
      protocol->store_longlong((longlong)xs->xid.formatID, FALSE);
      protocol->store_longlong((longlong)xs->xid.gtrid_length, FALSE);
      protocol->store_longlong((longlong)xs->xid.bqual_length, FALSE);
      protocol->store(xs->xid.data, xs->xid.gtrid_length+xs->xid.bqual_length,
                      &my_charset_bin);
      if (protocol->write())
1194
      {
1195 1196
        pthread_mutex_unlock(&LOCK_xid_cache);
        DBUG_RETURN(1);
1197 1198
      }
    }
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1199
  }
1200

1201
  pthread_mutex_unlock(&LOCK_xid_cache);
1202
  send_eof(thd);
1203
  DBUG_RETURN(0);
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1204
}
1205

1206 1207
/**
  @details
1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218
  This function should be called when MySQL sends rows of a SELECT result set
  or the EOF mark to the client. It releases a possible adaptive hash index
  S-latch held by thd in InnoDB and also releases a possible InnoDB query
  FIFO ticket to enter InnoDB. To save CPU time, InnoDB allows a thd to
  keep them over several calls of the InnoDB handler interface when a join
  is executed. But when we let the control to pass to the client they have
  to be released because if the application program uses mysql_use_result(),
  it may deadlock on the S-latch if the application on another connection
  performs another SQL query. In MySQL-4.1 this is even more important because
  there a connection can have several SELECT queries open at the same time.

1219 1220 1221 1222
  @param thd           the thread handle of the current connection

  @return
    always 0
1223
*/
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1224
static my_bool release_temporary_latches(THD *thd, plugin_ref plugin,
1225 1226
                                 void *unused)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1227
  handlerton *hton= plugin_data(plugin, handlerton *);
1228 1229

  if (hton->state == SHOW_OPTION_YES && hton->release_temporary_latches)
1230
    hton->release_temporary_latches(hton, thd);
1231 1232 1233 1234 1235

  return FALSE;
}


1236 1237
int ha_release_temporary_latches(THD *thd)
{
1238 1239 1240
  plugin_foreach(thd, release_temporary_latches, MYSQL_STORAGE_ENGINE_PLUGIN, 
                 NULL);

1241
  return 0;
1242 1243
}

1244
int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1245 1246
{
  int error=0;
1247 1248
  THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
                                        &thd->transaction.all);
1249 1250
  handlerton **ht=trans->ht, **end_ht;
  DBUG_ENTER("ha_rollback_to_savepoint");
1251

1252 1253 1254 1255 1256 1257 1258 1259 1260 1261
  trans->nht=sv->nht;
  trans->no_2pc=0;
  end_ht=ht+sv->nht;
  /*
    rolling back to savepoint in all storage engines that were part of the
    transaction when the savepoint was set
  */
  for (; ht < end_ht; ht++)
  {
    int err;
1262
    DBUG_ASSERT((*ht)->savepoint_set != 0);
1263
    if ((err= (*(*ht)->savepoint_rollback)(*ht, thd, (uchar *)(sv+1)+(*ht)->savepoint_offset)))
1264 1265 1266
    { // cannot happen
      my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
      error=1;
1267
    }
1268
    status_var_increment(thd->status_var.ha_savepoint_rollback_count);
1269
    trans->no_2pc|=(*ht)->prepare == 0;
1270
  }
1271 1272 1273 1274 1275
  /*
    rolling back the transaction in all storage engines that were not part of
    the transaction when the savepoint was set
  */
  for (; *ht ; ht++)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1276
  {
1277
    int err;
1278
    if ((err= (*(*ht)->rollback)(*ht, thd, !thd->in_sub_stmt)))
1279 1280 1281
    { // cannot happen
      my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
      error=1;
guilhem@mysql.com's avatar
guilhem@mysql.com committed
1282
    }
1283
    status_var_increment(thd->status_var.ha_rollback_count);
1284
    *ht=0; // keep it conveniently zero-filled
1285
  }
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1286 1287 1288
  DBUG_RETURN(error);
}

1289 1290 1291
/**
  @note
  according to the sql standard (ISO/IEC 9075-2:2003)
1292 1293
  section "4.33.4 SQL-statements and transaction states",
  SAVEPOINT is *not* transaction-initiating SQL-statement
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1294
*/
1295
int ha_savepoint(THD *thd, SAVEPOINT *sv)
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1296 1297
{
  int error=0;
1298 1299
  THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
                                        &thd->transaction.all);
1300 1301
  handlerton **ht=trans->ht;
  DBUG_ENTER("ha_savepoint");
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1302
#ifdef USING_TRANSACTIONS
1303
  for (; *ht; ht++)
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1304
  {
1305 1306
    int err;
    if (! (*ht)->savepoint_set)
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1307
    {
1308
      my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "SAVEPOINT");
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1309
      error=1;
1310
      break;
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1311
    }
1312
    if ((err= (*(*ht)->savepoint_set)(*ht, thd, (uchar *)(sv+1)+(*ht)->savepoint_offset)))
1313 1314
    { // cannot happen
      my_error(ER_GET_ERRNO, MYF(0), err);
1315 1316
      error=1;
    }
1317
    status_var_increment(thd->status_var.ha_savepoint_count);
1318
  }
1319
  sv->nht=trans->nht;
1320 1321 1322 1323
#endif /* USING_TRANSACTIONS */
  DBUG_RETURN(error);
}

1324
int ha_release_savepoint(THD *thd, SAVEPOINT *sv)
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1325 1326
{
  int error=0;
1327 1328 1329
  THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
                                        &thd->transaction.all);
  handlerton **ht=trans->ht, **end_ht;
1330 1331 1332 1333
  DBUG_ENTER("ha_release_savepoint");

  end_ht=ht+sv->nht;
  for (; ht < end_ht; ht++)
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1334
  {
1335 1336 1337
    int err;
    if (!(*ht)->savepoint_release)
      continue;
1338
    if ((err= (*(*ht)->savepoint_release)(*ht, thd, 
1339
                                          (uchar *)(sv+1)+
1340
                                          (*ht)->savepoint_offset)))
1341 1342 1343
    { // cannot happen
      my_error(ER_GET_ERRNO, MYF(0), err);
      error=1;
guilhem@mysql.com's avatar
guilhem@mysql.com committed
1344
    }
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
1345 1346 1347 1348
  }
  DBUG_RETURN(error);
}

1349

antony@ppcg5.local's avatar
antony@ppcg5.local committed
1350
static my_bool snapshot_handlerton(THD *thd, plugin_ref plugin,
1351 1352
                                   void *arg)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1353
  handlerton *hton= plugin_data(plugin, handlerton *);
1354 1355 1356
  if (hton->state == SHOW_OPTION_YES &&
      hton->start_consistent_snapshot)
  {
1357
    hton->start_consistent_snapshot(hton, thd);
1358 1359 1360 1361 1362
    *((bool *)arg)= false;
  }
  return FALSE;
}

1363 1364
int ha_start_consistent_snapshot(THD *thd)
{
1365 1366
  bool warn= true;

1367 1368
  plugin_foreach(thd, snapshot_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, &warn);

1369 1370 1371 1372
  /*
    Same idea as when one wants to CREATE TABLE in one engine which does not
    exist:
  */
1373 1374 1375 1376
  if (warn)
    push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
                 "This MySQL server does not support any "
                 "consistent-read capable storage engine");
1377 1378 1379 1380
  return 0;
}


antony@ppcg5.local's avatar
antony@ppcg5.local committed
1381
static my_bool flush_handlerton(THD *thd, plugin_ref plugin,
1382
                                void *arg)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1383
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1384
  handlerton *hton= plugin_data(plugin, handlerton *);
1385 1386
  if (hton->state == SHOW_OPTION_YES && hton->flush_logs && 
      hton->flush_logs(hton))
1387 1388 1389 1390
    return TRUE;
  return FALSE;
}

1391

1392 1393 1394
bool ha_flush_logs(handlerton *db_type)
{
  if (db_type == NULL)
1395
  {
1396 1397 1398
    if (plugin_foreach(NULL, flush_handlerton,
                          MYSQL_STORAGE_ENGINE_PLUGIN, 0))
      return TRUE;
1399
  }
1400 1401 1402
  else
  {
    if (db_type->state != SHOW_OPTION_YES ||
1403
        (db_type->flush_logs && db_type->flush_logs(db_type)))
1404 1405 1406
      return TRUE;
  }
  return FALSE;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1407 1408
}

1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427
static const char *check_lowercase_names(handler *file, const char *path,
                                         char *tmp_path)
{
  if (lower_case_table_names != 2 || (file->ha_table_flags() & HA_FILE_BASED))
    return path;

  /* Ensure that table handler get path in lower case */
  if (tmp_path != path)
    strmov(tmp_path, path);

  /*
    we only should turn into lowercase database/table part
    so start the process after homedirectory
  */
  my_casedn_str(files_charset_info, tmp_path + mysql_data_home_len);
  return tmp_path;
}


1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457
/**
  An interceptor to hijack the text of the error message without
  setting an error in the thread. We need the text to present it
  in the form of a warning to the user.
*/

struct Ha_delete_table_error_handler: public Internal_error_handler
{
public:
  virtual bool handle_error(uint sql_errno,
                            const char *message,
                            MYSQL_ERROR::enum_warning_level level,
                            THD *thd);
  char buff[MYSQL_ERRMSG_SIZE];
};


bool
Ha_delete_table_error_handler::
handle_error(uint sql_errno,
             const char *message,
             MYSQL_ERROR::enum_warning_level level,
             THD *thd)
{
  /* Grab the error message */
  strmake(buff, message, sizeof(buff)-1);
  return TRUE;
}


1458
/** @brief
1459 1460 1461
  This should return ENOENT if the file doesn't exists.
  The .frm file will be deleted only if we return 0 or ENOENT
*/
1462
int ha_delete_table(THD *thd, handlerton *table_type, const char *path,
1463
                    const char *db, const char *alias, bool generate_warning)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1464
{
1465
  handler *file;
1466
  char tmp_path[FN_REFLEN];
1467 1468 1469 1470 1471 1472 1473 1474
  int error;
  TABLE dummy_table;
  TABLE_SHARE dummy_share;
  DBUG_ENTER("ha_delete_table");

  bzero((char*) &dummy_table, sizeof(dummy_table));
  bzero((char*) &dummy_share, sizeof(dummy_share));
  dummy_table.s= &dummy_share;
1475 1476

  /* DB_TYPE_UNKNOWN is used in ALTER TABLE when renaming only .frm files */
1477
  if (table_type == NULL ||
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1478
      ! (file=get_new_handler((TABLE_SHARE*)0, thd->mem_root, table_type)))
1479
    DBUG_RETURN(ENOENT);
1480

1481
  path= check_lowercase_names(file, path, tmp_path);
1482
  if ((error= file->ha_delete_table(path)) && generate_warning)
1483 1484 1485
  {
    /*
      Because file->print_error() use my_error() to generate the error message
1486 1487 1488
      we use an internal error handler to intercept it and store the text
      in a temporary buffer. Later the message will be presented to user
      as a warning.
1489
    */
1490
    Ha_delete_table_error_handler ha_delete_table_error_handler;
1491 1492

    /* Fill up strucutures that print_error may need */
1493 1494 1495 1496 1497 1498
    dummy_share.path.str= (char*) path;
    dummy_share.path.length= strlen(path);
    dummy_share.db.str= (char*) db;
    dummy_share.db.length= strlen(db);
    dummy_share.table_name.str= (char*) alias;
    dummy_share.table_name.length= strlen(alias);
1499 1500
    dummy_table.alias= alias;

1501
    file->change_table_ptr(&dummy_table, &dummy_share);
1502 1503

    thd->push_internal_handler(&ha_delete_table_error_handler);
1504
    file->print_error(error, 0);
1505 1506 1507 1508 1509 1510 1511 1512 1513

    thd->pop_internal_handler();

    /*
      XXX: should we convert *all* errors to warnings here?
      What if the error is fatal?
    */
    push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, error,
                ha_delete_table_error_handler.buff);
1514
  }
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1515
  delete file;
1516
  DBUG_RETURN(error);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1517
}
monty@hundin.mysql.fi's avatar
monty@hundin.mysql.fi committed
1518

bk@work.mysql.com's avatar
bk@work.mysql.com committed
1519 1520 1521
/****************************************************************************
** General handler functions
****************************************************************************/
1522 1523
handler *handler::clone(MEM_ROOT *mem_root)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1524
  handler *new_handler= get_new_handler(table->s, mem_root, table->s->db_type());
svoj@april.(none)'s avatar
svoj@april.(none) committed
1525 1526 1527
  if (new_handler && !new_handler->ha_open(table,
                                           table->s->normalized_path.str,
                                           table->db_stat,
1528 1529 1530 1531 1532
                                           HA_OPEN_IGNORE_IF_LOCKED))
    return new_handler;
  return NULL;
}

bk@work.mysql.com's avatar
bk@work.mysql.com committed
1533

1534 1535 1536

void handler::ha_statistic_increment(ulong SSV::*offset) const
{
1537
  status_var_increment(table->in_use->status_var.*offset);
1538 1539
}

1540
void **handler::ha_data(THD *thd) const
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1541
{
1542
  return thd_ha_data(thd, ht);
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1543 1544 1545 1546
}

THD *handler::ha_thd(void) const
{
serg@janus.mylan's avatar
serg@janus.mylan committed
1547
  DBUG_ASSERT(!table || !table->in_use || table->in_use == current_thd);
antony@ppcg5.local's avatar
antony@ppcg5.local committed
1548 1549 1550
  return (table && table->in_use) ? table->in_use : current_thd;
}

1551

1552
/** @brief
1553 1554 1555 1556 1557 1558 1559 1560
  Open database-handler.

  IMPLEMENTATION
    Try O_RDONLY if cannot open as O_RDWR
    Don't wait for locks if not HA_OPEN_WAIT_IF_LOCKED is set
*/
int handler::ha_open(TABLE *table_arg, const char *name, int mode,
                     int test_if_locked)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1561 1562
{
  int error;
1563
  DBUG_ENTER("handler::ha_open");
1564 1565
  DBUG_PRINT("enter",
             ("name: %s  db_type: %d  db_stat: %d  mode: %d  lock_test: %d",
1566
              name, ht->db_type, table_arg->db_stat, mode,
1567 1568 1569 1570
              test_if_locked));

  table= table_arg;
  DBUG_ASSERT(table->s == table_share);
1571
  DBUG_ASSERT(alloc_root_inited(&table->mem_root));
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583

  if ((error=open(name,mode,test_if_locked)))
  {
    if ((error == EACCES || error == EROFS) && mode == O_RDWR &&
	(table->db_stat & HA_TRY_READ_ONLY))
    {
      table->db_stat|=HA_READ_ONLY;
      error=open(name,O_RDONLY,test_if_locked);
    }
  }
  if (error)
  {
1584
    my_errno= error;                            /* Safeguard */
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1585 1586 1587 1588
    DBUG_PRINT("error",("error: %d  errno: %d",error,errno));
  }
  else
  {
1589
    if (table->s->db_options_in_use & HA_OPTION_READ_ONLY_DATA)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1590
      table->db_stat|=HA_READ_ONLY;
1591 1592
    (void) extra(HA_EXTRA_NO_READCHECK);	// Not needed in SQL

1593
    if (!(ref= (uchar*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2)))
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1594 1595 1596 1597 1598
    {
      close();
      error=HA_ERR_OUT_OF_MEM;
    }
    else
1599 1600
      dup_ref=ref+ALIGN_SIZE(ref_length);
    cached_table_flags= table_flags();
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1601 1602 1603 1604
  }
  DBUG_RETURN(error);
}

1605

1606 1607 1608
/**
  Read first row (only) from a table.

1609
  This is never called for InnoDB tables, as these table types
1610
  has the HA_STATS_RECORDS_IS_EXACT set.
1611
*/
1612
int handler::read_first_row(uchar * buf, uint primary_key)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1613 1614
{
  register int error;
1615
  DBUG_ENTER("handler::read_first_row");
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1616

antony@ppcg5.local's avatar
antony@ppcg5.local committed
1617
  ha_statistic_increment(&SSV::ha_read_first_count);
1618 1619 1620 1621

  /*
    If there is very few deleted rows in the table, find the first row by
    scanning the table.
1622
    TODO remove the test for HA_READ_ORDER
1623
  */
1624
  if (stats.deleted < 10 || primary_key >= MAX_KEY ||
1625
      !(index_flags(primary_key, 0, 0) & HA_READ_ORDER))
1626
  {
1627
    (void) ha_rnd_init(1);
1628
    while ((error= rnd_next(buf)) == HA_ERR_RECORD_DELETED) ;
1629
    (void) ha_rnd_end();
1630 1631 1632 1633
  }
  else
  {
    /* Find the first row through the primary key */
1634
    (void) ha_index_init(primary_key, 0);
1635
    error=index_first(buf);
1636
    (void) ha_index_end();
1637
  }
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1638 1639 1640
  DBUG_RETURN(error);
}

1641 1642
/**
  Generate the next auto-increment number based on increment and offset.
1643 1644 1645
  computes the lowest number
  - strictly greater than "nr"
  - of the form: auto_increment_offset + N * auto_increment_increment
serg@serg.mylan's avatar
serg@serg.mylan committed
1646

1647
  In most cases increment= offset= 1, in which case we get:
1648 1649 1650
  @verbatim 1,2,3,4,5,... @endverbatim
    If increment=10 and offset=5 and previous number is 1, we get:
  @verbatim 1,5,15,25,35,... @endverbatim
1651 1652
*/
inline ulonglong
1653
compute_next_insert_id(ulonglong nr,struct system_variables *variables)
1654
{
1655 1656
  if (variables->auto_increment_increment == 1)
    return (nr+1); // optimization of the formula below
1657 1658 1659 1660 1661 1662 1663 1664
  nr= (((nr+ variables->auto_increment_increment -
         variables->auto_increment_offset)) /
       (ulonglong) variables->auto_increment_increment);
  return (nr* (ulonglong) variables->auto_increment_increment +
          variables->auto_increment_offset);
}


1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676
void handler::adjust_next_insert_id_after_explicit_value(ulonglong nr)
{
  /*
    If we have set THD::next_insert_id previously and plan to insert an
    explicitely-specified value larger than this, we need to increase
    THD::next_insert_id to be greater than the explicit value.
  */
  if ((next_insert_id > 0) && (nr >= next_insert_id))
    set_next_insert_id(compute_next_insert_id(nr, &table->in_use->variables));
}


1677
/** @brief
1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703
  Computes the largest number X:
  - smaller than or equal to "nr"
  - of the form: auto_increment_offset + N * auto_increment_increment
  where N>=0.

  SYNOPSIS
    prev_insert_id
      nr            Number to "round down"
      variables     variables struct containing auto_increment_increment and
                    auto_increment_offset

  RETURN
    The number X if it exists, "nr" otherwise.
*/
inline ulonglong
prev_insert_id(ulonglong nr, struct system_variables *variables)
{
  if (unlikely(nr < variables->auto_increment_offset))
  {
    /*
      There's nothing good we can do here. That is a pathological case, where
      the offset is larger than the column's max possible value, i.e. not even
      the first sequence value may be inserted. User will receive warning.
    */
    DBUG_PRINT("info",("auto_increment: nr: %lu cannot honour "
                       "auto_increment_offset: %lu",
1704
                       (ulong) nr, variables->auto_increment_offset));
1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715
    return nr;
  }
  if (variables->auto_increment_increment == 1)
    return nr; // optimization of the formula below
  nr= (((nr - variables->auto_increment_offset)) /
       (ulonglong) variables->auto_increment_increment);
  return (nr * (ulonglong) variables->auto_increment_increment +
          variables->auto_increment_offset);
}


1716 1717
/**
  Update the auto_increment field if necessary.
1718

1719
  Updates columns with type NEXT_NUMBER if:
1720 1721 1722 1723 1724 1725 1726

  - If column value is set to NULL (in which case
    auto_increment_field_not_null is 0)
  - If column is set to 0 and (sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO) is not
    set. In the future we will only set NEXT_NUMBER fields if one sets them
    to NULL (or they are not included in the insert list).

1727 1728 1729
    In those cases, we check if the currently reserved interval still has
    values we have not used. If yes, we pick the smallest one and use it.
    Otherwise:
1730

1731 1732 1733
  - If a list of intervals has been provided to the statement via SET
    INSERT_ID or via an Intvar_log_event (in a replication slave), we pick the
    first unused interval from this list, consider it as reserved.
1734

1735 1736 1737 1738 1739 1740
  - Otherwise we set the column for the first row to the value
    next_insert_id(get_auto_increment(column))) which is usually
    max-used-column-value+1.
    We call get_auto_increment() for the first row in a multi-row
    statement. get_auto_increment() will tell us the interval of values it
    reserved for us.
1741

1742 1743 1744 1745 1746
  - In both cases, for the following rows we use those reserved values without
    calling the handler again (we just progress in the interval, computing
    each new value from the previous one). Until we have exhausted them, then
    we either take the next provided interval or call get_auto_increment()
    again to reserve a new interval.
1747

1748 1749 1750 1751
  - In both cases, the reserved intervals are remembered in
    thd->auto_inc_intervals_in_cur_stmt_for_binlog if statement-based
    binlogging; the last reserved interval is remembered in
    auto_inc_interval_for_cur_row.
1752 1753 1754 1755 1756 1757 1758 1759 1760 1761

    The idea is that generated auto_increment values are predictable and
    independent of the column values in the table.  This is needed to be
    able to replicate into a table that already has rows with a higher
    auto-increment value than the one that is inserted.

    After we have already generated an auto-increment number and the user
    inserts a column with a higher value than the last used one, we will
    start counting from the inserted value.

1762 1763 1764 1765 1766 1767 1768
    This function's "outputs" are: the table's auto_increment field is filled
    with a value, thd->next_insert_id is filled with the value to use for the
    next row, if a value was autogenerated for the current row it is stored in
    thd->insert_id_for_cur_row, if get_auto_increment() was called
    thd->auto_inc_interval_for_cur_row is modified, if that interval is not
    present in thd->auto_inc_intervals_in_cur_stmt_for_binlog it is added to
    this list.
1769

1770
  @todo
1771 1772 1773 1774 1775
    Replace all references to "next number" or NEXT_NUMBER to
    "auto_increment", everywhere (see below: there is
    table->auto_increment_field_not_null, and there also exists
    table->next_number_field, it's not consistent).

1776 1777 1778 1779 1780 1781 1782 1783
  @retval
    0	ok
  @retval
    HA_ERR_AUTOINC_READ_FAILED  get_auto_increment() was called and
    returned ~(ulonglong) 0
  @retval
    HA_ERR_AUTOINC_ERANGE storing value in field caused strict mode
    failure.
1784
*/
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1785

1786 1787 1788 1789
#define AUTO_INC_DEFAULT_NB_ROWS 1 // Some prefer 1024 here
#define AUTO_INC_DEFAULT_NB_MAX_BITS 16
#define AUTO_INC_DEFAULT_NB_MAX ((1 << AUTO_INC_DEFAULT_NB_MAX_BITS) - 1)

1790
int handler::update_auto_increment()
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1791
{
1792 1793
  ulonglong nr, nb_reserved_values;
  bool append= FALSE;
1794 1795
  THD *thd= table->in_use;
  struct system_variables *variables= &thd->variables;
1796
  DBUG_ENTER("handler::update_auto_increment");
1797 1798

  /*
1799 1800
    next_insert_id is a "cursor" into the reserved interval, it may go greater
    than the interval, but not smaller.
1801
  */
1802
  DBUG_ASSERT(next_insert_id >= auto_inc_interval_for_cur_row.minimum());
1803 1804

  if ((nr= table->next_number_field->val_int()) != 0 ||
1805
      table->auto_increment_field_not_null &&
1806
      thd->variables.sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO)
1807
  {
1808 1809 1810 1811 1812 1813
    /*
      Update next_insert_id if we had already generated a value in this
      statement (case of INSERT VALUES(null),(3763),(null):
      the last NULL needs to insert 3764, not the value of the first NULL plus
      1).
    */
1814
    adjust_next_insert_id_after_explicit_value(nr);
1815
    insert_id_for_cur_row= 0; // didn't generate anything
1816
    DBUG_RETURN(0);
1817
  }
1818 1819

  if ((nr= next_insert_id) >= auto_inc_interval_for_cur_row.maximum())
1820
  {
1821 1822 1823 1824 1825 1826 1827 1828 1829
    /* next_insert_id is beyond what is reserved, so we reserve more. */
    const Discrete_interval *forced=
      thd->auto_inc_intervals_forced.get_next();
    if (forced != NULL)
    {
      nr= forced->minimum();
      nb_reserved_values= forced->values();
    }
    else
1830
    {
1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852
      /*
        handler::estimation_rows_to_insert was set by
        handler::ha_start_bulk_insert(); if 0 it means "unknown".
      */
      uint nb_already_reserved_intervals=
        thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements();
      ulonglong nb_desired_values;
      /*
        If an estimation was given to the engine:
        - use it.
        - if we already reserved numbers, it means the estimation was
        not accurate, then we'll reserve 2*AUTO_INC_DEFAULT_NB_ROWS the 2nd
        time, twice that the 3rd time etc.
        If no estimation was given, use those increasing defaults from the
        start, starting from AUTO_INC_DEFAULT_NB_ROWS.
        Don't go beyond a max to not reserve "way too much" (because
        reservation means potentially losing unused values).
      */
      if (nb_already_reserved_intervals == 0 &&
          (estimation_rows_to_insert > 0))
        nb_desired_values= estimation_rows_to_insert;
      else /* go with the increasing defaults */
1853
      {
1854 1855 1856 1857 1858 1859 1860 1861 1862
        /* avoid overflow in formula, with this if() */
        if (nb_already_reserved_intervals <= AUTO_INC_DEFAULT_NB_MAX_BITS)
        {
          nb_desired_values= AUTO_INC_DEFAULT_NB_ROWS * 
            (1 << nb_already_reserved_intervals);
          set_if_smaller(nb_desired_values, AUTO_INC_DEFAULT_NB_MAX);
        }
        else
          nb_desired_values= AUTO_INC_DEFAULT_NB_MAX;
1863
      }
1864 1865 1866 1867 1868 1869
      /* This call ignores all its parameters but nr, currently */
      get_auto_increment(variables->auto_increment_offset,
                         variables->auto_increment_increment,
                         nb_desired_values, &nr,
                         &nb_reserved_values);
      if (nr == ~(ulonglong) 0)
1870
        DBUG_RETURN(HA_ERR_AUTOINC_READ_FAILED);  // Mark failure
1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882
      
      /*
        That rounding below should not be needed when all engines actually
        respect offset and increment in get_auto_increment(). But they don't
        so we still do it. Wonder if for the not-first-in-index we should do
        it. Hope that this rounding didn't push us out of the interval; even
        if it did we cannot do anything about it (calling the engine again
        will not help as we inserted no row).
      */
      nr= compute_next_insert_id(nr-1, variables);
    }
    
1883
    if (table->s->next_number_keypart == 0)
1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896
    {
      /* We must defer the appending until "nr" has been possibly truncated */
      append= TRUE;
    }
    else
    {
      /*
        For such auto_increment there is no notion of interval, just a
        singleton. The interval is not even stored in
        thd->auto_inc_interval_for_cur_row, so we are sure to call the engine
        for next row.
      */
      DBUG_PRINT("info",("auto_increment: special not-first-in-index"));
1897
    }
1898 1899 1900 1901
  }

  DBUG_PRINT("info",("auto_increment: %lu", (ulong) nr));

1902
  if (unlikely(table->next_number_field->store((longlong) nr, TRUE)))
1903
  {
1904
    /*
1905 1906 1907 1908 1909
      first test if the query was aborted due to strict mode constraints
    */
    if (thd->killed == THD::KILL_BAD_DATA)
      DBUG_RETURN(HA_ERR_AUTOINC_ERANGE);

1910
    /*
1911
      field refused this value (overflow) and truncated it, use the result of
1912 1913
      the truncation (which is going to be inserted); however we try to
      decrease it to honour auto_increment_* variables.
1914 1915 1916
      That will shift the left bound of the reserved interval, we don't
      bother shifting the right bound (anyway any other value from this
      interval will cause a duplicate key).
1917
    */
1918 1919 1920
    nr= prev_insert_id(table->next_number_field->val_int(), variables);
    if (unlikely(table->next_number_field->store((longlong) nr, TRUE)))
      nr= table->next_number_field->val_int();
1921 1922 1923 1924 1925 1926 1927 1928 1929 1930
  }
  if (append)
  {
    auto_inc_interval_for_cur_row.replace(nr, nb_reserved_values,
                                          variables->auto_increment_increment);
    /* Row-based replication does not need to store intervals in binlog */
    if (!thd->current_stmt_binlog_row_based)
        thd->auto_inc_intervals_in_cur_stmt_for_binlog.append(auto_inc_interval_for_cur_row.minimum(),
                                                              auto_inc_interval_for_cur_row.values(),
                                                              variables->auto_increment_increment);
1931
  }
1932

1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945
  /*
    Record this autogenerated value. If the caller then
    succeeds to insert this value, it will call
    record_first_successful_insert_id_in_cur_stmt()
    which will set first_successful_insert_id_in_cur_stmt if it's not
    already set.
  */
  insert_id_for_cur_row= nr;
  /*
    Set next insert id to point to next auto-increment value to be able to
    handle multi-row statements.
  */
  set_next_insert_id(compute_next_insert_id(nr, variables));
1946

1947
  DBUG_RETURN(0);
1948 1949
}

bk@work.mysql.com's avatar
bk@work.mysql.com committed
1950

1951
/** @brief
1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966
  MySQL signal that it changed the column bitmap

  USAGE
    This is for handlers that needs to setup their own column bitmaps.
    Normally the handler should set up their own column bitmaps in
    index_init() or rnd_init() and in any column_bitmaps_signal() call after
    this.

    The handler is allowd to do changes to the bitmap after a index_init or
    rnd_init() call is made as after this, MySQL will not use the bitmap
    for any program logic checking.
*/
void handler::column_bitmaps_signal()
{
  DBUG_ENTER("column_bitmaps_signal");
1967 1968
  DBUG_PRINT("info", ("read_set: 0x%lx  write_set: 0x%lx", (long) table->read_set,
                      (long) table->write_set));
1969 1970 1971 1972
  DBUG_VOID_RETURN;
}


1973
/** @brief
1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993
  Reserves an interval of auto_increment values from the handler.

  SYNOPSIS
    get_auto_increment()
    offset              
    increment
    nb_desired_values   how many values we want
    first_value         (OUT) the first value reserved by the handler
    nb_reserved_values  (OUT) how many values the handler reserved

  offset and increment means that we want values to be of the form
  offset + N * increment, where N>=0 is integer.
  If the function sets *first_value to ~(ulonglong)0 it means an error.
  If the function sets *nb_reserved_values to ULONGLONG_MAX it means it has
  reserved to "positive infinite".
*/
void handler::get_auto_increment(ulonglong offset, ulonglong increment,
                                 ulonglong nb_desired_values,
                                 ulonglong *first_value,
                                 ulonglong *nb_reserved_values)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1994
{
1995
  ulonglong nr;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
1996
  int error;
1997

bk@work.mysql.com's avatar
bk@work.mysql.com committed
1998
  (void) extra(HA_EXTRA_KEYREAD);
1999 2000 2001
  table->mark_columns_used_by_index_no_reset(table->s->next_number_index,
                                        table->read_set);
  column_bitmaps_signal();
2002
  index_init(table->s->next_number_index, 1);
2003
  if (table->s->next_number_keypart == 0)
2004 2005
  {						// Autoincrement at key-start
    error=index_last(table->record[1]);
2006 2007 2008 2009 2010 2011
    /*
      MySQL implicitely assumes such method does locking (as MySQL decides to
      use nr+increment without checking again with the handler, in
      handler::update_auto_increment()), so reserves to infinite.
    */
    *nb_reserved_values= ULONGLONG_MAX;
2012 2013 2014
  }
  else
  {
2015
    uchar key[MAX_KEY_LENGTH];
2016
    key_copy(key, table->record[0],
2017 2018
             table->key_info + table->s->next_number_index,
             table->s->next_number_key_offset);
2019 2020 2021
    error= index_read_map(table->record[1], key,
                          make_prev_keypart_map(table->s->next_number_keypart),
                          HA_READ_PREFIX_LAST);
2022 2023 2024 2025 2026 2027 2028
    /*
      MySQL needs to call us for next row: assume we are inserting ("a",null)
      here, we return 3, and next this statement will want to insert
      ("b",null): there is no reason why ("b",3+1) would be the good row to
      insert: maybe it already exists, maybe 3+1 is too large...
    */
    *nb_reserved_values= 1;
2029 2030
  }

bk@work.mysql.com's avatar
bk@work.mysql.com committed
2031 2032 2033
  if (error)
    nr=1;
  else
2034 2035
    nr= ((ulonglong) table->next_number_field->
         val_int_offset(table->s->rec_buff_length)+1);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2036
  index_end();
2037
  (void) extra(HA_EXTRA_NO_KEYREAD);
2038
  *first_value= nr;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2039 2040
}

2041

2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058
void handler::ha_release_auto_increment()
{
  release_auto_increment();
  insert_id_for_cur_row= 0;
  auto_inc_interval_for_cur_row.replace(0, 0, 0);
  if (next_insert_id > 0)
  {
    next_insert_id= 0;
    /*
      this statement used forced auto_increment values if there were some,
      wipe them away for other statements.
    */
    table->in_use->auto_inc_intervals_forced.empty();
  }
}


2059
void handler::print_keydup_error(uint key_nr, const char *msg)
2060 2061 2062 2063
{
  /* Write the duplicated key in the error message */
  char key[MAX_KEY_LENGTH];
  String str(key,sizeof(key),system_charset_info);
2064 2065 2066 2067

  if (key_nr == MAX_KEY)
  {
    /* Key is unknown */
2068
    str.copy("", 0, system_charset_info);
2069
    my_printf_error(ER_DUP_ENTRY, msg, MYF(0), str.c_ptr(), "*UNKNOWN*");
2070 2071
  }
  else
2072
  {
2073 2074 2075 2076 2077 2078 2079 2080
    /* Table is opened and defined at this point */
    key_unpack(&str,table,(uint) key_nr);
    uint max_length=MYSQL_ERRMSG_SIZE-(uint) strlen(msg);
    if (str.length() >= max_length)
    {
      str.length(max_length-4);
      str.append(STRING_WITH_LEN("..."));
    }
2081
    my_printf_error(ER_DUP_ENTRY, msg,
2082
		    MYF(0), str.c_ptr(), table->key_info[key_nr].name);
2083 2084 2085 2086
  }
}


2087 2088
/**
  Print error that we got from handler function.
2089

2090 2091 2092 2093 2094
  @note
    In case of delete table it's only safe to use the following parts of
    the 'table' structure:
    - table->s->path
    - table->alias
2095
*/
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2096 2097
void handler::print_error(int error, myf errflag)
{
2098
  DBUG_ENTER("handler::print_error");
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2099 2100 2101 2102
  DBUG_PRINT("enter",("error: %d",error));

  int textno=ER_GET_ERRNO;
  switch (error) {
2103 2104 2105
  case EACCES:
    textno=ER_OPEN_AS_READONLY;
    break;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116
  case EAGAIN:
    textno=ER_FILE_USED;
    break;
  case ENOENT:
    textno=ER_FILE_NOT_FOUND;
    break;
  case HA_ERR_KEY_NOT_FOUND:
  case HA_ERR_NO_ACTIVE_RECORD:
  case HA_ERR_END_OF_FILE:
    textno=ER_KEY_NOT_FOUND;
    break;
2117
  case HA_ERR_WRONG_MRG_TABLE_DEF:
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2118 2119 2120 2121 2122 2123 2124
    textno=ER_WRONG_MRG_TABLE;
    break;
  case HA_ERR_FOUND_DUPP_KEY:
  {
    uint key_nr=get_dup_key(error);
    if ((int) key_nr >= 0)
    {
2125
      print_keydup_error(key_nr, ER(ER_DUP_ENTRY_WITH_KEY_NAME));
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2126 2127 2128 2129 2130
      DBUG_VOID_RETURN;
    }
    textno=ER_DUP_KEY;
    break;
  }
2131 2132 2133 2134 2135
  case HA_ERR_FOREIGN_DUPLICATE_KEY:
  {
    uint key_nr= get_dup_key(error);
    if ((int) key_nr >= 0)
    {
2136
      uint max_length;
2137 2138 2139 2140 2141
      /* Write the key in the error message */
      char key[MAX_KEY_LENGTH];
      String str(key,sizeof(key),system_charset_info);
      /* Table is opened and defined at this point */
      key_unpack(&str,table,(uint) key_nr);
2142 2143
      max_length= (MYSQL_ERRMSG_SIZE-
                   (uint) strlen(ER(ER_FOREIGN_DUPLICATE_KEY)));
2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155
      if (str.length() >= max_length)
      {
        str.length(max_length-4);
        str.append(STRING_WITH_LEN("..."));
      }
      my_error(ER_FOREIGN_DUPLICATE_KEY, MYF(0), table_share->table_name.str,
        str.c_ptr(), key_nr+1);
      DBUG_VOID_RETURN;
    }
    textno= ER_DUP_KEY;
    break;
  }
2156
  case HA_ERR_NULL_IN_SPATIAL:
2157 2158
    my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0));
    DBUG_VOID_RETURN;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2159 2160 2161 2162 2163 2164 2165 2166 2167
  case HA_ERR_FOUND_DUPP_UNIQUE:
    textno=ER_DUP_UNIQUE;
    break;
  case HA_ERR_RECORD_CHANGED:
    textno=ER_CHECKREAD;
    break;
  case HA_ERR_CRASHED:
    textno=ER_NOT_KEYFILE;
    break;
2168 2169 2170
  case HA_ERR_WRONG_IN_RECORD:
    textno= ER_CRASHED_ON_USAGE;
    break;
2171 2172 2173
  case HA_ERR_CRASHED_ON_USAGE:
    textno=ER_CRASHED_ON_USAGE;
    break;
2174 2175 2176
  case HA_ERR_NOT_A_TABLE:
    textno= error;
    break;
2177 2178 2179
  case HA_ERR_CRASHED_ON_REPAIR:
    textno=ER_CRASHED_ON_REPAIR;
    break;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2180
  case HA_ERR_OUT_OF_MEM:
2181 2182
    textno=ER_OUT_OF_RESOURCES;
    break;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2183 2184 2185 2186 2187 2188 2189 2190 2191 2192
  case HA_ERR_WRONG_COMMAND:
    textno=ER_ILLEGAL_HA;
    break;
  case HA_ERR_OLD_FILE:
    textno=ER_OLD_KEYFILE;
    break;
  case HA_ERR_UNSUPPORTED:
    textno=ER_UNSUPPORTED_EXTENSION;
    break;
  case HA_ERR_RECORD_FILE_FULL:
2193
  case HA_ERR_INDEX_FILE_FULL:
2194
    textno=ER_RECORD_FILE_FULL;
2195
    break;
2196 2197 2198 2199 2200 2201
  case HA_ERR_LOCK_WAIT_TIMEOUT:
    textno=ER_LOCK_WAIT_TIMEOUT;
    break;
  case HA_ERR_LOCK_TABLE_FULL:
    textno=ER_LOCK_TABLE_FULL;
    break;
2202 2203 2204
  case HA_ERR_LOCK_DEADLOCK:
    textno=ER_LOCK_DEADLOCK;
    break;
2205 2206 2207
  case HA_ERR_READ_ONLY_TRANSACTION:
    textno=ER_READ_ONLY_TRANSACTION;
    break;
monty@donna.mysql.fi's avatar
Merge  
monty@donna.mysql.fi committed
2208 2209 2210 2211
  case HA_ERR_CANNOT_ADD_FOREIGN:
    textno=ER_CANNOT_ADD_FOREIGN;
    break;
  case HA_ERR_ROW_IS_REFERENCED:
2212 2213 2214 2215 2216 2217
  {
    String str;
    get_error_message(error, &str);
    my_error(ER_ROW_IS_REFERENCED_2, MYF(0), str.c_ptr_safe());
    DBUG_VOID_RETURN;
  }
monty@donna.mysql.fi's avatar
Merge  
monty@donna.mysql.fi committed
2218
  case HA_ERR_NO_REFERENCED_ROW:
2219 2220 2221 2222 2223 2224
  {
    String str;
    get_error_message(error, &str);
    my_error(ER_NO_REFERENCED_ROW_2, MYF(0), str.c_ptr_safe());
    DBUG_VOID_RETURN;
  }
2225 2226 2227
  case HA_ERR_TABLE_DEF_CHANGED:
    textno=ER_TABLE_DEF_CHANGED;
    break;
2228
  case HA_ERR_NO_SUCH_TABLE:
2229 2230
    my_error(ER_NO_SUCH_TABLE, MYF(0), table_share->db.str,
             table_share->table_name.str);
2231
    DBUG_VOID_RETURN;
2232 2233 2234
  case HA_ERR_RBR_LOGGING_FAILED:
    textno= ER_BINLOG_ROW_LOGGING_FAILED;
    break;
2235 2236 2237 2238 2239 2240 2241 2242 2243
  case HA_ERR_DROP_INDEX_FK:
  {
    const char *ptr= "???";
    uint key_nr= get_dup_key(error);
    if ((int) key_nr >= 0)
      ptr= table->key_info[key_nr].name;
    my_error(ER_DROP_INDEX_FK, MYF(0), ptr);
    DBUG_VOID_RETURN;
  }
2244 2245 2246
  case HA_ERR_TABLE_NEEDS_UPGRADE:
    textno=ER_TABLE_NEEDS_UPGRADE;
    break;
2247 2248 2249
  case HA_ERR_TABLE_READONLY:
    textno= ER_OPEN_AS_READONLY;
    break;
2250 2251 2252 2253 2254 2255
  case HA_ERR_AUTOINC_READ_FAILED:
    textno= ER_AUTOINC_READ_FAILED;
    break;
  case HA_ERR_AUTOINC_ERANGE:
    textno= ER_WARN_DATA_OUT_OF_RANGE;
    break;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2256 2257
  default:
    {
2258 2259 2260
      /* The error was "unknown" to this function.
	 Ask handler if it has got a message for this error */
      bool temporary= FALSE;
2261 2262 2263
      String str;
      temporary= get_error_message(error, &str);
      if (!str.is_empty())
2264
      {
2265
	const char* engine= table_type();
2266
	if (temporary)
2267
	  my_error(ER_GET_TEMPORARY_ERRMSG, MYF(0), error, str.ptr(), engine);
2268
	else
2269
	  my_error(ER_GET_ERRMSG, MYF(0), error, str.ptr(), engine);
2270
      }
2271
      else
2272
	my_error(ER_GET_ERRNO,errflag,error);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2273 2274 2275
      DBUG_VOID_RETURN;
    }
  }
2276
  my_error(textno, errflag, table_share->table_name.str, error);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2277 2278 2279
  DBUG_VOID_RETURN;
}

2280

2281 2282
/**
  Return an error message specific to this handler.
2283

2284 2285
  @param error  error code previously returned by handler
  @param buf    pointer to String where to add error message
serg@serg.mylan's avatar
serg@serg.mylan committed
2286

2287 2288
  @return
    Returns true if this is a temporary error
2289
*/
2290
bool handler::get_error_message(int error, String* buf)
2291
{
2292
  return FALSE;
2293 2294 2295
}


2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314
int handler::ha_check_for_upgrade(HA_CHECK_OPT *check_opt)
{
  KEY *keyinfo, *keyend;
  KEY_PART_INFO *keypart, *keypartend;

  if (!table->s->mysql_version)
  {
    /* check for blob-in-key error */
    keyinfo= table->key_info;
    keyend= table->key_info + table->s->keys;
    for (; keyinfo < keyend; keyinfo++)
    {
      keypart= keyinfo->key_part;
      keypartend= keypart + keyinfo->key_parts;
      for (; keypart < keypartend; keypart++)
      {
        if (!keypart->fieldnr)
          continue;
        Field *field= table->field[keypart->fieldnr-1];
2315
        if (field->type() == MYSQL_TYPE_BLOB)
2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336
        {
          if (check_opt->sql_flags & TT_FOR_UPGRADE)
            check_opt->flags= T_MEDIUM;
          return HA_ADMIN_NEEDS_CHECK;
        }
      }
    }
  }
  return check_for_upgrade(check_opt);
}


int handler::check_old_types()
{
  Field** field;

  if (!table->s->mysql_version)
  {
    /* check for bad DECIMAL field */
    for (field= table->field; (*field); field++)
    {
2337
      if ((*field)->type() == MYSQL_TYPE_NEWDECIMAL)
2338 2339 2340
      {
        return HA_ADMIN_NEEDS_ALTER;
      }
2341 2342 2343 2344
      if ((*field)->type() == MYSQL_TYPE_VAR_STRING)
      {
        return HA_ADMIN_NEEDS_ALTER;
      }
2345 2346 2347 2348 2349 2350
    }
  }
  return 0;
}


2351
static bool update_frm_version(TABLE *table)
2352 2353 2354 2355 2356 2357
{
  char path[FN_REFLEN];
  File file;
  int result= 1;
  DBUG_ENTER("update_frm_version");

2358 2359 2360 2361 2362 2363 2364
  /*
    No need to update frm version in case table was created or checked
    by server with the same version. This also ensures that we do not
    update frm version for temporary tables as this code doesn't support
    temporary tables.
  */
  if (table->s->mysql_version == MYSQL_VERSION_ID)
2365 2366
    DBUG_RETURN(0);

2367
  strxmov(path, table->s->normalized_path.str, reg_ext, NullS);
2368 2369 2370 2371

  if ((file= my_open(path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0)
  {
    uchar version[4];
2372 2373
    char *key= table->s->table_cache_key.str;
    uint key_length= table->s->table_cache_key.length;
2374 2375 2376 2377 2378
    TABLE *entry;
    HASH_SEARCH_STATE state;

    int4store(version, MYSQL_VERSION_ID);

2379
    if ((result= my_pwrite(file,(uchar*) version,4,51L,MYF_RW)))
2380 2381
      goto err;

2382
    for (entry=(TABLE*) hash_first(&open_cache,(uchar*) key,key_length, &state);
2383
         entry;
2384
         entry= (TABLE*) hash_next(&open_cache,(uchar*) key,key_length, &state))
2385 2386 2387 2388 2389 2390 2391 2392 2393 2394
      entry->s->mysql_version= MYSQL_VERSION_ID;
  }
err:
  if (file >= 0)
    VOID(my_close(file,MYF(MY_WME)));
  DBUG_RETURN(result);
}



2395 2396 2397 2398
/**
  @return
    key if error because of duplicated keys
*/
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2399 2400
uint handler::get_dup_key(int error)
{
2401
  DBUG_ENTER("handler::get_dup_key");
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2402
  table->file->errkey  = (uint) -1;
2403 2404 2405
  if (error == HA_ERR_FOUND_DUPP_KEY || error == HA_ERR_FOREIGN_DUPLICATE_KEY ||
      error == HA_ERR_FOUND_DUPP_UNIQUE || error == HA_ERR_NULL_IN_SPATIAL ||
      error == HA_ERR_DROP_INDEX_FK)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2406 2407 2408 2409
    info(HA_STATUS_ERRKEY | HA_STATUS_NO_LOCK);
  DBUG_RETURN(table->file->errkey);
}

2410

2411 2412
/**
  Delete all files with extension from bas_ext().
2413

2414
  @param name		Base name of table
2415

2416
  @note
2417 2418 2419
    We assume that the handler may return more extensions than
    was actually used for the file.

2420
  @retval
2421
    0   If we successfully deleted at least one file from base_ext and
2422 2423 2424
    didn't get any other errors than ENOENT
  @retval
    !0  Error
2425
*/
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2426 2427
int handler::delete_table(const char *name)
{
2428 2429
  int error= 0;
  int enoent_or_zero= ENOENT;                   // Error if no file was deleted
2430
  char buff[FN_REFLEN];
2431

bk@work.mysql.com's avatar
bk@work.mysql.com committed
2432 2433
  for (const char **ext=bas_ext(); *ext ; ext++)
  {
2434
    fn_format(buff, name, "", *ext, MY_UNPACK_FILENAME|MY_APPEND_EXT);
2435
    if (my_delete_with_symlink(buff, MYF(0)))
2436
    {
2437
      if ((error= my_errno) != ENOENT)
2438 2439
	break;
    }
2440
    else
2441
      enoent_or_zero= 0;                        // No error for ENOENT
2442
    error= enoent_or_zero;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2443
  }
2444
  return error;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2445 2446 2447 2448 2449
}


int handler::rename_table(const char * from, const char * to)
{
2450 2451
  int error= 0;
  for (const char **ext= bas_ext(); *ext ; ext++)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2452
  {
2453 2454 2455 2456 2457 2458
    if (rename_file_ext(from, to, *ext))
    {
      if ((error=my_errno) != ENOENT)
	break;
      error= 0;
    }
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2459
  }
2460
  return error;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2461 2462
}

2463 2464 2465 2466 2467 2468 2469 2470

void handler::drop_table(const char *name)
{
  close();
  delete_table(name);
}


2471 2472
/**
  Performs checks upon the table.
2473

2474 2475
  @param thd                thread doing CHECK TABLE operation
  @param check_opt          options from the parser
2476

2477 2478 2479 2480 2481 2482 2483 2484
  @retval
    HA_ADMIN_OK               Successful upgrade
  @retval
    HA_ADMIN_NEEDS_UPGRADE    Table has structures requiring upgrade
  @retval
    HA_ADMIN_NEEDS_ALTER      Table has structures requiring ALTER TABLE
  @retval
    HA_ADMIN_NOT_IMPLEMENTED
2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505
*/
int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt)
{
  int error;

  if ((table->s->mysql_version >= MYSQL_VERSION_ID) &&
      (check_opt->sql_flags & TT_FOR_UPGRADE))
    return 0;

  if (table->s->mysql_version < MYSQL_VERSION_ID)
  {
    if ((error= check_old_types()))
      return error;
    error= ha_check_for_upgrade(check_opt);
    if (error && (error != HA_ADMIN_NEEDS_CHECK))
      return error;
    if (!error && (check_opt->sql_flags & TT_FOR_UPGRADE))
      return 0;
  }
  if ((error= check(thd, check_opt)))
    return error;
2506
  return update_frm_version(table);
2507 2508 2509
}


2510 2511 2512 2513 2514 2515
/**
  Repair table: public interface.

  @sa handler::repair()
*/

2516 2517 2518 2519 2520
int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt)
{
  int result;
  if ((result= repair(thd, check_opt)))
    return result;
2521
  return update_frm_version(table);
2522 2523 2524
}


2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846
/**
  Bulk update row: public interface.

  @sa handler::bulk_update_row()
*/

int
handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data,
                            uint *dup_key_found)
{
  return bulk_update_row(old_data, new_data, dup_key_found);
}


/**
  Delete all rows: public interface.

  @sa handler::delete_all_rows()
*/

int
handler::ha_delete_all_rows()
{
  return delete_all_rows();
}


/**
  Reset auto increment: public interface.

  @sa handler::reset_auto_increment()
*/

int
handler::ha_reset_auto_increment(ulonglong value)
{
  return reset_auto_increment(value);
}


/**
  Backup table: public interface.

  @sa handler::backup()
*/

int
handler::ha_backup(THD* thd, HA_CHECK_OPT* check_opt)
{
  return backup(thd, check_opt);
}


/**
  Restore table: public interface.

  @sa handler::restore()
*/

int
handler::ha_restore(THD* thd, HA_CHECK_OPT* check_opt)
{
  return restore(thd, check_opt);
}


/**
  Optimize table: public interface.

  @sa handler::optimize()
*/

int
handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt)
{
  return optimize(thd, check_opt);
}


/**
  Analyze table: public interface.

  @sa handler::analyze()
*/

int
handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt)
{
  return analyze(thd, check_opt);
}


/**
  Check and repair table: public interface.

  @sa handler::check_and_repair()
*/

bool
handler::ha_check_and_repair(THD *thd)
{
  return check_and_repair(thd);
}


/**
  Disable indexes: public interface.

  @sa handler::disable_indexes()
*/

int
handler::ha_disable_indexes(uint mode)
{
  return disable_indexes(mode);
}


/**
  Enable indexes: public interface.

  @sa handler::enable_indexes()
*/

int
handler::ha_enable_indexes(uint mode)
{
  return enable_indexes(mode);
}


/**
  Discard or import tablespace: public interface.

  @sa handler::discard_or_import_tablespace()
*/

int
handler::ha_discard_or_import_tablespace(my_bool discard)
{
  return discard_or_import_tablespace(discard);
}


/**
  Prepare for alter: public interface.

  Called to prepare an *online* ALTER.

  @sa handler::prepare_for_alter()
*/

void
handler::ha_prepare_for_alter()
{
  prepare_for_alter();
}


/**
  Rename table: public interface.

  @sa handler::rename_table()
*/

int
handler::ha_rename_table(const char *from, const char *to)
{
  return rename_table(from, to);
}


/**
  Delete table: public interface.

  @sa handler::delete_table()
*/

int
handler::ha_delete_table(const char *name)
{
  return delete_table(name);
}


/**
  Drop table in the engine: public interface.

  @sa handler::drop_table()
*/

void
handler::ha_drop_table(const char *name)
{
  return drop_table(name);
}


/**
  Create a table in the engine: public interface.

  @sa handler::create()
*/

int
handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info)
{
  return create(name, form, info);
}


/**
  Create handler files for CREATE TABLE: public interface.

  @sa handler::create_handler_files()
*/

int
handler::ha_create_handler_files(const char *name, const char *old_name,
                        int action_flag, HA_CREATE_INFO *info)
{
  return create_handler_files(name, old_name, action_flag, info);
}


/**
  Change partitions: public interface.

  @sa handler::change_partitions()
*/

int
handler::ha_change_partitions(HA_CREATE_INFO *create_info,
                     const char *path,
                     ulonglong *copied,
                     ulonglong *deleted,
                     const uchar *pack_frm_data,
                     size_t pack_frm_len)
{
  return change_partitions(create_info, path, copied, deleted,
                           pack_frm_data, pack_frm_len);
}


/**
  Drop partitions: public interface.

  @sa handler::drop_partitions()
*/

int
handler::ha_drop_partitions(const char *path)
{
  return drop_partitions(path);
}


/**
  Rename partitions: public interface.

  @sa handler::rename_partitions()
*/

int
handler::ha_rename_partitions(const char *path)
{
  return rename_partitions(path);
}


/**
  Optimize partitions: public interface.

  @sa handler::optimize_partitions()
*/

int
handler::ha_optimize_partitions(THD *thd)
{
  return optimize_partitions(thd);
}


/**
  Analyze partitions: public interface.

  @sa handler::analyze_partitions()
*/

int
handler::ha_analyze_partitions(THD *thd)
{
  return analyze_partitions(thd);
}


/**
  Check partitions: public interface.

  @sa handler::check_partitions()
*/

int
handler::ha_check_partitions(THD *thd)
{
  return check_partitions(thd);
}


/**
  Repair partitions: public interface.

  @sa handler::repair_partitions()
*/

int
handler::ha_repair_partitions(THD *thd)
{
  return repair_partitions(thd);
}


2847
/**
2848 2849 2850 2851 2852 2853
  Tell the storage engine that it is allowed to "disable transaction" in the
  handler. It is a hint that ACID is not required - it is used in NDB for
  ALTER TABLE, for example, when data are copied to temporary table.
  A storage engine may treat this hint any way it likes. NDB for example
  starts to commit every now and then automatically.
  This hint can be safely ignored.
2854
*/
2855
int ha_enable_transaction(THD *thd, bool on)
2856 2857 2858
{
  int error=0;

2859 2860
  DBUG_ENTER("ha_enable_transaction");
  thd->transaction.on= on;
serg@serg.mylan's avatar
serg@serg.mylan committed
2861
  if (on)
2862 2863 2864 2865 2866 2867 2868
  {
    /*
      Now all storage engines should have transaction handling enabled.
      But some may have it enabled all the time - "disabling" transactions
      is an optimization hint that storage engine is free to ignore.
      So, let's commit an open transaction (if any) now.
    */
2869 2870
    if (!(error= ha_commit_stmt(thd)))
      error= end_trans(thd, COMMIT);
2871
  }
2872 2873 2874
  DBUG_RETURN(error);
}

2875
int handler::index_next_same(uchar *buf, const uchar *key, uint keylen)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2876 2877
{
  int error;
2878
  DBUG_ENTER("index_next_same");
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2879 2880
  if (!(error=index_next(buf)))
  {
2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912
    my_ptrdiff_t ptrdiff= buf - table->record[0];
    uchar *save_record_0;
    KEY *key_info;
    KEY_PART_INFO *key_part;
    KEY_PART_INFO *key_part_end;
    LINT_INIT(save_record_0);
    LINT_INIT(key_info);
    LINT_INIT(key_part);
    LINT_INIT(key_part_end);

    /*
      key_cmp_if_same() compares table->record[0] against 'key'.
      In parts it uses table->record[0] directly, in parts it uses
      field objects with their local pointers into table->record[0].
      If 'buf' is distinct from table->record[0], we need to move
      all record references. This is table->record[0] itself and
      the field pointers of the fields used in this key.
    */
    if (ptrdiff)
    {
      save_record_0= table->record[0];
      table->record[0]= buf;
      key_info= table->key_info + active_index;
      key_part= key_info->key_part;
      key_part_end= key_part + key_info->key_parts;
      for (; key_part < key_part_end; key_part++)
      {
        DBUG_ASSERT(key_part->field);
        key_part->field->move_field_offset(ptrdiff);
      }
    }

2913
    if (key_cmp_if_same(table, key, active_index, keylen))
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2914 2915 2916 2917
    {
      table->status=STATUS_NOT_FOUND;
      error=HA_ERR_END_OF_FILE;
    }
2918 2919 2920 2921 2922 2923 2924 2925

    /* Move back if necessary. */
    if (ptrdiff)
    {
      table->record[0]= save_record_0;
      for (key_part= key_info->key_part; key_part < key_part_end; key_part++)
        key_part->field->move_field_offset(-ptrdiff);
    }
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2926
  }
2927
  DBUG_RETURN(error);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2928 2929 2930
}


2931 2932
void handler::get_dynamic_partition_info(PARTITION_INFO *stat_info,
                                         uint part_id)
2933 2934 2935
{
  info(HA_STATUS_CONST | HA_STATUS_TIME | HA_STATUS_VARIABLE |
       HA_STATUS_NO_LOCK);
2936 2937 2938 2939 2940 2941 2942 2943 2944 2945
  stat_info->records=              stats.records;
  stat_info->mean_rec_length=      stats.mean_rec_length;
  stat_info->data_file_length=     stats.data_file_length;
  stat_info->max_data_file_length= stats.max_data_file_length;
  stat_info->index_file_length=    stats.index_file_length;
  stat_info->delete_length=        stats.delete_length;
  stat_info->create_time=          stats.create_time;
  stat_info->update_time=          stats.update_time;
  stat_info->check_time=           stats.check_time;
  stat_info->check_sum=            0;
2946
  if (table_flags() & (ulong) HA_HAS_CHECKSUM)
2947
    stat_info->check_sum= checksum();
2948 2949 2950 2951
  return;
}


bk@work.mysql.com's avatar
bk@work.mysql.com committed
2952 2953 2954 2955
/****************************************************************************
** Some general functions that isn't in the handler class
****************************************************************************/

2956 2957
/**
  Initiates table-file and calls appropriate database-creator.
2958

2959
  @retval
2960
   0  ok
2961
  @retval
2962
   1  error
2963
*/
2964 2965 2966
int ha_create_table(THD *thd, const char *path,
                    const char *db, const char *table_name,
                    HA_CREATE_INFO *create_info,
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2967 2968
		    bool update_create_info)
{
2969
  int error= 1;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2970
  TABLE table;
2971
  char name_buff[FN_REFLEN];
2972 2973
  const char *name;
  TABLE_SHARE share;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2974
  DBUG_ENTER("ha_create_table");
2975
  
2976
  init_tmp_table_share(thd, &share, db, 0, table_name, path);
2977
  if (open_table_def(thd, &share, 0) ||
2978 2979
      open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, 0, &table,
                            TRUE))
2980
    goto err;
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2981 2982 2983

  if (update_create_info)
    update_create_info_from_table(create_info, &table);
2984

2985
  name= check_lowercase_names(table.file, share.path.str, name_buff);
2986

2987
  error= table.file->ha_create(name, &table, create_info);
2988
  VOID(closefrm(&table, 0));
2989
  if (error)
2990 2991 2992 2993 2994 2995
  {
    strxmov(name_buff, db, ".", table_name, NullS);
    my_error(ER_CANT_CREATE_TABLE, MYF(ME_BELL+ME_WAITTANG), name_buff, error);
  }
err:
  free_table_share(&share);
bk@work.mysql.com's avatar
bk@work.mysql.com committed
2996 2997 2998
  DBUG_RETURN(error != 0);
}

2999 3000
/**
  Try to discover table from engine.
3001

3002
  @note
3003
    If found, write the frm file to disk.
3004

3005
  @retval
3006
  -1    Table did not exists
3007
  @retval
3008
   0    Table created ok
3009
  @retval
3010
   > 0  Error, table existed but could not be created
3011
*/
3012
int ha_create_table_from_engine(THD* thd, const char *db, const char *name)
3013
{
3014
  int error;
3015 3016
  uchar *frmblob;
  size_t frmlen;
3017 3018 3019
  char path[FN_REFLEN];
  HA_CREATE_INFO create_info;
  TABLE table;
3020
  TABLE_SHARE share;
3021
  DBUG_ENTER("ha_create_table_from_engine");
monty@mishka.local's avatar
monty@mishka.local committed
3022
  DBUG_PRINT("enter", ("name '%s'.'%s'", db, name));
3023

3024
  bzero((uchar*) &create_info,sizeof(create_info));
3025
  if ((error= ha_discover(thd, db, name, &frmblob, &frmlen)))
3026
  {
monty@mishka.local's avatar
monty@mishka.local committed
3027
    /* Table could not be discovered and thus not created */
3028
    DBUG_RETURN(error);
3029 3030
  }

3031
  /*
3032 3033
    Table exists in handler and could be discovered
    frmblob and frmlen are set, write the frm to disk
3034
  */
3035

3036
  build_table_filename(path, FN_REFLEN-1, db, name, "", 0);
3037
  // Save the frm file
monty@mishka.local's avatar
monty@mishka.local committed
3038
  error= writefrm(path, frmblob, frmlen);
3039
  my_free(frmblob, MYF(0));
monty@mishka.local's avatar
monty@mishka.local committed
3040
  if (error)
3041
    DBUG_RETURN(2);
3042

3043
  init_tmp_table_share(thd, &share, db, 0, name, path);
3044 3045 3046 3047
  if (open_table_def(thd, &share, 0))
  {
    DBUG_RETURN(3);
  }
3048
  if (open_table_from_share(thd, &share, "" ,0, 0, 0, &table, FALSE))
3049 3050
  {
    free_table_share(&share);
3051
    DBUG_RETURN(3);
3052
  }
3053

3054
  update_create_info_from_table(&create_info, &table);
3055
  create_info.table_options|= HA_OPTION_CREATE_FROM_ENGINE;
3056

3057
  check_lowercase_names(table.file, path, path);
3058
  error=table.file->ha_create(path, &table, &create_info);
3059
  VOID(closefrm(&table, 1));
3060

3061
  DBUG_RETURN(error != 0);
3062 3063
}

3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080
void st_ha_check_opt::init()
{
  flags= sql_flags= 0;
  sort_buffer_size = current_thd->variables.myisam_sort_buff_size;
}


/*****************************************************************************
  Key cache handling.

  This code is only relevant for ISAM/MyISAM tables

  key_cache->cache may be 0 only in the case where a key cache is not
  initialized or when we where not able to init the key cache in a previous
  call to ha_init_key_cache() (probably out of memory)
*****************************************************************************/

3081 3082
/**
  Init a key cache if it has not been initied before.
3083
*/
3084
int ha_init_key_cache(const char *name, KEY_CACHE *key_cache)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
3085
{
3086 3087
  DBUG_ENTER("ha_init_key_cache");

3088
  if (!key_cache->key_cache_inited)
3089 3090
  {
    pthread_mutex_lock(&LOCK_global_system_variables);
3091 3092
    ulong tmp_buff_size= (ulong) key_cache->param_buff_size;
    uint tmp_block_size= (uint) key_cache->param_block_size;
3093 3094
    uint division_limit= key_cache->param_division_limit;
    uint age_threshold=  key_cache->param_age_threshold;
3095
    pthread_mutex_unlock(&LOCK_global_system_variables);
3096
    DBUG_RETURN(!init_key_cache(key_cache,
3097 3098
				tmp_block_size,
				tmp_buff_size,
3099
				division_limit, age_threshold));
3100
  }
3101
  DBUG_RETURN(0);
3102 3103
}

3104

3105 3106
/**
  Resize key cache.
3107
*/
3108
int ha_resize_key_cache(KEY_CACHE *key_cache)
igor@rurik.mysql.com's avatar
igor@rurik.mysql.com committed
3109
{
3110 3111
  DBUG_ENTER("ha_resize_key_cache");

3112
  if (key_cache->key_cache_inited)
3113 3114
  {
    pthread_mutex_lock(&LOCK_global_system_variables);
3115 3116 3117 3118
    long tmp_buff_size= (long) key_cache->param_buff_size;
    long tmp_block_size= (long) key_cache->param_block_size;
    uint division_limit= key_cache->param_division_limit;
    uint age_threshold=  key_cache->param_age_threshold;
3119
    pthread_mutex_unlock(&LOCK_global_system_variables);
3120 3121 3122
    DBUG_RETURN(!resize_key_cache(key_cache, tmp_block_size,
				  tmp_buff_size,
				  division_limit, age_threshold));
3123
  }
3124
  DBUG_RETURN(0);
3125 3126
}

3127

3128
/**
3129 3130
  Change parameters for key cache (like size)
*/
3131
int ha_change_key_cache_param(KEY_CACHE *key_cache)
3132
{
3133 3134 3135 3136 3137 3138 3139 3140
  if (key_cache->key_cache_inited)
  {
    pthread_mutex_lock(&LOCK_global_system_variables);
    uint division_limit= key_cache->param_division_limit;
    uint age_threshold=  key_cache->param_age_threshold;
    pthread_mutex_unlock(&LOCK_global_system_variables);
    change_key_cache_param(key_cache, division_limit, age_threshold);
  }
3141 3142
  return 0;
}
3143

3144 3145
/**
  Free memory allocated by a key cache.
3146
*/
3147
int ha_end_key_cache(KEY_CACHE *key_cache)
3148
{
3149
  end_key_cache(key_cache, 1);		// Can never fail
3150
  return 0;
3151
}
bk@work.mysql.com's avatar
bk@work.mysql.com committed
3152

3153 3154
/**
  Move all tables from one key cache to another one.
3155
*/
3156 3157
int ha_change_key_cache(KEY_CACHE *old_key_cache,
			KEY_CACHE *new_key_cache)
bk@work.mysql.com's avatar
bk@work.mysql.com committed
3158
{
3159 3160
  mi_change_key_cache(old_key_cache, new_key_cache);
  return 0;
3161
}
3162 3163


3164 3165
/**
  Try to discover one table from handler(s).
3166

3167 3168 3169 3170 3171 3172
  @retval
    -1   Table did not exists
  @retval
    0   OK. In this case *frmblob and *frmlen are set
  @retval
    >0   error.  frmblob and frmlen may not be set
3173
*/
3174
struct st_discover_args
3175 3176 3177
{
  const char *db;
  const char *name;
3178 3179
  uchar **frmblob; 
  size_t *frmlen;
3180 3181
};

antony@ppcg5.local's avatar
antony@ppcg5.local committed
3182
static my_bool discover_handlerton(THD *thd, plugin_ref plugin,
3183 3184 3185
                                   void *arg)
{
  st_discover_args *vargs= (st_discover_args *)arg;
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3186
  handlerton *hton= plugin_data(plugin, handlerton *);
3187
  if (hton->state == SHOW_OPTION_YES && hton->discover &&
3188 3189 3190
      (!(hton->discover(hton, thd, vargs->db, vargs->name, 
                        vargs->frmblob, 
                        vargs->frmlen))))
3191 3192 3193 3194 3195
    return TRUE;

  return FALSE;
}

3196
int ha_discover(THD *thd, const char *db, const char *name,
3197
		uchar **frmblob, size_t *frmlen)
3198
{
3199
  int error= -1; // Table does not exist in any handler
3200
  DBUG_ENTER("ha_discover");
3201
  DBUG_PRINT("enter", ("db: %s, name: %s", db, name));
3202 3203
  st_discover_args args= {db, name, frmblob, frmlen};

3204 3205
  if (is_prefix(name,tmp_file_prefix)) /* skip temporary tables */
    DBUG_RETURN(error);
3206 3207 3208 3209 3210

  if (plugin_foreach(thd, discover_handlerton,
                 MYSQL_STORAGE_ENGINE_PLUGIN, &args))
    error= 0;

3211
  if (!error)
3212
    status_var_increment(thd->status_var.ha_discover_count);
3213 3214 3215 3216
  DBUG_RETURN(error);
}


3217 3218 3219
/**
  Call this function in order to give the handler the possiblity
  to ask engine if there are any new tables that should be written to disk
3220
  or any dropped tables that need to be removed from disk
3221
*/
3222
struct st_find_files_args
3223 3224 3225 3226 3227
{
  const char *db;
  const char *path;
  const char *wild;
  bool dir;
3228
  List<LEX_STRING> *files;
3229 3230
};

antony@ppcg5.local's avatar
antony@ppcg5.local committed
3231
static my_bool find_files_handlerton(THD *thd, plugin_ref plugin,
3232 3233 3234
                                   void *arg)
{
  st_find_files_args *vargs= (st_find_files_args *)arg;
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3235
  handlerton *hton= plugin_data(plugin, handlerton *);
3236 3237 3238


  if (hton->state == SHOW_OPTION_YES && hton->find_files)
3239
      if (hton->find_files(hton, thd, vargs->db, vargs->path, vargs->wild, 
3240 3241 3242 3243 3244
                          vargs->dir, vargs->files))
        return TRUE;

  return FALSE;
}
3245

3246 3247
int
ha_find_files(THD *thd,const char *db,const char *path,
3248
	      const char *wild, bool dir, List<LEX_STRING> *files)
3249 3250
{
  int error= 0;
3251
  DBUG_ENTER("ha_find_files");
3252 3253
  DBUG_PRINT("enter", ("db: '%s'  path: '%s'  wild: '%s'  dir: %d", 
		       db, path, wild ? wild : "NULL", dir));
3254 3255 3256 3257 3258
  st_find_files_args args= {db, path, wild, dir, files};

  plugin_foreach(thd, find_files_handlerton,
                 MYSQL_STORAGE_ENGINE_PLUGIN, &args);
  /* The return value is not currently used */
3259 3260 3261
  DBUG_RETURN(error);
}

3262 3263 3264
/**
  Ask handler if the table exists in engine.
  @retval
3265
    HA_ERR_NO_SUCH_TABLE     Table does not exist
3266
  @retval
3267
    HA_ERR_TABLE_EXIST       Table exists
3268 3269 3270
  @retval
    \#                  Error code
*/
3271
struct st_table_exists_in_engine_args
3272 3273 3274
{
  const char *db;
  const char *name;
3275
  int err;
3276 3277
};

antony@ppcg5.local's avatar
antony@ppcg5.local committed
3278
static my_bool table_exists_in_engine_handlerton(THD *thd, plugin_ref plugin,
3279 3280 3281
                                   void *arg)
{
  st_table_exists_in_engine_args *vargs= (st_table_exists_in_engine_args *)arg;
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3282
  handlerton *hton= plugin_data(plugin, handlerton *);
3283

3284 3285
  int err= HA_ERR_NO_SUCH_TABLE;

3286
  if (hton->state == SHOW_OPTION_YES && hton->table_exists_in_engine)
3287 3288 3289 3290 3291
    err = hton->table_exists_in_engine(hton, thd, vargs->db, vargs->name);

  vargs->err = err;
  if (vargs->err == HA_ERR_TABLE_EXIST)
    return TRUE;
3292 3293 3294 3295

  return FALSE;
}

3296
int ha_table_exists_in_engine(THD* thd, const char* db, const char* name)
3297
{
3298
  DBUG_ENTER("ha_table_exists_in_engine");
3299
  DBUG_PRINT("enter", ("db: %s, name: %s", db, name));
3300 3301
  st_table_exists_in_engine_args args= {db, name, HA_ERR_NO_SUCH_TABLE};
  plugin_foreach(thd, table_exists_in_engine_handlerton,
3302
                 MYSQL_STORAGE_ENGINE_PLUGIN, &args);
3303 3304
  DBUG_PRINT("exit", ("error: %d", args.err));
  DBUG_RETURN(args.err);
3305 3306
}

tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326
#ifdef HAVE_NDB_BINLOG
/*
  TODO: change this into a dynamic struct
  List<handlerton> does not work as
  1. binlog_end is called when MEM_ROOT is gone
  2. cannot work with thd MEM_ROOT as memory should be freed
*/
#define MAX_HTON_LIST_ST 63
struct hton_list_st
{
  handlerton *hton[MAX_HTON_LIST_ST];
  uint sz;
};

struct binlog_func_st
{
  enum_binlog_func fn;
  void *arg;
};

3327
/** @brief
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3328 3329
  Listing handlertons first to avoid recursive calls and deadlock
*/
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3330
static my_bool binlog_func_list(THD *thd, plugin_ref plugin, void *arg)
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3331 3332
{
  hton_list_st *hton_list= (hton_list_st *)arg;
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3333
  handlerton *hton= plugin_data(plugin, handlerton *);
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350
  if (hton->state == SHOW_OPTION_YES && hton->binlog_func)
  {
    uint sz= hton_list->sz;
    if (sz == MAX_HTON_LIST_ST-1)
    {
      /* list full */
      return FALSE;
    }
    hton_list->hton[sz]= hton;
    hton_list->sz= sz+1;
  }
  return FALSE;
}

static my_bool binlog_func_foreach(THD *thd, binlog_func_st *bfn)
{
  hton_list_st hton_list;
3351 3352
  uint i, sz;

tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3353 3354 3355 3356
  hton_list.sz= 0;
  plugin_foreach(thd, binlog_func_list,
                 MYSQL_STORAGE_ENGINE_PLUGIN, &hton_list);

3357 3358
  for (i= 0, sz= hton_list.sz; i < sz ; i++)
    hton_list.hton[i]->binlog_func(hton_list.hton[i], thd, bfn->fn, bfn->arg);
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391
  return FALSE;
}

int ha_reset_logs(THD *thd)
{
  binlog_func_st bfn= {BFN_RESET_LOGS, 0};
  binlog_func_foreach(thd, &bfn);
  return 0;
}

void ha_reset_slave(THD* thd)
{
  binlog_func_st bfn= {BFN_RESET_SLAVE, 0};
  binlog_func_foreach(thd, &bfn);
}

void ha_binlog_wait(THD* thd)
{
  binlog_func_st bfn= {BFN_BINLOG_WAIT, 0};
  binlog_func_foreach(thd, &bfn);
}

int ha_binlog_end(THD* thd)
{
  binlog_func_st bfn= {BFN_BINLOG_END, 0};
  binlog_func_foreach(thd, &bfn);
  return 0;
}

int ha_binlog_index_purge_file(THD *thd, const char *file)
{
  binlog_func_st bfn= {BFN_BINLOG_PURGE_FILE, (void *)file};
  binlog_func_foreach(thd, &bfn);
knielsen@mysql.com's avatar
knielsen@mysql.com committed
3392
  return 0;
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403
}

struct binlog_log_query_st
{
  enum_binlog_command binlog_command;
  const char *query;
  uint query_length;
  const char *db;
  const char *table_name;
};

3404
static my_bool binlog_log_query_handlerton2(THD *thd,
3405
                                            handlerton *hton,
3406
                                            void *args)
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3407 3408 3409
{
  struct binlog_log_query_st *b= (struct binlog_log_query_st*)args;
  if (hton->state == SHOW_OPTION_YES && hton->binlog_log_query)
3410
    hton->binlog_log_query(hton, thd,
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3411 3412 3413 3414 3415 3416 3417 3418
                           b->binlog_command,
                           b->query,
                           b->query_length,
                           b->db,
                           b->table_name);
  return FALSE;
}

3419
static my_bool binlog_log_query_handlerton(THD *thd,
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3420
                                           plugin_ref plugin,
3421 3422
                                           void *args)
{
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3423
  return binlog_log_query_handlerton2(thd, plugin_data(plugin, handlerton *), args);
3424 3425
}

3426
void ha_binlog_log_query(THD *thd, handlerton *hton,
3427
                         enum_binlog_command binlog_command,
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3428 3429 3430 3431 3432 3433 3434 3435 3436
                         const char *query, uint query_length,
                         const char *db, const char *table_name)
{
  struct binlog_log_query_st b;
  b.binlog_command= binlog_command;
  b.query= query;
  b.query_length= query_length;
  b.db= db;
  b.table_name= table_name;
3437 3438 3439 3440 3441
  if (hton == 0)
    plugin_foreach(thd, binlog_log_query_handlerton,
                   MYSQL_STORAGE_ENGINE_PLUGIN, &b);
  else
    binlog_log_query_handlerton2(thd, hton, &b);
tomas@poseidon.ndb.mysql.com's avatar
tomas@poseidon.ndb.mysql.com committed
3442 3443
}
#endif
3444

3445
/**
ingo@mysql.com's avatar
ingo@mysql.com committed
3446 3447
  Read the first row of a multi-range set.

3448 3449 3450 3451 3452 3453 3454 3455 3456 3457
  @param found_range_p       Returns a pointer to the element in 'ranges' that
                             corresponds to the returned row.
  @param ranges              An array of KEY_MULTI_RANGE range descriptions.
  @param range_count         Number of ranges in 'ranges'.
  @param sorted	      If result should be sorted per key.
  @param buffer              A HANDLER_BUFFER for internal handler usage.

  @note
    - Record is read into table->record[0].
    - *found_range_p returns a valid value only if read_multi_range_first()
ingo@mysql.com's avatar
ingo@mysql.com committed
3458
    returns 0.
3459
    - Sorting is done within each range. If you want an overall sort, enter
ingo@mysql.com's avatar
ingo@mysql.com committed
3460 3461
    'ranges' with sorted ranges.

3462
  @retval
ingo@mysql.com's avatar
ingo@mysql.com committed
3463
    0			OK, found a row
3464
  @retval
ingo@mysql.com's avatar
ingo@mysql.com committed
3465
    HA_ERR_END_OF_FILE	No rows in range
3466 3467
  @retval
    \#			Error code
ingo@mysql.com's avatar
ingo@mysql.com committed
3468 3469 3470 3471 3472 3473 3474 3475 3476 3477
*/
int handler::read_multi_range_first(KEY_MULTI_RANGE **found_range_p,
                                    KEY_MULTI_RANGE *ranges, uint range_count,
                                    bool sorted, HANDLER_BUFFER *buffer)
{
  int result= HA_ERR_END_OF_FILE;
  DBUG_ENTER("handler::read_multi_range_first");
  multi_range_sorted= sorted;
  multi_range_buffer= buffer;

3478 3479 3480
  table->mark_columns_used_by_index_no_reset(active_index, table->read_set);
  table->column_bitmaps_set(table->read_set, table->write_set);

ingo@mysql.com's avatar
ingo@mysql.com committed
3481 3482 3483 3484
  for (multi_range_curr= ranges, multi_range_end= ranges + range_count;
       multi_range_curr < multi_range_end;
       multi_range_curr++)
  {
3485
    result= read_range_first(multi_range_curr->start_key.keypart_map ?
ingo@mysql.com's avatar
ingo@mysql.com committed
3486
                             &multi_range_curr->start_key : 0,
3487
                             multi_range_curr->end_key.keypart_map ?
ingo@mysql.com's avatar
ingo@mysql.com committed
3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500
                             &multi_range_curr->end_key : 0,
                             test(multi_range_curr->range_flag & EQ_RANGE),
                             multi_range_sorted);
    if (result != HA_ERR_END_OF_FILE)
      break;
  }

  *found_range_p= multi_range_curr;
  DBUG_PRINT("exit",("result %d", result));
  DBUG_RETURN(result);
}


3501
/**
ingo@mysql.com's avatar
ingo@mysql.com committed
3502 3503
  Read the next row of a multi-range set.

3504 3505
  @param found_range_p       Returns a pointer to the element in 'ranges' that
                             corresponds to the returned row.
ingo@mysql.com's avatar
ingo@mysql.com committed
3506

3507 3508 3509
  @note
    - Record is read into table->record[0].
    - *found_range_p returns a valid value only if read_multi_range_next()
ingo@mysql.com's avatar
ingo@mysql.com committed
3510 3511
    returns 0.

3512
  @retval
ingo@mysql.com's avatar
ingo@mysql.com committed
3513
    0			OK, found a row
3514
  @retval
ingo@mysql.com's avatar
ingo@mysql.com committed
3515
    HA_ERR_END_OF_FILE	No (more) rows in range
3516 3517
  @retval
    \#			Error code
ingo@mysql.com's avatar
ingo@mysql.com committed
3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539
*/
int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p)
{
  int result;
  DBUG_ENTER("handler::read_multi_range_next");

  /* We should not be called after the last call returned EOF. */
  DBUG_ASSERT(multi_range_curr < multi_range_end);

  do
  {
    /* Save a call if there can be only one row in range. */
    if (multi_range_curr->range_flag != (UNIQUE_RANGE | EQ_RANGE))
    {
      result= read_range_next();

      /* On success or non-EOF errors jump to the end. */
      if (result != HA_ERR_END_OF_FILE)
        break;
    }
    else
    {
gshchepa/uchum@gleb.loc's avatar
gshchepa/uchum@gleb.loc committed
3540 3541
      if (was_semi_consistent_read())
        goto scan_it_again;
ingo@mysql.com's avatar
ingo@mysql.com committed
3542 3543 3544 3545 3546 3547 3548
      /*
        We need to set this for the last range only, but checking this
        condition is more expensive than just setting the result code.
      */
      result= HA_ERR_END_OF_FILE;
    }

gshchepa/uchum@gleb.loc's avatar
gshchepa/uchum@gleb.loc committed
3549 3550
    multi_range_curr++;
scan_it_again:
ingo@mysql.com's avatar
ingo@mysql.com committed
3551
    /* Try the next range(s) until one matches a record. */
gshchepa/uchum@gleb.loc's avatar
gshchepa/uchum@gleb.loc committed
3552
    for (; multi_range_curr < multi_range_end; multi_range_curr++)
ingo@mysql.com's avatar
ingo@mysql.com committed
3553
    {
3554
      result= read_range_first(multi_range_curr->start_key.keypart_map ?
ingo@mysql.com's avatar
ingo@mysql.com committed
3555
                               &multi_range_curr->start_key : 0,
3556
                               multi_range_curr->end_key.keypart_map ?
ingo@mysql.com's avatar
ingo@mysql.com committed
3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572
                               &multi_range_curr->end_key : 0,
                               test(multi_range_curr->range_flag & EQ_RANGE),
                               multi_range_sorted);
      if (result != HA_ERR_END_OF_FILE)
        break;
    }
  }
  while ((result == HA_ERR_END_OF_FILE) &&
         (multi_range_curr < multi_range_end));

  *found_range_p= multi_range_curr;
  DBUG_PRINT("exit",("handler::read_multi_range_next: result %d", result));
  DBUG_RETURN(result);
}


3573
/**
3574
  Read first row between two ranges.
3575
  Store ranges for future calls to read_range_next.
3576

3577 3578 3579 3580 3581 3582
  @param start_key		Start key. Is 0 if no min range
  @param end_key		End key.  Is 0 if no max range
  @param eq_range_arg	        Set to 1 if start_key == end_key
  @param sorted		Set to 1 if result should be sorted per key

  @note
3583 3584
    Record is read into table->record[0]

3585
  @retval
3586
    0			Found row
3587
  @retval
3588
    HA_ERR_END_OF_FILE	No rows in range
3589 3590
  @retval
    \#			Error code
3591 3592 3593
*/
int handler::read_range_first(const key_range *start_key,
			      const key_range *end_key,
3594
			      bool eq_range_arg, bool sorted)
3595 3596 3597 3598
{
  int result;
  DBUG_ENTER("handler::read_range_first");

3599
  eq_range= eq_range_arg;
3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612
  end_range= 0;
  if (end_key)
  {
    end_range= &save_end_range;
    save_end_range= *end_key;
    key_compare_result_on_equal= ((end_key->flag == HA_READ_BEFORE_KEY) ? 1 :
				  (end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0);
  }
  range_key_part= table->key_info[active_index].key_part;

  if (!start_key)			// Read first record
    result= index_first(table->record[0]);
  else
3613 3614 3615 3616
    result= index_read_map(table->record[0],
                           start_key->key,
                           start_key->keypart_map,
                           start_key->flag);
3617
  if (result)
3618 3619 3620
    DBUG_RETURN((result == HA_ERR_KEY_NOT_FOUND) 
		? HA_ERR_END_OF_FILE
		: result);
3621 3622 3623 3624 3625

  DBUG_RETURN (compare_key(end_range) <= 0 ? 0 : HA_ERR_END_OF_FILE);
}


3626
/**
3627 3628
  Read next row between two ranges.

3629
  @note
3630 3631
    Record is read into table->record[0]

3632
  @retval
3633
    0			Found row
3634
  @retval
3635
    HA_ERR_END_OF_FILE	No rows in range
3636 3637
  @retval
    \#			Error code
3638
*/
3639
int handler::read_range_next()
3640 3641 3642 3643 3644
{
  int result;
  DBUG_ENTER("handler::read_range_next");

  if (eq_range)
3645 3646 3647 3648 3649 3650 3651
  {
    /* We trust that index_next_same always gives a row in range */
    DBUG_RETURN(index_next_same(table->record[0],
                                end_range->key,
                                end_range->length));
  }
  result= index_next(table->record[0]);
3652 3653 3654 3655 3656 3657
  if (result)
    DBUG_RETURN(result);
  DBUG_RETURN(compare_key(end_range) <= 0 ? 0 : HA_ERR_END_OF_FILE);
}


3658 3659
/**
  Compare if found key (in row) is over max-value.
3660

3661
  @param range		range to compare to row. May be 0 for no range
3662

3663 3664 3665 3666
  @seealso
    key.cc::key_cmp()

  @return
3667 3668
    The return value is SIGN(key_in_row - range_key):

3669 3670 3671
    - 0   : Key is equal to range or 'range' == 0 (no range)
    - -1  : Key is less than range
    - 1   : Key is larger than range
3672 3673 3674
*/
int handler::compare_key(key_range *range)
{
3675
  int cmp;
3676 3677
  if (!range)
    return 0;					// No max range
3678 3679 3680 3681
  cmp= key_cmp(range_key_part, range->key, range->length);
  if (!cmp)
    cmp= key_compare_result_on_equal;
  return cmp;
3682
}
3683

3684

3685 3686 3687
int handler::index_read_idx_map(uchar * buf, uint index, const uchar * key,
                                key_part_map keypart_map,
                                enum ha_rkey_function find_flag)
3688
{
3689 3690
  int error, error1;
  error= index_init(index, 0);
3691
  if (!error)
3692
  {
3693
    error= index_read_map(buf, key, keypart_map, find_flag);
3694 3695 3696
    error1= index_end();
  }
  return error ?  error : error1;
3697 3698
}

3699

3700
/**
3701 3702 3703
  Returns a list of all known extensions.

    No mutexes, worst case race is a minor surplus memory allocation
3704 3705
    We have to recreate the extension map if mysqld is restarted (for example
    within libmysqld)
3706

3707
  @retval
3708 3709
    pointer		pointer to TYPELIB structure
*/
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3710
static my_bool exts_handlerton(THD *unused, plugin_ref plugin,
3711 3712 3713
                               void *arg)
{
  List<char> *found_exts= (List<char> *) arg;
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3714
  handlerton *hton= plugin_data(plugin, handlerton *);
3715 3716
  handler *file;
  if (hton->state == SHOW_OPTION_YES && hton->create &&
3717
      (file= hton->create(hton, (TABLE_SHARE*) 0, current_thd->mem_root)))
3718 3719 3720
  {
    List_iterator_fast<char> it(*found_exts);
    const char **ext, *old_ext;
3721

3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738
    for (ext= file->bas_ext(); *ext; ext++)
    {
      while ((old_ext= it++))
      {
        if (!strcmp(old_ext, *ext))
	  break;
      }
      if (!old_ext)
        found_exts->push_back((char *) *ext);

      it.rewind();
    }
    delete file;
  }
  return FALSE;
}

3739 3740
TYPELIB *ha_known_exts(void)
{
3741
  if (!known_extensions.type_names || mysys_usage_id != known_extensions_id)
3742 3743
  {
    List<char> found_exts;
3744 3745 3746
    const char **ext, *old_ext;

    known_extensions_id= mysys_usage_id;
3747 3748
    found_exts.push_back((char*) TRG_EXT);
    found_exts.push_back((char*) TRN_EXT);
3749 3750

    plugin_foreach(NULL, exts_handlerton,
3751 3752
                   MYSQL_STORAGE_ENGINE_PLUGIN, &found_exts);

3753 3754 3755
    ext= (const char **) my_once_alloc(sizeof(char *)*
                                       (found_exts.elements+1),
                                       MYF(MY_WME | MY_FAE));
3756

3757
    DBUG_ASSERT(ext != 0);
3758 3759
    known_extensions.count= found_exts.elements;
    known_extensions.type_names= ext;
3760

3761
    List_iterator_fast<char> it(found_exts);
3762 3763 3764
    while ((old_ext= it++))
      *ext++= old_ext;
    *ext= 0;
3765 3766 3767
  }
  return &known_extensions;
}
heikki@hundin.mysql.fi's avatar
heikki@hundin.mysql.fi committed
3768

3769

3770 3771 3772
static bool stat_print(THD *thd, const char *type, uint type_len,
                       const char *file, uint file_len,
                       const char *status, uint status_len)
3773 3774 3775
{
  Protocol *protocol= thd->protocol;
  protocol->prepare_for_resend();
3776 3777 3778
  protocol->store(type, type_len, system_charset_info);
  protocol->store(file, file_len, system_charset_info);
  protocol->store(status, status_len, system_charset_info);
3779 3780 3781 3782 3783
  if (protocol->write())
    return TRUE;
  return FALSE;
}

3784

antony@ppcg5.local's avatar
antony@ppcg5.local committed
3785
static my_bool showstat_handlerton(THD *thd, plugin_ref plugin,
3786 3787 3788
                                   void *arg)
{
  enum ha_stat_type stat= *(enum ha_stat_type *) arg;
antony@ppcg5.local's avatar
antony@ppcg5.local committed
3789
  handlerton *hton= plugin_data(plugin, handlerton *);
3790
  if (hton->state == SHOW_OPTION_YES && hton->show_status &&
3791
      hton->show_status(hton, thd, stat_print, stat))
3792 3793 3794 3795 3796
    return TRUE;
  return FALSE;
}

bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat)
3797 3798 3799
{
  List<Item> field_list;
  Protocol *protocol= thd->protocol;
3800
  bool result;
3801 3802 3803 3804 3805 3806 3807 3808 3809

  field_list.push_back(new Item_empty_string("Type",10));
  field_list.push_back(new Item_empty_string("Name",FN_REFLEN));
  field_list.push_back(new Item_empty_string("Status",10));

  if (protocol->send_fields(&field_list,
                            Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
    return TRUE;

3810
  if (db_type == NULL)
3811
  {
3812
    result= plugin_foreach(thd, showstat_handlerton,
3813 3814 3815 3816 3817
                           MYSQL_STORAGE_ENGINE_PLUGIN, &stat);
  }
  else
  {
    if (db_type->state != SHOW_OPTION_YES)
3818
    {
serg@sergbook.mysql.com's avatar
serg@sergbook.mysql.com committed
3819 3820
      const LEX_STRING *name=&hton2plugin[db_type->slot]->name;
      result= stat_print(thd, name->str, name->length,
3821
                         "", 0, "DISABLED", 8) ? 1 : 0;
3822
    }
3823
    else
3824
      result= db_type->show_status &&
3825
              db_type->show_status(db_type, thd, stat_print, stat) ? 1 : 0;
3826 3827
  }

3828 3829 3830
  if (!result)
    send_eof(thd);
  return result;
3831 3832
}

3833 3834 3835 3836 3837
/*
  Function to check if the conditions for row-based binlogging is
  correct for the table.

  A row in the given table should be replicated if:
3838
  - Row-based replication is enabled in the current thread
3839
  - The binlog is enabled
3840 3841 3842
  - It is not a temporary table
  - The binary log is open
  - The database the table resides in shall be binlogged (binlog_*_db rules)
3843
  - table is not mysql.event
3844 3845
*/

3846 3847 3848
static bool check_table_binlog_row_based(THD *thd, TABLE *table)
{
  if (table->s->cached_row_logging_check == -1)
3849
  {
3850 3851 3852 3853
    int const check(table->s->tmp_table == NO_TMP_TABLE &&
                    binlog_filter->db_ok(table->s->db.str));
    table->s->cached_row_logging_check= check;
  }
3854

3855 3856
  DBUG_ASSERT(table->s->cached_row_logging_check == 0 ||
              table->s->cached_row_logging_check == 1);
3857

3858 3859 3860 3861
  return (thd->current_stmt_binlog_row_based &&
          table->s->cached_row_logging_check &&
          (thd->options & OPTION_BIN_LOG) &&
          mysql_bin_log.is_open());
3862 3863 3864
}


3865
/** @brief
3866 3867 3868
   Write table maps for all (manually or automatically) locked tables
   to the binary log.

3869 3870 3871 3872 3873 3874 3875 3876 3877
   SYNOPSIS
     write_locked_table_maps()
       thd     Pointer to THD structure

   DESCRIPTION
       This function will generate and write table maps for all tables
       that are locked by the thread 'thd'.  Either manually locked
       (stored in THD::locked_tables) and automatically locked (stored
       in THD::lock) are considered.
3878

3879 3880 3881 3882 3883 3884 3885
   RETURN VALUE
       0   All OK
       1   Failed to write all table maps

   SEE ALSO
       THD::lock
       THD::locked_tables
3886
*/
3887

3888
static int write_locked_table_maps(THD *thd)
3889
{
3890 3891 3892 3893 3894
  DBUG_ENTER("write_locked_table_maps");
  DBUG_PRINT("enter", ("thd: 0x%lx  thd->lock: 0x%lx  thd->locked_tables: 0x%lx  "
                       "thd->extra_lock: 0x%lx",
                       (long) thd, (long) thd->lock,
                       (long) thd->locked_tables, (long) thd->extra_lock));
3895

3896 3897 3898 3899 3900 3901 3902
  if (thd->get_binlog_table_maps() == 0)
  {
    MYSQL_LOCK *locks[3];
    locks[0]= thd->extra_lock;
    locks[1]= thd->lock;
    locks[2]= thd->locked_tables;
    for (uint i= 0 ; i < sizeof(locks)/sizeof(*locks) ; ++i )
3903
    {
3904 3905 3906 3907 3908 3909 3910 3911
      MYSQL_LOCK const *const lock= locks[i];
      if (lock == NULL)
        continue;

      TABLE **const end_ptr= lock->table + lock->table_count;
      for (TABLE **table_ptr= lock->table ; 
           table_ptr != end_ptr ;
           ++table_ptr)
3912
      {
3913 3914 3915 3916
        TABLE *const table= *table_ptr;
        DBUG_PRINT("info", ("Checking table %s", table->s->table_name.str));
        if (table->current_lock == F_WRLCK &&
            check_table_binlog_row_based(thd, table))
3917
        {
3918 3919 3920 3921 3922 3923 3924 3925
          int const has_trans= table->file->has_transactions();
          int const error= thd->binlog_write_table_map(table, has_trans);
          /*
            If an error occurs, it is the responsibility of the caller to
            roll back the transaction.
          */
          if (unlikely(error))
            DBUG_RETURN(1);
3926
        }
3927 3928 3929
      }
    }
  }
3930 3931
  DBUG_RETURN(0);
}
3932

3933 3934 3935 3936

typedef bool Log_func(THD*, TABLE*, bool, MY_BITMAP*,
                      uint, const uchar*, const uchar*);

3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947
static int binlog_log_row(TABLE* table,
                          const uchar *before_record,
                          const uchar *after_record,
                          Log_func *log_func)
{
  if (table->no_replicate)
    return 0;
  bool error= 0;
  THD *const thd= table->in_use;

  if (check_table_binlog_row_based(thd, table))
3948
  {
3949 3950 3951 3952 3953
    MY_BITMAP cols;
    /* Potential buffer on the stack for the bitmap */
    uint32 bitbuf[BITMAP_STACKBUF_SIZE/sizeof(uint32)];
    uint n_fields= table->s->fields;
    my_bool use_bitbuf= n_fields <= sizeof(bitbuf)*8;
3954

3955 3956 3957 3958 3959 3960 3961 3962 3963
    /*
      If there are no table maps written to the binary log, this is
      the first row handled in this statement. In that case, we need
      to write table maps for all locked tables to the binary log.
    */
    if (likely(!(error= bitmap_init(&cols,
                                    use_bitbuf ? bitbuf : NULL,
                                    (n_fields + 7) & ~7UL,
                                    FALSE))))
3964
    {
3965 3966 3967 3968 3969 3970 3971 3972
      bitmap_set_all(&cols);
      if (likely(!(error= write_locked_table_maps(thd))))
        error= (*log_func)(thd, table, table->file->has_transactions(),
                           &cols, table->s->fields,
                           before_record, after_record);

      if (!use_bitbuf)
        bitmap_free(&cols);
3973 3974
    }
  }
3975
  return error ? HA_ERR_RBR_LOGGING_FAILED : 0;
3976
}
3977

3978
int handler::ha_external_lock(THD *thd, int lock_type)
3979
{
3980
  DBUG_ENTER("handler::ha_external_lock");
3981 3982 3983 3984 3985 3986
  /*
    Whether this is lock or unlock, this should be true, and is to verify that
    if get_auto_increment() was called (thus may have reserved intervals or
    taken a table lock), ha_release_auto_increment() was too.
  */
  DBUG_ASSERT(next_insert_id == 0);
3987 3988 3989 3990 3991 3992 3993 3994 3995

  /*
    We cache the table flags if the locking succeeded. Otherwise, we
    keep them as they were when they were fetched in ha_open().
  */
  int error= external_lock(thd, lock_type);
  if (error == 0)
    cached_table_flags= table_flags();
  DBUG_RETURN(error);
3996 3997
}

3998

3999
/** @brief
4000 4001 4002 4003 4004
  Check handler usage and reset state of file to after 'open'
*/
int handler::ha_reset()
{
  DBUG_ENTER("ha_reset");
4005
  /* Check that we have called all proper deallocation functions */
4006
  DBUG_ASSERT((uchar*) table->def_read_set.bitmap +
4007
              table->s->column_bitmap_size ==
4008
              (uchar*) table->def_write_set.bitmap);
4009 4010 4011 4012 4013 4014
  DBUG_ASSERT(bitmap_is_set_all(&table->s->all_set));
  DBUG_ASSERT(table->key_read == 0);
  /* ensure that ha_index_end / ha_rnd_end has been called */
  DBUG_ASSERT(inited == NONE);
  /* Free cache used by filesort */
  free_io_cache(table);
4015 4016
  /* reset the bitmaps to point to defaults */
  table->default_column_bitmaps();
4017 4018 4019 4020
  DBUG_RETURN(reset());
}


4021
int handler::ha_write_row(uchar *buf)
4022 4023
{
  int error;
4024
  Log_func *log_func= Write_rows_log_event::binlog_row_logging_function;
4025
  DBUG_ENTER("handler::ha_write_row");
4026
  if (unlikely(error= write_row(buf)))
4027
    DBUG_RETURN(error);
4028
  if (unlikely(error= binlog_log_row(table, 0, buf, log_func)))
4029 4030
    DBUG_RETURN(error); /* purecov: inspected */
  DBUG_RETURN(0);
4031 4032
}

4033

4034
int handler::ha_update_row(const uchar *old_data, uchar *new_data)
4035 4036
{
  int error;
4037
  Log_func *log_func= Update_rows_log_event::binlog_row_logging_function;
4038 4039 4040 4041 4042 4043 4044

  /*
    Some storage engines require that the new record is in record[0]
    (and the old record is in record[1]).
   */
  DBUG_ASSERT(new_data == table->record[0]);

4045 4046
  if (unlikely(error= update_row(old_data, new_data)))
    return error;
4047
  if (unlikely(error= binlog_log_row(table, old_data, new_data, log_func)))
4048 4049
    return error;
  return 0;
4050 4051
}

4052
int handler::ha_delete_row(const uchar *buf)
4053 4054
{
  int error;
4055
  Log_func *log_func= Delete_rows_log_event::binlog_row_logging_function;
4056 4057
  if (unlikely(error= delete_row(buf)))
    return error;
4058
  if (unlikely(error= binlog_log_row(table, buf, 0, log_func)))
4059 4060
    return error;
  return 0;
4061
}
4062

4063

4064

4065
/** @brief
4066 4067 4068 4069 4070 4071 4072 4073 4074
  use_hidden_primary_key() is called in case of an update/delete when
  (table_flags() and HA_PRIMARY_KEY_REQUIRED_FOR_DELETE) is defined
  but we don't have a primary key
*/
void handler::use_hidden_primary_key()
{
  /* fallback to use all columns in the table to identify row */
  table->use_all_columns();
}
4075 4076


4077
/** @brief
4078 4079 4080 4081 4082 4083 4084 4085 4086 4087
  Dummy function which accept information about log files which is not need
  by handlers
*/
void signal_log_not_needed(struct handlerton, char *log_file)
{
  DBUG_ENTER("signal_log_not_needed");
  DBUG_PRINT("enter", ("logfile '%s'", log_file));
  DBUG_VOID_RETURN;
}

4088

4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105
#ifdef TRANS_LOG_MGM_EXAMPLE_CODE
/*
  Example of transaction log management functions based on assumption that logs
  placed into a directory
*/
#include <my_dir.h>
#include <my_sys.h>
int example_of_iterator_using_for_logs_cleanup(handlerton *hton)
{
  void *buffer;
  int res= 1;
  struct handler_iterator iterator;
  struct handler_log_file_data data;

  if (!hton->create_iterator)
    return 1; /* iterator creator is not supported */

4106
  if ((*hton->create_iterator)(hton, HA_TRANSACTLOG_ITERATOR, &iterator) !=
4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135
      HA_ITERATOR_OK)
  {
    /* error during creation of log iterator or iterator is not supported */
    return 1;
  }
  while((*iterator.next)(&iterator, (void*)&data) == 0)
  {
    printf("%s\n", data.filename.str);
    if (data.status == HA_LOG_STATUS_FREE &&
        my_delete(data.filename.str, MYF(MY_WME)))
      goto err;
  }
  res= 0;
err:
  (*iterator.destroy)(&iterator);
  return res;
}


/*
  Here we should get info from handler where it save logs but here is
  just example, so we use constant.
  IMHO FN_ROOTDIR ("/") is safe enough for example, because nobody has
  rights on it except root and it consist of directories only at lest for
  *nix (sorry, can't find windows-safe solution here, but it is only example).
*/
#define fl_dir FN_ROOTDIR


4136
/** @brief
4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175
  Dummy function to return log status should be replaced by function which
  really detect the log status and check that the file is a log of this
  handler.
*/
enum log_status fl_get_log_status(char *log)
{
  MY_STAT stat_buff;
  if (my_stat(log, &stat_buff, MYF(0)))
    return HA_LOG_STATUS_INUSE;
  return HA_LOG_STATUS_NOSUCHLOG;
}


struct fl_buff
{
  LEX_STRING *names;
  enum log_status *statuses;
  uint32 entries;
  uint32 current;
};


int fl_log_iterator_next(struct handler_iterator *iterator,
                          void *iterator_object)
{
  struct fl_buff *buff= (struct fl_buff *)iterator->buffer;
  struct handler_log_file_data *data=
    (struct handler_log_file_data *) iterator_object;
  if (buff->current >= buff->entries)
    return 1;
  data->filename= buff->names[buff->current];
  data->status= buff->statuses[buff->current];
  buff->current++;
  return 0;
}


void fl_log_iterator_destroy(struct handler_iterator *iterator)
{
4176
  my_free((uchar*)iterator->buffer, MYF(MY_ALLOW_ZERO_PTR));
4177 4178 4179
}


4180
/** @brief
4181 4182 4183 4184 4185 4186 4187 4188
  returns buffer, to be assigned in handler_iterator struct
*/
enum handler_create_iterator_result
fl_log_iterator_buffer_init(struct handler_iterator *iterator)
{
  MY_DIR *dirp;
  struct fl_buff *buff;
  char *name_ptr;
4189
  uchar *ptr;
4190 4191 4192 4193 4194 4195 4196 4197 4198 4199
  FILEINFO *file;
  uint32 i;

  /* to be able to make my_free without crash in case of error */
  iterator->buffer= 0;

  if (!(dirp = my_dir(fl_dir, MYF(0))))
  {
    return HA_ITERATOR_ERROR;
  }
4200
  if ((ptr= (uchar*)my_malloc(ALIGN_SIZE(sizeof(fl_buff)) +
4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247
                             ((ALIGN_SIZE(sizeof(LEX_STRING)) +
                               sizeof(enum log_status) +
                               + FN_REFLEN) *
                              (uint) dirp->number_off_files),
                             MYF(0))) == 0)
  {
    return HA_ITERATOR_ERROR;
  }
  buff= (struct fl_buff *)ptr;
  buff->entries= buff->current= 0;
  ptr= ptr + (ALIGN_SIZE(sizeof(fl_buff)));
  buff->names= (LEX_STRING*) (ptr);
  ptr= ptr + ((ALIGN_SIZE(sizeof(LEX_STRING)) *
               (uint) dirp->number_off_files));
  buff->statuses= (enum log_status *)(ptr);
  name_ptr= (char *)(ptr + (sizeof(enum log_status) *
                            (uint) dirp->number_off_files));
  for (i=0 ; i < (uint) dirp->number_off_files  ; i++)
  {
    enum log_status st;
    file= dirp->dir_entry + i;
    if ((file->name[0] == '.' &&
         ((file->name[1] == '.' && file->name[2] == '\0') ||
            file->name[1] == '\0')))
      continue;
    if ((st= fl_get_log_status(file->name)) == HA_LOG_STATUS_NOSUCHLOG)
      continue;
    name_ptr= strxnmov(buff->names[buff->entries].str= name_ptr,
                       FN_REFLEN, fl_dir, file->name, NullS);
    buff->names[buff->entries].length= (name_ptr -
                                        buff->names[buff->entries].str) - 1;
    buff->statuses[buff->entries]= st;
    buff->entries++;
  }

  iterator->buffer= buff;
  iterator->next= &fl_log_iterator_next;
  iterator->destroy= &fl_log_iterator_destroy;
  return HA_ITERATOR_OK;
}


/* An example of a iterator creator */
enum handler_create_iterator_result
fl_create_iterator(enum handler_iterator_type type,
                   struct handler_iterator *iterator)
{
4248
  switch(type) {
4249 4250 4251 4252 4253 4254 4255
  case HA_TRANSACTLOG_ITERATOR:
    return fl_log_iterator_buffer_init(iterator);
  default:
    return HA_ITERATOR_UNSUPPORTED;
  }
}
#endif /*TRANS_LOG_MGM_EXAMPLE_CODE*/