diff options
author | Mattias Jonsson <mattias.jonsson@oracle.com> | 2012-09-10 13:32:50 +0200 |
---|---|---|
committer | Mattias Jonsson <mattias.jonsson@oracle.com> | 2012-09-10 13:32:50 +0200 |
commit | 8ce6582c37be81281b7ada6797c456c661c214dd (patch) | |
tree | dd9ae7dc854d8862ade0eaaa3586c019be839fca /sql/ha_partition.cc | |
parent | 7dbada1674aee6914aff096885dd18a7ea7b73c0 (diff) | |
download | mariadb-git-8ce6582c37be81281b7ada6797c456c661c214dd.tar.gz |
Bug#14495351: CRASH IN HA_PARTITION::HANDLE_UNORDERED_NEXT
The partitioning engine does not implement index_next for partitions
which return HA_ERR_KEY_NOT_FOUND in index_read_map.
If HA_ERR_KEY_NOT_FOUND was returned by a partition during
index_read_map, that partition would not be included in following
calls to index_next. If no partition returned a row in index_read_map,
then the subsequent call to index_next would try to use a non existing
handler (index out of bound).
Even after fixing the index out of bound if at least one partition
returned.
So it is really two connected bugs
1) crash due to index out of bound (-1 unsigned).
2) not including partitions that returned HA_ERR_KEY_NOT_FOUND.
Fixed by recording the partitions that returned HA_ERR_KEY_NOT_FOUND,
and include them too when doing handle_ordered_next the first time.
Diffstat (limited to 'sql/ha_partition.cc')
-rw-r--r-- | sql/ha_partition.cc | 218 |
1 files changed, 172 insertions, 46 deletions
diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 14b89bf0dc8..e9ffc7abbac 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -2670,6 +2670,17 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) if (bitmap_init(&m_bulk_insert_started, NULL, m_tot_parts + 1, FALSE)) DBUG_RETURN(error); bitmap_clear_all(&m_bulk_insert_started); + /* + Initialize the bitmap we use to keep track of partitions which returned + HA_ERR_KEY_NOT_FOUND from index_read_map. + */ + if (bitmap_init(&m_key_not_found_partitions, NULL, m_tot_parts, FALSE)) + { + bitmap_free(&m_bulk_insert_started); + DBUG_RETURN(error); + } + bitmap_clear_all(&m_key_not_found_partitions); + m_key_not_found= false; /* Initialize the bitmap we use to determine what partitions are used */ if (!m_is_clone_of) { @@ -2810,6 +2821,7 @@ err_handler: (*file)->close(); err_alloc: bitmap_free(&m_bulk_insert_started); + bitmap_free(&m_key_not_found_partitions); if (!m_is_clone_of) bitmap_free(&(m_part_info->used_partitions)); @@ -2886,6 +2898,7 @@ int ha_partition::close(void) DBUG_ASSERT(table->s == table_share); destroy_record_priority_queue(); bitmap_free(&m_bulk_insert_started); + bitmap_free(&m_key_not_found_partitions); if (!m_is_clone_of) bitmap_free(&(m_part_info->used_partitions)); file= m_file; @@ -4419,21 +4432,24 @@ int ha_partition::index_read_map(uchar *buf, const uchar *key, } -/* +/** Common routine for a number of index_read variants - SYNOPSIS - ha_partition::common_index_read() - buf Buffer where the record should be returned - have_start_key TRUE <=> the left endpoint is available, i.e. - we're in index_read call or in read_range_first - call and the range has left endpoint - - FALSE <=> there is no left endpoint (we're in - read_range_first() call and the range has no left - endpoint) + @param buf Buffer where the record should be returned. + @param have_start_key TRUE <=> the left endpoint is available, i.e. + we're in index_read call or in read_range_first + call and the range has left endpoint. + FALSE <=> there is no left endpoint (we're in + read_range_first() call and the range has no left + endpoint). - DESCRIPTION + @return Operation status + @retval 0 OK + @retval HA_ERR_END_OF_FILE Whole index scanned, without finding the record. + @retval HA_ERR_KEY_NOT_FOUND Record not found, but index cursor positioned. + @retval other error code. + + @details Start scanning the range (when invoked from read_range_first()) or doing an index lookup (when invoked from index_read_XXX): - If possible, perform partition selection @@ -4443,10 +4459,6 @@ int ha_partition::index_read_map(uchar *buf, const uchar *key, handle_unordered_scan_next_partition) YES: Fill the priority queue and get the record that is the first in the ordering - - RETURN - 0 OK - other HA_ERR_END_OF_FILE or other error code. */ int ha_partition::common_index_read(uchar *buf, bool have_start_key) @@ -4456,14 +4468,16 @@ int ha_partition::common_index_read(uchar *buf, bool have_start_key) bool reverse_order= FALSE; DBUG_ENTER("ha_partition::common_index_read"); - DBUG_PRINT("info", ("m_ordered %u m_ordered_scan_ong %u have_start_key %u", - m_ordered, m_ordered_scan_ongoing, have_start_key)); + DBUG_PRINT("info", ("m_ordered %u m_ordered_scan_ong %u", + m_ordered, m_ordered_scan_ongoing)); if (have_start_key) { m_start_key.length= key_len= calculate_key_len(table, active_index, m_start_key.key, m_start_key.keypart_map); + DBUG_PRINT("info", ("have_start_key map %u find_flag %u len %u", + m_start_key.keypart_map, m_start_key.flag, key_len)); DBUG_ASSERT(key_len); } if ((error= partition_scan_set_up(buf, have_start_key))) @@ -4481,24 +4495,16 @@ int ha_partition::common_index_read(uchar *buf, bool have_start_key) } DBUG_PRINT("info", ("m_ordered %u m_o_scan_ong %u have_start_key %u", m_ordered, m_ordered_scan_ongoing, have_start_key)); - if (!m_ordered_scan_ongoing || - (have_start_key && m_start_key.flag == HA_READ_KEY_EXACT && - !m_pkey_is_clustered && - key_len >= m_curr_key_info[0]->key_length)) + if (!m_ordered_scan_ongoing) { /* - We use unordered index scan either when read_range is used and flag - is set to not use ordered or when an exact key is used and in this - case all records will be sorted equal and thus the sort order of the - resulting records doesn't matter. + We use unordered index scan when read_range is used and flag + is set to not use ordered. We also use an unordered index scan when the number of partitions to scan is only one. The unordered index scan will use the partition set created. - Need to set unordered scan ongoing since we can come here even when - it isn't set. */ DBUG_PRINT("info", ("doing unordered scan")); - m_ordered_scan_ongoing= FALSE; error= handle_unordered_scan_next_partition(buf); } else @@ -4616,7 +4622,7 @@ int ha_partition::common_first_last(uchar *buf) int ha_partition::index_read_last_map(uchar *buf, const uchar *key, key_part_map keypart_map) { - DBUG_ENTER("ha_partition::index_read_last"); + DBUG_ENTER("ha_partition::index_read_last_map"); m_ordered= TRUE; // Safety measure end_range= 0; @@ -4709,6 +4715,8 @@ int ha_partition::index_next(uchar * buf) TODO(low priority): If we want partition to work with the HANDLER commands, we must be able to do index_last() -> index_prev() -> index_next() + and if direction changes, we must step back those partitions in + the record queue so we don't return a value from the wrong direction. */ DBUG_ASSERT(m_index_scan_type != partition_index_last); if (!m_ordered_scan_ongoing) @@ -4960,10 +4968,18 @@ int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag) int ha_partition::handle_unordered_next(uchar *buf, bool is_next_same) { - handler *file= m_file[m_part_spec.start_part]; + handler *file; int error; DBUG_ENTER("ha_partition::handle_unordered_next"); + if (m_part_spec.start_part >= m_tot_parts) + { + /* Should never happen! */ + DBUG_ASSERT(0); + DBUG_RETURN(HA_ERR_END_OF_FILE); + } + file= m_file[m_part_spec.start_part]; + /* We should consider if this should be split into three functions as partition_read_range is_next_same are always local constants @@ -5024,6 +5040,7 @@ int ha_partition::handle_unordered_next(uchar *buf, bool is_next_same) int ha_partition::handle_unordered_scan_next_partition(uchar * buf) { uint i; + int saved_error= HA_ERR_END_OF_FILE; DBUG_ENTER("ha_partition::handle_unordered_scan_next_partition"); for (i= m_part_spec.start_part; i <= m_part_spec.end_part; i++) @@ -5074,26 +5091,33 @@ int ha_partition::handle_unordered_scan_next_partition(uchar * buf) } if ((error != HA_ERR_END_OF_FILE) && (error != HA_ERR_KEY_NOT_FOUND)) DBUG_RETURN(error); - DBUG_PRINT("info", ("HA_ERR_END_OF_FILE on partition %d", i)); + + /* + If HA_ERR_KEY_NOT_FOUND, we must return that error instead of + HA_ERR_END_OF_FILE, to be able to continue search. + */ + if (saved_error != HA_ERR_KEY_NOT_FOUND) + saved_error= error; + DBUG_PRINT("info", ("END_OF_FILE/KEY_NOT_FOUND on partition %d", i)); } - m_part_spec.start_part= NO_CURRENT_PART_ID; - DBUG_RETURN(HA_ERR_END_OF_FILE); + if (saved_error == HA_ERR_END_OF_FILE) + m_part_spec.start_part= NO_CURRENT_PART_ID; + DBUG_RETURN(saved_error); } -/* - Common routine to start index scan with ordered results +/** + Common routine to start index scan with ordered results. - SYNOPSIS - handle_ordered_index_scan() - out:buf Read row in MySQL Row Format + @param[out] buf Read row in MySQL Row Format - RETURN VALUE - HA_ERR_END_OF_FILE End of scan - 0 Success - other Error code + @return Operation status + @retval HA_ERR_END_OF_FILE End of scan + @retval HA_ERR_KEY_NOT_FOUNE End of scan + @retval 0 Success + @retval other Error code - DESCRIPTION + @details This part contains the logic to handle index scans that require ordered output. This includes all except those started by read_range_first with the flag ordered set to FALSE. Thus most direct index_read and all @@ -5115,8 +5139,14 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order) uint j= 0; bool found= FALSE; uchar *part_rec_buf_ptr= m_ordered_rec_buffer; + int saved_error= HA_ERR_END_OF_FILE; DBUG_ENTER("ha_partition::handle_ordered_index_scan"); + if (m_key_not_found) + { + m_key_not_found= false; + bitmap_clear_all(&m_key_not_found_partitions); + } m_top_entry= NO_CURRENT_PART_ID; queue_remove_all(&m_queue); @@ -5178,6 +5208,13 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order) { DBUG_RETURN(error); } + else if (error == HA_ERR_KEY_NOT_FOUND) + { + DBUG_PRINT("info", ("HA_ERR_KEY_NOT_FOUND from partition %u", i)); + bitmap_set_bit(&m_key_not_found_partitions, i); + m_key_not_found= true; + saved_error= error; + } part_rec_buf_ptr+= m_rec_length + PARTITION_BYTES_IN_POS; } if (found) @@ -5195,7 +5232,7 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order) DBUG_PRINT("info", ("Record returned from partition %d", m_top_entry)); DBUG_RETURN(0); } - DBUG_RETURN(HA_ERR_END_OF_FILE); + DBUG_RETURN(saved_error); } @@ -5223,6 +5260,59 @@ void ha_partition::return_top_record(uchar *buf) } +/** + Add index_next/prev from partitions without exact match. + + If there where any partitions that returned HA_ERR_KEY_NOT_FOUND when + ha_index_read_map was done, those partitions must be included in the + following index_next/prev call. +*/ + +int ha_partition::handle_ordered_index_scan_key_not_found() +{ + int error; + uint i; + uchar *part_buf= m_ordered_rec_buffer; + uchar *curr_rec_buf= NULL; + DBUG_ENTER("ha_partition::handle_ordered_index_scan_key_not_found"); + DBUG_ASSERT(m_key_not_found); + /* + Loop over all used partitions to get the correct offset + into m_ordered_rec_buffer. + */ + for (i= 0; i < m_tot_parts; i++) + { + if (!bitmap_is_set(&m_part_info->used_partitions, i)) + continue; + + if (bitmap_is_set(&m_key_not_found_partitions, i)) + { + /* + This partition is used and did return HA_ERR_KEY_NOT_FOUND + in index_read_map. + */ + curr_rec_buf= part_buf + PARTITION_BYTES_IN_POS; + error= m_file[i]->index_next(curr_rec_buf); + /* HA_ERR_KEY_NOT_FOUND is not allowed from index_next! */ + DBUG_ASSERT(error != HA_ERR_KEY_NOT_FOUND); + if (!error) + queue_insert(&m_queue, part_buf); + else if (error != HA_ERR_END_OF_FILE && error != HA_ERR_KEY_NOT_FOUND) + DBUG_RETURN(error); + } + part_buf+= m_rec_length + PARTITION_BYTES_IN_POS; + } + DBUG_ASSERT(curr_rec_buf); + bitmap_clear_all(&m_key_not_found_partitions); + m_key_not_found= false; + + /* Update m_top_entry, which may have changed. */ + uchar *key_buffer= queue_top(&m_queue); + m_top_entry= uint2korr(key_buffer); + DBUG_RETURN(0); +} + + /* Common routine to handle index_next with ordered results @@ -5242,9 +5332,45 @@ int ha_partition::handle_ordered_next(uchar *buf, bool is_next_same) int error; uint part_id= m_top_entry; uchar *rec_buf= queue_top(&m_queue) + PARTITION_BYTES_IN_POS; - handler *file= m_file[part_id]; + handler *file; DBUG_ENTER("ha_partition::handle_ordered_next"); + if (m_key_not_found) + { + if (is_next_same) + { + /* Only rows which match the key. */ + m_key_not_found= false; + bitmap_clear_all(&m_key_not_found_partitions); + } + else + { + /* There are partitions not included in the index record queue. */ + uint old_elements= m_queue.elements; + if ((error= handle_ordered_index_scan_key_not_found())) + DBUG_RETURN(error); + /* + If the queue top changed, i.e. one of the partitions that gave + HA_ERR_KEY_NOT_FOUND in index_read_map found the next record, + return it. + Otherwise replace the old with a call to index_next (fall through). + */ + if (old_elements != m_queue.elements && part_id != m_top_entry) + { + return_top_record(buf); + DBUG_RETURN(0); + } + } + } + if (part_id >= m_tot_parts) + { + /* This should never happen! */ + DBUG_ASSERT(0); + DBUG_RETURN(HA_ERR_END_OF_FILE); + } + + file= m_file[part_id]; + if (m_index_scan_type == partition_read_range) { error= file->read_range_next(); |