| 1 | /* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult AB |
| 2 | |
| 3 | This program is free software; you can redistribute it and/or modify |
| 4 | it under the terms of the GNU General Public License as published by |
| 5 | the Free Software Foundation; version 2 of the License. |
| 6 | |
| 7 | This program is distributed in the hope that it will be useful, |
| 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | GNU General Public License for more details. |
| 11 | |
| 12 | You should have received a copy of the GNU General Public License |
| 13 | along with this program; if not, write to the Free Software |
| 14 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ |
| 15 | |
| 16 | /* Read record based on a key */ |
| 17 | |
| 18 | #include "maria_def.h" |
| 19 | #include "ma_rt_index.h" |
| 20 | |
| 21 | /** |
| 22 | Read a record using key |
| 23 | |
| 24 | @note |
| 25 | Ordinary search_flag is 0 ; Give error if no record with key |
| 26 | */ |
| 27 | |
| 28 | int maria_rkey(MARIA_HA *info, uchar *buf, int inx, const uchar *key_data, |
| 29 | key_part_map keypart_map, enum ha_rkey_function search_flag) |
| 30 | { |
| 31 | uchar *key_buff; |
| 32 | MARIA_SHARE *share= info->s; |
| 33 | MARIA_KEYDEF *keyinfo; |
| 34 | HA_KEYSEG *last_used_keyseg; |
| 35 | uint32 nextflag; |
| 36 | MARIA_KEY key; |
| 37 | ICP_RESULT icp_res= ICP_MATCH; |
| 38 | DBUG_ENTER("maria_rkey" ); |
| 39 | DBUG_PRINT("enter" , ("base:%p buf:%p inx: %d search_flag: %d" , |
| 40 | info, buf, inx, search_flag)); |
| 41 | |
| 42 | if ((inx = _ma_check_index(info,inx)) < 0) |
| 43 | DBUG_RETURN(my_errno); |
| 44 | |
| 45 | info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); |
| 46 | info->last_key_func= search_flag; |
| 47 | keyinfo= info->last_key.keyinfo; |
| 48 | |
| 49 | key_buff= info->lastkey_buff2; |
| 50 | |
| 51 | if (info->once_flags & USE_PACKED_KEYS) |
| 52 | { |
| 53 | info->once_flags&= ~USE_PACKED_KEYS; /* Reset flag */ |
| 54 | /* |
| 55 | key is already packed!; This happens when we are using a MERGE TABLE |
| 56 | In this key 'key_part_map' is the length of the key ! |
| 57 | */ |
| 58 | if (key_buff != key_data) |
| 59 | bmove(key_buff, key_data, keypart_map); |
| 60 | key.data= key_buff; |
| 61 | key.keyinfo= keyinfo; |
| 62 | key.data_length= keypart_map; |
| 63 | key.ref_length= 0; |
| 64 | key.flag= 0; |
| 65 | |
| 66 | last_used_keyseg= keyinfo->seg + info->last_used_keyseg; |
| 67 | } |
| 68 | else |
| 69 | { |
| 70 | DBUG_ASSERT(keypart_map); |
| 71 | /* Save the packed key for later use in the second buffer of lastkey. */ |
| 72 | _ma_pack_key(info, &key, inx, key_buff, key_data, |
| 73 | keypart_map, &last_used_keyseg); |
| 74 | /* Save packed_key_length for use by the MERGE engine. */ |
| 75 | info->pack_key_length= key.data_length; |
| 76 | info->last_used_keyseg= (uint16) (last_used_keyseg - |
| 77 | keyinfo->seg); |
| 78 | DBUG_EXECUTE("key" , _ma_print_key(DBUG_FILE, &key);); |
| 79 | } |
| 80 | |
| 81 | if (fast_ma_readinfo(info)) |
| 82 | goto err; |
| 83 | if (share->lock_key_trees) |
| 84 | mysql_rwlock_rdlock(&keyinfo->root_lock); |
| 85 | |
| 86 | nextflag= maria_read_vec[search_flag] | key.flag; |
| 87 | if (search_flag != HA_READ_KEY_EXACT) |
| 88 | { |
| 89 | /* Assume we will get a read next/previous call after this one */ |
| 90 | nextflag|= SEARCH_SAVE_BUFF; |
| 91 | } |
| 92 | switch (keyinfo->key_alg) { |
| 93 | #ifdef HAVE_RTREE_KEYS |
| 94 | case HA_KEY_ALG_RTREE: |
| 95 | if (maria_rtree_find_first(info, &key, nextflag) < 0) |
| 96 | { |
| 97 | _ma_set_fatal_error(share, HA_ERR_CRASHED); |
| 98 | info->cur_row.lastpos= HA_OFFSET_ERROR; |
| 99 | } |
| 100 | break; |
| 101 | #endif |
| 102 | case HA_KEY_ALG_BTREE: |
| 103 | default: |
| 104 | if (!_ma_search(info, &key, nextflag, info->s->state.key_root[inx])) |
| 105 | { |
| 106 | MARIA_KEY lastkey; |
| 107 | /* |
| 108 | Found a key, but it might not be usable. We cannot use rows that |
| 109 | are inserted by other threads after we got our table lock |
| 110 | ("concurrent inserts"). The record may not even be present yet. |
| 111 | Keys are inserted into the index(es) before the record is |
| 112 | inserted into the data file. |
| 113 | |
| 114 | If index condition is present, it must be either satisfied or |
| 115 | not satisfied with an out-of-range condition. |
| 116 | */ |
| 117 | if ((*share->row_is_visible)(info) && |
| 118 | ((icp_res= ma_check_index_cond(info, inx, buf)) != ICP_NO_MATCH)) |
| 119 | break; |
| 120 | |
| 121 | /* The key references a concurrently inserted record. */ |
| 122 | if (search_flag == HA_READ_KEY_EXACT && |
| 123 | last_used_keyseg == keyinfo->seg + keyinfo->keysegs) |
| 124 | { |
| 125 | /* Simply ignore the key if it matches exactly. (Bug #29838) */ |
| 126 | my_errno= HA_ERR_KEY_NOT_FOUND; |
| 127 | info->cur_row.lastpos= HA_OFFSET_ERROR; |
| 128 | break; |
| 129 | } |
| 130 | |
| 131 | lastkey.keyinfo= keyinfo; |
| 132 | lastkey.data= info->lastkey_buff; |
| 133 | do |
| 134 | { |
| 135 | uint not_used[2]; |
| 136 | /* |
| 137 | Skip rows that are inserted by other threads since we got |
| 138 | a lock. Note that this can only happen if we are not |
| 139 | searching after a full length exact key, because the keys |
| 140 | are sorted according to position. |
| 141 | */ |
| 142 | lastkey.data_length= info->last_key.data_length; |
| 143 | lastkey.ref_length= info->last_key.ref_length; |
| 144 | lastkey.flag= info->last_key.flag; |
| 145 | if (_ma_search_next(info, &lastkey, maria_readnext_vec[search_flag], |
| 146 | info->s->state.key_root[inx])) |
| 147 | break; /* purecov: inspected */ |
| 148 | |
| 149 | /* |
| 150 | If we are at the last key on the key page, allow writers to |
| 151 | access the index. |
| 152 | */ |
| 153 | if (info->int_keypos >= info->int_maxpos && |
| 154 | ma_yield_and_check_if_killed(info, inx)) |
| 155 | { |
| 156 | DBUG_ASSERT(info->cur_row.lastpos == HA_OFFSET_ERROR); |
| 157 | break; |
| 158 | } |
| 159 | |
| 160 | /* |
| 161 | Check that the found key does still match the search. |
| 162 | _ma_search_next() delivers the next key regardless of its |
| 163 | value. |
| 164 | */ |
| 165 | if (!(nextflag & (SEARCH_BIGGER | SEARCH_SMALLER)) && |
| 166 | ha_key_cmp(keyinfo->seg, info->last_key.data, key.data, |
| 167 | key.data_length, SEARCH_FIND, not_used)) |
| 168 | { |
| 169 | /* purecov: begin inspected */ |
| 170 | my_errno= HA_ERR_KEY_NOT_FOUND; |
| 171 | info->cur_row.lastpos= HA_OFFSET_ERROR; |
| 172 | break; |
| 173 | /* purecov: end */ |
| 174 | } |
| 175 | |
| 176 | } while (!(*share->row_is_visible)(info) || |
| 177 | ((icp_res= ma_check_index_cond(info, inx, buf)) == 0)); |
| 178 | } |
| 179 | else |
| 180 | { |
| 181 | DBUG_ASSERT(info->cur_row.lastpos); |
| 182 | } |
| 183 | } |
| 184 | if (share->lock_key_trees) |
| 185 | mysql_rwlock_unlock(&keyinfo->root_lock); |
| 186 | |
| 187 | if (info->cur_row.lastpos == HA_OFFSET_ERROR) |
| 188 | { |
| 189 | if (icp_res == ICP_OUT_OF_RANGE) |
| 190 | { |
| 191 | /* We don't want HA_ERR_END_OF_FILE in this particular case */ |
| 192 | my_errno= HA_ERR_KEY_NOT_FOUND; |
| 193 | } |
| 194 | fast_ma_writeinfo(info); |
| 195 | goto err; |
| 196 | } |
| 197 | |
| 198 | /* Calculate length of the found key; Used by maria_rnext_same */ |
| 199 | if ((keyinfo->flag & HA_VAR_LENGTH_KEY)) |
| 200 | info->last_rkey_length= _ma_keylength_part(keyinfo, info->lastkey_buff, |
| 201 | last_used_keyseg); |
| 202 | else |
| 203 | info->last_rkey_length= key.data_length; |
| 204 | |
| 205 | /* Check if we don't want to have record back, only error message */ |
| 206 | if (!buf) |
| 207 | { |
| 208 | fast_ma_writeinfo(info); |
| 209 | DBUG_RETURN(0); |
| 210 | } |
| 211 | if (!(*info->read_record)(info, buf, info->cur_row.lastpos)) |
| 212 | { |
| 213 | info->update|= HA_STATE_AKTIV; /* Record is read */ |
| 214 | DBUG_RETURN(0); |
| 215 | } |
| 216 | |
| 217 | info->cur_row.lastpos= HA_OFFSET_ERROR; /* Didn't find row */ |
| 218 | |
| 219 | err: |
| 220 | /* Store last used key as a base for read next */ |
| 221 | memcpy(info->last_key.data, key_buff, key.data_length); |
| 222 | info->last_key.data_length= key.data_length; |
| 223 | info->last_key.ref_length= info->s->base.rec_reflength; |
| 224 | info->last_key.flag= 0; |
| 225 | /* Create key with rowid 0 */ |
| 226 | bzero((char*) info->last_key.data + info->last_key.data_length, |
| 227 | info->s->base.rec_reflength); |
| 228 | |
| 229 | if (search_flag == HA_READ_AFTER_KEY) |
| 230 | info->update|=HA_STATE_NEXT_FOUND; /* Previous gives last row */ |
| 231 | DBUG_RETURN(my_errno); |
| 232 | } /* _ma_rkey */ |
| 233 | |
| 234 | |
| 235 | /* |
| 236 | Yield to possible other writers during a index scan. |
| 237 | Check also if we got killed by the user and if yes, return |
| 238 | HA_ERR_LOCK_WAIT_TIMEOUT |
| 239 | |
| 240 | return 0 ok |
| 241 | return 1 Query has been requested to be killed |
| 242 | */ |
| 243 | |
| 244 | my_bool ma_yield_and_check_if_killed(MARIA_HA *info, int inx) |
| 245 | { |
| 246 | MARIA_SHARE *share; |
| 247 | if (ma_killed(info)) |
| 248 | { |
| 249 | /* purecov: begin tested */ |
| 250 | /* Mark that we don't have an active row */ |
| 251 | info->cur_row.lastpos= HA_OFFSET_ERROR; |
| 252 | /* Set error that we where aborted by kill from application */ |
| 253 | my_errno= HA_ERR_ABORTED_BY_USER; |
| 254 | return 1; |
| 255 | /* purecov: end */ |
| 256 | } |
| 257 | |
| 258 | if ((share= info->s)->lock_key_trees) |
| 259 | { |
| 260 | /* Give writers a chance to access index */ |
| 261 | mysql_rwlock_unlock(&share->keyinfo[inx].root_lock); |
| 262 | mysql_rwlock_rdlock(&share->keyinfo[inx].root_lock); |
| 263 | } |
| 264 | return 0; |
| 265 | } |
| 266 | |
| 267 | |