mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 15:50:51 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			2482 lines
		
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2482 lines
		
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (C) 2000 MySQL AB
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
 | |
| 
 | |
| /*
 | |
|    subselect Item
 | |
| 
 | |
| SUBSELECT TODO:
 | |
|    - add function from mysql_select that use JOIN* as parameter to JOIN methods
 | |
|      (sql_select.h/sql_select.cc)
 | |
| */
 | |
| 
 | |
| #ifdef USE_PRAGMA_IMPLEMENTATION
 | |
| #pragma implementation				// gcc: Class implementation
 | |
| #endif
 | |
| 
 | |
| #include "mysql_priv.h"
 | |
| #include "sql_select.h"
 | |
| 
 | |
| inline Item * and_items(Item* cond, Item *item)
 | |
| {
 | |
|   return (cond? (new Item_cond_and(cond, item)) : item);
 | |
| }
 | |
| 
 | |
| Item_subselect::Item_subselect():
 | |
|   Item_result_field(), value_assigned(0), thd(0), substitution(0),
 | |
|   engine(0), old_engine(0), used_tables_cache(0), have_to_be_excluded(0),
 | |
|   const_item_cache(1), engine_changed(0), changed(0), is_correlated(FALSE)
 | |
| {
 | |
|   with_subselect= 1;
 | |
|   reset();
 | |
|   /*
 | |
|     item value is NULL if select_subselect not changed this value
 | |
|     (i.e. some rows will be found returned)
 | |
|   */
 | |
|   null_value= 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_subselect::init(st_select_lex *select_lex,
 | |
| 			  select_subselect *result)
 | |
| {
 | |
|   /*
 | |
|     Please see Item_singlerow_subselect::invalidate_and_restore_select_lex(),
 | |
|     which depends on alterations to the parse tree implemented here.
 | |
|   */
 | |
| 
 | |
|   DBUG_ENTER("Item_subselect::init");
 | |
|   DBUG_PRINT("enter", ("select_lex: 0x%lx", (long) select_lex));
 | |
|   unit= select_lex->master_unit();
 | |
| 
 | |
|   if (unit->item)
 | |
|   {
 | |
|     /*
 | |
|       Item can be changed in JOIN::prepare while engine in JOIN::optimize
 | |
|       => we do not copy old_engine here
 | |
|     */
 | |
|     engine= unit->item->engine;
 | |
|     parsing_place= unit->item->parsing_place;
 | |
|     unit->item->engine= 0;
 | |
|     unit->item= this;
 | |
|     engine->change_result(this, result);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     SELECT_LEX *outer_select= unit->outer_select();
 | |
|     /*
 | |
|       do not take into account expression inside aggregate functions because
 | |
|       they can access original table fields
 | |
|     */
 | |
|     parsing_place= (outer_select->in_sum_expr ?
 | |
|                     NO_MATTER :
 | |
|                     outer_select->parsing_place);
 | |
|     if (unit->is_union())
 | |
|       engine= new subselect_union_engine(unit, result, this);
 | |
|     else
 | |
|       engine= new subselect_single_select_engine(select_lex, result, this);
 | |
|   }
 | |
|   {
 | |
|     SELECT_LEX *upper= unit->outer_select();
 | |
|     if (upper->parsing_place == IN_HAVING)
 | |
|       upper->subquery_in_having= 1;
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| st_select_lex *
 | |
| Item_subselect::get_select_lex()
 | |
| {
 | |
|   return unit->first_select();
 | |
| }
 | |
| 
 | |
| void Item_subselect::cleanup()
 | |
| {
 | |
|   DBUG_ENTER("Item_subselect::cleanup");
 | |
|   Item_result_field::cleanup();
 | |
|   if (old_engine)
 | |
|   {
 | |
|     if (engine)
 | |
|       engine->cleanup();
 | |
|     engine= old_engine;
 | |
|     old_engine= 0;
 | |
|   }
 | |
|   if (engine)
 | |
|     engine->cleanup();
 | |
|   reset();
 | |
|   value_assigned= 0;
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| void Item_singlerow_subselect::cleanup()
 | |
| {
 | |
|   DBUG_ENTER("Item_singlerow_subselect::cleanup");
 | |
|   value= 0; row= 0;
 | |
|   Item_subselect::cleanup();
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| Item_subselect::~Item_subselect()
 | |
| {
 | |
|   delete engine;
 | |
| }
 | |
| 
 | |
| Item_subselect::trans_res
 | |
| Item_subselect::select_transformer(JOIN *join)
 | |
| {
 | |
|   DBUG_ENTER("Item_subselect::select_transformer");
 | |
|   DBUG_RETURN(RES_OK);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_subselect::fix_fields(THD *thd_param, Item **ref)
 | |
| {
 | |
|   char const *save_where= thd_param->where;
 | |
|   uint8 uncacheable;
 | |
|   bool res;
 | |
| 
 | |
|   DBUG_ASSERT(fixed == 0);
 | |
|   engine->set_thd((thd= thd_param));
 | |
| 
 | |
|   if (check_stack_overrun(thd, STACK_MIN_SIZE, (uchar*)&res))
 | |
|     return TRUE;
 | |
| 
 | |
|   res= engine->prepare();
 | |
| 
 | |
|   // all transformation is done (used by prepared statements)
 | |
|   changed= 1;
 | |
| 
 | |
|   if (!res)
 | |
|   {
 | |
|     if (substitution)
 | |
|     {
 | |
|       int ret= 0;
 | |
| 
 | |
|       // did we changed top item of WHERE condition
 | |
|       if (unit->outer_select()->where == (*ref))
 | |
| 	unit->outer_select()->where= substitution; // correct WHERE for PS
 | |
|       else if (unit->outer_select()->having == (*ref))
 | |
| 	unit->outer_select()->having= substitution; // correct HAVING for PS
 | |
| 
 | |
|       (*ref)= substitution;
 | |
|       substitution->name= name;
 | |
|       if (have_to_be_excluded)
 | |
| 	engine->exclude();
 | |
|       substitution= 0;
 | |
|       thd->where= "checking transformed subquery";
 | |
|       if (!(*ref)->fixed)
 | |
| 	ret= (*ref)->fix_fields(thd, ref);
 | |
|       thd->where= save_where;
 | |
|       return ret;
 | |
|     }
 | |
|     // Is it one field subselect?
 | |
|     if (engine->cols() > max_columns)
 | |
|     {
 | |
|       my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
 | |
|       return TRUE;
 | |
|     }
 | |
|     fix_length_and_dec();
 | |
|   }
 | |
|   else
 | |
|     goto err;
 | |
|   
 | |
|   if ((uncacheable= engine->uncacheable()))
 | |
|   {
 | |
|     const_item_cache= 0;
 | |
|     if (uncacheable & UNCACHEABLE_RAND)
 | |
|       used_tables_cache|= RAND_TABLE_BIT;
 | |
|   }
 | |
|   fixed= 1;
 | |
| 
 | |
| err:
 | |
|   thd->where= save_where;
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_subselect::walk(Item_processor processor, bool walk_subquery,
 | |
|                           uchar *argument)
 | |
| {
 | |
| 
 | |
|   if (walk_subquery)
 | |
|   {
 | |
|     for (SELECT_LEX *lex= unit->first_select(); lex; lex= lex->next_select())
 | |
|     {
 | |
|       List_iterator<Item> li(lex->item_list);
 | |
|       Item *item;
 | |
|       ORDER *order;
 | |
| 
 | |
|       if (lex->where && (lex->where)->walk(processor, walk_subquery, argument))
 | |
|         return 1;
 | |
|       if (lex->having && (lex->having)->walk(processor, walk_subquery,
 | |
|                                              argument))
 | |
|         return 1;
 | |
| 
 | |
|       while ((item=li++))
 | |
|       {
 | |
|         if (item->walk(processor, walk_subquery, argument))
 | |
|           return 1;
 | |
|       }
 | |
|       for (order= (ORDER*) lex->order_list.first ; order; order= order->next)
 | |
|       {
 | |
|         if ((*order->item)->walk(processor, walk_subquery, argument))
 | |
|           return 1;
 | |
|       }
 | |
|       for (order= (ORDER*) lex->group_list.first ; order; order= order->next)
 | |
|       {
 | |
|         if ((*order->item)->walk(processor, walk_subquery, argument))
 | |
|           return 1;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return (this->*processor)(argument);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_subselect::exec()
 | |
| {
 | |
|   int res;
 | |
| 
 | |
|   if (thd->net.report_error)
 | |
|   /* Do not execute subselect in case of a fatal error */
 | |
|     return 1;
 | |
| 
 | |
|   res= engine->exec();
 | |
| 
 | |
|   if (engine_changed)
 | |
|   {
 | |
|     engine_changed= 0;
 | |
|     return exec();
 | |
|   }
 | |
|   return (res);
 | |
| }
 | |
| 
 | |
| Item::Type Item_subselect::type() const
 | |
| {
 | |
|   return SUBSELECT_ITEM;
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_subselect::fix_length_and_dec()
 | |
| {
 | |
|   engine->fix_length_and_dec(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| table_map Item_subselect::used_tables() const
 | |
| {
 | |
|   return (table_map) (engine->uncacheable() ? used_tables_cache : 0L);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_subselect::const_item() const
 | |
| {
 | |
|   return const_item_cache;
 | |
| }
 | |
| 
 | |
| Item *Item_subselect::get_tmp_table_item(THD *thd_arg)
 | |
| {
 | |
|   if (!with_sum_func && !const_item())
 | |
|     return new Item_field(result_field);
 | |
|   return copy_or_same(thd_arg);
 | |
| }
 | |
| 
 | |
| void Item_subselect::update_used_tables()
 | |
| {
 | |
|   if (!engine->uncacheable())
 | |
|   {
 | |
|     // did all used tables become static?
 | |
|     if (!(used_tables_cache & ~engine->upper_select_const_tables()))
 | |
|       const_item_cache= 1;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_subselect::print(String *str)
 | |
| {
 | |
|   str->append('(');
 | |
|   engine->print(str);
 | |
|   str->append(')');
 | |
| }
 | |
| 
 | |
| 
 | |
| Item_singlerow_subselect::Item_singlerow_subselect(st_select_lex *select_lex)
 | |
|   :Item_subselect(), value(0)
 | |
| {
 | |
|   DBUG_ENTER("Item_singlerow_subselect::Item_singlerow_subselect");
 | |
|   init(select_lex, new select_singlerow_subselect(this));
 | |
|   maybe_null= 1;
 | |
|   max_columns= UINT_MAX;
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| st_select_lex *
 | |
| Item_singlerow_subselect::invalidate_and_restore_select_lex()
 | |
| {
 | |
|   DBUG_ENTER("Item_singlerow_subselect::invalidate_and_restore_select_lex");
 | |
|   st_select_lex *result= get_select_lex();
 | |
| 
 | |
|   DBUG_ASSERT(result);
 | |
| 
 | |
|   /*
 | |
|     This code restore the parse tree in it's state before the execution of
 | |
|     Item_singlerow_subselect::Item_singlerow_subselect(),
 | |
|     and in particular decouples this object from the SELECT_LEX,
 | |
|     so that the SELECT_LEX can be used with a different flavor
 | |
|     or Item_subselect instead, as part of query rewriting.
 | |
|   */
 | |
|   unit->item= NULL;
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| Item_maxmin_subselect::Item_maxmin_subselect(THD *thd_param,
 | |
|                                              Item_subselect *parent,
 | |
| 					     st_select_lex *select_lex,
 | |
| 					     bool max_arg)
 | |
|   :Item_singlerow_subselect(), was_values(TRUE)
 | |
| {
 | |
|   DBUG_ENTER("Item_maxmin_subselect::Item_maxmin_subselect");
 | |
|   max= max_arg;
 | |
|   init(select_lex, new select_max_min_finder_subselect(this, max_arg));
 | |
|   max_columns= 1;
 | |
|   maybe_null= 1;
 | |
|   max_columns= 1;
 | |
| 
 | |
|   /*
 | |
|     Following information was collected during performing fix_fields()
 | |
|     of Items belonged to subquery, which will be not repeated
 | |
|   */
 | |
|   used_tables_cache= parent->get_used_tables_cache();
 | |
|   const_item_cache= parent->get_const_item_cache();
 | |
| 
 | |
|   /*
 | |
|     this subquery always creates during preparation, so we can assign
 | |
|     thd here
 | |
|   */
 | |
|   thd= thd_param;
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| void Item_maxmin_subselect::cleanup()
 | |
| {
 | |
|   DBUG_ENTER("Item_maxmin_subselect::cleanup");
 | |
|   Item_singlerow_subselect::cleanup();
 | |
| 
 | |
|   /*
 | |
|     By default it is TRUE to avoid TRUE reporting by
 | |
|     Item_func_not_all/Item_func_nop_all if this item was never called.
 | |
| 
 | |
|     Engine exec() set it to FALSE by reset_value_registration() call.
 | |
|     select_max_min_finder_subselect::send_data() set it back to TRUE if some
 | |
|     value will be found.
 | |
|   */
 | |
|   was_values= TRUE;
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_maxmin_subselect::print(String *str)
 | |
| {
 | |
|   str->append(max?"<max>":"<min>", 5);
 | |
|   Item_singlerow_subselect::print(str);
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_singlerow_subselect::reset()
 | |
| {
 | |
|   null_value= 1;
 | |
|   if (value)
 | |
|     value->null_value= 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| Item_subselect::trans_res
 | |
| Item_singlerow_subselect::select_transformer(JOIN *join)
 | |
| {
 | |
|   if (changed)
 | |
|     return RES_OK;
 | |
| 
 | |
|   SELECT_LEX *select_lex= join->select_lex;
 | |
|   Query_arena *arena= thd->stmt_arena;
 | |
|  
 | |
|   if (!select_lex->master_unit()->is_union() &&
 | |
|       !select_lex->table_list.elements &&
 | |
|       select_lex->item_list.elements == 1 &&
 | |
|       !select_lex->item_list.head()->with_sum_func &&
 | |
|       /*
 | |
| 	We cant change name of Item_field or Item_ref, because it will
 | |
| 	prevent it's correct resolving, but we should save name of
 | |
| 	removed item => we do not make optimization if top item of
 | |
| 	list is field or reference.
 | |
| 	TODO: solve above problem
 | |
|       */
 | |
|       !(select_lex->item_list.head()->type() == FIELD_ITEM ||
 | |
| 	select_lex->item_list.head()->type() == REF_ITEM) &&
 | |
|       !join->conds && !join->having &&
 | |
|       /*
 | |
|         switch off this optimization for prepare statement,
 | |
|         because we do not rollback this changes
 | |
|         TODO: make rollback for it, or special name resolving mode in 5.0.
 | |
|       */
 | |
|       !arena->is_stmt_prepare_or_first_sp_execute()
 | |
|       )
 | |
|   {
 | |
| 
 | |
|     have_to_be_excluded= 1;
 | |
|     if (thd->lex->describe)
 | |
|     {
 | |
|       char warn_buff[MYSQL_ERRMSG_SIZE];
 | |
|       sprintf(warn_buff, ER(ER_SELECT_REDUCED), select_lex->select_number);
 | |
|       push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
 | |
| 		   ER_SELECT_REDUCED, warn_buff);
 | |
|     }
 | |
|     substitution= select_lex->item_list.head();
 | |
|     /*
 | |
|       as far as we moved content to upper level, field which depend of
 | |
|       'upper' select is not really dependent => we remove this dependence
 | |
|     */
 | |
|     substitution->walk(&Item::remove_dependence_processor, 0,
 | |
| 		       (uchar *) select_lex->outer_select());
 | |
|     return RES_REDUCE;
 | |
|   }
 | |
|   return RES_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_singlerow_subselect::store(uint i, Item *item)
 | |
| {
 | |
|   row[i]->store(item);
 | |
| }
 | |
| 
 | |
| enum Item_result Item_singlerow_subselect::result_type() const
 | |
| {
 | |
|   return engine->type();
 | |
| }
 | |
| 
 | |
| /* 
 | |
|  Don't rely on the result type to calculate field type. 
 | |
|  Ask the engine instead.
 | |
| */
 | |
| enum_field_types Item_singlerow_subselect::field_type() const
 | |
| {
 | |
|   return engine->field_type();
 | |
| }
 | |
| 
 | |
| void Item_singlerow_subselect::fix_length_and_dec()
 | |
| {
 | |
|   if ((max_columns= engine->cols()) == 1)
 | |
|   {
 | |
|     engine->fix_length_and_dec(row= &value);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (!(row= (Item_cache**) sql_alloc(sizeof(Item_cache*)*max_columns)))
 | |
|       return;
 | |
|     engine->fix_length_and_dec(row);
 | |
|     value= *row;
 | |
|   }
 | |
|   unsigned_flag= value->unsigned_flag;
 | |
|   /*
 | |
|     If there are not tables in subquery then ability to have NULL value
 | |
|     depends on SELECT list (if single row subquery have tables then it
 | |
|     always can be NULL if there are not records fetched).
 | |
|   */
 | |
|   if (engine->no_tables())
 | |
|     maybe_null= engine->may_be_null();
 | |
| }
 | |
| 
 | |
| uint Item_singlerow_subselect::cols()
 | |
| {
 | |
|   return engine->cols();
 | |
| }
 | |
| 
 | |
| bool Item_singlerow_subselect::check_cols(uint c)
 | |
| {
 | |
|   if (c != engine->cols())
 | |
|   {
 | |
|     my_error(ER_OPERAND_COLUMNS, MYF(0), c);
 | |
|     return 1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| bool Item_singlerow_subselect::null_inside()
 | |
| {
 | |
|   for (uint i= 0; i < max_columns ; i++)
 | |
|   {
 | |
|     if (row[i]->null_value)
 | |
|       return 1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| void Item_singlerow_subselect::bring_value()
 | |
| {
 | |
|   exec();
 | |
| }
 | |
| 
 | |
| double Item_singlerow_subselect::val_real()
 | |
| {
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   if (!exec() && !value->null_value)
 | |
|   {
 | |
|     null_value= 0;
 | |
|     return value->val_real();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| longlong Item_singlerow_subselect::val_int()
 | |
| {
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   if (!exec() && !value->null_value)
 | |
|   {
 | |
|     null_value= 0;
 | |
|     return value->val_int();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| String *Item_singlerow_subselect::val_str(String *str)
 | |
| {
 | |
|   if (!exec() && !value->null_value)
 | |
|   {
 | |
|     null_value= 0;
 | |
|     return value->val_str(str);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| my_decimal *Item_singlerow_subselect::val_decimal(my_decimal *decimal_value)
 | |
| {
 | |
|   if (!exec() && !value->null_value)
 | |
|   {
 | |
|     null_value= 0;
 | |
|     return value->val_decimal(decimal_value);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_singlerow_subselect::val_bool()
 | |
| {
 | |
|   if (!exec() && !value->null_value)
 | |
|   {
 | |
|     null_value= 0;
 | |
|     return value->val_bool();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| Item_exists_subselect::Item_exists_subselect(st_select_lex *select_lex):
 | |
|   Item_subselect()
 | |
| {
 | |
|   DBUG_ENTER("Item_exists_subselect::Item_exists_subselect");
 | |
|   bool val_bool();
 | |
|   init(select_lex, new select_exists_subselect(this));
 | |
|   max_columns= UINT_MAX;
 | |
|   null_value= 0; //can't be NULL
 | |
|   maybe_null= 0; //can't be NULL
 | |
|   value= 0;
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_exists_subselect::print(String *str)
 | |
| {
 | |
|   str->append(STRING_WITH_LEN("exists"));
 | |
|   Item_subselect::print(str);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_in_subselect::test_limit(SELECT_LEX_UNIT *unit_arg)
 | |
| {
 | |
|   if (unit_arg->fake_select_lex &&
 | |
|       unit_arg->fake_select_lex->test_limit())
 | |
|     return(1);
 | |
| 
 | |
|   SELECT_LEX *sl= unit_arg->first_select();
 | |
|   for (; sl; sl= sl->next_select())
 | |
|   {
 | |
|     if (sl->test_limit())
 | |
|       return(1);
 | |
|   }
 | |
|   return(0);
 | |
| }
 | |
| 
 | |
| Item_in_subselect::Item_in_subselect(Item * left_exp,
 | |
| 				     st_select_lex *select_lex):
 | |
|   Item_exists_subselect(), optimizer(0), transformed(0),
 | |
|   pushed_cond_guards(NULL), upper_item(0)
 | |
| {
 | |
|   DBUG_ENTER("Item_in_subselect::Item_in_subselect");
 | |
|   left_expr= left_exp;
 | |
|   init(select_lex, new select_exists_subselect(this));
 | |
|   max_columns= UINT_MAX;
 | |
|   maybe_null= 1;
 | |
|   abort_on_null= 0;
 | |
|   reset();
 | |
|   //if test_limit will fail then error will be reported to client
 | |
|   test_limit(select_lex->master_unit());
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| Item_allany_subselect::Item_allany_subselect(Item * left_exp,
 | |
|                                              chooser_compare_func_creator fc,
 | |
| 					     st_select_lex *select_lex,
 | |
| 					     bool all_arg)
 | |
|   :Item_in_subselect(), func_creator(fc), all(all_arg)
 | |
| {
 | |
|   DBUG_ENTER("Item_in_subselect::Item_in_subselect");
 | |
|   left_expr= left_exp;
 | |
|   func= func_creator(all_arg);
 | |
|   init(select_lex, new select_exists_subselect(this));
 | |
|   max_columns= 1;
 | |
|   abort_on_null= 0;
 | |
|   reset();
 | |
|   //if test_limit will fail then error will be reported to client
 | |
|   test_limit(select_lex->master_unit());
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_exists_subselect::fix_length_and_dec()
 | |
| {
 | |
|    decimals= 0;
 | |
|    max_length= 1;
 | |
|    max_columns= engine->cols();
 | |
|   /* We need only 1 row to determine existence */
 | |
|   unit->global_parameters->select_limit= new Item_int((int32) 1);
 | |
| }
 | |
| 
 | |
| double Item_exists_subselect::val_real()
 | |
| {
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
|   return (double) value;
 | |
| }
 | |
| 
 | |
| longlong Item_exists_subselect::val_int()
 | |
| {
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
|   return value;
 | |
| }
 | |
| 
 | |
| String *Item_exists_subselect::val_str(String *str)
 | |
| {
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
|   str->set((ulonglong)value,&my_charset_bin);
 | |
|   return str;
 | |
| }
 | |
| 
 | |
| 
 | |
| my_decimal *Item_exists_subselect::val_decimal(my_decimal *decimal_value)
 | |
| {
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
|   int2my_decimal(E_DEC_FATAL_ERROR, value, 0, decimal_value);
 | |
|   return decimal_value;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_exists_subselect::val_bool()
 | |
| {
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     return 0;
 | |
|   }
 | |
|   return value != 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| double Item_in_subselect::val_real()
 | |
| {
 | |
|   /*
 | |
|     As far as Item_in_subselect called only from Item_in_optimizer this
 | |
|     method should not be used
 | |
|   */
 | |
|   DBUG_ASSERT(0);
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   null_value= 0;
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     null_value= 1;
 | |
|     return 0;
 | |
|   }
 | |
|   if (was_null && !value)
 | |
|     null_value= 1;
 | |
|   return (double) value;
 | |
| }
 | |
| 
 | |
| 
 | |
| longlong Item_in_subselect::val_int()
 | |
| {
 | |
|   /*
 | |
|     As far as Item_in_subselect called only from Item_in_optimizer this
 | |
|     method should not be used
 | |
|   */
 | |
|   DBUG_ASSERT(0);
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   null_value= 0;
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     null_value= 1;
 | |
|     return 0;
 | |
|   }
 | |
|   if (was_null && !value)
 | |
|     null_value= 1;
 | |
|   return value;
 | |
| }
 | |
| 
 | |
| 
 | |
| String *Item_in_subselect::val_str(String *str)
 | |
| {
 | |
|   /*
 | |
|     As far as Item_in_subselect called only from Item_in_optimizer this
 | |
|     method should not be used
 | |
|   */
 | |
|   DBUG_ASSERT(0);
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   null_value= 0;
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     null_value= 1;
 | |
|     return 0;
 | |
|   }
 | |
|   if (was_null && !value)
 | |
|   {
 | |
|     null_value= 1;
 | |
|     return 0;
 | |
|   }
 | |
|   str->set((ulonglong)value, &my_charset_bin);
 | |
|   return str;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_in_subselect::val_bool()
 | |
| {
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   null_value= 0;
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     null_value= 1;
 | |
|     return 0;
 | |
|   }
 | |
|   if (was_null && !value)
 | |
|     null_value= 1;
 | |
|   return value;
 | |
| }
 | |
| 
 | |
| my_decimal *Item_in_subselect::val_decimal(my_decimal *decimal_value)
 | |
| {
 | |
|   /*
 | |
|     As far as Item_in_subselect called only from Item_in_optimizer this
 | |
|     method should not be used
 | |
|   */
 | |
|   DBUG_ASSERT(0);
 | |
|   null_value= 0;
 | |
|   DBUG_ASSERT(fixed == 1);
 | |
|   if (exec())
 | |
|   {
 | |
|     reset();
 | |
|     null_value= 1;
 | |
|     return 0;
 | |
|   }
 | |
|   if (was_null && !value)
 | |
|     null_value= 1;
 | |
|   int2my_decimal(E_DEC_FATAL_ERROR, value, 0, decimal_value);
 | |
|   return decimal_value;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* 
 | |
|   Rewrite a single-column IN/ALL/ANY subselect
 | |
| 
 | |
|   SYNOPSIS
 | |
|     Item_in_subselect::single_value_transformer()
 | |
|       join  Join object of the subquery (i.e. 'child' join).
 | |
|       func  Subquery comparison creator
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Rewrite a single-column subquery using rule-based approach. The subquery
 | |
|     
 | |
|        oe $cmp$ (SELECT ie FROM ... WHERE subq_where ... HAVING subq_having)
 | |
|     
 | |
|     First, try to convert the subquery to scalar-result subquery in one of
 | |
|     the forms:
 | |
|     
 | |
|        - oe $cmp$ (SELECT MAX(...) )  // handled by Item_singlerow_subselect
 | |
|        - oe $cmp$ <max>(SELECT ...)   // handled by Item_maxmin_subselect
 | |
|    
 | |
|     If that fails, the subquery will be handled with class Item_in_optimizer, 
 | |
|     Inject the predicates into subquery, i.e. convert it to:
 | |
| 
 | |
|     - If the subquery has aggregates, GROUP BY, or HAVING, convert to
 | |
| 
 | |
|        SELECT ie FROM ...  HAVING subq_having AND 
 | |
|                                    trigcond(oe $cmp$ ref_or_null_helper<ie>)
 | |
|                                    
 | |
|       the addition is wrapped into trigger only when we want to distinguish
 | |
|       between NULL and FALSE results.
 | |
| 
 | |
|     - Otherwise (no aggregates/GROUP BY/HAVING) convert it to one of the
 | |
|       following:
 | |
| 
 | |
|       = If we don't need to distinguish between NULL and FALSE subquery:
 | |
|         
 | |
|         SELECT 1 FROM ... WHERE (oe $cmp$ ie) AND subq_where
 | |
| 
 | |
|       = If we need to distinguish between those:
 | |
| 
 | |
|         SELECT 1 FROM ...
 | |
|           WHERE  subq_where AND trigcond((oe $cmp$ ie) OR (ie IS NULL))
 | |
|           HAVING trigcond(<is_not_null_test>(ie))
 | |
| 
 | |
|   RETURN
 | |
|     RES_OK     - OK, either subquery was transformed, or appopriate
 | |
|                  predicates where injected into it.
 | |
|     RES_REDUCE - The subquery was reduced to non-subquery
 | |
|     RES_ERROR  - Error
 | |
| */
 | |
| 
 | |
| Item_subselect::trans_res
 | |
| Item_in_subselect::single_value_transformer(JOIN *join,
 | |
| 					    Comp_creator *func)
 | |
| {
 | |
|   SELECT_LEX *select_lex= join->select_lex;
 | |
|   DBUG_ENTER("Item_in_subselect::single_value_transformer");
 | |
| 
 | |
|   /*
 | |
|     Check that the right part of the subselect contains no more than one
 | |
|     column. E.g. in SELECT 1 IN (SELECT * ..) the right part is (SELECT * ...)
 | |
|   */
 | |
|   if (select_lex->item_list.elements > 1)
 | |
|   {
 | |
|     my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
 | |
|     DBUG_RETURN(RES_ERROR);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     If this is an ALL/ANY single-value subselect, try to rewrite it with
 | |
|     a MIN/MAX subselect. We can do that if a possible NULL result of the
 | |
|     subselect can be ignored.
 | |
|     E.g. SELECT * FROM t1 WHERE b > ANY (SELECT a FROM t2) can be rewritten
 | |
|     with SELECT * FROM t1 WHERE b > (SELECT MAX(a) FROM t2).
 | |
|     We can't check that this optimization is safe if it's not a top-level
 | |
|     item of the WHERE clause (e.g. because the WHERE clause can contain IS
 | |
|     NULL/IS NOT NULL functions). If so, we rewrite ALL/ANY with NOT EXISTS
 | |
|     later in this method.
 | |
|   */
 | |
|   if ((abort_on_null || (upper_item && upper_item->top_level())) &&
 | |
|       !select_lex->master_unit()->uncacheable && !func->eqne_op())
 | |
|   {
 | |
|     if (substitution)
 | |
|     {
 | |
|       // It is second (third, ...) SELECT of UNION => All is done
 | |
|       DBUG_RETURN(RES_OK);
 | |
|     }
 | |
| 
 | |
|     Item *subs;
 | |
|     if (!select_lex->group_list.elements &&
 | |
|         !select_lex->having &&
 | |
| 	!select_lex->with_sum_func &&
 | |
| 	!(select_lex->next_select()) &&
 | |
|         select_lex->table_list.elements)
 | |
|     {
 | |
|       Item_sum_hybrid *item;
 | |
|       nesting_map save_allow_sum_func;
 | |
|       if (func->l_op())
 | |
|       {
 | |
| 	/*
 | |
| 	  (ALL && (> || =>)) || (ANY && (< || =<))
 | |
| 	  for ALL condition is inverted
 | |
| 	*/
 | |
| 	item= new Item_sum_max(*select_lex->ref_pointer_array);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
| 	/*
 | |
| 	  (ALL && (< || =<)) || (ANY && (> || =>))
 | |
| 	  for ALL condition is inverted
 | |
| 	*/
 | |
| 	item= new Item_sum_min(*select_lex->ref_pointer_array);
 | |
|       }
 | |
|       if (upper_item)
 | |
|         upper_item->set_sum_test(item);
 | |
|       *select_lex->ref_pointer_array= item;
 | |
|       {
 | |
| 	List_iterator<Item> it(select_lex->item_list);
 | |
| 	it++;
 | |
| 	it.replace(item);
 | |
|       }
 | |
| 
 | |
|       save_allow_sum_func= thd->lex->allow_sum_func;
 | |
|       thd->lex->allow_sum_func|= 1 << thd->lex->current_select->nest_level;
 | |
|       /*
 | |
| 	Item_sum_(max|min) can't substitute other item => we can use 0 as
 | |
|         reference, also Item_sum_(max|min) can't be fixed after creation, so
 | |
|         we do not check item->fixed
 | |
|       */
 | |
|       if (item->fix_fields(thd, 0))
 | |
| 	DBUG_RETURN(RES_ERROR);
 | |
|       thd->lex->allow_sum_func= save_allow_sum_func; 
 | |
|       /* we added aggregate function => we have to change statistic */
 | |
|       count_field_types(&join->tmp_table_param, join->all_fields, 0);
 | |
| 
 | |
|       subs= new Item_singlerow_subselect(select_lex);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       Item_maxmin_subselect *item;
 | |
|       subs= item= new Item_maxmin_subselect(thd, this, select_lex, func->l_op());
 | |
|       if (upper_item)
 | |
|         upper_item->set_sub_test(item);
 | |
|     }
 | |
|     /* fix fields is already called for  left expression */
 | |
|     substitution= func->create(left_expr, subs);
 | |
|     DBUG_RETURN(RES_OK);
 | |
|   }
 | |
| 
 | |
|   if (!substitution)
 | |
|   {
 | |
|     /* We're invoked for the 1st (or the only) SELECT in the subquery UNION */
 | |
|     SELECT_LEX_UNIT *master_unit= select_lex->master_unit();
 | |
|     substitution= optimizer;
 | |
| 
 | |
|     SELECT_LEX *current= thd->lex->current_select, *up;
 | |
| 
 | |
|     thd->lex->current_select= up= current->return_after_parsing();
 | |
|     //optimizer never use Item **ref => we can pass 0 as parameter
 | |
|     if (!optimizer || optimizer->fix_left(thd, 0))
 | |
|     {
 | |
|       thd->lex->current_select= current;
 | |
|       DBUG_RETURN(RES_ERROR);
 | |
|     }
 | |
|     thd->lex->current_select= current;
 | |
| 
 | |
|     /*
 | |
|       As far as  Item_ref_in_optimizer do not substitute itself on fix_fields
 | |
|       we can use same item for all selects.
 | |
|     */
 | |
|     expr= new Item_direct_ref(&select_lex->context,
 | |
|                               (Item**)optimizer->get_cache(),
 | |
| 			      (char *)"<no matter>",
 | |
| 			      (char *)in_left_expr_name);
 | |
| 
 | |
|     master_unit->uncacheable|= UNCACHEABLE_DEPENDENT;
 | |
|   }
 | |
|   if (!abort_on_null && left_expr->maybe_null && !pushed_cond_guards)
 | |
|   {
 | |
|     if (!(pushed_cond_guards= (bool*)join->thd->alloc(sizeof(bool))))
 | |
|       DBUG_RETURN(RES_ERROR);
 | |
|     pushed_cond_guards[0]= TRUE;
 | |
|   }
 | |
| 
 | |
|   select_lex->uncacheable|= UNCACHEABLE_DEPENDENT;
 | |
|   if (join->having || select_lex->with_sum_func ||
 | |
|       select_lex->group_list.elements)
 | |
|   {
 | |
|     bool tmp;
 | |
|     Item *item= func->create(expr,
 | |
|                              new Item_ref_null_helper(&select_lex->context,
 | |
|                                                       this,
 | |
|                                                       select_lex->
 | |
|                                                       ref_pointer_array,
 | |
|                                                       (char *)"<ref>",
 | |
|                                                       this->full_name()));
 | |
|     if (!abort_on_null && left_expr->maybe_null)
 | |
|     {
 | |
|       /* 
 | |
|         We can encounter "NULL IN (SELECT ...)". Wrap the added condition
 | |
|         within a trig_cond.
 | |
|       */
 | |
|       item= new Item_func_trig_cond(item, get_cond_guard(0));
 | |
|     }
 | |
|     
 | |
|     /*
 | |
|       AND and comparison functions can't be changed during fix_fields()
 | |
|       we can assign select_lex->having here, and pass 0 as last
 | |
|       argument (reference) to fix_fields()
 | |
|     */
 | |
|     select_lex->having= join->having= and_items(join->having, item);
 | |
|     if (join->having == item)
 | |
|       item->name= (char*)in_having_cond;
 | |
|     select_lex->having_fix_field= 1;
 | |
|     /*
 | |
|       we do not check join->having->fixed, because Item_and (from and_items)
 | |
|       or comparison function (from func->create) can't be fixed after creation
 | |
|     */
 | |
|     tmp= join->having->fix_fields(thd, 0);
 | |
|     select_lex->having_fix_field= 0;
 | |
|     if (tmp)
 | |
|       DBUG_RETURN(RES_ERROR);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     Item *item= (Item*) select_lex->item_list.head();
 | |
| 
 | |
|     if (select_lex->table_list.elements)
 | |
|     {
 | |
|       bool tmp;
 | |
|       Item *having= item, *orig_item= item;
 | |
|       select_lex->item_list.empty();
 | |
|       select_lex->item_list.push_back(new Item_int("Not_used",
 | |
|                                                    (longlong) 1,
 | |
|                                                    MY_INT64_NUM_DECIMAL_DIGITS));
 | |
|       select_lex->ref_pointer_array[0]= select_lex->item_list.head();
 | |
|        
 | |
|       item= func->create(expr, item);
 | |
|       if (!abort_on_null && orig_item->maybe_null)
 | |
|       {
 | |
| 	having= new Item_is_not_null_test(this, having);
 | |
|         if (left_expr->maybe_null)
 | |
|         {
 | |
|           if (!(having= new Item_func_trig_cond(having,
 | |
|                                                 get_cond_guard(0))))
 | |
|             DBUG_RETURN(RES_ERROR);
 | |
|         }
 | |
| 	/*
 | |
| 	  Item_is_not_null_test can't be changed during fix_fields()
 | |
| 	  we can assign select_lex->having here, and pass 0 as last
 | |
| 	  argument (reference) to fix_fields()
 | |
| 	*/
 | |
|         having->name= (char*)in_having_cond;
 | |
| 	select_lex->having= join->having= having;
 | |
| 	select_lex->having_fix_field= 1;
 | |
|         /*
 | |
|           we do not check join->having->fixed, because Item_and (from
 | |
|           and_items) or comparison function (from func->create) can't be
 | |
|           fixed after creation
 | |
|         */
 | |
| 	tmp= join->having->fix_fields(thd, 0);
 | |
|         select_lex->having_fix_field= 0;
 | |
|         if (tmp)
 | |
| 	  DBUG_RETURN(RES_ERROR);
 | |
| 	item= new Item_cond_or(item,
 | |
| 			       new Item_func_isnull(orig_item));
 | |
|       }
 | |
|       /* 
 | |
|         If we may encounter NULL IN (SELECT ...) and care whether subquery
 | |
|         result is NULL or FALSE, wrap condition in a trig_cond.
 | |
|       */
 | |
|       if (!abort_on_null && left_expr->maybe_null)
 | |
|       {
 | |
|         if (!(item= new Item_func_trig_cond(item, get_cond_guard(0))))
 | |
|           DBUG_RETURN(RES_ERROR);
 | |
|       }
 | |
|       /*
 | |
|         TODO: figure out why the following is done here in 
 | |
|         single_value_transformer but there is no corresponding action in
 | |
|         row_value_transformer?
 | |
|       */
 | |
|       item->name= (char *)in_additional_cond;
 | |
| 
 | |
|       /*
 | |
| 	AND can't be changed during fix_fields()
 | |
| 	we can assign select_lex->having here, and pass 0 as last
 | |
| 	argument (reference) to fix_fields()
 | |
|       */
 | |
|       select_lex->where= join->conds= and_items(join->conds, item);
 | |
|       select_lex->where->top_level_item();
 | |
|       /*
 | |
|         we do not check join->conds->fixed, because Item_and can't be fixed
 | |
|         after creation
 | |
|       */
 | |
|       if (join->conds->fix_fields(thd, 0))
 | |
| 	DBUG_RETURN(RES_ERROR);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       bool tmp;
 | |
|       if (select_lex->master_unit()->is_union())
 | |
|       {
 | |
| 	/*
 | |
| 	  comparison functions can't be changed during fix_fields()
 | |
| 	  we can assign select_lex->having here, and pass 0 as last
 | |
| 	  argument (reference) to fix_fields()
 | |
| 	*/
 | |
|         Item *new_having=
 | |
|           func->create(expr,
 | |
|                        new Item_ref_null_helper(&select_lex->context, this,
 | |
|                                             select_lex->ref_pointer_array,
 | |
|                                             (char *)"<no matter>",
 | |
|                                             (char *)"<result>"));
 | |
|         if (!abort_on_null && left_expr->maybe_null)
 | |
|         {
 | |
|           if (!(new_having= new Item_func_trig_cond(new_having,
 | |
|                                                     get_cond_guard(0))))
 | |
|             DBUG_RETURN(RES_ERROR);
 | |
|         }
 | |
|         new_having->name= (char*)in_having_cond;
 | |
| 	select_lex->having= join->having= new_having;
 | |
| 	select_lex->having_fix_field= 1;
 | |
|         
 | |
|         /*
 | |
|           we do not check join->having->fixed, because comparison function
 | |
|           (from func->create) can't be fixed after creation
 | |
|         */
 | |
| 	tmp= join->having->fix_fields(thd, 0);
 | |
|         select_lex->having_fix_field= 0;
 | |
|         if (tmp)
 | |
| 	  DBUG_RETURN(RES_ERROR);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
| 	// it is single select without tables => possible optimization
 | |
| 	item= func->create(left_expr, item);
 | |
| 	// fix_field of item will be done in time of substituting
 | |
| 	substitution= item;
 | |
| 	have_to_be_excluded= 1;
 | |
| 	if (thd->lex->describe)
 | |
| 	{
 | |
| 	  char warn_buff[MYSQL_ERRMSG_SIZE];
 | |
| 	  sprintf(warn_buff, ER(ER_SELECT_REDUCED), select_lex->select_number);
 | |
| 	  push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
 | |
| 		       ER_SELECT_REDUCED, warn_buff);
 | |
| 	}
 | |
| 	DBUG_RETURN(RES_REDUCE);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(RES_OK);
 | |
| }
 | |
| 
 | |
| 
 | |
| Item_subselect::trans_res
 | |
| Item_in_subselect::row_value_transformer(JOIN *join)
 | |
| {
 | |
|   SELECT_LEX *select_lex= join->select_lex;
 | |
|   Item *having_item= 0;
 | |
|   uint cols_num= left_expr->cols();
 | |
|   bool is_having_used= (join->having || select_lex->with_sum_func ||
 | |
|                         select_lex->group_list.first ||
 | |
|                         !select_lex->table_list.elements);
 | |
|   DBUG_ENTER("Item_in_subselect::row_value_transformer");
 | |
| 
 | |
|   if (select_lex->item_list.elements != left_expr->cols())
 | |
|   {
 | |
|     my_error(ER_OPERAND_COLUMNS, MYF(0), left_expr->cols());
 | |
|     DBUG_RETURN(RES_ERROR);
 | |
|   }
 | |
| 
 | |
|   if (!substitution)
 | |
|   {
 | |
|     //first call for this unit
 | |
|     SELECT_LEX_UNIT *master_unit= select_lex->master_unit();
 | |
|     substitution= optimizer;
 | |
| 
 | |
|     SELECT_LEX *current= thd->lex->current_select, *up;
 | |
|     thd->lex->current_select= up= current->return_after_parsing();
 | |
|     //optimizer never use Item **ref => we can pass 0 as parameter
 | |
|     if (!optimizer || optimizer->fix_left(thd, 0))
 | |
|     {
 | |
|       thd->lex->current_select= current;
 | |
|       DBUG_RETURN(RES_ERROR);
 | |
|     }
 | |
| 
 | |
|     // we will refer to upper level cache array => we have to save it in PS
 | |
|     optimizer->keep_top_level_cache();
 | |
| 
 | |
|     thd->lex->current_select= current;
 | |
|     master_unit->uncacheable|= UNCACHEABLE_DEPENDENT;
 | |
| 
 | |
|     if (!abort_on_null && left_expr->maybe_null && !pushed_cond_guards)
 | |
|     {
 | |
|       if (!(pushed_cond_guards= (bool*)join->thd->alloc(sizeof(bool) *
 | |
|                                                         left_expr->cols())))
 | |
|         DBUG_RETURN(RES_ERROR);
 | |
|       for (uint i= 0; i < cols_num; i++)
 | |
|         pushed_cond_guards[i]= TRUE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   select_lex->uncacheable|= UNCACHEABLE_DEPENDENT;
 | |
|   if (is_having_used)
 | |
|   {
 | |
|     /*
 | |
|       (l1, l2, l3) IN (SELECT v1, v2, v3 ... HAVING having) =>
 | |
|       EXISTS (SELECT ... HAVING having and
 | |
|                                 (l1 = v1 or is null v1) and
 | |
|                                 (l2 = v2 or is null v2) and
 | |
|                                 (l3 = v3 or is null v3) and
 | |
|                                 is_not_null_test(v1) and
 | |
|                                 is_not_null_test(v2) and
 | |
|                                 is_not_null_test(v3))
 | |
|       where is_not_null_test used to register nulls in case if we have
 | |
|       not found matching to return correct NULL value
 | |
|       TODO: say here explicitly if the order of AND parts matters or not.
 | |
|     */
 | |
|     Item *item_having_part2= 0;
 | |
|     for (uint i= 0; i < cols_num; i++)
 | |
|     {
 | |
|       DBUG_ASSERT(left_expr->fixed && select_lex->ref_pointer_array[i]->fixed);
 | |
|       if (select_lex->ref_pointer_array[i]->
 | |
|           check_cols(left_expr->element_index(i)->cols()))
 | |
|         DBUG_RETURN(RES_ERROR);
 | |
|       Item *item_eq=
 | |
|         new Item_func_eq(new
 | |
|                          Item_ref(&select_lex->context,
 | |
|                                   (*optimizer->get_cache())->
 | |
|                                   addr(i),
 | |
|                                   (char *)"<no matter>",
 | |
|                                   (char *)in_left_expr_name),
 | |
|                          new
 | |
|                          Item_ref(&select_lex->context,
 | |
|                                   select_lex->ref_pointer_array + i,
 | |
|                                   (char *)"<no matter>",
 | |
|                                   (char *)"<list ref>")
 | |
|                         );
 | |
|       Item *item_isnull=
 | |
|         new Item_func_isnull(new
 | |
|                              Item_ref(&select_lex->context,
 | |
|                                       select_lex->ref_pointer_array+i,
 | |
|                                       (char *)"<no matter>",
 | |
|                                       (char *)"<list ref>")
 | |
|                             );
 | |
|       Item *col_item= new Item_cond_or(item_eq, item_isnull);
 | |
|       if (!abort_on_null && left_expr->element_index(i)->maybe_null)
 | |
|       {
 | |
|         if (!(col_item= new Item_func_trig_cond(col_item, get_cond_guard(i))))
 | |
|           DBUG_RETURN(RES_ERROR);
 | |
|       }
 | |
|       having_item= and_items(having_item, col_item);
 | |
|       
 | |
|       Item *item_nnull_test= 
 | |
|          new Item_is_not_null_test(this,
 | |
|                                    new Item_ref(&select_lex->context,
 | |
|                                                 select_lex->
 | |
|                                                 ref_pointer_array + i,
 | |
|                                                 (char *)"<no matter>",
 | |
|                                                 (char *)"<list ref>"));
 | |
|       if (!abort_on_null && left_expr->element_index(i)->maybe_null)
 | |
|       {
 | |
|         if (!(item_nnull_test= 
 | |
|               new Item_func_trig_cond(item_nnull_test, get_cond_guard(i))))
 | |
|           DBUG_RETURN(RES_ERROR);
 | |
|       }
 | |
|       item_having_part2= and_items(item_having_part2, item_nnull_test);
 | |
|       item_having_part2->top_level_item();
 | |
|     }
 | |
|     having_item= and_items(having_item, item_having_part2);
 | |
|     having_item->top_level_item();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       (l1, l2, l3) IN (SELECT v1, v2, v3 ... WHERE where) =>
 | |
|       EXISTS (SELECT ... WHERE where and
 | |
|                                (l1 = v1 or is null v1) and
 | |
|                                (l2 = v2 or is null v2) and
 | |
|                                (l3 = v3 or is null v3)
 | |
|                          HAVING is_not_null_test(v1) and
 | |
|                                 is_not_null_test(v2) and
 | |
|                                 is_not_null_test(v3))
 | |
|       where is_not_null_test register NULLs values but reject rows
 | |
| 
 | |
|       in case when we do not need correct NULL, we have simplier construction:
 | |
|       EXISTS (SELECT ... WHERE where and
 | |
|                                (l1 = v1) and
 | |
|                                (l2 = v2) and
 | |
|                                (l3 = v3)
 | |
|     */
 | |
|     Item *where_item= 0;
 | |
|     for (uint i= 0; i < cols_num; i++)
 | |
|     {
 | |
|       Item *item, *item_isnull;
 | |
|       DBUG_ASSERT(left_expr->fixed && select_lex->ref_pointer_array[i]->fixed);
 | |
|       if (select_lex->ref_pointer_array[i]->
 | |
|           check_cols(left_expr->element_index(i)->cols()))
 | |
|         DBUG_RETURN(RES_ERROR);
 | |
|       item=
 | |
|         new Item_func_eq(new
 | |
|                          Item_direct_ref(&select_lex->context,
 | |
|                                          (*optimizer->get_cache())->
 | |
|                                          addr(i),
 | |
|                                          (char *)"<no matter>",
 | |
|                                          (char *)in_left_expr_name),
 | |
|                          new
 | |
|                          Item_direct_ref(&select_lex->context,
 | |
|                                          select_lex->
 | |
|                                          ref_pointer_array+i,
 | |
|                                          (char *)"<no matter>",
 | |
|                                          (char *)"<list ref>")
 | |
|                         );
 | |
|       if (!abort_on_null)
 | |
|       {
 | |
|         Item *having_col_item=
 | |
|           new Item_is_not_null_test(this,
 | |
|                                     new
 | |
|                                     Item_ref(&select_lex->context, 
 | |
|                                              select_lex->ref_pointer_array + i,
 | |
|                                              (char *)"<no matter>",
 | |
|                                              (char *)"<list ref>"));
 | |
|         
 | |
|         
 | |
|         item_isnull= new
 | |
|           Item_func_isnull(new
 | |
|                            Item_direct_ref(&select_lex->context,
 | |
|                                            select_lex->
 | |
|                                            ref_pointer_array+i,
 | |
|                                            (char *)"<no matter>",
 | |
|                                            (char *)"<list ref>")
 | |
|                           );
 | |
|         item= new Item_cond_or(item, item_isnull);
 | |
|         /* 
 | |
|           TODO: why we create the above for cases where the right part
 | |
|                 cant be NULL?
 | |
|         */
 | |
|         if (left_expr->element_index(i)->maybe_null)
 | |
|         {
 | |
|           if (!(item= new Item_func_trig_cond(item, get_cond_guard(i))))
 | |
|             DBUG_RETURN(RES_ERROR);
 | |
|           if (!(having_col_item= 
 | |
|                   new Item_func_trig_cond(having_col_item, get_cond_guard(i))))
 | |
|             DBUG_RETURN(RES_ERROR);
 | |
|         }
 | |
|         having_item= and_items(having_item, having_col_item);
 | |
|       }
 | |
|       where_item= and_items(where_item, item);
 | |
|     }
 | |
|     /*
 | |
|       AND can't be changed during fix_fields()
 | |
|       we can assign select_lex->where here, and pass 0 as last
 | |
|       argument (reference) to fix_fields()
 | |
|     */
 | |
|     select_lex->where= join->conds= and_items(join->conds, where_item);
 | |
|     select_lex->where->top_level_item();
 | |
|     if (join->conds->fix_fields(thd, 0))
 | |
|       DBUG_RETURN(RES_ERROR);
 | |
|   }
 | |
|   if (having_item)
 | |
|   {
 | |
|     bool res;
 | |
|     select_lex->having= join->having= and_items(join->having, having_item);
 | |
|     if (having_item == select_lex->having)
 | |
|       having_item->name= (char*)in_having_cond;
 | |
|     select_lex->having->top_level_item();
 | |
|     /*
 | |
|       AND can't be changed during fix_fields()
 | |
|       we can assign select_lex->having here, and pass 0 as last
 | |
|       argument (reference) to fix_fields()
 | |
|     */
 | |
|     select_lex->having_fix_field= 1;
 | |
|     res= join->having->fix_fields(thd, 0);
 | |
|     select_lex->having_fix_field= 0;
 | |
|     if (res)
 | |
|     {
 | |
|       DBUG_RETURN(RES_ERROR);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(RES_OK);
 | |
| }
 | |
| 
 | |
| 
 | |
| Item_subselect::trans_res
 | |
| Item_in_subselect::select_transformer(JOIN *join)
 | |
| {
 | |
|   return select_in_like_transformer(join, &eq_creator);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Prepare IN/ALL/ANY/SOME subquery transformation and call appropriate
 | |
|   transformation function
 | |
| 
 | |
|   SYNOPSIS
 | |
|     Item_in_subselect::select_in_like_transformer()
 | |
|     join    JOIN object of transforming subquery
 | |
|     func    creator of condition function of subquery
 | |
| 
 | |
|   DESCRIPTION
 | |
|     To decide which transformation procedure (scalar or row) applicable here
 | |
|     we have to call fix_fields() for left expression to be able to call
 | |
|     cols() method on it. Also this method make arena management for
 | |
|     underlying transformation methods.
 | |
| 
 | |
|   RETURN
 | |
|     RES_OK      OK
 | |
|     RES_REDUCE  OK, and current subquery was reduced during transformation
 | |
|     RES_ERROR   Error
 | |
| */
 | |
| 
 | |
| Item_subselect::trans_res
 | |
| Item_in_subselect::select_in_like_transformer(JOIN *join, Comp_creator *func)
 | |
| {
 | |
|   Query_arena *arena, backup;
 | |
|   SELECT_LEX *current= thd->lex->current_select, *up;
 | |
|   const char *save_where= thd->where;
 | |
|   Item_subselect::trans_res res= RES_ERROR;
 | |
|   bool result;
 | |
| 
 | |
|   DBUG_ENTER("Item_in_subselect::select_in_like_transformer");
 | |
| 
 | |
|   if (changed)
 | |
|   {
 | |
|     DBUG_RETURN(RES_OK);
 | |
|   }
 | |
| 
 | |
|   thd->where= "IN/ALL/ANY subquery";
 | |
| 
 | |
|   /*
 | |
|     In some optimisation cases we will not need this Item_in_optimizer
 | |
|     object, but we can't know it here, but here we need address correct
 | |
|     reference on left expresion.
 | |
|   */
 | |
|   if (!optimizer)
 | |
|   {
 | |
|     arena= thd->activate_stmt_arena_if_needed(&backup);
 | |
|     result= (!(optimizer= new Item_in_optimizer(left_expr, this)));
 | |
|     if (arena)
 | |
|       thd->restore_active_arena(arena, &backup);
 | |
|     if (result)
 | |
|       goto err;
 | |
|   }
 | |
| 
 | |
|   thd->lex->current_select= up= current->return_after_parsing();
 | |
|   result= (!left_expr->fixed &&
 | |
|            left_expr->fix_fields(thd, optimizer->arguments()));
 | |
|   /* fix_fields can change reference to left_expr, we need reassign it */
 | |
|   left_expr= optimizer->arguments()[0];
 | |
| 
 | |
|   thd->lex->current_select= current;
 | |
|   if (result)
 | |
|     goto err;
 | |
| 
 | |
|   transformed= 1;
 | |
|   arena= thd->activate_stmt_arena_if_needed(&backup);
 | |
|   /*
 | |
|     Both transformers call fix_fields() only for Items created inside them,
 | |
|     and all that items do not make permanent changes in current item arena
 | |
|     which allow to us call them with changed arena (if we do not know nature
 | |
|     of Item, we have to call fix_fields() for it only with original arena to
 | |
|     avoid memory leack)
 | |
|   */
 | |
|   if (left_expr->cols() == 1)
 | |
|     res= single_value_transformer(join, func);
 | |
|   else
 | |
|   {
 | |
|     /* we do not support row operation for ALL/ANY/SOME */
 | |
|     if (func != &eq_creator)
 | |
|     {
 | |
|       if (arena)
 | |
|         thd->restore_active_arena(arena, &backup);
 | |
|       my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
 | |
|       DBUG_RETURN(RES_ERROR);
 | |
|     }
 | |
|     res= row_value_transformer(join);
 | |
|   }
 | |
|   if (arena)
 | |
|     thd->restore_active_arena(arena, &backup);
 | |
| err:
 | |
|   thd->where= save_where;
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_in_subselect::print(String *str)
 | |
| {
 | |
|   if (transformed)
 | |
|     str->append(STRING_WITH_LEN("<exists>"));
 | |
|   else
 | |
|   {
 | |
|     left_expr->print(str);
 | |
|     str->append(STRING_WITH_LEN(" in "));
 | |
|   }
 | |
|   Item_subselect::print(str);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref)
 | |
| {
 | |
|   bool result = 0;
 | |
|   
 | |
|   if (thd_arg->lex->view_prepare_mode && left_expr && !left_expr->fixed)
 | |
|     result = left_expr->fix_fields(thd_arg, &left_expr);
 | |
| 
 | |
|   return result || Item_subselect::fix_fields(thd_arg, ref);
 | |
| }
 | |
| 
 | |
| 
 | |
| Item_subselect::trans_res
 | |
| Item_allany_subselect::select_transformer(JOIN *join)
 | |
| {
 | |
|   transformed= 1;
 | |
|   if (upper_item)
 | |
|     upper_item->show= 1;
 | |
|   return select_in_like_transformer(join, func);
 | |
| }
 | |
| 
 | |
| 
 | |
| void Item_allany_subselect::print(String *str)
 | |
| {
 | |
|   if (transformed)
 | |
|     str->append(STRING_WITH_LEN("<exists>"));
 | |
|   else
 | |
|   {
 | |
|     left_expr->print(str);
 | |
|     str->append(' ');
 | |
|     str->append(func->symbol(all));
 | |
|     str->append(all ? " all " : " any ", 5);
 | |
|   }
 | |
|   Item_subselect::print(str);
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_engine::set_thd(THD *thd_arg)
 | |
| {
 | |
|   thd= thd_arg;
 | |
|   if (result)
 | |
|     result->set_thd(thd_arg);
 | |
| }
 | |
| 
 | |
| 
 | |
| subselect_single_select_engine::
 | |
| subselect_single_select_engine(st_select_lex *select,
 | |
| 			       select_subselect *result_arg,
 | |
| 			       Item_subselect *item_arg)
 | |
|   :subselect_engine(item_arg, result_arg),
 | |
|    prepared(0), optimized(0), executed(0),
 | |
|    select_lex(select), join(0)
 | |
| {
 | |
|   select_lex->master_unit()->item= item_arg;
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_single_select_engine::cleanup()
 | |
| {
 | |
|   DBUG_ENTER("subselect_single_select_engine::cleanup");
 | |
|   prepared= optimized= executed= 0;
 | |
|   join= 0;
 | |
|   result->cleanup();
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_union_engine::cleanup()
 | |
| {
 | |
|   DBUG_ENTER("subselect_union_engine::cleanup");
 | |
|   unit->reinit_exec_mechanism();
 | |
|   result->cleanup();
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool subselect_union_engine::is_executed() const
 | |
| {
 | |
|   return unit->executed;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check if last execution of the subquery engine produced any rows
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_union_engine::no_rows()
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Check if last execution of the subquery engine produced any rows. The
 | |
|     return value is undefined if last execution ended in an error.
 | |
| 
 | |
|   RETURN
 | |
|     TRUE  - Last subselect execution has produced no rows
 | |
|     FALSE - Otherwise
 | |
| */
 | |
| 
 | |
| bool subselect_union_engine::no_rows()
 | |
| {
 | |
|   /* Check if we got any rows when reading UNION result from temp. table: */
 | |
|   return test(!unit->fake_select_lex->join->send_records);
 | |
| }
 | |
| 
 | |
| void subselect_uniquesubquery_engine::cleanup()
 | |
| {
 | |
|   DBUG_ENTER("subselect_uniquesubquery_engine::cleanup");
 | |
|   /*
 | |
|     subselect_uniquesubquery_engine have not 'result' assigbed, so we do not
 | |
|     cleanup() it
 | |
|   */
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| subselect_union_engine::subselect_union_engine(st_select_lex_unit *u,
 | |
| 					       select_subselect *result_arg,
 | |
| 					       Item_subselect *item_arg)
 | |
|   :subselect_engine(item_arg, result_arg)
 | |
| {
 | |
|   unit= u;
 | |
|   if (!result_arg)				//out of memory
 | |
|     current_thd->fatal_error();
 | |
|   unit->item= item_arg;
 | |
| }
 | |
| 
 | |
| 
 | |
| int subselect_single_select_engine::prepare()
 | |
| {
 | |
|   if (prepared)
 | |
|     return 0;
 | |
|   join= new JOIN(thd, select_lex->item_list,
 | |
| 		 select_lex->options | SELECT_NO_UNLOCK, result);
 | |
|   if (!join || !result)
 | |
|   {
 | |
|     thd->fatal_error();				//out of memory
 | |
|     return 1;
 | |
|   }
 | |
|   prepared= 1;
 | |
|   SELECT_LEX *save_select= thd->lex->current_select;
 | |
|   thd->lex->current_select= select_lex;
 | |
|   if (join->prepare(&select_lex->ref_pointer_array,
 | |
| 		    (TABLE_LIST*) select_lex->table_list.first,
 | |
| 		    select_lex->with_wild,
 | |
| 		    select_lex->where,
 | |
| 		    select_lex->order_list.elements +
 | |
| 		    select_lex->group_list.elements,
 | |
| 		    (ORDER*) select_lex->order_list.first,
 | |
| 		    (ORDER*) select_lex->group_list.first,
 | |
| 		    select_lex->having,
 | |
| 		    (ORDER*) 0, select_lex,
 | |
| 		    select_lex->master_unit()))
 | |
|     return 1;
 | |
|   thd->lex->current_select= save_select;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| int subselect_union_engine::prepare()
 | |
| {
 | |
|   return unit->prepare(thd, result, SELECT_NO_UNLOCK);
 | |
| }
 | |
| 
 | |
| int subselect_uniquesubquery_engine::prepare()
 | |
| {
 | |
|   //this never should be called
 | |
|   DBUG_ASSERT(0);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check if last execution of the subquery engine produced any rows
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_single_select_engine::no_rows()
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Check if last execution of the subquery engine produced any rows. The
 | |
|     return value is undefined if last execution ended in an error.
 | |
| 
 | |
|   RETURN
 | |
|     TRUE  - Last subselect execution has produced no rows
 | |
|     FALSE - Otherwise
 | |
| */
 | |
| 
 | |
| bool subselect_single_select_engine::no_rows()
 | |
| { 
 | |
|   return !item->assigned();
 | |
| }
 | |
| 
 | |
| 
 | |
| /* 
 | |
|  makes storage for the output values for the subquery and calcuates 
 | |
|  their data and column types and their nullability.
 | |
| */ 
 | |
| void subselect_engine::set_row(List<Item> &item_list, Item_cache **row)
 | |
| {
 | |
|   Item *sel_item;
 | |
|   List_iterator_fast<Item> li(item_list);
 | |
|   res_type= STRING_RESULT;
 | |
|   res_field_type= MYSQL_TYPE_VAR_STRING;
 | |
|   for (uint i= 0; (sel_item= li++); i++)
 | |
|   {
 | |
|     item->max_length= sel_item->max_length;
 | |
|     res_type= sel_item->result_type();
 | |
|     res_field_type= sel_item->field_type();
 | |
|     item->decimals= sel_item->decimals;
 | |
|     item->unsigned_flag= sel_item->unsigned_flag;
 | |
|     maybe_null= sel_item->maybe_null;
 | |
|     if (!(row[i]= Item_cache::get_cache(res_type)))
 | |
|       return;
 | |
|     row[i]->setup(sel_item);
 | |
|   }
 | |
|   if (item_list.elements > 1)
 | |
|     res_type= ROW_RESULT;
 | |
| }
 | |
| 
 | |
| void subselect_single_select_engine::fix_length_and_dec(Item_cache **row)
 | |
| {
 | |
|   DBUG_ASSERT(row || select_lex->item_list.elements==1);
 | |
|   set_row(select_lex->item_list, row);
 | |
|   item->collation.set(row[0]->collation);
 | |
|   if (cols() != 1)
 | |
|     maybe_null= 0;
 | |
| }
 | |
| 
 | |
| void subselect_union_engine::fix_length_and_dec(Item_cache **row)
 | |
| {
 | |
|   DBUG_ASSERT(row || unit->first_select()->item_list.elements==1);
 | |
| 
 | |
|   if (unit->first_select()->item_list.elements == 1)
 | |
|   {
 | |
|     set_row(unit->types, row);
 | |
|     item->collation.set(row[0]->collation);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     bool maybe_null_saved= maybe_null;
 | |
|     set_row(unit->types, row);
 | |
|     maybe_null= maybe_null_saved;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void subselect_uniquesubquery_engine::fix_length_and_dec(Item_cache **row)
 | |
| {
 | |
|   //this never should be called
 | |
|   DBUG_ASSERT(0);
 | |
| }
 | |
| 
 | |
| int  init_read_record_seq(JOIN_TAB *tab);
 | |
| int join_read_always_key_or_null(JOIN_TAB *tab);
 | |
| int join_read_next_same_or_null(READ_RECORD *info);
 | |
| 
 | |
| int subselect_single_select_engine::exec()
 | |
| {
 | |
|   DBUG_ENTER("subselect_single_select_engine::exec");
 | |
|   char const *save_where= thd->where;
 | |
|   SELECT_LEX *save_select= thd->lex->current_select;
 | |
|   thd->lex->current_select= select_lex;
 | |
|   if (!optimized)
 | |
|   {
 | |
|     SELECT_LEX_UNIT *unit= select_lex->master_unit();
 | |
| 
 | |
|     optimized= 1;
 | |
|     unit->set_limit(unit->global_parameters);
 | |
|     if (join->optimize())
 | |
|     {
 | |
|       thd->where= save_where;
 | |
|       executed= 1;
 | |
|       thd->lex->current_select= save_select;
 | |
|       DBUG_RETURN(join->error ? join->error : 1);
 | |
|     }
 | |
|     if (!select_lex->uncacheable && thd->lex->describe && 
 | |
|         !(join->select_options & SELECT_DESCRIBE) && 
 | |
|         join->need_tmp && item->const_item())
 | |
|     {
 | |
|       /*
 | |
|         Force join->join_tmp creation, because this subquery will be replaced
 | |
|         by a simple select from the materialization temp table by optimize()
 | |
|         called by EXPLAIN and we need to preserve the initial query structure
 | |
|         so we can display it.
 | |
|        */
 | |
|       select_lex->uncacheable|= UNCACHEABLE_EXPLAIN;
 | |
|       select_lex->master_unit()->uncacheable|= UNCACHEABLE_EXPLAIN;
 | |
|       if (join->init_save_join_tab())
 | |
|         DBUG_RETURN(1);                        /* purecov: inspected */
 | |
|     }
 | |
|     if (item->engine_changed)
 | |
|     {
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|   }
 | |
|   if (select_lex->uncacheable && executed)
 | |
|   {
 | |
|     if (join->reinit())
 | |
|     {
 | |
|       thd->where= save_where;
 | |
|       thd->lex->current_select= save_select;
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     item->reset();
 | |
|     item->assigned((executed= 0));
 | |
|   }
 | |
|   if (!executed)
 | |
|   {
 | |
|     item->reset_value_registration();
 | |
|     JOIN_TAB *changed_tabs[MAX_TABLES];
 | |
|     JOIN_TAB **last_changed_tab= changed_tabs;
 | |
|     if (item->have_guarded_conds())
 | |
|     {
 | |
|       /*
 | |
|         For at least one of the pushed predicates the following is true:
 | |
|         We should not apply optimizations based on the condition that was
 | |
|         pushed down into the subquery. Those optimizations are ref[_or_null]
 | |
|         acceses. Change them to be full table scans.
 | |
|       */
 | |
|       for (uint i=join->const_tables ; i < join->tables ; i++)
 | |
|       {
 | |
|         JOIN_TAB *tab=join->join_tab+i;
 | |
|         if (tab && tab->keyuse)
 | |
|         {
 | |
|           for (uint i= 0; i < tab->ref.key_parts; i++)
 | |
|           {
 | |
|             bool *cond_guard= tab->ref.cond_guards[i];
 | |
|             if (cond_guard && !*cond_guard)
 | |
|             {
 | |
|               /* Change the access method to full table scan */
 | |
|               tab->save_read_first_record= tab->read_first_record;
 | |
|               tab->save_read_record= tab->read_record.read_record;
 | |
|               tab->read_first_record= init_read_record_seq;
 | |
|               tab->read_record.record= tab->table->record[0];
 | |
|               tab->read_record.thd= join->thd;
 | |
|               tab->read_record.ref_length= tab->table->file->ref_length;
 | |
|               *(last_changed_tab++)= tab;
 | |
|               break;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     join->exec();
 | |
| 
 | |
|     /* Enable the optimizations back */
 | |
|     for (JOIN_TAB **ptab= changed_tabs; ptab != last_changed_tab; ptab++)
 | |
|     {
 | |
|       JOIN_TAB *tab= *ptab;
 | |
|       tab->read_record.record= 0;
 | |
|       tab->read_record.ref_length= 0;
 | |
|       tab->read_first_record= tab->save_read_first_record; 
 | |
|       tab->read_record.read_record= tab->save_read_record;
 | |
|     }
 | |
|     executed= 1;
 | |
|     thd->where= save_where;
 | |
|     thd->lex->current_select= save_select;
 | |
|     DBUG_RETURN(join->error||thd->is_fatal_error);
 | |
|   }
 | |
|   thd->where= save_where;
 | |
|   thd->lex->current_select= save_select;
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| int subselect_union_engine::exec()
 | |
| {
 | |
|   char const *save_where= thd->where;
 | |
|   int res= unit->exec();
 | |
|   thd->where= save_where;
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Search for at least one row satisfying select condition
 | |
|  
 | |
|   SYNOPSIS
 | |
|     subselect_uniquesubquery_engine::scan_table()
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Scan the table using sequential access until we find at least one row
 | |
|     satisfying select condition.
 | |
|     
 | |
|     The caller must set this->empty_result_set=FALSE before calling this
 | |
|     function. This function will set it to TRUE if it finds a matching row.
 | |
| 
 | |
|   RETURN
 | |
|     FALSE - OK
 | |
|     TRUE  - Error
 | |
| */
 | |
| 
 | |
| int subselect_uniquesubquery_engine::scan_table()
 | |
| {
 | |
|   int error;
 | |
|   TABLE *table= tab->table;
 | |
|   DBUG_ENTER("subselect_uniquesubquery_engine::scan_table");
 | |
| 
 | |
|   if (table->file->inited)
 | |
|     table->file->ha_index_end();
 | |
|  
 | |
|   table->file->ha_rnd_init(1);
 | |
|   table->file->extra_opt(HA_EXTRA_CACHE,
 | |
|                          current_thd->variables.read_buff_size);
 | |
|   table->null_row= 0;
 | |
|   for (;;)
 | |
|   {
 | |
|     error=table->file->rnd_next(table->record[0]);
 | |
|     if (error && error != HA_ERR_END_OF_FILE)
 | |
|     {
 | |
|       error= report_error(table, error);
 | |
|       break;
 | |
|     }
 | |
|     /* No more rows */
 | |
|     if (table->status)
 | |
|       break;
 | |
| 
 | |
|     if (!cond || cond->val_int())
 | |
|     {
 | |
|       empty_result_set= FALSE;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   table->file->ha_rnd_end();
 | |
|   DBUG_RETURN(error != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Copy ref key and check for null parts in it
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_uniquesubquery_engine::copy_ref_key()
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Copy ref key and check for null parts in it.
 | |
| 
 | |
|   RETURN
 | |
|     FALSE - ok, index lookup key without keys copied.
 | |
|     TRUE  - an error occured while copying the key
 | |
| */
 | |
| 
 | |
| bool subselect_uniquesubquery_engine::copy_ref_key()
 | |
| {
 | |
|   DBUG_ENTER("subselect_uniquesubquery_engine::copy_ref_key");
 | |
| 
 | |
|   for (store_key **copy= tab->ref.key_copy ; *copy ; copy++)
 | |
|   {
 | |
|     tab->ref.key_err= (*copy)->copy();
 | |
| 
 | |
|     /*
 | |
|       When there is a NULL part in the key we don't need to make index
 | |
|       lookup for such key thus we don't need to copy whole key.
 | |
|       If we later should do a sequential scan return OK. Fail otherwise.
 | |
| 
 | |
|       See also the comment for the subselect_uniquesubquery_engine::exec()
 | |
|       function.
 | |
|     */
 | |
|     null_keypart= (*copy)->null_key;
 | |
|     bool top_level= ((Item_in_subselect *) item)->is_top_level_item();
 | |
|     if (null_keypart && !top_level)
 | |
|       break;
 | |
|     if ((tab->ref.key_err) & 1 || (null_keypart && top_level))
 | |
|     {
 | |
|       tab->table->status= STATUS_NOT_FOUND;
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Execute subselect
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_uniquesubquery_engine::exec()
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Find rows corresponding to the ref key using index access.
 | |
|     If some part of the lookup key is NULL, then we're evaluating
 | |
|       NULL IN (SELECT ... )
 | |
|     This is a special case, we don't need to search for NULL in the table,
 | |
|     instead, the result value is 
 | |
|       - NULL  if select produces empty row set
 | |
|       - FALSE otherwise.
 | |
| 
 | |
|     In some cases (IN subselect is a top level item, i.e. abort_on_null==TRUE)
 | |
|     the caller doesn't distinguish between NULL and FALSE result and we just
 | |
|     return FALSE. 
 | |
|     Otherwise we make a full table scan to see if there is at least one 
 | |
|     matching row.
 | |
|     
 | |
|     The result of this function (info about whether a row was found) is
 | |
|     stored in this->empty_result_set.
 | |
|   NOTE
 | |
|     
 | |
|   RETURN
 | |
|     FALSE - ok
 | |
|     TRUE  - an error occured while scanning
 | |
| */
 | |
| 
 | |
| int subselect_uniquesubquery_engine::exec()
 | |
| {
 | |
|   DBUG_ENTER("subselect_uniquesubquery_engine::exec");
 | |
|   int error;
 | |
|   TABLE *table= tab->table;
 | |
|   empty_result_set= TRUE;
 | |
|  
 | |
|   /* TODO: change to use of 'full_scan' here? */
 | |
|   if (copy_ref_key())
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   if (null_keypart)
 | |
|     DBUG_RETURN(scan_table());
 | |
|  
 | |
|   if (!table->file->inited)
 | |
|     table->file->ha_index_init(tab->ref.key, 0);
 | |
|   error= table->file->index_read(table->record[0],
 | |
|                                  tab->ref.key_buff,
 | |
|                                  make_prev_keypart_map(tab->ref.key_parts),
 | |
|                                  HA_READ_KEY_EXACT);
 | |
|   if (error &&
 | |
|       error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
 | |
|     error= report_error(table, error);
 | |
|   else
 | |
|   {
 | |
|     error= 0;
 | |
|     table->null_row= 0;
 | |
|     if (!table->status && (!cond || cond->val_int()))
 | |
|     {
 | |
|       ((Item_in_subselect *) item)->value= 1;
 | |
|       empty_result_set= FALSE;
 | |
|     }
 | |
|     else
 | |
|       ((Item_in_subselect *) item)->value= 0;
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(error != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| subselect_uniquesubquery_engine::~subselect_uniquesubquery_engine()
 | |
| {
 | |
|   /* Tell handler we don't need the index anymore */
 | |
|   tab->table->file->ha_index_end();
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Index-lookup subselect 'engine' - run the subquery
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_uniquesubquery_engine:exec()
 | |
|       full_scan 
 | |
| 
 | |
|   DESCRIPTION
 | |
|     The engine is used to resolve subqueries in form
 | |
| 
 | |
|       oe IN (SELECT key FROM tbl WHERE subq_where) 
 | |
| 
 | |
|     The value of the predicate is calculated as follows: 
 | |
|     1. If oe IS NULL, this is a special case, do a full table scan on
 | |
|        table tbl and search for row that satisfies subq_where. If such 
 | |
|        row is found, return NULL, otherwise return FALSE.
 | |
|     2. Make an index lookup via key=oe, search for a row that satisfies
 | |
|        subq_where. If found, return TRUE.
 | |
|     3. If check_null==TRUE, make another lookup via key=NULL, search for a 
 | |
|        row that satisfies subq_where. If found, return NULL, otherwise
 | |
|        return FALSE.
 | |
| 
 | |
|   TODO
 | |
|     The step #1 can be optimized further when the index has several key
 | |
|     parts. Consider a subquery:
 | |
|     
 | |
|       (oe1, oe2) IN (SELECT keypart1, keypart2 FROM tbl WHERE subq_where)
 | |
| 
 | |
|     and suppose we need to evaluate it for {oe1, oe2}=={const1, NULL}.
 | |
|     Current code will do a full table scan and obtain correct result. There
 | |
|     is a better option: instead of evaluating
 | |
| 
 | |
|       SELECT keypart1, keypart2 FROM tbl WHERE subq_where            (1)
 | |
| 
 | |
|     and checking if it has produced any matching rows, evaluate
 | |
|     
 | |
|       SELECT keypart2 FROM tbl WHERE subq_where AND keypart1=const1  (2)
 | |
| 
 | |
|     If this query produces a row, the result is NULL (as we're evaluating 
 | |
|     "(const1, NULL) IN { (const1, X), ... }", which has a value of UNKNOWN,
 | |
|     i.e. NULL).  If the query produces no rows, the result is FALSE.
 | |
| 
 | |
|     We currently evaluate (1) by doing a full table scan. (2) can be
 | |
|     evaluated by doing a "ref" scan on "keypart1=const1", which can be much
 | |
|     cheaper. We can use index statistics to quickly check whether "ref" scan
 | |
|     will be cheaper than full table scan.
 | |
| 
 | |
|   RETURN
 | |
|     0
 | |
|     1
 | |
| */
 | |
| 
 | |
| int subselect_indexsubquery_engine::exec()
 | |
| {
 | |
|   DBUG_ENTER("subselect_indexsubquery_engine::exec");
 | |
|   int error;
 | |
|   bool null_finding= 0;
 | |
|   TABLE *table= tab->table;
 | |
| 
 | |
|   ((Item_in_subselect *) item)->value= 0;
 | |
|   empty_result_set= TRUE;
 | |
|   null_keypart= 0;
 | |
| 
 | |
|   if (check_null)
 | |
|   {
 | |
|     /* We need to check for NULL if there wasn't a matching value */
 | |
|     *tab->ref.null_ref_key= 0;			// Search first for not null
 | |
|     ((Item_in_subselect *) item)->was_null= 0;
 | |
|   }
 | |
| 
 | |
|   /* Copy the ref key and check for nulls... */
 | |
|   if (copy_ref_key())
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   if (null_keypart)
 | |
|     DBUG_RETURN(scan_table());
 | |
| 
 | |
|   if (!table->file->inited)
 | |
|     table->file->ha_index_init(tab->ref.key, 1);
 | |
|   error= table->file->index_read(table->record[0],
 | |
|                                  tab->ref.key_buff,
 | |
|                                  make_prev_keypart_map(tab->ref.key_parts),
 | |
|                                  HA_READ_KEY_EXACT);
 | |
|   if (error &&
 | |
|       error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
 | |
|     error= report_error(table, error);
 | |
|   else
 | |
|   {
 | |
|     for (;;)
 | |
|     {
 | |
|       error= 0;
 | |
|       table->null_row= 0;
 | |
|       if (!table->status)
 | |
|       {
 | |
|         if ((!cond || cond->val_int()) && (!having || having->val_int()))
 | |
|         {
 | |
|           empty_result_set= FALSE;
 | |
|           if (null_finding)
 | |
|             ((Item_in_subselect *) item)->was_null= 1;
 | |
|           else
 | |
|             ((Item_in_subselect *) item)->value= 1;
 | |
|           break;
 | |
|         }
 | |
|         error= table->file->index_next_same(table->record[0],
 | |
|                                             tab->ref.key_buff,
 | |
|                                             tab->ref.key_length);
 | |
|         if (error && error != HA_ERR_END_OF_FILE)
 | |
|         {
 | |
|           error= report_error(table, error);
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (!check_null || null_finding)
 | |
|           break;			/* We don't need to check nulls */
 | |
|         *tab->ref.null_ref_key= 1;
 | |
|         null_finding= 1;
 | |
|         /* Check if there exists a row with a null value in the index */
 | |
|         if ((error= (safe_index_read(tab) == 1)))
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(error != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| uint subselect_single_select_engine::cols()
 | |
| {
 | |
|   DBUG_ASSERT(select_lex->join != 0); // should be called after fix_fields()
 | |
|   return select_lex->join->fields_list.elements;
 | |
| }
 | |
| 
 | |
| 
 | |
| uint subselect_union_engine::cols()
 | |
| {
 | |
|   DBUG_ASSERT(unit->is_prepared());  // should be called after fix_fields()
 | |
|   return unit->types.elements;
 | |
| }
 | |
| 
 | |
| 
 | |
| uint8 subselect_single_select_engine::uncacheable()
 | |
| {
 | |
|   return select_lex->uncacheable;
 | |
| }
 | |
| 
 | |
| 
 | |
| uint8 subselect_union_engine::uncacheable()
 | |
| {
 | |
|   return unit->uncacheable;
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_single_select_engine::exclude()
 | |
| {
 | |
|   select_lex->master_unit()->exclude_level();
 | |
| }
 | |
| 
 | |
| void subselect_union_engine::exclude()
 | |
| {
 | |
|   unit->exclude_level();
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_uniquesubquery_engine::exclude()
 | |
| {
 | |
|   //this never should be called
 | |
|   DBUG_ASSERT(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| table_map subselect_engine::calc_const_tables(TABLE_LIST *table)
 | |
| {
 | |
|   table_map map= 0;
 | |
|   for (; table; table= table->next_leaf)
 | |
|   {
 | |
|     TABLE *tbl= table->table;
 | |
|     if (tbl && tbl->const_table)
 | |
|       map|= tbl->map;
 | |
|   }
 | |
|   return map;
 | |
| }
 | |
| 
 | |
| 
 | |
| table_map subselect_single_select_engine::upper_select_const_tables()
 | |
| {
 | |
|   return calc_const_tables((TABLE_LIST *) select_lex->outer_select()->
 | |
| 			   leaf_tables);
 | |
| }
 | |
| 
 | |
| 
 | |
| table_map subselect_union_engine::upper_select_const_tables()
 | |
| {
 | |
|   return calc_const_tables((TABLE_LIST *) unit->outer_select()->leaf_tables);
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_single_select_engine::print(String *str)
 | |
| {
 | |
|   select_lex->print(thd, str);
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_union_engine::print(String *str)
 | |
| {
 | |
|   unit->print(str);
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_uniquesubquery_engine::print(String *str)
 | |
| {
 | |
|   str->append(STRING_WITH_LEN("<primary_index_lookup>("));
 | |
|   tab->ref.items[0]->print(str);
 | |
|   str->append(STRING_WITH_LEN(" in "));
 | |
|   str->append(tab->table->s->table_name.str, tab->table->s->table_name.length);
 | |
|   KEY *key_info= tab->table->key_info+ tab->ref.key;
 | |
|   str->append(STRING_WITH_LEN(" on "));
 | |
|   str->append(key_info->name);
 | |
|   if (cond)
 | |
|   {
 | |
|     str->append(STRING_WITH_LEN(" where "));
 | |
|     cond->print(str);
 | |
|   }
 | |
|   str->append(')');
 | |
| }
 | |
| 
 | |
| 
 | |
| void subselect_indexsubquery_engine::print(String *str)
 | |
| {
 | |
|   str->append(STRING_WITH_LEN("<index_lookup>("));
 | |
|   tab->ref.items[0]->print(str);
 | |
|   str->append(STRING_WITH_LEN(" in "));
 | |
|   str->append(tab->table->s->table_name.str, tab->table->s->table_name.length);
 | |
|   KEY *key_info= tab->table->key_info+ tab->ref.key;
 | |
|   str->append(STRING_WITH_LEN(" on "));
 | |
|   str->append(key_info->name);
 | |
|   if (check_null)
 | |
|     str->append(STRING_WITH_LEN(" checking NULL"));
 | |
|   if (cond)
 | |
|   {
 | |
|     str->append(STRING_WITH_LEN(" where "));
 | |
|     cond->print(str);
 | |
|   }
 | |
|   if (having)
 | |
|   {
 | |
|     str->append(STRING_WITH_LEN(" having "));
 | |
|     having->print(str);
 | |
|   }
 | |
|   str->append(')');
 | |
| }
 | |
| 
 | |
| /*
 | |
|   change select_result object of engine
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_single_select_engine::change_result()
 | |
|     si		new subselect Item
 | |
|     res		new select_result object
 | |
| 
 | |
|   RETURN
 | |
|     FALSE OK
 | |
|     TRUE  error
 | |
| */
 | |
| 
 | |
| bool subselect_single_select_engine::change_result(Item_subselect *si,
 | |
|                                                  select_subselect *res)
 | |
| {
 | |
|   item= si;
 | |
|   result= res;
 | |
|   return select_lex->join->change_result(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   change select_result object of engine
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_single_select_engine::change_result()
 | |
|     si		new subselect Item
 | |
|     res		new select_result object
 | |
| 
 | |
|   RETURN
 | |
|     FALSE OK
 | |
|     TRUE  error
 | |
| */
 | |
| 
 | |
| bool subselect_union_engine::change_result(Item_subselect *si,
 | |
|                                          select_subselect *res)
 | |
| {
 | |
|   item= si;
 | |
|   int rc= unit->change_result(res, result);
 | |
|   result= res;
 | |
|   return rc;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   change select_result emulation, never should be called
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_single_select_engine::change_result()
 | |
|     si		new subselect Item
 | |
|     res		new select_result object
 | |
| 
 | |
|   RETURN
 | |
|     FALSE OK
 | |
|     TRUE  error
 | |
| */
 | |
| 
 | |
| bool subselect_uniquesubquery_engine::change_result(Item_subselect *si,
 | |
|                                                   select_subselect *res)
 | |
| {
 | |
|   DBUG_ASSERT(0);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Report about presence of tables in subquery
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_single_select_engine::no_tables()
 | |
| 
 | |
|   RETURN
 | |
|     TRUE  there are not tables used in subquery
 | |
|     FALSE there are some tables in subquery
 | |
| */
 | |
| bool subselect_single_select_engine::no_tables()
 | |
| {
 | |
|   return(select_lex->table_list.elements == 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check statically whether the subquery can return NULL
 | |
| 
 | |
|   SINOPSYS
 | |
|     subselect_single_select_engine::may_be_null()
 | |
| 
 | |
|   RETURN
 | |
|     FALSE  can guarantee that the subquery never return NULL
 | |
|     TRUE   otherwise
 | |
| */
 | |
| bool subselect_single_select_engine::may_be_null()
 | |
| {
 | |
|   return ((no_tables() && !join->conds && !join->having) ? maybe_null : 1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Report about presence of tables in subquery
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_union_engine::no_tables()
 | |
| 
 | |
|   RETURN
 | |
|     TRUE  there are not tables used in subquery
 | |
|     FALSE there are some tables in subquery
 | |
| */
 | |
| bool subselect_union_engine::no_tables()
 | |
| {
 | |
|   for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
 | |
|   {
 | |
|     if (sl->table_list.elements)
 | |
|       return FALSE;
 | |
|   }
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Report about presence of tables in subquery
 | |
| 
 | |
|   SYNOPSIS
 | |
|     subselect_uniquesubquery_engine::no_tables()
 | |
| 
 | |
|   RETURN
 | |
|     TRUE  there are not tables used in subquery
 | |
|     FALSE there are some tables in subquery
 | |
| */
 | |
| 
 | |
| bool subselect_uniquesubquery_engine::no_tables()
 | |
| {
 | |
|   /* returning value is correct, but this method should never be called */
 | |
|   return 0;
 | |
| }
 | 
