Skip to content

Commit

Permalink
Bug#32547812 SIG11 IN QUERY_BLOCK::NEXT_QUERY_BLOCK|SQL/SQL_LEX.H
Browse files Browse the repository at this point in the history
This error happens for queries such as:

SELECT ( SELECT 1 FROM t1 ) AS a,
  ( SELECT a FROM ( SELECT x FROM t1 ORDER BY a ) AS d1 );

Query_block::prepare() for query block #4 (corresponding to the 4th
SELECT in the query above) calls setup_order() which again calls
find_order_in_list(). That function replaces an Item_ident for 'a' in
Query_block.order_list with an Item_ref pointing to query block #2.
Then Query_block::merge_derived() merges query block #4 into query
block #3. The Item_ref mentioned above is then moved to the order_list
of query block #3.

In the next step, find_order_in_list() is called for query block #3.
At this point, 'a' in the select list has been resolved to another
Item_ref, also pointing to query block #2. find_order_in_list()
detects that the Item_ref in the order_list is equivalent to the
Item_ref in the select list, and therefore decides to replace the
former with the latter. Then find_order_in_list() calls
Item::clean_up_after_removal() recursively (via Item::walk()) for the
order_list Item_ref (since that is no longer needed).

When calling clean_up_after_removal(), no
Cleanup_after_removal_context object is passed. This is the actual
error, as there should be a context pointing to query block #3 that
ensures that clean_up_after_removal() only purge Item_subselect.unit
if both of the following conditions hold:

1) The Item_subselect should not be in any of the Item trees in the
   select list of query block #3.

2) Item_subselect.unit should be a descendant of query block #3.

These conditions ensure that we only purge Item_subselect.unit if we
are sure that it is not needed elsewhere. But without the right
context, query block #2 gets purged even if it is used in the select
lists of query blocks #1 and #3.

The fix is to pass a context (for query block #3) to clean_up_after_removal().
Both of the above conditions then become false, and Item_subselect.unit is
not purged. As an additional shortcut, find_order_in_list() will not call
clean_up_after_removal() if real_item() of the order item and the select
list item are identical.

In addition, this commit changes clean_up_after_removal() so that it
requires the context to be non-null, to prevent similar errors. It
also simplifies Item_sum::clean_up_after_removal() by removing window
functions unconditionally (and adds a corresponding test case).

Change-Id: I449be15d369dba97b23900d1a9742e9f6bad4355
  • Loading branch information
Jan Wedvik committed Sep 8, 2021
1 parent c2339a1 commit 1d8e28a
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 23 deletions.
23 changes: 18 additions & 5 deletions sql/item.h
Original file line number Diff line number Diff line change
Expand Up @@ -2625,22 +2625,35 @@ class Item : public Parse_tree_node {
*/
virtual void bind_fields() {}

struct Cleanup_after_removal_context {
/**
Context object for (functions that override)
Item::clean_up_after_removal().
*/
class Cleanup_after_removal_context final {
public:
Cleanup_after_removal_context(Query_block *root) : m_root(root) {
assert(root != nullptr);
}

Query_block *get_root() { return m_root; }

private:
/**
Pointer to Cleanup_after_removal_context containing from which
select the walk started, i.e., the Query_block that contained the clause
that was removed.
*/
Query_block *m_root;

Cleanup_after_removal_context(Query_block *root) : m_root(root) {}
Query_block *const m_root;
};
/**
Clean up after removing the item from the item tree.
param arg pointer to a Cleanup_after_removal_context object
*/
virtual bool clean_up_after_removal(uchar *) { return false; }
virtual bool clean_up_after_removal(uchar *arg) {
assert(arg != nullptr);
return false;
}

/**
Propagate components that use referenced columns from derived tables.
Expand Down
13 changes: 6 additions & 7 deletions sql/item_subselect.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2653,15 +2653,14 @@ bool Item_subselect::clean_up_after_removal(uchar *arg) {
in the SELECT list via an alias.
In that case, do not remove this subselect.
*/
auto *ctx = pointer_cast<Cleanup_after_removal_context *>(arg);
Query_block *root = nullptr;
Query_block *const root =
pointer_cast<Cleanup_after_removal_context *>(arg)->get_root();

if (ctx != nullptr) {
if ((ctx->m_root->resolve_place != Query_block::RESOLVE_SELECT_LIST) &&
ctx->m_root->is_in_select_list(this))
return false;
root = ctx->m_root;
if ((root->resolve_place != Query_block::RESOLVE_SELECT_LIST) &&
root->is_in_select_list(this)) {
return false;
}

Query_block *sl = unit->outer_query_block();

// Notify flatten_subqueries() that subquery has been removed.
Expand Down
16 changes: 7 additions & 9 deletions sql/item_sum.cc
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ bool Item_sum::resolve_type(THD *thd) {
@see Item_cond::remove_const_cond()
*/
bool Item_sum::clean_up_after_removal(uchar *arg) {
assert(arg != nullptr);
/*
Don't do anything if
1) this is an unresolved item (This may happen if an
Expand All @@ -535,15 +536,12 @@ bool Item_sum::clean_up_after_removal(uchar *arg) {

if (m_window) {
// Cleanup the reference for this window function from m_functions
auto *ctx = pointer_cast<Cleanup_after_removal_context *>(arg);
if (ctx != nullptr) {
List_iterator<Item_sum> li(m_window->functions());
Item *item = nullptr;
while ((item = li++)) {
if (item == this) {
li.remove();
break;
}
List_iterator<Item_sum> li(m_window->functions());
Item *item = nullptr;
while ((item = li++)) {
if (item == this) {
li.remove();
break;
}
}
} else {
Expand Down
8 changes: 6 additions & 2 deletions sql/sql_resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4333,10 +4333,14 @@ bool find_order_in_list(THD *thd, Ref_item_array ref_item_array,
cleaning up), but it may contain subqueries that should be
unlinked.
*/
if ((*order->item)->real_item() != *select_item)
if ((*order->item)->real_item() != (*select_item)->real_item()) {
Item::Cleanup_after_removal_context ctx(
thd->lex->current_query_block());

(*order->item)
->walk(&Item::clean_up_after_removal, enum_walk::SUBQUERY_POSTFIX,
nullptr);
pointer_cast<uchar *>(&ctx));
}
order->item = &ref_item_array[counter];
order->in_field_list = true;
if (resolution == RESOLVED_AGAINST_ALIAS && from_field == not_found_field)
Expand Down

0 comments on commit 1d8e28a

Please sign in to comment.