Skip to content

Commit cb3cdfd

Browse files
committed
Bug#20487336: Assert 'length > 0 && keyparts != 0' in calc_length_and_keyparts
The real fix for the problem. The problem was that fix_tables_after_pullout() was too eager to adjust TABLE_LIST::sj_depends_on and sj_corr_tables for a semi-join: It was called for semi-join nests that had already been adjusted when merging a derived table/view. The current solution calls fix_tables_after_pullout() only for the derived table/view that is merged. Previously, this function was called for all tables of a query block. This solution will only work if derived tables are merged in a strict order, see the comment above fix_tables_after_pullout() for details. It was also necessary to convert fix_tables_after_pullout() to be called for a join nest instead of a list of join nests. The changed plan for the query in derived.test was due to there being a semi-join inside a derived table, and the sj_depends_on field was adjusted wrongly due to this bug.
1 parent bd310e0 commit cb3cdfd

File tree

3 files changed

+218
-48
lines changed

3 files changed

+218
-48
lines changed

mysql-test/r/derived.result

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2987,12 +2987,11 @@ id select_type table partitions type possible_keys key key_len ref rows filtered
29872987
1 SIMPLE sq1_t1 NULL ALL NULL NULL NULL NULL 1 100.00 NULL
29882988
1 SIMPLE sq1_t2 NULL index PRIMARY PRIMARY 4 NULL 1 100.00 Using where; Using index; Using join buffer (Block Nested Loop)
29892989
1 SIMPLE sq1_t3 NULL eq_ref PRIMARY,col_int_key PRIMARY 4 test.sq1_t2.pk 1 100.00 Using where
2990-
1 SIMPLE <subquery3> NULL ALL NULL NULL NULL NULL NULL 100.00 Using where; Using join buffer (Block Nested Loop)
2990+
1 SIMPLE child_sq1_t2 NULL ALL NULL NULL NULL NULL 1 100.00 Using where; FirstMatch(sq1_t3); Using join buffer (Block Nested Loop)
29912991
1 SIMPLE table2 NULL ALL NULL NULL NULL NULL 1 100.00 Using join buffer (Block Nested Loop)
29922992
1 SIMPLE sq2_t1 NULL ALL NULL NULL NULL NULL 1 100.00 Using where; Using join buffer (Block Nested Loop)
2993-
3 MATERIALIZED child_sq1_t2 NULL ALL NULL NULL NULL NULL 1 100.00 NULL
29942993
Warnings:
2995-
Note 1003 /* select#1 */ select `test`.`table2`.`pk` AS `field2` from `test`.`t1` `sq1_t1` semi join (`test`.`t1` `child_sq1_t2`) join `test`.`t2` `sq1_t3` join `test`.`t1` `sq1_t2` left join (`test`.`t1` `table2` join `test`.`t1` `sq2_t1`) on(((`test`.`sq2_t1`.`col_varchar_key` = `test`.`table2`.`col_varchar_key`))) where ((`<subquery3>`.`child_sq1_field1` = `test`.`sq1_t1`.`col_varchar_key`) and (`test`.`sq1_t3`.`col_int_key` = `test`.`sq1_t2`.`pk`) and (`test`.`sq1_t3`.`pk` = `test`.`sq1_t2`.`pk`))
2994+
Note 1003 /* select#1 */ select `test`.`table2`.`pk` AS `field2` from `test`.`t1` `sq1_t1` semi join (`test`.`t1` `child_sq1_t2`) join `test`.`t2` `sq1_t3` join `test`.`t1` `sq1_t2` left join (`test`.`t1` `table2` join `test`.`t1` `sq2_t1`) on(((`test`.`sq2_t1`.`col_varchar_key` = `test`.`table2`.`col_varchar_key`))) where ((`test`.`child_sq1_t2`.`col_varchar_key` = `test`.`sq1_t1`.`col_varchar_key`) and (`test`.`sq1_t3`.`col_int_key` = `test`.`sq1_t2`.`pk`) and (`test`.`sq1_t3`.`pk` = `test`.`sq1_t2`.`pk`))
29962995
SELECT table2.pk AS field2
29972996
FROM (SELECT sq1_t2.pk
29982997
FROM t1 AS sq1_t1
@@ -3164,6 +3163,84 @@ DEALLOCATE PREPARE s1;
31643163
DEALLOCATE PREPARE s2;
31653164
DROP VIEW v;
31663165
DROP TABLE t;
3166+
# Bug#20487336 Assert length > 0 && keyparts != 0 in calc_length_and_key
3167+
CREATE TABLE t1
3168+
(pk INTEGER PRIMARY KEY,
3169+
col_int_nokey INTEGER,
3170+
col_varchar_nokey VARCHAR(1),
3171+
col_varchar_key VARCHAR(1),
3172+
KEY col_varchar_key(col_varchar_key)
3173+
) engine=innodb;
3174+
INSERT INTO t1 (pk) VALUES
3175+
(1), (2), (3), (4), (5), (6), (7), (8), (9), (10),
3176+
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);
3177+
CREATE TABLE t2
3178+
(pk INTEGER PRIMARY KEY,
3179+
col_varchar_key VARCHAR(1),
3180+
KEY col_varchar_key(col_varchar_key)
3181+
) engine=innodb;
3182+
INSERT INTO t2 (pk) VALUES
3183+
(1), (2), (3), (4), (5), (6), (7), (8), (9), (10),
3184+
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);
3185+
CREATE TABLE t3
3186+
(pk INTEGER PRIMARY KEY,
3187+
col_varchar_key VARCHAR(1),
3188+
KEY col_varchar_key(col_varchar_key)
3189+
) engine=innodb;
3190+
INSERT INTO t3 (pk) VALUES
3191+
(1), (2);
3192+
ANALYZE TABLE t1, t2, t3;
3193+
Table Op Msg_type Msg_text
3194+
test.t1 analyze status OK
3195+
test.t2 analyze status OK
3196+
test.t3 analyze status OK
3197+
explain SELECT MIN(alias1.col_varchar_nokey) AS field1
3198+
FROM (SELECT sq1_alias1.*
3199+
FROM t1 AS sq1_alias1, t1 AS sq1_alias2
3200+
WHERE sq1_alias1.col_varchar_nokey IN
3201+
(SELECT c_sq1_alias2.col_varchar_key AS c_sq1_field1
3202+
FROM t1 AS c_sq1_alias1 INNER JOIN
3203+
t1 AS c_sq1_alias2
3204+
ON c_sq1_alias2.col_varchar_nokey = c_sq1_alias1.col_varchar_key
3205+
WHERE c_sq1_alias1.col_int_nokey <> c_sq1_alias1.col_int_nokey
3206+
) AND
3207+
sq1_alias2.pk = sq1_alias1.pk
3208+
) AS alias1,
3209+
(SELECT sq2_alias1.*
3210+
FROM t2 AS sq2_alias1 RIGHT JOIN
3211+
t3 AS sq2_alias2
3212+
ON sq2_alias2.col_varchar_key = sq2_alias1.col_varchar_key
3213+
) AS alias2;
3214+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
3215+
1 SIMPLE sq2_alias2 NULL index NULL col_varchar_key 4 NULL 2 100.00 Using index
3216+
1 SIMPLE <subquery3> NULL ALL NULL NULL NULL NULL NULL 100.00 Using join buffer (Block Nested Loop)
3217+
1 SIMPLE sq1_alias1 NULL ALL PRIMARY NULL NULL NULL 20 10.00 Using where; Using join buffer (Block Nested Loop)
3218+
1 SIMPLE sq1_alias2 NULL eq_ref PRIMARY PRIMARY 4 test.sq1_alias1.pk 1 100.00 Using index
3219+
1 SIMPLE sq2_alias1 NULL ref col_varchar_key col_varchar_key 4 test.sq2_alias2.col_varchar_key 20 100.00 Using index
3220+
3 MATERIALIZED c_sq1_alias1 NULL ALL col_varchar_key NULL NULL NULL 20 90.00 Using where
3221+
3 MATERIALIZED c_sq1_alias2 NULL ALL col_varchar_key NULL NULL NULL 20 10.00 Using where; Using join buffer (Block Nested Loop)
3222+
Warnings:
3223+
Note 1003 /* select#1 */ select min(`test`.`sq1_alias1`.`col_varchar_nokey`) AS `field1` from `test`.`t1` `sq1_alias1` semi join (`test`.`t1` `c_sq1_alias1` join `test`.`t1` `c_sq1_alias2`) join `test`.`t1` `sq1_alias2` join `test`.`t3` `sq2_alias2` left join `test`.`t2` `sq2_alias1` on((`test`.`sq2_alias1`.`col_varchar_key` = `test`.`sq2_alias2`.`col_varchar_key`)) where ((`test`.`c_sq1_alias2`.`col_varchar_nokey` = `test`.`c_sq1_alias1`.`col_varchar_key`) and (`test`.`sq1_alias1`.`col_varchar_nokey` = `<subquery3>`.`c_sq1_field1`) and (`test`.`sq1_alias2`.`pk` = `test`.`sq1_alias1`.`pk`) and (`test`.`c_sq1_alias1`.`col_int_nokey` <> `test`.`c_sq1_alias1`.`col_int_nokey`))
3224+
SELECT MIN(alias1.col_varchar_nokey) AS field1
3225+
FROM (SELECT sq1_alias1.*
3226+
FROM t1 AS sq1_alias1, t1 AS sq1_alias2
3227+
WHERE sq1_alias1.col_varchar_nokey IN
3228+
(SELECT c_sq1_alias2.col_varchar_key AS c_sq1_field1
3229+
FROM t1 AS c_sq1_alias1 INNER JOIN
3230+
t1 AS c_sq1_alias2
3231+
ON c_sq1_alias2.col_varchar_nokey = c_sq1_alias1.col_varchar_key
3232+
WHERE c_sq1_alias1.col_int_nokey <> c_sq1_alias1.col_int_nokey
3233+
) AND
3234+
sq1_alias2.pk = sq1_alias1.pk
3235+
) AS alias1,
3236+
(SELECT sq2_alias1.*
3237+
FROM t2 AS sq2_alias1 RIGHT JOIN
3238+
t3 AS sq2_alias2
3239+
ON sq2_alias2.col_varchar_key = sq2_alias1.col_varchar_key
3240+
) AS alias2;
3241+
field1
3242+
NULL
3243+
DROP TABLE t1, t2, t3;
31673244
#
31683245
# Bug #18607971 : 5.5 TO 5.6 REGRESSION WITH A SUBQUERY IN THE FROM
31693246
# CLAUSE.

