Skip to content

Commit

Permalink
Fixed the bug mdev-12563.
Browse files Browse the repository at this point in the history
The bug happened when the specification of a recursive CTE had
no recursive references at the top level of the specification.
In this case the regular processing of derived table references
of the select containing a non-recursive reference to this
recursive CTE misses handling the specification unit.
At the preparation stage any non-recursive reference to a
recursive CTE must be handled after the preparation of the
specification unit for this CTE. So we have to force this
preparation when regular handling of derived tables does not
do it.
  • Loading branch information
igorbabaev committed Apr 29, 2017
1 parent 4b24467 commit 7a29ca2
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 6 deletions.
62 changes: 62 additions & 0 deletions mysql-test/r/cte_recursive.result
Original file line number Diff line number Diff line change
Expand Up @@ -2813,3 +2813,65 @@ SELECT c2 FROM t, cte
COUNT(*)
4
DROP TABLE t;
#
# mdev-12563: no recursive references on the top level of the CTE spec
#
CREATE TABLE t (i int);
INSERT INTO t VALUES (3), (1),(2);
SET standard_compliant_cte=0;
WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t WHERE i NOT IN ( SELECT * FROM cte )
) SELECT * FROM cte;
f
3
1
2
WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t WHERE i NOT IN ( SELECT * FROM cte WHERE i < 2 )
UNION
SELECT i FROM t WHERE i NOT IN ( SELECT * FROM cte WHERE i > 2 )
) SELECT * FROM cte;
f
3
1
2
WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t
WHERE i NOT IN ( SELECT * FROM cte WHERE i < 2
UNION
SELECT * FROM cte WHERE i > 2)
) SELECT * FROM cte;
f
3
1
2
WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t
WHERE i NOT IN ( SELECT * FROM t
WHERE i IN ( SELECT * FROM cte ) GROUP BY i )
) SELECT * FROM cte;
f
3
1
2
WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t WHERE i NOT IN ( SELECT * FROM cte )
UNION
SELECT * FROM cte WHERE f > 2
) SELECT * FROM cte;
f
3
1
2
set standard_compliant_cte=default;
DROP TABLE t;
52 changes: 52 additions & 0 deletions mysql-test/t/cte_recursive.test
Original file line number Diff line number Diff line change
Expand Up @@ -1876,3 +1876,55 @@ eval $q2;

DROP TABLE t;

--echo #
--echo # mdev-12563: no recursive references on the top level of the CTE spec
--echo #

CREATE TABLE t (i int);
INSERT INTO t VALUES (3), (1),(2);

SET standard_compliant_cte=0;

WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t WHERE i NOT IN ( SELECT * FROM cte )
) SELECT * FROM cte;

WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t WHERE i NOT IN ( SELECT * FROM cte WHERE i < 2 )
UNION
SELECT i FROM t WHERE i NOT IN ( SELECT * FROM cte WHERE i > 2 )
) SELECT * FROM cte;

WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t
WHERE i NOT IN ( SELECT * FROM cte WHERE i < 2
UNION
SELECT * FROM cte WHERE i > 2)
) SELECT * FROM cte;

WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t
WHERE i NOT IN ( SELECT * FROM t
WHERE i IN ( SELECT * FROM cte ) GROUP BY i )
) SELECT * FROM cte;

WITH RECURSIVE cte(f) AS (
SELECT i FROM t
UNION
SELECT i FROM t WHERE i NOT IN ( SELECT * FROM cte )
UNION
SELECT * FROM cte WHERE f > 2
) SELECT * FROM cte;

set standard_compliant_cte=default;

DROP TABLE t;

60 changes: 59 additions & 1 deletion sql/sql_cte.cc
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ bool With_clause::check_dependencies()

/*
Mark those elements where tables are defined with direct or indirect
make recursion.
recursion.
*/
for (With_element *with_elem= with_list.first;
with_elem;
Expand Down Expand Up @@ -342,6 +342,8 @@ void With_element::check_dependencies_in_select(st_select_lex *sl,
tbl->with_internal_reference_map= get_elem_map();
if (in_subq)
sq_dep_map|= tbl->with->get_elem_map();
else
top_level_dep_map|= tbl->with->get_elem_map();
}
}
/* Now look for the dependencies in the subqueries of sl */
Expand All @@ -351,6 +353,53 @@ void With_element::check_dependencies_in_select(st_select_lex *sl,
}


/**
@brief
Find a recursive reference to this with element in subqueries of a select
@param sel The select in whose subqueries the reference
to be looked for
@details
The function looks for a recursive reference to this with element in
subqueries of select sl. When the first such reference is found
it is returned as the result.
The function assumes that the identification of all CTE references
has been performed earlier.
@retval
Pointer to the found recursive reference if the search succeeded
NULL - otherwise
*/

TABLE_LIST *With_element::find_first_sq_rec_ref_in_select(st_select_lex *sel)
{
TABLE_LIST *rec_ref= NULL;
st_select_lex_unit *inner_unit= sel->first_inner_unit();
for (; inner_unit; inner_unit= inner_unit->next_unit())
{
st_select_lex *sl= inner_unit->first_select();
for (; sl; sl= sl->next_select())
{
for (TABLE_LIST *tbl= sl->table_list.first; tbl; tbl= tbl->next_local)
{
if (tbl->derived || tbl->nested_join)
continue;
if (tbl->with && tbl->with->owner== this->owner &&
(tbl->with_internal_reference_map & mutually_recursive))
{
rec_ref= tbl;
return rec_ref;
}
}
if ((rec_ref= find_first_sq_rec_ref_in_select(sl)))
return rec_ref;
}
}
return 0;
}


