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
25int 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 */
231err:
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
245my_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