Commit 9be915e7 authored by timour/tkatchaounov@lapi.mysql.com's avatar timour/tkatchaounov@lapi.mysql.com
Browse files

Fix for BUG#32694 "NOT NULL table field in a subquery produces invalid results"

  
The problem was that when convert_constant_item is called for subqueries,
this happens when we already started executing the top-level query, and
the field argument of convert_constant_item pointed to a valid table row.
In turn convert_constant_item used the field buffer to compute the value
of its item argument. This copied the item's value into the field,
and made equalities with outer references always true.
  
The fix saves/restores the original field's value when it belongs to an
outer table.
parent 987ec3f3
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -505,4 +505,59 @@ select sum(a) from t1 group by convert(a, datetime);
sum(a)
NULL
drop table t1;
create table t1 (id int(10) not null, cur_date datetime not null);
create table t2 (id int(10) not null, cur_date date not null);
insert into t1 (id, cur_date) values (1, '2007-04-25 18:30:22');
insert into t2 (id, cur_date) values (1, '2007-04-25');
explain extended
select * from t1
where id in (select id from t1 as x1 where (t1.cur_date is null));
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Impossible WHERE noticed after reading const tables
2	DEPENDENT SUBQUERY	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Impossible WHERE
Warnings:
Note	1276	Field or reference 'test.t1.cur_date' of SELECT #2 was resolved in SELECT #1
Note	1003	select '1' AS `id`,'2007-04-25 18:30:22' AS `cur_date` from `test`.`t1` where <in_optimizer>('1',<exists>(select 1 AS `Not_used` from `test`.`t1` `x1` where 0))
select * from t1
where id in (select id from t1 as x1 where (t1.cur_date is null));
id	cur_date
explain extended
select * from t2
where id in (select id from t2 as x1 where (t2.cur_date is null));
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Impossible WHERE noticed after reading const tables
2	DEPENDENT SUBQUERY	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Impossible WHERE
Warnings:
Note	1276	Field or reference 'test.t2.cur_date' of SELECT #2 was resolved in SELECT #1
Note	1003	select '1' AS `id`,'2007-04-25' AS `cur_date` from `test`.`t2` where <in_optimizer>('1',<exists>(select 1 AS `Not_used` from `test`.`t2` `x1` where 0))
select * from t2
where id in (select id from t2 as x1 where (t2.cur_date is null));
id	cur_date
insert into t1 (id, cur_date) values (2, '2007-04-26 18:30:22');
insert into t2 (id, cur_date) values (2, '2007-04-26');
explain extended
select * from t1
where id in (select id from t1 as x1 where (t1.cur_date is null));
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	2	Using where
2	DEPENDENT SUBQUERY	x1	ALL	NULL	NULL	NULL	NULL	2	Using where
Warnings:
Note	1276	Field or reference 'test.t1.cur_date' of SELECT #2 was resolved in SELECT #1
Note	1003	select `test`.`t1`.`id` AS `id`,`test`.`t1`.`cur_date` AS `cur_date` from `test`.`t1` where <in_optimizer>(`test`.`t1`.`id`,<exists>(select 1 AS `Not_used` from `test`.`t1` `x1` where ((`test`.`t1`.`cur_date` = 0) and (<cache>(`test`.`t1`.`id`) = `test`.`x1`.`id`))))
select * from t1
where id in (select id from t1 as x1 where (t1.cur_date is null));
id	cur_date
explain extended
select * from t2
where id in (select id from t2 as x1 where (t2.cur_date is null));
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	2	Using where
2	DEPENDENT SUBQUERY	x1	ALL	NULL	NULL	NULL	NULL	2	Using where
Warnings:
Note	1276	Field or reference 'test.t2.cur_date' of SELECT #2 was resolved in SELECT #1
Note	1003	select `test`.`t2`.`id` AS `id`,`test`.`t2`.`cur_date` AS `cur_date` from `test`.`t2` where <in_optimizer>(`test`.`t2`.`id`,<exists>(select 1 AS `Not_used` from `test`.`t2` `x1` where ((`test`.`t2`.`cur_date` = 0) and (<cache>(`test`.`t2`.`id`) = `test`.`x1`.`id`))))
select * from t2
where id in (select id from t2 as x1 where (t2.cur_date is null));
id	cur_date
drop table t1,t2;
End of 5.0 tests
+37 −0
Original line number Diff line number Diff line
@@ -338,4 +338,41 @@ insert into t1 values (), (), ();
select sum(a) from t1 group by convert(a, datetime);
drop table t1;

#
# Bug #32694: NOT NULL table field in a subquery produces invalid results
#
create table t1 (id int(10) not null, cur_date datetime not null);
create table t2 (id int(10) not null, cur_date date not null);
insert into t1 (id, cur_date) values (1, '2007-04-25 18:30:22');
insert into t2 (id, cur_date) values (1, '2007-04-25');

explain extended
select * from t1
where id in (select id from t1 as x1 where (t1.cur_date is null));
select * from t1
where id in (select id from t1 as x1 where (t1.cur_date is null));

explain extended
select * from t2
where id in (select id from t2 as x1 where (t2.cur_date is null));
select * from t2
where id in (select id from t2 as x1 where (t2.cur_date is null));

insert into t1 (id, cur_date) values (2, '2007-04-26 18:30:22');
insert into t2 (id, cur_date) values (2, '2007-04-26');

explain extended
select * from t1
where id in (select id from t1 as x1 where (t1.cur_date is null));
select * from t1
where id in (select id from t1 as x1 where (t1.cur_date is null));

explain extended
select * from t2
where id in (select id from t2 as x1 where (t2.cur_date is null));
select * from t2
where id in (select id from t2 as x1 where (t2.cur_date is null));

drop table t1,t2;

--echo End of 5.0 tests
+41 −26
Original line number Diff line number Diff line
@@ -24,7 +24,8 @@
#include <m_ctype.h>
#include "sql_select.h"

static bool convert_constant_item(THD *thd, Field *field, Item **item);
static bool convert_constant_item(THD *thd, Item_field *field_item,
                                  Item **item);

static Item_result item_store_type(Item_result a, Item *item,
                                   my_bool unsigned_flag)
@@ -317,7 +318,7 @@ longlong Item_func_nop_all::val_int()
  SYNOPSIS
    convert_constant_item()
    thd             thread handle
    field           item will be converted using the type of this field
    field_item      item will be converted using the type of this field
    item  [in/out]  reference to the item to convert

  DESCRIPTION
@@ -340,30 +341,46 @@ longlong Item_func_nop_all::val_int()
  1	Item was replaced with an integer version of the item
*/

static bool convert_constant_item(THD *thd, Field *field, Item **item)
static bool convert_constant_item(THD *thd, Item_field *field_item,
                                  Item **item)
{
  Field *field= field_item->field;
  int result= 0;

  if (!(*item)->with_subselect && (*item)->const_item())
  {
    /* For comparison purposes allow invalid dates like 2000-01-32 */
    ulong orig_sql_mode= thd->variables.sql_mode;
    enum_check_fields orig_count_cuted_fields= thd->count_cuted_fields;
    ulonglong orig_field_val; /* original field value if valid */
    thd->variables.sql_mode= (orig_sql_mode & ~MODE_NO_ZERO_DATE) | 
                             MODE_INVALID_DATES;
    thd->count_cuted_fields= CHECK_FIELD_IGNORE;
    /*
      Store the value of the field if it references an outer field because
      the call to save_in_field below overrides that value.
    */
    if (field_item->depended_from)
      orig_field_val= field->val_int();
    if (!(*item)->is_null() && !(*item)->save_in_field(field, 1))
    {
      Item *tmp=new Item_int_with_ref(field->val_int(), *item,
                                      test(field->flags & UNSIGNED_FLAG));
      thd->variables.sql_mode= orig_sql_mode;
      thd->count_cuted_fields= orig_count_cuted_fields;
      if (tmp)
        thd->change_item_tree(item, tmp);
      return 1;					// Item was replaced
      result= 1;					// Item was replaced
    }
    /* Restore the original field value. */
    if (field_item->depended_from)
    {
      result= field->store(orig_field_val, TRUE);
      /* orig_field_val must be a valid value that can be restored back. */
      DBUG_ASSERT(!result);
    }
    thd->variables.sql_mode= orig_sql_mode;
    thd->count_cuted_fields= orig_count_cuted_fields;
  }
  return 0;
  return result;
}