mysql-test/t/derived.test

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,65 @@ DEALLOCATE PREPARE s2;
21642164
DROP VIEW v;
21652165
DROP TABLE t;
21662166

2167+
--echo # Bug#20487336 Assert length > 0 && keyparts != 0 in calc_length_and_key
2168+
2169+
CREATE TABLE t1
2170+
(pk INTEGER PRIMARY KEY,
2171+
col_int_nokey INTEGER,
2172+
col_varchar_nokey VARCHAR(1),
2173+
col_varchar_key VARCHAR(1),
2174+
KEY col_varchar_key(col_varchar_key)
2175+
) engine=innodb;
2176+
2177+
INSERT INTO t1 (pk) VALUES
2178+
(1), (2), (3), (4), (5), (6), (7), (8), (9), (10),
2179+
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);
2180+
2181+
CREATE TABLE t2
2182+
(pk INTEGER PRIMARY KEY,
2183+
col_varchar_key VARCHAR(1),
2184+
KEY col_varchar_key(col_varchar_key)
2185+
) engine=innodb;
2186+
2187+
INSERT INTO t2 (pk) VALUES
2188+
(1), (2), (3), (4), (5), (6), (7), (8), (9), (10),
2189+
(11), (12), (13), (14), (15), (16), (17), (18), (19), (20);
2190+
2191+
CREATE TABLE t3
2192+
(pk INTEGER PRIMARY KEY,
2193+
col_varchar_key VARCHAR(1),
2194+
KEY col_varchar_key(col_varchar_key)
2195+
) engine=innodb;
2196+
2197+
INSERT INTO t3 (pk) VALUES
2198+
(1), (2);
2199+
2200+
ANALYZE TABLE t1, t2, t3;
2201+
2202+
let $query=
2203+
SELECT MIN(alias1.col_varchar_nokey) AS field1
2204+
FROM (SELECT sq1_alias1.*
2205+
FROM t1 AS sq1_alias1, t1 AS sq1_alias2
2206+
WHERE sq1_alias1.col_varchar_nokey IN
2207+
(SELECT c_sq1_alias2.col_varchar_key AS c_sq1_field1
2208+
FROM t1 AS c_sq1_alias1 INNER JOIN
2209+
t1 AS c_sq1_alias2
2210+
ON c_sq1_alias2.col_varchar_nokey = c_sq1_alias1.col_varchar_key
2211+
WHERE c_sq1_alias1.col_int_nokey <> c_sq1_alias1.col_int_nokey
2212+
) AND
2213+
sq1_alias2.pk = sq1_alias1.pk
2214+
) AS alias1,
2215+
(SELECT sq2_alias1.*
2216+
FROM t2 AS sq2_alias1 RIGHT JOIN
2217+
t3 AS sq2_alias2
2218+
ON sq2_alias2.col_varchar_key = sq2_alias1.col_varchar_key
2219+
) AS alias2;
2220+
2221+
eval explain $query;
2222+
eval $query;
2223+
2224+
DROP TABLE t1, t2, t3;
2225+
21672226
--echo #
21682227
--echo # Bug #18607971 : 5.5 TO 5.6 REGRESSION WITH A SUBQUERY IN THE FROM
21692228
--echo # CLAUSE.

