| 1 | /* |
| 2 | Copyright (c) 2000, 2010, Oracle and/or its affiliates |
| 3 | |
| 4 | This program is free software; you can redistribute it and/or modify |
| 5 | it under the terms of the GNU General Public License as published by |
| 6 | the Free Software Foundation; version 2 of the License. |
| 7 | |
| 8 | This program is distributed in the hope that it will be useful, |
| 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | GNU General Public License for more details. |
| 12 | |
| 13 | You should have received a copy of the GNU General Public License |
| 14 | along with this program; if not, write to the Free Software |
| 15 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
| 16 | |
| 17 | #include "myisamdef.h" |
| 18 | #ifdef HAVE_SYS_MMAN_H |
| 19 | #include <sys/mman.h> |
| 20 | #endif |
| 21 | |
| 22 | static void mi_extra_keyflag(MI_INFO *info, enum ha_extra_function function); |
| 23 | |
| 24 | |
| 25 | /* |
| 26 | Set options and buffers to optimize table handling |
| 27 | |
| 28 | SYNOPSIS |
| 29 | mi_extra() |
| 30 | info open table |
| 31 | function operation |
| 32 | extra_arg Pointer to extra argument (normally pointer to ulong) |
| 33 | Used when function is one of: |
| 34 | HA_EXTRA_WRITE_CACHE |
| 35 | HA_EXTRA_CACHE |
| 36 | RETURN VALUES |
| 37 | 0 ok |
| 38 | # error |
| 39 | */ |
| 40 | |
| 41 | int (MI_INFO *info, enum ha_extra_function function, void *) |
| 42 | { |
| 43 | int error=0; |
| 44 | ulong cache_size; |
| 45 | MYISAM_SHARE *share=info->s; |
| 46 | DBUG_ENTER("mi_extra" ); |
| 47 | DBUG_PRINT("enter" ,("function: %d" ,(int) function)); |
| 48 | |
| 49 | switch (function) { |
| 50 | case HA_EXTRA_RESET_STATE: /* Reset state (don't free buffers) */ |
| 51 | info->lastinx= 0; /* Use first index as def */ |
| 52 | info->last_search_keypage=info->lastpos= HA_OFFSET_ERROR; |
| 53 | info->page_changed=1; |
| 54 | /* Next/prev gives first/last */ |
| 55 | if (info->opt_flag & READ_CACHE_USED) |
| 56 | { |
| 57 | reinit_io_cache(&info->rec_cache,READ_CACHE,0, |
| 58 | (pbool) (info->lock_type != F_UNLCK), |
| 59 | (pbool) MY_TEST(info->update & HA_STATE_ROW_CHANGED) |
| 60 | ); |
| 61 | } |
| 62 | info->update= ((info->update & HA_STATE_CHANGED) | HA_STATE_NEXT_FOUND | |
| 63 | HA_STATE_PREV_FOUND); |
| 64 | break; |
| 65 | case HA_EXTRA_CACHE: |
| 66 | if (info->lock_type == F_UNLCK && |
| 67 | (share->options & HA_OPTION_PACK_RECORD)) |
| 68 | { |
| 69 | error=1; /* Not possibly if not locked */ |
| 70 | my_errno=EACCES; |
| 71 | break; |
| 72 | } |
| 73 | if (info->s->file_map) /* Don't use cache if mmap */ |
| 74 | break; |
| 75 | #if defined(HAVE_MMAP) && defined(HAVE_MADVISE) |
| 76 | if ((share->options & HA_OPTION_COMPRESS_RECORD)) |
| 77 | { |
| 78 | mysql_mutex_lock(&share->intern_lock); |
| 79 | if (_mi_memmap_file(info)) |
| 80 | { |
| 81 | /* We don't nead MADV_SEQUENTIAL if small file */ |
| 82 | madvise((char*) share->file_map, share->state.state.data_file_length, |
| 83 | share->state.state.data_file_length <= RECORD_CACHE_SIZE*16 ? |
| 84 | MADV_RANDOM : MADV_SEQUENTIAL); |
| 85 | mysql_mutex_unlock(&share->intern_lock); |
| 86 | break; |
| 87 | } |
| 88 | mysql_mutex_unlock(&share->intern_lock); |
| 89 | } |
| 90 | #endif |
| 91 | if (info->opt_flag & WRITE_CACHE_USED) |
| 92 | { |
| 93 | info->opt_flag&= ~WRITE_CACHE_USED; |
| 94 | if ((error=end_io_cache(&info->rec_cache))) |
| 95 | break; |
| 96 | } |
| 97 | if (!(info->opt_flag & |
| 98 | (READ_CACHE_USED | WRITE_CACHE_USED | MEMMAP_USED))) |
| 99 | { |
| 100 | cache_size= (extra_arg ? *(ulong*) extra_arg : |
| 101 | my_default_record_cache_size); |
| 102 | if (!(init_io_cache(&info->rec_cache,info->dfile, |
| 103 | (uint) MY_MIN(info->state->data_file_length+1, |
| 104 | cache_size), |
| 105 | READ_CACHE,0L,(pbool) (info->lock_type != F_UNLCK), |
| 106 | MYF(share->write_flag & MY_WAIT_IF_FULL)))) |
| 107 | { |
| 108 | info->opt_flag|=READ_CACHE_USED; |
| 109 | info->update&= ~HA_STATE_ROW_CHANGED; |
| 110 | } |
| 111 | if (share->concurrent_insert) |
| 112 | info->rec_cache.end_of_file=info->state->data_file_length; |
| 113 | } |
| 114 | break; |
| 115 | case HA_EXTRA_REINIT_CACHE: |
| 116 | if (info->opt_flag & READ_CACHE_USED) |
| 117 | { |
| 118 | reinit_io_cache(&info->rec_cache,READ_CACHE,info->nextpos, |
| 119 | (pbool) (info->lock_type != F_UNLCK), |
| 120 | (pbool) MY_TEST(info->update & HA_STATE_ROW_CHANGED)); |
| 121 | info->update&= ~HA_STATE_ROW_CHANGED; |
| 122 | if (share->concurrent_insert) |
| 123 | info->rec_cache.end_of_file=info->state->data_file_length; |
| 124 | } |
| 125 | break; |
| 126 | case HA_EXTRA_WRITE_CACHE: |
| 127 | if (info->lock_type == F_UNLCK) |
| 128 | { |
| 129 | error=1; /* Not possibly if not locked */ |
| 130 | break; |
| 131 | } |
| 132 | |
| 133 | cache_size= (extra_arg ? *(ulong*) extra_arg : |
| 134 | my_default_record_cache_size); |
| 135 | if (!(info->opt_flag & |
| 136 | (READ_CACHE_USED | WRITE_CACHE_USED | OPT_NO_ROWS)) && |
| 137 | !share->state.header.uniques) |
| 138 | if (!(init_io_cache(&info->rec_cache,info->dfile, cache_size, |
| 139 | WRITE_CACHE,info->state->data_file_length, |
| 140 | (pbool) (info->lock_type != F_UNLCK), |
| 141 | MYF(share->write_flag & MY_WAIT_IF_FULL)))) |
| 142 | { |
| 143 | info->opt_flag|=WRITE_CACHE_USED; |
| 144 | info->update&= ~(HA_STATE_ROW_CHANGED | |
| 145 | HA_STATE_WRITE_AT_END | |
| 146 | HA_STATE_EXTEND_BLOCK); |
| 147 | } |
| 148 | break; |
| 149 | case HA_EXTRA_PREPARE_FOR_UPDATE: |
| 150 | if (info->s->data_file_type != DYNAMIC_RECORD) |
| 151 | break; |
| 152 | /* Remove read/write cache if dynamic rows */ |
| 153 | /* fall through */ |
| 154 | case HA_EXTRA_NO_CACHE: |
| 155 | if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) |
| 156 | { |
| 157 | info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); |
| 158 | error=end_io_cache(&info->rec_cache); |
| 159 | /* Sergei will insert full text index caching here */ |
| 160 | } |
| 161 | #if defined(HAVE_MMAP) && defined(HAVE_MADVISE) |
| 162 | if (info->opt_flag & MEMMAP_USED) |
| 163 | madvise((char*) share->file_map, share->state.state.data_file_length, |
| 164 | MADV_RANDOM); |
| 165 | #endif |
| 166 | break; |
| 167 | case HA_EXTRA_FLUSH_CACHE: |
| 168 | if (info->opt_flag & WRITE_CACHE_USED) |
| 169 | { |
| 170 | if ((error=flush_io_cache(&info->rec_cache))) |
| 171 | { |
| 172 | mi_print_error(info->s, HA_ERR_CRASHED); |
| 173 | mi_mark_crashed(info); /* Fatal error found */ |
| 174 | } |
| 175 | } |
| 176 | break; |
| 177 | case HA_EXTRA_NO_READCHECK: |
| 178 | info->opt_flag&= ~READ_CHECK_USED; /* No readcheck */ |
| 179 | break; |
| 180 | case HA_EXTRA_READCHECK: |
| 181 | info->opt_flag|= READ_CHECK_USED; |
| 182 | break; |
| 183 | case HA_EXTRA_KEYREAD: /* Read only keys to record */ |
| 184 | case HA_EXTRA_REMEMBER_POS: |
| 185 | info->opt_flag |= REMEMBER_OLD_POS; |
| 186 | bmove((uchar*) info->lastkey+share->base.max_key_length*2, |
| 187 | (uchar*) info->lastkey,info->lastkey_length); |
| 188 | info->save_update= info->update; |
| 189 | info->save_lastinx= info->lastinx; |
| 190 | info->save_lastpos= info->lastpos; |
| 191 | info->save_lastkey_length=info->lastkey_length; |
| 192 | if (function == HA_EXTRA_REMEMBER_POS) |
| 193 | break; |
| 194 | /* fall through */ |
| 195 | case HA_EXTRA_KEYREAD_CHANGE_POS: |
| 196 | info->opt_flag |= KEY_READ_USED; |
| 197 | info->read_record=_mi_read_key_record; |
| 198 | break; |
| 199 | case HA_EXTRA_NO_KEYREAD: |
| 200 | case HA_EXTRA_RESTORE_POS: |
| 201 | if (info->opt_flag & REMEMBER_OLD_POS) |
| 202 | { |
| 203 | bmove((uchar*) info->lastkey, |
| 204 | (uchar*) info->lastkey+share->base.max_key_length*2, |
| 205 | info->save_lastkey_length); |
| 206 | info->update= info->save_update | HA_STATE_WRITTEN; |
| 207 | info->lastinx= info->save_lastinx; |
| 208 | info->lastpos= info->save_lastpos; |
| 209 | info->lastkey_length=info->save_lastkey_length; |
| 210 | } |
| 211 | info->read_record= share->read_record; |
| 212 | info->opt_flag&= ~(KEY_READ_USED | REMEMBER_OLD_POS); |
| 213 | break; |
| 214 | case HA_EXTRA_NO_USER_CHANGE: /* Database is somehow locked agains changes */ |
| 215 | info->lock_type= F_EXTRA_LCK; /* Simulate as locked */ |
| 216 | break; |
| 217 | case HA_EXTRA_WAIT_LOCK: |
| 218 | info->lock_wait=0; |
| 219 | break; |
| 220 | case HA_EXTRA_NO_WAIT_LOCK: |
| 221 | info->lock_wait= MY_SHORT_WAIT; |
| 222 | break; |
| 223 | case HA_EXTRA_NO_KEYS: |
| 224 | if (info->lock_type == F_UNLCK) |
| 225 | { |
| 226 | error=1; /* Not possibly if not lock */ |
| 227 | break; |
| 228 | } |
| 229 | if (mi_is_any_key_active(share->state.key_map)) |
| 230 | { |
| 231 | MI_KEYDEF *key=share->keyinfo; |
| 232 | uint i; |
| 233 | for (i=0 ; i < share->base.keys ; i++,key++) |
| 234 | { |
| 235 | if (!(key->flag & HA_NOSAME) && info->s->base.auto_key != i+1) |
| 236 | { |
| 237 | mi_clear_key_active(share->state.key_map, i); |
| 238 | info->update|= HA_STATE_CHANGED; |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | if (!share->changed) |
| 243 | { |
| 244 | share->state.changed|= STATE_CHANGED | STATE_NOT_ANALYZED; |
| 245 | share->changed=1; /* Update on close */ |
| 246 | if (!share->global_changed) |
| 247 | { |
| 248 | share->global_changed=1; |
| 249 | share->state.open_count++; |
| 250 | } |
| 251 | } |
| 252 | share->state.state= *info->state; |
| 253 | error=mi_state_info_write(share->kfile,&share->state,1 | 2); |
| 254 | } |
| 255 | break; |
| 256 | case HA_EXTRA_FORCE_REOPEN: |
| 257 | mysql_mutex_lock(&THR_LOCK_myisam); |
| 258 | share->last_version= 0L; /* Impossible version */ |
| 259 | mysql_mutex_unlock(&THR_LOCK_myisam); |
| 260 | break; |
| 261 | case HA_EXTRA_PREPARE_FOR_DROP: |
| 262 | /* Signals about intent to delete this table */ |
| 263 | share->deleting= TRUE; |
| 264 | share->global_changed= FALSE; /* force writing changed flag */ |
| 265 | _mi_mark_file_changed(info); |
| 266 | /* fall through */ |
| 267 | case HA_EXTRA_PREPARE_FOR_RENAME: |
| 268 | DBUG_ASSERT(!share->temporary); |
| 269 | mysql_mutex_lock(&THR_LOCK_myisam); |
| 270 | share->last_version= 0L; /* Impossible version */ |
| 271 | mysql_mutex_lock(&share->intern_lock); |
| 272 | /* Flush pages that we don't need anymore */ |
| 273 | if (flush_key_blocks(share->key_cache, share->kfile, |
| 274 | &share->dirty_part_map, |
| 275 | (function == HA_EXTRA_PREPARE_FOR_DROP ? |
| 276 | FLUSH_IGNORE_CHANGED : FLUSH_RELEASE))) |
| 277 | { |
| 278 | error=my_errno; |
| 279 | share->changed=1; |
| 280 | mi_print_error(info->s, HA_ERR_CRASHED); |
| 281 | mi_mark_crashed(info); /* Fatal error found */ |
| 282 | } |
| 283 | #ifdef __WIN__REMOVE_OBSOLETE_WORKAROUND |
| 284 | /* Close the isam and data files as Win32 can't drop an open table */ |
| 285 | if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) |
| 286 | { |
| 287 | info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); |
| 288 | error=end_io_cache(&info->rec_cache); |
| 289 | } |
| 290 | if (info->lock_type != F_UNLCK && ! info->was_locked) |
| 291 | { |
| 292 | info->was_locked=info->lock_type; |
| 293 | if (mi_lock_database(info,F_UNLCK)) |
| 294 | error=my_errno; |
| 295 | info->lock_type = F_UNLCK; |
| 296 | } |
| 297 | if (share->kfile >= 0) |
| 298 | { |
| 299 | /* |
| 300 | We don't need to call _mi_decrement_open_count() if we are |
| 301 | dropping the table, as the files will be removed anyway. If we |
| 302 | are aborted before the files is removed, it's better to not |
| 303 | call it as in that case the automatic repair on open will add |
| 304 | the missing index entries |
| 305 | */ |
| 306 | if (function != HA_EXTRA_PREPARE_FOR_DROP) |
| 307 | _mi_decrement_open_count(info); |
| 308 | if (mysql_file_close(share->kfile,MYF(0))) |
| 309 | error=my_errno; |
| 310 | } |
| 311 | { |
| 312 | LIST *list_element ; |
| 313 | for (list_element=myisam_open_list ; |
| 314 | list_element ; |
| 315 | list_element=list_element->next) |
| 316 | { |
| 317 | MI_INFO *tmpinfo=(MI_INFO*) list_element->data; |
| 318 | if (tmpinfo->s == info->s) |
| 319 | { |
| 320 | if (tmpinfo->dfile >= 0 && mysql_file_close(tmpinfo->dfile, MYF(0))) |
| 321 | error = my_errno; |
| 322 | tmpinfo->dfile= -1; |
| 323 | } |
| 324 | } |
| 325 | } |
| 326 | share->kfile= -1; /* Files aren't open anymore */ |
| 327 | #endif |
| 328 | mysql_mutex_unlock(&share->intern_lock); |
| 329 | mysql_mutex_unlock(&THR_LOCK_myisam); |
| 330 | break; |
| 331 | case HA_EXTRA_FLUSH: |
| 332 | if (!share->temporary) |
| 333 | flush_key_blocks(share->key_cache, share->kfile, &share->dirty_part_map, |
| 334 | FLUSH_KEEP); |
| 335 | _mi_decrement_open_count(info); |
| 336 | if (share->not_flushed) |
| 337 | { |
| 338 | share->not_flushed=0; |
| 339 | if (mysql_file_sync(share->kfile, MYF(0))) |
| 340 | error= my_errno; |
| 341 | if (mysql_file_sync(info->dfile, MYF(0))) |
| 342 | error= my_errno; |
| 343 | if (error) |
| 344 | { |
| 345 | share->changed=1; |
| 346 | mi_print_error(info->s, HA_ERR_CRASHED); |
| 347 | mi_mark_crashed(info); /* Fatal error found */ |
| 348 | } |
| 349 | } |
| 350 | if (share->base.blobs) |
| 351 | mi_alloc_rec_buff(info, -1, &info->rec_buff); |
| 352 | break; |
| 353 | case HA_EXTRA_NORMAL: /* Theese isn't in use */ |
| 354 | info->quick_mode=0; |
| 355 | break; |
| 356 | case HA_EXTRA_QUICK: |
| 357 | info->quick_mode=1; |
| 358 | break; |
| 359 | case HA_EXTRA_NO_ROWS: |
| 360 | if (!share->state.header.uniques) |
| 361 | info->opt_flag|= OPT_NO_ROWS; |
| 362 | break; |
| 363 | case HA_EXTRA_PRELOAD_BUFFER_SIZE: |
| 364 | info->preload_buff_size= *((ulong *) extra_arg); |
| 365 | break; |
| 366 | case HA_EXTRA_CHANGE_KEY_TO_UNIQUE: |
| 367 | case HA_EXTRA_CHANGE_KEY_TO_DUP: |
| 368 | mi_extra_keyflag(info, function); |
| 369 | break; |
| 370 | case HA_EXTRA_MMAP: |
| 371 | #ifdef HAVE_MMAP |
| 372 | mysql_mutex_lock(&share->intern_lock); |
| 373 | /* |
| 374 | Memory map the data file if it is not already mapped. It is safe |
| 375 | to memory map a file while other threads are using file I/O on it. |
| 376 | Assigning a new address to a function pointer is an atomic |
| 377 | operation. intern_lock prevents that two or more mappings are done |
| 378 | at the same time. |
| 379 | */ |
| 380 | if (!share->file_map) |
| 381 | { |
| 382 | if (mi_dynmap_file(info, share->state.state.data_file_length)) |
| 383 | { |
| 384 | DBUG_PRINT("warning" ,("mmap failed: errno: %d" ,errno)); |
| 385 | error= my_errno= errno; |
| 386 | } |
| 387 | } |
| 388 | mysql_mutex_unlock(&share->intern_lock); |
| 389 | #endif |
| 390 | break; |
| 391 | case HA_EXTRA_MARK_AS_LOG_TABLE: |
| 392 | mysql_mutex_lock(&share->intern_lock); |
| 393 | share->is_log_table= TRUE; |
| 394 | mysql_mutex_unlock(&share->intern_lock); |
| 395 | break; |
| 396 | case HA_EXTRA_DETACH_CHILD: /* When used with MERGE tables */ |
| 397 | info->open_flag&= ~HA_OPEN_MERGE_TABLE; |
| 398 | info->lock.priority&= ~THR_LOCK_MERGE_PRIV; |
| 399 | break; |
| 400 | |
| 401 | case HA_EXTRA_KEY_CACHE: |
| 402 | case HA_EXTRA_NO_KEY_CACHE: |
| 403 | default: |
| 404 | break; |
| 405 | } |
| 406 | { |
| 407 | char tmp[1]; |
| 408 | tmp[0]=function; |
| 409 | myisam_log_command(MI_LOG_EXTRA,info,(uchar*) tmp,1,error); |
| 410 | } |
| 411 | DBUG_RETURN(error); |
| 412 | } /* mi_extra */ |
| 413 | |
| 414 | void mi_set_index_cond_func(MI_INFO *info, index_cond_func_t func, |
| 415 | void *func_arg) |
| 416 | { |
| 417 | info->index_cond_func= func; |
| 418 | info->index_cond_func_arg= func_arg; |
| 419 | } |
| 420 | |
| 421 | /* |
| 422 | Start/Stop Inserting Duplicates Into a Table, WL#1648. |
| 423 | */ |
| 424 | static void (MI_INFO *info, enum ha_extra_function function) |
| 425 | { |
| 426 | uint idx; |
| 427 | |
| 428 | for (idx= 0; idx< info->s->base.keys; idx++) |
| 429 | { |
| 430 | switch (function) { |
| 431 | case HA_EXTRA_CHANGE_KEY_TO_UNIQUE: |
| 432 | info->s->keyinfo[idx].flag|= HA_NOSAME; |
| 433 | break; |
| 434 | case HA_EXTRA_CHANGE_KEY_TO_DUP: |
| 435 | info->s->keyinfo[idx].flag&= ~(HA_NOSAME); |
| 436 | break; |
| 437 | default: |
| 438 | break; |
| 439 | } |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | |
| 444 | int mi_reset(MI_INFO *info) |
| 445 | { |
| 446 | int error= 0; |
| 447 | MYISAM_SHARE *share=info->s; |
| 448 | DBUG_ENTER("mi_reset" ); |
| 449 | /* |
| 450 | Free buffers and reset the following flags: |
| 451 | EXTRA_CACHE, EXTRA_WRITE_CACHE, EXTRA_KEYREAD, EXTRA_QUICK |
| 452 | |
| 453 | If the row buffer cache is large (for dynamic tables), reduce it |
| 454 | to save memory. |
| 455 | */ |
| 456 | if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) |
| 457 | { |
| 458 | info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); |
| 459 | error= end_io_cache(&info->rec_cache); |
| 460 | } |
| 461 | if (share->base.blobs) |
| 462 | mi_alloc_rec_buff(info, -1, &info->rec_buff); |
| 463 | #if defined(HAVE_MMAP) && defined(HAVE_MADVISE) |
| 464 | if (info->opt_flag & MEMMAP_USED) |
| 465 | madvise((char*) share->file_map, share->state.state.data_file_length, |
| 466 | MADV_RANDOM); |
| 467 | #endif |
| 468 | info->opt_flag&= ~(KEY_READ_USED | REMEMBER_OLD_POS); |
| 469 | info->quick_mode=0; |
| 470 | info->lastinx= 0; /* Use first index as def */ |
| 471 | info->last_search_keypage= info->lastpos= HA_OFFSET_ERROR; |
| 472 | info->page_changed= 1; |
| 473 | info->update= ((info->update & HA_STATE_CHANGED) | HA_STATE_NEXT_FOUND | |
| 474 | HA_STATE_PREV_FOUND); |
| 475 | DBUG_RETURN(error); |
| 476 | } |
| 477 | |
| 478 | my_bool mi_killed_standalone(MI_INFO *info __attribute__((unused))) |
| 479 | { |
| 480 | return 0; |
| 481 | } |
| 482 | |