@@ -411,15 +428,14 @@ void Item_bool_func2::fix_length_and_dec()
  thd= current_thd;
  if (!thd->is_context_analysis_only())
  {
    Item *arg_real_item= args[0]->real_item();
    if (arg_real_item->type() == FIELD_ITEM)
    if (args[0]->real_item()->type() == FIELD_ITEM)
    {
      Field *field=((Item_field*) arg_real_item)->field;
      if (field->can_be_compared_as_longlong() &&
          !(arg_real_item->is_datetime() &&
      Item_field *field_item= (Item_field*) (args[0]->real_item());
      if (field_item->field->can_be_compared_as_longlong() &&
          !(field_item->is_datetime() &&
            args[1]->result_type() == STRING_RESULT))
      {
        if (convert_constant_item(thd, field,&args[1]))
        if (convert_constant_item(thd, field_item, &args[1]))
        {
          cmp.set_cmp_func(this, tmp_arg, tmp_arg+1,
                           INT_RESULT);		// Works for all types.
@@ -428,15 +444,14 @@ void Item_bool_func2::fix_length_and_dec()
        }
      }
    }
    arg_real_item= args[1]->real_item();
    if (arg_real_item->type() == FIELD_ITEM)
    if (args[1]->real_item()->type() == FIELD_ITEM)
    {
      Field *field=((Item_field*) arg_real_item)->field;
      if (field->can_be_compared_as_longlong() &&
          !(arg_real_item->is_datetime() &&
      Item_field *field_item= (Item_field*) (args[1]->real_item());
      if (field_item->field->can_be_compared_as_longlong() &&
          !(field_item->is_datetime() &&
            args[0]->result_type() == STRING_RESULT))
      {
        if (convert_constant_item(thd, field,&args[0]))
        if (convert_constant_item(thd, field_item, &args[0]))
        {
          cmp.set_cmp_func(this, tmp_arg, tmp_arg+1,
                           INT_RESULT); // Works for all types.
@@ -1889,16 +1904,16 @@ void Item_func_between::fix_length_and_dec()
           thd->lex->sql_command != SQLCOM_CREATE_VIEW &&
           thd->lex->sql_command != SQLCOM_SHOW_CREATE)
  {
    Field *field=((Item_field*) (args[0]->real_item()))->field;
    if (field->can_be_compared_as_longlong())
    Item_field *field_item= (Item_field*) (args[0]->real_item());
    if (field_item->field->can_be_compared_as_longlong())
    {
      /*
        The following can't be recoded with || as convert_constant_item
        changes the argument
      */
      if (convert_constant_item(thd, field,&args[1]))
      if (convert_constant_item(thd, field_item, &args[1]))
        cmp_type=INT_RESULT;			// Works for all types.
      if (convert_constant_item(thd, field,&args[2]))
      if (convert_constant_item(thd, field_item, &args[2]))
        cmp_type=INT_RESULT;			// Works for all types.
    }
  }
@@ -3491,13 +3506,13 @@ void Item_func_in::fix_length_and_dec()
          thd->lex->sql_command != SQLCOM_SHOW_CREATE &&
          cmp_type != INT_RESULT)
      {
        Field *field= ((Item_field*) (args[0]->real_item()))->field;
        if (field->can_be_compared_as_longlong())
        Item_field *field_item= (Item_field*) (args[0]->real_item());
        if (field_item->field->can_be_compared_as_longlong())
        {
          bool all_converted= TRUE;
          for (arg=args+1, arg_end=args+arg_count; arg != arg_end ; arg++)
          {
            if (!convert_constant_item (thd, field, &arg[0]))
            if (!convert_constant_item (thd, field_item, &arg[0]))
              all_converted= FALSE;
          }
          if (all_converted)