Commit 5fa8e768 authored by Chaithra Gopalareddy's avatar Chaithra Gopalareddy

Bug#17909656 - WRONG RESULTS FOR A SIMPLE QUERY WITH GROUP BY

Problem:
If there is a predicate on a column referenced by MIN/MAX and
that predicate is not present in all the disjunctions on
keyparts earlier in the compound index, Loose Index Scan will
not return correct result.

Analysis:
When loose index scan is chosen, range optimizer currently
groups all the predicates that contain group parts separately
and minmax parts separately. It therefore applies all the
conditions on the group parts first to the fetched row.
Then in the call to next_max, it processes the conditions
which have min/max keypart.

For ex in the following query:
Select f1, max(f2) from t1 where (f1 = 10 and f2 = 13) or
(f1 = 3) group by f1;
Condition (f2 = 13) would be applied even for rows that
satisfy (f1 = 3) thereby giving wrong results.

Solution:
Do not choose loose_index_scan for such cases. So a new rule
WA2 is introduced to take care of the same.

WA2: "If there are predicates on C, these predicates must
be in conjuction to all predicates on all earlier keyparts
in I."

Todo the same, fix reuses the function get_constant_key_infix().
Since this funciton will fail for all multi-range conditions, it
is re-written to recognize that if the sub-conditions are
equivalent across the disjuncts: it will now succeed.
And to achieve this a new helper function is introduced called
all_same().

