📄 row0sel.c
字号:
to index! */ byte* buf, /* in: buffer to use in field conversions */ ulint buf_len, /* in: buffer length */ dict_index_t* index, /* in: index of the key value */ byte* key_ptr, /* in: MySQL key value */ ulint key_len, /* in: MySQL key value length */ trx_t* trx) /* in: transaction */{ byte* original_buf = buf; byte* original_key_ptr = key_ptr; dict_field_t* field; dfield_t* dfield; ulint data_offset; ulint data_len; ulint data_field_len; ibool is_null; byte* key_end; ulint n_fields = 0; ulint type; /* For documentation of the key value storage format in MySQL, see ha_innobase::store_key_val_for_row() in ha_innodb.cc. */ key_end = key_ptr + key_len; /* Permit us to access any field in the tuple (ULINT_MAX): */ dtuple_set_n_fields(tuple, ULINT_MAX); dfield = dtuple_get_nth_field(tuple, 0); field = dict_index_get_nth_field(index, 0); if (dfield_get_type(dfield)->mtype == DATA_SYS) { /* A special case: we are looking for a position in the generated clustered index which InnoDB automatically added to a table with no primary key: the first and the only ordering column is ROW_ID which InnoDB stored to the key_ptr buffer. */ ut_a(key_len == DATA_ROW_ID_LEN); dfield_set_data(dfield, key_ptr, DATA_ROW_ID_LEN); dtuple_set_n_fields(tuple, 1); return; } while (key_ptr < key_end) { ut_a(dict_col_get_type(field->col)->mtype == dfield_get_type(dfield)->mtype); data_offset = 0; is_null = FALSE; if (!(dfield_get_type(dfield)->prtype & DATA_NOT_NULL)) { /* The first byte in the field tells if this is an SQL NULL value */ data_offset = 1; if (*key_ptr != 0) { dfield_set_data(dfield, NULL, UNIV_SQL_NULL); is_null = TRUE; } } type = dfield_get_type(dfield)->mtype; /* Calculate data length and data field total length */ if (type == DATA_BLOB) { /* The key field is a column prefix of a BLOB or TEXT */ ut_a(field->prefix_len > 0); /* MySQL stores the actual data length to the first 2 bytes after the optional SQL NULL marker byte. The storage format is little-endian, that is, the most significant byte at a higher address. In UTF-8, MySQL seems to reserve field->prefix_len bytes for storing this field in the key value buffer, even though the actual value only takes data_len bytes from the start. */ data_len = key_ptr[data_offset] + 256 * key_ptr[data_offset + 1]; data_field_len = data_offset + 2 + field->prefix_len; data_offset += 2; /* Now that we know the length, we store the column value like it would be a fixed char field */ } else if (field->prefix_len > 0) { /* Looks like MySQL pads unused end bytes in the prefix with space. Therefore, also in UTF-8, it is ok to compare with a prefix containing full prefix_len bytes, and no need to take at most prefix_len / 3 UTF-8 characters from the start. If the prefix is used as the upper end of a LIKE 'abc%' query, then MySQL pads the end with chars 0xff. TODO: in that case does it any harm to compare with the full prefix_len bytes. How do characters 0xff in UTF-8 behave? */ data_len = field->prefix_len; data_field_len = data_offset + data_len; } else { data_len = dfield_get_type(dfield)->len; data_field_len = data_offset + data_len; } if (dtype_get_mysql_type(dfield_get_type(dfield)) == DATA_MYSQL_TRUE_VARCHAR && dfield_get_type(dfield)->mtype != DATA_INT) { /* In a MySQL key value format, a true VARCHAR is always preceded by 2 bytes of a length field. dfield_get_type(dfield)->len returns the maximum 'payload' len in bytes. That does not include the 2 bytes that tell the actual data length. We added the check != DATA_INT to make sure we do not treat MySQL ENUM or SET as a true VARCHAR! */ data_len += 2; data_field_len += 2; } /* Storing may use at most data_len bytes of buf */ if (!is_null) { row_mysql_store_col_in_innobase_format( dfield, buf, FALSE, /* MySQL key value format col */ key_ptr + data_offset, data_len, index->table->comp); buf += data_len; } key_ptr += data_field_len; if (key_ptr > key_end) { /* The last field in key was not a complete key field but a prefix of it. Print a warning about this! HA_READ_PREFIX_LAST does not currently work in InnoDB with partial-field key value prefixes. Since MySQL currently uses a padding trick to calculate LIKE 'abc%' type queries there should never be partial-field prefixes in searches. */ ut_print_timestamp(stderr); fputs( " InnoDB: Warning: using a partial-field key prefix in search.\n" "InnoDB: ", stderr); dict_index_name_print(stderr, trx, index); fprintf(stderr, ". Last data field length %lu bytes,\n" "InnoDB: key ptr now exceeds key end by %lu bytes.\n" "InnoDB: Key value in the MySQL format:\n", (ulong) data_field_len, (ulong) (key_ptr - key_end)); fflush(stderr); ut_print_buf(stderr, original_key_ptr, key_len); fprintf(stderr, "\n"); if (!is_null) { dfield->len -= (ulint)(key_ptr - key_end); } } n_fields++; field++; dfield++; } ut_a(buf <= original_buf + buf_len); /* We set the length of tuple to n_fields: we assume that the memory area allocated for it is big enough (usually bigger than n_fields). */ dtuple_set_n_fields(tuple, n_fields);}/******************************************************************Stores the row id to the prebuilt struct. */staticvoidrow_sel_store_row_id_to_prebuilt(/*=============================*/ row_prebuilt_t* prebuilt, /* in: prebuilt */ rec_t* index_rec, /* in: record */ dict_index_t* index, /* in: index of the record */ const ulint* offsets) /* in: rec_get_offsets (index_rec, index) */{ byte* data; ulint len; ut_ad(rec_offs_validate(index_rec, index, offsets)); data = rec_get_nth_field(index_rec, offsets, dict_index_get_sys_col_pos(index, DATA_ROW_ID), &len); if (len != DATA_ROW_ID_LEN) { fprintf(stderr,"InnoDB: Error: Row id field is wrong length %lu in ", (ulong) len); dict_index_name_print(stderr, prebuilt->trx, index); fprintf(stderr, "\n""InnoDB: Field number %lu, record:\n", (ulong) dict_index_get_sys_col_pos(index, DATA_ROW_ID)); rec_print_new(stderr, index_rec, offsets); putc('\n', stderr); ut_error; } ut_memcpy(prebuilt->row_id, data, len);}/******************************************************************Stores a non-SQL-NULL field in the MySQL format. The counterpart of thisfunction is row_mysql_store_col_in_innobase_format() in row0mysql.c. */staticvoidrow_sel_field_store_in_mysql_format(/*================================*/ byte* dest, /* in/out: buffer where to store; NOTE that BLOBs are not in themselves stored here: the caller must allocate and copy the BLOB into buffer before, and pass the pointer to the BLOB in 'data' */ const mysql_row_templ_t* templ, /* in: MySQL column template. Its following fields are referenced: type, is_unsigned, mysql_col_len, mbminlen, mbmaxlen */ byte* data, /* in: data to store */ ulint len) /* in: length of the data */{ byte* ptr; byte* field_end; byte* pad_ptr; ut_ad(len != UNIV_SQL_NULL); if (templ->type == DATA_INT) { /* Convert integer data from Innobase to a little-endian format, sign bit restored to normal */ ptr = dest + len; for (;;) { ptr--; *ptr = *data; if (ptr == dest) { break; } data++; } if (!templ->is_unsigned) { dest[len - 1] = (byte) (dest[len - 1] ^ 128); } ut_ad(templ->mysql_col_len == len); } else if (templ->type == DATA_VARCHAR || templ->type == DATA_VARMYSQL || templ->type == DATA_BINARY) { field_end = dest + templ->mysql_col_len; if (templ->mysql_type == DATA_MYSQL_TRUE_VARCHAR) { /* This is a >= 5.0.3 type true VARCHAR. Store the length of the data to the first byte or the first two bytes of dest. */ dest = row_mysql_store_true_var_len(dest, len, templ->mysql_length_bytes); } /* Copy the actual data */ ut_memcpy(dest, data, len); /* Pad with trailing spaces. We pad with spaces also the unused end of a >= 5.0.3 true VARCHAR column, just in case MySQL expects its contents to be deterministic. */ pad_ptr = dest + len; ut_ad(templ->mbminlen <= templ->mbmaxlen); /* We handle UCS2 charset strings differently. */ if (templ->mbminlen == 2) { /* A space char is two bytes, 0x0020 in UCS2 */ if (len & 1) { /* A 0x20 has been stripped from the column. Pad it back. */ if (pad_ptr < field_end) { *pad_ptr = 0x20; pad_ptr++; } } /* Pad the rest of the string with 0x0020 */ while (pad_ptr < field_end) { *pad_ptr = 0x00; pad_ptr++; *pad_ptr = 0x20; pad_ptr++; } } else { ut_ad(templ->mbminlen == 1); /* space=0x20 */ memset(pad_ptr, 0x20, field_end - pad_ptr); } } else if (templ->type == DATA_BLOB) { /* Store a pointer to the BLOB buffer to dest: the BLOB was already copied to the buffer in row_sel_store_mysql_rec */ row_mysql_store_blob_ref(dest, templ->mysql_col_len, data, len); } else if (templ->type == DATA_MYSQL) { memcpy(dest, data, len); ut_ad(templ->mysql_col_len >= len); ut_ad(templ->mbmaxlen >= templ->mbminlen); ut_ad(templ->mbmaxlen > templ->mbminlen || templ->mysql_col_len == len); /* The following assertion would fail for old tables containing UTF-8 ENUM columns due to Bug #9526. */ ut_ad(!templ->mbmaxlen || !(templ->mysql_col_len % templ->mbmaxlen)); ut_ad(len * templ->mbmaxlen >= templ->mysql_col_len); if (templ->mbminlen != templ->mbmaxlen) { /* Pad with spaces. This undoes the stripping done in row0mysql.ic, function row_mysql_store_col_in_innobase_format(). */ memset(dest + len, 0x20, templ->mysql_col_len - len); } } else { ut_ad(templ->type == DATA_CHAR || templ->type == DATA_FIXBINARY /*|| templ->type == DATA_SYS_CHILD || templ->type == DATA_SYS*/ || templ->type == DATA_FLOAT || templ->type == DATA_DOUBLE || templ->type == DATA_DECIMAL); ut_ad(templ->mysql_col_len == len); memcpy(dest, data, len); }}/******************************************************************Convert a row in the Innobase format to a row in the MySQL format.Note that the template in prebuilt may advise us to copy only a fewcolumns to mysql_rec, other columns are left blank. All columns may notbe needed in the query. */staticiboolrow_sel_store_mysql_rec(/*====================*/ /* out: TRUE if success, FALSE if could not allocate memory for a BLOB (though we may also assert in that case) */ byte* mysql_rec, /* out: row in the MySQL format */ row_prebuilt_t* prebuilt, /* in: prebuilt struct */ rec_t* rec, /* in: Innobase record in the index which was described in prebuilt's template */ const ulint* offsets) /* in: array returned by rec_get_offsets() */{ mysql_row_templ_t* templ; mem_heap_t* extern_field_heap = NULL; byte* data; ulint len; ulint i; ut_ad(prebuilt->mysql_template); ut_ad(rec_offs_validate(rec, NULL, offsets)); if (UNIV_LIKELY_NULL(prebuilt->blob_heap)) { mem_heap_free(prebuilt->blob_heap); prebuilt->blob_heap = NULL; } for (i = 0; i < prebuilt->n_template; i++) { templ = prebuilt->mysql_template + i; data = rec_get_nth_field(rec, offsets, templ->rec_field_no, &len); if (UNIV_UNLIKELY(rec_offs_nth_extern(offsets, templ->rec_field_no))) { /* Copy an externally stored field to the temporary heap */ ut_a(!prebuilt->trx->has_search_latch); extern_field_heap = mem_heap_create(UNIV_PAGE_SIZE); /* NOTE: if we are retrieving a big BLOB, we may already run out of memory in the next call, which causes an assert */ data = btr_rec_copy_externally_stored_field(rec, offsets, templ->rec_field_no, &len, extern_field_heap); ut_a(len != UNIV_SQL_NULL); } if (len != UNIV_SQL_NULL) { if (UNIV_UNLIKELY(templ->type == DATA_BLOB)) { ut_a(prebuilt->templ_contains_blob); /* A heuristic test that we can allocate the memory for a big BLOB. We have a safety margin of 1000000 bytes. Since the test takes some CPU time, we do not use it for small BLOBs. */ if (UNIV_UNLIKELY(len > 2000000) && UNIV_UNLIKELY(!ut_test_malloc( len + 1000000))) { ut_print_timestamp(stderr); fprintf(stderr," InnoDB: Warning: could not allocate %lu + 1000000 bytes to retrieve\n""InnoDB: a big column. Table name ", (ulong) len); ut_print_name(stderr, prebuilt->trx, prebuilt->table->name); putc('\n', stderr); if (extern_field_heap) { mem_heap_free( extern_field_heap); } return(FALSE); } /* Copy the BLOB data to the BLOB heap of prebuilt */ if (prebuilt->blob_heap == NULL) { prebuilt->blob_heap = mem_heap_create(len); } data = memcpy(mem_heap_alloc( prebuilt->blob_heap, len), data, len); } row_sel_field_store_in_mysql_format( mysql_rec + templ->mysql_col_offset, templ, data, len); /* Cleanup */ if (extern_field_heap) { mem_heap_free(extern_field_heap); extern_field_heap = NULL; } if (templ->mysql_null_bit_mask) { /* It is a nullable column with a non-NULL value */ mysql_rec[templ->mysql_null_byte_offset] &= ~(byte) (templ->mysql_null_bit_mask); } } else { /* MySQL seems to assume the field for an SQL NULL value is set to zero or space. Not taking this into account caused seg faults with NULL BLOB fields, and bug number 154 in the MySQL bug database: GROUP BY and DISTINCT could treat NULL values inequal. */ int pad_char; mysql_rec[templ->mysql_null_byte_offset] |= (byte) (templ->mysql_null_bit_mask); switch (templ->type) { case DATA_VARCHAR: case DATA_BINARY: case DATA_VARMYSQL: if (templ->mysql_type == DATA_MYSQL_TRUE_VARCHAR) { /* This is a >= 5.0.3 type true VARCHAR. Zero the field. */ pad_char = 0x00;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -