summaryrefslogtreecommitdiffstats
path: root/storage/myisam/mi_range.c
diff options
context:
space:
mode:
Diffstat (limited to 'storage/myisam/mi_range.c')
-rw-r--r--storage/myisam/mi_range.c327
1 files changed, 327 insertions, 0 deletions
diff --git a/storage/myisam/mi_range.c b/storage/myisam/mi_range.c
new file mode 100644
index 00000000..54350f7a
--- /dev/null
+++ b/storage/myisam/mi_range.c
@@ -0,0 +1,327 @@
+/*
+ Copyright (c) 2000, 2011, Oracle and/or its affiliates
+ Copyright (c) 2010, 2020, MariaDB Corporation.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+/*
+ Gives a approximated number of how many records there is between two keys.
+ Used when optimizing querries.
+ */
+
+#include "myisamdef.h"
+#include "rt_index.h"
+
+static double _mi_record_pos(MI_INFO *, const uchar *, key_part_map,
+ enum ha_rkey_function, ulonglong *);
+static double _mi_search_pos(MI_INFO *,MI_KEYDEF *,uchar *, uint,uint,
+ my_off_t,my_bool, ulonglong *);
+static uint _mi_keynr(MI_INFO *info,MI_KEYDEF *,uchar *, uchar *,uint *);
+
+/*
+ Estimate how many records there is in a given range
+
+ SYNOPSIS
+ mi_records_in_range()
+ info MyISAM handler
+ inx Index to use
+ min_key Min key. Is = 0 if no min range
+ max_key Max key. Is = 0 if no max range
+
+ NOTES
+ We should ONLY return 0 if there is no rows in range
+
+ RETURN
+ HA_POS_ERROR error (or we can't estimate number of rows)
+ number Estimated number of rows
+*/
+
+ha_rows mi_records_in_range(MI_INFO *info, int inx,
+ const key_range *min_key, const key_range *max_key,
+ page_range *pages)
+{
+ ha_rows res;
+ double start_pos,end_pos,diff;
+ DBUG_ENTER("mi_records_in_range");
+
+ if ((inx = _mi_check_index(info,inx)) < 0)
+ DBUG_RETURN(HA_POS_ERROR);
+
+ if (fast_mi_readinfo(info))
+ DBUG_RETURN(HA_POS_ERROR);
+ info->update&= (HA_STATE_CHANGED+HA_STATE_ROW_CHANGED);
+ if (info->s->concurrent_insert)
+ mysql_rwlock_rdlock(&info->s->key_root_lock[inx]);
+
+ switch(info->s->keyinfo[inx].key_alg){
+#ifdef HAVE_RTREE_KEYS
+ case HA_KEY_ALG_RTREE:
+ {
+ uchar * key_buff;
+ uint start_key_len;
+
+ /*
+ The problem is that the optimizer doesn't support
+ RTree keys properly at the moment.
+ Hope this will be fixed some day.
+ But now NULL in the min_key means that we
+ didn't make the task for the RTree key
+ and expect BTree functionality from it.
+ As it's not able to handle such request
+ we return the error.
+ */
+ if (!min_key)
+ {
+ res= HA_POS_ERROR;
+ break;
+ }
+ key_buff= info->lastkey+info->s->base.max_key_length;
+ start_key_len= _mi_pack_key(info,inx, key_buff,
+ (uchar*) min_key->key, min_key->keypart_map,
+ (HA_KEYSEG**) 0);
+ res= rtree_estimate(info, inx, key_buff, start_key_len,
+ myisam_read_vec[min_key->flag]);
+ res= res ? res : 1; /* Don't return 0 */
+ break;
+ }
+#endif
+ case HA_KEY_ALG_BTREE:
+ default:
+ start_pos= (min_key ?_mi_record_pos(info, min_key->key,
+ min_key->keypart_map, min_key->flag,
+ &pages->first_page)
+ : (double) 0);
+ end_pos= (max_key ? _mi_record_pos(info, max_key->key,
+ max_key->keypart_map, max_key->flag,
+ &pages->last_page)
+ : (double) info->state->records);
+ res= (end_pos < start_pos ? (ha_rows) 0 :
+ (end_pos == start_pos ? (ha_rows) 1 : (ha_rows) (end_pos-start_pos)));
+ if (start_pos == (double) HA_POS_ERROR || end_pos == (double) HA_POS_ERROR)
+ res=HA_POS_ERROR;
+ else
+ {
+ diff= end_pos - start_pos;
+ if (diff >= 0)
+ {
+ if (!(res= (ha_rows) (diff + 0.5)))
+ res= 1;
+ }
+ else
+ res= 0;
+ }
+ }
+
+ if (info->s->concurrent_insert)
+ mysql_rwlock_unlock(&info->s->key_root_lock[inx]);
+ fast_mi_writeinfo(info);
+
+ DBUG_PRINT("info",("records: %ld",(ulong) (res)));
+ DBUG_RETURN(res);
+}
+
+
+/*
+ To find an approximate relative position of a key tuple among all index
+ key tuples would not be hard if we considered B-trees where all key
+ tuples were contained only in leaf nodes. If we consider a B-tree where
+ key tuples are stored also in non-leaf nodes we have to convert such
+ tree into the tree of the first type. The transformation procedure is
+ simple: the key tuple k goes alter the last key tuple in the most right
+ sub-tree pointer to which is coupled with k. As a result of this
+ transformation each leaf node except the most right one in the tree will
+ contain one extra key tuple following those originally belonging to
+ the leaf.
+*/
+
+
+/* Find relative position (in records) for key in index-tree */
+
+static double _mi_record_pos(MI_INFO *info, const uchar *key,
+ key_part_map keypart_map,
+ enum ha_rkey_function search_flag,
+ ulonglong *final_page)
+{
+ uint inx=(uint) info->lastinx, nextflag, key_len;
+ MI_KEYDEF *keyinfo=info->s->keyinfo+inx;
+ uchar *key_buff;
+ double pos;
+
+ DBUG_ENTER("_mi_record_pos");
+ DBUG_PRINT("enter",("search_flag: %d",search_flag));
+ DBUG_ASSERT(keypart_map);
+
+ key_buff=info->lastkey+info->s->base.max_key_length;
+ key_len=_mi_pack_key(info,inx,key_buff,(uchar*) key, keypart_map,
+ (HA_KEYSEG**) 0);
+ DBUG_EXECUTE("key",_mi_print_key(DBUG_FILE,keyinfo->seg,
+ (uchar*) key_buff,key_len););
+ nextflag=myisam_read_vec[search_flag];
+ if (!(nextflag & (SEARCH_FIND | SEARCH_NO_FIND | SEARCH_LAST)))
+ key_len=USE_WHOLE_KEY;
+
+ /*
+ my_handler.c:ha_compare_text() has a flag 'skip_end_space'.
+ This is set in my_handler.c:ha_key_cmp() in dependence on the
+ compare flags 'nextflag' and the column type.
+
+ TEXT columns are of type HA_KEYTYPE_VARTEXT. In this case the
+ condition is skip_end_space= ((nextflag & (SEARCH_FIND |
+ SEARCH_UPDATE)) == SEARCH_FIND).
+
+ SEARCH_FIND is used for an exact key search. The combination
+ SEARCH_FIND | SEARCH_UPDATE is used in write/update/delete
+ operations with a comment like "Not real duplicates", whatever this
+ means. From the condition above we can see that 'skip_end_space' is
+ always false for these operations. The result is that trailing space
+ counts in key comparison and hence, empty strings ('', string length
+ zero, but not NULL) compare less that strings starting with control
+ characters and these in turn compare less than strings starting with
+ blanks.
+
+ When estimating the number of records in a key range, we request an
+ exact search for the minimum key. This translates into a plain
+ SEARCH_FIND flag. Using this alone would lead to a 'skip_end_space'
+ compare. Empty strings would be expected above control characters.
+ Their keys would not be found because they are located below control
+ characters.
+
+ This is the reason that we add the SEARCH_UPDATE flag here. It makes
+ the key estimation compare in the same way like key write operations
+ do. Only so we will find the keys where they have been inserted.
+
+ Adding the flag unconditionally does not hurt as it is used in the
+ above mentioned condition only. So it can safely be used together
+ with other flags.
+ */
+ pos=_mi_search_pos(info,keyinfo,key_buff,key_len,
+ nextflag | SEARCH_SAVE_BUFF | SEARCH_UPDATE,
+ info->s->state.key_root[inx], TRUE,
+ final_page);
+ if (pos >= 0.0)
+ {
+ DBUG_PRINT("exit",("pos: %g",(pos*info->state->records)));
+ DBUG_RETURN(pos*info->state->records);
+ }
+ DBUG_RETURN((double) (HA_POS_ERROR));
+}
+
+
+ /* This is a modified version of _mi_search */
+ /* Returns offset for key in indextable (decimal 0.0 <= x <= 1.0) */
+
+static double _mi_search_pos(register MI_INFO *info,
+ register MI_KEYDEF *keyinfo,
+ uchar *key, uint key_len, uint nextflag,
+ register my_off_t pos, my_bool last_in_level,
+ ulonglong *final_page)
+{
+ int flag;
+ uint nod_flag,keynr,UNINIT_VAR(max_keynr);
+ my_bool after_key;
+ uchar *keypos,*buff;
+ double offset;
+ DBUG_ENTER("_mi_search_pos");
+
+ if (pos == HA_OFFSET_ERROR)
+ DBUG_RETURN(0.5);
+
+ if (!(buff=_mi_fetch_keypage(info,keyinfo,pos,DFLT_INIT_HITS,info->buff,1)))
+ goto err;
+ *final_page= pos;
+ flag=(*keyinfo->bin_search)(info,keyinfo,buff,key,key_len,nextflag,
+ &keypos,info->lastkey, &after_key);
+ nod_flag=mi_test_if_nod(buff);
+ keynr=_mi_keynr(info,keyinfo,buff,keypos,&max_keynr);
+
+ if (flag)
+ {
+ if (flag == MI_FOUND_WRONG_KEY)
+ DBUG_RETURN(-1); /* error */
+ /*
+ Didn't found match. keypos points at next (bigger) key
+ Try to find a smaller, better matching key.
+ Matches keynr + [0-1]
+ */
+ if (flag > 0 && ! nod_flag)
+ offset= 1.0;
+ else if ((offset=_mi_search_pos(info,keyinfo,key,key_len,nextflag,
+ _mi_kpos(nod_flag,keypos),
+ last_in_level && after_key,
+ final_page)) < 0)
+ DBUG_RETURN(offset);
+ }
+ else
+ {
+ /*
+ Found match. Keypos points at the start of the found key
+ Matches keynr+1
+ */
+ offset=1.0; /* Matches keynr+1 */
+ if ((nextflag & SEARCH_FIND) && nod_flag &&
+ ((keyinfo->flag & (HA_NOSAME | HA_NULL_PART)) != HA_NOSAME ||
+ key_len != USE_WHOLE_KEY))
+ {
+ /*
+ There may be identical keys in the tree. Try to match on of those.
+ Matches keynr + [0-1]
+ */
+ if ((offset=_mi_search_pos(info,keyinfo,key,key_len,SEARCH_FIND,
+ _mi_kpos(nod_flag,keypos),
+ last_in_level && after_key,
+ final_page)) < 0)
+ DBUG_RETURN(offset); /* Read error */
+ }
+ }
+ DBUG_PRINT("info",("keynr: %d offset: %g max_keynr: %d nod: %d flag: %d",
+ keynr,offset,max_keynr,nod_flag,flag));
+ DBUG_RETURN((keynr+offset-MY_TEST(!nod_flag))/
+ (max_keynr+MY_TEST(nod_flag || !last_in_level)));
+err:
+ DBUG_PRINT("exit",("Error: %d",my_errno));
+ DBUG_RETURN (-1.0);
+}
+
+
+ /* Get keynummer of current key and max number of keys in nod */
+
+static uint _mi_keynr(MI_INFO *info, register MI_KEYDEF *keyinfo, uchar *page,
+ uchar *keypos, uint *ret_max_key)
+{
+ uint nod_flag,keynr,max_key;
+ uchar t_buff[HA_MAX_KEY_BUFF],*end;
+
+ end= page+mi_getint(page);
+ nod_flag=mi_test_if_nod(page);
+ page+=2+nod_flag;
+
+ if (!(keyinfo->flag & (HA_VAR_LENGTH_KEY | HA_BINARY_PACK_KEY)))
+ {
+ *ret_max_key= (uint) (end-page)/(keyinfo->keylength+nod_flag);
+ return (uint) (keypos-page)/(keyinfo->keylength+nod_flag);
+ }
+
+ max_key=keynr=0;
+ t_buff[0]=0; /* Safety */
+ while (page < end)
+ {
+ if (!(*keyinfo->get_key)(keyinfo,nod_flag,&page,t_buff))
+ return 0; /* Error */
+ max_key++;
+ if (page == keypos)
+ keynr=max_key;
+ }
+ *ret_max_key=max_key;
+ return(keynr);
+}