The fix also moves the test of NGA3 up to the former only
caller, get_constant_key_infix().
parent 390ab764
......@@ -118,3 +118,171 @@ COUNT(DISTINCT a)
1
DROP TABLE t1;
End of 5.5 tests
#
# Bug#17909656 - WRONG RESULTS FOR A SIMPLE QUERY WITH GROUP BY
#
CREATE TABLE t0 (
i1 INTEGER NOT NULL
);
INSERT INTO t0 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),
(21),(22),(23),(24),(25),(26),(27),(28),(29),(30);
CREATE TABLE t1 (
c1 CHAR(1) NOT NULL,
i1 INTEGER NOT NULL,
i2 INTEGER NOT NULL,
UNIQUE KEY k1 (c1,i2)
) ENGINE=InnoDB;
INSERT INTO t1 SELECT 'A',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'B',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'C',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'D',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'E',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'F',i1,i1 FROM t0;
CREATE TABLE t2 (
c1 CHAR(1) NOT NULL,
i1 INTEGER NOT NULL,
i2 INTEGER NOT NULL,
UNIQUE KEY k2 (c1,i1,i2)
) ENGINE=InnoDB;
INSERT INTO t2 SELECT 'A',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'B',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'C',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'D',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'E',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'F',i1,i1 FROM t0;
ANALYZE TABLE t1;
ANALYZE TABLE t2;
EXPLAIN SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' AND i2 = 17) OR ( c1 = 'F')
GROUP BY c1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range k1 k1 5 NULL 31 Using where; Using index
SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' AND i2 = 17) OR ( c1 = 'F')
GROUP BY c1;
c1 max(i2)
C 17
F 30
EXPLAIN SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' OR ( c1 = 'F' AND i2 = 17))
GROUP BY c1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range k1 k1 5 NULL 31 Using where; Using index
SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' OR ( c1 = 'F' AND i2 = 17))
GROUP BY c1;
c1 max(i2)
C 30
F 17
EXPLAIN SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' OR c1 = 'F' ) AND ( i2 = 17 )
GROUP BY c1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range k1 k1 5 NULL 1 Using where; Using index for group-by
SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' OR c1 = 'F' ) AND ( i2 = 17 )
GROUP BY c1;
c1 max(i2)
C 17
F 17
EXPLAIN SELECT c1, max(i2) FROM t1
WHERE ((c1 = 'C' AND (i2 = 40 OR i2 = 30)) OR ( c1 = 'F' AND (i2 = 40 )))
GROUP BY c1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range k1 k1 5 NULL 3 Using where; Using index
SELECT c1, max(i2) FROM t1
WHERE ((c1 = 'C' AND (i2 = 40 OR i2 = 30)) OR ( c1 = 'F' AND (i2 = 40 )))
GROUP BY c1;
c1 max(i2)
C 30
EXPLAIN SELECT c1, i1, max(i2) FROM t2
WHERE (c1 = 'C' OR ( c1 = 'F' AND i1 < 35)) AND ( i2 = 17 )
GROUP BY c1,i1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t2 range k2 k2 9 NULL 59 Using where; Using index for group-by
SELECT c1, i1, max(i2) FROM t2
WHERE (c1 = 'C' OR ( c1 = 'F' AND i1 < 35)) AND ( i2 = 17 )
GROUP BY c1,i1;
c1 i1 max(i2)
C 17 17
F 17 17
EXPLAIN SELECT c1, i1, max(i2) FROM t2
WHERE (((c1 = 'C' AND i1 < 40) OR ( c1 = 'F' AND i1 < 35)) AND ( i2 = 17 ))
GROUP BY c1,i1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t2 range k2 k2 9 NULL 58 Using where; Using index for group-by
SELECT c1, i1, max(i2) FROM t2
WHERE (((c1 = 'C' AND i1 < 40) OR ( c1 = 'F' AND i1 < 35)) AND ( i2 = 17 ))
GROUP BY c1,i1;
c1 i1 max(i2)
C 17 17
F 17 17
EXPLAIN SELECT c1, i1, max(i2) FROM t2
WHERE ((c1 = 'C' AND i1 < 40) OR ( c1 = 'F' AND i1 < 35) OR ( i2 = 17 ))
GROUP BY c1,i1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t2 range k2 k2 5 NULL 181 Using where; Using index for group-by
SELECT c1, i1, max(i2) FROM t2
WHERE ((c1 = 'C' AND i1 < 40) OR ( c1 = 'F' AND i1 < 35) OR ( i2 = 17 ))
GROUP BY c1,i1;
c1 i1 max(i2)
A 17 17
B 17 17
C 1 1
C 2 2
C 3 3
C 4 4
C 5 5
C 6 6
C 7 7
C 8 8
C 9 9
C 10 10
C 11 11
C 12 12
C 13 13
C 14 14
C 15 15
C 16 16
C 17 17
C 18 18
C 19 19
C 20 20
C 21 21
C 22 22
C 23 23
C 24 24
C 25 25
C 26 26
C 27 27
C 28 28
C 29 29
C 30 30
D 17 17
E 17 17
F 1 1
F 2 2
F 3 3
F 4 4
F 5 5
F 6 6
F 7 7
F 8 8
F 9 9
F 10 10
F 11 11
F 12 12
F 13 13
F 14 14
F 15 15
F 16 16
F 17 17
F 18 18
F 19 19
F 20 20
F 21 21
F 22 22
F 23 23
F 24 24
F 25 25
F 26 26
F 27 27
F 28 28
F 29 29
F 30 30
DROP TABLE t0,t1,t2;
......@@ -137,3 +137,96 @@ SELECT COUNT(DISTINCT a) FROM t1 WHERE b = 'b';
DROP TABLE t1;
--echo End of 5.5 tests
--echo #
--echo # Bug#17909656 - WRONG RESULTS FOR A SIMPLE QUERY WITH GROUP BY
--echo #
CREATE TABLE t0 (
i1 INTEGER NOT NULL
);
INSERT INTO t0 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),
(21),(22),(23),(24),(25),(26),(27),(28),(29),(30);
CREATE TABLE t1 (
c1 CHAR(1) NOT NULL,
i1 INTEGER NOT NULL,
i2 INTEGER NOT NULL,
UNIQUE KEY k1 (c1,i2)
) ENGINE=InnoDB;
INSERT INTO t1 SELECT 'A',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'B',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'C',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'D',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'E',i1,i1 FROM t0;
INSERT INTO t1 SELECT 'F',i1,i1 FROM t0;
CREATE TABLE t2 (
c1 CHAR(1) NOT NULL,
i1 INTEGER NOT NULL,
i2 INTEGER NOT NULL,
UNIQUE KEY k2 (c1,i1,i2)
) ENGINE=InnoDB;
INSERT INTO t2 SELECT 'A',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'B',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'C',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'D',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'E',i1,i1 FROM t0;
INSERT INTO t2 SELECT 'F',i1,i1 FROM t0;
-- disable_result_log
ANALYZE TABLE t1;
ANALYZE TABLE t2;
-- enable_result_log
let query=
SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' AND i2 = 17) OR ( c1 = 'F')
GROUP BY c1;
eval EXPLAIN $query;
eval $query;
let query=
SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' OR ( c1 = 'F' AND i2 = 17))
GROUP BY c1;
eval EXPLAIN $query;
eval $query;
let query=
SELECT c1, max(i2) FROM t1 WHERE (c1 = 'C' OR c1 = 'F' ) AND ( i2 = 17 )
GROUP BY c1;
eval EXPLAIN $query;
eval $query;
let query=
SELECT c1, max(i2) FROM t1
WHERE ((c1 = 'C' AND (i2 = 40 OR i2 = 30)) OR ( c1 = 'F' AND (i2 = 40 )))
GROUP BY c1;
eval EXPLAIN $query;
eval $query;
let query=
SELECT c1, i1, max(i2) FROM t2
WHERE (c1 = 'C' OR ( c1 = 'F' AND i1 < 35)) AND ( i2 = 17 )
GROUP BY c1,i1;
eval EXPLAIN $query;
eval $query;
let query=
SELECT c1, i1, max(i2) FROM t2
WHERE (((c1 = 'C' AND i1 < 40) OR ( c1 = 'F' AND i1 < 35)) AND ( i2 = 17 ))
GROUP BY c1,i1;
eval EXPLAIN $query;
eval $query;
let query=
SELECT c1, i1, max(i2) FROM t2
WHERE ((c1 = 'C' AND i1 < 40) OR ( c1 = 'F' AND i1 < 35) OR ( i2 = 17 ))
GROUP BY c1,i1;
eval EXPLAIN $query;
eval $query;
DROP TABLE t0,t1,t2;
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
/* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights
* reserved.
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
......@@ -304,31 +305,54 @@ public:
:min_flag(0),elements(1),use_count(1),left(0),right(0),next_key_part(0),
color(BLACK), type(type_arg)
{}
inline bool is_same(SEL_ARG *arg)
/**
returns true if a range predicate is equal. Use all_same()
to check for equality of all the predicates on this keypart.
*/
inline bool is_same(const SEL_ARG *arg) const
{
if (type != arg->type || part != arg->part)
return 0;
return false;
if (type != KEY_RANGE)
return 1;
return true;
return cmp_min_to_min(arg) == 0 && cmp_max_to_max(arg) == 0;
}
/**
returns true if all the predicates in the keypart tree are equal
*/
bool all_same(const SEL_ARG *arg) const
{
if (type != arg->type || part != arg->part)
return false;
if (type != KEY_RANGE)
return true;
if (arg == this)
return true;
const SEL_ARG *cmp_arg= arg->first();
const SEL_ARG *cur_arg= first();
for (; cur_arg && cmp_arg && cur_arg->is_same(cmp_arg);
cur_arg= cur_arg->next, cmp_arg= cmp_arg->next);
if (cur_arg || cmp_arg)
return false;
return true;
}
inline void merge_flags(SEL_ARG *arg) { maybe_flag|=arg->maybe_flag; }
inline void maybe_smaller() { maybe_flag=1; }
/* Return true iff it's a single-point null interval */
inline bool is_null_interval() { return maybe_null && max_value[0] == 1; }
inline int cmp_min_to_min(SEL_ARG* arg)
inline int cmp_min_to_min(const SEL_ARG* arg) const
{
return sel_cmp(field,min_value, arg->min_value, min_flag, arg->min_flag);
}
inline int cmp_min_to_max(SEL_ARG* arg)
inline int cmp_min_to_max(const SEL_ARG* arg) const
{
return sel_cmp(field,min_value, arg->max_value, min_flag, arg->max_flag);
}
inline int cmp_max_to_max(SEL_ARG* arg)
inline int cmp_max_to_max(const SEL_ARG* arg) const
{
return sel_cmp(field,max_value, arg->max_value, max_flag, arg->max_flag);
}
inline int cmp_max_to_min(SEL_ARG* arg)
inline int cmp_max_to_min(const SEL_ARG* arg) const
{
return sel_cmp(field,max_value, arg->min_value, max_flag, arg->min_flag);
}
......@@ -507,6 +531,7 @@ public:
void test_use_count(SEL_ARG *root);
#endif
SEL_ARG *first();
const SEL_ARG *first() const;
SEL_ARG *last();
void make_root();
inline bool simple_key()
......@@ -583,6 +608,18 @@ public:
SEL_ARG *clone_tree(RANGE_OPT_PARAM *param);
};
/**
Helper function to compare two SEL_ARG's.
*/
static bool all_same(const SEL_ARG *sa1, const SEL_ARG *sa2)
{
if (sa1 == NULL && sa2 == NULL)
return true;
if ((sa1 != NULL && sa2 == NULL) || (sa1 == NULL && sa2 != NULL))
return false;
return sa1->all_same(sa2);
}
class SEL_IMERGE;
......@@ -1770,6 +1807,13 @@ SEL_ARG *SEL_ARG::clone(RANGE_OPT_PARAM *param, SEL_ARG *new_parent,
return tmp;
}
/**
This gives the first SEL_ARG in the interval list, and the minimal element
in the red-black tree
@return
SEL_ARG first SEL_ARG in the interval list
*/
SEL_ARG *SEL_ARG::first()
{
SEL_ARG *next_arg=this;
......@@ -1780,6 +1824,11 @@ SEL_ARG *SEL_ARG::first()
return next_arg;
}
const SEL_ARG *SEL_ARG::first() const
{
return const_cast<SEL_ARG*>(this)->first();
}
SEL_ARG *SEL_ARG::last()
{
SEL_ARG *next_arg=this;
......@@ -9356,6 +9405,8 @@ void QUICK_ROR_UNION_SELECT::add_keys_and_lengths(String *key_names,
static inline uint get_field_keypart(KEY *index, Field *field);
static inline SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree,
PARAM *param, uint *param_idx);
static bool get_sel_arg_for_keypart(Field *field, SEL_ARG *index_range_tree,
SEL_ARG **cur_range);
static bool get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree,
KEY_PART_INFO *first_non_group_part,
KEY_PART_INFO *min_max_arg_part,
......@@ -9451,6 +9502,8 @@ cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts,
above tests. By transitivity then it also follows that each WA_i
participates in the index I (if this was already tested for GA, NGA
and C).
WA2. If there is a predicate on C, then it must be in conjunction
to all predicates on all earlier keyparts in I.
C) Overall query form:
SELECT EXPR([A_1,...,A_k], [B_1,...,B_m], [MIN(C)], [MAX(C)])
......@@ -9856,6 +9909,25 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
}
}
/**
Test WA2:If there are conditions on a column C participating in
MIN/MAX, those conditions must be conjunctions to all earlier
keyparts. Otherwise, Loose Index Scan cannot be used.
*/
if (tree && min_max_arg_item)
{
uint dummy;
SEL_ARG *index_range_tree= get_index_range_tree(cur_index, tree, param,
&dummy);
SEL_ARG *cur_range= NULL;
if (get_sel_arg_for_keypart(min_max_arg_part->field,
index_range_tree, &cur_range) ||
(cur_range && cur_range->type != SEL_ARG::KEY_RANGE))
{
goto next_index;
}
}
/* If we got to this point, cur_index_info passes the test. */
key_infix_parts= cur_key_infix_len ? (uint)
(first_non_infix_part - first_non_group_part) : 0;
......@@ -10099,73 +10171,75 @@ check_group_min_max_predicates(COND *cond, Item_field *min_max_arg_item,
/*
Get SEL_ARG tree, if any, for the keypart covering non grouping
attribute (NGA) field 'nga_field'.
Get the SEL_ARG tree 'tree' for the keypart covering 'field', if
any. 'tree' must be a unique conjunction to ALL predicates in earlier
keyparts of 'keypart_tree'.
E.g., if 'keypart_tree' is for a composite index (kp1,kp2) and kp2
covers 'field', all these conditions satisfies the requirement:
This function enforces the NGA3 test: If 'keypart_tree' contains a
condition for 'nga_field', there can only be one range. In the
opposite case, this function returns with error and 'cur_range'
should not be used.
1. "(kp1=2 OR kp1=3) AND kp2=10" => returns "kp2=10"
2. "(kp1=2 AND kp2=10) OR (kp1=3 AND kp2=10)" => returns "kp2=10"
3. "(kp1=2 AND (kp2=10 OR kp2=11)) OR (kp1=3 AND (kp2=10 OR kp2=11))"
=> returns "kp2=10 OR kp2=11"
Note that the NGA1 and NGA2 requirements, like whether or not the
range predicate for 'nga_field' is equality, is not tested by this
function.
whereas these do not
1. "(kp1=2 AND kp2=10) OR kp1=3"
2. "(kp1=2 AND kp2=10) OR (kp1=3 AND kp2=11)"
3. "(kp1=2 AND kp2=10) OR (kp1=3 AND (kp2=10 OR kp2=11))"
@param[in] nga_field The NGA field we want the SEL_ARG tree for
This function effectively tests requirement WA2. In combination with
a test that the returned tree has no more than one range it is also
a test of NGA3.
@param[in] field The field we want the SEL_ARG tree for
@param[in] keypart_tree Root node of the SEL_ARG* tree for the index
@param[out] cur_range The SEL_ARG tree, if any, for the keypart
covering field 'keypart_field'
@retval true 'keypart_tree' contained a predicate for 'nga_field' but
multiple ranges exists. 'cur_range' should not be used.
@retval true 'keypart_tree' contained a predicate for 'field' that
is not conjunction to all predicates on earlier keyparts
@retval false otherwise
*/
static bool
get_sel_arg_for_keypart(Field *nga_field,
get_sel_arg_for_keypart(Field *field,
SEL_ARG *keypart_tree,
SEL_ARG **cur_range)
{
if(keypart_tree == NULL)
if (keypart_tree == NULL)
return false;
if(keypart_tree->field->eq(nga_field))
if (keypart_tree->field->eq(field))
{
/*
Enforce NGA3: If a condition for nga_field has been found, only
a single range is allowed.
*/
if (keypart_tree->prev || keypart_tree->next)
return true; // There are multiple ranges
*cur_range= keypart_tree;
return false;
}
SEL_ARG *found_tree= NULL;
SEL_ARG *tree_first_range= NULL;
SEL_ARG *first_kp= keypart_tree->first();
for (SEL_ARG *cur_kp= first_kp; cur_kp && !found_tree;
cur_kp= cur_kp->next)
for (SEL_ARG *cur_kp= first_kp; cur_kp; cur_kp= cur_kp->next)
{
SEL_ARG *curr_tree= NULL;
if (cur_kp->next_key_part)
{
if (get_sel_arg_for_keypart(nga_field,
if (get_sel_arg_for_keypart(field,
cur_kp->next_key_part,
&found_tree))
&curr_tree))
return true;
}
/*
Enforce NGA3: If a condition for nga_field has been found,only
a single range is allowed.
Check if the SEL_ARG tree for 'field' is identical for all ranges in
'keypart_tree
*/
if (found_tree && first_kp->next)
return true; // There are multiple ranges
if (cur_kp == first_kp)
tree_first_range= curr_tree;
else if (!all_same(tree_first_range, curr_tree))
return true;
}
*cur_range= found_tree;
*cur_range= tree_first_range;
return false;
}
/*
Extract a sequence of constants from a conjunction of equality predicates.
......@@ -10188,7 +10262,8 @@ get_sel_arg_for_keypart(Field *nga_field,
(const_ci = NG_i).. In addition, there can only be one range when there is
such a gap.
Thus all the NGF_i attributes must fill the 'gap' between the last group-by
attribute and the MIN/MAX attribute in the index (if present). If these
attribute and the MIN/MAX attribute in the index (if present). Also ensure
that there is only a single range on NGF_i (NGA3). If these
conditions hold, copy each constant from its corresponding predicate into
key_infix, in the order its NG_i attribute appears in the index, and update
key_infix_len with the total length of the key parts in key_infix.
......@@ -10197,7 +10272,6 @@ get_sel_arg_for_keypart(Field *nga_field,
TRUE if the index passes the test
FALSE o/w
*/
static bool
get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree,
KEY_PART_INFO *first_non_group_part,
......@@ -10217,27 +10291,37 @@ get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree,
{
cur_range= NULL;
/*
Find the range tree for the current keypart. We assume that
index_range_tree points to the first keypart in the index.
Check NGA3:
1. get_sel_arg_for_keypart gets the range tree for the 'field' and also
checks for a unique conjunction of this tree with all the predicates
on the earlier keyparts in the index.
2. Check for multiple ranges on the found keypart tree.
We assume that index_range_tree points to the leftmost keypart in
the index.
*/
if(get_sel_arg_for_keypart(cur_part->field, index_range_tree, &cur_range))
if (get_sel_arg_for_keypart(cur_part->field, index_range_tree,
&cur_range))
return false;
if (cur_range && cur_range->elements > 1)
return false;
if (!cur_range)
{
if (min_max_arg_part)
return FALSE; /* The current keypart has no range predicates at all. */
return false; /* The current keypart has no range predicates at all. */
else
{
*first_non_infix_part= cur_part;
return TRUE;
return true;
}
}
if ((cur_range->min_flag & NO_MIN_RANGE) ||
(cur_range->max_flag & NO_MAX_RANGE) ||
(cur_range->min_flag & NEAR_MIN) || (cur_range->max_flag & NEAR_MAX))
return FALSE;
return false;
uint field_length= cur_part->store_length;
if (cur_range->maybe_null &&
......@@ -10261,7 +10345,7 @@ get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree,
*key_infix_len+= field_length;
}
else
return FALSE;
return false;
}
if (!min_max_arg_part && (cur_part == last_part))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment