| 1 | /* Copyright (c) 2000-2007 MySQL AB, 2009 Sun Microsystems, Inc. |
| 2 | Use is subject to license terms. |
| 3 | |
| 4 | This program is free software; you can redistribute it and/or modify |
| 5 | it under the terms of the GNU General Public License as published by |
| 6 | the Free Software Foundation; version 2 of the License. |
| 7 | |
| 8 | This program is distributed in the hope that it will be useful, |
| 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | GNU General Public License for more details. |
| 12 | |
| 13 | You should have received a copy of the GNU General Public License |
| 14 | along with this program; if not, write to the Free Software |
| 15 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
| 16 | |
| 17 | /* Read record based on a key */ |
| 18 | |
| 19 | #include "myisamdef.h" |
| 20 | #include "rt_index.h" |
| 21 | |
| 22 | /* Read a record using key */ |
| 23 | /* Ordinary search_flag is 0 ; Give error if no record with key */ |
| 24 | |
| 25 | int mi_rkey(MI_INFO *info, uchar *buf, int inx, const uchar *key, |
| 26 | key_part_map keypart_map, enum ha_rkey_function search_flag) |
| 27 | { |
| 28 | uchar *key_buff; |
| 29 | MYISAM_SHARE *share=info->s; |
| 30 | MI_KEYDEF *keyinfo; |
| 31 | HA_KEYSEG *last_used_keyseg; |
| 32 | uint pack_key_length, use_key_length, nextflag; |
| 33 | ICP_RESULT res= ICP_NO_MATCH; |
| 34 | DBUG_ENTER("mi_rkey" ); |
| 35 | DBUG_PRINT("enter" , ("base: %p buf: %p inx: %d search_flag: %d" , |
| 36 | info, buf, inx, search_flag)); |
| 37 | |
| 38 | if ((inx = _mi_check_index(info,inx)) < 0) |
| 39 | DBUG_RETURN(my_errno); |
| 40 | |
| 41 | info->update&= (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); |
| 42 | info->last_key_func= search_flag; |
| 43 | keyinfo= share->keyinfo + inx; |
| 44 | |
| 45 | if (info->once_flags & USE_PACKED_KEYS) |
| 46 | { |
| 47 | info->once_flags&= ~USE_PACKED_KEYS; /* Reset flag */ |
| 48 | /* |
| 49 | key is already packed!; This happens when we are using a MERGE TABLE |
| 50 | In this key 'key_part_map' is the length of the key ! |
| 51 | */ |
| 52 | key_buff=info->lastkey+info->s->base.max_key_length; |
| 53 | pack_key_length= keypart_map; |
| 54 | bmove(key_buff, key, pack_key_length); |
| 55 | last_used_keyseg= info->s->keyinfo[inx].seg + info->last_used_keyseg; |
| 56 | } |
| 57 | else |
| 58 | { |
| 59 | DBUG_ASSERT(keypart_map); |
| 60 | /* Save the packed key for later use in the second buffer of lastkey. */ |
| 61 | key_buff=info->lastkey+info->s->base.max_key_length; |
| 62 | pack_key_length=_mi_pack_key(info,(uint) inx, key_buff, (uchar*) key, |
| 63 | keypart_map, &last_used_keyseg); |
| 64 | /* Save packed_key_length for use by the MERGE engine. */ |
| 65 | info->pack_key_length= pack_key_length; |
| 66 | info->last_used_keyseg= (uint16) (last_used_keyseg - |
| 67 | info->s->keyinfo[inx].seg); |
| 68 | DBUG_EXECUTE("key" ,_mi_print_key(DBUG_FILE, keyinfo->seg, |
| 69 | key_buff, pack_key_length);); |
| 70 | } |
| 71 | |
| 72 | if (fast_mi_readinfo(info)) |
| 73 | goto err; |
| 74 | |
| 75 | if (share->concurrent_insert) |
| 76 | mysql_rwlock_rdlock(&share->key_root_lock[inx]); |
| 77 | |
| 78 | nextflag=myisam_read_vec[search_flag]; |
| 79 | use_key_length=pack_key_length; |
| 80 | if (!(nextflag & (SEARCH_FIND | SEARCH_NO_FIND | SEARCH_LAST))) |
| 81 | use_key_length=USE_WHOLE_KEY; |
| 82 | |
| 83 | switch (info->s->keyinfo[inx].key_alg) { |
| 84 | #ifdef HAVE_RTREE_KEYS |
| 85 | case HA_KEY_ALG_RTREE: |
| 86 | if (rtree_find_first(info,inx,key_buff,use_key_length,nextflag) < 0) |
| 87 | { |
| 88 | mi_print_error(info->s, HA_ERR_CRASHED); |
| 89 | my_errno=HA_ERR_CRASHED; |
| 90 | if (share->concurrent_insert) |
| 91 | mysql_rwlock_unlock(&share->key_root_lock[inx]); |
| 92 | fast_mi_writeinfo(info); |
| 93 | goto err; |
| 94 | } |
| 95 | break; |
| 96 | #endif |
| 97 | case HA_KEY_ALG_BTREE: |
| 98 | default: |
| 99 | if (!_mi_search(info, keyinfo, key_buff, use_key_length, |
| 100 | myisam_read_vec[search_flag], info->s->state.key_root[inx])) |
| 101 | { |
| 102 | /* |
| 103 | Found a key, but it might not be usable. We cannot use rows that |
| 104 | are inserted by other threads after we got our table lock |
| 105 | ("concurrent inserts"). The record may not even be present yet. |
| 106 | Keys are inserted into the index(es) before the record is |
| 107 | inserted into the data file. When we got our table lock, we |
| 108 | saved the current data_file_length. Concurrent inserts always go |
| 109 | to the end of the file. So we can test if the found key |
| 110 | references a new record. |
| 111 | |
| 112 | If we are searching for a partial key (or using >, >=, < or <=) and |
| 113 | the data is outside of the data file, we need to continue searching |
| 114 | for the first key inside the data file. |
| 115 | |
| 116 | We do also continue searching if an index condition check function |
| 117 | is available. |
| 118 | */ |
| 119 | while ((info->lastpos >= info->state->data_file_length && |
| 120 | (search_flag != HA_READ_KEY_EXACT || |
| 121 | last_used_keyseg != keyinfo->seg + keyinfo->keysegs)) || |
| 122 | (info->index_cond_func && |
| 123 | (res= mi_check_index_cond(info, inx, buf)) == ICP_NO_MATCH)) |
| 124 | { |
| 125 | uint not_used[2]; |
| 126 | /* |
| 127 | Skip rows that are inserted by other threads since we got a lock |
| 128 | Note that this can only happen if we are not searching after an |
| 129 | full length exact key, because the keys are sorted |
| 130 | according to position |
| 131 | */ |
| 132 | if (_mi_search_next(info, keyinfo, info->lastkey, |
| 133 | info->lastkey_length, |
| 134 | myisam_readnext_vec[search_flag], |
| 135 | info->s->state.key_root[inx])) |
| 136 | { |
| 137 | info->lastpos= HA_OFFSET_ERROR; |
| 138 | break; |
| 139 | } |
| 140 | /* |
| 141 | Check that the found key does still match the search. |
| 142 | _mi_search_next() delivers the next key regardless of its |
| 143 | value. |
| 144 | */ |
| 145 | if (search_flag == HA_READ_KEY_EXACT && |
| 146 | ha_key_cmp(keyinfo->seg, key_buff, info->lastkey, use_key_length, |
| 147 | SEARCH_FIND, not_used)) |
| 148 | { |
| 149 | my_errno= HA_ERR_KEY_NOT_FOUND; |
| 150 | info->lastpos= HA_OFFSET_ERROR; |
| 151 | break; |
| 152 | } |
| 153 | /* |
| 154 | If we are at the last key on the key page, allow writers to |
| 155 | access the index. |
| 156 | */ |
| 157 | if (info->int_keypos >= info->int_maxpos && |
| 158 | mi_yield_and_check_if_killed(info, inx)) |
| 159 | { |
| 160 | /* Aborted by user */ |
| 161 | DBUG_ASSERT(info->lastpos == HA_OFFSET_ERROR && |
| 162 | my_errno == HA_ERR_ABORTED_BY_USER); |
| 163 | res= ICP_ERROR; |
| 164 | buf= 0; /* Fast abort */ |
| 165 | break; |
| 166 | } |
| 167 | } |
| 168 | if (res == ICP_OUT_OF_RANGE) |
| 169 | { |
| 170 | /* Change error from HA_ERR_END_OF_FILE */ |
| 171 | DBUG_ASSERT(info->lastpos == HA_OFFSET_ERROR); |
| 172 | my_errno= HA_ERR_KEY_NOT_FOUND; |
| 173 | } |
| 174 | /* |
| 175 | Error if no row found within the data file. (Bug #29838) |
| 176 | Do not overwrite my_errno if already at HA_OFFSET_ERROR. |
| 177 | */ |
| 178 | if (info->lastpos != HA_OFFSET_ERROR && |
| 179 | info->lastpos >= info->state->data_file_length) |
| 180 | { |
| 181 | info->lastpos= HA_OFFSET_ERROR; |
| 182 | my_errno= HA_ERR_KEY_NOT_FOUND; |
| 183 | } |
| 184 | } |
| 185 | else |
| 186 | { |
| 187 | DBUG_ASSERT(info->lastpos == HA_OFFSET_ERROR); |
| 188 | } |
| 189 | } |
| 190 | if (share->concurrent_insert) |
| 191 | mysql_rwlock_unlock(&share->key_root_lock[inx]); |
| 192 | |
| 193 | info->last_rkey_length= pack_key_length; |
| 194 | |
| 195 | if (info->lastpos == HA_OFFSET_ERROR) /* No such record */ |
| 196 | { |
| 197 | fast_mi_writeinfo(info); |
| 198 | if (!buf) |
| 199 | DBUG_RETURN(my_errno); |
| 200 | } |
| 201 | else |
| 202 | { |
| 203 | /* Calculate length of the found key; Used by mi_rnext_same */ |
| 204 | if ((keyinfo->flag & HA_VAR_LENGTH_KEY) && last_used_keyseg) |
| 205 | info->last_rkey_length= _mi_keylength_part(keyinfo, info->lastkey, |
| 206 | last_used_keyseg); |
| 207 | |
| 208 | /* Check if we don't want to have record back, only error message */ |
| 209 | if (!buf) |
| 210 | { |
| 211 | fast_mi_writeinfo(info); |
| 212 | DBUG_RETURN(0); |
| 213 | } |
| 214 | if (!(*info->read_record)(info,info->lastpos,buf)) |
| 215 | { |
| 216 | info->update|= HA_STATE_AKTIV; /* Record is read */ |
| 217 | DBUG_RETURN(0); |
| 218 | } |
| 219 | DBUG_PRINT("error" , ("Didn't find row. Error %d" , my_errno)); |
| 220 | info->lastpos= HA_OFFSET_ERROR; /* Didn't find row */ |
| 221 | } |
| 222 | |
| 223 | /* Store last used key as a base for read next */ |
| 224 | memcpy(info->lastkey,key_buff,pack_key_length); |
| 225 | info->last_rkey_length= pack_key_length; |
| 226 | bzero((char*) info->lastkey+pack_key_length,info->s->base.rec_reflength); |
| 227 | info->lastkey_length=pack_key_length+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 | err: |
| 232 | DBUG_RETURN(my_errno); |
| 233 | } /* _mi_rkey */ |
| 234 | |
| 235 | |
| 236 | /* |
| 237 | Yield to possible other writers during a index scan. |
| 238 | Check also if we got killed by the user and if yes, return |
| 239 | HA_ERR_LOCK_WAIT_TIMEOUT |
| 240 | |
| 241 | return 0 ok |
| 242 | return 1 Query has been requested to be killed |
| 243 | */ |
| 244 | |
| 245 | my_bool mi_yield_and_check_if_killed(MI_INFO *info, int inx) |
| 246 | { |
| 247 | MYISAM_SHARE *share; |
| 248 | if (mi_killed(info)) |
| 249 | { |
| 250 | /* purecov: begin tested */ |
| 251 | info->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 | |
| 259 | if ((share= info->s)->concurrent_insert) |
| 260 | { |
| 261 | /* Give writers a chance to access index */ |
| 262 | mysql_rwlock_unlock(&share->key_root_lock[inx]); |
| 263 | mysql_rwlock_rdlock(&share->key_root_lock[inx]); |
| 264 | } |
| 265 | return 0; |
| 266 | } |
| 267 | |