sql/sql_resolver.cc

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,72 +1663,107 @@ static int subq_sj_candidate_cmp(Item_exists_subselect* const *el1,
16631663

16641664
/**
16651665
Update table reference information for conditions and expressions due to
1666-
query blocks having been merged in from derived tables and due to
1666+
query blocks having been merged in from derived tables/views and due to
16671667
semi-join transformation.
16681668
16691669
This is needed for two reasons:
16701670
16711671
1. Since table numbers are changed, we need to update used_tables
16721672
information for all conditions and expressions that are possibly touched.
1673-
Notice that requirements are different for the two transforms:
1674-
semi-join only changes table numbers for tables from the subquery,
1675-
while derived tables may also change table numbers from the query block
1676-
being merged into.
16771673
16781674
2. For semi-join, some column references are changed from outer references
16791675
to local references.
16801676
1681-
Notice that function needs to recursively walk down into join nests,
1677+
The function needs to recursively walk down into join nests,
16821678
in order to cover all conditions and expressions.
16831679
1680+
For a semi-join, tables from the subquery are added last in the query block.
1681+
This means that conditions and expressions from the outer query block
1682+
are unaffected. But all conditions inside the semi-join nest, including
1683+
join conditions, must have their table numbers changed.
1684+
1685+
For a derived table/view, tables from the subquery are merged into the
1686+
outer query, and this function is called for every derived table that is
1687+
merged in. This algorithm only works when derived tables are merged in
1688+
the order of their original table numbers.
1689+
1690+
A hypothetical example with a triple self-join over a mergeable view:
1691+
1692+
CREATE VIEW v AS SELECT t1.a, t2.b FROM t1 JOIN t2 USING (a);
1693+
SELECT v1.a, v1.b, v2.b, v3.b
1694+
FROM v AS v1 JOIN v AS v2 ON ... JOIN v AS v3 ON ...;
1695+
1696+
The analysis starts with three tables v1, v2 and v3 having numbers 0, 1, 2.
1697+
First we merge in v1, so we get (t1, t2, v2, v3). v2 and v3 are shifted up.
1698+
Tables from v1 need to have their table numbers altered (actually they do not
1699+
since both old and new numbers are 0 and 1, but this is a special case).
1700+
v2 and v3 are not merged in yet, so we delay pullout on them until they
1701+
are merged. Conditions and expressions from the outer query are not resolved
1702+
yet, so regular resolving will take of them later.
1703+
Then we merge in v2, so we get (t1, t2, t1, t2, v3). The tables from this
1704+
view gets numbers 2 and 3, and v3 gets number 4.
1705+
Because v2 had a higher number than the tables from v1, the join nest
1706+
representing v1 is unaffected. And v3 is still not merged, so the only
1707+
join nest we need to consider is v2.
1708+
Finally we merge in v3, and then we have tables (t1, t2, t1, t2, t1, t2),
1709+
with numbers 0 through 5.
1710+
Again, since v3 has higher number than any of the already merged in views,
1711+
only this join nest needs the pullout.
1712+
16841713
@param parent_select Query block being merged into
16851714
@param removed_select Query block that is removed (subquery)
1686-
@param tlist List of tables to be checked, given as
1687-
semi_join: List of tables from subquery
1688-
derived table: List of tables from outer query
1689-
recursive call: List of tables from join nest
1690-
@param table_adjust Number of positions that a derived table nest is
1691-
adjusted, used to fix up semi-join related fields.
1692-
Tables are adjusted from position N to N+table_adjust
1715+
@param tr Table object this pullout is applied to
1716+
@param table_adjust Number of positions that a derived table nest is
1717+
adjusted, used to fix up semi-join related fields.
1718+
Tables are adjusted from position N to N+table_adjust
16931719
*/
16941720

16951721
static void fix_tables_after_pullout(st_select_lex *parent_select,
16961722
st_select_lex *removed_select,
1697-
List<TABLE_LIST> *tlist,
1723+
TABLE_LIST *tr,
16981724
uint table_adjust)
16991725
{
1700-
List_iterator<TABLE_LIST> it(*tlist);
1701-
TABLE_LIST *table;
1702-
while ((table= it++))
1726+
if (tr->is_merged())
17031727
{
1704-
if (table->is_merged())
1728+
// Update select list of merged derived tables:
1729+
for (Field_translator *transl= tr->field_translation;
1730+
transl < tr->field_translation_end;
1731+
transl++)
17051732
{
1706-
// Update select list of merged derived tables:
1707-
for (Field_translator *transl= table->field_translation;
1708-
transl < table->field_translation_end;
1709-
transl++)
1710-
{
1711-
DBUG_ASSERT(transl->item->fixed);
1712-
transl->item->fix_after_pullout(parent_select, removed_select);
1713-
}
1714-
// Update used table info for the WHERE clause of the derived table
1715-
DBUG_ASSERT(!table->derived_where_cond ||
1716-
table->derived_where_cond->fixed);
1717-
if (table->derived_where_cond)
1718-
table->derived_where_cond->fix_after_pullout(parent_select,
1719-
removed_select);
1733+
DBUG_ASSERT(transl->item->fixed);
1734+
transl->item->fix_after_pullout(parent_select, removed_select);
17201735
}
1721-
if (table->join_cond() && table->join_cond()->fixed)
1722-
table->join_cond()->fix_after_pullout(parent_select, removed_select);
1723-
if (table->nested_join)
1724-
{
1725-
// In case a derived table is merged-in, these fields need adjustment:
1726-
table->nested_join->sj_corr_tables<<= table_adjust;
1727-
table->nested_join->sj_depends_on<<= table_adjust;
1736+
// Update used table info for the WHERE clause of the derived table
1737+
DBUG_ASSERT(!tr->derived_where_cond ||
1738+
tr->derived_where_cond->fixed);
1739+
if (tr->derived_where_cond)
1740+
tr->derived_where_cond->fix_after_pullout(parent_select,
1741+
removed_select);
1742+
}
17281743

1729-
fix_tables_after_pullout(parent_select, removed_select,
1730-
&table->nested_join->join_list, table_adjust);
1731-
}
1744+
/*
1745+
If join_cond() is fixed, it contains a join condition from a subquery
1746+
that has already been resolved. Call fix_after_pullout() to update
1747+
used table information since table numbers may have changed.
1748+
If join_cond() is not fixed, it contains a condition that was generated
1749+
in the derived table merge operation, which will be fixed later.
1750+
This condition may also contain a fixed part, but this is saved as
1751+
derived_where_cond and is pulled out explicitly.
1752+
*/
1753+
if (tr->join_cond() && tr->join_cond()->fixed)
1754+
tr->join_cond()->fix_after_pullout(parent_select, removed_select);
1755+
1756+
if (tr->nested_join)
1757+
{
1758+
// In case a derived table is merged-in, these fields need adjustment:
1759+
tr->nested_join->sj_corr_tables<<= table_adjust;
1760+
tr->nested_join->sj_depends_on<<= table_adjust;
1761+
1762+
List_iterator<TABLE_LIST> it(tr->nested_join->join_list);
1763+
TABLE_LIST *child;
1764+
while ((child= it++))
1765+
fix_tables_after_pullout(parent_select, removed_select, child,
1766+
table_adjust);
17321767
}
17331768
}
17341769

@@ -2123,8 +2158,7 @@ SELECT_LEX::convert_subquery_to_semijoin(Item_exists_subselect *subq_pred)
21232158
sj_nest->sj_cond()->fix_after_pullout(this, subq_select);
21242159

21252160
// Update table map for semi-join nest's WHERE condition and join conditions
2126-
fix_tables_after_pullout(this, subq_select,
2127-
&sj_nest->nested_join->join_list, 0);
2161+
fix_tables_after_pullout(this, subq_select, sj_nest, 0);
21282162

21292163
//TODO fix QT_
21302164
DBUG_EXECUTE("where",
@@ -2331,7 +2365,7 @@ bool SELECT_LEX::merge_derived(THD *thd, TABLE_LIST *derived_table)
23312365
remap_tables(thd);
23322366

23332367
// Update table info of referenced expressions after query block is merged
2334-
fix_tables_after_pullout(this, derived_select, &top_join_list, table_adjust);
2368+
fix_tables_after_pullout(this, derived_select, derived_table, table_adjust);
23352369

23362370
if (derived_select->is_ordered())
23372371
{

0 commit comments

Comments
 (0)