Commit 5454f706 authored by Yuchen Pei's avatar Yuchen Pei Committed by Sergei Petrunia

MDEV-22534 Fix self-referencing Item_direct_view_ref

The loop is created in substitute_for_best_equal_field().

It calls Item_ref::transform (for the Item_direct_view_ref object)
which calls:

  Item *new_item= (*ref)->transform(thd, transformer, arg); // TRANSFORM-CALL

where transformer is Item::replace_equal_field().

Consider *vr1->ref==field, where vr1 is an Item_direct_view_ref, and
field is an Item_field that erroneously participates in a multiple
equality. If field->replace_equal_field() returns vr2, another
Item_direct_view_ref, with vr2->ref == vr1->ref. vr1->transform()
will, after the call on field->transform() that returns vr2 for
replacement, update its ref to point to vr2, so now *vr1->ref==vr2,
and since vr2->ref==vr1->ref, this gives us *vr2->ref == vr2, a self
reference.

This can be generalised to nested Item_direct_view_refs too, if
field->transform() returns an Item_direct_view_ref that has the same
ref field as the ref field of any Item_direct_view_ref in the chain.

On to the fix. If we maintain the rule that

  Objects inside Item_direct_view_ref do not participate in multiple
  equalities.

then the TRANSFORM-CALL will have new_item == *ref. That is, the
transform will be a no-op and no loop will be created.

The participation of multiple equalities is set during call to
Item::propagate_equal_fields(). This commit fixes
Item_ref::propagate_equal_fields() so that the rule is not violated
when a (non-Item_direct_view_ref) Item_ref points to an
Item_direct_view_ref:

Item_ref->Item_ref->...->Item_direct_view_ref->Item_direct_view_ref->...->Item_field

