Skip to content

Commit 195bc49

Browse files
committed
Bug#21770798: OPTIMIZER DOES NOT USE INDEX FOR GENERATED EXPRESSIONS
WITH LOGICAL OPERATORS Before this patch, only expressions with item type ITEM_FUNC were eligible for substitution with an equivalent generated column. AND and OR expressions have item type ITEM_COND, and such expressions were therefore not replaced with a generated column. For example, a predicate such as (a AND b) = 1 would not be replaced with gcol = 1, even if there was an indexed generated column gcol whose generation expression was (a AND b). This meant that some opportunities for using an index were missed. This patch enables substitution also for expressions that use AND or OR. Since the substitution code works on Item_func objects, and items of type COND_ITEM already belong to subclasses of Item_func, not much has to be changed in the substitution logic. The patch mainly loosens up some type checks to allow both FUNC_ITEM and COND_ITEM to be substituted. It also adds an override of eq() in Item_cond to make it possible to check if two logical expressions are equal.
1 parent ab45d19 commit 195bc49

File tree

8 files changed

+262
-13
lines changed

8 files changed

+262
-13
lines changed

mysql-test/suite/gcol/inc/gcol_keys.inc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,3 +694,44 @@ DROP TABLE t;
694694
}
695695

696696
--echo #
697+
--echo # Bug#21770798 OPTIMIZER DOES NOT USE INDEX FOR GENERATED EXPRESSIONS
698+
--echo # WITH LOGICAL OPERATORS
699+
--echo #
700+
CREATE TABLE t (a INT, b INT,
701+
gc_and INT GENERATED ALWAYS AS (a AND b) STORED,
702+
gc_or INT GENERATED ALWAYS AS (a OR b) STORED,
703+
gc_xor INT GENERATED ALWAYS AS (a XOR b) STORED,
704+
gc_not INT GENERATED ALWAYS AS (NOT a) STORED,
705+
gc_case INT GENERATED ALWAYS AS
706+
(CASE WHEN (a AND b) THEN a ELSE b END) STORED,
707+
INDEX(gc_and), INDEX(gc_or), INDEX(gc_xor), INDEX(gc_not),
708+
INDEX(gc_case));
709+
INSERT INTO t (a, b) VALUES (0, 0), (0, 1), (1, 0), (1, 1);
710+
ANALYZE TABLE t;
711+
EXPLAIN SELECT a, b FROM t WHERE (a AND b) = 1;
712+
SELECT a, b FROM t WHERE (a AND b) = 1;
713+
EXPLAIN SELECT a, b FROM t WHERE 1 = (a AND b);
714+
SELECT a, b FROM t WHERE 1 = (a AND b);
715+
EXPLAIN SELECT a, b FROM t WHERE (a AND b) IN (1, 2, 3);
716+
SELECT a, b FROM t WHERE (a AND b) IN (1, 2, 3);
717+
EXPLAIN SELECT a, b FROM t WHERE (a OR b) = 1;
718+
--sorted_result
719+
SELECT a, b FROM t WHERE (a OR b) = 1;
720+
EXPLAIN SELECT a, b FROM t WHERE (a OR b) BETWEEN 1 AND 10;
721+
--sorted_result
722+
SELECT a, b FROM t WHERE (a OR b) BETWEEN 1 AND 10;
723+
# XOR and NOT worked even before the bug fix, but we test all logical
724+
# operators here for completeness.
725+
EXPLAIN SELECT a, b FROM t WHERE (a XOR b) = 1;
726+
--sorted_result
727+
SELECT a, b FROM t WHERE (a XOR b) = 1;
728+
EXPLAIN SELECT a FROM t WHERE (NOT a) = 1;
729+
SELECT a FROM t WHERE (NOT a) = 1;
730+
# Also verify that a logical expression nested inside another
731+
# expression doesn't prevent substitution.
732+
EXPLAIN SELECT a FROM t WHERE (CASE WHEN (a AND b) THEN a ELSE b END) = 1;
733+
--sorted_result
734+
SELECT a FROM t WHERE (CASE WHEN (a AND b) THEN a ELSE b END) = 1;
735+
DROP TABLE t;
736+
737+
--echo #

mysql-test/suite/gcol/r/gcol_keys_innodb.result

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,95 @@ ERROR 22012: Division by 0
12281228
CALL mtr.add_suppression("\\[Warning\\] InnoDB: Compute virtual column values failed");
12291229
DROP TABLE t;
12301230
#
1231+
# Bug#21770798 OPTIMIZER DOES NOT USE INDEX FOR GENERATED EXPRESSIONS
1232+
# WITH LOGICAL OPERATORS
1233+
#
1234+
CREATE TABLE t (a INT, b INT,
1235+
gc_and INT GENERATED ALWAYS AS (a AND b) STORED,
1236+
gc_or INT GENERATED ALWAYS AS (a OR b) STORED,
1237+
gc_xor INT GENERATED ALWAYS AS (a XOR b) STORED,
1238+
gc_not INT GENERATED ALWAYS AS (NOT a) STORED,
1239+
gc_case INT GENERATED ALWAYS AS
1240+
(CASE WHEN (a AND b) THEN a ELSE b END) STORED,
1241+
INDEX(gc_and), INDEX(gc_or), INDEX(gc_xor), INDEX(gc_not),
1242+
INDEX(gc_case));
1243+
INSERT INTO t (a, b) VALUES (0, 0), (0, 1), (1, 0), (1, 1);
1244+
ANALYZE TABLE t;
1245+
Table Op Msg_type Msg_text
1246+
test.t analyze status OK
1247+
EXPLAIN SELECT a, b FROM t WHERE (a AND b) = 1;
1248+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1249+
1 SIMPLE t NULL ref gc_and gc_and 5 const 1 100.00 NULL
1250+
Warnings:
1251+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_and` = 1)
1252+
SELECT a, b FROM t WHERE (a AND b) = 1;
1253+
a b
1254+
1 1
1255+
EXPLAIN SELECT a, b FROM t WHERE 1 = (a AND b);
1256+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1257+
1 SIMPLE t NULL ref gc_and gc_and 5 const 1 100.00 NULL
1258+
Warnings:
1259+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (1 = `test`.`t`.`gc_and`)
1260+
SELECT a, b FROM t WHERE 1 = (a AND b);
1261+
a b
1262+
1 1
1263+
EXPLAIN SELECT a, b FROM t WHERE (a AND b) IN (1, 2, 3);
1264+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1265+
1 SIMPLE t NULL ALL gc_and NULL NULL NULL 4 75.00 Using where
1266+
Warnings:
1267+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_and` in (1,2,3))
1268+
SELECT a, b FROM t WHERE (a AND b) IN (1, 2, 3);
1269+
a b
1270+
1 1
1271+
EXPLAIN SELECT a, b FROM t WHERE (a OR b) = 1;
1272+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1273+
1 SIMPLE t NULL ALL gc_or NULL NULL NULL 4 75.00 Using where
1274+
Warnings:
1275+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_or` = 1)
1276+
SELECT a, b FROM t WHERE (a OR b) = 1;
1277+
a b
1278+
0 1
1279+
1 0
1280+
1 1
1281+
EXPLAIN SELECT a, b FROM t WHERE (a OR b) BETWEEN 1 AND 10;
1282+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1283+
1 SIMPLE t NULL ALL gc_or NULL NULL NULL 4 75.00 Using where
1284+
Warnings:
1285+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_or` between 1 and 10)
1286+
SELECT a, b FROM t WHERE (a OR b) BETWEEN 1 AND 10;
1287+
a b
1288+
0 1
1289+
1 0
1290+
1 1
1291+
EXPLAIN SELECT a, b FROM t WHERE (a XOR b) = 1;
1292+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1293+
1 SIMPLE t NULL ref gc_xor gc_xor 5 const 2 100.00 NULL
1294+
Warnings:
1295+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_xor` = 1)
1296+
SELECT a, b FROM t WHERE (a XOR b) = 1;
1297+
a b
1298+
0 1
1299+
1 0
1300+
EXPLAIN SELECT a FROM t WHERE (NOT a) = 1;
1301+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1302+
1 SIMPLE t NULL ref gc_not gc_not 5 const 2 100.00 NULL
1303+
Warnings:
1304+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a` from `test`.`t` where (`test`.`t`.`gc_not` = 1)
1305+
SELECT a FROM t WHERE (NOT a) = 1;
1306+
a
1307+
0
1308+
0
1309+
EXPLAIN SELECT a FROM t WHERE (CASE WHEN (a AND b) THEN a ELSE b END) = 1;
1310+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1311+
1 SIMPLE t NULL ref gc_case gc_case 5 const 2 100.00 NULL
1312+
Warnings:
1313+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a` from `test`.`t` where (`test`.`t`.`gc_case` = 1)
1314+
SELECT a FROM t WHERE (CASE WHEN (a AND b) THEN a ELSE b END) = 1;
1315+
a
1316+
0
1317+
1
1318+
DROP TABLE t;
1319+
#
12311320
#
12321321
# BUG#21365158 WL8149:ASSERTION `!TABLE || (!TABLE->WRITE_SET
12331322
#

mysql-test/suite/gcol/r/gcol_keys_myisam.result

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,95 @@ Warnings:
800800
Note 1003 /* select#1 */ select ('7' + 1) AS `field1`,'7' AS `field2` from dual where 1 group by `col_int_gc_key`,`field2`
801801
DROP TABLE t1;
802802
#
803+
# Bug#21770798 OPTIMIZER DOES NOT USE INDEX FOR GENERATED EXPRESSIONS
804+
# WITH LOGICAL OPERATORS
805+
#
806+
CREATE TABLE t (a INT, b INT,
807+
gc_and INT GENERATED ALWAYS AS (a AND b) STORED,
808+
gc_or INT GENERATED ALWAYS AS (a OR b) STORED,
809+
gc_xor INT GENERATED ALWAYS AS (a XOR b) STORED,
810+
gc_not INT GENERATED ALWAYS AS (NOT a) STORED,
811+
gc_case INT GENERATED ALWAYS AS
812+
(CASE WHEN (a AND b) THEN a ELSE b END) STORED,
813+
INDEX(gc_and), INDEX(gc_or), INDEX(gc_xor), INDEX(gc_not),
814+
INDEX(gc_case));
815+
INSERT INTO t (a, b) VALUES (0, 0), (0, 1), (1, 0), (1, 1);
816+
ANALYZE TABLE t;
817+
Table Op Msg_type Msg_text
818+
test.t analyze status OK
819+
EXPLAIN SELECT a, b FROM t WHERE (a AND b) = 1;
820+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
821+
1 SIMPLE t NULL ref gc_and gc_and 5 const 1 100.00 NULL
822+
Warnings:
823+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_and` = 1)
824+
SELECT a, b FROM t WHERE (a AND b) = 1;
825+
a b
826+
1 1
827+
EXPLAIN SELECT a, b FROM t WHERE 1 = (a AND b);
828+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
829+
1 SIMPLE t NULL ref gc_and gc_and 5 const 1 100.00 NULL
830+
Warnings:
831+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (1 = `test`.`t`.`gc_and`)
832+
SELECT a, b FROM t WHERE 1 = (a AND b);
833+
a b
834+
1 1
835+
EXPLAIN SELECT a, b FROM t WHERE (a AND b) IN (1, 2, 3);
836+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
837+
1 SIMPLE t NULL ALL gc_and NULL NULL NULL 4 75.00 Using where
838+
Warnings:
839+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_and` in (1,2,3))
840+
SELECT a, b FROM t WHERE (a AND b) IN (1, 2, 3);
841+
a b
842+
1 1
843+
EXPLAIN SELECT a, b FROM t WHERE (a OR b) = 1;
844+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
845+
1 SIMPLE t NULL ref gc_or gc_or 5 const 2 100.00 NULL
846+
Warnings:
847+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_or` = 1)
848+
SELECT a, b FROM t WHERE (a OR b) = 1;
849+
a b
850+
0 1
851+
1 0
852+
1 1
853+
EXPLAIN SELECT a, b FROM t WHERE (a OR b) BETWEEN 1 AND 10;
854+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
855+
1 SIMPLE t NULL range gc_or gc_or 5 NULL 2 100.00 Using index condition
856+
Warnings:
857+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_or` between 1 and 10)
858+
SELECT a, b FROM t WHERE (a OR b) BETWEEN 1 AND 10;
859+
a b
860+
0 1
861+
1 0
862+
1 1
863+
EXPLAIN SELECT a, b FROM t WHERE (a XOR b) = 1;
864+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
865+
1 SIMPLE t NULL ref gc_xor gc_xor 5 const 2 100.00 NULL
866+
Warnings:
867+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a`,`test`.`t`.`b` AS `b` from `test`.`t` where (`test`.`t`.`gc_xor` = 1)
868+
SELECT a, b FROM t WHERE (a XOR b) = 1;
869+
a b
870+
0 1
871+
1 0
872+
EXPLAIN SELECT a FROM t WHERE (NOT a) = 1;
873+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
874+
1 SIMPLE t NULL ref gc_not gc_not 5 const 2 100.00 NULL
875+
Warnings:
876+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a` from `test`.`t` where (`test`.`t`.`gc_not` = 1)
877+
SELECT a FROM t WHERE (NOT a) = 1;
878+
a
879+
0
880+
0
881+
EXPLAIN SELECT a FROM t WHERE (CASE WHEN (a AND b) THEN a ELSE b END) = 1;
882+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
883+
1 SIMPLE t NULL ref gc_case gc_case 5 const 2 100.00 NULL
884+
Warnings:
885+
Note 1003 /* select#1 */ select `test`.`t`.`a` AS `a` from `test`.`t` where (`test`.`t`.`gc_case` = 1)
886+
SELECT a FROM t WHERE (CASE WHEN (a AND b) THEN a ELSE b END) = 1;
887+
a
888+
0
889+
1
890+
DROP TABLE t;
891+
#
803892
DROP VIEW IF EXISTS v1,v2;
804893
DROP TABLE IF EXISTS t1,t2,t3;
805894
DROP PROCEDURE IF EXISTS p1;

