| 1 | /* |
| 2 | Copyright (c) 2000, 2011, Oracle and/or its affiliates |
| 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 | /* |
| 18 | Gives a approximated number of how many records there is between two keys. |
| 19 | Used when optimizing querries. |
| 20 | */ |
| 21 | |
| 22 | #include "myisamdef.h" |
| 23 | #include "rt_index.h" |
| 24 | |
| 25 | static ha_rows _mi_record_pos(MI_INFO *, const uchar *, key_part_map, |
| 26 | enum ha_rkey_function); |
| 27 | static double _mi_search_pos(MI_INFO *,MI_KEYDEF *,uchar *, uint,uint,my_off_t); |
| 28 | static uint _mi_keynr(MI_INFO *info,MI_KEYDEF *,uchar *, uchar *,uint *); |
| 29 | |
| 30 | /* |
| 31 | Estimate how many records there is in a given range |
| 32 | |
| 33 | SYNOPSIS |
| 34 | mi_records_in_range() |
| 35 | info MyISAM handler |
| 36 | inx Index to use |
| 37 | min_key Min key. Is = 0 if no min range |
| 38 | max_key Max key. Is = 0 if no max range |
| 39 | |
| 40 | NOTES |
| 41 | We should ONLY return 0 if there is no rows in range |
| 42 | |
| 43 | RETURN |
| 44 | HA_POS_ERROR error (or we can't estimate number of rows) |
| 45 | number Estimated number of rows |
| 46 | */ |
| 47 | |
| 48 | ha_rows mi_records_in_range(MI_INFO *info, int inx, |
| 49 | key_range *min_key, key_range *max_key) |
| 50 | { |
| 51 | ha_rows start_pos,end_pos,res; |
| 52 | DBUG_ENTER("mi_records_in_range" ); |
| 53 | |
| 54 | if ((inx = _mi_check_index(info,inx)) < 0) |
| 55 | DBUG_RETURN(HA_POS_ERROR); |
| 56 | |
| 57 | if (fast_mi_readinfo(info)) |
| 58 | DBUG_RETURN(HA_POS_ERROR); |
| 59 | info->update&= (HA_STATE_CHANGED+HA_STATE_ROW_CHANGED); |
| 60 | if (info->s->concurrent_insert) |
| 61 | mysql_rwlock_rdlock(&info->s->key_root_lock[inx]); |
| 62 | |
| 63 | switch(info->s->keyinfo[inx].key_alg){ |
| 64 | #ifdef HAVE_RTREE_KEYS |
| 65 | case HA_KEY_ALG_RTREE: |
| 66 | { |
| 67 | uchar * key_buff; |
| 68 | uint start_key_len; |
| 69 | |
| 70 | /* |
| 71 | The problem is that the optimizer doesn't support |
| 72 | RTree keys properly at the moment. |
| 73 | Hope this will be fixed some day. |
| 74 | But now NULL in the min_key means that we |
| 75 | didn't make the task for the RTree key |
| 76 | and expect BTree functionality from it. |
| 77 | As it's not able to handle such request |
| 78 | we return the error. |
| 79 | */ |
| 80 | if (!min_key) |
| 81 | { |
| 82 | res= HA_POS_ERROR; |
| 83 | break; |
| 84 | } |
| 85 | key_buff= info->lastkey+info->s->base.max_key_length; |
| 86 | start_key_len= _mi_pack_key(info,inx, key_buff, |
| 87 | (uchar*) min_key->key, min_key->keypart_map, |
| 88 | (HA_KEYSEG**) 0); |
| 89 | res= rtree_estimate(info, inx, key_buff, start_key_len, |
| 90 | myisam_read_vec[min_key->flag]); |
| 91 | res= res ? res : 1; /* Don't return 0 */ |
| 92 | break; |
| 93 | } |
| 94 | #endif |
| 95 | case HA_KEY_ALG_BTREE: |
| 96 | default: |
| 97 | start_pos= (min_key ? _mi_record_pos(info, min_key->key, |
| 98 | min_key->keypart_map, min_key->flag) |
| 99 | : (ha_rows) 0); |
| 100 | end_pos= (max_key ? _mi_record_pos(info, max_key->key, |
| 101 | max_key->keypart_map, max_key->flag) |
| 102 | : info->state->records + (ha_rows) 1); |
| 103 | res= (end_pos < start_pos ? (ha_rows) 0 : |
| 104 | (end_pos == start_pos ? (ha_rows) 1 : end_pos-start_pos)); |
| 105 | if (start_pos == HA_POS_ERROR || end_pos == HA_POS_ERROR) |
| 106 | res=HA_POS_ERROR; |
| 107 | } |
| 108 | |
| 109 | if (info->s->concurrent_insert) |
| 110 | mysql_rwlock_unlock(&info->s->key_root_lock[inx]); |
| 111 | fast_mi_writeinfo(info); |
| 112 | |
| 113 | DBUG_PRINT("info" ,("records: %ld" ,(ulong) (res))); |
| 114 | DBUG_RETURN(res); |
| 115 | } |
| 116 | |
| 117 | |
| 118 | /* Find relative position (in records) for key in index-tree */ |
| 119 | |
| 120 | static ha_rows _mi_record_pos(MI_INFO *info, const uchar *key, |
| 121 | key_part_map keypart_map, |
| 122 | enum ha_rkey_function search_flag) |
| 123 | { |
| 124 | uint inx=(uint) info->lastinx, nextflag, key_len; |
| 125 | MI_KEYDEF *keyinfo=info->s->keyinfo+inx; |
| 126 | uchar *key_buff; |
| 127 | double pos; |
| 128 | |
| 129 | DBUG_ENTER("_mi_record_pos" ); |
| 130 | DBUG_PRINT("enter" ,("search_flag: %d" ,search_flag)); |
| 131 | DBUG_ASSERT(keypart_map); |
| 132 | |
| 133 | key_buff=info->lastkey+info->s->base.max_key_length; |
| 134 | key_len=_mi_pack_key(info,inx,key_buff,(uchar*) key, keypart_map, |
| 135 | (HA_KEYSEG**) 0); |
| 136 | DBUG_EXECUTE("key" ,_mi_print_key(DBUG_FILE,keyinfo->seg, |
| 137 | (uchar*) key_buff,key_len);); |
| 138 | nextflag=myisam_read_vec[search_flag]; |
| 139 | if (!(nextflag & (SEARCH_FIND | SEARCH_NO_FIND | SEARCH_LAST))) |
| 140 | key_len=USE_WHOLE_KEY; |
| 141 | |
| 142 | /* |
| 143 | my_handler.c:ha_compare_text() has a flag 'skip_end_space'. |
| 144 | This is set in my_handler.c:ha_key_cmp() in dependence on the |
| 145 | compare flags 'nextflag' and the column type. |
| 146 | |
| 147 | TEXT columns are of type HA_KEYTYPE_VARTEXT. In this case the |
| 148 | condition is skip_end_space= ((nextflag & (SEARCH_FIND | |
| 149 | SEARCH_UPDATE)) == SEARCH_FIND). |
| 150 | |
| 151 | SEARCH_FIND is used for an exact key search. The combination |
| 152 | SEARCH_FIND | SEARCH_UPDATE is used in write/update/delete |
| 153 | operations with a comment like "Not real duplicates", whatever this |
| 154 | means. From the condition above we can see that 'skip_end_space' is |
| 155 | always false for these operations. The result is that trailing space |
| 156 | counts in key comparison and hence, empty strings ('', string length |
| 157 | zero, but not NULL) compare less that strings starting with control |
| 158 | characters and these in turn compare less than strings starting with |
| 159 | blanks. |
| 160 | |
| 161 | When estimating the number of records in a key range, we request an |
| 162 | exact search for the minimum key. This translates into a plain |
| 163 | SEARCH_FIND flag. Using this alone would lead to a 'skip_end_space' |
| 164 | compare. Empty strings would be expected above control characters. |
| 165 | Their keys would not be found because they are located below control |
| 166 | characters. |
| 167 | |
| 168 | This is the reason that we add the SEARCH_UPDATE flag here. It makes |
| 169 | the key estimation compare in the same way like key write operations |
| 170 | do. Only so we will find the keys where they have been inserted. |
| 171 | |
| 172 | Adding the flag unconditionally does not hurt as it is used in the |
| 173 | above mentioned condition only. So it can safely be used together |
| 174 | with other flags. |
| 175 | */ |
| 176 | pos=_mi_search_pos(info,keyinfo,key_buff,key_len, |
| 177 | nextflag | SEARCH_SAVE_BUFF | SEARCH_UPDATE, |
| 178 | info->s->state.key_root[inx]); |
| 179 | if (pos >= 0.0) |
| 180 | { |
| 181 | DBUG_PRINT("exit" ,("pos: %ld" ,(ulong) (pos*info->state->records))); |
| 182 | DBUG_RETURN((ulong) (pos*info->state->records+0.5)); |
| 183 | } |
| 184 | DBUG_RETURN(HA_POS_ERROR); |
| 185 | } |
| 186 | |
| 187 | |
| 188 | /* This is a modified version of _mi_search */ |
| 189 | /* Returns offset for key in indextable (decimal 0.0 <= x <= 1.0) */ |
| 190 | |
| 191 | static double _mi_search_pos(register MI_INFO *info, |
| 192 | register MI_KEYDEF *keyinfo, |
| 193 | uchar *key, uint key_len, uint nextflag, |
| 194 | register my_off_t pos) |
| 195 | { |
| 196 | int flag; |
| 197 | uint nod_flag,keynr,UNINIT_VAR(max_keynr); |
| 198 | my_bool after_key; |
| 199 | uchar *keypos,*buff; |
| 200 | double offset; |
| 201 | DBUG_ENTER("_mi_search_pos" ); |
| 202 | |
| 203 | if (pos == HA_OFFSET_ERROR) |
| 204 | DBUG_RETURN(0.5); |
| 205 | |
| 206 | if (!(buff=_mi_fetch_keypage(info,keyinfo,pos,DFLT_INIT_HITS,info->buff,1))) |
| 207 | goto err; |
| 208 | flag=(*keyinfo->bin_search)(info,keyinfo,buff,key,key_len,nextflag, |
| 209 | &keypos,info->lastkey, &after_key); |
| 210 | nod_flag=mi_test_if_nod(buff); |
| 211 | keynr=_mi_keynr(info,keyinfo,buff,keypos,&max_keynr); |
| 212 | |
| 213 | if (flag) |
| 214 | { |
| 215 | if (flag == MI_FOUND_WRONG_KEY) |
| 216 | DBUG_RETURN(-1); /* error */ |
| 217 | /* |
| 218 | Didn't found match. keypos points at next (bigger) key |
| 219 | Try to find a smaller, better matching key. |
| 220 | Matches keynr + [0-1] |
| 221 | */ |
| 222 | if (flag > 0 && ! nod_flag) |
| 223 | offset= 1.0; |
| 224 | else if ((offset=_mi_search_pos(info,keyinfo,key,key_len,nextflag, |
| 225 | _mi_kpos(nod_flag,keypos))) < 0) |
| 226 | DBUG_RETURN(offset); |
| 227 | } |
| 228 | else |
| 229 | { |
| 230 | /* |
| 231 | Found match. Keypos points at the start of the found key |
| 232 | Matches keynr+1 |
| 233 | */ |
| 234 | offset=1.0; /* Matches keynr+1 */ |
| 235 | if ((nextflag & SEARCH_FIND) && nod_flag && |
| 236 | ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART)) != HA_NOSAME || |
| 237 | key_len != USE_WHOLE_KEY)) |
| 238 | { |
| 239 | /* |
| 240 | There may be identical keys in the tree. Try to match on of those. |
| 241 | Matches keynr + [0-1] |
| 242 | */ |
| 243 | if ((offset=_mi_search_pos(info,keyinfo,key,key_len,SEARCH_FIND, |
| 244 | _mi_kpos(nod_flag,keypos))) < 0) |
| 245 | DBUG_RETURN(offset); /* Read error */ |
| 246 | } |
| 247 | } |
| 248 | DBUG_PRINT("info" ,("keynr: %d offset: %g max_keynr: %d nod: %d flag: %d" , |
| 249 | keynr,offset,max_keynr,nod_flag,flag)); |
| 250 | DBUG_RETURN((keynr+offset)/(max_keynr+1)); |
| 251 | err: |
| 252 | DBUG_PRINT("exit" ,("Error: %d" ,my_errno)); |
| 253 | DBUG_RETURN (-1.0); |
| 254 | } |
| 255 | |
| 256 | |
| 257 | /* Get keynummer of current key and max number of keys in nod */ |
| 258 | |
| 259 | static uint _mi_keynr(MI_INFO *info, register MI_KEYDEF *keyinfo, uchar *page, |
| 260 | uchar *keypos, uint *ret_max_key) |
| 261 | { |
| 262 | uint nod_flag,keynr,max_key; |
| 263 | uchar t_buff[HA_MAX_KEY_BUFF],*end; |
| 264 | |
| 265 | end= page+mi_getint(page); |
| 266 | nod_flag=mi_test_if_nod(page); |
| 267 | page+=2+nod_flag; |
| 268 | |
| 269 | if (!(keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY))) |
| 270 | { |
| 271 | *ret_max_key= (uint) (end-page)/(keyinfo->keylength+nod_flag); |
| 272 | return (uint) (keypos-page)/(keyinfo->keylength+nod_flag); |
| 273 | } |
| 274 | |
| 275 | max_key=keynr=0; |
| 276 | t_buff[0]=0; /* Safety */ |
| 277 | while (page < end) |
| 278 | { |
| 279 | if (!(*keyinfo->get_key)(keyinfo,nod_flag,&page,t_buff)) |
| 280 | return 0; /* Error */ |
| 281 | max_key++; |
| 282 | if (page == keypos) |
| 283 | keynr=max_key; |
| 284 | } |
| 285 | *ret_max_key=max_key; |
| 286 | return(keynr); |
| 287 | } |
| 288 | |