diff options
author | Alexander Barkov <bar@mariadb.org> | 2015-08-12 17:28:45 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mariadb.org> | 2015-08-12 17:28:45 +0400 |
commit | 9d884fd3d3fadd5ad31ecfee915877b98258e546 (patch) | |
tree | 717a99115c7d9516dd65c38ef8804edf9cad107c | |
parent | 6e091dc7ff06f9ff0bbe834f6be338f5b695ed81 (diff) | |
download | mariadb-git-9d884fd3d3fadd5ad31ecfee915877b98258e546.tar.gz |
MDEV-8599 "WHERE varchar_field LIKE temporal_const" does not use range optimizer
-rw-r--r-- | mysql-test/r/func_like.result | 25 | ||||
-rw-r--r-- | mysql-test/t/func_like.test | 19 | ||||
-rw-r--r-- | sql/item_cmpfunc.h | 14 | ||||
-rw-r--r-- | sql/opt_range.cc | 351 | ||||
-rw-r--r-- | sql/opt_range.h | 4 |
5 files changed, 243 insertions, 170 deletions
diff --git a/mysql-test/r/func_like.result b/mysql-test/r/func_like.result index 2fdcf1226b7..9c8e9727d16 100644 --- a/mysql-test/r/func_like.result +++ b/mysql-test/r/func_like.result @@ -227,5 +227,30 @@ Warnings: Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where ((`test`.`t1`.`a` = 10.0) and (`test`.`t1`.`a` like 10.00)) DROP TABLE t1; # +# MDEV-8599 "WHERE varchar_field LIKE temporal_const" does not use range optimizer +# +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1, KEY(a)) ENGINE=MyISAM; +INSERT INTO t1 VALUES ('00:00:00'); +INSERT INTO t1 VALUES ('00:00:01'); +INSERT INTO t1 VALUES ('00:00:02'); +INSERT INTO t1 VALUES ('00:00:03'); +INSERT INTO t1 VALUES ('00:00:04'); +INSERT INTO t1 VALUES ('00:00:05'); +INSERT INTO t1 VALUES ('00:00:06'); +INSERT INTO t1 VALUES ('00:00:07'); +EXPLAIN SELECT * FROM t1 WHERE a LIKE '00:00:00'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 13 NULL 1 Using where; Using index +EXPLAIN SELECT * FROM t1 WHERE a LIKE TIME'00:00:00'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 13 NULL 1 Using where; Using index +SELECT * FROM t1 WHERE a LIKE '00:00:00'; +a +00:00:00 +SELECT * FROM t1 WHERE a LIKE TIME'00:00:00'; +a +00:00:00 +DROP TABLE t1; +# # End of 10.1 tests # diff --git a/mysql-test/t/func_like.test b/mysql-test/t/func_like.test index aab3c7fe43b..d1b9b170a3b 100644 --- a/mysql-test/t/func_like.test +++ b/mysql-test/t/func_like.test @@ -164,5 +164,24 @@ EXPLAIN EXTENDED SELECT * FROM t1 WHERE a=10.0 AND a LIKE 10.00; DROP TABLE t1; --echo # +--echo # MDEV-8599 "WHERE varchar_field LIKE temporal_const" does not use range optimizer +--echo # +CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1, KEY(a)) ENGINE=MyISAM; +INSERT INTO t1 VALUES ('00:00:00'); +INSERT INTO t1 VALUES ('00:00:01'); +INSERT INTO t1 VALUES ('00:00:02'); +INSERT INTO t1 VALUES ('00:00:03'); +INSERT INTO t1 VALUES ('00:00:04'); +INSERT INTO t1 VALUES ('00:00:05'); +INSERT INTO t1 VALUES ('00:00:06'); +INSERT INTO t1 VALUES ('00:00:07'); +EXPLAIN SELECT * FROM t1 WHERE a LIKE '00:00:00'; +EXPLAIN SELECT * FROM t1 WHERE a LIKE TIME'00:00:00'; +SELECT * FROM t1 WHERE a LIKE '00:00:00'; +SELECT * FROM t1 WHERE a LIKE TIME'00:00:00'; +DROP TABLE t1; + + +--echo # --echo # End of 10.1 tests --echo # diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index f0ef596ff8c..b4f5d2dd963 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -120,6 +120,10 @@ public: friend class Item_func; }; + +class SEL_ARG; +struct KEY_PART; + class Item_bool_func :public Item_int_func { protected: @@ -147,6 +151,9 @@ protected: SEL_TREE *get_ne_mm_tree(RANGE_OPT_PARAM *param, Field *field, Item *lt_value, Item *gt_value, Item_result cmp_type); + virtual SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param, Field *field, + KEY_PART *key_part, + Item_func::Functype type, Item *value); public: Item_bool_func() :Item_int_func() {} Item_bool_func(Item *a) :Item_int_func(a) {} @@ -1437,6 +1444,9 @@ protected: DBUG_ENTER("Item_func_null_predicate::get_func_mm_tree"); DBUG_RETURN(get_mm_parts(param, field, functype(), value, cmp_type)); } + SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param, Field *field, + KEY_PART *key_part, + Item_func::Functype type, Item *value); public: Item_func_null_predicate(Item *a) :Item_bool_func(a) { } void add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level, @@ -1552,6 +1562,10 @@ class Item_func_like :public Item_bool_func2 DTCollation cmp_collation; String cmp_value1, cmp_value2; bool with_sargable_pattern() const; +protected: + SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param, Field *field, + KEY_PART *key_part, + Item_func::Functype type, Item *value); public: int escape; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index afb9dc6458c..3ff73bccaba 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -945,10 +945,6 @@ class TABLE_READ_PLAN; struct st_index_scan_info; struct st_ror_scan_info; -static SEL_ARG *get_mm_leaf(RANGE_OPT_PARAM *param,COND *cond_func,Field *field, - KEY_PART *key_part, - Item_func::Functype type,Item *value); - static bool is_key_scan_ror(PARAM *param, uint keynr, uint8 nparts); static ha_rows check_quick_select(PARAM *param, uint idx, bool index_only, SEL_ARG *tree, bool update_tbl_stats, @@ -8235,7 +8231,19 @@ Item_bool_func::get_mm_parts(RANGE_OPT_PARAM *param, Field *field, DBUG_RETURN(0); // OOM if (!value || !(value->used_tables() & ~param->read_tables)) { - sel_arg= get_mm_leaf(param,this,key_part->field,key_part,type,value); + /* + We need to restore the runtime mem_root of the thread in this + function because it evaluates the value of its argument, while + the argument can be any, e.g. a subselect. The subselect + items, in turn, assume that all the memory allocated during + the evaluation has the same life span as the item itself. + TODO: opt_range.cc should not reset thd->mem_root at all. + */ + MEM_ROOT *tmp_root= param->mem_root; + param->thd->mem_root= param->old_root; + sel_arg= get_mm_leaf(param, key_part->field, key_part, type, value); + param->thd->mem_root= tmp_root; + if (!sel_arg) continue; if (sel_arg->type == SEL_ARG::IMPOSSIBLE) @@ -8263,53 +8271,144 @@ Item_bool_func::get_mm_parts(RANGE_OPT_PARAM *param, Field *field, } -static SEL_ARG * -get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, - KEY_PART *key_part, Item_func::Functype type,Item *value) +SEL_ARG * +Item_func_null_predicate::get_mm_leaf(RANGE_OPT_PARAM *param, + Field *field, KEY_PART *key_part, + Item_func::Functype type, + Item *value) { - uint maybe_null=(uint) field->real_maybe_null(); - bool optimize_range; - SEL_ARG *tree= 0; MEM_ROOT *alloc= param->mem_root; - uchar *str; - int err; - DBUG_ENTER("get_mm_leaf"); + DBUG_ENTER("Item_func_null_predicate::get_mm_leaf"); + DBUG_ASSERT(!value); + /* + No check for field->table->maybe_null. It's perfecly fine to use range + access for cases like + + SELECT * FROM t1 LEFT JOIN t2 ON t2.key IS [NOT] NULL + + ON expression is evaluated before considering NULL-complemented rows, so + IS [NOT] NULL has regular semantics. + */ + if (!field->real_maybe_null()) + DBUG_RETURN(type == ISNULL_FUNC ? &null_element : NULL); + SEL_ARG *tree; + if (!(tree= new (alloc) SEL_ARG(field, is_null_string, is_null_string))) + DBUG_RETURN(0); + if (type == Item_func::ISNOTNULL_FUNC) + { + tree->min_flag=NEAR_MIN; /* IS NOT NULL -> X > NULL */ + tree->max_flag=NO_MAX_RANGE; + } + DBUG_RETURN(tree); +} + + +SEL_ARG * +Item_func_like::get_mm_leaf(RANGE_OPT_PARAM *param, + Field *field, KEY_PART *key_part, + Item_func::Functype type, Item *value) +{ + DBUG_ENTER("Item_func_like::get_mm_leaf"); + DBUG_ASSERT(value); + + if (key_part->image_type != Field::itRAW) + DBUG_RETURN(0); + + if (param->using_real_indexes && + !field->optimize_range(param->real_keynr[key_part->key], + key_part->part)) + DBUG_RETURN(0); + + if (field->result_type() == STRING_RESULT && + field->charset() != compare_collation()) + DBUG_RETURN(0); + + StringBuffer<MAX_FIELD_WIDTH> tmp(value->collation.collation); + String *res; + + if (!(res= value->val_str(&tmp))) + DBUG_RETURN(&null_element); + + if (field->cmp_type() != STRING_RESULT) + DBUG_RETURN(0); /* - We need to restore the runtime mem_root of the thread in this - function because it evaluates the value of its argument, while - the argument can be any, e.g. a subselect. The subselect - items, in turn, assume that all the memory allocated during - the evaluation has the same life span as the item itself. - TODO: opt_range.cc should not reset thd->mem_root at all. + TODO: + Check if this was a function. This should have be optimized away + in the sql_select.cc */ - param->thd->mem_root= param->old_root; - if (!value) // IS NULL or IS NOT NULL + if (res != &tmp) { - /* - No check for field->table->maybe_null. It's perfecly fine to use range - access for cases like + tmp.copy(*res); // Get own copy + res= &tmp; + } - SELECT * FROM t1 LEFT JOIN t2 ON t2.key IS [NOT] NULL + uint maybe_null= (uint) field->real_maybe_null(); + uint field_length= field->pack_length() + maybe_null; + size_t offset= maybe_null; + size_t length= key_part->store_length; - ON expression is evaluated before considering NULL-complemented rows, so - IS [NOT] NULL has regular semantics. - */ - if (!maybe_null) // Not null field - { - if (type == Item_func::ISNULL_FUNC) - tree= &null_element; - goto end; - } - if (!(tree= new (alloc) SEL_ARG(field,is_null_string,is_null_string))) - goto end; // out of memory - if (type == Item_func::ISNOTNULL_FUNC) + if (length != key_part->length + maybe_null) + { + /* key packed with length prefix */ + offset+= HA_KEY_BLOB_LENGTH; + field_length= length - HA_KEY_BLOB_LENGTH; + } + else + { + if (unlikely(length < field_length)) { - tree->min_flag=NEAR_MIN; /* IS NOT NULL -> X > NULL */ - tree->max_flag=NO_MAX_RANGE; + /* + This can only happen in a table created with UNIREG where one key + overlaps many fields + */ + length= field_length; } - goto end; + else + field_length= length; } + length+= offset; + uchar *min_str,*max_str; + if (!(min_str= (uchar*) alloc_root(param->mem_root, length*2))) + DBUG_RETURN(0); + max_str= min_str + length; + if (maybe_null) + max_str[0]= min_str[0]=0; + + size_t min_length, max_length; + field_length-= maybe_null; + if (my_like_range(field->charset(), + res->ptr(), res->length(), + escape, wild_one, wild_many, + field_length, + (char*) min_str + offset, + (char*) max_str + offset, + &min_length, &max_length)) + DBUG_RETURN(0); // Can't optimize with LIKE + + if (offset != maybe_null) // BLOB or VARCHAR + { + int2store(min_str + maybe_null, min_length); + int2store(max_str + maybe_null, max_length); + } + SEL_ARG *tree= new (param->mem_root) SEL_ARG(field, min_str, max_str); + DBUG_RETURN(tree); +} + + +SEL_ARG * +Item_bool_func::get_mm_leaf(RANGE_OPT_PARAM *param, + Field *field, KEY_PART *key_part, + Item_func::Functype type, Item *value) +{ + uint maybe_null=(uint) field->real_maybe_null(); + SEL_ARG *tree= 0; + MEM_ROOT *alloc= param->mem_root; + uchar *str; + int err; + DBUG_ENTER("Item_bool_func::get_mm_leaf"); + + DBUG_ASSERT(value); // IS NULL and IS NOT NULL are handled separately /* 1. Usually we can't use an index if the column collation @@ -8327,9 +8426,9 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, field->match_collation_to_optimize_range() && value->result_type() == STRING_RESULT && key_part->image_type == Field::itRAW && - field->charset() != conf_func->compare_collation() && - !((type == Item_func::EQUAL_FUNC || type == Item_func::EQ_FUNC) && - conf_func->compare_collation()->state & MY_CS_BINSORT)) + field->charset() != compare_collation() && + !((type == EQUAL_FUNC || type == EQ_FUNC) && + compare_collation()->state & MY_CS_BINSORT)) goto end; if (value->cmp_type() == TIME_RESULT && field->cmp_type() != TIME_RESULT) goto end; @@ -8338,14 +8437,14 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, { // @todo: use is_spatial_operator() instead? switch (type) { - case Item_func::SP_EQUALS_FUNC: - case Item_func::SP_DISJOINT_FUNC: - case Item_func::SP_INTERSECTS_FUNC: - case Item_func::SP_TOUCHES_FUNC: - case Item_func::SP_CROSSES_FUNC: - case Item_func::SP_WITHIN_FUNC: - case Item_func::SP_CONTAINS_FUNC: - case Item_func::SP_OVERLAPS_FUNC: + case SP_EQUALS_FUNC: + case SP_DISJOINT_FUNC: + case SP_INTERSECTS_FUNC: + case SP_TOUCHES_FUNC: + case SP_CROSSES_FUNC: + case SP_WITHIN_FUNC: + case SP_CONTAINS_FUNC: + case SP_OVERLAPS_FUNC: break; default: /* @@ -8356,95 +8455,11 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, } } - if (param->using_real_indexes) - optimize_range= field->optimize_range(param->real_keynr[key_part->key], - key_part->part); - else - optimize_range= TRUE; - - if (type == Item_func::LIKE_FUNC) - { - bool like_error; - char buff1[MAX_FIELD_WIDTH]; - uchar *min_str,*max_str; - String tmp(buff1,sizeof(buff1),value->collation.collation),*res; - size_t length, offset, min_length, max_length; - uint field_length= field->pack_length()+maybe_null; - - if (!optimize_range) - goto end; - if (!(res= value->val_str(&tmp))) - { - tree= &null_element; - goto end; - } - - /* - TODO: - Check if this was a function. This should have be optimized away - in the sql_select.cc - */ - if (res != &tmp) - { - tmp.copy(*res); // Get own copy - res= &tmp; - } - if (field->cmp_type() != STRING_RESULT) - goto end; // Can only optimize strings - - offset=maybe_null; - length=key_part->store_length; - - if (length != key_part->length + maybe_null) - { - /* key packed with length prefix */ - offset+= HA_KEY_BLOB_LENGTH; - field_length= length - HA_KEY_BLOB_LENGTH; - } - else - { - if (unlikely(length < field_length)) - { - /* - This can only happen in a table created with UNIREG where one key - overlaps many fields - */ - length= field_length; - } - else - field_length= length; - } - length+=offset; - if (!(min_str= (uchar*) alloc_root(alloc, length*2))) - goto end; - - max_str=min_str+length; - if (maybe_null) - max_str[0]= min_str[0]=0; - - field_length-= maybe_null; - like_error= my_like_range(field->charset(), - res->ptr(), res->length(), - ((Item_func_like*)(conf_func))->escape, - wild_one, wild_many, - field_length, - (char*) min_str+offset, (char*) max_str+offset, - &min_length, &max_length); - if (like_error) // Can't optimize with LIKE - goto end; - - if (offset != maybe_null) // BLOB or VARCHAR - { - int2store(min_str+maybe_null,min_length); - int2store(max_str+maybe_null,max_length); - } - tree= new (alloc) SEL_ARG(field, min_str, max_str); - goto end; - } - - if (!optimize_range && - type != Item_func::EQ_FUNC && - type != Item_func::EQUAL_FUNC) + if (param->using_real_indexes && + !field->optimize_range(param->real_keynr[key_part->key], + key_part->part) && + type != EQ_FUNC && + type != EQUAL_FUNC) goto end; // Can't optimize this /* @@ -8456,7 +8471,7 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, err= value->save_in_field_no_warnings(field, 1); if (err == 2 && field->cmp_type() == STRING_RESULT) { - if (type == Item_func::EQ_FUNC) + if (type == EQ_FUNC) { tree= new (alloc) SEL_ARG(field, 0, 0); tree->type= SEL_ARG::IMPOSSIBLE; @@ -8469,7 +8484,7 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, { if (field->cmp_type() != value->result_type()) { - if ((type == Item_func::EQ_FUNC || type == Item_func::EQUAL_FUNC) && + if ((type == EQ_FUNC || type == EQUAL_FUNC) && value->result_type() == item_cmp_type(field->result_type(), value->result_type())) { @@ -8485,8 +8500,8 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, */ tree= 0; if (err == 3 && field->type() == FIELD_TYPE_DATE && - (type == Item_func::GT_FUNC || type == Item_func::GE_FUNC || - type == Item_func::LT_FUNC || type == Item_func::LE_FUNC) ) + (type == GT_FUNC || type == GE_FUNC || + type == LT_FUNC || type == LE_FUNC) ) { /* We were saving DATETIME into a DATE column, the conversion went ok @@ -8519,14 +8534,14 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, */ else if (err == 1 && field->result_type() == INT_RESULT) { - if (type == Item_func::LT_FUNC && (value->val_int() > 0)) - type = Item_func::LE_FUNC; - else if (type == Item_func::GT_FUNC && + if (type == LT_FUNC && (value->val_int() > 0)) + type= LE_FUNC; + else if (type == GT_FUNC && (field->type() != FIELD_TYPE_BIT) && !((Field_num*)field)->unsigned_flag && !((Item_int*)value)->unsigned_flag && (value->val_int() < 0)) - type = Item_func::GE_FUNC; + type= GE_FUNC; } } else if (err < 0) @@ -8540,7 +8555,7 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, Any sargable predicate except "<=>" involving NULL as a constant is always FALSE */ - if (type != Item_func::EQUAL_FUNC && field->is_real_null()) + if (type != EQUAL_FUNC && field->is_real_null()) { tree= &null_element; goto end; @@ -8576,12 +8591,12 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, longlong item_val= value->val_int(); if (item_val < 0) { - if (type == Item_func::LT_FUNC || type == Item_func::LE_FUNC) + if (type == LT_FUNC || type == LE_FUNC) { tree->type= SEL_ARG::IMPOSSIBLE; goto end; } - if (type == Item_func::GT_FUNC || type == Item_func::GE_FUNC) + if (type == GT_FUNC || type == GE_FUNC) { tree= 0; goto end; @@ -8590,11 +8605,11 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, } switch (type) { - case Item_func::LT_FUNC: + case LT_FUNC: if (stored_field_cmp_to_item(param->thd, field, value) == 0) tree->max_flag=NEAR_MAX; /* fall through */ - case Item_func::LE_FUNC: + case LE_FUNC: if (!maybe_null) tree->min_flag=NO_MIN_RANGE; /* From start */ else @@ -8603,61 +8618,61 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, tree->min_flag=NEAR_MIN; } break; - case Item_func::GT_FUNC: + case GT_FUNC: /* Don't use open ranges for partial key_segments */ if ((!(key_part->flag & HA_PART_KEY_SEG)) && (stored_field_cmp_to_item(param->thd, field, value) <= 0)) tree->min_flag=NEAR_MIN; tree->max_flag= NO_MAX_RANGE; break; - case Item_func::GE_FUNC: + case GE_FUNC: /* Don't use open ranges for partial key_segments */ if ((!(key_part->flag & HA_PART_KEY_SEG)) && (stored_field_cmp_to_item(param->thd, field, value) < 0)) tree->min_flag= NEAR_MIN; tree->max_flag=NO_MAX_RANGE; break; - case Item_func::SP_EQUALS_FUNC: + case SP_EQUALS_FUNC: tree->min_flag=GEOM_FLAG | HA_READ_MBR_EQUAL;// NEAR_MIN;//512; tree->max_flag=NO_MAX_RANGE; break; - case Item_func::SP_DISJOINT_FUNC: + case SP_DISJOINT_FUNC: tree->min_flag=GEOM_FLAG | HA_READ_MBR_DISJOINT;// NEAR_MIN;//512; tree->max_flag=NO_MAX_RANGE; break; - case Item_func::SP_INTERSECTS_FUNC: + case SP_INTERSECTS_FUNC: tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; tree->max_flag=NO_MAX_RANGE; break; - case Item_func::SP_TOUCHES_FUNC: + case SP_TOUCHES_FUNC: tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; tree->max_flag=NO_MAX_RANGE; break; - - case Item_func::SP_CROSSES_FUNC: + case SP_CROSSES_FUNC: tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; tree->max_flag=NO_MAX_RANGE; break; - case Item_func::SP_WITHIN_FUNC: + case SP_WITHIN_FUNC: tree->min_flag=GEOM_FLAG | HA_READ_MBR_WITHIN;// NEAR_MIN;//512; tree->max_flag=NO_MAX_RANGE; break; - - case Item_func::SP_CONTAINS_FUNC: + case SP_CONTAINS_FUNC: tree->min_flag=GEOM_FLAG | HA_READ_MBR_CONTAIN;// NEAR_MIN;//512; tree->max_flag=NO_MAX_RANGE; break; - case Item_func::SP_OVERLAPS_FUNC: + case SP_OVERLAPS_FUNC: tree->min_flag=GEOM_FLAG | HA_READ_MBR_INTERSECT;// NEAR_MIN;//512; tree->max_flag=NO_MAX_RANGE; break; - + case EQ_FUNC: + case EQUAL_FUNC: + break; default: + DBUG_ASSERT(0); break; } end: - param->thd->mem_root= alloc; DBUG_RETURN(tree); } diff --git a/sql/opt_range.h b/sql/opt_range.h index c29b09a6359..2f108c1f6b5 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -38,7 +38,7 @@ class JOIN; class Item_sum; -typedef struct st_key_part { +struct KEY_PART { uint16 key,part; /* See KEY_PART_INFO for meaning of the next two: */ uint16 store_length, length; @@ -50,7 +50,7 @@ typedef struct st_key_part { uint8 flag; Field *field; Field::imagetype image_type; -} KEY_PART; +}; class Explain_quick_select; /* |