sql/item.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2212,6 +2212,16 @@ class Item : public Parse_tree_node
22122212
Transformer function for GC substitution. @see JOIN::substitute_gc()
22132213
*/
22142214
virtual Item *gc_subst_transformer(uchar *arg) { return this; }
2215+
/**
2216+
Check if this item is of a type that is eligible for GC
2217+
substitution. All items that belong to subclasses of Item_func are
2218+
eligible for substitution. @see JOIN::substitute_gc()
2219+
*/
2220+
bool can_be_substituted_for_gc() const
2221+
{
2222+
const Type t= type();
2223+
return t == FUNC_ITEM || t == COND_ITEM;
2224+
}
22152225
private:
22162226
virtual bool subq_opt_away_processor(uchar *arg) { return false; }
22172227
};

sql/item_cmpfunc.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5795,6 +5795,24 @@ void Item_cond::fix_after_pullout(st_select_lex *parent_select,
57955795
}
57965796

57975797

5798+
bool Item_cond::eq(const Item *item, bool binary_cmp) const
5799+
{
5800+
if (this == item)
5801+
return true;
5802+
if (item->type() != COND_ITEM)
5803+
return false;
5804+
const Item_cond *item_cond= down_cast<const Item_cond *>(item);
5805+
if (functype() != item_cond->functype() ||
5806+
arg_count != item_cond->arg_count ||
5807+
func_name() != item_cond->func_name())
5808+
return false;
5809+
for (size_t i= 0; i < arg_count; ++i)
5810+
if (!args[i]->eq(item_cond->args[i], binary_cmp))
5811+
return false;
5812+
return true;
5813+
}
5814+
5815+
57985816
bool Item_cond::walk(Item_processor processor, enum_walk walk, uchar *arg)
57995817
{
58005818
if ((walk & WALK_PREFIX) && (this->*processor)(arg))

sql/item_cmpfunc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,6 +2035,7 @@ class Item_cond :public Item_bool_func
20352035

20362036
enum Type type() const { return COND_ITEM; }
20372037
List<Item>* argument_list() { return &list; }
2038+
bool eq(const Item *item, bool binary_cmp) const;
20382039
table_map used_tables() const { return used_tables_cache; }
20392040
void update_used_tables();
20402041
virtual void print(String *str, enum_query_type query_type);

sql/item_func.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -963,11 +963,11 @@ Item_field *get_gc_for_expr(Item_func **func, Field *fld, Item_result type)
963963
if (!strcmp(expr->func_name(),"json_unquote") &&
964964
strcmp((*func)->func_name(),"json_unquote"))
965965
{
966-
if (expr->arguments()[0]->type() != Item::FUNC_ITEM)
966+
if (!expr->arguments()[0]->can_be_substituted_for_gc())
967967
return NULL;
968-
expr= (Item_func*)expr->arguments()[0];
968+
expr= down_cast<Item_func*>(expr->arguments()[0]);
969969
}
970-
DBUG_ASSERT(expr->type() == Item::FUNC_ITEM);
970+
DBUG_ASSERT(expr->can_be_substituted_for_gc());
971971

972972
if (type == fld->result_type() && (*func)->eq(expr, false))
973973
{
@@ -1016,12 +1016,12 @@ Item *Item_func::gc_subst_transformer(uchar *arg)
10161016
List<Field> *gc_fields= (List<Field> *)arg;
10171017
List_iterator<Field> li(*gc_fields);
10181018
// Check if we can substitute a function with a GC
1019-
if (args[0]->type() == FUNC_ITEM && args[1]->const_item())
1019+
if (args[0]->can_be_substituted_for_gc() && args[1]->const_item())
10201020
{
10211021
func= (Item_func**)args;
10221022
val= args + 1;
10231023
}
1024-
else if (args[1]->type() == FUNC_ITEM && args[0]->const_item())
1024+
else if (args[1]->can_be_substituted_for_gc() && args[0]->const_item())
10251025
{
10261026
func= (Item_func**)args + 1;
10271027
val= args;
@@ -1056,7 +1056,7 @@ Item *Item_func::gc_subst_transformer(uchar *arg)
10561056
{
10571057
List<Field> *gc_fields= (List<Field> *)arg;
10581058
List_iterator<Field> li(*gc_fields);
1059-
if (args[0]->type() != FUNC_ITEM)
1059+
if (!args[0]->can_be_substituted_for_gc())
10601060
break;
10611061
Item_result type= args[1]->result_type();
10621062
bool can_do_subst= args[1]->const_item();

sql/sql_optimizer.cc

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -780,12 +780,13 @@ void JOIN::substitute_gc()
780780
for (uint i= 0; i < tl->table->s->fields; i++)
781781
{
782782
Field *fld= tl->table->field[i];
783-
if (!fld->gcol_info || fld->part_of_key.is_clear_all() ||
784-
fld->gcol_info->expr_item->type() != Item::FUNC_ITEM)
785-
continue;
786-
// Don't check allowed keys here as conditions/group/order use
787-
// different keymaps for that
788-
indexed_gc.push_back(fld);
783+
if (fld->is_gcol() && !fld->part_of_key.is_clear_all() &&
784+
fld->gcol_info->expr_item->can_be_substituted_for_gc())
785+
{
786+
// Don't check allowed keys here as conditions/group/order use
787+
// different keymaps for that.
788+
indexed_gc.push_back(fld);
789+
}
789790
}
790791
}
791792
// No GC in the tables used in the query
@@ -824,7 +825,7 @@ void JOIN::substitute_gc()
824825
for (ORDER *ord= list; ord; ord= ord->next)
825826
{
826827
li.rewind();
827-
if ((*ord->item)->type() != Item::FUNC_ITEM)
828+
if (!(*ord->item)->can_be_substituted_for_gc())
828829
continue;
829830
while ((gc= li++))
830831
{

0 commit comments

Comments
 (0)