More specifically, we now delegate to the dereferenced
item (i.e. *ref) in such calls, rather than directly to the underlying
Item_field. By doing this, the call to propagate_equal_fields() on
toplevel Item_direct_view_ref will make it participate in the multiple
equality, instead of any of its downstream items.
parent 4236b120
......@@ -656,7 +656,7 @@ EXPLAIN
"rows": 21,
"cost": "COST_REPLACED",
"filtered": 100,
"attached_condition": "!(t1.b is not null and <in_optimizer>((t1.a,t1.b),<exists>(subquery#2)))"
"attached_condition": "!(<in_optimizer>((t1.a,t1.b),<exists>(subquery#2)) and t1.b is not null)"
}
}
],
......@@ -716,7 +716,7 @@ EXPLAIN
"rows": 21,
"cost": "COST_REPLACED",
"filtered": 100,
"attached_condition": "!(t1.b is not null and <in_optimizer>((t1.a,t1.b),<exists>(subquery#2)))"
"attached_condition": "!(<in_optimizer>((t1.a,t1.b),<exists>(subquery#2)) and t1.b is not null)"
}
}
],
......
......@@ -338,7 +338,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
3 MATERIALIZED t3 ALL NULL NULL NULL NULL 2 100.00 Using where
Warnings:
Note 1276 Field or reference 'test.t2.b' of SELECT #3 was resolved in SELECT #2
Note 1003 /* select#1 */ select (/* select#2 */ select 1 from dual where !(1 is not null and <in_optimizer>(1,1 in (<primary_index_lookup>(1 in <temporary table> on distinct_key where 1 = `<subquery3>`.`c`))))) AS `( SELECT b FROM t2 WHERE NOT EXISTS ( SELECT c FROM t3 WHERE c = b ) )` from `test`.`t1`
Note 1003 /* select#1 */ select (/* select#2 */ select 1 from dual where !(<in_optimizer>(1,1 in (<primary_index_lookup>(1 in <temporary table> on distinct_key where 1 = `<subquery3>`.`c`))) and 1 is not null)) AS `( SELECT b FROM t2 WHERE NOT EXISTS ( SELECT c FROM t3 WHERE c = b ) )` from `test`.`t1`
SELECT ( SELECT b FROM t2 WHERE NOT EXISTS ( SELECT c FROM t3 WHERE c = b ) ) FROM t1;
( SELECT b FROM t2 WHERE NOT EXISTS ( SELECT c FROM t3 WHERE c = b ) )
1
......@@ -352,7 +352,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
3 MATERIALIZED t3 ALL NULL NULL NULL NULL 2 100.00 Using where
Warnings:
Note 1276 Field or reference 'test.t2.b' of SELECT #3 was resolved in SELECT #2
Note 1003 /* select#1 */ select (/* select#2 */ select 1 from dual where !(1 is not null and <in_optimizer>(1,1 in (<primary_index_lookup>(1 in <temporary table> on distinct_key where 1 = `<subquery3>`.`c`))))) AS `( SELECT b FROM t2 WHERE NOT EXISTS ( SELECT c FROM t3 WHERE c = b ) )` from `test`.`t1`
Note 1003 /* select#1 */ select (/* select#2 */ select 1 from dual where !(<in_optimizer>(1,1 in (<primary_index_lookup>(1 in <temporary table> on distinct_key where 1 = `<subquery3>`.`c`))) and 1 is not null)) AS `( SELECT b FROM t2 WHERE NOT EXISTS ( SELECT c FROM t3 WHERE c = b ) )` from `test`.`t1`
SELECT ( SELECT b FROM t2 WHERE NOT EXISTS ( SELECT c FROM t3 WHERE c = b ) ) FROM t1;
( SELECT b FROM t2 WHERE NOT EXISTS ( SELECT c FROM t3 WHERE c = b ) )
1
......@@ -576,7 +576,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1
Note 1003 /* select#1 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` where !(`test`.`t1`.`a` is not null and <expr_cache><`test`.`t1`.`a`>(<in_optimizer>(`test`.`t1`.`a`,<exists>(/* select#2 */ select `test`.`t2`.`b` from `test`.`t2` where `test`.`t2`.`b` is not null and <cache>(`test`.`t1`.`a`) = `test`.`t2`.`b`))))
Note 1003 /* select#1 */ select `test`.`t1`.`a` AS `a` from `test`.`t1` where !(<expr_cache><`test`.`t1`.`a`>(<in_optimizer>(`test`.`t1`.`a`,<exists>(/* select#2 */ select `test`.`t2`.`b` from `test`.`t2` where `test`.`t2`.`b` is not null and <cache>(`test`.`t1`.`a`) = `test`.`t2`.`b`))) and `test`.`t1`.`a` is not null)
drop table t1,t2;
set optimizer_switch=default;
set optimizer_switch='exists_to_in=on';
......@@ -796,6 +796,21 @@ INSERT INTO t3 VALUES (4),(6);
SELECT * FROM t1, ( SELECT * FROM t2 ) alias WHERE NOT EXISTS ( SELECT * FROM t3 WHERE c = b ) AND a = b;
a b
drop table t1, t2, t3;
CREATE TABLE t1 ( a INT );
INSERT INTO t1 VALUES (1),(5);
CREATE TABLE t2 ( b INT ) ENGINE=MyISAM;
INSERT INTO t2 VALUES (1);
CREATE TABLE t3 ( c INT );
INSERT INTO t3 VALUES (4),(5);
SELECT *
FROM
t1,
( SELECT * FROM t2 ) alias
WHERE
NOT EXISTS ( SELECT * FROM t3 WHERE c = b ) AND a = b;
a b
1 1
DROP TABLE t1, t2, t3;
#
# MDEV-3906: Server crashes in Dependency_marker::visit_field
# on 2nd execution of PS with exists_to_in and NOT EXISTS subquery
......
......@@ -657,6 +657,24 @@ SELECT * FROM t1, ( SELECT * FROM t2 ) alias WHERE NOT EXISTS ( SELECT * FROM t3
drop table t1, t2, t3;
# A simpler version that could trigger a self-referencing
# Item_direct_view_ref
CREATE TABLE t1 ( a INT );
INSERT INTO t1 VALUES (1),(5);
CREATE TABLE t2 ( b INT ) ENGINE=MyISAM;
INSERT INTO t2 VALUES (1);
CREATE TABLE t3 ( c INT );
INSERT INTO t3 VALUES (4),(5);
SELECT *
FROM
t1,
( SELECT * FROM t2 ) alias
WHERE
NOT EXISTS ( SELECT * FROM t3 WHERE c = b ) AND a = b;
DROP TABLE t1, t2, t3;
--echo #
--echo # MDEV-3906: Server crashes in Dependency_marker::visit_field
--echo # on 2nd execution of PS with exists_to_in and NOT EXISTS subquery
......
......@@ -9388,7 +9388,10 @@ Item *Item_direct_view_ref::propagate_equal_fields(THD *thd,
set_item_equal(field_item->get_item_equal());
field_item->set_item_equal(NULL);
if (item != field_item)
{
DBUG_ASSERT(item->const_item());
return item;
}
return this;
}
......@@ -9396,12 +9399,28 @@ Item *Item_direct_view_ref::propagate_equal_fields(THD *thd,
Item *Item_ref::propagate_equal_fields(THD *thd, const Context &ctx,
COND_EQUAL *cond)
{
Item *field_item= real_item();
if (field_item->type() != FIELD_ITEM)
/*
Don't use the value of real_item() here: *ref may point to an
Item_direct_view_ref, and we must not walk inside it, which could
cause the underlying Item_field to participate in a multiple
equality when it should be the Item_direct_view_ref that
participates.
*/
Item *derefed = *ref;
if (derefed->type() != REF_ITEM && derefed->type() != FIELD_ITEM)
return this;
Item *item= field_item->propagate_equal_fields(thd, ctx, cond);
if (item != field_item)
Item *item= derefed->propagate_equal_fields(thd, ctx, cond);
if (item != derefed)
{
/*
We only get here if we've got a constant. It is not necessary to
wrap it in *this (and convert val_XXX to val_XXX_result() calls).
Return the constant.
*/
DBUG_ASSERT(item->const_item());
return item;
}
return this;
}
......
......@@ -6035,9 +6035,16 @@ class Item_cache_wrapper :public Item_result_field
/*
Class for view fields, the same as Item_direct_ref, but call fix_fields
of reference if it is not called yet
This represents a field of a merged VIEW (or a derived table, or a CTE).
"ref" points to an entry in the VIEW's field_translation table. There can
be multiple Item_direct_view_ref objects pointing to the same place.
Item_direct_view_ref objects have some common properties with Item_field:
- They participate in multiple equalities
- They can be "null-complemented", see null_ref_table.
*/
class Item_direct_view_ref :public Item_direct_ref
{
Item_equal *item_equal;
......@@ -6093,6 +6100,10 @@ class Item_direct_view_ref :public Item_direct_ref
void set_item_equal(Item_equal *item_eq) override { item_equal= item_eq; }
Item_equal *find_item_equal(COND_EQUAL *cond_equal) override;
Item* propagate_equal_fields(THD *, const Context &, COND_EQUAL *) override;
/*
Return an item in the multiple equality the Item participates, or
the Item itself if it does not participate in any.
*/
Item *replace_equal_field(THD *thd, uchar *arg) override;
table_map used_tables() const override;
void update_used_tables() override;
......
......@@ -3321,11 +3321,11 @@ bool Item_exists_subselect::exists2in_create_or_update_in(
outer_expr IS NOT NULL so that the exists2in / decorrelate-in
transformation is correct, i.e. new IN subquery evals to the same
value as the as the old EXISTS/IN subquery.
@detail
Example transformation, offset=2:
NOT ( (orig_expr1, orig_expr2, added_expr1, added_expr2) IN
NOT ( (orig_expr1, orig_expr2, added_expr1, added_expr2) IN
(SELECT ... FROM ...))
->
......@@ -3390,20 +3390,12 @@ bool Item_exists_subselect::exists2in_and_is_not_nulls(uint offset,
}
}
}
if (and_list->elements > 1)
if (and_list->elements >= 1)
{
and_list->push_front(exp, thd->mem_root);
exp= new (thd->mem_root) Item_cond_and(thd, *and_list);
}
/*
If we do not single out this case, and merge it with the >1
case, we would get an infinite loop in resolving
Item_direct_view_ref::real_item() like in MDEV-3881 when
left_exp has scalar type
*/
else if (and_list->elements == 1)
exp= new (thd->mem_root) Item_cond_and(thd, and_list->head(), exp);
upper_not->arguments()[0]= exp;
if (exp->fix_fields_if_needed(thd, upper_not->arguments()))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment