ha_innodb.cpp
上传用户:romrleung
上传日期:2022-05-23
资源大小:18897k
文件大小:165k
- uint keynr) /* in: key (index) number */
- {
- int error = 0;
- DBUG_ENTER("index_init");
- error = change_active_index(keynr);
- DBUG_RETURN(error);
- }
- /**********************************************************************
- Currently does nothing. */
- int
- ha_innobase::index_end(void)
- /*========================*/
- {
- int error = 0;
- DBUG_ENTER("index_end");
- active_index=MAX_KEY;
- DBUG_RETURN(error);
- }
- /*************************************************************************
- Converts a search mode flag understood by MySQL to a flag understood
- by InnoDB. */
- inline
- ulint
- convert_search_mode_to_innobase(
- /*============================*/
- enum ha_rkey_function find_flag)
- {
- switch (find_flag) {
- case HA_READ_KEY_EXACT: return(PAGE_CUR_GE);
- /* the above does not require the index to be UNIQUE */
- case HA_READ_KEY_OR_NEXT: return(PAGE_CUR_GE);
- case HA_READ_KEY_OR_PREV: return(PAGE_CUR_LE);
- case HA_READ_AFTER_KEY: return(PAGE_CUR_G);
- case HA_READ_BEFORE_KEY: return(PAGE_CUR_L);
- case HA_READ_PREFIX: return(PAGE_CUR_GE);
- case HA_READ_PREFIX_LAST: return(PAGE_CUR_LE);
- case HA_READ_PREFIX_LAST_OR_PREV:return(PAGE_CUR_LE);
- /* In MySQL-4.0 HA_READ_PREFIX and HA_READ_PREFIX_LAST always
- pass a complete-field prefix of a key value as the search
- tuple. I.e., it is not allowed that the last field would
- just contain n first bytes of the full field value.
- MySQL uses a 'padding' trick to convert LIKE 'abc%'
- type queries so that it can use as a search tuple
- a complete-field-prefix of a key value. Thus, the InnoDB
- search mode PAGE_CUR_LE_OR_EXTENDS is never used.
- TODO: when/if MySQL starts to use also partial-field
- prefixes, we have to deal with stripping of spaces
- and comparison of non-latin1 char type fields in
- innobase_mysql_cmp() to get PAGE_CUR_LE_OR_EXTENDS to
- work correctly. */
- default: assert(0);
- }
- return(0);
- }
- /*
- BACKGROUND INFO: HOW A SELECT SQL QUERY IS EXECUTED
- ---------------------------------------------------
- The following does not cover all the details, but explains how we determine
- the start of a new SQL statement, and what is associated with it.
- For each table in the database the MySQL interpreter may have several
- table handle instances in use, also in a single SQL query. For each table
- handle instance there is an InnoDB 'prebuilt' struct which contains most
- of the InnoDB data associated with this table handle instance.
- A) if the user has not explicitly set any MySQL table level locks:
- 1) MySQL calls ::external_lock to set an 'intention' table level lock on
- the table of the handle instance. There we set
- prebuilt->sql_stat_start = TRUE. The flag sql_stat_start should be set
- true if we are taking this table handle instance to use in a new SQL
- statement issued by the user. We also increment trx->n_mysql_tables_in_use.
- 2) If prebuilt->sql_stat_start == TRUE we 'pre-compile' the MySQL search
- instructions to prebuilt->template of the table handle instance in
- ::index_read. The template is used to save CPU time in large joins.
- 3) In row_search_for_mysql, if prebuilt->sql_stat_start is true, we
- allocate a new consistent read view for the trx if it does not yet have one,
- or in the case of a locking read, set an InnoDB 'intention' table level
- lock on the table.
- 4) We do the SELECT. MySQL may repeatedly call ::index_read for the
- same table handle instance, if it is a join.
- 5) When the SELECT ends, MySQL removes its intention table level locks
- in ::external_lock. When trx->n_mysql_tables_in_use drops to zero,
- (a) we execute a COMMIT there if the autocommit is on,
- (b) we also release possible 'SQL statement level resources' InnoDB may
- have for this SQL statement. The MySQL interpreter does NOT execute
- autocommit for pure read transactions, though it should. That is why the
- table handler in that case has to execute the COMMIT in ::external_lock.
- B) If the user has explicitly set MySQL table level locks, then MySQL
- does NOT call ::external_lock at the start of the statement. To determine
- when we are at the start of a new SQL statement we at the start of
- ::index_read also compare the query id to the latest query id where the
- table handle instance was used. If it has changed, we know we are at the
- start of a new SQL statement. Since the query id can theoretically
- overwrap, we use this test only as a secondary way of determining the
- start of a new SQL statement. */
- /**************************************************************************
- Positions an index cursor to the index specified in the handle. Fetches the
- row if any. */
- int
- ha_innobase::index_read(
- /*====================*/
- /* out: 0, HA_ERR_KEY_NOT_FOUND,
- or error number */
- mysql_byte* buf, /* in/out: buffer for the returned
- row */
- const mysql_byte* key_ptr,/* in: key value; if this is NULL
- we position the cursor at the
- start or end of index; this can
- also contain an InnoDB row id, in
- which case key_len is the InnoDB
- row id length; the key value can
- also be a prefix of a full key value,
- and the last column can be a prefix
- of a full column */
- uint key_len,/* in: key value length */
- enum ha_rkey_function find_flag)/* in: search flags from my_base.h */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- ulint mode;
- dict_index_t* index;
- ulint match_mode = 0;
- int error;
- ulint ret;
- DBUG_ENTER("index_read");
- ut_ad(prebuilt->trx ==
- (trx_t*) current_thd->transaction.all.innobase_tid);
- statistic_increment(ha_read_key_count, &LOCK_status);
- if (last_query_id != user_thd->query_id) {
- prebuilt->sql_stat_start = TRUE;
- last_query_id = user_thd->query_id;
- innobase_release_stat_resources(prebuilt->trx);
- }
- index = prebuilt->index;
- /* Note that if the index for which the search template is built is not
- necessarily prebuilt->index, but can also be the clustered index */
- if (prebuilt->sql_stat_start) {
- build_template(prebuilt, user_thd, table,
- ROW_MYSQL_REC_FIELDS);
- }
- if (key_ptr) {
- /* Convert the search key value to InnoDB format into
- prebuilt->search_tuple */
- row_sel_convert_mysql_key_to_innobase(prebuilt->search_tuple,
- (byte*) key_val_buff,
- (ulint)upd_and_key_val_buff_len,
- index,
- (byte*) key_ptr,
- (ulint) key_len, prebuilt->trx);
- } else {
- /* We position the cursor to the last or the first entry
- in the index */
- dtuple_set_n_fields(prebuilt->search_tuple, 0);
- }
- mode = convert_search_mode_to_innobase(find_flag);
- match_mode = 0;
- if (find_flag == HA_READ_KEY_EXACT) {
- match_mode = ROW_SEL_EXACT;
- } else if (find_flag == HA_READ_PREFIX
- || find_flag == HA_READ_PREFIX_LAST) {
- match_mode = ROW_SEL_EXACT_PREFIX;
- }
- last_match_mode = match_mode;
- innodb_srv_conc_enter_innodb(prebuilt->trx);
- ret = row_search_for_mysql((byte*) buf, mode, prebuilt, match_mode, 0);
- innodb_srv_conc_exit_innodb(prebuilt->trx);
- if (ret == DB_SUCCESS) {
- error = 0;
- table->status = 0;
- } else if (ret == DB_RECORD_NOT_FOUND) {
- error = HA_ERR_KEY_NOT_FOUND;
- table->status = STATUS_NOT_FOUND;
- } else if (ret == DB_END_OF_INDEX) {
- error = HA_ERR_KEY_NOT_FOUND;
- table->status = STATUS_NOT_FOUND;
- } else {
- error = convert_error_code_to_mysql(ret, user_thd);
- table->status = STATUS_NOT_FOUND;
- }
- DBUG_RETURN(error);
- }
- /***********************************************************************
- The following functions works like index_read, but it find the last
- row with the current key value or prefix. */
- int
- ha_innobase::index_read_last(
- /*=========================*/
- /* out: 0, HA_ERR_KEY_NOT_FOUND, or an
- error code */
- mysql_byte* buf, /* out: fetched row */
- const mysql_byte* key_ptr, /* in: key value, or a prefix of a full
- key value */
- uint key_len) /* in: length of the key val or prefix
- in bytes */
- {
- return(index_read(buf, key_ptr, key_len, HA_READ_PREFIX_LAST));
- }
- /************************************************************************
- Changes the active index of a handle. */
- int
- ha_innobase::change_active_index(
- /*=============================*/
- /* out: 0 or error code */
- uint keynr) /* in: use this index; MAX_KEY means always clustered
- index, even if it was internally generated by
- InnoDB */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- KEY* key=0;
- statistic_increment(ha_read_key_count, &LOCK_status);
- DBUG_ENTER("change_active_index");
- ut_ad(user_thd == current_thd);
- ut_ad(prebuilt->trx ==
- (trx_t*) current_thd->transaction.all.innobase_tid);
- active_index = keynr;
- if (keynr != MAX_KEY && table->keys > 0) {
- key = table->key_info + active_index;
- prebuilt->index = dict_table_get_index_noninline(
- prebuilt->table,
- key->name);
- } else {
- prebuilt->index = dict_table_get_first_index_noninline(
- prebuilt->table);
- }
- if (!prebuilt->index) {
- sql_print_error(
- "Innodb could not find key n:o %u with name %s from dict cache for table %s",
- keynr, key ? key->name : "NULL", prebuilt->table->name);
- DBUG_RETURN(1);
- }
- assert(prebuilt->search_tuple != 0);
- dtuple_set_n_fields(prebuilt->search_tuple, prebuilt->index->n_fields);
- dict_index_copy_types(prebuilt->search_tuple, prebuilt->index,
- prebuilt->index->n_fields);
- /* MySQL changes the active index for a handle also during some
- queries, for example SELECT MAX(a), SUM(a) first retrieves the MAX()
- and then calculates the sum. Previously we played safe and used
- the flag ROW_MYSQL_WHOLE_ROW below, but that caused unnecessary
- copying. Starting from MySQL-4.1 we use a more efficient flag here. */
- build_template(prebuilt, user_thd, table, ROW_MYSQL_REC_FIELDS);
- DBUG_RETURN(0);
- }
- /**************************************************************************
- Positions an index cursor to the index specified in keynr. Fetches the
- row if any. */
- /* ??? This is only used to read whole keys ??? */
- int
- ha_innobase::index_read_idx(
- /*========================*/
- /* out: error number or 0 */
- mysql_byte* buf, /* in/out: buffer for the returned
- row */
- uint keynr, /* in: use this index */
- const mysql_byte* key, /* in: key value; if this is NULL
- we position the cursor at the
- start or end of index */
- uint key_len, /* in: key value length */
- enum ha_rkey_function find_flag)/* in: search flags from my_base.h */
- {
- if (change_active_index(keynr)) {
- return(1);
- }
- return(index_read(buf, key, key_len, find_flag));
- }
- /***************************************************************************
- Reads the next or previous row from a cursor, which must have previously been
- positioned using index_read. */
- int
- ha_innobase::general_fetch(
- /*=======================*/
- /* out: 0, HA_ERR_END_OF_FILE, or error
- number */
- mysql_byte* buf, /* in/out: buffer for next row in MySQL
- format */
- uint direction, /* in: ROW_SEL_NEXT or ROW_SEL_PREV */
- uint match_mode) /* in: 0, ROW_SEL_EXACT, or
- ROW_SEL_EXACT_PREFIX */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- ulint ret;
- int error = 0;
- DBUG_ENTER("general_fetch");
- ut_ad(prebuilt->trx ==
- (trx_t*) current_thd->transaction.all.innobase_tid);
- innodb_srv_conc_enter_innodb(prebuilt->trx);
- ret = row_search_for_mysql((byte*)buf, 0, prebuilt, match_mode,
- direction);
- innodb_srv_conc_exit_innodb(prebuilt->trx);
- if (ret == DB_SUCCESS) {
- error = 0;
- table->status = 0;
- } else if (ret == DB_RECORD_NOT_FOUND) {
- error = HA_ERR_END_OF_FILE;
- table->status = STATUS_NOT_FOUND;
- } else if (ret == DB_END_OF_INDEX) {
- error = HA_ERR_END_OF_FILE;
- table->status = STATUS_NOT_FOUND;
- } else {
- error = convert_error_code_to_mysql(ret, user_thd);
- table->status = STATUS_NOT_FOUND;
- }
- DBUG_RETURN(error);
- }
- /***************************************************************************
- Reads the next row from a cursor, which must have previously been
- positioned using index_read. */
- int
- ha_innobase::index_next(
- /*====================*/
- /* out: 0, HA_ERR_END_OF_FILE, or error
- number */
- mysql_byte* buf) /* in/out: buffer for next row in MySQL
- format */
- {
- statistic_increment(ha_read_next_count, &LOCK_status);
- return(general_fetch(buf, ROW_SEL_NEXT, 0));
- }
- /***********************************************************************
- Reads the next row matching to the key value given as the parameter. */
- int
- ha_innobase::index_next_same(
- /*=========================*/
- /* out: 0, HA_ERR_END_OF_FILE, or error
- number */
- mysql_byte* buf, /* in/out: buffer for the row */
- const mysql_byte* key, /* in: key value */
- uint keylen) /* in: key value length */
- {
- statistic_increment(ha_read_next_count, &LOCK_status);
- return(general_fetch(buf, ROW_SEL_NEXT, last_match_mode));
- }
- /***************************************************************************
- Reads the previous row from a cursor, which must have previously been
- positioned using index_read. */
- int
- ha_innobase::index_prev(
- /*====================*/
- /* out: 0, HA_ERR_END_OF_FILE, or error
- number */
- mysql_byte* buf) /* in/out: buffer for previous row in MySQL
- format */
- {
- return(general_fetch(buf, ROW_SEL_PREV, 0));
- }
- /************************************************************************
- Positions a cursor on the first record in an index and reads the
- corresponding row to buf. */
- int
- ha_innobase::index_first(
- /*=====================*/
- /* out: 0, HA_ERR_END_OF_FILE,
- or error code */
- mysql_byte* buf) /* in/out: buffer for the row */
- {
- int error;
- DBUG_ENTER("index_first");
- statistic_increment(ha_read_first_count, &LOCK_status);
- error = index_read(buf, NULL, 0, HA_READ_AFTER_KEY);
- /* MySQL does not seem to allow this to return HA_ERR_KEY_NOT_FOUND */
- if (error == HA_ERR_KEY_NOT_FOUND) {
- error = HA_ERR_END_OF_FILE;
- }
- DBUG_RETURN(error);
- }
- /************************************************************************
- Positions a cursor on the last record in an index and reads the
- corresponding row to buf. */
- int
- ha_innobase::index_last(
- /*====================*/
- /* out: 0, HA_ERR_END_OF_FILE, or error code */
- mysql_byte* buf) /* in/out: buffer for the row */
- {
- int error;
- DBUG_ENTER("index_last");
- statistic_increment(ha_read_last_count, &LOCK_status);
- error = index_read(buf, NULL, 0, HA_READ_BEFORE_KEY);
- /* MySQL does not seem to allow this to return HA_ERR_KEY_NOT_FOUND */
- if (error == HA_ERR_KEY_NOT_FOUND) {
- error = HA_ERR_END_OF_FILE;
- }
- DBUG_RETURN(error);
- }
- /********************************************************************
- Initialize a table scan. */
- int
- ha_innobase::rnd_init(
- /*==================*/
- /* out: 0 or error number */
- bool scan) /* in: ???????? */
- {
- int err;
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- /* Store the active index value so that we can restore the original
- value after a scan */
- if (prebuilt->clust_index_was_generated) {
- err = change_active_index(MAX_KEY);
- } else {
- err = change_active_index(primary_key);
- }
- start_of_scan = 1;
- return(err);
- }
- /*********************************************************************
- Ends a table scan. */
- int
- ha_innobase::rnd_end(void)
- /*======================*/
- /* out: 0 or error number */
- {
- return(index_end());
- }
- /*********************************************************************
- Reads the next row in a table scan (also used to read the FIRST row
- in a table scan). */
- int
- ha_innobase::rnd_next(
- /*==================*/
- /* out: 0, HA_ERR_END_OF_FILE, or error number */
- mysql_byte* buf)/* in/out: returns the row in this buffer,
- in MySQL format */
- {
- int error;
- DBUG_ENTER("rnd_next");
- statistic_increment(ha_read_rnd_next_count, &LOCK_status);
- if (start_of_scan) {
- error = index_first(buf);
- if (error == HA_ERR_KEY_NOT_FOUND) {
- error = HA_ERR_END_OF_FILE;
- }
- start_of_scan = 0;
- } else {
- error = general_fetch(buf, ROW_SEL_NEXT, 0);
- }
- DBUG_RETURN(error);
- }
- /**************************************************************************
- Fetches a row from the table based on a row reference. */
- int
- ha_innobase::rnd_pos(
- /*=================*/
- /* out: 0, HA_ERR_KEY_NOT_FOUND,
- or error code */
- mysql_byte* buf, /* in/out: buffer for the row */
- mysql_byte* pos) /* in: primary key value of the row in the
- MySQL format, or the row id if the clustered
- index was internally generated by InnoDB;
- the length of data in pos has to be
- ref_length */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- int error;
- uint keynr = active_index;
- DBUG_ENTER("rnd_pos");
- DBUG_DUMP("key", (char*) pos, ref_length);
- statistic_increment(ha_read_rnd_count, &LOCK_status);
- ut_ad(prebuilt->trx ==
- (trx_t*) current_thd->transaction.all.innobase_tid);
- if (prebuilt->clust_index_was_generated) {
- /* No primary key was defined for the table and we
- generated the clustered index from the row id: the
- row reference is the row id, not any key value
- that MySQL knows of */
- error = change_active_index(MAX_KEY);
- } else {
- error = change_active_index(primary_key);
- }
- if (error) {
- DBUG_PRINT("error",("Got error: %ld",error));
- DBUG_RETURN(error);
- }
- /* Note that we assume the length of the row reference is fixed
- for the table, and it is == ref_length */
- error = index_read(buf, pos, ref_length, HA_READ_KEY_EXACT);
- if (error)
- {
- DBUG_PRINT("error",("Got error: %ld",error));
- }
- change_active_index(keynr);
- DBUG_RETURN(error);
- }
- /*************************************************************************
- Stores a reference to the current row to 'ref' field of the handle. Note
- that in the case where we have generated the clustered index for the
- table, the function parameter is illogical: we MUST ASSUME that 'record'
- is the current 'position' of the handle, because if row ref is actually
- the row id internally generated in InnoDB, then 'record' does not contain
- it. We just guess that the row id must be for the record where the handle
- was positioned the last time. */
- void
- ha_innobase::position(
- /*==================*/
- const mysql_byte* record) /* in: row in MySQL format */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- uint len;
- ut_ad(prebuilt->trx ==
- (trx_t*) current_thd->transaction.all.innobase_tid);
- if (prebuilt->clust_index_was_generated) {
- /* No primary key was defined for the table and we
- generated the clustered index from row id: the
- row reference will be the row id, not any key value
- that MySQL knows of */
- len = DATA_ROW_ID_LEN;
- memcpy(ref, prebuilt->row_id, len);
- } else {
- len = store_key_val_for_row(primary_key, (char*)ref,
- ref_length, record);
- }
- /* Since we do not store len to the buffer 'ref', we must assume
- that len is always fixed for this table. The following assertion
- checks this. */
-
- if (len != ref_length) {
- fprintf(stderr,
- "InnoDB: Error: stored ref len is %lu, but table ref len is %lun",
- (ulong)len, (ulong)ref_length);
- }
- }
- /*********************************************************************
- Creates a table definition to an InnoDB database. */
- static
- int
- create_table_def(
- /*=============*/
- trx_t* trx, /* in: InnoDB transaction handle */
- TABLE* form, /* in: information on table
- columns and indexes */
- const char* table_name, /* in: table name */
- const char* path_of_temp_table)/* in: if this is a table explicitly
- created by the user with the
- TEMPORARY keyword, then this
- parameter is the dir path where the
- table should be placed if we create
- an .ibd file for it (no .ibd extension
- in the path, though); otherwise this
- is NULL */
- {
- Field* field;
- dict_table_t* table;
- ulint n_cols;
- int error;
- ulint col_type;
- ulint nulls_allowed;
- ulint unsigned_type;
- ulint binary_type;
- ulint charset_no;
- ulint i;
- DBUG_ENTER("create_table_def");
- DBUG_PRINT("enter", ("table_name: %s", table_name));
- n_cols = form->fields;
- /* We pass 0 as the space id, and determine at a lower level the space
- id where to store the table */
- table = dict_mem_table_create((char*) table_name, 0, n_cols);
- if (path_of_temp_table) {
- table->dir_path_of_temp_table =
- mem_heap_strdup(table->heap, path_of_temp_table);
- }
- for (i = 0; i < n_cols; i++) {
- field = form->field[i];
- col_type = get_innobase_type_from_mysql_type(&unsigned_type,
- field);
- if (field->null_ptr) {
- nulls_allowed = 0;
- } else {
- nulls_allowed = DATA_NOT_NULL;
- }
- if (field->binary()) {
- binary_type = DATA_BINARY_TYPE;
- } else {
- binary_type = 0;
- }
- charset_no = 0;
- if (dtype_is_string_type(col_type)) {
- charset_no = (ulint)field->charset()->number;
- ut_a(charset_no < 256); /* in ut0type.h we assume that
- the number fits in one byte */
- }
- dict_mem_table_add_col(table, (char*) field->field_name,
- col_type, dtype_form_prtype(
- (ulint)field->type()
- | nulls_allowed | unsigned_type
- | binary_type,
- + charset_no),
- field->pack_length(), 0);
- }
- error = row_create_table_for_mysql(table, trx);
- error = convert_error_code_to_mysql(error, NULL);
- DBUG_RETURN(error);
- }
- /*********************************************************************
- Creates an index in an InnoDB database. */
- static
- int
- create_index(
- /*=========*/
- trx_t* trx, /* in: InnoDB transaction handle */
- TABLE* form, /* in: information on table
- columns and indexes */
- const char* table_name, /* in: table name */
- uint key_num) /* in: index number */
- {
- Field* field;
- dict_index_t* index;
- int error;
- ulint n_fields;
- KEY* key;
- KEY_PART_INFO* key_part;
- ulint ind_type;
- ulint col_type;
- ulint prefix_len;
- ulint is_unsigned;
- ulint i;
- ulint j;
- DBUG_ENTER("create_index");
- key = form->key_info + key_num;
- n_fields = key->key_parts;
- ind_type = 0;
- if (key_num == form->primary_key) {
- ind_type = ind_type | DICT_CLUSTERED;
- }
- if (key->flags & HA_NOSAME ) {
- ind_type = ind_type | DICT_UNIQUE;
- }
- /* We pass 0 as the space id, and determine at a lower level the space
- id where to store the table */
- index = dict_mem_index_create((char*) table_name, key->name, 0,
- ind_type, n_fields);
- for (i = 0; i < n_fields; i++) {
- key_part = key->key_part + i;
- /* (The flag HA_PART_KEY_SEG denotes in MySQL a column prefix
- field in an index: we only store a specified number of first
- bytes of the column to the index field.) The flag does not
- seem to be properly set by MySQL. Let us fall back on testing
- the length of the key part versus the column. */
-
- field = NULL;
- for (j = 0; j < form->fields; j++) {
- field = form->field[j];
- if (0 == innobase_strcasecmp(
- field->field_name,
- key_part->field->field_name)) {
- /* Found the corresponding column */
- break;
- }
- }
- ut_a(j < form->fields);
- col_type = get_innobase_type_from_mysql_type(
- &is_unsigned, key_part->field);
- if (DATA_BLOB == col_type
- || key_part->length < field->pack_length()) {
- prefix_len = key_part->length;
- if (col_type == DATA_INT
- || col_type == DATA_FLOAT
- || col_type == DATA_DOUBLE
- || col_type == DATA_DECIMAL) {
- fprintf(stderr,
- "InnoDB: error: MySQL is trying to create a column prefix index fieldn"
- "InnoDB: on an inappropriate data type. Table name %s, column name %s.n",
- table_name, key_part->field->field_name);
-
- prefix_len = 0;
- }
- } else {
- prefix_len = 0;
- }
- /* We assume all fields should be sorted in ascending
- order, hence the '0': */
- dict_mem_index_add_field(index,
- (char*) key_part->field->field_name,
- 0, prefix_len);
- }
- error = row_create_index_for_mysql(index, trx);
- error = convert_error_code_to_mysql(error, NULL);
- DBUG_RETURN(error);
- }
- /*********************************************************************
- Creates an index to an InnoDB table when the user has defined no
- primary index. */
- static
- int
- create_clustered_index_when_no_primary(
- /*===================================*/
- trx_t* trx, /* in: InnoDB transaction handle */
- const char* table_name) /* in: table name */
- {
- dict_index_t* index;
- int error;
- /* We pass 0 as the space id, and determine at a lower level the space
- id where to store the table */
- index = dict_mem_index_create((char*) table_name,
- (char*) "GEN_CLUST_INDEX",
- 0, DICT_CLUSTERED, 0);
- error = row_create_index_for_mysql(index, trx);
- error = convert_error_code_to_mysql(error, NULL);
- return(error);
- }
- /*********************************************************************
- Creates a new table to an InnoDB database. */
- int
- ha_innobase::create(
- /*================*/
- /* out: error number */
- const char* name, /* in: table name */
- TABLE* form, /* in: information on table
- columns and indexes */
- HA_CREATE_INFO* create_info) /* in: more information of the
- created table, contains also the
- create statement string */
- {
- int error;
- dict_table_t* innobase_table;
- trx_t* parent_trx;
- trx_t* trx;
- int primary_key_no;
- uint i;
- char name2[FN_REFLEN];
- char norm_name[FN_REFLEN];
- THD *thd= current_thd;
- ib_longlong auto_inc_value;
- DBUG_ENTER("ha_innobase::create");
- DBUG_ASSERT(thd != NULL);
- if (form->fields > 1000) {
- /* The limit probably should be REC_MAX_N_FIELDS - 3 = 1020,
- but we play safe here */
- DBUG_RETURN(HA_ERR_TO_BIG_ROW);
- }
- /* Get the transaction associated with the current thd, or create one
- if not yet created */
-
- parent_trx = check_trx_exists(current_thd);
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- trx_search_latch_release_if_reserved(parent_trx);
-
- trx = trx_allocate_for_mysql();
-
- trx->mysql_thd = thd;
- trx->mysql_query_str = &((*thd).query);
- if (thd->options & OPTION_NO_FOREIGN_KEY_CHECKS) {
- trx->check_foreigns = FALSE;
- }
- if (thd->options & OPTION_RELAXED_UNIQUE_CHECKS) {
- trx->check_unique_secondary = FALSE;
- }
- if (lower_case_table_names) {
- srv_lower_case_table_names = TRUE;
- } else {
- srv_lower_case_table_names = FALSE;
- }
- fn_format(name2, name, "", "", 2); // Remove the .frm extension
- normalize_table_name(norm_name, name2);
- /* Latch the InnoDB data dictionary exclusively so that no deadlocks
- or lock waits can happen in it during a table create operation.
- Drop table etc. do this latching in row0mysql.c. */
- row_mysql_lock_data_dictionary(trx);
- /* Create the table definition in InnoDB */
- if (create_info->options & HA_LEX_CREATE_TMP_TABLE) {
- error = create_table_def(trx, form, norm_name, name2);
- } else {
- error = create_table_def(trx, form, norm_name, NULL);
- }
- if (error) {
- innobase_commit_low(trx);
- row_mysql_unlock_data_dictionary(trx);
- trx_free_for_mysql(trx);
- DBUG_RETURN(error);
- }
- /* Look for a primary key */
- primary_key_no= (table->primary_key != MAX_KEY ?
- (int) table->primary_key :
- -1);
- /* Our function row_get_mysql_key_number_for_index assumes
- the primary key is always number 0, if it exists */
- DBUG_ASSERT(primary_key_no == -1 || primary_key_no == 0);
- /* Create the keys */
- if (form->keys == 0 || primary_key_no == -1) {
- /* Create an index which is used as the clustered index;
- order the rows by their row id which is internally generated
- by InnoDB */
- error = create_clustered_index_when_no_primary(trx,
- norm_name);
- if (error) {
- innobase_commit_low(trx);
- row_mysql_unlock_data_dictionary(trx);
- trx_free_for_mysql(trx);
- DBUG_RETURN(error);
- }
- }
- if (primary_key_no != -1) {
- /* In InnoDB the clustered index must always be created
- first */
- if ((error = create_index(trx, form, norm_name,
- (uint) primary_key_no))) {
- innobase_commit_low(trx);
- row_mysql_unlock_data_dictionary(trx);
- trx_free_for_mysql(trx);
- DBUG_RETURN(error);
- }
- }
- for (i = 0; i < form->keys; i++) {
- if (i != (uint) primary_key_no) {
- if ((error = create_index(trx, form, norm_name, i))) {
- innobase_commit_low(trx);
- row_mysql_unlock_data_dictionary(trx);
- trx_free_for_mysql(trx);
- DBUG_RETURN(error);
- }
- }
- }
- if (current_thd->query != NULL) {
- LEX_STRING q;
- if (thd->convert_string(&q, system_charset_info,
- current_thd->query,
- current_thd->query_length,
- current_thd->charset())) {
- error = HA_ERR_OUT_OF_MEM;
- } else {
- error = row_table_add_foreign_constraints(trx,
- q.str, norm_name);
- error = convert_error_code_to_mysql(error, NULL);
- }
- if (error) {
- innobase_commit_low(trx);
- row_mysql_unlock_data_dictionary(trx);
- trx_free_for_mysql(trx);
- DBUG_RETURN(error);
- }
- }
- innobase_commit_low(trx);
- row_mysql_unlock_data_dictionary(trx);
- /* Flush the log to reduce probability that the .frm files and
- the InnoDB data dictionary get out-of-sync if the user runs
- with innodb_flush_log_at_trx_commit = 0 */
- log_buffer_flush_to_disk();
- innobase_table = dict_table_get(norm_name, NULL);
- DBUG_ASSERT(innobase_table != 0);
- if ((create_info->used_fields & HA_CREATE_USED_AUTO) &&
- (create_info->auto_increment_value != 0)) {
- /* Query was ALTER TABLE...AUTO_INCREMENT = x; or
- CREATE TABLE ...AUTO_INCREMENT = x; Find out a table
- definition from the dictionary and get the current value
- of the auto increment field. Set a new value to the
- auto increment field if the value is greater than the
- maximum value in the column. */
- auto_inc_value = create_info->auto_increment_value;
- dict_table_autoinc_initialize(innobase_table, auto_inc_value);
- }
- /* Tell the InnoDB server that there might be work for
- utility threads: */
- srv_active_wake_master_thread();
- trx_free_for_mysql(trx);
- DBUG_RETURN(0);
- }
- /*********************************************************************
- Discards or imports an InnoDB tablespace. */
- int
- ha_innobase::discard_or_import_tablespace(
- /*======================================*/
- /* out: 0 == success, -1 == error */
- my_bool discard) /* in: TRUE if discard, else import */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- dict_table_t* dict_table;
- trx_t* trx;
- int err;
- DBUG_ENTER("ha_innobase::discard_or_import_tablespace");
- ut_a(prebuilt->trx && prebuilt->trx->magic_n == TRX_MAGIC_N);
- ut_a(prebuilt->trx ==
- (trx_t*) current_thd->transaction.all.innobase_tid);
- dict_table = prebuilt->table;
- trx = prebuilt->trx;
- if (discard) {
- err = row_discard_tablespace_for_mysql(dict_table->name, trx);
- } else {
- err = row_import_tablespace_for_mysql(dict_table->name, trx);
- }
- err = convert_error_code_to_mysql(err, NULL);
- DBUG_RETURN(err);
- }
- /*********************************************************************
- Drops a table from an InnoDB database. Before calling this function,
- MySQL calls innobase_commit to commit the transaction of the current user.
- Then the current user cannot have locks set on the table. Drop table
- operation inside InnoDB will remove all locks any user has on the table
- inside InnoDB. */
- int
- ha_innobase::delete_table(
- /*======================*/
- /* out: error number */
- const char* name) /* in: table name */
- {
- ulint name_len;
- int error;
- trx_t* parent_trx;
- trx_t* trx;
- THD *thd= current_thd;
- char norm_name[1000];
- DBUG_ENTER("ha_innobase::delete_table");
- /* Get the transaction associated with the current thd, or create one
- if not yet created */
-
- parent_trx = check_trx_exists(current_thd);
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- trx_search_latch_release_if_reserved(parent_trx);
- if (lower_case_table_names) {
- srv_lower_case_table_names = TRUE;
- } else {
- srv_lower_case_table_names = FALSE;
- }
- trx = trx_allocate_for_mysql();
- trx->mysql_thd = current_thd;
- trx->mysql_query_str = &((*current_thd).query);
- if (thd->options & OPTION_NO_FOREIGN_KEY_CHECKS) {
- trx->check_foreigns = FALSE;
- }
- if (thd->options & OPTION_RELAXED_UNIQUE_CHECKS) {
- trx->check_unique_secondary = FALSE;
- }
- name_len = strlen(name);
- assert(name_len < 1000);
- /* Strangely, MySQL passes the table name without the '.frm'
- extension, in contrast to ::create */
- normalize_table_name(norm_name, name);
- /* Drop the table in InnoDB */
- error = row_drop_table_for_mysql(norm_name, trx,
- thd->lex->sql_command == SQLCOM_DROP_DB);
- /* Flush the log to reduce probability that the .frm files and
- the InnoDB data dictionary get out-of-sync if the user runs
- with innodb_flush_log_at_trx_commit = 0 */
- log_buffer_flush_to_disk();
- /* Tell the InnoDB server that there might be work for
- utility threads: */
- srv_active_wake_master_thread();
- innobase_commit_low(trx);
- trx_free_for_mysql(trx);
- error = convert_error_code_to_mysql(error, NULL);
- DBUG_RETURN(error);
- }
- /*********************************************************************
- Removes all tables in the named database inside InnoDB. */
- int
- innobase_drop_database(
- /*===================*/
- /* out: error number */
- char* path) /* in: database path; inside InnoDB the name
- of the last directory in the path is used as
- the database name: for example, in 'mysql/data/test'
- the database name is 'test' */
- {
- ulint len = 0;
- trx_t* parent_trx;
- trx_t* trx;
- char* ptr;
- int error;
- char* namebuf;
- /* Get the transaction associated with the current thd, or create one
- if not yet created */
-
- parent_trx = check_trx_exists(current_thd);
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- trx_search_latch_release_if_reserved(parent_trx);
- ptr = strend(path) - 2;
- while (ptr >= path && *ptr != '\' && *ptr != '/') {
- ptr--;
- len++;
- }
- ptr++;
- namebuf = my_malloc(len + 2, MYF(0));
- memcpy(namebuf, ptr, len);
- namebuf[len] = '/';
- namebuf[len + 1] = ' ';
- #ifdef __WIN__
- innobase_casedn_str(namebuf);
- #endif
- trx = trx_allocate_for_mysql();
- trx->mysql_thd = current_thd;
- trx->mysql_query_str = &((*current_thd).query);
- if (current_thd->options & OPTION_NO_FOREIGN_KEY_CHECKS) {
- trx->check_foreigns = FALSE;
- }
- error = row_drop_database_for_mysql(namebuf, trx);
- my_free(namebuf, MYF(0));
- /* Flush the log to reduce probability that the .frm files and
- the InnoDB data dictionary get out-of-sync if the user runs
- with innodb_flush_log_at_trx_commit = 0 */
- log_buffer_flush_to_disk();
- /* Tell the InnoDB server that there might be work for
- utility threads: */
- srv_active_wake_master_thread();
- innobase_commit_low(trx);
- trx_free_for_mysql(trx);
- error = convert_error_code_to_mysql(error, NULL);
- return(error);
- }
- /*************************************************************************
- Renames an InnoDB table. */
- int
- ha_innobase::rename_table(
- /*======================*/
- /* out: 0 or error code */
- const char* from, /* in: old name of the table */
- const char* to) /* in: new name of the table */
- {
- ulint name_len1;
- ulint name_len2;
- int error;
- trx_t* parent_trx;
- trx_t* trx;
- char norm_from[1000];
- char norm_to[1000];
- DBUG_ENTER("ha_innobase::rename_table");
- /* Get the transaction associated with the current thd, or create one
- if not yet created */
-
- parent_trx = check_trx_exists(current_thd);
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- trx_search_latch_release_if_reserved(parent_trx);
- if (lower_case_table_names) {
- srv_lower_case_table_names = TRUE;
- } else {
- srv_lower_case_table_names = FALSE;
- }
- trx = trx_allocate_for_mysql();
- trx->mysql_thd = current_thd;
- trx->mysql_query_str = &((*current_thd).query);
- if (current_thd->options & OPTION_NO_FOREIGN_KEY_CHECKS) {
- trx->check_foreigns = FALSE;
- }
- name_len1 = strlen(from);
- name_len2 = strlen(to);
- assert(name_len1 < 1000);
- assert(name_len2 < 1000);
- normalize_table_name(norm_from, from);
- normalize_table_name(norm_to, to);
- /* Rename the table in InnoDB */
- error = row_rename_table_for_mysql(norm_from, norm_to, trx);
- /* Flush the log to reduce probability that the .frm files and
- the InnoDB data dictionary get out-of-sync if the user runs
- with innodb_flush_log_at_trx_commit = 0 */
- log_buffer_flush_to_disk();
- /* Tell the InnoDB server that there might be work for
- utility threads: */
- srv_active_wake_master_thread();
- innobase_commit_low(trx);
- trx_free_for_mysql(trx);
- error = convert_error_code_to_mysql(error, NULL);
- DBUG_RETURN(error);
- }
- /*************************************************************************
- Estimates the number of index records in a range. */
- ha_rows
- ha_innobase::records_in_range(
- /*==========================*/
- /* out: estimated number of
- rows */
- uint keynr, /* in: index number */
- key_range *min_key, /* in: start key value of the
- range, may also be 0 */
- key_range *max_key) /* in: range end key val, may
- also be 0 */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- KEY* key;
- dict_index_t* index;
- mysql_byte* key_val_buff2 = (mysql_byte*) my_malloc(
- table->reclength
- + table->max_key_length + 100,
- MYF(MY_WME));
- ulint buff2_len = table->reclength
- + table->max_key_length + 100;
- dtuple_t* range_start;
- dtuple_t* range_end;
- ib_longlong n_rows;
- ulint mode1;
- ulint mode2;
- void* heap1;
- void* heap2;
- DBUG_ENTER("records_in_range");
- prebuilt->trx->op_info = (char*)"estimating records in index range";
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- trx_search_latch_release_if_reserved(prebuilt->trx);
- active_index = keynr;
- key = table->key_info + active_index;
- index = dict_table_get_index_noninline(prebuilt->table, key->name);
- range_start = dtuple_create_for_mysql(&heap1, key->key_parts);
- dict_index_copy_types(range_start, index, key->key_parts);
- range_end = dtuple_create_for_mysql(&heap2, key->key_parts);
- dict_index_copy_types(range_end, index, key->key_parts);
- row_sel_convert_mysql_key_to_innobase(
- range_start, (byte*) key_val_buff,
- (ulint)upd_and_key_val_buff_len,
- index,
- (byte*) (min_key ? min_key->key :
- (const mysql_byte*) 0),
- (ulint) (min_key ? min_key->length : 0),
- prebuilt->trx);
- row_sel_convert_mysql_key_to_innobase(
- range_end, (byte*) key_val_buff2,
- buff2_len, index,
- (byte*) (max_key ? max_key->key :
- (const mysql_byte*) 0),
- (ulint) (max_key ? max_key->length : 0),
- prebuilt->trx);
- mode1 = convert_search_mode_to_innobase(min_key ? min_key->flag :
- HA_READ_KEY_EXACT);
- mode2 = convert_search_mode_to_innobase(max_key ? max_key->flag :
- HA_READ_KEY_EXACT);
- n_rows = btr_estimate_n_rows_in_range(index, range_start,
- mode1, range_end, mode2);
- dtuple_free_for_mysql(heap1);
- dtuple_free_for_mysql(heap2);
- my_free((char*) key_val_buff2, MYF(0));
- prebuilt->trx->op_info = (char*)"";
- /* The MySQL optimizer seems to believe an estimate of 0 rows is
- always accurate and may return the result 'Empty set' based on that.
- The accuracy is not guaranteed, and even if it were, for a locking
- read we should anyway perform the search to set the next-key lock.
- Add 1 to the value to make sure MySQL does not make the assumption! */
- if (n_rows == 0) {
- n_rows = 1;
- }
- DBUG_RETURN((ha_rows) n_rows);
- }
- /*************************************************************************
- Gives an UPPER BOUND to the number of rows in a table. This is used in
- filesort.cc. */
- ha_rows
- ha_innobase::estimate_rows_upper_bound(void)
- /*======================================*/
- /* out: upper bound of rows */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- dict_index_t* index;
- ulonglong estimate;
- ulonglong local_data_file_length;
- DBUG_ENTER("estimate_rows_upper_bound");
- /* We do not know if MySQL can call this function before calling
- external_lock(). To be safe, update the thd of the current table
- handle. */
- update_thd(current_thd);
- prebuilt->trx->op_info = (char*)
- "calculating upper bound for table rows";
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- trx_search_latch_release_if_reserved(prebuilt->trx);
- index = dict_table_get_first_index_noninline(prebuilt->table);
- local_data_file_length = ((ulonglong) index->stat_n_leaf_pages)
- * UNIV_PAGE_SIZE;
- /* Calculate a minimum length for a clustered index record and from
- that an upper bound for the number of rows. Since we only calculate
- new statistics in row0mysql.c when a table has grown by a threshold
- factor, we must add a safety factor 2 in front of the formula below. */
- estimate = 2 * local_data_file_length /
- dict_index_calc_min_rec_len(index);
- prebuilt->trx->op_info = (char*)"";
- DBUG_RETURN((ha_rows) estimate);
- }
- /*************************************************************************
- How many seeks it will take to read through the table. This is to be
- comparable to the number returned by records_in_range so that we can
- decide if we should scan the table or use keys. */
- double
- ha_innobase::scan_time()
- /*====================*/
- /* out: estimated time measured in disk seeks */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- /* Since MySQL seems to favor table scans too much over index
- searches, we pretend that a sequential read takes the same time
- as a random disk read, that is, we do not divide the following
- by 10, which would be physically realistic. */
-
- return((double) (prebuilt->table->stat_clustered_index_size));
- }
- /**********************************************************************
- Calculate the time it takes to read a set of ranges through an index
- This enables us to optimise reads for clustered indexes. */
- double
- ha_innobase::read_time(
- /*===================*/
- /* out: estimated time measured in disk seeks */
- uint index, /* in: key number */
- uint ranges, /* in: how many ranges */
- ha_rows rows) /* in: estimated number of rows in the ranges */
- {
- ha_rows total_rows;
- double time_for_scan;
-
- if (index != table->primary_key)
- return handler::read_time(index, ranges, rows); // Not clustered
- if (rows <= 2)
- return (double) rows;
- /* Assume that the read time is proportional to the scan time for all
- rows + at most one seek per range. */
- time_for_scan = scan_time();
- if ((total_rows = estimate_rows_upper_bound()) < rows)
- return time_for_scan;
- return (ranges + (double) rows / (double) total_rows * time_for_scan);
- }
- /*************************************************************************
- Returns statistics information of the table to the MySQL interpreter,
- in various fields of the handle object. */
- void
- ha_innobase::info(
- /*==============*/
- uint flag) /* in: what information MySQL requests */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- dict_table_t* ib_table;
- dict_index_t* index;
- ha_rows rec_per_key;
- ib_longlong n_rows;
- ulong j;
- ulong i;
- char path[FN_REFLEN];
- os_file_stat_t stat_info;
- DBUG_ENTER("info");
- /* If we are forcing recovery at a high level, we will suppress
- statistics calculation on tables, because that may crash the
- server if an index is badly corrupted. */
- if (srv_force_recovery >= SRV_FORCE_NO_IBUF_MERGE) {
- DBUG_VOID_RETURN;
- }
- /* We do not know if MySQL can call this function before calling
- external_lock(). To be safe, update the thd of the current table
- handle. */
- update_thd(current_thd);
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- prebuilt->trx->op_info = (char*)"returning various info to MySQL";
- trx_search_latch_release_if_reserved(prebuilt->trx);
- ib_table = prebuilt->table;
- if (flag & HA_STATUS_TIME) {
- /* In sql_show we call with this flag: update then statistics
- so that they are up-to-date */
- prebuilt->trx->op_info = (char*)"updating table statistics";
- dict_update_statistics(ib_table);
- prebuilt->trx->op_info = (char*)
- "returning various info to MySQL";
- if (ib_table->space != 0) {
- my_snprintf(path, sizeof(path), "%s/%s%s",
- mysql_data_home, ib_table->name,
- ".ibd");
- unpack_filename(path,path);
- } else {
- my_snprintf(path, sizeof(path), "%s/%s%s",
- mysql_data_home, ib_table->name,
- reg_ext);
-
- unpack_filename(path,path);
- }
- /* Note that we do not know the access time of the table,
- nor the CHECK TABLE time, nor the UPDATE or INSERT time. */
- if (os_file_get_status(path,&stat_info)) {
- create_time = stat_info.ctime;
- }
- }
- if (flag & HA_STATUS_VARIABLE) {
- n_rows = ib_table->stat_n_rows;
- /* Because we do not protect stat_n_rows by any mutex in a
- delete, it is theoretically possible that the value can be
- smaller than zero! TODO: fix this race.
- The MySQL optimizer seems to assume in a left join that n_rows
- is an accurate estimate if it is zero. Of course, it is not,
- since we do not have any locks on the rows yet at this phase.
- Since SHOW TABLE STATUS seems to call this function with the
- HA_STATUS_TIME flag set, while the left join optizer does not
- set that flag, we add one to a zero value if the flag is not
- set. That way SHOW TABLE STATUS will show the best estimate,
- while the optimizer never sees the table empty. */
- if (n_rows < 0) {
- n_rows = 0;
- }
- if (n_rows == 0 && !(flag & HA_STATUS_TIME)) {
- n_rows++;
- }
- records = (ha_rows)n_rows;
- deleted = 0;
- data_file_length = ((ulonglong)
- ib_table->stat_clustered_index_size)
- * UNIV_PAGE_SIZE;
- index_file_length = ((ulonglong)
- ib_table->stat_sum_of_other_index_sizes)
- * UNIV_PAGE_SIZE;
- delete_length = 0;
- check_time = 0;
- if (records == 0) {
- mean_rec_length = 0;
- } else {
- mean_rec_length = (ulong) (data_file_length / records);
- }
- }
- if (flag & HA_STATUS_CONST) {
- index = dict_table_get_first_index_noninline(ib_table);
- if (prebuilt->clust_index_was_generated) {
- index = dict_table_get_next_index_noninline(index);
- }
- for (i = 0; i < table->keys; i++) {
- if (index == NULL) {
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Error: table %s contains less indexes inside InnoDBn"
- "InnoDB: than are defined in the MySQL .frm file. Have you mixed upn"
- "InnoDB: .frm files from different installations? See sectionn"
- "InnoDB: 15.1 at http://www.innodb.com/ibman.htmln",
- ib_table->name);
- break;
- }
- for (j = 0; j < table->key_info[i].key_parts; j++) {
- if (j + 1 > index->n_uniq) {
- ut_print_timestamp(stderr);
- fprintf(stderr,
- " InnoDB: Error: index %s of %s has %lu columns unique inside InnoDBn"
- "InnoDB: but MySQL is asking statistics for %lu columns. Have you mixed upn"
- "InnoDB: .frm files from different installations? See sectionn"
- "InnoDB: 15.1 at http://www.innodb.com/ibman.htmln",
- index->name,
- ib_table->name,
- (unsigned long) index->n_uniq,
- j + 1);
- break;
- }
- if (index->stat_n_diff_key_vals[j + 1] == 0) {
- rec_per_key = records;
- } else {
- rec_per_key = (ha_rows)(records /
- index->stat_n_diff_key_vals[j + 1]);
- }
- /* Since MySQL seems to favor table scans
- too much over index searches, we pretend
- index selectivity is 2 times better than
- our estimate: */
- rec_per_key = rec_per_key / 2;
- if (rec_per_key == 0) {
- rec_per_key = 1;
- }
- table->key_info[i].rec_per_key[j]=
- rec_per_key >= ~(ulong) 0 ? ~(ulong) 0 :
- rec_per_key;
- }
- index = dict_table_get_next_index_noninline(index);
- }
- }
- if (flag & HA_STATUS_ERRKEY) {
- ut_a(prebuilt->trx && prebuilt->trx->magic_n == TRX_MAGIC_N);
- errkey = (unsigned int) row_get_mysql_key_number_for_index(
- (dict_index_t*)
- trx_get_error_info(prebuilt->trx));
- }
- prebuilt->trx->op_info = (char*)"";
- DBUG_VOID_RETURN;
- }
- /**************************************************************************
- Updates index cardinalities of the table, based on 8 random dives into
- each index tree. This does NOT calculate exact statistics on the table. */
- int
- ha_innobase::analyze(
- /*=================*/
- /* out: returns always 0 (success) */
- THD* thd, /* in: connection thread handle */
- HA_CHECK_OPT* check_opt) /* in: currently ignored */
- {
- /* Simply call ::info() with all the flags */
- info(HA_STATUS_TIME | HA_STATUS_CONST | HA_STATUS_VARIABLE);
- return(0);
- }
- /**************************************************************************
- This is mapped to "ALTER TABLE tablename TYPE=InnoDB", which rebuilds
- the table in MySQL. */
- int
- ha_innobase::optimize(
- /*==================*/
- THD* thd, /* in: connection thread handle */
- HA_CHECK_OPT* check_opt) /* in: currently ignored */
- {
- return(HA_ADMIN_TRY_ALTER);
- }
- /***********************************************************************
- Tries to check that an InnoDB table is not corrupted. If corruption is
- noticed, prints to stderr information about it. In case of corruption
- may also assert a failure and crash the server. */
- int
- ha_innobase::check(
- /*===============*/
- /* out: HA_ADMIN_CORRUPT or
- HA_ADMIN_OK */
- THD* thd, /* in: user thread handle */
- HA_CHECK_OPT* check_opt) /* in: check options, currently
- ignored */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- ulint ret;
- ut_a(prebuilt->trx && prebuilt->trx->magic_n == TRX_MAGIC_N);
- ut_a(prebuilt->trx ==
- (trx_t*) current_thd->transaction.all.innobase_tid);
- if (prebuilt->mysql_template == NULL) {
- /* Build the template; we will use a dummy template
- in index scans done in checking */
- build_template(prebuilt, NULL, table, ROW_MYSQL_WHOLE_ROW);
- }
- ret = row_check_table_for_mysql(prebuilt);
- if (ret == DB_SUCCESS) {
- return(HA_ADMIN_OK);
- }
- return(HA_ADMIN_CORRUPT);
- }
- /*****************************************************************
- Adds information about free space in the InnoDB tablespace to a table comment
- which is printed out when a user calls SHOW TABLE STATUS. Adds also info on
- foreign keys. */
- char*
- ha_innobase::update_table_comment(
- /*==============================*/
- /* out: table comment + InnoDB free space +
- info on foreign keys */
- const char* comment)/* in: table comment defined by user */
- {
- uint length = strlen(comment);
- char* str;
- row_prebuilt_t* prebuilt = (row_prebuilt_t*)innobase_prebuilt;
- /* We do not know if MySQL can call this function before calling
- external_lock(). To be safe, update the thd of the current table
- handle. */
- if(length > 64000 - 3) {
- return((char*)comment); /* string too long */
- }
- update_thd(current_thd);
- prebuilt->trx->op_info = (char*)"returning table comment";
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- trx_search_latch_release_if_reserved(prebuilt->trx);
- str = NULL;
- if (FILE* file = os_file_create_tmpfile()) {
- long flen;
- /* output the data to a temporary file */
- fprintf(file, "InnoDB free: %lu kB",
- (ulong) fsp_get_available_space_in_free_extents(
- prebuilt->table->space));
- dict_print_info_on_foreign_keys(FALSE, file,
- prebuilt->trx, prebuilt->table);
- flen = ftell(file);
- if (flen < 0) {
- flen = 0;
- } else if (length + flen + 3 > 64000) {
- flen = 64000 - 3 - length;
- }
- /* allocate buffer for the full string, and
- read the contents of the temporary file */
- str = my_malloc(length + flen + 3, MYF(0));
- if (str) {
- char* pos = str + length;
- if(length) {
- memcpy(str, comment, length);
- *pos++ = ';';
- *pos++ = ' ';
- }
- rewind(file);
- flen = fread(pos, 1, flen, file);
- pos[flen] = 0;
- }
- fclose(file);
- }
- prebuilt->trx->op_info = (char*)"";
- return(str ? str : (char*) comment);
- }
- /***********************************************************************
- Gets the foreign key create info for a table stored in InnoDB. */
- char*
- ha_innobase::get_foreign_key_create_info(void)
- /*==========================================*/
- /* out, own: character string in the form which
- can be inserted to the CREATE TABLE statement,
- MUST be freed with ::free_foreign_key_create_info */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*)innobase_prebuilt;
- char* str = 0;
- ut_a(prebuilt != NULL);
- /* We do not know if MySQL can call this function before calling
- external_lock(). To be safe, update the thd of the current table
- handle. */
- update_thd(current_thd);
- if (FILE* file = os_file_create_tmpfile()) {
- long flen;
- prebuilt->trx->op_info = (char*)"getting info on foreign keys";
- /* In case MySQL calls this in the middle of a SELECT query,
- release possible adaptive hash latch to avoid
- deadlocks of threads */
- trx_search_latch_release_if_reserved(prebuilt->trx);
- /* output the data to a temporary file */
- dict_print_info_on_foreign_keys(TRUE, file,
- prebuilt->trx, prebuilt->table);
- prebuilt->trx->op_info = (char*)"";
- flen = ftell(file);
- if (flen < 0) {
- flen = 0;
- } else if(flen > 64000 - 1) {
- flen = 64000 - 1;
- }
- /* allocate buffer for the string, and
- read the contents of the temporary file */
- str = my_malloc(flen + 1, MYF(0));
- if (str) {
- rewind(file);
- flen = fread(str, 1, flen, file);
- str[flen] = 0;
- }
- fclose(file);
- } else {
- /* unable to create temporary file */
- str = my_strdup(
- "/* Error: cannot display foreign key constraints */", MYF(0));
- }
- return(str);
- }
- /*********************************************************************
- Checks if ALTER TABLE may change the storage engine of the table.
- Changing storage engines is not allowed for tables for which there
- are foreign key constraints (parent or child tables). */
- bool
- ha_innobase::can_switch_engines(void)
- /*=================================*/
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- bool can_switch;
- DBUG_ENTER("ha_innobase::can_switch_engines");
- prebuilt->trx->op_info =
- "determining if there are foreign key constraints";
- row_mysql_lock_data_dictionary(prebuilt->trx);
- can_switch = !UT_LIST_GET_FIRST(prebuilt->table->referenced_list)
- && !UT_LIST_GET_FIRST(prebuilt->table->foreign_list);
- row_mysql_unlock_data_dictionary(prebuilt->trx);
- prebuilt->trx->op_info = "";
- DBUG_RETURN(can_switch);
- }
- /***********************************************************************
- Checks if a table is referenced by a foreign key. The MySQL manual states that
- a REPLACE is either equivalent to an INSERT, or DELETE(s) + INSERT. Only a
- delete is then allowed internally to resolve a duplicate key conflict in
- REPLACE, not an update. */
- uint
- ha_innobase::referenced_by_foreign_key(void)
- /*========================================*/
- /* out: > 0 if referenced by a FOREIGN KEY */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*)innobase_prebuilt;
- if (dict_table_referenced_by_foreign_key(prebuilt->table)) {
- return(1);
- }
- return(0);
- }
- /***********************************************************************
- Frees the foreign key create info for a table stored in InnoDB, if it is
- non-NULL. */
- void
- ha_innobase::free_foreign_key_create_info(
- /*======================================*/
- char* str) /* in, own: create info string to free */
- {
- if (str) {
- my_free(str, MYF(0));
- }
- }
- /***********************************************************************
- Tells something additional to the handler about how to do things. */
- int
- ha_innobase::extra(
- /*===============*/
- /* out: 0 or error number */
- enum ha_extra_function operation)
- /* in: HA_EXTRA_RETRIEVE_ALL_COLS or some
- other flag */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- /* Warning: since it is not sure that MySQL calls external_lock
- before calling this function, the trx field in prebuilt can be
- obsolete! */
- switch (operation) {
- case HA_EXTRA_FLUSH:
- if (prebuilt->blob_heap) {
- row_mysql_prebuilt_free_blob_heap(prebuilt);
- }
- break;
- case HA_EXTRA_RESET:
- if (prebuilt->blob_heap) {
- row_mysql_prebuilt_free_blob_heap(prebuilt);
- }
- prebuilt->read_just_key = 0;
- break;
- case HA_EXTRA_RESET_STATE:
- prebuilt->read_just_key = 0;
- break;
- case HA_EXTRA_NO_KEYREAD:
- prebuilt->read_just_key = 0;
- break;
- case HA_EXTRA_RETRIEVE_ALL_COLS:
- prebuilt->hint_need_to_fetch_extra_cols
- = ROW_RETRIEVE_ALL_COLS;
- break;
- case HA_EXTRA_RETRIEVE_PRIMARY_KEY:
- if (prebuilt->hint_need_to_fetch_extra_cols == 0) {
- prebuilt->hint_need_to_fetch_extra_cols
- = ROW_RETRIEVE_PRIMARY_KEY;
- }
- break;
- case HA_EXTRA_KEYREAD:
- prebuilt->read_just_key = 1;
- break;
- default:/* Do nothing */
- ;
- }
- return(0);
- }
- /**********************************************************************
- MySQL calls this function at the start of each SQL statement inside LOCK
- TABLES. Inside LOCK TABLES the ::external_lock method does not work to
- mark SQL statement borders. Note also a special case: if a temporary table
- is created inside LOCK TABLES, MySQL has not called external_lock() at all
- on that table. */
- int
- ha_innobase::start_stmt(
- /*====================*/
- /* out: 0 or error code */
- THD* thd) /* in: handle to the user thread */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- trx_t* trx;
- update_thd(thd);
- trx = prebuilt->trx;
- /* Here we release the search latch and the InnoDB thread FIFO ticket
- if they were reserved. They should have been released already at the
- end of the previous statement, but because inside LOCK TABLES the
- lock count method does not work to mark the end of a SELECT statement,
- that may not be the case. We MUST release the search latch before an
- INSERT, for example. */
- innobase_release_stat_resources(trx);
- if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
- && trx->read_view) {
- /* At low transaction isolation levels we let
- each consistent read set its own snapshot */
- read_view_close_for_mysql(trx);
- }
- auto_inc_counter_for_this_stat = 0;
- prebuilt->sql_stat_start = TRUE;
- prebuilt->hint_need_to_fetch_extra_cols = 0;
- prebuilt->read_just_key = 0;
- if (!prebuilt->mysql_has_locked) {
- /* This handle is for a temporary table created inside
- this same LOCK TABLES; since MySQL does NOT call external_lock
- in this case, we must use x-row locks inside InnoDB to be
- prepared for an update of a row */
-
- prebuilt->select_lock_type = LOCK_X;
- } else {
- if (trx->isolation_level != TRX_ISO_SERIALIZABLE
- && thd->lex->sql_command == SQLCOM_SELECT
- && thd->lex->lock_option == TL_READ) {
-
- /* For other than temporary tables, we obtain
- no lock for consistent read (plain SELECT). */
- prebuilt->select_lock_type = LOCK_NONE;
- } else {
- /* Not a consistent read: restore the
- select_lock_type value. The value of
- stored_select_lock_type was decided in:
- 1) ::store_lock(),
- 2) ::external_lock(), and
- 3) ::init_table_handle_for_HANDLER(). */
- prebuilt->select_lock_type =
- prebuilt->stored_select_lock_type;
- }
- if (prebuilt->stored_select_lock_type != LOCK_S
- && prebuilt->stored_select_lock_type != LOCK_X) {
- fprintf(stderr,
- "InnoDB: Error: stored_select_lock_type is %lu inside ::start_stmt()!n",
- prebuilt->stored_select_lock_type);
- /* Set the value to LOCK_X: this is just fault
- tolerance, we do not know what the correct value
- should be! */
- prebuilt->select_lock_type = LOCK_X;
- }
- }
- /* Set the MySQL flag to mark that there is an active transaction */
- thd->transaction.all.innodb_active_trans = 1;
- return(0);
- }
- /**********************************************************************
- Maps a MySQL trx isolation level code to the InnoDB isolation level code */
- inline
- ulint
- innobase_map_isolation_level(
- /*=========================*/
- /* out: InnoDB isolation level */
- enum_tx_isolation iso) /* in: MySQL isolation level code */
- {
- switch(iso) {
- case ISO_REPEATABLE_READ: return(TRX_ISO_REPEATABLE_READ);
- case ISO_READ_COMMITTED: return(TRX_ISO_READ_COMMITTED);
- case ISO_SERIALIZABLE: return(TRX_ISO_SERIALIZABLE);
- case ISO_READ_UNCOMMITTED: return(TRX_ISO_READ_UNCOMMITTED);
- default: ut_a(0); return(0);
- }
- }
-
- /**********************************************************************
- As MySQL will execute an external lock for every new table it uses when it
- starts to process an SQL statement (an exception is when MySQL calls
- start_stmt for the handle) we can use this function to store the pointer to
- the THD in the handle. We will also use this function to communicate
- to InnoDB that a new SQL statement has started and that we must store a
- savepoint to our transaction handle, so that we are able to roll back
- the SQL statement in case of an error. */
- int
- ha_innobase::external_lock(
- /*=======================*/
- /* out: 0 */
- THD* thd, /* in: handle to the user thread */
- int lock_type) /* in: lock type */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- trx_t* trx;
- DBUG_ENTER("ha_innobase::external_lock");
- DBUG_PRINT("enter",("lock_type: %d", lock_type));
- update_thd(thd);
- trx = prebuilt->trx;
- prebuilt->sql_stat_start = TRUE;
- prebuilt->hint_need_to_fetch_extra_cols = 0;
- prebuilt->read_just_key = 0;
- if (lock_type == F_WRLCK) {
- /* If this is a SELECT, then it is in UPDATE TABLE ...
- or SELECT ... FOR UPDATE */
- prebuilt->select_lock_type = LOCK_X;
- prebuilt->stored_select_lock_type = LOCK_X;
- }
- if (lock_type != F_UNLCK) {
- /* MySQL is setting a new table lock */
- /* Set the MySQL flag to mark that there is an active
- transaction */
- thd->transaction.all.innodb_active_trans = 1;
- trx->n_mysql_tables_in_use++;
- prebuilt->mysql_has_locked = TRUE;
- if (trx->n_mysql_tables_in_use == 1) {
- trx->isolation_level = innobase_map_isolation_level(
- (enum_tx_isolation)
- thd->variables.tx_isolation);
- }
- if (trx->isolation_level == TRX_ISO_SERIALIZABLE
- && prebuilt->select_lock_type == LOCK_NONE
- && (thd->options
- & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
- /* To get serializable execution, we let InnoDB
- conceptually add 'LOCK IN SHARE MODE' to all SELECTs
- which otherwise would have been consistent reads. An
- exception is consistent reads in the AUTOCOMMIT=1 mode:
- we know that they are read-only transactions, and they
- can be serialized also if performed as consistent
- reads. */
- prebuilt->select_lock_type = LOCK_S;
- }
- /* Starting from 4.1.9, no InnoDB table lock is taken in LOCK
- TABLES if AUTOCOMMIT=1. It does not make much sense to acquire
- an InnoDB table lock if it is released immediately at the end
- of LOCK TABLES, and InnoDB's table locks in that case cause
- VERY easily deadlocks. */
- if (prebuilt->select_lock_type != LOCK_NONE) {
- if (thd->in_lock_tables &&
- thd->variables.innodb_table_locks &&
- (thd->options & OPTION_NOT_AUTOCOMMIT)) {
- ulint error;
- error = row_lock_table_for_mysql(prebuilt,
- NULL, LOCK_TABLE_EXP);
- if (error != DB_SUCCESS) {
- error = convert_error_code_to_mysql(
- error, user_thd);
- DBUG_RETURN(error);
- }
- }
- trx->mysql_n_tables_locked++;
- }
- DBUG_RETURN(0);
- }
- /* MySQL is releasing a table lock */
- trx->n_mysql_tables_in_use--;
- prebuilt->mysql_has_locked = FALSE;
- auto_inc_counter_for_this_stat = 0;
- if (trx->n_lock_table_exp) {
- row_unlock_tables_for_mysql(trx);
- }
- /* If the MySQL lock count drops to zero we know that the current SQL
- statement has ended */
- if (trx->n_mysql_tables_in_use == 0) {
- trx->mysql_n_tables_locked = 0;
- prebuilt->used_in_HANDLER = FALSE;
-
- /* Release a possible FIFO ticket and search latch. Since we
- may reserve the kernel mutex, we have to release the search
- system latch first to obey the latching order. */
- innobase_release_stat_resources(trx);
- if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
- if (thd->transaction.all.innodb_active_trans != 0) {
- innobase_commit(thd, trx);
- }
- } else {
- if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
- && trx->read_view) {
- /* At low transaction isolation levels we let
- each consistent read set its own snapshot */
- read_view_close_for_mysql(trx);
- }
- }
- }
- DBUG_RETURN(0);
- }
- /****************************************************************************
- Implements the SHOW INNODB STATUS command. Sends the output of the InnoDB
- Monitor to the client. */
- int
- innodb_show_status(
- /*===============*/
- THD* thd) /* in: the MySQL query thread of the caller */
- {
- Protocol* protocol = thd->protocol;
- trx_t* trx;
- static const char truncated_msg[] = "... truncated...n";
- const long MAX_STATUS_SIZE = 64000;
- ulint trx_list_start = ULINT_UNDEFINED;
- ulint trx_list_end = ULINT_UNDEFINED;
- DBUG_ENTER("innodb_show_status");
- if (have_innodb != SHOW_OPTION_YES) {
- my_message(ER_NOT_SUPPORTED_YET,
- "Cannot call SHOW INNODB STATUS because skip-innodb is defined",
- MYF(0));
- DBUG_RETURN(-1);
- }
- trx = check_trx_exists(thd);
- innobase_release_stat_resources(trx);
- /* We let the InnoDB Monitor to output at most MAX_STATUS_SIZE
- bytes of text. */
- long flen, usable_len;
- char* str;
- mutex_enter_noninline(&srv_monitor_file_mutex);
- rewind(srv_monitor_file);
- srv_printf_innodb_monitor(srv_monitor_file,
- &trx_list_start, &trx_list_end);
- flen = ftell(srv_monitor_file);
- os_file_set_eof(srv_monitor_file);
- if (flen < 0) {
- flen = 0;
- }
- if (flen > MAX_STATUS_SIZE) {
- usable_len = MAX_STATUS_SIZE;
- } else {
- usable_len = flen;
- }
- /* allocate buffer for the string, and
- read the contents of the temporary file */
- if (!(str = my_malloc(usable_len + 1, MYF(0))))
- {
- mutex_exit_noninline(&srv_monitor_file_mutex);
- DBUG_RETURN(-1);
- }
- rewind(srv_monitor_file);
- if (flen < MAX_STATUS_SIZE) {
- /* Display the entire output. */
- flen = fread(str, 1, flen, srv_monitor_file);
- } else if (trx_list_end < (ulint) flen
- && trx_list_start < trx_list_end
- && trx_list_start + (flen - trx_list_end)
- < MAX_STATUS_SIZE - sizeof truncated_msg - 1) {
- /* Omit the beginning of the list of active transactions. */
- long len = fread(str, 1, trx_list_start, srv_monitor_file);
- memcpy(str + len, truncated_msg, sizeof truncated_msg - 1);
- len += sizeof truncated_msg - 1;
- usable_len = (MAX_STATUS_SIZE - 1) - len;
- fseek(srv_monitor_file, flen - usable_len, SEEK_SET);
- len += fread(str + len, 1, usable_len, srv_monitor_file);
- flen = len;
- } else {
- /* Omit the end of the output. */
- flen = fread(str, 1, MAX_STATUS_SIZE - 1, srv_monitor_file);
- }
- mutex_exit_noninline(&srv_monitor_file_mutex);
- List<Item> field_list;
- field_list.push_back(new Item_empty_string("Status", flen));
- if (protocol->send_fields(&field_list, 1)) {
- my_free(str, MYF(0));
- DBUG_RETURN(-1);
- }
- protocol->prepare_for_resend();
- protocol->store(str, flen, system_charset_info);
- my_free(str, MYF(0));
- if (protocol->write())
- DBUG_RETURN(-1);
- send_eof(thd);
- DBUG_RETURN(0);
- }
- /****************************************************************************
- Handling the shared INNOBASE_SHARE structure that is needed to provide table
- locking.
- ****************************************************************************/
- static mysql_byte* innobase_get_key(INNOBASE_SHARE *share,uint *length,
- my_bool not_used __attribute__((unused)))
- {
- *length=share->table_name_length;
- return (mysql_byte*) share->table_name;
- }
- static INNOBASE_SHARE *get_share(const char *table_name)
- {
- INNOBASE_SHARE *share;
- pthread_mutex_lock(&innobase_mutex);
- uint length=(uint) strlen(table_name);
- if (!(share=(INNOBASE_SHARE*) hash_search(&innobase_open_tables,
- (mysql_byte*) table_name,
- length)))
- {
- if ((share=(INNOBASE_SHARE *) my_malloc(sizeof(*share)+length+1,
- MYF(MY_WME | MY_ZEROFILL))))
- {
- share->table_name_length=length;
- share->table_name=(char*) (share+1);
- strmov(share->table_name,table_name);
- if (my_hash_insert(&innobase_open_tables, (mysql_byte*) share))
- {
- pthread_mutex_unlock(&innobase_mutex);
- my_free((gptr) share,0);
- return 0;
- }
- thr_lock_init(&share->lock);
- pthread_mutex_init(&share->mutex,MY_MUTEX_INIT_FAST);
- }
- }
- share->use_count++;
- pthread_mutex_unlock(&innobase_mutex);
- return share;
- }
- static void free_share(INNOBASE_SHARE *share)
- {
- pthread_mutex_lock(&innobase_mutex);
- if (!--share->use_count)
- {
- hash_delete(&innobase_open_tables, (mysql_byte*) share);
- thr_lock_delete(&share->lock);
- pthread_mutex_destroy(&share->mutex);
- my_free((gptr) share, MYF(0));
- }
- pthread_mutex_unlock(&innobase_mutex);
- }
- /*********************************************************************
- Converts a MySQL table lock stored in the 'lock' field of the handle to
- a proper type before storing pointer to the lock into an array of pointers.
- MySQL also calls this if it wants to reset some table locks to a not-locked
- state during the processing of an SQL query. An example is that during a
- SELECT the read lock is released early on the 'const' tables where we only
- fetch one row. MySQL does not call this when it releases all locks at the
- end of an SQL statement. */
- THR_LOCK_DATA**
- ha_innobase::store_lock(
- /*====================*/
- /* out: pointer to the next
- element in the 'to' array */
- THD* thd, /* in: user thread handle */
- THR_LOCK_DATA** to, /* in: pointer to an array
- of pointers to lock structs;
- pointer to the 'lock' field
- of current handle is stored
- next to this array */
- enum thr_lock_type lock_type) /* in: lock type to store in
- 'lock' */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- if ((lock_type == TL_READ && thd->in_lock_tables) ||
- (lock_type == TL_READ_HIGH_PRIORITY && thd->in_lock_tables) ||
- lock_type == TL_READ_WITH_SHARED_LOCKS ||
- lock_type == TL_READ_NO_INSERT ||
- (thd->lex->sql_command != SQLCOM_SELECT
- && lock_type != TL_IGNORE)) {
- /* The OR cases above are in this order:
- 1) MySQL is doing LOCK TABLES ... READ LOCAL, or
- 2) (we do not know when TL_READ_HIGH_PRIORITY is used), or
- 3) this is a SELECT ... IN SHARE MODE, or
- 4) we are doing a complex SQL statement like
- INSERT INTO ... SELECT ... and the logical logging (MySQL
- binlog) requires the use of a locking read, or
- MySQL is doing LOCK TABLES ... READ.
- 5) we let InnoDB do locking reads for all SQL statements that
- are not simple SELECTs; note that select_lock_type in this
- case may get strengthened in ::external_lock() to LOCK_X. */
- if (srv_locks_unsafe_for_binlog &&
- prebuilt->trx->isolation_level != TRX_ISO_SERIALIZABLE &&
- (lock_type == TL_READ || lock_type == TL_READ_NO_INSERT) &&
- thd->lex->sql_command != SQLCOM_SELECT &&
- thd->lex->sql_command != SQLCOM_UPDATE_MULTI &&
- thd->lex->sql_command != SQLCOM_DELETE_MULTI &&
- thd->lex->sql_command != SQLCOM_LOCK_TABLES) {
- /* In case we have innobase_locks_unsafe_for_binlog
- option set and isolation level of the transaction
- is not set to serializable and MySQL is doing
- INSERT INTO...SELECT or UPDATE ... = (SELECT ...)
- without FOR UPDATE or IN SHARE MODE in select, then
- we use consistent read for select. */
- prebuilt->select_lock_type = LOCK_NONE;
- prebuilt->stored_select_lock_type = LOCK_NONE;
- } else if (thd->lex->sql_command == SQLCOM_CHECKSUM) {
- /* Use consistent read for checksum table and
- convert lock type to the TL_READ */
- prebuilt->select_lock_type = LOCK_NONE;
- prebuilt->stored_select_lock_type = LOCK_NONE;
- lock.type = TL_READ;
- } else {
- prebuilt->select_lock_type = LOCK_S;
- prebuilt->stored_select_lock_type = LOCK_S;
- }
- } else if (lock_type != TL_IGNORE) {
- /* In ha_berkeley.cc there is a comment that MySQL
- may in exceptional cases call this with TL_IGNORE also
- when it is NOT going to release the lock. */
- /* We set possible LOCK_X value in external_lock, not yet
- here even if this would be SELECT ... FOR UPDATE */
- prebuilt->select_lock_type = LOCK_NONE;
- prebuilt->stored_select_lock_type = LOCK_NONE;
- }
- if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) {
- if (lock_type == TL_READ && thd->in_lock_tables) {
- /* We come here if MySQL is processing LOCK TABLES
- ... READ LOCAL. MyISAM under that table lock type
- reads the table as it was at the time the lock was
- granted (new inserts are allowed, but not seen by the
- reader). To get a similar effect on an InnoDB table,
- we must use LOCK TABLES ... READ. We convert the lock
- type here, so that for InnoDB, READ LOCAL is
- equivalent to READ. This will change the InnoDB
- behavior in mysqldump, so that dumps of InnoDB tables
- are consistent with dumps of MyISAM tables. */
- lock_type = TL_READ_NO_INSERT;
- }
- /* If we are not doing a LOCK TABLE or DISCARD/IMPORT
- TABLESPACE, then allow multiple writers */
- if ((lock_type >= TL_WRITE_CONCURRENT_INSERT &&
- lock_type <= TL_WRITE) && !thd->in_lock_tables
- && !thd->tablespace_op
- && thd->lex->sql_command != SQLCOM_CREATE_TABLE) {
- lock_type = TL_WRITE_ALLOW_WRITE;
- }
- /* In queries of type INSERT INTO t1 SELECT ... FROM t2 ...
- MySQL would use the lock TL_READ_NO_INSERT on t2, and that
- would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts
- to t2. Convert the lock to a normal read lock to allow
- concurrent inserts to t2. */
-
- if (lock_type == TL_READ_NO_INSERT && !thd->in_lock_tables) {
- lock_type = TL_READ;
- }
-
- lock.type=lock_type;
- }
- *to++= &lock;
- return(to);
- }
- /***********************************************************************
- This function initializes the auto-inc counter if it has not been
- initialized yet. This function does not change the value of the auto-inc
- counter if it already has been initialized. In paramete ret returns
- the value of the auto-inc counter. */
- int
- ha_innobase::innobase_read_and_init_auto_inc(
- /*=========================================*/
- /* out: 0 or error code: deadlock or
- lock wait timeout */
- longlong* ret) /* out: auto-inc value */
- {
- row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
- longlong auto_inc;
- int error;
- ut_a(prebuilt);
- ut_a(prebuilt->trx ==
- (trx_t*) current_thd->transaction.all.innobase_tid);
- ut_a(prebuilt->table);
-
- /* In case MySQL calls this in the middle of a SELECT query, release
- possible adaptive hash latch to avoid deadlocks of threads */
- trx_search_latch_release_if_reserved(prebuilt->trx);
- auto_inc = dict_table_autoinc_read(prebuilt->table);
- if (auto_inc != 0) {
- /* Already initialized */
- *ret = auto_inc;
-
- return(0);
- }
- error = row_lock_table_autoinc_for_mysql(prebuilt);
- if (error != DB_SUCCESS) {
- error = convert_error_code_to_mysql(error, user_thd);
- goto func_exit;
- }
- /* Check again if someone has initialized the counter meanwhile */
- auto_inc = dict_table_autoinc_read(prebuilt->table);
- if (auto_inc != 0) {
- *ret = auto_inc;
-
- return(0);
- }
- (void) extra(HA_EXTRA_KEYREAD);
- index_init(table->next_number_index);
- /* We use an exclusive lock when we read the max key value from the
- auto-increment column index. This is because then build_template will
- advise InnoDB to fetch all columns. In SHOW TABLE STATUS the query
- id of the auto-increment column is not changed, and previously InnoDB
- did not fetch it, causing SHOW TABLE STATUS to show wrong values
- for the autoinc column. */
- prebuilt->select_lock_type = LOCK_X;
- /* Play safe and also give in another way the hint to fetch
- all columns in the key: */
-
- prebuilt->hint_need_to_fetch_extra_cols = ROW_RETRIEVE_ALL_COLS;
- prebuilt->trx->mysql_n_tables_locked += 1;
-
- error = index_last(table->record[1]);
- if (error) {
- if (error == HA_ERR_END_OF_FILE) {
- /* The table was empty, initialize to 1 */
- auto_inc = 1;
- error = 0;
- } else {
- /* Deadlock or a lock wait timeout */
- auto_inc = -1;
- goto func_exit;
- }
- } else {
- /* Initialize to max(col) + 1 */
- auto_inc = (longlong) table->next_number_field->
- val_int_offset(table->rec_buff_length) + 1;
- }
- dict_table_autoinc_initialize(prebuilt->table, auto_inc);
- func_exit:
- (void) extra(HA_EXTRA_NO_KEYREAD);
- index_end();
- *ret = auto_inc;
- return(error);
- }
- /***********************************************************************
- This function initializes the auto-inc counter if it has not been
- initialized yet. This function does not change the value of the auto-inc
- counter if it already has been initialized. Returns the value of the
- auto-inc counter. */
- longlong
- ha_innobase::get_auto_increment()
- /*=============================*/
- /* out: auto-increment column value, -1 if error
- (deadlock or lock wait timeout) */
- {
- longlong nr;
- int error;
-
- error = innobase_read_and_init_auto_inc(&nr);
- if (error) {
- return(-1);
- }
- return(nr);
- }
- /***********************************************************************
- This function stores the binlog offset and flushes logs. */
- void
- innobase_store_binlog_offset_and_flush_log(
- /*=======================================*/
- char *binlog_name, /* in: binlog name */
- longlong offset) /* in: binlog offset */
- {
- mtr_t mtr;
-
- assert(binlog_name != NULL);
- /* Start a mini-transaction */
- mtr_start_noninline(&mtr);
- /* Update the latest MySQL binlog name and offset info
- in trx sys header */
- trx_sys_update_mysql_binlog_offset(
- binlog_name,
- offset,
- TRX_SYS_MYSQL_LOG_INFO, &mtr);
- /* Commits the mini-transaction */
- mtr_commit(&mtr);
-
- /* Syncronous flush of the log buffer to disk */
- log_buffer_flush_to_disk();
- }
- char*
- ha_innobase::get_mysql_bin_log_name()
- {
- return(trx_sys_mysql_bin_log_name);
- }
- ulonglong
- ha_innobase::get_mysql_bin_log_pos()
- {
- /* trx... is ib_longlong, which is a typedef for a 64-bit integer
- (__int64 or longlong) so it's ok to cast it to ulonglong. */
- return(trx_sys_mysql_bin_log_pos);
- }
- extern "C" {
- /**********************************************************************
- This function is used to find the storage length in bytes of the first n
- characters for prefix indexes using a multibyte character set. The function
- finds charset information and returns length of prefix_len characters in the
- index field in bytes.
- NOTE: the prototype of this function is copied to data0type.c! If you change
- this function, you MUST change also data0type.c! */
- ulint
- innobase_get_at_most_n_mbchars(
- /*===========================*/
- /* out: number of bytes occupied by the first
- n characters */
- ulint charset_id, /* in: character set id */
- ulint prefix_len, /* in: prefix length in bytes of the index
- (this has to be divided by mbmaxlen to get the
- number of CHARACTERS n in the prefix) */
- ulint data_len, /* in: length of the string in bytes */
- const char* str) /* in: character string */
- {
- ulint char_length; /* character length in bytes */
- ulint n_chars; /* number of characters in prefix */
- CHARSET_INFO* charset; /* charset used in the field */
- charset = get_charset(charset_id, MYF(MY_WME));
- ut_ad(charset);
- ut_ad(charset->mbmaxlen);
- /* Calculate how many characters at most the prefix index contains */
- n_chars = prefix_len / charset->mbmaxlen;
- /* If the charset is multi-byte, then we must find the length of the
- first at most n chars in the string. If the string contains less
- characters than n, then we return the length to the end of the last
- character. */
- if (charset->mbmaxlen > 1) {
- /* my_charpos() returns the byte length of the first n_chars
- characters, or a value bigger than the length of str, if
- there were not enough full characters in str.
- Why does the code below work:
- Suppose that we are looking for n UTF-8 characters.
- 1) If the string is long enough, then the prefix contains at
- least n complete UTF-8 characters + maybe some extra
- characters + an incomplete UTF-8 character. No problem in
- this case. The function returns the pointer to the
- end of the nth character.
- 2) If the string is not long enough, then the string contains
- the complete value of a column, that is, only complete UTF-8
- characters, and we can store in the column prefix index the
- whole string. */
- char_length = my_charpos(charset, str,
- str + data_len, n_chars);
- if (char_length > data_len) {
- char_length = data_len;
- }
- } else {
- if (data_len < prefix_len) {
- char_length = data_len;
- } else {
- char_length = prefix_len;
- }
- }
- return(char_length);
- }
- }
- extern "C" {
- /**********************************************************************
- This function returns true if
- 1) SQL-query in the current thread
- is either REPLACE or LOAD DATA INFILE REPLACE.
- 2) SQL-query in the current thread
- is INSERT ON DUPLICATE KEY UPDATE.
- NOTE that /mysql/innobase/row/row0ins.c must contain the
- prototype for this function ! */
- ibool
- innobase_query_is_update(void)
- /*==========================*/
- {
- THD* thd;
-
- thd = (THD *)innobase_current_thd();
-
- if ( thd->lex->sql_command == SQLCOM_REPLACE ||
- thd->lex->sql_command == SQLCOM_REPLACE_SELECT ||
- ( thd->lex->sql_command == SQLCOM_LOAD &&
- thd->lex->duplicates == DUP_REPLACE )) {
- return(1);
- }
- if ( thd->lex->sql_command == SQLCOM_INSERT &&
- thd->lex->duplicates == DUP_UPDATE ) {
- return(1);
- }
- return(0);
- }
- }
- #endif /* HAVE_INNOBASE_DB */