diff options
Diffstat (limited to 'storage/innobase/dict/dict0load.cc')
-rw-r--r-- | storage/innobase/dict/dict0load.cc | 857 |
1 files changed, 697 insertions, 160 deletions
diff --git a/storage/innobase/dict/dict0load.cc b/storage/innobase/dict/dict0load.cc index ff93be3e76a..46d72786ac6 100644 --- a/storage/innobase/dict/dict0load.cc +++ b/storage/innobase/dict/dict0load.cc @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 1996, 2012, Oracle and/or its affiliates. All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -41,18 +41,22 @@ Created 4/24/1996 Heikki Tuuri #include "rem0cmp.h" #include "srv0start.h" #include "srv0srv.h" +#include "dict0crea.h" #include "dict0priv.h" #include "ha_prototypes.h" /* innobase_casedn_str() */ #include "fts0priv.h" -/** Following are six InnoDB system tables */ +/** Following are the InnoDB system tables. The positions in +this array are referenced by enum dict_system_table_id. */ static const char* SYSTEM_TABLE_NAME[] = { "SYS_TABLES", "SYS_INDEXES", "SYS_COLUMNS", "SYS_FIELDS", "SYS_FOREIGN", - "SYS_FOREIGN_COLS" + "SYS_FOREIGN_COLS", + "SYS_TABLESPACES", + "SYS_DATAFILES" }; /* If this flag is TRUE, then we will load the cluster index's (and tables') @@ -183,7 +187,8 @@ dict_print(void) os_increment_counter_by_amount( server_mutex, - srv_fatal_semaphore_wait_threshold, 7200/*2 hours*/); + srv_fatal_semaphore_wait_threshold, + SRV_SEMAPHORE_WAIT_EXTENSION); heap = mem_heap_create(1000); mutex_enter(&(dict_sys->mutex)); @@ -196,13 +201,11 @@ dict_print(void) err_msg = static_cast<const char*>( dict_process_sys_tables_rec_and_mtr_commit( - heap, rec, &table, - static_cast<dict_table_info_t>( - DICT_TABLE_LOAD_FROM_CACHE - | DICT_TABLE_UPDATE_STATS), &mtr)); + heap, rec, &table, DICT_TABLE_LOAD_FROM_CACHE, + &mtr)); if (!err_msg) { - dict_table_print_low(table); + dict_table_print(table); } else { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: %s\n", err_msg); @@ -221,7 +224,8 @@ dict_print(void) /* Restore the fatal semaphore wait timeout */ os_decrement_counter_by_amount( server_mutex, - srv_fatal_semaphore_wait_threshold, 7200/*2 hours*/); + srv_fatal_semaphore_wait_threshold, + SRV_SEMAPHORE_WAIT_EXTENSION); } /********************************************************************//** @@ -278,8 +282,8 @@ dict_startscan_system( clust_index = UT_LIST_GET_FIRST(system_table->indexes); - btr_pcur_open_at_index_side(TRUE, clust_index, BTR_SEARCH_LEAF, pcur, - TRUE, mtr); + btr_pcur_open_at_index_side(true, clust_index, BTR_SEARCH_LEAF, pcur, + true, 0, mtr); rec = dict_getnext_system_low(pcur, mtr); @@ -307,6 +311,7 @@ dict_getnext_system( return(rec); } + /********************************************************************//** This function processes one SYS_TABLES record and populate the dict_table_t struct for the table. Extracted out of dict_print() to be used by @@ -362,15 +367,6 @@ dict_process_sys_tables_rec_and_mtr_commit( return(err_msg); } - if ((status & DICT_TABLE_UPDATE_STATS) - && dict_table_get_first_index(*table)) { - - /* Update statistics member fields in *table if - DICT_TABLE_UPDATE_STATS is set */ - ut_ad(mutex_own(&dict_sys->mutex)); - dict_stats_update(*table, DICT_STATS_FETCH, TRUE); - } - return(NULL); } @@ -401,6 +397,7 @@ dict_process_sys_indexes_rec( return(err_msg); } + /********************************************************************//** This function parses a SYS_COLUMNS record and populate a dict_column_t structure with the information from the record. @@ -423,6 +420,7 @@ dict_process_sys_columns_rec( return(err_msg); } + /********************************************************************//** This function parses a SYS_FIELDS record and populates a dict_field_t structure with the information from the record. @@ -475,7 +473,7 @@ dict_process_sys_foreign_rec( const byte* field; ulint n_fields_and_type; - if (UNIV_UNLIKELY(rec_get_deleted_flag(rec, 0))) { + if (rec_get_deleted_flag(rec, 0)) { return("delete-marked record in SYS_FOREIGN"); } @@ -485,7 +483,7 @@ dict_process_sys_foreign_rec( field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN__ID, &len); - if (UNIV_UNLIKELY(len < 1 || len == UNIV_SQL_NULL)) { + if (len == 0 || len == UNIV_SQL_NULL) { err_len: return("incorrect column length in SYS_FOREIGN"); } @@ -512,7 +510,7 @@ err_len: field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN__FOR_NAME, &len); - if (len < 1 || len == UNIV_SQL_NULL) { + if (len == 0 || len == UNIV_SQL_NULL) { goto err_len; } foreign->foreign_table_name = mem_heap_strdupl( @@ -520,7 +518,7 @@ err_len: field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN__REF_NAME, &len); - if (len < 1 || len == UNIV_SQL_NULL) { + if (len == 0 || len == UNIV_SQL_NULL) { goto err_len; } foreign->referenced_table_name = mem_heap_strdupl( @@ -568,7 +566,7 @@ dict_process_sys_foreign_col_rec( field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN_COLS__ID, &len); - if (len < 1 || len == UNIV_SQL_NULL) { + if (len == 0 || len == UNIV_SQL_NULL) { err_len: return("incorrect column length in SYS_FOREIGN_COLS"); } @@ -594,14 +592,14 @@ err_len: field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN_COLS__FOR_COL_NAME, &len); - if (len < 1 || len == UNIV_SQL_NULL) { + if (len == 0 || len == UNIV_SQL_NULL) { goto err_len; } *for_col_name = mem_heap_strdupl(heap, (char*) field, len); field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN_COLS__REF_COL_NAME, &len); - if (len < 1 || len == UNIV_SQL_NULL) { + if (len == 0 || len == UNIV_SQL_NULL) { goto err_len; } *ref_col_name = mem_heap_strdupl(heap, (char*) field, len); @@ -610,6 +608,127 @@ err_len: } /********************************************************************//** +This function parses a SYS_TABLESPACES record, extracts necessary +information from the record and returns to caller. +@return error message, or NULL on success */ +UNIV_INTERN +const char* +dict_process_sys_tablespaces( +/*=========================*/ + mem_heap_t* heap, /*!< in/out: heap memory */ + const rec_t* rec, /*!< in: current SYS_TABLESPACES rec */ + ulint* space, /*!< out: space id */ + const char** name, /*!< out: tablespace name */ + ulint* flags) /*!< out: tablespace flags */ +{ + ulint len; + const byte* field; + + /* Initialize the output values */ + *space = ULINT_UNDEFINED; + *name = NULL; + *flags = ULINT_UNDEFINED; + + if (rec_get_deleted_flag(rec, 0)) { + return("delete-marked record in SYS_TABLESPACES"); + } + + if (rec_get_n_fields_old(rec) != DICT_NUM_FIELDS__SYS_TABLESPACES) { + return("wrong number of columns in SYS_TABLESPACES record"); + } + + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_TABLESPACES__SPACE, &len); + if (len != DICT_FLD_LEN_SPACE) { +err_len: + return("incorrect column length in SYS_TABLESPACES"); + } + *space = mach_read_from_4(field); + + rec_get_nth_field_offs_old( + rec, DICT_FLD__SYS_TABLESPACES__DB_TRX_ID, &len); + if (len != DATA_TRX_ID_LEN && len != UNIV_SQL_NULL) { + goto err_len; + } + + rec_get_nth_field_offs_old( + rec, DICT_FLD__SYS_TABLESPACES__DB_ROLL_PTR, &len); + if (len != DATA_ROLL_PTR_LEN && len != UNIV_SQL_NULL) { + goto err_len; + } + + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_TABLESPACES__NAME, &len); + if (len == 0 || len == UNIV_SQL_NULL) { + goto err_len; + } + *name = mem_heap_strdupl(heap, (char*) field, len); + + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_TABLESPACES__FLAGS, &len); + if (len != DICT_FLD_LEN_FLAGS) { + goto err_len; + } + *flags = mach_read_from_4(field); + + return(NULL); +} + +/********************************************************************//** +This function parses a SYS_DATAFILES record, extracts necessary +information from the record and returns it to the caller. +@return error message, or NULL on success */ +UNIV_INTERN +const char* +dict_process_sys_datafiles( +/*=======================*/ + mem_heap_t* heap, /*!< in/out: heap memory */ + const rec_t* rec, /*!< in: current SYS_DATAFILES rec */ + ulint* space, /*!< out: space id */ + const char** path) /*!< out: datafile paths */ +{ + ulint len; + const byte* field; + + if (rec_get_deleted_flag(rec, 0)) { + return("delete-marked record in SYS_DATAFILES"); + } + + if (rec_get_n_fields_old(rec) != DICT_NUM_FIELDS__SYS_DATAFILES) { + return("wrong number of columns in SYS_DATAFILES record"); + } + + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_DATAFILES__SPACE, &len); + if (len != DICT_FLD_LEN_SPACE) { +err_len: + return("incorrect column length in SYS_DATAFILES"); + } + *space = mach_read_from_4(field); + + rec_get_nth_field_offs_old( + rec, DICT_FLD__SYS_DATAFILES__DB_TRX_ID, &len); + if (len != DATA_TRX_ID_LEN && len != UNIV_SQL_NULL) { + goto err_len; + } + + rec_get_nth_field_offs_old( + rec, DICT_FLD__SYS_DATAFILES__DB_ROLL_PTR, &len); + if (len != DATA_ROLL_PTR_LEN && len != UNIV_SQL_NULL) { + goto err_len; + } + + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_DATAFILES__PATH, &len); + if (len == 0 || len == UNIV_SQL_NULL) { + goto err_len; + } + *path = mem_heap_strdupl(heap, (char*) field, len); + + return(NULL); +} + +/********************************************************************//** Determine the flags of a table as stored in SYS_TABLES.TYPE and N_COLS. @return ULINT_UNDEFINED if error, else a valid dict_table_t::flags. */ static @@ -629,11 +748,9 @@ dict_sys_tables_get_flags( ut_a(len == 4); type = mach_read_from_4(field); - /* The low order bit of SYS_TABLES.TYPE is always set to 1. If no - other bits are used, that is defined as SYS_TABLE_TYPE_ANTELOPE. - But in dict_table_t::flags the low order bit is used to determine - if the row format is Redundant or Compact when the format is - Antelope. + /* The low order bit of SYS_TABLES.TYPE is always set to 1. But in + dict_table_t::flags the low order bit is used to determine if the + row format is Redundant or Compact when the format is Antelope. Read the 4 byte N_COLS field and look at the high order bit. It should be set for COMPACT and later. It should not be set for REDUNDANT. */ @@ -645,10 +762,193 @@ dict_sys_tables_get_flags( /* This validation function also combines the DICT_N_COLS_COMPACT flag in n_cols into the type field to effectively make it a dict_table_t::flags. */ - return(dict_sys_tables_type_validate(type, n_cols)); + + if (ULINT_UNDEFINED == dict_sys_tables_type_validate(type, n_cols)) { + return(ULINT_UNDEFINED); + } + + return(dict_sys_tables_type_to_tf(type, n_cols)); } /********************************************************************//** +Gets the filepath for a spaceid from SYS_DATAFILES and checks it against +the contents of a link file. This function is called when there is no +fil_node_t entry for this space ID so both durable locations on disk +must be checked and compared. +We use a temporary heap here for the table lookup, but not for the path +returned which the caller must free. +This function can return NULL if the space ID is not found in SYS_DATAFILES, +then the caller will assume that the ibd file is in the normal datadir. +@return own: A copy of the first datafile found in SYS_DATAFILES.PATH for +the given space ID. NULL if space ID is zero or not found. */ +UNIV_INTERN +char* +dict_get_first_path( +/*================*/ + ulint space, /*!< in: space id */ + const char* name) /*!< in: tablespace name */ +{ + mtr_t mtr; + dict_table_t* sys_datafiles; + dict_index_t* sys_index; + dtuple_t* tuple; + dfield_t* dfield; + byte* buf; + btr_pcur_t pcur; + const rec_t* rec; + const byte* field; + ulint len; + char* dict_filepath = NULL; + mem_heap_t* heap = mem_heap_create(1024); + + ut_ad(mutex_own(&(dict_sys->mutex))); + + mtr_start(&mtr); + + sys_datafiles = dict_table_get_low("SYS_DATAFILES"); + sys_index = UT_LIST_GET_FIRST(sys_datafiles->indexes); + ut_ad(!dict_table_is_comp(sys_datafiles)); + ut_ad(name_of_col_is(sys_datafiles, sys_index, + DICT_FLD__SYS_DATAFILES__SPACE, "SPACE")); + ut_ad(name_of_col_is(sys_datafiles, sys_index, + DICT_FLD__SYS_DATAFILES__PATH, "PATH")); + + tuple = dtuple_create(heap, 1); + dfield = dtuple_get_nth_field(tuple, DICT_FLD__SYS_DATAFILES__SPACE); + + buf = static_cast<byte*>(mem_heap_alloc(heap, 4)); + mach_write_to_4(buf, space); + + dfield_set_data(dfield, buf, 4); + dict_index_copy_types(tuple, sys_index, 1); + + btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &pcur, &mtr); + + rec = btr_pcur_get_rec(&pcur); + + /* If the file-per-table tablespace was created with + an earlier version of InnoDB, then this record is not + in SYS_DATAFILES. But a link file still might exist. */ + + if (btr_pcur_is_on_user_rec(&pcur)) { + /* A record for this space ID was found. */ + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_DATAFILES__PATH, &len); + ut_a(len > 0 || len == UNIV_SQL_NULL); + ut_a(len < OS_FILE_MAX_PATH); + dict_filepath = mem_strdupl((char*) field, len); + ut_a(dict_filepath); + } + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + mem_heap_free(heap); + + return(dict_filepath); +} + +/********************************************************************//** +Update the record for space_id in SYS_TABLESPACES to this filepath. +@return DB_SUCCESS if OK, dberr_t if the insert failed */ +UNIV_INTERN +dberr_t +dict_update_filepath( +/*=================*/ + ulint space_id, /*!< in: space id */ + const char* filepath) /*!< in: filepath */ +{ + dberr_t err = DB_SUCCESS; + trx_t* trx; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(mutex_own(&(dict_sys->mutex))); + + trx = trx_allocate_for_background(); + trx->op_info = "update filepath"; + trx->dict_operation_lock_mode = RW_X_LATCH; + trx_start_for_ddl(trx, TRX_DICT_OP_INDEX); + + pars_info_t* info = pars_info_create(); + + pars_info_add_int4_literal(info, "space", space_id); + pars_info_add_str_literal(info, "path", filepath); + + err = que_eval_sql(info, + "PROCEDURE UPDATE_FILEPATH () IS\n" + "BEGIN\n" + "UPDATE SYS_DATAFILES" + " SET PATH = :path\n" + " WHERE SPACE = :space;\n" + "END;\n", FALSE, trx); + + trx_commit_for_mysql(trx); + trx->dict_operation_lock_mode = 0; + trx_free_for_background(trx); + + if (err == DB_SUCCESS) { + /* We just updated SYS_DATAFILES due to the contents in + a link file. Make a note that we did this. */ + ib_logf(IB_LOG_LEVEL_INFO, + "The InnoDB data dictionary table SYS_DATAFILES " + "for tablespace ID %lu was updated to use file %s.", + (ulong) space_id, filepath); + } else { + ib_logf(IB_LOG_LEVEL_WARN, + "Problem updating InnoDB data dictionary table " + "SYS_DATAFILES for tablespace ID %lu to file %s.", + (ulong) space_id, filepath); + } + + return(err); +} + +/********************************************************************//** +Insert records into SYS_TABLESPACES and SYS_DATAFILES. +@return DB_SUCCESS if OK, dberr_t if the insert failed */ +UNIV_INTERN +dberr_t +dict_insert_tablespace_and_filepath( +/*================================*/ + ulint space, /*!< in: space id */ + const char* name, /*!< in: talespace name */ + const char* filepath, /*!< in: filepath */ + ulint fsp_flags) /*!< in: tablespace flags */ +{ + dberr_t err = DB_SUCCESS; + trx_t* trx; + +#ifdef UNIV_SYNC_DEBUG + ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX)); +#endif /* UNIV_SYNC_DEBUG */ + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_ad(filepath); + + trx = trx_allocate_for_background(); + trx->op_info = "insert tablespace and filepath"; + trx->dict_operation_lock_mode = RW_X_LATCH; + trx_start_for_ddl(trx, TRX_DICT_OP_INDEX); + + /* A record for this space ID was not found in + SYS_DATAFILES. Assume the record is also missing in + SYS_TABLESPACES. Insert records onto them both. */ + err = dict_create_add_tablespace_to_dictionary( + space, name, fsp_flags, filepath, trx, false); + + trx_commit_for_mysql(trx); + trx->dict_operation_lock_mode = 0; + trx_free_for_background(trx); + + return(err); +} + +/********************************************************************//** +This function looks at each table defined in SYS_TABLES. It checks the +tablespace for any table with a space_id > 0. It looks up the tablespace +in SYS_DATAFILES to ensure the correct path. + In a crash recovery we already have all the tablespace objects created. This function compares the space id information in the InnoDB data dictionary to what we already read with fil_load_single_table_tablespaces(). @@ -669,6 +969,7 @@ dict_check_tablespaces_and_store_max_id( ulint max_space_id; mtr_t mtr; + rw_lock_x_lock(&dict_operation_lock); mutex_enter(&(dict_sys->mutex)); mtr_start(&mtr); @@ -682,8 +983,8 @@ dict_check_tablespaces_and_store_max_id( MLOG_4BYTES, &mtr); fil_set_max_space_id_if_bigger(max_space_id); - btr_pcur_open_at_index_side(TRUE, sys_index, BTR_SEARCH_LEAF, &pcur, - TRUE, &mtr); + btr_pcur_open_at_index_side(true, sys_index, BTR_SEARCH_LEAF, &pcur, + true, 0, &mtr); loop: btr_pcur_move_to_next_user_rec(&pcur, &mtr); @@ -703,6 +1004,7 @@ loop: fil_set_max_space_id_if_bigger(max_space_id); mutex_exit(&(dict_sys->mutex)); + rw_lock_x_unlock(&dict_operation_lock); return; } @@ -718,8 +1020,14 @@ loop: field = rec_get_nth_field_old( rec, DICT_FLD__SYS_TABLES__NAME, &len); + name = mem_strdupl((char*) field, len); + char table_name[MAX_FULL_NAME_LEN + 1]; + + innobase_format_name( + table_name, sizeof(table_name), name, FALSE); + flags = dict_sys_tables_get_flags(rec); if (UNIV_UNLIKELY(flags == ULINT_UNDEFINED)) { /* Read again the 4 bytes from rec. */ @@ -728,13 +1036,9 @@ loop: ut_ad(len == 4); /* this was checked earlier */ flags = mach_read_from_4(field); - ut_print_timestamp(stderr); - fputs(" InnoDB: Error: table ", stderr); - ut_print_filename(stderr, name); - fprintf(stderr, "\n" - "InnoDB: in InnoDB data dictionary" - " has unknown type %lx.\n", - (ulong) flags); + ib_logf(IB_LOG_LEVEL_ERROR, + "Table '%s' in InnoDB data dictionary" + " has unknown type %lx", table_name, flags); goto loop; } @@ -749,43 +1053,84 @@ loop: mtr_commit(&mtr); + /* For tables created with old versions of InnoDB, + SYS_TABLES.MIX_LEN may contain garbage. Such tables + would always be in ROW_FORMAT=REDUNDANT. Pretend that + all such tables are non-temporary. That is, do not + suppress error printouts about temporary or discarded + tablespaces not being found. */ + + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_TABLES__MIX_LEN, &len); + + bool is_temp = false; + bool discarded = false; + ib_uint32_t flags2 = mach_read_from_4(field); + + /* Check that the tablespace (the .ibd file) really + exists; print a warning to the .err log if not. + Do not print warnings for temporary tables or for + tablespaces that have been discarded. */ + + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_TABLES__N_COLS, &len); + + /* MIX_LEN valid only for ROW_FORMAT > REDUNDANT. */ + if (mach_read_from_4(field) & DICT_N_COLS_COMPACT) { + + is_temp = !!(flags2 & DICT_TF2_TEMPORARY); + discarded = !!(flags2 & DICT_TF2_DISCARDED); + } + if (space_id == 0) { /* The system tablespace always exists. */ + ut_ad(!discarded); } else if (in_crash_recovery) { - /* Check that the tablespace (the .ibd file) really - exists; print a warning to the .err log if not. - Do not print warnings for temporary tables. */ - ibool is_temp; + /* All tablespaces should have been found in + fil_load_single_table_tablespaces(). */ - field = rec_get_nth_field_old( - rec, DICT_FLD__SYS_TABLES__N_COLS, &len); - if (mach_read_from_4(field) & DICT_N_COLS_COMPACT) { - /* ROW_FORMAT=COMPACT: read the is_temp - flag from SYS_TABLES.MIX_LEN. */ - field = rec_get_nth_field_old( - rec, 7/*MIX_LEN*/, &len); - is_temp = !!(mach_read_from_4(field) - & DICT_TF2_TEMPORARY); - } else { - /* For tables created with old versions - of InnoDB, SYS_TABLES.MIX_LEN may contain - garbage. Such tables would always be - in ROW_FORMAT=REDUNDANT. Pretend that - all such tables are non-temporary. That is, - do not suppress error printouts about - temporary tables not being found. */ - is_temp = FALSE; + fil_space_for_table_exists_in_mem( + space_id, name, TRUE, !(is_temp || discarded), + false, NULL, 0); + + } else if (!discarded) { + + /* It is a normal database startup: create the + space object and check that the .ibd file exists. + If the table uses a remote tablespace, look for the + space_id in SYS_DATAFILES to find the filepath */ + + /* Use the remote filepath if known. */ + char* filepath = NULL; + if (DICT_TF_HAS_DATA_DIR(flags)) { + filepath = dict_get_first_path( + space_id, name); } - fil_space_for_table_exists_in_mem( - space_id, name, TRUE, !is_temp); - } else { - /* It is a normal database startup: create the space - object and check that the .ibd file exists. */ + /* We set the 2nd param (fix_dict = true) + here because we already have an x-lock on + dict_operation_lock and dict_sys->mutex. Besides, + this is at startup and we are now single threaded. + If the filepath is not known, it will need to + be discovered. */ + dberr_t err = fil_open_single_table_tablespace( + false, srv_read_only_mode ? false : true, + space_id, dict_tf_to_fsp_flags(flags), + name, filepath); + + if (err != DB_SUCCESS) { + ib_logf(IB_LOG_LEVEL_ERROR, + "Tablespace open failed for '%s', " + "ignored.", table_name); + } - fil_open_single_table_tablespace( - FALSE, space_id, - dict_tf_to_fsp_flags(flags), name); + if (filepath) { + mem_free(filepath); + } + } else { + ib_logf(IB_LOG_LEVEL_INFO, + "DISCARD flag set for table '%s', ignored.", + table_name); } mem_free(name); @@ -879,7 +1224,7 @@ err_len: field = rec_get_nth_field_old( rec, DICT_FLD__SYS_COLUMNS__NAME, &len); - if (len < 1 || len == UNIV_SQL_NULL) { + if (len == 0 || len == UNIV_SQL_NULL) { goto err_len; } @@ -1003,6 +1348,11 @@ dict_load_columns( err_msg = dict_load_column_low(table, heap, NULL, NULL, &name, rec); + if (err_msg) { + fprintf(stderr, "InnoDB: %s\n", err_msg); + ut_error; + } + /* Note: Currently we have one DOC_ID column that is shared by all FTS indexes on a table. */ if (innobase_strcasecmp(name, @@ -1037,11 +1387,6 @@ dict_load_columns( table->fts->doc_col = i; } - if (err_msg) { - fprintf(stderr, "InnoDB: %s\n", err_msg); - ut_error; - } - btr_pcur_move_to_next_user_rec(&pcur, &mtr); } @@ -1154,7 +1499,7 @@ err_len: field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FIELDS__COL_NAME, &len); - if (len < 1 || len == UNIV_SQL_NULL) { + if (len == 0 || len == UNIV_SQL_NULL) { goto err_len; } @@ -1194,7 +1539,7 @@ dict_load_fields( byte* buf; ulint i; mtr_t mtr; - ulint error; + dberr_t error; ut_ad(mutex_own(&(dict_sys->mutex))); @@ -1394,8 +1739,8 @@ Loads definitions for table indexes. Adds them to the data dictionary cache. @return DB_SUCCESS if ok, DB_CORRUPTION if corruption of dictionary table or DB_UNSUPPORTED if table has unknown index type */ -static -ulint +static __attribute__((nonnull)) +dberr_t dict_load_indexes( /*==============*/ dict_table_t* table, /*!< in/out: table */ @@ -1412,7 +1757,7 @@ dict_load_indexes( const rec_t* rec; byte* buf; mtr_t mtr; - ulint error = DB_SUCCESS; + dberr_t error = DB_SUCCESS; ut_ad(mutex_own(&(dict_sys->mutex))); @@ -1443,6 +1788,21 @@ dict_load_indexes( if (!btr_pcur_is_on_user_rec(&pcur)) { + /* We should allow the table to open even + without index when DICT_ERR_IGNORE_CORRUPT is set. + DICT_ERR_IGNORE_CORRUPT is currently only set + for drop table */ + if (dict_table_get_first_index(table) == NULL + && !(ignore_err & DICT_ERR_IGNORE_CORRUPT)) { + ib_logf(IB_LOG_LEVEL_WARN, + "Cannot load table %s " + "because it has no indexes in " + "InnoDB internal data dictionary.", + table->name); + error = DB_CORRUPTION; + goto func_exit; + } + break; } @@ -1456,6 +1816,20 @@ dict_load_indexes( if (err_msg == dict_load_index_id_err) { /* TABLE_ID mismatch means that we have run out of index definitions for the table. */ + + if (dict_table_get_first_index(table) == NULL + && !(ignore_err & DICT_ERR_IGNORE_CORRUPT)) { + ib_logf(IB_LOG_LEVEL_WARN, + "Failed to load the " + "clustered index for table %s " + "because of the following error: %s. " + "Refusing to load the rest of the " + "indexes (if any) and the whole table " + "altogether.", table->name, err_msg); + error = DB_CORRUPTION; + goto func_exit; + } + break; } else if (err_msg == dict_load_index_del) { /* Skip delete-marked records. */ @@ -1510,15 +1884,15 @@ dict_load_indexes( subsequent checks are relevant for the supported types. */ if (index->type & ~(DICT_CLUSTERED | DICT_UNIQUE | DICT_CORRUPT | DICT_FTS)) { - fprintf(stderr, - "InnoDB: Error: unknown type %lu" - " of index %s of table %s\n", + ib_logf(IB_LOG_LEVEL_ERROR, + "Unknown type %lu of index %s of table %s", (ulong) index->type, index->name, table->name); error = DB_UNSUPPORTED; dict_mem_index_free(index); goto func_exit; } else if (index->page == FIL_NULL + && !table->ibd_file_missing && (!(index->type & DICT_FTS))) { fprintf(stderr, @@ -1560,7 +1934,7 @@ corrupted: " is not clustered!\n", stderr); goto corrupted; - } else if (table->id < DICT_HDR_FIRST_ID + } else if (dict_is_sys_table(table->id) && (dict_index_is_clust(index) || ((table == dict_sys->sys_tables) && !strcmp("ID_IND", index->name)))) { @@ -1570,8 +1944,10 @@ corrupted: dict_mem_index_free(index); } else { dict_load_fields(index, heap); - error = dict_index_add_to_cache(table, index, - index->page, FALSE); + + error = dict_index_add_to_cache( + table, index, index->page, FALSE); + /* The data dictionary tables should never contain invalid index definitions. If we ignored this error and simply did not load this index definition, the @@ -1629,7 +2005,7 @@ dict_load_table_low( rec_get_nth_field_offs_old( rec, DICT_FLD__SYS_TABLES__NAME, &len); - if (len < 1 || len == UNIV_SQL_NULL) { + if (len == 0 || len == UNIV_SQL_NULL) { err_len: return("incorrect column length in SYS_TABLES"); } @@ -1751,6 +2127,77 @@ err_len: } /********************************************************************//** +Using the table->heap, copy the null-terminated filepath into +table->data_dir_path and replace the 'databasename/tablename.ibd' +portion with 'tablename'. +This allows SHOW CREATE TABLE to return the correct DATA DIRECTORY path. +Make this data directory path only if it has not yet been saved. */ +UNIV_INTERN +void +dict_save_data_dir_path( +/*====================*/ + dict_table_t* table, /*!< in/out: table */ + char* filepath) /*!< in: filepath of tablespace */ +{ + ut_ad(mutex_own(&(dict_sys->mutex))); + ut_a(DICT_TF_HAS_DATA_DIR(table->flags)); + + ut_a(!table->data_dir_path); + ut_a(filepath); + + /* Be sure this filepath is not the default filepath. */ + char* default_filepath = fil_make_ibd_name(table->name, false); + if (strcmp(filepath, default_filepath)) { + ulint pathlen = strlen(filepath); + ut_a(pathlen < OS_FILE_MAX_PATH); + ut_a(0 == strcmp(filepath + pathlen - 4, ".ibd")); + + table->data_dir_path = mem_heap_strdup(table->heap, filepath); + os_file_make_data_dir_path(table->data_dir_path); + } else { + /* This does not change SYS_DATAFILES or SYS_TABLES + or FSP_FLAGS on the header page of the tablespace, + but it makes dict_table_t consistent */ + table->flags &= ~DICT_TF_MASK_DATA_DIR; + } + mem_free(default_filepath); +} + +/*****************************************************************//** +Make sure the data_file_name is saved in dict_table_t if needed. Try to +read it from the file dictionary first, then from SYS_DATAFILES. */ +UNIV_INTERN +void +dict_get_and_save_data_dir_path( +/*============================*/ + dict_table_t* table, /*!< in/out: table */ + bool dict_mutex_own) /*!< in: true if dict_sys->mutex + is owned already */ +{ + if (DICT_TF_HAS_DATA_DIR(table->flags) + && (!table->data_dir_path)) { + char* path = fil_space_get_first_path(table->space); + + if (!dict_mutex_own) { + dict_mutex_enter_for_mysql(); + } + if (!path) { + path = dict_get_first_path( + table->space, table->name); + } + + if (path) { + dict_save_data_dir_path(table, path); + mem_free(path); + } + + if (!dict_mutex_own) { + dict_mutex_exit_for_mysql(); + } + } +} + +/********************************************************************//** Loads a table definition and also all its index definitions, and also the cluster definition if the table is a member in a cluster. Also loads all foreign key constraints where the foreign key is in the table or where @@ -1770,6 +2217,7 @@ dict_load_table( /*!< in: error to be ignored when loading table and its indexes' definition */ { + dberr_t err; dict_table_t* table; dict_table_t* sys_tables; btr_pcur_t pcur; @@ -1780,7 +2228,7 @@ dict_load_table( const rec_t* rec; const byte* field; ulint len; - ulint err; + char* filepath = NULL; const char* err_msg; mtr_t mtr; @@ -1843,39 +2291,71 @@ err_exit: goto err_exit; } + char table_name[MAX_FULL_NAME_LEN + 1]; + + innobase_format_name(table_name, sizeof(table_name), name, FALSE); + + btr_pcur_close(&pcur); + mtr_commit(&mtr); + if (table->space == 0) { /* The system tablespace is always available. */ + } else if (table->flags2 & DICT_TF2_DISCARDED) { + + ib_logf(IB_LOG_LEVEL_WARN, + "Table '%s' tablespace is set as discarded.", + table_name); + + table->ibd_file_missing = TRUE; + } else if (!fil_space_for_table_exists_in_mem( - table->space, name, FALSE, FALSE)) { + table->space, name, FALSE, FALSE, true, heap, + table->id)) { - if (table->flags2 & DICT_TF2_TEMPORARY) { + if (DICT_TF2_FLAG_IS_SET(table, DICT_TF2_TEMPORARY)) { /* Do not bother to retry opening temporary tables. */ table->ibd_file_missing = TRUE; + } else { - ut_print_timestamp(stderr); - fprintf(stderr, - " InnoDB: error: space object of table "); - ut_print_filename(stderr, name); - fprintf(stderr, ",\n" - "InnoDB: space id %lu did not exist in memory." - " Retrying an open.\n", - (ulong) table->space); - /* Try to open the tablespace */ - if (!fil_open_single_table_tablespace( - TRUE, table->space, + ib_logf(IB_LOG_LEVEL_ERROR, + "Failed to find tablespace for table '%s' " + "in the cache. Attempting to load the " + "tablespace with space id %lu.", + table_name, (ulong) table->space); + + /* Use the remote filepath if needed. */ + if (DICT_TF_HAS_DATA_DIR(table->flags)) { + /* This needs to be added to the table + from SYS_DATAFILES */ + dict_get_and_save_data_dir_path(table, true); + + if (table->data_dir_path) { + filepath = os_file_make_remote_pathname( + table->data_dir_path, + table->name, "ibd"); + } + } + + /* Try to open the tablespace. We set the + 2nd param (fix_dict = false) here because we + do not have an x-lock on dict_operation_lock */ + err = fil_open_single_table_tablespace( + true, false, table->space, dict_tf_to_fsp_flags(table->flags), - name)) { + name, filepath); + + if (err != DB_SUCCESS) { /* We failed to find a sensible tablespace file */ table->ibd_file_missing = TRUE; } + if (filepath) { + mem_free(filepath); + } } } - btr_pcur_close(&pcur); - mtr_commit(&mtr); - dict_load_columns(table, heap); if (cached) { @@ -1886,7 +2366,15 @@ err_exit: mem_heap_empty(heap); - err = dict_load_indexes(table, heap, ignore_err); + /* If there is no tablespace for the table then we only need to + load the index definitions. So that we can IMPORT the tablespace + later. */ + if (table->ibd_file_missing) { + err = dict_load_indexes( + table, heap, DICT_ERR_IGNORE_ALL); + } else { + err = dict_load_indexes(table, heap, ignore_err); + } if (err == DB_INDEX_CORRUPT) { /* Refuse to load the table if the table has a corrupted @@ -1920,7 +2408,8 @@ err_exit: of the error condition, since the user may want to dump data from the clustered index. However we load the foreign key information only if all indexes were loaded. */ - if (!cached) { + if (!cached || table->ibd_file_missing) { + /* Don't attempt to load the indexes from disk. */ } else if (err == DB_SUCCESS) { err = dict_load_foreigns(table->name, TRUE, TRUE); @@ -1937,11 +2426,15 @@ err_exit: Otherwise refuse to load the table */ index = dict_table_get_first_index(table); - if (!srv_force_recovery || !index + if (!srv_force_recovery + || !index || !dict_index_is_clust(index)) { + dict_table_remove_from_cache(table); table = NULL; - } else if (dict_index_is_corrupted(index)) { + + } else if (dict_index_is_corrupted(index) + && !table->ibd_file_missing) { /* It is possible we force to load a corrupted clustered index if srv_load_corrupted is set. @@ -1949,36 +2442,28 @@ err_exit: table->corrupted = TRUE; } } -#if 0 - if (err != DB_SUCCESS && table != NULL) { - mutex_enter(&dict_foreign_err_mutex); - - ut_print_timestamp(stderr); - - fprintf(stderr, - " InnoDB: Error: could not make a foreign key" - " definition to match\n" - "InnoDB: the foreign key table" - " or the referenced table!\n" - "InnoDB: The data dictionary of InnoDB is corrupt." - " You may need to drop\n" - "InnoDB: and recreate the foreign key table" - " or the referenced table.\n" - "InnoDB: Submit a detailed bug report" - " to http://bugs.mysql.com\n" - "InnoDB: Latest foreign key error printout:\n%s\n", - dict_foreign_err_buf); - - mutex_exit(&dict_foreign_err_mutex); - } -#endif /* 0 */ func_exit: mem_heap_free(heap); - ut_ad(!table || ignore_err != DICT_ERR_IGNORE_NONE + ut_ad(!table + || ignore_err != DICT_ERR_IGNORE_NONE + || table->ibd_file_missing || !table->corrupted); + if (table && table->fts) { + if (!(dict_table_has_fts_index(table) + || DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID) + || DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_ADD_DOC_ID))) { + /* the table->fts could be created in dict_load_column + when a user defined FTS_DOC_ID is present, but no + FTS */ + fts_free(table); + } else { + fts_optimize_add_table(table); + } + } + return(table); } @@ -2019,6 +2504,7 @@ dict_load_table_on_id( sys_table_ids = dict_table_get_next_index( dict_table_get_first_index(sys_tables)); ut_ad(!dict_table_is_comp(sys_tables)); + ut_ad(!dict_index_is_clust(sys_table_ids)); heap = mem_heap_create(256); tuple = dtuple_create(heap, 1); @@ -2099,15 +2585,20 @@ dict_load_sys_table( } /********************************************************************//** -Loads foreign key constraint col names (also for the referenced table). */ +Loads foreign key constraint col names (also for the referenced table). +Members that must be set (and valid) in foreign: +foreign->heap +foreign->n_fields +foreign->id ('\0'-terminated) +Members that will be created and set by this function: +foreign->foreign_col_names[i] +foreign->referenced_col_names[i] +(for i=0..foreign->n_fields-1) */ static void dict_load_foreign_cols( /*===================*/ - const char* id, /*!< in: foreign constraint id, not - necessary '\0'-terminated */ - ulint id_len, /*!< in: id length */ - dict_foreign_t* foreign)/*!< in: foreign constraint object */ + dict_foreign_t* foreign)/*!< in/out: foreign constraint object */ { dict_table_t* sys_foreign_cols; dict_index_t* sys_index; @@ -2119,9 +2610,12 @@ dict_load_foreign_cols( ulint len; ulint i; mtr_t mtr; + size_t id_len; ut_ad(mutex_own(&(dict_sys->mutex))); + id_len = strlen(foreign->id); + foreign->foreign_col_names = static_cast<const char**>( mem_heap_alloc(foreign->heap, foreign->n_fields * sizeof(void*))); @@ -2140,7 +2634,7 @@ dict_load_foreign_cols( tuple = dtuple_create(foreign->heap, 1); dfield = dtuple_get_nth_field(tuple, 0); - dfield_set_data(dfield, id, id_len); + dfield_set_data(dfield, foreign->id, id_len); dict_index_copy_types(tuple, sys_index, 1); btr_pcur_open_on_user_rec(sys_index, tuple, PAGE_CUR_GE, @@ -2154,8 +2648,42 @@ dict_load_foreign_cols( field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN_COLS__ID, &len); - ut_a(len == id_len); - ut_a(ut_memcmp(id, field, len) == 0); + + if (len != id_len || ut_memcmp(foreign->id, field, len) != 0) { + const rec_t* pos; + ulint pos_len; + const rec_t* for_col_name; + ulint for_col_name_len; + const rec_t* ref_col_name; + ulint ref_col_name_len; + + pos = rec_get_nth_field_old( + rec, DICT_FLD__SYS_FOREIGN_COLS__POS, + &pos_len); + + for_col_name = rec_get_nth_field_old( + rec, DICT_FLD__SYS_FOREIGN_COLS__FOR_COL_NAME, + &for_col_name_len); + + ref_col_name = rec_get_nth_field_old( + rec, DICT_FLD__SYS_FOREIGN_COLS__REF_COL_NAME, + &ref_col_name_len); + + ib_logf(IB_LOG_LEVEL_ERROR, + "Unable to load columns names for foreign " + "key '%s' because it was not found in " + "InnoDB internal table SYS_FOREIGN_COLS. The " + "closest entry we found is: " + "(ID='%.*s', POS=%lu, FOR_COL_NAME='%.*s', " + "REF_COL_NAME='%.*s')", + foreign->id, + (int) len, field, + mach_read_from_4(pos), + (int) for_col_name_len, for_col_name, + (int) ref_col_name_len, ref_col_name); + + ut_error; + } field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN_COLS__POS, &len); @@ -2182,13 +2710,12 @@ dict_load_foreign_cols( /***********************************************************************//** Loads a foreign key constraint to the dictionary cache. @return DB_SUCCESS or error code */ -static -ulint +static __attribute__((nonnull, warn_unused_result)) +dberr_t dict_load_foreign( /*==============*/ - const char* id, /*!< in: foreign constraint id, not - necessary '\0'-terminated */ - ulint id_len, /*!< in: id length */ + const char* id, /*!< in: foreign constraint id, must be + '\0'-terminated */ ibool check_charsets, /*!< in: TRUE=check charset compatibility */ ibool check_recursive) @@ -2210,9 +2737,12 @@ dict_load_foreign( mtr_t mtr; dict_table_t* for_table; dict_table_t* ref_table; + size_t id_len; ut_ad(mutex_own(&(dict_sys->mutex))); + id_len = strlen(id); + heap2 = mem_heap_create(1000); mtr_start(&mtr); @@ -2238,8 +2768,8 @@ dict_load_foreign( fprintf(stderr, "InnoDB: Error: cannot load foreign constraint " - "%.*s: could not find the relevant record in " - "SYS_FOREIGN\n", (int) id_len, id); + "%s: could not find the relevant record in " + "SYS_FOREIGN\n", id); btr_pcur_close(&pcur); mtr_commit(&mtr); @@ -2255,8 +2785,8 @@ dict_load_foreign( fprintf(stderr, "InnoDB: Error: cannot load foreign constraint " - "%.*s: found %.*s instead in SYS_FOREIGN\n", - (int) id_len, id, (int) len, field); + "%s: found %.*s instead in SYS_FOREIGN\n", + id, (int) len, field); btr_pcur_close(&pcur); mtr_commit(&mtr); @@ -2301,7 +2831,7 @@ dict_load_foreign( btr_pcur_close(&pcur); mtr_commit(&mtr); - dict_load_foreign_cols(id, id_len, foreign); + dict_load_foreign_cols(foreign); ref_table = dict_table_check_if_in_cache_low( foreign->referenced_table_name_lookup); @@ -2371,7 +2901,7 @@ cache already contains all constraints where the other relevant table is already in the dictionary cache. @return DB_SUCCESS or error code */ UNIV_INTERN -ulint +dberr_t dict_load_foreigns( /*===============*/ const char* table_name, /*!< in: table name */ @@ -2389,7 +2919,7 @@ dict_load_foreigns( const rec_t* rec; const byte* field; ulint len; - ulint err; + dberr_t err; mtr_t mtr; ut_ad(mutex_own(&(dict_sys->mutex))); @@ -2414,6 +2944,7 @@ dict_load_foreigns( sec_index = dict_table_get_next_index( dict_table_get_first_index(sys_foreign)); + ut_ad(!dict_index_is_clust(sec_index)); start_load: tuple = dtuple_create_from_mem(tuple_buf, sizeof(tuple_buf), 1); @@ -2436,7 +2967,6 @@ loop: /* Now we have the record in the secondary index containing a table name and a foreign constraint ID */ - rec = btr_pcur_get_rec(&pcur); field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN_FOR_NAME__NAME, &len); @@ -2475,14 +3005,21 @@ loop: field = rec_get_nth_field_old( rec, DICT_FLD__SYS_FOREIGN_FOR_NAME__ID, &len); + /* Copy the string because the page may be modified or evicted + after mtr_commit() below. */ + char fk_id[MAX_TABLE_NAME_LEN + 1]; + + ut_a(len <= MAX_TABLE_NAME_LEN); + memcpy(fk_id, field, len); + fk_id[len] = '\0'; + btr_pcur_store_position(&pcur, &mtr); mtr_commit(&mtr); /* Load the foreign constraint definition to the dictionary cache */ - err = dict_load_foreign((char*) field, len, check_charsets, - check_recursive); + err = dict_load_foreign(fk_id, check_charsets, check_recursive); if (err != DB_SUCCESS) { btr_pcur_close(&pcur); |