/**
@brief
Find the dependencies of this element on its siblings in a unit
Expand Down Expand Up @@ -602,6 +651,10 @@ void With_clause::move_anchors_ahead()
@details
If the specification of this with element contains anchors the method
moves them at the very beginning of the specification.
Additionally for the other selects of the specification if none of them
contains a recursive reference to this with element or a mutually recursive
one the method looks for the first such reference in the first recursive
select and set a pointer to it in this->sq_rec_ref.
*/

void With_element::move_anchors_ahead()
Expand All @@ -618,6 +671,11 @@ void With_element::move_anchors_ahead()
sl->move_node(new_pos);
new_pos= sl->next_select();
}
else if (!sq_rec_ref && no_rec_ref_on_top_level())
{
sq_rec_ref= find_first_sq_rec_ref_in_select(sl);
DBUG_ASSERT(sq_rec_ref != NULL);
}
last_sl= sl;
}
if (spec->union_distinct)
Expand Down
20 changes: 19 additions & 1 deletion sql/sql_cte.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ class With_element : public Sql_alloc
table_map work_dep_map; // dependency map used for work
/* Dependency map of with elements mutually recursive with this with element */
table_map mutually_recursive;
/*
Dependency map built only for the top level references i.e. for those that
are encountered in from lists of the selects of the specification unit
*/
table_map top_level_dep_map;
/*
Points to a recursive reference in subqueries.
Used only for specifications without recursive references on the top level.
*/
TABLE_LIST *sq_rec_ref;
/*
The next with element from the circular chain of the with elements
mutually recursive with this with element.
Expand Down Expand Up @@ -118,7 +128,7 @@ class With_element : public Sql_alloc
stage and is used at the execution stage.
*/
select_union_recursive *rec_result;

/* List of Item_subselects containing recursive references to this CTE */
SQL_I_List<Item_subselect> sq_with_rec_ref;

Expand All @@ -127,6 +137,7 @@ class With_element : public Sql_alloc
st_select_lex_unit *unit)
: next(NULL), base_dep_map(0), derived_dep_map(0),
sq_dep_map(0), work_dep_map(0), mutually_recursive(0),
top_level_dep_map(0), sq_rec_ref(NULL),
next_mutually_recursive(NULL), references(0),
query_name(name), column_list(list), spec(unit),
is_recursive(false), with_anchor(false),
Expand Down Expand Up @@ -154,6 +165,8 @@ class With_element : public Sql_alloc
bool check_dependency_on(With_element *with_elem)
{ return base_dep_map & with_elem->get_elem_map(); }

TABLE_LIST *find_first_sq_rec_ref_in_select(st_select_lex *sel);

bool set_unparsed_spec(THD *thd, char *spec_start, char *spec_end);

st_select_lex_unit *clone_parsed_spec(THD *thd, TABLE_LIST *with_table);
Expand All @@ -177,11 +190,16 @@ class With_element : public Sql_alloc
bool contains_sq_with_recursive_reference()
{ return sq_dep_map & mutually_recursive; }

bool no_rec_ref_on_top_level()
{ return !(top_level_dep_map & mutually_recursive); }

table_map get_mutually_recursive() { return mutually_recursive; }

With_element *get_next_mutually_recursive()
{ return next_mutually_recursive; }

TABLE_LIST *get_sq_rec_ref() { return sq_rec_ref; }

bool is_anchor(st_select_lex *sel);

void move_anchors_ahead();
Expand Down
17 changes: 17 additions & 0 deletions sql/sql_derived.cc
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,23 @@ bool mysql_derived_prepare(THD *thd, LEX *lex, TABLE_LIST *derived)

SELECT_LEX *first_select= unit->first_select();

if (derived->is_recursive_with_table() &&
!derived->is_with_table_recursive_reference() &&
!derived->with->rec_result && derived->with->get_sq_rec_ref())
{
/*
This is a non-recursive reference to a recursive CTE whose
specification unit has not been prepared at the regular processing of
derived table references. This can happen only in the case when
the specification unit has no recursive references at the top level.
Force the preparation of the specification unit. Use a recursive
table reference from a subquery for this.
*/
DBUG_ASSERT(derived->with->get_sq_rec_ref());
if (mysql_derived_prepare(lex->thd, lex, derived->with->get_sq_rec_ref()))
DBUG_RETURN(TRUE);
}

if (unit->prepared && derived->is_recursive_with_table() &&
!derived->table)
{
Expand Down
4 changes: 0 additions & 4 deletions sql/sql_derived.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ bool mysql_derived_cleanup(THD *thd, LEX *lex, TABLE_LIST *derived);

Item *delete_not_needed_parts(THD *thd, Item *cond);

#if 0
bool pushdown_cond_for_derived(THD *thd, Item **cond, TABLE_LIST *derived);
#else
bool pushdown_cond_for_derived(THD *thd, Item *cond, TABLE_LIST *derived);
#endif

#endif /* SQL_DERIVED_INCLUDED */

0 comments on commit 7a29ca2

Please sign in to comment.