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 | |