List:Commits« Previous MessageNext Message »
From:Sergey Petrunia Date:May 22 2007 7:53pm
Subject:bk commit into 5.2 tree (sergefp:1.2456)
View as plain text  
Below is the list of changes that have just been committed into a local
5.2 repository of psergey. When psergey does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2007-05-22 23:53:44+04:00, sergefp@stripped +31 -0
  WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
  WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
  Combined into one commit

  mysql-test/r/kill.result@stripped, 2007-05-22 23:53:38+04:00, sergefp@stripped +1 -1
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Updated test results

  mysql-test/r/subselect.result@stripped, 2007-05-22 23:53:38+04:00, sergefp@stripped +26 -26
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Updated test results

  mysql-test/r/subselect2.result@stripped, 2007-05-22 23:53:38+04:00, sergefp@stripped +6 -6
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Updated test results

  mysql-test/r/subselect3.result@stripped, 2007-05-22 23:53:38+04:00, sergefp@stripped +5 -5
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Updated test results

  mysql-test/r/subselect_sj.result@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +196 -0
    New BitKeeper file ``mysql-test/r/subselect_sj.result''

  mysql-test/r/subselect_sj.result@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +0 -0

  mysql-test/r/subselect_sj2.result@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +267 -0
    New BitKeeper file ``mysql-test/r/subselect_sj2.result''

  mysql-test/r/subselect_sj2.result@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +0 -0

  mysql-test/t/kill.test@stripped, 2007-05-22 23:53:38+04:00, sergefp@stripped +1 -2
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Updated testcase

  mysql-test/t/subselect_sj.test@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +88 -0
    New BitKeeper file ``mysql-test/t/subselect_sj.test''

  mysql-test/t/subselect_sj.test@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +0 -0

  mysql-test/t/subselect_sj2.test@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +147 -0
    New BitKeeper file ``mysql-test/t/subselect_sj2.test''

  mysql-test/t/subselect_sj2.test@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +0 -0

  sql/item.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +21 -1
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +10 -1
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item_cmpfunc.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +38 -0
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item_cmpfunc.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +1 -0
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item_func.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +25 -1
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item_func.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +1 -0
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item_row.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +12 -0
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item_row.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +1 -0
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item_subselect.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +6 -2
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/item_subselect.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +20 -3
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added Item_XXX::fix_after_pullout()

  sql/mysql_priv.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +6 -0
    Added print_keyuse_array(), dump_TABLE_LIST_graph() debug functions

  sql/sql_array.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +5 -0
    Added Dynamic_array::clear()

  sql/sql_base.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +5 -0
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Use THD::thd_marker to track if we're doing fix_fields() for an AND-part 
      of the WHERE/ON clause 

  sql/sql_bitmap.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +57 -0
    Added Table_map_iterator

  sql/sql_class.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +2 -0
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Added THD::thd_marker

  sql/sql_select.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +1755 -167
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Statement preparation phoase: Convert appropriate subquery predicates to 
      semi-join TABLE_LIST nests 
    - Join optimization: mark tables that can be pulled out of semi-join nests.
    - Plan refinement: set up temptable-based duplicate elimination (use FirstMatch 
      in the confluent case)
    - Join exection: Invoke duplicate weedout functions as necessary
    - EXPLAIN [EXTENDED] output changed to display the new changes.

  sql/sql_select.h@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +136 -12
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Let join execution runtime do duplicate elimination

  sql/sql_show.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +4 -3
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Changed the parameters of create_myisam_from_heap()

  sql/sql_test.cc@stripped, 2007-05-22 23:53:39+04:00, sergefp@stripped +238 -0
    Added print_keyuse_array, dump_TABLE_LIST_graph debug functions

  sql/sql_union.cc@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +15 -1
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Changed the parameters of create_myisam_from_heap()
    - Invoke flatten_subqueries() for UNION members

  sql/sql_update.cc@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +3 -1
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Changed the parameters of create_myisam_from_heap()

  sql/table.h@stripped, 2007-05-22 23:53:40+04:00, sergefp@stripped +6 -1
    WL#3740 "Subquery optimization: Semijoin: Pull-out of inner tables" &
    WL#3741 "Subquery optimization: Semijoin: Duplicate elimination strategy"
    - Now TABLE_LIST can also represent a semi-join nest.

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	sergefp
# Host:	pylon.mylan
# Root:	/home/psergey/mysql-5.2-subq-r4

--- 1.186/sql/item.cc	2007-05-22 23:53:55 +04:00
+++ 1.187/sql/item.cc	2007-05-22 23:53:55 +04:00
@@ -1984,6 +1984,19 @@
 }
 
 
+void Item_field::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+  if (new_parent == depended_from)
+    depended_from= NULL;
+  Name_resolution_context *ctx= new Name_resolution_context();
+  ctx->outer_context= NULL; // We don't build a complete name resolver
+  ctx->select_lex= new_parent;
+  ctx->first_name_resolution_table= context->first_name_resolution_table;
+  ctx->last_name_resolution_table=  context->last_name_resolution_table;
+  this->context=ctx;
+}
+
+
 Item *Item_field::get_tmp_table_item(THD *thd)
 {
   Item_field *new_item= new Item_field(thd, this);
@@ -3930,7 +3943,6 @@
   return TRUE;
 }
 
-
 Item *Item_field::safe_charset_converter(CHARSET_INFO *tocs)
 {
   no_const_subst= 1;
@@ -5587,6 +5599,14 @@
   table_name= outer_field->table_name;
   return Item_direct_ref::fix_fields(thd, reference);
 }
+
+
+void Item_outer_ref::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+  if (depended_from == new_parent)
+    *ref= outer_field;
+}
+
 
 /*
   Compare two view column references for equality.

--- 1.206/sql/item.h	2007-05-22 23:53:55 +04:00
+++ 1.207/sql/item.h	2007-05-22 23:53:55 +04:00
@@ -443,7 +443,8 @@
 typedef void (*Cond_traverser) (const Item *item, void *arg);
 
 
-class Item {
+class Item
+{
   Item(const Item &);			/* Prevent use of these */
   void operator=(Item &);
 public:
@@ -521,6 +522,12 @@
   Field *make_string_field(TABLE *table);
   virtual bool fix_fields(THD *, Item **);
   /*
+    Fix after some tables has been pulled out. Basically re-calculate all
+    attributes that are dependent on the tables.
+  */
+  virtual void fix_after_pullout(st_select_lex *new_parent, Item **ref) {};
+
+  /*
     should be used in case where we are sure that we do not need
     complete fix_fields() procedure.
   */
@@ -1351,6 +1358,7 @@
   bool send(Protocol *protocol, String *str_arg);
   void reset_field(Field *f);
   bool fix_fields(THD *, Item **);
+  void fix_after_pullout(st_select_lex *new_parent, Item **ref);
   void make_field(Send_field *tmp_field);
   int save_in_field(Field *field,bool no_conversions);
   void save_org_in_field(Field *field);
@@ -2088,6 +2096,7 @@
     outer_field->save_org_in_field(result_field);
   }
   bool fix_fields(THD *, Item **);
+  void fix_after_pullout(st_select_lex *new_parent, Item **ref);
   table_map used_tables() const
   {
     return (*ref)->const_item() ? 0 : OUTER_REF_TABLE_BIT;

--- 1.248/sql/item_cmpfunc.cc	2007-05-22 23:53:55 +04:00
+++ 1.249/sql/item_cmpfunc.cc	2007-05-22 23:53:55 +04:00
@@ -2795,11 +2795,15 @@
   DBUG_ASSERT(fixed == 0);
   List_iterator<Item> li(list);
   Item *item;
+  void *orig_thd_marker= thd->thd_marker;
 #ifndef EMBEDDED_LIBRARY
   char buff[sizeof(char*)];			// Max local vars in function
 #endif
   not_null_tables_cache= used_tables_cache= 0;
   const_item_cache= 1;
+
+  if (functype() == COND_OR_FUNC)
+    thd->thd_marker= 0;
   /*
     and_table_cache is the value that Item_cond_or() returns for
     not_null_tables()
@@ -2858,10 +2862,44 @@
       maybe_null=1;
   }
   thd->lex->current_select->cond_count+= list.elements;
+  thd->thd_marker= orig_thd_marker;
   fix_length_and_dec();
   fixed= 1;
   return FALSE;
 }
+
+
+void Item_cond::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+  List_iterator<Item> li(list);
+  Item *item;
+
+  used_tables_cache=0;
+  const_item_cache=1;
+
+  and_tables_cache= ~(table_map) 0; // Here and below we do as fix_fields does
+  not_null_tables_cache= 0;
+
+  while ((item=li++))
+  {
+    table_map tmp_table_map;
+    item->fix_after_pullout(new_parent, li.ref());
+    item= *li.ref();
+    used_tables_cache|= item->used_tables();
+    const_item_cache&= item->const_item();
+
+    if (item->const_item())
+      and_tables_cache= (table_map) 0;
+    else
+    {
+      tmp_table_map= item->not_null_tables();
+      not_null_tables_cache|= tmp_table_map;
+      and_tables_cache&= tmp_table_map;
+      const_item_cache= FALSE;
+    }  
+  }
+}
+
 
 bool Item_cond::walk(Item_processor processor, bool walk_subquery, byte *arg)
 {

--- 1.151/sql/item_cmpfunc.h	2007-05-22 23:53:55 +04:00
+++ 1.152/sql/item_cmpfunc.h	2007-05-22 23:53:55 +04:00
@@ -1284,6 +1284,7 @@
   bool add(Item *item) { return list.push_back(item); }
   void add_at_head(List<Item> *nlist) { list.prepand(nlist); }
   bool fix_fields(THD *, Item **ref);
+  void fix_after_pullout(st_select_lex *new_parent, Item **ref);
 
   enum Type type() const { return COND_ITEM; }
   List<Item>* argument_list() { return &list; }

--- 1.357/sql/item_func.cc	2007-05-22 23:53:55 +04:00
+++ 1.358/sql/item_func.cc	2007-05-22 23:53:55 +04:00
@@ -141,10 +141,11 @@
 {
   DBUG_ASSERT(fixed == 0);
   Item **arg,**arg_end;
+  void *save_thd_marker= thd->thd_marker;
 #ifndef EMBEDDED_LIBRARY			// Avoid compiler warning
   char buff[STACK_BUFF_ALLOC];			// Max argument in function
 #endif
-
+  thd->thd_marker= 0;
   used_tables_cache= not_null_tables_cache= 0;
   const_item_cache=1;
 
@@ -190,7 +191,30 @@
   if (thd->net.report_error) // An error inside fix_length_and_dec occured
     return TRUE;
   fixed= 1;
+  thd->thd_marker= save_thd_marker;
   return FALSE;
+}
+
+
+void Item_func::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+  Item **arg,**arg_end;
+
+  used_tables_cache= not_null_tables_cache= 0;
+  const_item_cache=1;
+
+  if (arg_count)
+  {
+    for (arg=args, arg_end=args+arg_count; arg != arg_end ; arg++)
+    {
+      (*arg)->fix_after_pullout(new_parent, arg);
+      Item *item= *arg;
+
+      used_tables_cache|=     item->used_tables();
+      not_null_tables_cache|= item->not_null_tables();
+      const_item_cache&=      item->const_item();
+    }
+  }
 }
 
 

--- 1.161/sql/item_func.h	2007-05-22 23:53:55 +04:00
+++ 1.162/sql/item_func.h	2007-05-22 23:53:55 +04:00
@@ -116,6 +116,7 @@
   // Constructor used for Item_cond_and/or (see Item comment)
   Item_func(THD *thd, Item_func *item);
   bool fix_fields(THD *, Item **ref);
+  void fix_after_pullout(st_select_lex *new_parent, Item **ref);
   table_map used_tables() const;
   table_map not_null_tables() const;
   void update_used_tables();

--- 1.486/sql/mysql_priv.h	2007-05-22 23:53:55 +04:00
+++ 1.487/sql/mysql_priv.h	2007-05-22 23:53:55 +04:00
@@ -1464,8 +1464,14 @@
 void TEST_filesort(SORT_FIELD *sortorder,uint s_length);
 void print_plan(JOIN* join,uint idx, double record_count, double read_time,
                 double current_read_time, const char *info);
+void print_keyuse_array(DYNAMIC_ARRAY *keyuse_array);
+#define EXTRA_DEBUG_DUMP_TABLE_LISTS
+#ifdef EXTRA_DEBUG_DUMP_TABLE_LISTS
+void dump_TABLE_LIST_graph(SELECT_LEX *select_lex, TABLE_LIST* tl);
+#endif
 #endif
 void mysql_print_status();
+
 /* key.cc */
 int find_ref_key(KEY *key, uint key_count, byte *record, Field *field,
                  uint *key_length);

--- 1.376/sql/sql_base.cc	2007-05-22 23:53:55 +04:00
+++ 1.377/sql/sql_base.cc	2007-05-22 23:53:55 +04:00
@@ -5874,6 +5874,7 @@
   SELECT_LEX *select_lex= thd->lex->current_select;
   Query_arena *arena= thd->stmt_arena, backup;
   TABLE_LIST *table= NULL;	// For HP compilers
+  void *save_thd_marker= thd->thd_marker;
   /*
     it_is_update set to TRUE when tables of primary SELECT_LEX (SELECT_LEX
     which belong to LEX, i.e. most up SELECT) will be updated by
@@ -5901,6 +5902,7 @@
       goto err_no_arena;
   }
 
+  thd->thd_marker= (void*)1;
   if (*conds)
   {
     thd->where="where clause";
@@ -5908,6 +5910,7 @@
 	(*conds)->check_cols(1))
       goto err_no_arena;
   }
+  thd->thd_marker= save_thd_marker;
 
   /*
     Apply fix_fields() to all ON clauses at all levels of nesting,
@@ -5923,6 +5926,7 @@
       if (embedded->on_expr)
       {
         /* Make a join an a expression */
+        thd->thd_marker= (void*)embedded;
         thd->where="on clause";
         if (!embedded->on_expr->fixed &&
             embedded->on_expr->fix_fields(thd, &embedded->on_expr) ||
@@ -5947,6 +5951,7 @@
       }
     }
   }
+  thd->thd_marker= save_thd_marker;
 
   if (!thd->stmt_arena->is_conventional())
   {

--- 1.341/sql/sql_class.h	2007-05-22 23:53:55 +04:00
+++ 1.342/sql/sql_class.h	2007-05-22 23:53:55 +04:00
@@ -954,6 +954,8 @@
   /* container for handler's private per-connection data */
   void *ha_data[MAX_HA];
 
+  /* Place to store various things */
+  void *thd_marker;
 #ifndef MYSQL_CLIENT
   int binlog_setup_trx_data();
 

--- 1.494/sql/sql_select.cc	2007-05-22 23:53:55 +04:00
+++ 1.495/sql/sql_select.cc	2007-05-22 23:53:55 +04:00
@@ -81,7 +81,7 @@
 static bool make_simple_join(JOIN *join,TABLE *tmp_table);
 static void make_outerjoin_info(JOIN *join);
 static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *item);
-static void make_join_readinfo(JOIN *join, ulonglong options);
+static void make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after);
 static bool only_eq_ref_tables(JOIN *join, ORDER *order, table_map tables);
 static void update_depend_map(JOIN *join);
 static void update_depend_map(JOIN *join, ORDER *order);
@@ -99,7 +99,7 @@
                                              COND_EQUAL *cond_equal,
                                              void *table_join_idx);
 static COND *simplify_joins(JOIN *join, List<TABLE_LIST> *join_list,
-                            COND *conds, bool top);
+                            COND *conds, bool top, bool in_sj);
 static bool check_interleaving_with_nj(JOIN_TAB *last, JOIN_TAB *next);
 static void restore_prev_nj_state(JOIN_TAB *last);
 static void reset_nj_counters(List<TABLE_LIST> *join_list);
@@ -111,7 +111,9 @@
 			   Item::cond_result *cond_value);
 static bool const_expression_in_where(COND *conds,Item *item, Item **comp_item);
 static bool open_tmp_table(TABLE *table);
-static bool create_myisam_tmp_table(TABLE *table,TMP_TABLE_PARAM *param,
+static bool create_myisam_tmp_table(TABLE *table, KEY *keyinfo, 
+                                    MI_COLUMNDEF *start_recinfo,
+                                    MI_COLUMNDEF **recinfo,
 				    ulonglong options);
 static int do_select(JOIN *join,List<Item> *fields,TABLE *tmp_table,
 		     Procedure *proc);
@@ -235,6 +237,7 @@
   {
     SELECT_LEX_UNIT *unit= &lex->unit;
     unit->set_limit(unit->global_parameters);
+    thd->thd_marker= 0;
     /*
       'options' of mysql_select will be set in JOIN, as far as JOIN for
       every PS/SP execution new, we will not need reset this flag if 
@@ -331,6 +334,7 @@
   return res;
 }
 
+#define MAGIC_IN_WHERE_TOP_LEVEL 10
 /*
   Function to setup clauses without sum functions
 */
@@ -440,15 +444,73 @@
   if (!thd->lex->view_prepare_mode)
   {
     Item_subselect *subselect;
-    /* Is it subselect? */
+    /* Are we in a subquery predicate? */
     if ((subselect= select_lex->master_unit()->item))
     {
       Item_subselect::trans_res res;
-      if ((res= subselect->select_transformer(this)) !=
-	  Item_subselect::RES_OK)
+      bool do_delay= TRUE;
+      /*
+        Check if we're in subquery that is a candidate for flattening into a
+        semi-join (which is done done in flatten_subqueries()). The
+        requirements are:
+          1. Subquery predicate is an IN/=ANY subq predicate
+          2. Subquery is a single SELECT (not a UNION)
+          3. Subquery does not have GROUP BY or ORDER BY
+          4. Subquery does not use aggregate functions or HAVING
+          5. Subquery predicate is at the AND-top-level of ON/WHERE clause
+
+          (*). We are not in a subquery of a single table UPDATE/DELETE that 
+               doesn't have a JOIN (TODO: We should handle this at some
+               point by switching to multi-table UPDATE/DELETE)
+
+          (**). We're not in a confluent table-less subquery, like
+                "SELECT 1". 
+      */
+      if (subselect->substype() == Item_subselect::IN_SUBS &&           // 1
+          !select_lex->master_unit()->first_select()->next_select() &&  // 2
+          !select_lex->group_list.elements && !order &&                 // 3
+          !having && !select_lex->with_sum_func &&                      // 4
+          thd->thd_marker &&                                            // 5
+          select_lex->outer_select()->join &&                           // (*)
+          select_lex->master_unit()->first_select()->leaf_tables &&     // (**) 
+          do_delay)
       {
-        select_lex->fix_prepare_information(thd, &conds, &having);
-	DBUG_RETURN((res == Item_subselect::RES_ERROR));
+        fprintf(stderr, "subq is an sj candidate\n");
+        Item_in_subselect *in_subs= (Item_in_subselect*)subselect;
+
+        if (thd->stmt_arena->state != Query_arena::PREPARED)
+        {
+          if (!in_subs->left_expr->fixed &&
+               in_subs->left_expr->fix_fields(thd, &in_subs->left_expr))
+          {
+            DBUG_RETURN(-1);
+          }
+          /*
+            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 (subselect->substype() == Item_subselect::IN_SUBS &&
+             (select_lex->item_list.elements != 
+              ((Item_in_subselect*)subselect)->left_expr->cols()))
+          {
+            my_error(ER_OPERAND_COLUMNS, MYF(0), ((Item_in_subselect*)subselect)->left_expr->cols());
+            DBUG_RETURN(-1);
+          }
+        }
+
+        /* Register the subquery for further processing */
+        select_lex->outer_select()->join->sj_subselects.append(in_subs);
+        in_subs->expr_join_nest= (TABLE_LIST*)thd->thd_marker;
+      }
+      else
+      {
+        fprintf(stderr, "subq is not an sj candidate\n");
+        if ((res= subselect->select_transformer(this)) !=
+            Item_subselect::RES_OK)
+        {
+          select_lex->fix_prepare_information(thd, &conds, &having);
+          DBUG_RETURN((res == Item_subselect::RES_ERROR));
+        }
       }
     }
   }
@@ -655,6 +717,283 @@
 }
 
 
+static bool bitmap_covers(const table_map x, const table_map y)
+{
+  return !test(y & ~x);
+}
+
+
+/*
+  Check if the table's rowid is included in the temptable
+
+  SYNOPSIS
+    sj_table_is_included()
+      join      The join
+      join_tab  The table to be checked
+
+  DESCRIPTION
+    SemiJoinDuplicateElimination: check the table's rowid should be included
+    in the temptable. This is so if
+
+    1. The table is not embedded within some semi-join nest
+    2. The has been pulled out of a semi-join nest, or
+
+    3. The table is functionally dependent on some previous table
+
+    [4. This is also true for constant tables that can't be
+        NULL-complemented but this function is not called for such tables]
+
+  RETURN
+    TRUE  - Include table's rowid
+    FALSE - Don't
+*/
+
+static bool sj_table_is_included(JOIN *join, JOIN_TAB *join_tab)
+{
+  if (join_tab->emb_sj_nest)
+    return FALSE;
+  
+  /* Check if this table is functionally dependent on the tables that
+     are within the same outer join nest
+  */
+  TABLE_LIST *embedding= join_tab->table->pos_in_table_list->embedding;
+  if (join_tab->type == JT_EQ_REF)
+  {
+    Table_map_iterator it(join_tab->ref.depend_map & ~PSEUDO_TABLE_BITS);
+    uint idx;
+    while ((idx= it.next_bit())!=Table_map_iterator::BITMAP_END)
+    {
+      JOIN_TAB *ref_tab= join->join_tab + idx;
+      if (embedding == ref_tab->table->pos_in_table_list->embedding)
+        return TRUE;
+    }
+    /* Ok, functionally dependent */
+    return FALSE;
+  }
+  /* Not functionally dependent => need to include*/
+  return TRUE;
+}
+
+
+TABLE *create_duplicate_weedout_tmp_table(THD *thd, uint uniq_tuple_length_arg,
+                                          SJ_TMP_TABLE *sjtbl);
+
+
+/*
+  SemiJoinDuplicateElimination: Setup the temporary tables
+  
+  SYNOPSIS
+    setup_semijoin_dups_elimination()
+      join  Join to process
+
+  DESCRIPTION
+    SemiJoinDuplicateElimination strategy: setup the temporary tables needed 
+    to handle semi-joins with strategy.
+
+    We have the join order. SJ-nest(s) have their members "dispersed" in the
+    join order. Each SJ-nest has a range that needs to be covered by a
+    temptable. The range is
+
+     [first-inner-table; latest(last-inner-table, last-correlated-table)]
+   
+    If ranges of tow SJ-nests have non-empty intersection, they need to be
+    joined into one range.
+
+    This function calculates those ranges, joining them if necessary.
+
+    This optimization is performed at plan refinement stage so it is
+    naturally a per-EXECUTE optimization.
+
+  RETURN
+    FALSE  OK 
+    TRUE   Out of memory error
+*/
+
+static
+int setup_semijoin_dups_elimination(JOIN *join, ulonglong options, uint no_jbuf_after)
+{
+  THD *thd= join->thd;
+  SJ_TMP_TABLE **next_sjtbl_ptr= &join->sj_tmp_tables;
+
+  SJ_TMP_TABLE::TAB  sjtabs[MAX_TABLES];
+  uint jt_start_idx;  // Start of dups-producing join order range 
+  uint jt_rowid_offset; // # tuple bytes are already occupied (w/o NULL bytes)
+  uint jt_null_bits;    // # null bits in tuple bytes
+  SJ_TMP_TABLE::TAB *jt_first_tab; // First temptable participant
+  SJ_TMP_TABLE::TAB *jt_last_tab;  // Last temptable participant + 1
+  table_map emb_sj_map= 0;  /* A bitmap of sj-nests we're currently in */
+  table_map cur_map= join->const_table_map;
+  bool dealing_with_jbuf= FALSE;
+  uint i;
+
+  DBUG_ENTER("setup_semijoin_dups_elimination");
+
+  jt_last_tab= jt_first_tab= sjtabs;
+
+  for (i=join->const_tables ; i < join->tables ; i++)
+  {
+    JOIN_TAB *tab=join->join_tab+i;
+    TABLE *table=tab->table;
+    cur_map |= table->map;
+
+    if (tab->emb_sj_nest)
+    {
+      if (!emb_sj_map)
+      {
+        jt_start_idx= i;
+        jt_rowid_offset= 0;
+        jt_null_bits= 0;
+      }
+      /* 
+        If we're entering the coverage of another SJ-nest, record that we need
+        all of its tables
+      */
+      emb_sj_map |= tab->emb_sj_nest->sj_inner_tables |
+                    tab->emb_sj_nest->nested_join->sj_depends_on;
+    }
+
+    /*
+      If we're in SJ-nest and current table should be included in it, add
+      it.
+    */
+    if (emb_sj_map)
+    {
+      tab->inside_sj_dups_range= TRUE;
+      if (sj_table_is_included(join, tab))
+      {
+        /* Get a TAB for this Item. */
+        jt_last_tab->join_tab= tab;
+        jt_last_tab->rowid_offset= jt_rowid_offset;
+        jt_rowid_offset += table->file->ref_length;
+        if (tab->table->maybe_null)
+        {
+          jt_last_tab->null_bit= jt_null_bits++;
+        }
+        jt_last_tab++;
+        tab->table->prepare_for_position();
+        tab->rowid_keep_flags= JOIN_TAB::KEEP_ROWID | JOIN_TAB::CALL_POSITION;
+      }
+    
+      if (i != join->const_tables && !(options & SELECT_NO_JOIN_CACHE) &&
+          tab->type == JT_ALL && tab->use_quick != 2 && !tab->first_inner &&
+          i <= no_jbuf_after && !dealing_with_jbuf)
+      {
+        /*
+          This table uses join buffering, which invalidates the usual
+          assumptions.
+           x x [x  x]  x  [x x x] x [x x X* x] x
+
+          We'll have to use temptable-based elimination w/o prefix tables.
+          
+
+          All preceding temptables are eliminated.
+          include all of the preceding tables.
+        */
+        dealing_with_jbuf= TRUE;
+
+        /* Reset the current sj nest processor: */
+        jt_start_idx= 0;
+        jt_rowid_offset= 0;
+        jt_null_bits= 0;
+
+        // Remove all other tables:
+        join->sj_tmp_tables= NULL; 
+        next_sjtbl_ptr= &join->sj_tmp_tables;
+        jt_last_tab= jt_first_tab= sjtabs;
+
+        JOIN_TAB *prev_tab;
+        for (prev_tab= join->join_tab + join->const_tables; 
+             prev_tab != tab+1; prev_tab++)
+        {
+          /* Remove temptable-based weedout tables */
+          prev_tab->flush_weedout_table= NULL;
+          prev_tab->check_weed_out_table= NULL;
+
+          if (sj_table_is_included(join, prev_tab))
+          {
+            jt_last_tab->join_tab= prev_tab;
+            jt_last_tab->rowid_offset= jt_rowid_offset;
+            jt_rowid_offset += table->file->ref_length;
+            if (tab->table->maybe_null)
+            {
+              jt_last_tab->null_bit= jt_null_bits++;
+            }
+            jt_last_tab++;
+            prev_tab->table->prepare_for_position();
+            prev_tab->rowid_keep_flags= JOIN_TAB::KEEP_ROWID | JOIN_TAB::CALL_POSITION;
+          }
+        }
+      }
+
+      /*
+        Check if the partial join order now contains all tables of all 
+        leaving coverage of all semi-joins.
+      */
+      if (bitmap_covers(cur_map, emb_sj_map & ~PSEUDO_TABLE_BITS))
+      {
+        /* Ok, this table concludes the duplicate-producing range */
+        if (jt_first_tab != jt_last_tab)
+        {
+          /* Need to use temptable */
+          SJ_TMP_TABLE *sjtbl;
+          uint tabs_size= (jt_last_tab - jt_first_tab) *
+                          sizeof(SJ_TMP_TABLE::TAB);
+          if (!(sjtbl= (SJ_TMP_TABLE*)thd->alloc(sizeof(SJ_TMP_TABLE))) ||
+              !(sjtbl->tabs= (SJ_TMP_TABLE::TAB*) thd->alloc(tabs_size)))
+            DBUG_RETURN(TRUE);
+          memcpy(sjtbl->tabs, jt_first_tab, tabs_size);
+          sjtbl->tabs_end= sjtbl->tabs + (jt_last_tab - jt_first_tab);
+          sjtbl->start_idx= jt_start_idx;
+          sjtbl->end_idx=   i;
+          sjtbl->rowid_len= jt_rowid_offset;
+          sjtbl->null_bits= jt_null_bits;
+          sjtbl->null_bytes= (jt_null_bits + 7)/8;
+
+          *next_sjtbl_ptr= sjtbl;
+          next_sjtbl_ptr= &(sjtbl->next);
+          sjtbl->next= NULL;
+
+          sjtbl->tmp_table= 
+            create_duplicate_weedout_tmp_table(thd, 
+                                               sjtbl->rowid_len + 
+                                               sjtbl->null_bytes,
+                                               sjtbl);
+
+          join->join_tab[jt_start_idx].flush_weedout_table= sjtbl;
+          tab->check_weed_out_table= sjtbl;
+        }
+        else
+        {
+          /*
+            No need to use temptable, setup shortcutting from tab #i to 
+            "just before tab #jt_start_idx".
+          */
+          tab->do_firstmatch= join->join_tab + ((jt_start_idx > 0)? 
+                                                jt_start_idx - 1 : 0);
+        }
+        emb_sj_map= 0;
+      }
+    }
+  }
+  DBUG_RETURN(FALSE);
+}
+
+
+static void cleanup_sj_tmp_tables(JOIN *join)
+{
+  for (SJ_TMP_TABLE *sj_tbl= join->sj_tmp_tables; sj_tbl; 
+       sj_tbl= sj_tbl->next)
+  {
+    if (sj_tbl->tmp_table)
+    {
+      free_tmp_table(join->thd, sj_tbl->tmp_table);
+    }
+  }
+  join->sj_tmp_tables= NULL;
+}
+
+uint make_join_orderinfo(JOIN *join);
+
 /*
   global select optimisation.
   return 0 - success
@@ -720,7 +1059,7 @@
     sel->first_cond_optimization= 0;
 
     /* Convert all outer joins to inner joins if possible */
-    conds= simplify_joins(this, join_list, conds, TRUE);
+    conds= simplify_joins(this, join_list, conds, TRUE, FALSE);
     build_bitmap_for_nested_joins(join_list, 0);
 
     sel->prep_where= conds ? conds->copy_andor_structure(thd) : 0;
@@ -1107,12 +1446,18 @@
 	      (group_list && order) ||
 	      test(select_options & OPTION_BUFFER_RESULT)));
 
-  // No cache for MATCH
-  make_join_readinfo(this,
-		     (select_options & (SELECT_DESCRIBE |
-					SELECT_NO_JOIN_CACHE)) |
-		     (select_lex->ftfunc_list->elements ?
-		      SELECT_NO_JOIN_CACHE : 0));
+  uint no_jbuf_after= make_join_orderinfo(this);
+  ulonglong select_opts_for_readinfo= 
+    (select_options & (SELECT_DESCRIBE | SELECT_NO_JOIN_CACHE)) |
+    (select_lex->ftfunc_list->elements ?  SELECT_NO_JOIN_CACHE : 0);
+
+  sj_tmp_tables= NULL;
+  if (sj_nests.elements())
+    setup_semijoin_dups_elimination(this, select_opts_for_readinfo,
+                                    no_jbuf_after);
+
+  // No cache for MATCH == 'Don't use join buffering when we use MATCH'.
+  make_join_readinfo(this, select_opts_for_readinfo, no_jbuf_after);
 
   /* Perform FULLTEXT search before all regular searches */
   if (!(select_options & SELECT_DESCRIBE))
@@ -2042,6 +2387,9 @@
   DBUG_RETURN(error);
 }
 
+
+
+
 /*
   An entry point to single-unit select (a select without UNION).
 
@@ -2146,6 +2494,18 @@
     }
   }
 
+  if (!unit->item)
+  {
+    //dump_TABLE_LIST_graph(select_lex, select_lex->leaf_tables);
+    /* We're not in a subquery predicate */
+    if (join->flatten_subqueries())
+    {
+      thd->net.report_error= 1;
+      goto err;
+    }
+    //dump_TABLE_LIST_struct(select_lex, select_lex->leaf_tables);
+  }
+
   if ((err= join->optimize()))
   {
     goto err;					// 1
@@ -2188,11 +2548,601 @@
   DBUG_RETURN(join->error);
 }
 
+
+int subq_sj_candidate_cmp(Item_in_subselect* const *el1, 
+                          Item_in_subselect * const *el2)
+{
+  return ((*el1)->sj_convert_priority < (*el2)->sj_convert_priority) ? 1 : 
+         ( ((*el1)->sj_convert_priority == (*el2)->sj_convert_priority)? 0 : -1);
+}
+
+
+inline Item * and_items(Item* cond, Item *item)
+{
+  return (cond? (new Item_cond_and(cond, item)) : item);
+}
+
+
+static TABLE_LIST *alloc_join_nest(THD *thd)
+{
+  TABLE_LIST *tbl;
+  if (!(tbl= (TABLE_LIST*) thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST))+
+                                       sizeof(NESTED_JOIN))))
+    return NULL;
+  tbl->nested_join= (NESTED_JOIN*) ((byte*)tbl + 
+                                    ALIGN_SIZE(sizeof(TABLE_LIST)));
+  return tbl;
+}
+
+
+void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist)
+{
+  List_iterator<TABLE_LIST> it(*tlist);
+  TABLE_LIST *table;
+  while ((table= it++))
+  {
+    if (table->on_expr)
+      table->on_expr->fix_after_pullout(new_parent, &table->on_expr);
+    if (table->nested_join)
+      fix_list_after_tbl_changes(new_parent, &table->nested_join->join_list);
+  }
+}
+
+
+/*
+  Convert a subquery predicate into a TABLE_LIST semi-join nest
+
+  SYNOPSIS
+    convert_subq_to_sj()
+       parent_join  Parent join, the one that has subq_pred in its WHERE/ON 
+                    clause
+       subq_pred    Subquery predicate to be converted
+  
+  DESCRIPTION
+    Convert a subquery predicate into a TABLE_LIST semi-join nest. All the 
+    prerequisites are already checked, so the conversion is always successfull.
+
+    Prepared Statements: the transformation is permanent:
+     - Changes in TABLE_LIST structures are naturally permanent
+     - Item tree changes are performed on statement MEM_ROOT:
+        = we activate statement MEM_ROOT 
+        = this function is called before the first fix_prepare_information
+          call.
+
+    This is intended because the criteria for subquery-to-sj conversion remain
+    constant for the lifetime of the Prepared Statement.
+
+  RETURN
+    FALSE  OK
+    TRUE   Out of memory error
+*/
+
+bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred)
+{
+  SELECT_LEX *parent_lex= parent_join->select_lex;
+  TABLE_LIST *emb_tbl_nest= NULL;
+  List<TABLE_LIST> *emb_join_list= &parent_lex->top_join_list;
+  Query_arena *arena, backup;
+  THD *thd= parent_join->thd;
+  DBUG_ENTER("convert_subq_to_sj");
+  arena= thd->activate_stmt_arena_if_needed(&backup);
+
+  /*
+    1. Find out where to put the predicate into.
+     Note: for "t1 LEFT JOIN t2" this will be t2, a leaf.
+  */
+  if ((void*)subq_pred->expr_join_nest != (void*)1)
+  {
+    if (subq_pred->expr_join_nest->nested_join)
+    {
+      /*
+        We're dealing with
+
+          ... [LEFT] JOIN  ( ... ) ON (subquery AND whatever) ...
+
+        The sj-nest will be inserted into the brackets nest.
+      */
+      emb_tbl_nest=  subq_pred->expr_join_nest;
+      emb_join_list= &emb_tbl_nest->nested_join->join_list;
+    }
+    else if (!subq_pred->expr_join_nest->outer_join)
+    {
+      /*
+        We're dealing with
+
+          ... INNER JOIN tblX ON (subquery AND whatever) ...
+
+        The sj-nest will be tblX's "sibling", i.e. another child of its
+        parent. This is ok because tblX is joined as an inner join.
+      */
+      emb_tbl_nest= subq_pred->expr_join_nest->embedding;
+      if (emb_tbl_nest)
+        emb_join_list= &emb_tbl_nest->nested_join->join_list;
+    }
+    else if (!subq_pred->expr_join_nest->nested_join)
+    {
+      TABLE_LIST *outer_tbl= subq_pred->expr_join_nest;      
+      TABLE_LIST *wrap_nest;
+      /*
+        We're dealing with
+
+          ... LEFT JOIN tbl ON (on_expr AND subq_pred) ...
+
+        we'll need to convert it into:
+
+          ... LEFT JOIN ( tbl SJ (subq_tables) ) ON (on_expr AND subq_pred) ...
+                        |                      |
+                        |<----- wrap_nest ---->|
+        
+        Q:  other subqueries may be pointing to this element. What to do?
+        A1: simple solution: copy *subq_pred->expr_join_nest= *parent_nest.
+            But we'll need to fix other pointers.
+        A2: Another way: have TABLE_LIST::next_ptr so the following
+            subqueries know the table has been nested.
+        A3: changes in the TABLE_LIST::outer_join will make everything work
+            automatically.
+      */
+      if (!(wrap_nest= alloc_join_nest(parent_join->thd)))
+      {
+        DBUG_RETURN(TRUE);
+      }
+      wrap_nest->embedding= outer_tbl->embedding;
+      wrap_nest->join_list= outer_tbl->join_list;
+      wrap_nest->alias= (char*) "(sj-wrap)";
+
+      wrap_nest->nested_join->join_list.empty();
+      wrap_nest->nested_join->join_list.push_back(outer_tbl);
+
+      outer_tbl->embedding= wrap_nest;
+      outer_tbl->join_list= &wrap_nest->nested_join->join_list;
+
+      /*
+        wrap_nest will take place of outer_tbl, so move the outer join flag
+        and on_expr
+      */
+      wrap_nest->outer_join= outer_tbl->outer_join;
+      outer_tbl->outer_join= 0;
+
+      wrap_nest->on_expr= outer_tbl->on_expr;
+      outer_tbl->on_expr= NULL;
+
+      List_iterator<TABLE_LIST> li(*wrap_nest->join_list);
+      TABLE_LIST *tbl;
+      while ((tbl= li++))
+      {
+        if (tbl == outer_tbl)
+        {
+          li.replace(wrap_nest);
+          break;
+        }
+      }
+      /*
+        Ok now wrap_nest 'contains' outer_tbl and we're ready to add the 
+        semi-join nest into it
+      */
+      emb_join_list= &wrap_nest->nested_join->join_list;
+      emb_tbl_nest=  wrap_nest;
+    }
+  }
+
+  TABLE_LIST *sj_nest;
+  NESTED_JOIN *nested_join;
+  if (!(sj_nest= alloc_join_nest(parent_join->thd)))
+  {
+    DBUG_RETURN(TRUE);
+  }
+  nested_join= sj_nest->nested_join;
+
+  sj_nest->join_list= emb_join_list;
+  sj_nest->embedding= emb_tbl_nest;
+  sj_nest->alias= (char*) "(sj-nest)";
+  /* Nests do not participate in those 'chains', so: */
+  /* sj_nest->next_leaf= sj_nest->next_local= sj_nest->next_global == NULL*/
+  emb_join_list->push_back(sj_nest);
+
+  /* 
+    nested_join->used_tables and nested_join->not_null_tables are
+    initialized in simplify_joins().
+  */
+  
+  /* 
+    2. Walk through subquery's top list and set 'embedding' to point to the
+       sj-nest.
+  */
+  st_select_lex *subq_lex= subq_pred->unit->first_select();
+  nested_join->join_list.empty();
+  List_iterator_fast<TABLE_LIST> li(subq_lex->top_join_list);
+  TABLE_LIST *tl, *last_leaf;
+  while ((tl= li++))
+  {
+    tl->embedding= sj_nest;
+    tl->join_list= &nested_join->join_list;
+    nested_join->join_list.push_back(tl);
+  }
+  
+  /*
+    Reconnect the next_leaf chain.
+    TODO: Do we have to put subquery's tables at the end of the chain?
+          Inserting them at the beginning would be a bit faster.
+    NOTE: We actually insert them at the front! That's because the order is
+          reversed in this list.
+  */
+  for (tl= parent_lex->leaf_tables; tl->next_leaf; tl= tl->next_leaf);
+  tl->next_leaf= subq_lex->leaf_tables;
+  last_leaf= tl;
+
+  /*
+    Same as above for next_local chain
+    (a theory: a next_local chain always starts with ::leaf_tables
+     because view's tables are inserted after the view)
+  */
+  for (tl= parent_lex->leaf_tables; tl->next_local; tl= tl->next_local);
+  tl->next_local= subq_lex->leaf_tables;
+
+  /* A theory: no need to re-connect the next_global chain */
+
+  /* 3. Remove the original subquery predicate from the WHERE/ON */
+  *(subq_pred->ref_ptr)= new Item_int(1);
+  subq_pred->converted_to_sj= TRUE; // for subsequent executions
+  /*TODO: also reset the 'with_subselect' there. */
+
+  /* n. Adjust the parent_join->tables counter */
+  uint table_no= parent_join->tables;
+  /* n. Walk through child's tables and adjust table->map */
+  for (tl= subq_lex->leaf_tables; tl; tl= tl->next_leaf, table_no++)
+  {
+    tl->table->tablenr= table_no;
+    tl->table->map= ((table_map)1) << table_no;
+    SELECT_LEX *old_sl= tl->select_lex;
+    tl->select_lex= parent_join->select_lex; 
+    for(TABLE_LIST *emb= tl->embedding; emb && emb->select_lex == old_sl; emb= emb->embedding)
+      emb->select_lex= parent_join->select_lex;
+  }
+  parent_join->tables += subq_lex->join->tables;
+
+  /* 
+    Put the subquery's WHERE into semi-join's sj_on_expr
+    Add the subquery-induced equalities too.
+  */
+  if (!subq_pred->left_expr->fixed &&
+       subq_pred->left_expr->fix_fields(thd, &subq_pred->left_expr));
+
+  sj_nest->nested_join->sj_depends_on=  subq_pred->used_tables() |
+                                        subq_pred->left_expr->used_tables();
+  sj_nest->sj_on_expr= subq_lex->where;
+  if (subq_pred->left_expr->cols() == 1)
+  {
+    Item *item_eq= 
+      new Item_func_eq(subq_pred->left_expr, 
+                       subq_lex->ref_pointer_array[0]);
+    sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq);
+  }
+  else
+  {
+    for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
+    {
+      Item *item_eq= 
+        new Item_func_eq(subq_pred->left_expr->element_index(i), 
+                         subq_lex->ref_pointer_array[i]);
+      sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq);
+    }
+  }
+  /* Fix the created equality and AND */
+  sj_nest->sj_on_expr->fix_fields(parent_join->thd, &sj_nest->sj_on_expr);
+
+  /*
+    Walk through sj nest's WHERE and ON expressions and call
+    item->fix_table_changes() for all items.
+  */
+  sj_nest->sj_on_expr->fix_after_pullout(parent_lex, &sj_nest->sj_on_expr);
+  fix_list_after_tbl_changes(parent_lex, &sj_nest->nested_join->join_list);
+
+
+  /* Unlink the child select_lex so it doesn't show up in EXPLAIN: */
+  subq_lex->master_unit()->exclude_level();
+
+  DBUG_EXECUTE("where",print_where(sj_nest->sj_on_expr,"SJ-EXPR"););
+  /* Inject sj_on_expr into the parent's WHERE or ON */
+  if (emb_tbl_nest)
+  {
+    emb_tbl_nest->on_expr= and_items(emb_tbl_nest->on_expr, 
+                                     sj_nest->sj_on_expr);
+    emb_tbl_nest->on_expr->fix_fields(parent_join->thd, &emb_tbl_nest->on_expr);
+  }
+  else
+  {
+    /* Inject into the WHERE */
+    parent_join->conds= and_items(parent_join->conds, sj_nest->sj_on_expr);
+    parent_join->conds->fix_fields(parent_join->thd, &parent_join->conds);
+    parent_join->select_lex->where= parent_join->conds;
+  }
+
+  if (subq_lex->ftfunc_list->elements)
+  {
+    Item_func_match *ifm;
+    List_iterator_fast<Item_func_match> li(*(subq_lex->ftfunc_list));
+    while ((ifm= li++))
+      parent_lex->ftfunc_list->push_front(ifm);
+  }
+
+  if (arena)
+    thd->restore_active_arena(arena, &backup);
+  DBUG_RETURN(FALSE);
+}
+
+
+/*
+  Convert candidate subquery predicates to semi-joins
+
+  SYNOPSIS
+    JOIN::flatten_subqueries()
+ 
+  DESCRIPTION
+    Convert candidate subquery predicates to semi-joins.
+
+  RETURN 
+    FALSE  OK
+    TRUE   Error
+*/
+
+bool JOIN::flatten_subqueries()
+{
+  DBUG_ENTER("JOIN::flatten_subqueries");
+  Item_in_subselect **in_subq;
+  Item_in_subselect **in_subq_end;
+
+  if (sj_subselects.elements() == 0)
+    DBUG_RETURN(FALSE);
+
+  /* 1. Fix children subqueries */
+  for (in_subq= sj_subselects.front(), in_subq_end= sj_subselects.back(); 
+       in_subq != in_subq_end; in_subq++)
+  {
+    JOIN *child_join= (*in_subq)->unit->first_select()->join;
+    child_join->outer_tables = child_join->tables;
+    if (child_join->flatten_subqueries())
+      DBUG_RETURN(TRUE);
+    (*in_subq)->sj_convert_priority= 
+      (*in_subq)->is_correlated * MAX_TABLES + child_join->outer_tables;
+  }
+
+  //dump_TABLE_LIST_struct(select_lex, select_lex->leaf_tables);
+  /* 
+    2. Pick which subqueries to convert:
+      sort the subquery array
+      - prefer correlated subqueries over uncorrelated;
+      - prefer subqueries that have greater number of outer tables;
+  */
+  sj_subselects.sort(subq_sj_candidate_cmp);
+  // #tables-in-parent-query + #tables-in-subquery < MAX_TABLES
+  bool do_converts= TRUE;
+  for (in_subq= sj_subselects.front(); 
+       in_subq != in_subq_end && 
+       tables + ((*in_subq)->sj_convert_priority % MAX_TABLES) < MAX_TABLES;
+       in_subq++)
+  {
+    if (!do_converts)
+      break;
+    if (convert_subq_to_sj(this, *in_subq))
+      DBUG_RETURN(TRUE);
+  }
+
+  /* 3. Finalize those we didn't convert */
+  for (; in_subq!= in_subq_end; in_subq++)
+  {
+    JOIN *child_join= (*in_subq)->unit->first_select()->join;
+    Item_subselect::trans_res res;
+    (*in_subq)->changed= 0;
+    (*in_subq)->fixed= 0;
+    res= (*in_subq)->select_transformer(child_join);
+    if (res == Item_subselect::RES_ERROR)
+      DBUG_RETURN(TRUE);
+
+    *((*in_subq)->ref_ptr)= (*in_subq)->substitution;
+    (*in_subq)->changed= 1;
+    (*in_subq)->fixed= 1;
+    if (!(*in_subq)->substitution->fixed &&
+      (*in_subq)->substitution->fix_fields(thd, (*in_subq)->ref_ptr))
+      DBUG_RETURN(TRUE);
+
+    //if ((*in_subq)->fix_fields(thd, (*in_subq)->ref_ptr))
+    //  DBUG_RETURN(TRUE);
+  }
+  sj_subselects.clear();
+  DBUG_RETURN(FALSE);
+}
+
+
+/*
+  Check if table's KEYUSE elements have an eq_ref(outer_tables) candidate
+
+  SYNOPSIS
+    find_eq_ref_candidate()
+      table             Table to be checked
+      sj_inner_tables   Bitmap of inner tables. eq_ref(inner_table) doesn't
+                        count.
+
+  DESCRIPTION
+    Check if table's KEYUSE elements have an eq_ref(outer_tables) candidate
+
+  TODO
+    Check again if it is feasible to factor common parts with constant table
+    search
+
+  RETURN
+    TRUE  - There exists an eq_ref(outer-tables) candidate
+    FALSE - Otherwise
+*/
+
+bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables)
+{
+  KEYUSE *keyuse= table->reginfo.join_tab->keyuse;
+  uint key, part;
+
+  if (keyuse)
+  {
+    while (1) /* For each key */
+    {
+      key= keyuse->key;
+      KEY *keyinfo= table->key_info + key;
+      key_part_map bound_parts= 0;
+      if ((keyinfo->flags & (HA_NOSAME | HA_END_SPACE_KEY)) == HA_NOSAME)
+      {
+        do  // For all key parts
+        { 
+          do 
+          {  // For all ways to read the keypart
+            /* check if this is "t.keypart = expr(outer_tables) */
+            if (!(keyuse->used_tables & sj_inner_tables) &&
+                !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
+            {
+              bound_parts |= 1 << keyuse->keypart;
+            }
+            keyuse++;
+          } while (keyuse->key == key && keyuse->keypart == part &&
+                   keyuse->table == table);
+        } while (keyuse->key == key && keyuse->table == table);
+
+        if (bound_parts == PREV_BITS(uint, keyinfo->key_parts))
+          return TRUE;
+        if (keyuse->table != table)
+          return FALSE;
+      }
+      else
+      {
+        do
+        {
+          keyuse++;
+          if (keyuse->table != table)
+            return FALSE;
+        }
+        while (keyuse->key == key);
+      }
+    }
+  }
+  return FALSE;
+}
+
+
+/*
+  Pull tables out of semi-join nests, if possible
+
+  SYNOPSIS
+    pull_out_semijoin_tables()
+      join  The join where to do the semi-join flattening
+
+  DESCRIPTION
+    Try to pull tables out of semi-join nests.
+     
+    PRECONDITIONS
+    When this function is called, the join may have several semi-join nests
+    (possibly within different semi-join nests), but it is guaranteed that
+    one semi-join nest does not contain another.
+   
+    ACTION
+    A table can be pulled out of the semi-join nest if
+     - It is a constant table
+     - It is accessed 
+
+    POSTCONDITIONS
+     * Pulled out tables have JOIN_TAB::emb_sj_nest == NULL (like the outer
+       tables)
+     * Tables that were not pulled out have JOIN_TAB::emb_sj_nest.
+     * Semi-join nests TABLE_LIST::sj_inner_tables
+
+    This operation is (and should be) performed at each PS execution since
+    tables may become/cease to be constant across PS reexecutions.
+
+  RETURN 
+    0 - OK
+    1 - Out of memory error
+*/
+
+int pull_out_semijoin_tables(JOIN *join)
+{
+  DBUG_ENTER("pull_out_semijoin_tables");
+  TABLE_LIST **sj_nest, **sj_nest_end;
+   
+  /* Try pulling out of the each of the semi-joins */
+  for (sj_nest= join->sj_nests.front(), sj_nest_end= join->sj_nests.back();
+       sj_nest != sj_nest_end; sj_nest++)
+  {
+    /* Action #1: Mark the constant tables to be pulled out */
+    table_map pulled_tables= 0;
+     
+    List_iterator<TABLE_LIST> child_li((*sj_nest)->nested_join->join_list);
+    TABLE_LIST *tbl;
+    while ((tbl= child_li++))
+    {
+      if (tbl->table)
+      {
+        tbl->table->reginfo.join_tab->emb_sj_nest= *sj_nest;
+        if (tbl->table->map & join->const_table_map)
+          pulled_tables |= tbl->table->map;
+      }
+    }
+    
+    /*
+      Action #2: Find which tables we can pull out based on
+      update_ref_and_keys() data. Note that pulling one table out can allow
+      us to pull out some other tables too.
+    */
+    bool pulled_a_table;
+    do 
+    {
+      pulled_a_table= FALSE;
+      child_li.rewind();
+      while ((tbl= child_li++))
+      {
+        if (tbl->table && !(pulled_tables & tbl->table->map))
+        {
+          if (find_eq_ref_candidate(tbl->table, (*sj_nest)->nested_join->used_tables))
+          {
+            pulled_a_table= TRUE;
+            pulled_tables |= tbl->table->map;
+          }
+        }
+      }
+    } while (pulled_a_table);
+ 
+    child_li.rewind();
+    if ((*sj_nest)->nested_join->used_tables == pulled_tables)
+    {
+      (*sj_nest)->sj_inner_tables= 0;
+      fprintf(stderr, "subq nest removed\n");
+      while ((tbl= child_li++))
+      {
+        if (tbl->table)
+          tbl->table->reginfo.join_tab->emb_sj_nest= NULL;
+      }
+    }
+    else
+    {
+      /* Record the bitmap of inner tables, mark the inner tables */
+      table_map inner_tables=(*sj_nest)->nested_join->used_tables & 
+                             ~pulled_tables;
+      (*sj_nest)->sj_inner_tables= inner_tables;
+      while ((tbl= child_li++))
+      {
+        if (tbl->table)
+        {
+          if (inner_tables & tbl->table->map)
+            tbl->table->reginfo.join_tab->emb_sj_nest= (*sj_nest);
+          else
+            tbl->table->reginfo.join_tab->emb_sj_nest= NULL;
+        }
+      }
+    }
+  }
+  DBUG_RETURN(0);
+}
+
 /*****************************************************************************
   Create JOIN_TABS, make a guess about the table types,
   Approximate how many records will be used in each table
 *****************************************************************************/
 
+
 static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select,
 				      TABLE *table,
 				      const key_map *keys,ha_rows limit)
@@ -2320,7 +3270,7 @@
         s->embedding_map|= embedding->nested_join->nj_map;
       continue;
     }
-    if (embedding)
+    if (embedding && !(embedding->sj_on_expr && ! embedding->embedding))
     {
       /* s belongs to a nested join, maybe to several embedded joins */
       s->embedding_map= 0;
@@ -2548,7 +3498,7 @@
       }
     }
   } while (join->const_table_map & found_ref && ref_changed);
-
+ 
   /* 
     Update info on indexes that can be used for search lookups as
     reading const tables may has added new sargable predicates. 
@@ -2569,6 +3519,12 @@
     }
   }
 
+  if (pull_out_semijoin_tables(join))
+  {
+    join->thd->net.report_error= 1;
+    DBUG_RETURN(TRUE);
+  }
+
   /* Calc how many (possible) matched records in each table */
 
   for (s=stat ; s < stat_end ; s++)
@@ -2679,7 +3635,7 @@
   Field		*field;
   Item		*val;			// May be empty if diff constant
   uint		level;
-  uint		optimize;
+  uint		optimize; // KEY_OPTIMIZE_*
   bool		eq_func;
   /*
     If true, the condition this struct represents will not be satisfied
@@ -2689,10 +3645,6 @@
   bool         *cond_guard; /* See KEYUSE::cond_guard */
 } KEY_FIELD;
 
-/* Values in optimize */
-#define KEY_OPTIMIZE_EXISTS		1
-#define KEY_OPTIMIZE_REF_OR_NULL	2
-
 /*
   Merge new key definitions to old ones, remove those not used in both
 
@@ -3406,20 +4358,34 @@
                                   SARGABLE_PARAM **sargables)
 {
   List_iterator<TABLE_LIST> li(nested_join_table->nested_join->join_list);
+  List_iterator<TABLE_LIST> li2(nested_join_table->nested_join->join_list);
+  bool have_another = FALSE;
   table_map tables= 0;
   TABLE_LIST *table;
   DBUG_ASSERT(nested_join_table->nested_join);
 
-  while ((table= li++))
+  while ((table= li++) || (have_another && (li=li2, have_another=FALSE,
+                                            (table= li++))))
   {
     if (table->nested_join)
-      add_key_fields_for_nj(join, table, end, and_level, sargables);
+    {
+      if (!table->on_expr)
+      {
+        /* It's a semi-join nest. Walk into it as if it wasn't a nest */
+        have_another= TRUE;
+        li2= li;
+        li= List_iterator<TABLE_LIST>(table->nested_join->join_list); 
+      }
+      else
+        add_key_fields_for_nj(join, table, end, and_level, sargables);
+    }
     else
       if (!table->on_expr)
         tables |= table->table->map;
   }
-  add_key_fields(join, end, and_level, nested_join_table->on_expr, tables,
-                 sargables);
+  if (nested_join_table->on_expr)
+    add_key_fields(join, end, and_level, nested_join_table->on_expr, tables,
+                   sargables);
 }
 
 
@@ -3599,6 +4565,7 @@
     VOID(set_dynamic(keyuse,(gptr) &key_end,i));
     keyuse->elements=i;
   }
+  DBUG_EXECUTE("opt", print_keyuse_array(keyuse););
   return FALSE;
 }
 
@@ -4151,7 +5118,7 @@
     This is because table scans uses index and we would not win
     anything by using a table scan.
 
-    A word for word translation of the below if-statement in psergey's
+    A word for word translation of the below if-statement in sergefp's
     understanding: we check if we should use table scan if:
     (1) The found 'ref' access produces more records than a table scan
         (or index scan, or quick select), or 'ref' is more expensive than
@@ -5456,6 +6423,8 @@
   join_tab->read_first_record= join_init_read_record;
   join_tab->join=join;
   join_tab->ref.key_parts= 0;
+  join_tab->flush_weedout_table= join_tab->check_weed_out_table= NULL;
+  join_tab->do_firstmatch= NULL;
   bzero((char*) &join_tab->read_record,sizeof(join_tab->read_record));
   tmp_table->status=0;
   tmp_table->null_row=0;
@@ -5684,8 +6653,11 @@
     }    
     for ( ; embedding ; embedding= embedding->embedding)
     {
+      /* Ignore sj-nests: */
+      if (!embedding->on_expr)
+        continue;
       NESTED_JOIN *nested_join= embedding->nested_join;
-      if (!nested_join->counter)
+      if (!nested_join->counter_)
       {
         /* 
           Table tab is the first inner table for nested_join.
@@ -5699,7 +6671,7 @@
       }
       if (!tab->first_inner)  
         tab->first_inner= nested_join->first_nested;
-      if (++nested_join->counter < nested_join->join_list.elements)
+      if (++nested_join->counter_ < nested_join->join_list.elements)
         break;
       /* Table tab is the last inner table for nested join. */
       nested_join->first_nested->last_inner= tab;
@@ -6055,12 +7027,43 @@
   DBUG_RETURN(0);
 }
 
+
+uint make_join_orderinfo(JOIN *join)
+{
+  uint i;
+  for (i=join->const_tables ; i < join->tables ; i++)
+  {
+    JOIN_TAB *tab=join->join_tab+i;
+    TABLE *table=tab->table;
+    if ((table == join->sort_by_table &&
+         (!join->order || join->skip_sort_order ||
+          test_if_skip_sort_order(tab, join->order, join->select_limit,
+                                  1))
+        ) ||
+        (join->sort_by_table == (TABLE *) 1 && i != join->const_tables))
+    {
+      break;
+    }
+  }
+  return i;
+}
+
+
+/*
+  ...
+  SYNOPSIS
+    make_join_readinfo()
+      join
+      options
+      no_jbuf_after  X.
+*/
+
 static void
-make_join_readinfo(JOIN *join, ulonglong options)
+make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after)
 {
   uint i;
   bool statistics= test(!(join->select_options & SELECT_DESCRIBE));
-  bool ordered_set= 0;
+  //bool ordered_set= 0;
   bool sorted= 1;
   DBUG_ENTER("make_join_readinfo");
 
@@ -6078,6 +7081,7 @@
       Code handles sort table that is at any location (not only first after 
       the const tables) despite the fact that it's currently prohibited.
     */
+    /*
     if (!ordered_set && 
         (table == join->sort_by_table &&
          (!join->order || join->skip_sort_order ||
@@ -6086,6 +7090,7 @@
         ) ||
         (join->sort_by_table == (TABLE *) 1 && i != join->const_tables))
       ordered_set= 1;
+    */
 
     tab->sorted= sorted;
     sorted= 0;                                  // only first must be sorted
@@ -6163,7 +7168,7 @@
       */
       table->status=STATUS_NO_RECORD;
       if (i != join->const_tables && !(options & SELECT_NO_JOIN_CACHE) &&
-          tab->use_quick != 2 && !tab->first_inner && !ordered_set)
+          tab->use_quick != 2 && !tab->first_inner && i <= no_jbuf_after)
       {
 	if ((options & SELECT_DESCRIBE) ||
 	    !join_init_cache(join->thd,join->join_tab+join->const_tables,
@@ -6458,6 +7463,7 @@
 	    tab->table->file->ha_index_or_rnd_end();
       }
     }
+    cleanup_sj_tmp_tables(this);//
   }
   /*
     We are not using tables anymore
@@ -8113,24 +9119,29 @@
     consider any plan where one of the inner tables is before some of outer
     tables.
 
-  IMPLEMENTATION.
+  IMPLEMENTATION
     The function is implemented by a recursive procedure.  On the recursive
     ascent all attributes are calculated, all outer joins that can be
     converted are replaced and then all unnecessary braces are removed.
     As join list contains join tables in the reverse order sequential
     elimination of outer joins does not require extra recursive calls.
 
+  SEMI-JOIN NOTES
+    Remove all semi-joins that have are within another semi-join (i.e. have
+    an "ancestor" semi-join nest)
+
   EXAMPLES
     Here is an example of a join query with invalid cross references:
       SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN t3 ON t3.b=t1.b 
      
   RETURN VALUE
     The new condition, if success
-    0, otherwise  
+    0, otherwise
 */
 
 static COND *
-simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top)
+simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top,
+               bool in_sj)
 {
   TABLE_LIST *table;
   NESTED_JOIN *nested_join;
@@ -8165,14 +9176,15 @@
            the corresponding on expression is added to E. 
 	*/ 
         expr= simplify_joins(join, &nested_join->join_list,
-                             expr, FALSE);
+                             expr, FALSE, in_sj || table->sj_on_expr);
         table->on_expr= expr;
         if (!table->prep_on_expr)
           table->prep_on_expr= expr->copy_andor_structure(join->thd);
       }
       nested_join->used_tables= (table_map) 0;
       nested_join->not_null_tables=(table_map) 0;
-      conds= simplify_joins(join, &nested_join->join_list, conds, top);
+      conds= simplify_joins(join, &nested_join->join_list, conds, top, 
+                            in_sj || table->sj_on_expr);
       used_tables= nested_join->used_tables;
       not_null_tables= nested_join->not_null_tables;  
     }
@@ -8200,7 +9212,7 @@
       table->outer_join= 0;
       if (table->on_expr)
       {
-        /* Add on expression to the where condition. */
+        /* Add ON expression to the WHERE or upper-level ON condition. */
         if (conds)
         {
           conds= and_conds(conds, table->on_expr);
@@ -8262,12 +9274,23 @@
     prev_table= table;
   }
     
-  /* Flatten nested joins that can be flattened. */
+  /* 
+    Flatten nested joins that can be flattened.
+    no ON expression and not a semi-join => can be flattened.
+  */
   li.rewind();
   while ((table= li++))
   {
     nested_join= table->nested_join;
-    if (nested_join && !table->on_expr)
+    if (table->sj_on_expr && !in_sj)
+    {
+       /*
+         If this is a semi-join that is not contained within another semi-join, 
+         leave it intact (otherwise it is flattened)
+       */
+      join->sj_nests.append(table);
+    }
+    else if (nested_join && !table->on_expr)
     {
       TABLE_LIST *tbl;
       List_iterator<TABLE_LIST> it(nested_join->join_list);
@@ -8319,16 +9342,20 @@
     {
       /*
         It is guaranteed by simplify_joins() function that a nested join
-        that has only one child represents a single table VIEW (and the child
-        is an underlying table). We don't assign bits to such nested join
-        structures because 
+        that has only one child is either
+         - a single-table view (the child is the underlying table), or 
+         - a single-table semi-join nest
+
+        We don't assign bits to such sj-nests because 
         1. it is redundant (a "sequence" of one table cannot be interleaved 
             with anything)
         2. we could run out bits in nested_join_map otherwise.
       */
       if (nested_join->join_list.elements != 1)
       {
-        nested_join->nj_map= (nested_join_map) 1 << first_unused++;
+        /* Don't assign bits to sj-nests */
+        if (table->on_expr)
+          nested_join->nj_map= (nested_join_map) 1 << first_unused++;
         first_unused= build_bitmap_for_nested_joins(&nested_join->join_list,
                                                     first_unused);
       }
@@ -8361,7 +9388,7 @@
     NESTED_JOIN *nested_join;
     if ((nested_join= table->nested_join))
     {
-      nested_join->counter= 0;
+      nested_join->counter_= 0;
       reset_nj_counters(&nested_join->join_list);
     }
   }
@@ -8481,8 +9508,8 @@
   */
   for (;next_emb; next_emb= next_emb->embedding)
   {
-    next_emb->nested_join->counter++;
-    if (next_emb->nested_join->counter == 1)
+    next_emb->nested_join->counter_++;
+    if (next_emb->nested_join->counter_ == 1)
     {
       /* 
         next_emb is the first table inside a nested join we've "entered". In
@@ -8493,7 +9520,7 @@
     }
     
     if (next_emb->nested_join->join_list.elements !=
-        next_emb->nested_join->counter)
+        next_emb->nested_join->counter_)
       break;
 
     /*
@@ -8525,9 +9552,14 @@
 {
   TABLE_LIST *last_emb= last->table->pos_in_table_list->embedding;
   JOIN *join= last->join;
-  while (last_emb && !(--last_emb->nested_join->counter))
+  while (last_emb)
   {
-    join->cur_embedding_map &= last_emb->nested_join->nj_map;
+    if (last_emb->on_expr)
+    {
+      if (!(--last_emb->nested_join->counter_))
+        return;
+      join->cur_embedding_map &= last_emb->nested_join->nj_map;
+    }
     last_emb= last_emb->embedding;
   }
 }
@@ -9847,7 +10879,8 @@
   share->db_record_offset= 1;
   if (share->db_type == myisam_hton)
   {
-    if (create_myisam_tmp_table(table,param,select_options))
+    if (create_myisam_tmp_table(table, param->keyinfo, param->start_recinfo,
+                                &param->recinfo, select_options))
       goto err;
   }
   if (open_tmp_table(table))
@@ -9866,6 +10899,326 @@
 }
 
 
+
+
+/*
+  Create a temporary table to weed out duplicate rowid combinations
+
+  SYNOPSIS
+
+    create_duplicate_weedout_tmp_table()
+      thd
+      uniq_tuple_length_arg
+      SJ_TMP_TABLE 
+
+  DESCRIPTION
+    Create a temporary table to weed out duplicate rowid combinations. The
+    table has a single column that is a concatenation of all rowids in the
+    combination. 
+
+    Depending on the needed length, there are two cases:
+
+    1. When the length of the column < max_key_length:
+
+      CREATE TABLE tmp (col VARBINARY(n) NOT NULL, UNIQUE KEY(col));
+
+    2. Otherwise (not a valid SQL syntax but internally supported):
+
+      CREATE TABLE tmp (col VARBINARY NOT NULL, UNIQUE CONSTRAINT(col));
+
+    The code in this function was produced by extraction of relevant parts
+    from create_tmp_table().
+
+  RETURN
+    created table
+    NULL on error
+*/
+
+TABLE *create_duplicate_weedout_tmp_table(THD *thd, 
+                                          uint uniq_tuple_length_arg,
+                                          SJ_TMP_TABLE *sjtbl)
+{
+  MEM_ROOT *mem_root_save, own_root;
+  TABLE *table;
+  TABLE_SHARE *share;
+  uint  temp_pool_slot=MY_BIT_NONE;
+  char	*tmpname,path[FN_REFLEN];
+  Field **reg_field;
+  KEY_PART_INFO *key_part_info;
+  KEY *keyinfo;
+  byte *group_buff;
+  byte *bitmaps;
+  uint *blob_field;
+  MI_COLUMNDEF *recinfo, *start_recinfo;
+  bool using_unique_constraint=FALSE;
+  bool use_packed_rows= FALSE;
+  Field *field, *key_field;
+  uint blob_count, null_pack_length, null_count;
+  uchar *null_flags;
+  byte *pos;
+  DBUG_ENTER("create_duplicate_weedout_tmp_table");
+  
+  /*
+    STEP 1: Get temporary table name
+  */
+  statistic_increment(thd->status_var.created_tmp_tables, &LOCK_status);
+  if (use_temp_pool && !(test_flags & TEST_KEEP_TMP_TABLES))
+    temp_pool_slot = bitmap_lock_set_next(&temp_pool);
+
+  if (temp_pool_slot != MY_BIT_NONE) // we got a slot
+    sprintf(path, "%s_%lx_%i", tmp_file_prefix,
+	    current_pid, temp_pool_slot);
+  else
+  {
+    /* if we run out of slots or we are not using tempool */
+    sprintf(path,"%s%lx_%lx_%x", tmp_file_prefix,current_pid,
+            thd->thread_id, thd->tmp_table++);
+  }
+  fn_format(path, path, mysql_tmpdir, "", MY_REPLACE_EXT|MY_UNPACK_FILENAME);
+
+  /* STEP 2: Figure if we'll be using a key or blob+constraint */
+  if (uniq_tuple_length_arg >= CONVERT_IF_BIGGER_TO_BLOB)
+    using_unique_constraint= TRUE;
+
+  /* STEP 3: Allocate memory for temptable description */
+  init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0);
+  if (!multi_alloc_root(&own_root,
+                        &table, sizeof(*table),
+                        &share, sizeof(*share),
+                        &reg_field, sizeof(Field*) * (1+1),
+                        &blob_field, sizeof(uint)*2,
+                        &keyinfo, sizeof(*keyinfo),
+                        &key_part_info, sizeof(*key_part_info) * 2,
+                        &start_recinfo,
+                        sizeof(*recinfo)*(1*2+4),
+                        &tmpname, (uint) strlen(path)+1,
+                        &group_buff, (!using_unique_constraint ?
+                                      uniq_tuple_length_arg : 0),
+                        &bitmaps, bitmap_buffer_size(1)*2,
+                        NullS))
+  {
+    if (temp_pool_slot != MY_BIT_NONE)
+      bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+    DBUG_RETURN(NULL);
+  }
+  strmov(tmpname,path);
+  
+
+  /* STEP 4: Create TABLE description */
+  bzero((char*) table,sizeof(*table));
+  bzero((char*) reg_field,sizeof(Field*)*2);
+
+  table->mem_root= own_root;
+  mem_root_save= thd->mem_root;
+  thd->mem_root= &table->mem_root;
+
+  table->field=reg_field;
+  table->alias= "weedout-tmp";
+  table->reginfo.lock_type=TL_WRITE;	/* Will be updated */
+  table->db_stat=HA_OPEN_KEYFILE+HA_OPEN_RNDFILE;
+  table->map=1;
+  table->temp_pool_slot = temp_pool_slot;
+  table->copy_blobs= 1;
+  table->in_use= thd;
+  table->quick_keys.init();
+  table->used_keys.init();
+  table->keys_in_use_for_query.init();
+
+  table->s= share;
+  init_tmp_table_share(share, "", 0, tmpname, tmpname);
+  share->blob_field= blob_field;
+  share->blob_ptr_size= mi_portable_sizeof_char_ptr;
+  share->db_low_byte_first=1;                // True for HEAP and MyISAM
+  share->table_charset= NULL;
+  share->primary_key= MAX_KEY;               // Indicate no primary key
+  share->keys_for_keyread.init();
+  share->keys_in_use.init();
+
+  blob_count= 0;
+
+  /* Create the field */
+  {
+    /*
+      For the sake of uniformity, always use Field_varstring (altough we could
+      use Field_string for shorter keys)
+    */
+    field= new Field_varstring(uniq_tuple_length_arg, FALSE, "rowids", share,
+                               &my_charset_bin);
+    if (!field)
+      DBUG_RETURN(0);
+    field->table= table;
+    field->key_start.init(0);
+    field->part_of_key.init(0);
+    field->part_of_sortkey.init(0);
+    field->unireg_check= Field::NONE;
+    field->flags= (NOT_NULL_FLAG | BINARY_FLAG | NO_DEFAULT_VALUE_FLAG);
+    field->reset_fields();
+    field->init(table);
+    field->orig_table= NULL;
+     
+    field->field_index= 0;
+    
+    *(reg_field++)= field;
+    *blob_field= 0;
+    *reg_field= 0;
+
+    share->fields= 1;
+    share->blob_fields= 0;
+  }
+
+  uint reclength= field->pack_length();
+  if (using_unique_constraint)
+  { 
+    table->file= get_new_handler(share, &table->mem_root,
+                                 share->db_type= myisam_hton);
+    DBUG_ASSERT(uniq_tuple_length_arg <= table->file->max_key_length());
+  }
+  else
+  {
+    table->file= get_new_handler(share, &table->mem_root,
+                                 share->db_type= heap_hton);
+  }
+  if (!table->file)
+    goto err;
+
+  null_count=1;
+  
+  null_pack_length= 1;
+  reclength += null_pack_length;
+
+  share->reclength= reclength;
+  {
+    uint alloc_length=ALIGN_SIZE(share->reclength + MI_UNIQUE_HASH_LENGTH+1);
+    share->rec_buff_length= alloc_length;
+    if (!(table->record[0]= (byte*)
+                            alloc_root(&table->mem_root, alloc_length*3)))
+      goto err;
+    table->record[1]= table->record[0]+alloc_length;
+    share->default_values= table->record[1]+alloc_length;
+  }
+  setup_tmp_table_column_bitmaps(table, bitmaps);
+
+  recinfo= start_recinfo;
+  null_flags=(uchar*) table->record[0];
+  pos=table->record[0]+ null_pack_length;
+  if (null_pack_length)
+  {
+    bzero((byte*) recinfo,sizeof(*recinfo));
+    recinfo->type=FIELD_NORMAL;
+    recinfo->length=null_pack_length;
+    recinfo++;
+    bfill(null_flags,null_pack_length,255);	// Set null fields
+
+    table->null_flags= (uchar*) table->record[0];
+    share->null_fields= null_count;
+    share->null_bytes= null_pack_length;
+  }
+  null_count=1;
+
+  {
+    //Field *field= *reg_field;
+    uint length;
+    bzero((byte*) recinfo,sizeof(*recinfo));
+    field->move_field((char*) pos,(uchar*) 0,0);
+
+    field->reset();
+    /*
+      Test if there is a default field value. The test for ->ptr is to skip
+      'offset' fields generated by initalize_tables
+    */
+    // Initialize the table field:
+    bzero(field->ptr, field->pack_length());
+
+    length=field->pack_length();
+    pos+= length;
+
+    /* Make entry for create table */
+    recinfo->length=length;
+    if (field->flags & BLOB_FLAG)
+      recinfo->type= (int) FIELD_BLOB;
+    else if (use_packed_rows &&
+             field->real_type() == MYSQL_TYPE_STRING &&
+	     length >= MIN_STRING_LENGTH_TO_PACK_ROWS)
+      recinfo->type=FIELD_SKIP_ENDSPACE;
+    else
+      recinfo->type=FIELD_NORMAL;
+
+    field->table_name= &table->alias;
+  }
+
+  //param->recinfo=recinfo;
+  //store_record(table,s->default_values);        // Make empty default record
+
+  if (thd->variables.tmp_table_size == ~ (ulonglong) 0)		// No limit
+    share->max_rows= ~(ha_rows) 0;
+  else
+    share->max_rows= (ha_rows) (((share->db_type == heap_hton) ?
+                                 min(thd->variables.tmp_table_size,
+                                     thd->variables.max_heap_table_size) :
+                                 thd->variables.tmp_table_size) /
+			         share->reclength);
+  set_if_bigger(share->max_rows,1);		// For dummy start options
+
+
+  //// keyinfo= param->keyinfo;
+  if (TRUE)
+  {
+    DBUG_PRINT("info",("Creating group key in temporary table"));
+    share->keys=1;
+    share->uniques= test(using_unique_constraint);
+    table->key_info=keyinfo;
+    keyinfo->key_part=key_part_info;
+    keyinfo->flags=HA_NOSAME;
+    keyinfo->usable_key_parts= keyinfo->key_parts= 1;
+    keyinfo->key_length=0;
+    keyinfo->rec_per_key=0;
+    keyinfo->algorithm= HA_KEY_ALG_UNDEF;
+    keyinfo->name= (char*) "weedout_key";
+    {
+      key_part_info->null_bit=0;
+      key_part_info->field=  field;
+      key_part_info->offset= field->offset(table->record[0]);
+      key_part_info->length= (uint16) field->key_length();
+      key_part_info->type=   (uint8) field->key_type();
+      key_part_info->key_type = FIELDFLAG_BINARY;
+      if (!using_unique_constraint)
+      {
+	if (!(key_field= field->new_key_field(thd->mem_root, table,
+                                              (char*) group_buff,
+                                              field->null_ptr,
+                                              field->null_bit)))
+	  goto err;
+        key_part_info->key_part_flag|= HA_END_SPACE_ARE_EQUAL; //todo need this?
+      }
+      keyinfo->key_length+=  key_part_info->length;
+    }
+  }
+
+  if (thd->is_fatal_error)				// If end of memory
+    goto err;
+  share->db_record_offset= 1;
+  if (share->db_type == myisam_hton)
+  {
+    recinfo++;
+    if (create_myisam_tmp_table(table, keyinfo, start_recinfo, &recinfo, 0))
+      goto err;
+  }
+  sjtbl->start_recinfo= start_recinfo;
+  sjtbl->recinfo=       recinfo;
+  if (open_tmp_table(table))
+    goto err;
+
+  thd->mem_root= mem_root_save;
+  DBUG_RETURN(table);
+
+err:
+  thd->mem_root= mem_root_save;
+  free_tmp_table(thd,table);                    /* purecov: inspected */
+  if (temp_pool_slot != MY_BIT_NONE)
+    bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+  DBUG_RETURN(NULL);				/* purecov: inspected */
+}
+
 /****************************************************************************/
 
 /*
@@ -10012,13 +11365,44 @@
 }
 
 
-static bool create_myisam_tmp_table(TABLE *table,TMP_TABLE_PARAM *param,
+/*
+  Create MyISAM temporary table
+
+  SYNOPSIS
+    create_myisam_tmp_table()
+      table           Table object that descrimes the table to be created
+      keyinfo         Description of the index (there is always one index)
+      start_recinfo   MyISAM's column descriptions
+      recinfo INOUT   End of MyISAM's column descriptions
+      options         Option bits
+   
+  DESCRIPTION
+    Create a MyISAM temporary table according to passed description. The is
+    assumed to have one unique index or constraint.
+
+    The passed array or MI_COLUMNDEF structures must have this form:
+
+      1. 1-byte column (afaiu for 'deleted' flag) (note maybe not 1-byte
+         when there are many nullable columns)
+      2. Table columns
+      3. One free MI_COLUMNDEF element (*recinfo points here)
+   
+    This function may use the free element to create hash column for unique
+    constraint.
+
+   RETURN
+     FALSE - OK
+     TRUE  - Error
+*/
+
+static bool create_myisam_tmp_table(TABLE *table, KEY *keyinfo, 
+                                    MI_COLUMNDEF *start_recinfo,
+                                    MI_COLUMNDEF **recinfo, 
 				    ulonglong options)
 {
   int error;
   MI_KEYDEF keydef;
   MI_UNIQUEDEF uniquedef;
-  KEY *keyinfo=param->keyinfo;
   TABLE_SHARE *share= table->s;
   DBUG_ENTER("create_myisam_tmp_table");
 
@@ -10045,10 +11429,10 @@
       uniquedef.null_are_equal=1;
 
       /* Create extra column for hash value */
-      bzero((byte*) param->recinfo,sizeof(*param->recinfo));
-      param->recinfo->type= FIELD_CHECK;
-      param->recinfo->length=MI_UNIQUE_HASH_LENGTH;
-      param->recinfo++;
+      bzero((byte*) *recinfo,sizeof(**recinfo));
+      (*recinfo)->type= FIELD_CHECK;
+      (*recinfo)->length=MI_UNIQUE_HASH_LENGTH;
+      (*recinfo)++;
       share->reclength+=MI_UNIQUE_HASH_LENGTH;
     }
     else
@@ -10105,8 +11489,8 @@
     create_info.data_file_length= ~(ulonglong) 0;
 
   if ((error=mi_create(share->table_name.str, share->keys, &keydef,
-		       (uint) (param->recinfo-param->start_recinfo),
-		       param->start_recinfo,
+		       (uint) (*recinfo-start_recinfo),
+		       start_recinfo,
 		       share->uniques, &uniquedef,
 		       &create_info,
 		       HA_CREATE_TMP_TABLE)))
@@ -10162,7 +11546,9 @@
 * If a HEAP table gets full, create a MyISAM table and copy all rows to this
 */
 
-bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param,
+bool create_myisam_from_heap(THD *thd, TABLE *table,
+                             MI_COLUMNDEF *start_recinfo,
+                             MI_COLUMNDEF **recinfo, 
 			     int error, bool ignore_last_dupp_key_error)
 {
   TABLE new_table;
@@ -10188,8 +11574,9 @@
   save_proc_info=thd->proc_info;
   THD_SET_PROC_INFO(thd, "converting HEAP to MyISAM");
 
-  if (create_myisam_tmp_table(&new_table, param,
-			      thd->lex->select_lex.options | thd->options))
+  if (create_myisam_tmp_table(&new_table, table->key_info, start_recinfo,
+                              recinfo, thd->lex->select_lex.options | 
+                                               thd->options))
     goto err2;
   if (open_tmp_table(&new_table))
     goto err1;
@@ -10485,6 +11872,7 @@
   return rc;
 }
 
+
 /*
   Retrieve records ends with a given beginning from the result of a join  
 
@@ -10596,6 +11984,7 @@
   RETURN
     return one of enum_nested_loop_state, except NESTED_LOOP_NO_MORE_ROWS.
 */
+int do_sj_reset(SJ_TMP_TABLE *sj_tbl);
 
 enum_nested_loop_state
 sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
@@ -10609,6 +11998,11 @@
   my_bool *report_error= &(join->thd->net.report_error);
   READ_RECORD *info= &join_tab->read_record;
 
+  if (join_tab->flush_weedout_table)
+  {
+    do_sj_reset(join_tab->flush_weedout_table);
+  }
+
   if (join->resume_nested_loop)
   {
     /* If not the last table, plunge down the nested loop */
@@ -10640,8 +12034,12 @@
     error= (*join_tab->read_first_record)(join_tab);
     rc= evaluate_join_record(join, join_tab, error, report_error);
   }
-
-  while (rc == NESTED_LOOP_OK)
+  
+  /* 
+    Note: psergey has added the 2nd part of the following condition; the 
+    change should probably be made in 5.1, too.
+  */
+  while (rc == NESTED_LOOP_OK && join->return_tab >= join_tab)
   {
     error= info->read_record(info);
     rc= evaluate_join_record(join, join_tab, error, report_error);
@@ -10658,6 +12056,90 @@
 
 
 /*
+  SemiJoinDuplicateElimination: Weed out duplicate row combinations
+
+  SYNPOSIS
+    do_sj_dups_weedout()
+      
+  RETURN
+    -1  Error
+    1   The row combination is a duplicate (discard it)
+    0   The row combination is not a duplicate (continue)
+*/
+
+int do_sj_dups_weedout(THD *thd, SJ_TMP_TABLE *sjtbl) 
+{
+  int error;
+  SJ_TMP_TABLE::TAB *tab= sjtbl->tabs;
+  SJ_TMP_TABLE::TAB *tab_end= sjtbl->tabs_end;
+  byte *ptr= sjtbl->tmp_table->record[0] + 1;
+  
+  /* Put the the rowids tuple into table->record[0]: */
+
+  // 1. Store the length 
+  if (((Field_varstring*)(sjtbl->tmp_table->field[0]))->length_bytes == 1)
+  {
+    *ptr= (uchar)(sjtbl->rowid_len + sjtbl->null_bytes);
+    ptr++;
+  }
+  else
+  {
+    int2store(ptr, sjtbl->rowid_len + sjtbl->null_bytes);
+    ptr += 2;
+  }
+
+  // 2. Zero the length 
+  if (sjtbl->null_bytes)
+  {
+    bzero(ptr, sjtbl->null_bytes);
+    ptr += sjtbl->null_bytes; 
+  }
+
+  // 3. Put the rowids
+  for (uint i=0; tab != tab_end; tab++, i++)
+  {
+    handler *h= tab->join_tab->table->file;
+    if (tab->join_tab->table->maybe_null && tab->join_tab->table->null_row)
+    {
+      /* It's a NULL-complemented row */
+      *(tab->null_addr) |= tab->null_bit;
+      bzero(ptr + tab->rowid_offset, h->ref_length);
+    }
+    else
+    {
+      /* Copy the rowid value */
+      if (tab->join_tab->rowid_keep_flags & JOIN_TAB::CALL_POSITION)
+        h->position(tab->join_tab->table->record[0]);
+      memcpy(ptr + tab->rowid_offset, h->ref, h->ref_length);
+    }
+  }
+
+  if ((error= sjtbl->tmp_table->file->write_row(sjtbl->tmp_table->record[0])))
+  {
+    /* create_myisam_from_heap will generate error if needed */
+    if (sjtbl->tmp_table->file->is_fatal_error(error, HA_CHECK_DUP) &&
+        create_myisam_from_heap(thd, sjtbl->tmp_table, sjtbl->start_recinfo, 
+                                &sjtbl->recinfo, error, 1))
+      return -1;
+    //return (error == HA_ERR_FOUND_DUPP_KEY || error== HA_ERR_FOUND_DUPP_UNIQUE) ? 1: -1;
+    return 1;
+  }
+  return 0;
+}
+
+
+/*
+  SemiJoinDuplicateElimination: Reset the temporary table
+*/
+
+int do_sj_reset(SJ_TMP_TABLE *sj_tbl)
+{
+  if (sj_tbl->tmp_table)
+    return sj_tbl->tmp_table->file->delete_all_rows();
+  return 0;
+}
+
+/*
   Process one record of the nested loop join.
 
   DESCRIPTION
@@ -10739,6 +12221,24 @@
       join_tab->first_unmatched= first_unmatched;
     }
 
+    JOIN_TAB *return_tab= join->return_tab;
+    if (join_tab->check_weed_out_table)
+    {
+      int res= do_sj_dups_weedout(join->thd, join_tab->check_weed_out_table);
+      if (res == -1)
+        return NESTED_LOOP_ERROR;
+      if (res == 1)
+        return NESTED_LOOP_OK;
+    }
+    else if (join_tab->do_firstmatch)
+    {
+      /* 
+        We should return to the join_tab->do_firstmatch after we have 
+        enumerated all the suffixes for current prefix row combination
+      */
+      return_tab= join_tab->do_firstmatch;
+    }
+
     /*
       It was not just a return to lower loop level when one
       of the newly activated predicates is evaluated as false
@@ -10758,6 +12258,9 @@
       rc= (*join_tab->next_select)(join, join_tab+1, 0);
       if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
         return rc;
+      if (return_tab < join->return_tab)
+        join->return_tab= return_tab;
+
       if (join->return_tab < join_tab)
         return NESTED_LOOP_OK;
       /*
@@ -10903,12 +12406,19 @@
 	read_cached_record(join_tab);
 	if (!select || !select->skip_record())
         {
-          rc= (join_tab->next_select)(join,join_tab+1,0);
-	  if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+          int res= 0;
+          if (!join_tab->check_weed_out_table || 
+              !(res= do_sj_dups_weedout(join->thd, join_tab->check_weed_out_table)))
           {
-            reset_cache_write(&join_tab->cache);
-            return rc;
+            rc= (join_tab->next_select)(join,join_tab+1,0);
+            if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
+            {
+              reset_cache_write(&join_tab->cache);
+              return rc;
+            }
           }
+          if (res == -1)
+            return NESTED_LOOP_ERROR;
         }
       }
     }
@@ -11682,8 +13192,10 @@
       {
         if (!table->file->is_fatal_error(error, HA_CHECK_DUP))
 	  goto end;
-	if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param,
-				    error,1))
+	if (create_myisam_from_heap(join->thd, table,
+                                    join->tmp_table_param.start_recinfo,
+                                    &join->tmp_table_param.recinfo,
+				    error, 1))
 	  DBUG_RETURN(NESTED_LOOP_ERROR);        // Not a table_is_full error
 	table->s->uniques=0;			// To ensure rows are the same
       }
@@ -11765,7 +13277,9 @@
   copy_funcs(join->tmp_table_param.items_to_copy);
   if ((error=table->file->write_row(table->record[0])))
   {
-    if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param,
+    if (create_myisam_from_heap(join->thd, table,
+                                join->tmp_table_param.start_recinfo,
+                                &join->tmp_table_param.recinfo,
 				error, 0))
       DBUG_RETURN(NESTED_LOOP_ERROR);            // Not a table_is_full error
     /* Change method to update rows */
@@ -11861,7 +13375,8 @@
 	{
           int error= table->file->write_row(table->record[0]);
           if (error && create_myisam_from_heap(join->thd, table,
-                                               &join->tmp_table_param,
+                                               join->tmp_table_param.start_recinfo,
+                                                &join->tmp_table_param.recinfo,
                                                error, 0))
 	    DBUG_RETURN(NESTED_LOOP_ERROR);
         }
@@ -13109,6 +14624,13 @@
       calc_used_field_length(thd, join_tab);
     cache->fields+=join_tab->used_fields;
     blobs+=join_tab->used_blobs;
+
+    /* SemiJoinDuplicateElimination: reserve space for rowid */
+    if (join_tab->rowid_keep_flags & JOIN_TAB::KEEP_ROWID)
+    {
+      cache->fields++;
+      join_tab->used_fieldlength += join_tab->table->file->ref_length;
+    }
   }
   if (!(cache->field=(CACHE_FIELD*)
 	sql_alloc(sizeof(CACHE_FIELD)*(cache->fields+table_count*2)+(blobs+1)*
@@ -13142,6 +14664,7 @@
 	  (*blob_ptr++)=copy;
 	if (field->maybe_null())
 	  null_fields++;
+        copy->get_rowid= NULL;
 	copy++;
       }
     }
@@ -13152,6 +14675,7 @@
       copy->length= tables[i].table->s->null_bytes;
       copy->strip=0;
       copy->blob_field=0;
+      copy->get_rowid= NULL;
       length+=copy->length;
       copy++;
       cache->fields++;
@@ -13163,10 +14687,28 @@
       copy->length=sizeof(tables[i].table->null_row);
       copy->strip=0;
       copy->blob_field=0;
+      copy->get_rowid= NULL;
       length+=copy->length;
       copy++;
       cache->fields++;
     }
+    /* SemiJoinDuplicateElimination: Allocate space for rowid if needed */
+    if (tables[i].rowid_keep_flags & JOIN_TAB::KEEP_ROWID)
+    {
+      copy->str= (char*)tables[i].table->file->ref;
+      copy->length= tables[i].table->file->ref_length;
+      copy->strip=0;
+      copy->blob_field=0;
+      copy->get_rowid= NULL;
+      if (tables[i].rowid_keep_flags & JOIN_TAB::CALL_POSITION)
+      {
+        /* We will need to call h->position(): */
+        copy->get_rowid= tables[i].table;
+        /* And those after us won't have to: */
+        tables[i].rowid_keep_flags &=  ~((int)JOIN_TAB::CALL_POSITION);
+      }
+      copy++;
+    }
   }
 
   cache->length=length+blobs*sizeof(char*);
@@ -13211,7 +14753,6 @@
     length+=used_blob_length(cache->blob_ptr);
   if ((last_record=(length+cache->length > (uint) (cache->end - pos))))
     cache->ptr_record=cache->records;
-
   /*
     There is room in cache. Put record there
   */
@@ -13236,6 +14777,10 @@
     }
     else
     {
+      // SemiJoinDuplicateElimination: Get the rowid into table->ref:
+      if (copy->get_rowid)
+        copy->get_rowid->file->position(copy->get_rowid->record[0]);
+
       if (copy->strip)
       {
 	char *str,*end;
@@ -13285,7 +14830,6 @@
 
   last_record=tab->cache.record_nr++ == tab->cache.ptr_record;
   pos=tab->cache.pos;
-
   for (copy=tab->cache.field,end_field=copy+tab->cache.fields ;
        copy < end_field;
        copy++)
@@ -14950,7 +16494,9 @@
       copy_sum_funcs(sum_funcs_end[i+1], sum_funcs_end[i]);
       if ((write_error= table_arg->file->write_row(table_arg->record[0])))
       {
-	if (create_myisam_from_heap(thd, table_arg, &tmp_table_param,
+	if (create_myisam_from_heap(thd, table_arg, 
+                                    tmp_table_param.start_recinfo,
+                                    &tmp_table_param.recinfo,
                                     write_error, 0))
 	  return 1;		     
       }
@@ -14988,6 +16534,7 @@
   Send a description about what how the select will be done to stdout
 ****************************************************************************/
 
+
 static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
 			    bool distinct,const char *message)
 {
@@ -15282,92 +16829,106 @@
         
       if (tab->info)
 	item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs));
-      else if (tab->packed_info & TAB_INFO_HAVE_VALUE)
+      else 
       {
-        if (tab->packed_info & TAB_INFO_USING_INDEX)
-          extra.append(STRING_WITH_LEN("; Using index"));
-        if (tab->packed_info & TAB_INFO_USING_WHERE)
-          extra.append(STRING_WITH_LEN("; Using where"));
-        if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL)
-          extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
-        /* Skip initial "; "*/
-        const char *str= extra.ptr();
-        uint32 len= extra.length();
-        if (len)
+        if (tab->packed_info & TAB_INFO_HAVE_VALUE)
         {
-          str += 2;
-          len -= 2;
+          if (tab->packed_info & TAB_INFO_USING_INDEX)
+            extra.append(STRING_WITH_LEN("; Using index"));
+          if (tab->packed_info & TAB_INFO_USING_WHERE)
+            extra.append(STRING_WITH_LEN("; Using where"));
+          if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL)
+            extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
         }
-	item_list.push_back(new Item_string(str, len, cs));
-      }
-      else
-      {
-        if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || 
-            quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
-            quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
+        else
         {
-          extra.append(STRING_WITH_LEN("; Using "));
-          tab->select->quick->add_info_string(&extra);
-        }
-	if (tab->select)
-	{
-	  if (tab->use_quick == 2)
-	  {
-            char buf[MAX_KEY/8+1];
-            extra.append(STRING_WITH_LEN("; Range checked for each "
-                                         "record (index map: 0x"));
-            extra.append(tab->keys.print(buf));
-            extra.append(')');
-	  }
-	  else if (tab->select->cond)
+          if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || 
+              quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
+              quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
           {
-            const COND *pushed_cond= tab->table->file->pushed_cond;
-
-            if (thd->variables.engine_condition_pushdown && pushed_cond)
+            extra.append(STRING_WITH_LEN("; Using "));
+            tab->select->quick->add_info_string(&extra);
+          }
+          if (tab->select)
+          {
+            if (tab->use_quick == 2)
+            {
+              char buf[MAX_KEY/8+1];
+              extra.append(STRING_WITH_LEN("; Range checked for each "
+                                           "record (index map: 0x"));
+              extra.append(tab->keys.print(buf));
+              extra.append(')');
+            }
+            else if (tab->select->cond)
             {
-              extra.append(STRING_WITH_LEN("; Using where with pushed "
-                                           "condition"));
-              if (thd->lex->describe & DESCRIBE_EXTENDED)
+              const COND *pushed_cond= tab->table->file->pushed_cond;
+
+              if (thd->variables.engine_condition_pushdown && pushed_cond)
               {
-                extra.append(STRING_WITH_LEN(": "));
-                ((COND *)pushed_cond)->print(&extra);
+                extra.append(STRING_WITH_LEN("; Using where with pushed "
+                                             "condition"));
+                if (thd->lex->describe & DESCRIBE_EXTENDED)
+                {
+                  extra.append(STRING_WITH_LEN(": "));
+                  ((COND *)pushed_cond)->print(&extra);
+                }
               }
+              else
+                extra.append(STRING_WITH_LEN("; Using where"));
             }
+          }
+          if (key_read)
+          {
+            if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
+              extra.append(STRING_WITH_LEN("; Using index for group-by"));
             else
-              extra.append(STRING_WITH_LEN("; Using where"));
+              extra.append(STRING_WITH_LEN("; Using index"));
           }
-	}
-	if (key_read)
-        {
-          if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
-            extra.append(STRING_WITH_LEN("; Using index for group-by"));
-          else
-            extra.append(STRING_WITH_LEN("; Using index"));
-        }
-	if (table->reginfo.not_exists_optimize)
-	  extra.append(STRING_WITH_LEN("; Not exists"));
-	if (need_tmp_table)
-	{
-	  need_tmp_table=0;
-	  extra.append(STRING_WITH_LEN("; Using temporary"));
-	}
-	if (need_order)
-	{
-	  need_order=0;
-	  extra.append(STRING_WITH_LEN("; Using filesort"));
-	}
-	if (distinct & test_all_bits(used_tables,thd->used_tables))
-	  extra.append(STRING_WITH_LEN("; Distinct"));
+          if (table->reginfo.not_exists_optimize)
+            extra.append(STRING_WITH_LEN("; Not exists"));
+          if (need_tmp_table)
+          {
+            need_tmp_table=0;
+            extra.append(STRING_WITH_LEN("; Using temporary"));
+          }
+          if (need_order)
+          {
+            need_order=0;
+            extra.append(STRING_WITH_LEN("; Using filesort"));
+          }
+          if (distinct & test_all_bits(used_tables,thd->used_tables))
+            extra.append(STRING_WITH_LEN("; Distinct"));
 
-        for (uint part= 0; part < tab->ref.key_parts; part++)
+          for (uint part= 0; part < tab->ref.key_parts; part++)
+          {
+            if (tab->ref.cond_guards[part])
+            {
+              extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
+              break;
+            }
+          }
+        }
+        if (tab->flush_weedout_table)
+          extra.append(STRING_WITH_LEN("; Start temporary"));
+        else if (tab->check_weed_out_table)
+          extra.append(STRING_WITH_LEN("; End temporary"));
+        else if (tab->do_firstmatch)
         {
-          if (tab->ref.cond_guards[part])
+          extra.append(STRING_WITH_LEN("; FirstMatch("));
+          TABLE *prev_table=tab->do_firstmatch->table;
+          if (prev_table->derived_select_number)
           {
-            extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
-            break;
+            char namebuf[NAME_LEN];
+            /* Derived table name generation */
+            int len= my_snprintf(namebuf, sizeof(namebuf)-1,
+                                 "<derived%u>",
+                                 prev_table->derived_select_number);
+            extra.append(namebuf, len);
           }
+          else
+            extra.append(prev_table->pos_in_table_list->alias);
+          extra.append(STRING_WITH_LEN(")"));
         }
-        
         /* Skip initial "; "*/
         const char *str= extra.ptr();
         uint32 len= extra.length();
@@ -15376,7 +16937,8 @@
           str += 2;
           len -= 2;
         }
-	item_list.push_back(new Item_string(str, len, cs));
+        item_list.push_back(new Item_string(str, len, cs));
+
       }
       // For next iteration
       used_tables|=table->map;
@@ -15453,6 +17015,36 @@
 }
 
 
+static void print_table_array(THD *thd, String *str, TABLE_LIST **table, 
+                              TABLE_LIST **end)
+{
+  (*table)->print(thd, str);
+
+  for (TABLE_LIST **tbl= table + 1; tbl < end; tbl++)
+  {
+    TABLE_LIST *curr= *tbl;
+    if (curr->outer_join)
+    {
+      /* MySQL converts right to left joins */
+      str->append(STRING_WITH_LEN(" left join "));
+    }
+    else if (curr->straight)
+      str->append(STRING_WITH_LEN(" straight_join "));
+    else if (curr->sj_inner_tables)
+      str->append(STRING_WITH_LEN(" semi join "));
+    else
+      str->append(STRING_WITH_LEN(" join "));
+    curr->print(thd, str);
+    if (curr->on_expr)
+    {
+      str->append(STRING_WITH_LEN(" on("));
+      curr->on_expr->print(str);
+      str->append(')');
+    }
+  }
+}
+
+
 /*
   Print joins from the FROM clause
 
@@ -15474,31 +17066,27 @@
 
   for (TABLE_LIST **t= table + (tables->elements - 1); t >= table; t--)
     *t= ti++;
-
-  DBUG_ASSERT(tables->elements >= 1);
-  (*table)->print(thd, str);
-
-  TABLE_LIST **end= table + tables->elements;
-  for (TABLE_LIST **tbl= table + 1; tbl < end; tbl++)
+  
+  /* 
+    If the first table is a semi-join nest, swap it with something that is
+    not a semi-join nest.
+  */
+  if ((*table)->sj_inner_tables)
   {
-    TABLE_LIST *curr= *tbl;
-    if (curr->outer_join)
-    {
-      /* MySQL converts right to left joins */
-      str->append(STRING_WITH_LEN(" left join "));
-    }
-    else if (curr->straight)
-      str->append(STRING_WITH_LEN(" straight_join "));
-    else
-      str->append(STRING_WITH_LEN(" join "));
-    curr->print(thd, str);
-    if (curr->on_expr)
+    TABLE_LIST **end= table + tables->elements;
+    for (TABLE_LIST **t2= table; t2!=end; t2++)
     {
-      str->append(STRING_WITH_LEN(" on("));
-      curr->on_expr->print(str);
-      str->append(')');
+      if (!(*t2)->sj_inner_tables)
+      {
+        TABLE_LIST *tmp= *t2;
+        *t2= *table;
+        *table= tmp;
+        break;
+      }
     }
   }
+  DBUG_ASSERT(tables->elements >= 1);
+  print_table_array(thd, str, table, table + tables->elements);
 }
 
 

--- 1.117/sql/sql_select.h	2007-05-22 23:53:55 +04:00
+++ 1.118/sql/sql_select.h	2007-05-22 23:53:55 +04:00
@@ -23,11 +23,16 @@
 #include "procedure.h"
 #include <myisam.h>
 
+/* Values in optimize */
+#define KEY_OPTIMIZE_EXISTS		1
+#define KEY_OPTIMIZE_REF_OR_NULL	2
+
 typedef struct keyuse_t {
   TABLE *table;
   Item	*val;				/* or value if no field */
   table_map used_tables;
-  uint	key, keypart, optimize;
+  uint	key, keypart;
+  uint optimize; // 0, or KEY_OPTIMIZE_*
   key_part_map keypart_map;
   ha_rows      ref_table_rows;
   /* 
@@ -89,17 +94,40 @@
 */
 
 typedef struct st_cache_field {
+  /* 
+    Where source data is located (i.e. this points to somewhere in 
+    tableX->record[0])
+  */
   char *str;
-  uint length,blob_length;
+  uint length; /* Length of data at *str, in bytes */
+  uint blob_length; /* Valid IFF blob_field != 0 */
   Field_blob *blob_field;
-  bool strip;
+  bool strip; /* TRUE <=> Strip endspaces ?? */
+
+  TABLE *get_rowid; /* _ != NULL <=> */
 } CACHE_FIELD;
 
 
-typedef struct st_join_cache {
-  uchar *buff,*pos,*end;
-  uint records,record_nr,ptr_record,fields,length,blobs;
-  CACHE_FIELD *field,**blob_ptr;
+typedef struct st_join_cache 
+{
+  uchar *buff;
+  uchar *pos;    /* Start of free space in the buffer */
+  uchar *end;
+  uint records;  /* # of row cominations currently stored in the cache */
+  uint record_nr;
+  uint ptr_record; 
+  /* 
+    Number of fields (i.e. cache_field objects). Those correspond to table
+    columns, and there are also special fields for
+     - table's column null bits
+     - table's null-complementation byte
+     - [new] table's rowid.
+  */
+  uint fields; 
+  uint length; 
+  uint blobs;
+  CACHE_FIELD *field;
+  CACHE_FIELD **blob_ptr;
   SQL_SELECT *select;
 } JOIN_CACHE;
 
@@ -127,6 +155,8 @@
 #define TAB_INFO_USING_WHERE 4
 #define TAB_INFO_FULL_SCAN_ON_NULL 8
 
+class SJ_TMP_TABLE;
+
 typedef enum_nested_loop_state
 (*Next_select_func)(JOIN *, struct st_join_table *, bool);
 typedef int (*Read_record_func)(struct st_join_table *tab);
@@ -190,9 +220,42 @@
   TABLE_REF	ref;
   JOIN_CACHE	cache;
   JOIN		*join;
-  /* Bitmap of nested joins this table is part of */
-  nested_join_map embedding_map;
+
+  /* SemiJoinDuplicateElimination variables: */
+  /*
+    Embedding SJ-nest (may be not the direct parent), or NULL if none.
+    This variable holds the result of table pullout.
+  */
+  TABLE_LIST    *emb_sj_nest;
+
+  /* Variables to control NL-Join executioner */
+  SJ_TMP_TABLE  *flush_weedout_table;
+  SJ_TMP_TABLE  *check_weed_out_table;
+  struct st_join_table  *do_firstmatch;
   
+  /* 
+    TRUE<=> this tab is covered by SJ dups range (used to disable join
+    buffering) [slated for removal]
+  */
+  bool inside_sj_dups_range;
+  enum { 
+    /* If set, the rowid of this table must be put into the temptable. */
+    KEEP_ROWID=1, 
+    /* 
+      If set, one should call h->position() to obtain the rowid,
+      otherwise, the rowid is assumed to already be in h->ref
+      (this is because join caching and filesort() save the rowid and then
+      put it back into h->ref)
+    */
+    CALL_POSITION=2
+  };
+  /* A set of flags from the above enum */
+  int  rowid_keep_flags;
+
+
+  /* NestedOuterJoins: Bitmap of nested joins this table is part of */
+  nested_join_map embedding_map;
+
   void cleanup();
   inline bool is_using_loose_index_scan()
   {
@@ -249,6 +312,54 @@
 } ROLLUP;
 
 
+/*
+  Describes use of one temporary table to weed out join duplicates.
+  The temporar
+
+  Used to
+    - create a temp table
+    - when we reach the weed-out tab, walk through rowid-ed tabs and
+      and copy rowids.
+      For each table we need
+       - rowid offset
+       - null bit address.
+
+  TODO describe the confluent case of FirstMatch
+  TODO walk through and remove extra members
+*/
+
+class SJ_TMP_TABLE : public Sql_alloc
+{
+public:
+  uint start_idx;
+  uint end_idx;
+
+  /* Array of pointers to tables that should be "used" */
+  class TAB
+  {
+  public:
+    JOIN_TAB *join_tab;
+    uint rowid_offset;
+    byte *null_addr;
+    byte null_bit;
+  };
+  TAB *tabs;
+  TAB *tabs_end;
+
+  uint null_bits;
+  uint null_bytes;
+  uint rowid_len;
+
+  TABLE *tmp_table;
+
+  MI_COLUMNDEF *start_recinfo;
+  MI_COLUMNDEF *recinfo;
+
+  /* Pointer to next table (next->start_idx > this->end_idx) */
+  SJ_TMP_TABLE *next; 
+};
+
+
 class JOIN :public Sql_alloc
 {
   JOIN(const JOIN &rhs);                        /* not implemented */
@@ -258,7 +369,9 @@
   JOIN_TAB **map2table;    // mapping between table indexes and JOIN_TABs
   JOIN_TAB *join_tab_save; // saved join_tab for subquery reexecution
   TABLE    **table,**all_tables,*sort_by_table;
-  uint	   tables,const_tables;
+  uint	   tables;        /* Number of tables in the join */
+  uint     outer_tables;  /* Number of tables that are not inside semijoin */
+  uint     const_tables;
   uint	   send_group_parts;
   bool	   sort_and_group,first_record,full_join,group, no_field_update;
   bool	   do_send_rows;
@@ -357,6 +470,13 @@
   
   bool union_part; // this subselect is part of union 
   bool optimized; // flag to avoid double optimization in EXPLAIN
+  
+  //psergey-todo: Those two leak memory atm. Pre-alloc on pool?
+  Dynamic_array<Item_in_subselect*> sj_subselects;
+  Dynamic_array<TABLE_LIST*> sj_nests;
+
+  /* Descriptions of temporary tables used to weed-out semi-join duplicates */
+  SJ_TMP_TABLE  *sj_tmp_tables;
 
   /* 
     storage for caching buffers allocated during query execution. 
@@ -429,6 +549,7 @@
     tmp_table_param.init();
     tmp_table_param.end_write_records= HA_POS_ERROR;
     rollup.state= ROLLUP::STATE_NONE;
+    sj_tmp_tables= NULL;
   }
 
   int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num,
@@ -441,6 +562,7 @@
   int destroy();
   void restore_tmp();
   bool alloc_func_list();
+  bool flatten_subqueries();
   bool make_sum_func_list(List<Item> &all_fields, List<Item> &send_fields,
 			  bool before_group_by, bool recompute= FALSE);
 
@@ -509,8 +631,10 @@
 		       uint elements, List<Item> &fields);
 void copy_fields(TMP_TABLE_PARAM *param);
 void copy_funcs(Item **func_ptr);
-bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param,
-			     int error, bool ignore_last_dupp_error);
+bool create_myisam_from_heap(THD *thd, TABLE *table,
+                             MI_COLUMNDEF *start_recinfo,
+                             MI_COLUMNDEF **recinfo, 
+			     int error, bool ignore_last_dupp_key_error);
 uint find_shortest_key(TABLE *table, const key_map *usable_keys);
 Field* create_tmp_field_from_field(THD *thd, Field* org_field,
                                    const char *name, TABLE *table,

--- 1.393/sql/sql_show.cc	2007-05-22 23:53:55 +04:00
+++ 1.394/sql/sql_show.cc	2007-05-22 23:53:55 +04:00
@@ -2224,9 +2224,10 @@
   int error;
   if ((error= table->file->ha_write_row(table->record[0])))
   {
-    if (create_myisam_from_heap(thd, table, 
-                                table->pos_in_table_list->schema_table_param,
-                                error, 0))
+    TMP_TABLE_PARAM *param= table->pos_in_table_list->schema_table_param;
+
+    if (create_myisam_from_heap(thd, table, param->start_recinfo, 
+                                &param->recinfo, error, 0))
       return 1;
   }
   return 0;

--- 1.51/sql/sql_test.cc	2007-05-22 23:53:55 +04:00
+++ 1.52/sql/sql_test.cc	2007-05-22 23:53:55 +04:00
@@ -206,6 +206,42 @@
   DBUG_VOID_RETURN;
 }
 
+#define FT_KEYPART   (MAX_REF_PARTS+10)
+
+void print_keyuse(KEYUSE *keyuse)
+{
+  char buff[256];
+  char buf2[64]; 
+  const char *fieldname;
+  String str(buff,(uint32) sizeof(buff), system_charset_info);
+  str.length(0);
+  keyuse->val->print(&str);
+  str.append('\0');
+  if (keyuse->keypart == FT_KEYPART)
+    fieldname= "FT_KEYPART";
+  else
+    fieldname= keyuse->table->key_info[keyuse->key].key_part[keyuse->keypart].field->field_name;
+  longlong2str(keyuse->used_tables, buf2, 16); 
+  DBUG_LOCK_FILE;
+  fprintf(DBUG_FILE, "KEYUSE: %s.%s=%s  optimize= %d used_tables=%s "
+          "ref_table_rows= %lu keypart_map= %0lx\n",
+          keyuse->table->alias, fieldname, str.ptr(),
+          keyuse->optimize, buf2, (ulong)keyuse->ref_table_rows, 
+          keyuse->keypart_map);
+  DBUG_UNLOCK_FILE;
+  //key_part_map keypart_map; --?? there can be several? 
+}
+
+
+void print_keyuse_array(DYNAMIC_ARRAY *keyuse_array)
+{
+  DBUG_LOCK_FILE;
+  fprintf(DBUG_FILE, "KEYUSE array (%d elements)\n", keyuse_array->elements);
+  DBUG_UNLOCK_FILE;
+  for(uint i=0; i < keyuse_array->elements; i++)
+    print_keyuse((KEYUSE*)dynamic_array_ptr(keyuse_array, i));
+}
+
 
 /* 
   Print the current state during query optimization.
@@ -540,3 +576,205 @@
   Events::get_instance()->dump_internal_status();
   puts("");
 }
+
+
+#ifndef DBUG_OFF
+#ifdef EXTRA_DEBUG_DUMP_TABLE_LISTS
+
+
+/*
+  A fixed-size FIFO pointer queue that also doesn't allow one to put an
+  element that has previously been put into it. 
+  
+  There is a hard-coded limit of the total number of queue put operations.
+  The implementation is trivial and is intended for use in debug dumps only.
+*/
+
+template <class T> class Unique_fifo_queue
+{
+public:
+  /* Add an element to the queue */
+  void push_back(T *tbl)
+  {
+    if (!tbl)
+      return;
+    // check if we've already scheduled and/or dumped the element
+    for (int i= 0; i < last; i++)
+    {
+      if (elems[i] == tbl)
+        return;
+    }
+    elems[last++]=  tbl;
+  }
+
+  bool pop_first(T **elem)
+  {
+    if (first < last)
+    {
+      *elem= elems[first++];
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  void reset()
+  {
+    first= last= 0;
+  }
+  enum { MAX_ELEMS=1000};
+  T *elems[MAX_ELEMS];
+  int first; // First undumped table
+  int last;  // Last undumped element
+};
+
+class Dbug_table_list_dumper
+{
+  FILE *out;
+  Unique_fifo_queue<TABLE_LIST> tables_fifo;
+  Unique_fifo_queue<List<TABLE_LIST> > tbl_lists;
+public:
+  void dump_one_struct(TABLE_LIST *tbl);
+
+  int dump_graph(st_select_lex *select_lex, TABLE_LIST *first_leaf);
+};
+
+
+void dump_TABLE_LIST_graph(SELECT_LEX *select_lex, TABLE_LIST* tl)
+{
+  Dbug_table_list_dumper dumper;
+  dumper.dump_graph(select_lex, tl);
+}
+
+
+/* 
+  - Dump one TABLE_LIST objects and its outgoing edges
+  - Schedule that other objects seen along the edges are dumped too.
+*/
+
+void Dbug_table_list_dumper::dump_one_struct(TABLE_LIST *tbl)
+{
+  fprintf(out, "\"%p\" [\n", tbl);
+  fprintf(out, "  label = \"%p|", tbl);
+  fprintf(out, "alias=%s|", tbl->alias? tbl->alias : "NULL");
+  fprintf(out, "<next_leaf>next_leaf=%p|", tbl->next_leaf);
+  fprintf(out, "<next_local>next_local=%p|", tbl->next_local);
+  fprintf(out, "<next_global>next_global=%p|", tbl->next_global);
+  fprintf(out, "<embedding>embedding=%p", tbl->embedding);
+
+  if (tbl->nested_join)
+    fprintf(out, "|<nested_j>nested_j=%p", tbl->nested_join);
+  if (tbl->join_list)
+    fprintf(out, "|<join_list>join_list=%p", tbl->join_list);
+  if (tbl->on_expr)
+    fprintf(out, "|<on_expr>on_expr=%p", tbl->on_expr);
+  fprintf(out, "\"\n");
+  fprintf(out, "  shape = \"record\"\n];\n\n");
+ 
+  if (tbl->next_leaf)
+  {
+    fprintf(out, "\n\"%p\":next_leaf -> \"%p\"[ color = \"#000000\" ];\n",  
+            tbl, tbl->next_leaf);
+    tables_fifo.push_back(tbl->next_leaf);
+  }
+  if (tbl->next_local)
+  {
+    fprintf(out, "\n\"%p\":next_local -> \"%p\"[ color = \"#404040\" ];\n",  
+            tbl, tbl->next_local);
+    tables_fifo.push_back(tbl->next_local);
+  }
+  if (tbl->next_global)
+  {
+    fprintf(out, "\n\"%p\":next_global -> \"%p\"[ color = \"#808080\" ];\n",  
+            tbl, tbl->next_global);
+    tables_fifo.push_back(tbl->next_global);
+  }
+
+  if (tbl->embedding)
+  {
+    fprintf(out, "\n\"%p\":embedding -> \"%p\"[ color = \"#FF0000\" ];\n",  
+            tbl, tbl->embedding);
+    tables_fifo.push_back(tbl->embedding);
+  }
+
+  if (tbl->join_list)
+  {
+    fprintf(out, "\n\"%p\":join_list -> \"%p\"[ color = \"#0000FF\" ];\n",  
+            tbl, tbl->join_list);
+    tbl_lists.push_back(tbl->join_list);
+  }
+}
+
+
+int Dbug_table_list_dumper::dump_graph(st_select_lex *select_lex, 
+                                       TABLE_LIST *first_leaf)
+{
+  DBUG_ENTER("Dbug_table_list_dumper::dump_graph");
+  char filename[500];
+  int no = 0;
+  do
+  {
+    sprintf(filename, "tlist_tree%.3d.g", no);
+    if ((out= fopen(filename, "rt")))
+    {
+      /* File exists, try next name */
+      fclose(out);
+    }
+    no++;
+  } while (out);
+ 
+  /* Ok, found an unoccupied name, create the file */
+  if (!(out= fopen(filename, "wt")))
+  {
+    DBUG_PRINT("tree_dump", ("Failed to create output file"));
+    DBUG_RETURN(1);
+  }
+ 
+  DBUG_PRINT("tree_dump", ("dumping tree to %s", filename));
+     
+  fputs("digraph g {\n", out);
+  fputs("graph [", out);
+  fputs("  rankdir = \"LR\"", out);
+  fputs("];", out);
+   
+  TABLE_LIST *tbl;
+  tables_fifo.reset();
+  dump_one_struct(first_leaf);   
+  while (tables_fifo.pop_first(&tbl))
+  {
+    dump_one_struct(tbl);
+  }
+
+  List<TABLE_LIST> *plist;
+  tbl_lists.push_back(&select_lex->top_join_list);
+  while (tbl_lists.pop_first(&plist))
+  {
+    fprintf(out, "\"%p\" [\n", plist);
+    fprintf(out, "  bgcolor = \"\"");
+    fprintf(out, "  label = \"L %p\"", plist);
+    fprintf(out, "  shape = \"record\"\n];\n\n");
+  }
+
+  fprintf(out, " { rank = same; ");
+  for (TABLE_LIST *tl=first_leaf; tl; tl= tl->next_leaf)
+    fprintf(out, " \"%p\"; ", tl);
+  fprintf(out, "};\n");
+  fputs("}", out);
+  fclose(out);
+ 
+  char filename2[500];
+  filename[strlen(filename) - 1]= 0;
+  filename[strlen(filename) - 1]= 0;
+  sprintf(filename2, "%s.query", filename);
+  
+  if ((out= fopen(filename2, "wt")))
+  {
+    fprintf(out, "%s", current_thd->query);
+    fclose(out);
+  }
+  DBUG_RETURN(0);
+}
+
+#endif
+
+#endif 
+

--- 1.218/sql/sql_update.cc	2007-05-22 23:53:55 +04:00
+++ 1.219/sql/sql_update.cc	2007-05-22 23:53:55 +04:00
@@ -1456,7 +1456,9 @@
       {
         if (tmp_table->file->is_fatal_error(error, HA_CHECK_DUP) &&
             create_myisam_from_heap(thd, tmp_table,
-                                         tmp_table_param + offset, error, 1))
+                                         tmp_table_param[offset].start_recinfo,
+                                         &tmp_table_param[offset].recinfo,
+                                         error, 1))
 	{
 	  do_update=0;
 	  DBUG_RETURN(1);			// Not a table_is_full error

--- 1.161/sql/table.h	2007-05-22 23:53:55 +04:00
+++ 1.162/sql/table.h	2007-05-22 23:53:55 +04:00
@@ -659,6 +659,8 @@
        (TABLE_LIST::natural_join != NULL)
        - JOIN ... USING
          (TABLE_LIST::join_using_fields != NULL)
+     - semi-join
+       ;
 */
 
 typedef struct st_table_list
@@ -675,6 +677,8 @@
   char		*db, *alias, *table_name, *schema_table_name;
   char          *option;                /* Used by cache index  */
   Item		*on_expr;		/* Used with outer join */
+  Item          *sj_on_expr;
+  table_map     sj_inner_tables;
   /*
     The structure of ON expression presented in the member above
     can be changed during certain optimizations. This member
@@ -1036,8 +1040,9 @@
        by the join optimizer. 
     Before each use the counters are zeroed by reset_nj_counters.
   */
-  uint              counter;
+  uint              counter_;
   nested_join_map   nj_map;          /* Bit used to identify this nested join*/
+  table_map         sj_depends_on;
 } NESTED_JOIN;
 
 

--- 1.146/sql/sql_union.cc	2007-05-22 23:53:55 +04:00
+++ 1.147/sql/sql_union.cc	2007-05-22 23:53:55 +04:00
@@ -65,7 +65,8 @@
   {
     /* create_myisam_from_heap will generate error if needed */
     if (table->file->is_fatal_error(error, HA_CHECK_DUP) &&
-        create_myisam_from_heap(thd, table, &tmp_table_param, error, 1))
+        create_myisam_from_heap(thd, table, tmp_table_param.start_recinfo, 
+                                &tmp_table_param.recinfo, error, 1))
       return 1;
   }
   return 0;
@@ -469,6 +470,19 @@
         sl->join->select_options= 
           (select_limit_cnt == HA_POS_ERROR || sl->braces) ?
           sl->options & ~OPTION_FOUND_ROWS : sl->options | found_rows_for_union;
+
+        if (!this->item)
+        {
+          //dump_TABLE_LIST_struct(select_lex, select_lex->leaf_tables);
+          /* We're not in a subquery predicate */
+          if (sl->join->flatten_subqueries())
+          {
+            thd->net.report_error= 1;
+            DBUG_RETURN(TRUE);
+          }
+          //dump_TABLE_LIST_struct(select_lex, select_lex->leaf_tables);
+        }
+        ///
 	saved_error= sl->join->optimize();
       }
       if (!saved_error)

--- 1.166/mysql-test/r/subselect.result	2007-05-22 23:53:55 +04:00
+++ 1.167/mysql-test/r/subselect.result	2007-05-22 23:53:55 +04:00
@@ -1294,31 +1294,31 @@
 4
 explain extended select * from t2 where t2.a in (select a from t1);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	NULL	PRIMARY	4	NULL	4	100.00	Using where; Using index
-2	DEPENDENT SUBQUERY	t1	unique_subquery	PRIMARY	PRIMARY	4	func	1	100.00	Using index
+1	PRIMARY	t2	index	PRIMARY	PRIMARY	4	NULL	4	100.00	Using index
+1	PRIMARY	t1	index	PRIMARY	PRIMARY	4	NULL	4	75.00	Using where; Using index
 Warnings:
-Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` where <in_optimizer>(`test`.`t2`.`a`,<exists>(<primary_index_lookup>(<cache>(`test`.`t2`.`a`) in t1 on PRIMARY)))
+Note	1003	select `test`.`t2`.`a` AS `a` from (`test`.`t1`) join `test`.`t2` where (`test`.`t1`.`a` = `test`.`t2`.`a`)
 select * from t2 where t2.a in (select a from t1 where t1.b <> 30);
 a
 2
 4
 explain extended select * from t2 where t2.a in (select a from t1 where t1.b <> 30);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	NULL	PRIMARY	4	NULL	4	100.00	Using where; Using index
-2	DEPENDENT SUBQUERY	t1	unique_subquery	PRIMARY	PRIMARY	4	func	1	100.00	Using where
+1	PRIMARY	t2	index	PRIMARY	PRIMARY	4	NULL	4	100.00	Using index
+1	PRIMARY	t1	ALL	PRIMARY	NULL	NULL	NULL	4	75.00	Using where
 Warnings:
-Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` where <in_optimizer>(`test`.`t2`.`a`,<exists>(<primary_index_lookup>(<cache>(`test`.`t2`.`a`) in t1 on PRIMARY where (`test`.`t1`.`b` <> 30))))
+Note	1003	select `test`.`t2`.`a` AS `a` from (`test`.`t1`) join `test`.`t2` where ((`test`.`t1`.`a` = `test`.`t2`.`a`) and (`test`.`t1`.`b` <> 30))
 select * from t2 where t2.a in (select t1.a from t1,t3 where t1.b=t3.a);
 a
 2
 3
 explain extended select * from t2 where t2.a in (select t1.a from t1,t3 where t1.b=t3.a);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	NULL	PRIMARY	4	NULL	4	100.00	Using where; Using index
-2	DEPENDENT SUBQUERY	t1	eq_ref	PRIMARY	PRIMARY	4	func	1	100.00	Using where
-2	DEPENDENT SUBQUERY	t3	eq_ref	PRIMARY	PRIMARY	4	test.t1.b	1	100.00	Using where; Using index
+1	PRIMARY	t2	index	PRIMARY	PRIMARY	4	NULL	4	100.00	Using index
+1	PRIMARY	t1	ALL	PRIMARY	NULL	NULL	NULL	4	75.00	Using where
+1	PRIMARY	t3	eq_ref	PRIMARY	PRIMARY	4	test.t1.b	1	100.00	Using index; FirstMatch(t1)
 Warnings:
-Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` where <in_optimizer>(`test`.`t2`.`a`,<exists>(select 1 AS `Not_used` from `test`.`t1` join `test`.`t3` where ((`test`.`t3`.`a` = `test`.`t1`.`b`) and (<cache>(`test`.`t2`.`a`) = `test`.`t1`.`a`))))
+Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1` join `test`.`t3`) where ((`test`.`t1`.`a` = `test`.`t2`.`a`) and (`test`.`t3`.`a` = `test`.`t1`.`b`))
 drop table t1, t2, t3;
 create table t1 (a int, b int, index a (a,b));
 create table t2 (a int, index a (a));
@@ -1333,31 +1333,31 @@
 4
 explain extended select * from t2 where t2.a in (select a from t1);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	NULL	a	5	NULL	4	100.00	Using where; Using index
-2	DEPENDENT SUBQUERY	t1	index_subquery	a	a	5	func	1001	100.00	Using index
+1	PRIMARY	t2	index	a	a	5	NULL	4	100.00	Using index
+1	PRIMARY	t1	ref	a	a	5	test.t2.a	101	100.00	Using where; Using index; FirstMatch(t2)
 Warnings:
-Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` where <in_optimizer>(`test`.`t2`.`a`,<exists>(<index_lookup>(<cache>(`test`.`t2`.`a`) in t1 on a)))
+Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) where (`test`.`t1`.`a` = `test`.`t2`.`a`)
 select * from t2 where t2.a in (select a from t1 where t1.b <> 30);
 a
 2
 4
 explain extended select * from t2 where t2.a in (select a from t1 where t1.b <> 30);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	NULL	a	5	NULL	4	100.00	Using where; Using index
-2	DEPENDENT SUBQUERY	t1	index_subquery	a	a	5	func	1001	100.00	Using index; Using where
+1	PRIMARY	t2	index	a	a	5	NULL	4	100.00	Using index
+1	PRIMARY	t1	ref	a	a	5	test.t2.a	101	100.00	Using where; Using index; FirstMatch(t2)
 Warnings:
-Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` where <in_optimizer>(`test`.`t2`.`a`,<exists>(<index_lookup>(<cache>(`test`.`t2`.`a`) in t1 on a where (`test`.`t1`.`b` <> 30))))
+Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) where ((`test`.`t1`.`a` = `test`.`t2`.`a`) and (`test`.`t1`.`b` <> 30))
 select * from t2 where t2.a in (select t1.a from t1,t3 where t1.b=t3.a);
 a
 2
 3
 explain extended select * from t2 where t2.a in (select t1.a from t1,t3 where t1.b=t3.a);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	NULL	a	5	NULL	4	100.00	Using where; Using index
-2	DEPENDENT SUBQUERY	t1	ref	a	a	5	func	1001	100.00	Using where; Using index
-2	DEPENDENT SUBQUERY	t3	index	a	a	5	NULL	3	100.00	Using where; Using index
+1	PRIMARY	t2	index	a	a	5	NULL	4	100.00	Using index; Start temporary
+1	PRIMARY	t1	ref	a	a	5	test.t2.a	101	100.00	Using where; Using index
+1	PRIMARY	t3	index	a	a	5	NULL	3	100.00	Using where; Using index; End temporary
 Warnings:
-Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` where <in_optimizer>(`test`.`t2`.`a`,<exists>(select 1 AS `Not_used` from `test`.`t1` join `test`.`t3` where ((`test`.`t3`.`a` = `test`.`t1`.`b`) and (<cache>(`test`.`t2`.`a`) = `test`.`t1`.`a`))))
+Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1` join `test`.`t3`) where ((`test`.`t1`.`a` = `test`.`t2`.`a`) and (`test`.`t3`.`a` = `test`.`t1`.`b`))
 insert into t1 values (3,31);
 select * from t2 where t2.a in (select a from t1 where t1.b <> 30);
 a
@@ -1370,10 +1370,10 @@
 4
 explain extended select * from t2 where t2.a in (select a from t1 where t1.b <> 30);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	NULL	a	5	NULL	4	100.00	Using where; Using index
-2	DEPENDENT SUBQUERY	t1	index_subquery	a	a	5	func	1001	100.00	Using index; Using where
+1	PRIMARY	t2	index	a	a	5	NULL	4	100.00	Using index
+1	PRIMARY	t1	ref	a	a	5	test.t2.a	101	100.00	Using where; Using index; FirstMatch(t2)
 Warnings:
-Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` where <in_optimizer>(`test`.`t2`.`a`,<exists>(<index_lookup>(<cache>(`test`.`t2`.`a`) in t1 on a where (`test`.`t1`.`b` <> 30))))
+Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) where ((`test`.`t1`.`a` = `test`.`t2`.`a`) and (`test`.`t1`.`b` <> 30))
 drop table t1, t2, t3;
 create table t1 (a int, b int);
 create table t2 (a int, b int);
@@ -2819,10 +2819,10 @@
 Note	1003	select `test`.`t1`.`one` AS `one`,`test`.`t1`.`two` AS `two`,<in_optimizer>((`test`.`t1`.`one`,`test`.`t1`.`two`),<exists>(select `test`.`t2`.`one` AS `one`,`test`.`t2`.`two` AS `two` from `test`.`t2` where ((`test`.`t2`.`flag` = _latin1'0') and trigcond(((<cache>(`test`.`t1`.`one`) = `test`.`t2`.`one`) or isnull(`test`.`t2`.`one`))) and trigcond(((<cache>(`test`.`t1`.`two`) = `test`.`t2`.`two`) or isnull(`test`.`t2`.`two`)))) having (trigcond(<is_not_null_test>(`test`.`t2`.`one`)) and trigcond(<is_not_null_test>(`test`.`t2`.`two`))))) AS `test` from `test`.`t1`
 explain extended SELECT one,two from t1 where ROW(one,two) IN (SELECT one,two FROM t2 WHERE flag = 'N');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	8	100.00	Using where
-2	DEPENDENT SUBQUERY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where
+1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	8	100.00	Start temporary
+1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; End temporary
 Warnings:
-Note	1003	select `test`.`t1`.`one` AS `one`,`test`.`t1`.`two` AS `two` from `test`.`t1` where <in_optimizer>((`test`.`t1`.`one`,`test`.`t1`.`two`),<exists>(select `test`.`t2`.`one` AS `one`,`test`.`t2`.`two` AS `two` from `test`.`t2` where ((`test`.`t2`.`flag` = _latin1'N') and (<cache>(`test`.`t1`.`one`) = `test`.`t2`.`one`) and (<cache>(`test`.`t1`.`two`) = `test`.`t2`.`two`))))
+Note	1003	select `test`.`t1`.`one` AS `one`,`test`.`t1`.`two` AS `two` from `test`.`t1` semi join (`test`.`t2`) where ((`test`.`t2`.`two` = `test`.`t1`.`two`) and (`test`.`t2`.`one` = `test`.`t1`.`one`) and (`test`.`t2`.`flag` = _latin1'N'))
 explain extended SELECT one,two,ROW(one,two) IN (SELECT one,two FROM t2 WHERE flag = '0' group by one,two) as 'test' from t1;
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	8	100.00	

--- 1.14/mysql-test/r/subselect2.result	2007-05-22 23:53:55 +04:00
+++ 1.15/mysql-test/r/subselect2.result	2007-05-22 23:53:55 +04:00
@@ -123,14 +123,14 @@
 c373e9f5ad07993f3859444553544200	Last Discussion	c373e9f5ad079174ff17444553544200	c373e9f5ad0796c0eca4444553544200	Goldilocks	2003-06-09 11:21:06	Title: Last Discussion	NULL	Setting new abstract and keeping doc checked out	2003-06-09 10:51:26	2003-06-09 10:51:26	NULL	NULL	NULL	03eea05112b845949f3fd03278b5fe43	2003-06-09 11:21:06	admin	0	NULL	Discussion	NULL	NULL
 EXPLAIN SELECT t2.*, t4.DOCTYPENAME, t1.CONTENTSIZE,t1.MIMETYPE FROM t2 INNER JOIN t4 ON t2.DOCTYPEID = t4.DOCTYPEID LEFT OUTER JOIN t1 ON t2.DOCID = t1.DOCID WHERE t2.FOLDERID IN(SELECT t3.FOLDERID FROM t3 WHERE t3.PARENTID IN(SELECT t3.FOLDERID FROM t3 WHERE t3.PARENTID IN(SELECT t3.FOLDERID FROM t3 WHERE t3.PARENTID IN(SELECT t3.FOLDERID FROM t3 WHERE t3.PARENTID IN(SELECT t3.FOLDERID FROM t3 WHERE t3.PARENTID='2f6161e879db43c1a5b82c21ddc49089' AND t3.FOLDERNAME = 'Level1') AND t3.FOLDERNAME = 'Level2') AND t3.FOLDERNAME = 'Level3') AND t3.FOLDERNAME = 'CopiedFolder') AND t3.FOLDERNAME = 'Movie Reviews') AND t2.DOCNAME = 'Last Discussion';
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t2	ALL	DDOCTYPEID_IDX	NULL	NULL	NULL	9	Using where
+1	PRIMARY	t3	ref	PRIMARY,FFOLDERID_IDX,CMFLDRPARNT_IDX	CMFLDRPARNT_IDX	35	const	2	Using where; Start temporary
+1	PRIMARY	t3	ref	PRIMARY,FFOLDERID_IDX,CMFLDRPARNT_IDX	CMFLDRPARNT_IDX	35	test.t3.FOLDERID	1	Using where
+1	PRIMARY	t3	ref	PRIMARY,FFOLDERID_IDX,CMFLDRPARNT_IDX	CMFLDRPARNT_IDX	35	test.t3.FOLDERID	1	Using where
+1	PRIMARY	t3	ref	PRIMARY,FFOLDERID_IDX,CMFLDRPARNT_IDX	CMFLDRPARNT_IDX	35	test.t3.FOLDERID	1	Using where
+1	PRIMARY	t3	ref	PRIMARY,FFOLDERID_IDX,CMFLDRPARNT_IDX	CMFLDRPARNT_IDX	35	test.t3.FOLDERID	1	Using where
+1	PRIMARY	t2	ALL	DDOCTYPEID_IDX,DFOLDERID_IDX	NULL	NULL	NULL	9	Using where; End temporary
 1	PRIMARY	t1	eq_ref	PRIMARY	PRIMARY	34	test.t2.DOCID	1	
 1	PRIMARY	t4	eq_ref	PRIMARY	PRIMARY	34	test.t2.DOCTYPEID	1	
-2	DEPENDENT SUBQUERY	t3	unique_subquery	PRIMARY,FFOLDERID_IDX	PRIMARY	34	func	1	Using where
-3	DEPENDENT SUBQUERY	t3	unique_subquery	PRIMARY,FFOLDERID_IDX	PRIMARY	34	func	1	Using where
-4	DEPENDENT SUBQUERY	t3	unique_subquery	PRIMARY,FFOLDERID_IDX	PRIMARY	34	func	1	Using where
-5	DEPENDENT SUBQUERY	t3	unique_subquery	PRIMARY,FFOLDERID_IDX	PRIMARY	34	func	1	Using where
-6	DEPENDENT SUBQUERY	t3	unique_subquery	PRIMARY,FFOLDERID_IDX,CMFLDRPARNT_IDX	PRIMARY	34	func	1	Using where
 drop table t1, t2, t3, t4;
 CREATE TABLE t1 (a int(10) , PRIMARY KEY (a)) Engine=InnoDB;
 INSERT INTO t1 VALUES (1),(2);

--- 1.35/sql/item_row.cc	2007-05-22 23:53:55 +04:00
+++ 1.36/sql/item_row.cc	2007-05-22 23:53:55 +04:00
@@ -119,6 +119,18 @@
   }
 }
 
+void Item_row::fix_after_pullout(st_select_lex *new_parent, Item **ref)
+{
+  used_tables_cache= 0;
+  const_item_cache= 1;
+  for (uint i= 0; i < arg_count; i++)
+  {
+    items[i]->fix_after_pullout(new_parent, &items[i]);
+    used_tables_cache|= items[i]->used_tables();
+    const_item_cache&= items[i]->const_item();
+  }
+}
+
 bool Item_row::check_cols(uint c)
 {
   if (c != arg_count)

--- 1.24/sql/item_row.h	2007-05-22 23:53:55 +04:00
+++ 1.25/sql/item_row.h	2007-05-22 23:53:55 +04:00
@@ -59,6 +59,7 @@
     return 0;
   };
   bool fix_fields(THD *thd, Item **ref);
+  void fix_after_pullout(st_select_lex *new_parent, Item **ref);
   void cleanup();
   void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields);
   table_map used_tables() const { return used_tables_cache; };

--- 1.146/sql/item_subselect.cc	2007-05-22 23:53:55 +04:00
+++ 1.147/sql/item_subselect.cc	2007-05-22 23:53:55 +04:00
@@ -612,7 +612,7 @@
 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)
+  pushed_cond_guards(NULL), upper_item(0), converted_to_sj(FALSE)
 {
   DBUG_ENTER("Item_in_subselect::Item_in_subselect");
   left_expr= left_exp;
@@ -1519,7 +1519,11 @@
 bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref)
 {
   bool result = 0;
-  
+  ref_ptr= ref;
+
+  if (converted_to_sj)
+    return !( (*ref)= new Item_int(1));
+
   if (thd_arg->lex->view_prepare_mode && left_expr && !left_expr->fixed)
     result = left_expr->fix_fields(thd_arg, &left_expr);
 

--- 1.89/sql/item_subselect.h	2007-05-22 23:53:55 +04:00
+++ 1.90/sql/item_subselect.h	2007-05-22 23:53:55 +04:00
@@ -31,13 +31,15 @@
 class Item_subselect :public Item_result_field
 {
   my_bool value_assigned; /* value already assigned to subselect */
-protected:
+public:
   /* thread handler, will be assigned in fix_fields only */
   THD *thd;
   /* substitution instead of subselect in case of optimization */
   Item *substitution;
   /* unit of subquery */
+public:
   st_select_lex_unit *unit;
+protected:
   /* engine that perform execution of subselect (single select or union) */
   subselect_engine *engine;
   /* old engine if engine was changed */
@@ -238,8 +240,9 @@
 
 class Item_in_subselect :public Item_exists_subselect
 {
-protected:
+public:
   Item *left_expr;
+protected:
   /*
     expr & optimizer used in subselect rewriting to store Item for
     all JOIN in UNION
@@ -252,6 +255,20 @@
 public:
   /* Used to trigger on/off conditions that were pushed down to subselect */
   bool *pushed_cond_guards;
+  
+  /* Priority of this predicate in the convert-to-semi-join-nest process. */
+  int sj_convert_priority;
+
+  /* TRUE <=> This Item was converted to semi-join nest and should be removed */
+  bool converted_to_sj;
+
+  /* 
+    Location of the subquery predicate. It is either
+     - pointer to join nest if the subquery predicate is in the ON expression
+     - (TABLE_LIST*)1 if the predicate is in the WHERE.
+  */
+  TABLE_LIST *expr_join_nest;
+  Item **ref_ptr;
 
   bool *get_cond_guard(int i)
   {
@@ -265,7 +282,7 @@
   Item_in_subselect(Item * left_expr, st_select_lex *select_lex);
   Item_in_subselect()
     :Item_exists_subselect(), optimizer(0), abort_on_null(0), transformed(0),
-     pushed_cond_guards(NULL), upper_item(0)
+     pushed_cond_guards(NULL), upper_item(0), converted_to_sj(FALSE)
   {}
 
   subs_type substype() { return IN_SUBS; }

--- 1.17/mysql-test/r/kill.result	2007-05-22 23:53:55 +04:00
+++ 1.18/mysql-test/r/kill.result	2007-05-22 23:53:55 +04:00
@@ -22,7 +22,7 @@
 insert into t2 select id from t1;
 create table t3 (kill_id int);
 insert into t3 values(connection_id());
-select id from t1 where id in (select distinct id from t2);
+select id from t1 where id in (select distinct id from t2) or id < 10000;
 select ((@id := kill_id) - kill_id) from t3;
 ((@id := kill_id) - kill_id)
 0

--- 1.25/mysql-test/t/kill.test	2007-05-22 23:53:55 +04:00
+++ 1.26/mysql-test/t/kill.test	2007-05-22 23:53:55 +04:00
@@ -75,13 +75,12 @@
 
 create table t3 (kill_id int);
 insert into t3 values(connection_id());
-
 connect (conn2, localhost, root,,);
 connection conn2;
 
 connection conn1;
 -- disable_result_log
-send select id from t1 where id in (select distinct id from t2);
+send select id from t1 where id in (select distinct id from t2) or id < 10000;
 -- enable_result_log
 
 connection conn2;

--- 1.18/sql/sql_bitmap.h	2007-05-22 23:53:55 +04:00
+++ 1.19/sql/sql_bitmap.h	2007-05-22 23:53:55 +04:00
@@ -138,3 +138,60 @@
   ulonglong to_ulonglong() const { return map; }
 };
 
+
+/* An iterator to quickly walk over bits in unlonglong bitmap. */
+class Table_map_iterator
+{
+  ulonglong bmp;
+  uint no;
+public:
+  Table_map_iterator(ulonglong t) : bmp(t), no(0) {}
+  int next_bit()
+  {
+    static const char last_bit[16]= {32, 0, 1, 0, 
+                                      2, 0, 1, 0, 
+                                      3, 0, 1, 0,
+                                      2, 0, 1, 0};
+    uint bit;
+    while ((bit= last_bit[bmp & 0xF]) == 32)
+    {
+      no += 4;
+      bmp= bmp >> 4;
+      if (!bmp)
+        return BITMAP_END;
+    }
+    bmp &= ~(1LL << bit);
+    return no + bit;
+  }
+  enum { BITMAP_END= 64 };
+};
+
+
+#if 0
+void print_bits(table_map bmp)
+{
+  Table_map_iterator it(bmp);
+  int i, first= 1;
+  fprintf(stderr, "0x%llx = ", bmp);
+  while ((i= it.next_bit()) != Table_map_iterator::BITMAP_END)
+  {
+    fprintf(stderr, " %s 2^%d", (first?"":"+"), i);
+    if (first)
+      first= 0;
+  }
+  fprintf(stderr, "\n");
+}
+
+int main()
+{
+  print_bits(1024);
+  print_bits(3);
+  print_bits(0xF);
+  print_bits(0xF0);
+  print_bits(35);
+  print_bits(1LL<<63);
+  print_bits(0);
+  print_bits(-1LL);
+}
+#endif
+

--- 1.6/mysql-test/r/subselect3.result	2007-05-22 23:53:55 +04:00
+++ 1.7/mysql-test/r/subselect3.result	2007-05-22 23:53:55 +04:00
@@ -99,7 +99,7 @@
 1	1
 show status like '%Handler_read_rnd_next';
 Variable_name	Value
-Handler_read_rnd_next	5
+Handler_read_rnd_next	11
 delete from t2;
 insert into t2 values (NULL, 0),(NULL, 0), (NULL, 0), (NULL, 0);
 flush status;
@@ -337,8 +337,8 @@
 bb	NULL	NULL
 select oref, a from t2 where a in (select ie from t1 where oref=t2.oref);
 oref	a
-ff	2
 aa	1
+ff	2
 select oref, a from t2 where a not in (select ie from t1 where oref=t2.oref);
 oref	a
 bb	2
@@ -421,8 +421,8 @@
 bb	NULL	NULL
 select oref, a from t2 where a in (select ie from t1 where oref=t2.oref);
 oref	a
-ff	2
 aa	1
+ff	2
 select oref, a from t2 where a not in (select ie from t1 where oref=t2.oref);
 oref	a
 bb	2
@@ -515,8 +515,8 @@
 dd	1	NULL	0
 select oref, a, b from t2 where (a,b) in (select ie1,ie2 from t1 where oref=t2.oref);
 oref	a	b
-ff	2	2
 aa	1	1
+ff	2	2
 select oref, a, b from t2 where (a,b) not in (select ie1,ie2 from t1 where oref=t2.oref);
 oref	a	b
 bb	2	1
@@ -560,8 +560,8 @@
 dd	1	NULL	0
 select oref, a, b from t2 where (a,b) in (select ie1,ie2 from t1 where oref=t2.oref);
 oref	a	b
-ff	2	2
 aa	1	1
+ff	2	2
 select oref, a, b from t2 where (a,b) not in (select ie1,ie2 from t1 where oref=t2.oref);
 oref	a	b
 bb	2	1
--- New file ---
+++ mysql-test/r/subselect_sj.result	07/05/22 23:53:40
drop table if exists t0, t1, t2, t10, t11, t12;
create table t0 (a int);
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
create table t1(a int, b int);
insert into t1 values (0,0),(1,1),(2,2);
create table t2 as select * from t1;
create table t11(a int, b int);
create table t10 (pk int, a int, primary key(pk));
insert into t10 select a,a from t0;
create table t12 like t10;
insert into t12 select * from t10;
Flattened because of dependency, t10=func(t1)
explain select * from t1 where a in (select pk from t10);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	t10	eq_ref	PRIMARY	PRIMARY	4	test.t1.a	1	Using index
select * from t1 where a in (select pk from t10);
a	b
0	0
1	1
2	2
A confluent case of dependency
explain select * from t1 where a in (select a from t10 where pk=12);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	t10	const	PRIMARY	PRIMARY	4	const	1	Using where
select * from t1 where a in (select a from t10 where pk=12);
a	b
An empty table inside
explain select * from t1 where a in (select a from t11);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t11	system	NULL	NULL	NULL	NULL	0	const row not found
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	Using where
select * from t1 where a in (select a from t11);
a	b
explain select * from t1 where a in (select pk from t10) and b in (select pk from t10);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	t10	eq_ref	PRIMARY	PRIMARY	4	test.t1.a	1	Using index
1	PRIMARY	t10	eq_ref	PRIMARY	PRIMARY	4	test.t1.b	1	Using index
select * from t1 where a in (select pk from t10) and b in (select pk from t10);
a	b
0	0
1	1
2	2
flattening a nested subquery
explain select * from t1 where a in (select pk from t10 where t10.a in (select pk from t12));
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	t10	eq_ref	PRIMARY	PRIMARY	4	test.t1.a	1	
1	PRIMARY	t12	eq_ref	PRIMARY	PRIMARY	4	test.t10.a	1	Using index; FirstMatch(t10)
select * from t1 where a in (select pk from t10 where t10.a in (select pk from t12));
a	b
0	0
1	1
2	2
flattening subquery w/ several tables
explain extended select * from t1 where a in (select t10.pk from t10, t12 where t12.pk=t10.a);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	100.00	
1	PRIMARY	t10	eq_ref	PRIMARY	PRIMARY	4	test.t1.a	1	100.00	
1	PRIMARY	t12	eq_ref	PRIMARY	PRIMARY	4	test.t10.a	1	100.00	Using index; FirstMatch(t10)
Warnings:
Note	1003	select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` semi join (`test`.`t10` join `test`.`t12`) where ((`test`.`t10`.`pk` = `test`.`t1`.`a`) and (`test`.`t12`.`pk` = `test`.`t10`.`a`))
subqueries within outer joins go into ON expr.
explain extended
select * from t1 left join (t2 A, t2 B) on ( A.a= t1.a and B.a in (select pk from t10));
id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	100.00	
1	PRIMARY	A	ALL	NULL	NULL	NULL	NULL	3	100.00	
1	PRIMARY	B	ALL	NULL	NULL	NULL	NULL	3	100.00	
1	PRIMARY	t10	eq_ref	PRIMARY	PRIMARY	4	test.B.a	1	100.00	Using index
Warnings:
Note	1003	select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`A`.`a` AS `a`,`test`.`A`.`b` AS `b`,`test`.`B`.`a` AS `a`,`test`.`B`.`b` AS `b` from `test`.`t1` left join ((`test`.`t10`) join `test`.`t2` `A` join `test`.`t2` `B`) on(((`test`.`A`.`a` = `test`.`t1`.`a`) and 1 and (`test`.`B`.`a` = `test`.`t10`.`pk`))) where 1
t2 should be wrapped into OJ-nest, so we have "t1 LJ (t2 J t10)"
explain extended
select * from t1 left join t2 on (t2.a= t1.a and t2.a in (select pk from t10));
id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	100.00	
1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	3	100.00	
1	PRIMARY	t10	eq_ref	PRIMARY	PRIMARY	4	test.t2.a	1	100.00	Using index
Warnings:
Note	1003	select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` left join ((`test`.`t10`) join `test`.`t2`) on(((`test`.`t2`.`a` = `test`.`t1`.`a`) and 1 and (`test`.`t2`.`a` = `test`.`t10`.`pk`))) where 1
we shouldn't flatten if we're going to get a join of > MAX_TABLES.
explain select * from 
t1 s00, t1 s01,  t1 s02, t1 s03, t1 s04,t1 s05,t1 s06,t1 s07,t1 s08,t1 s09,
t1 s10, t1 s11,  t1 s12, t1 s13, t1 s14,t1 s15,t1 s16,t1 s17,t1 s18,t1 s19,
t1 s20, t1 s21,  t1 s22, t1 s23, t1 s24,t1 s25,t1 s26,t1 s27,t1 s28,t1 s29,
t1 s30, t1 s31,  t1 s32, t1 s33, t1 s34,t1 s35,t1 s36,t1 s37,t1 s38,t1 s39,
t1 s40, t1 s41,  t1 s42, t1 s43, t1 s44,t1 s45,t1 s46,t1 s47,t1 s48,t1 s49
where
s00.a in (
select m00.a from
t1 m00, t1 m01,  t1 m02, t1 m03, t1 m04,t1 m05,t1 m06,t1 m07,t1 m08,t1 m09,
t1 m10, t1 m11,  t1 m12, t1 m13, t1 m14,t1 m15,t1 m16,t1 m17,t1 m18,t1 m19
);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	s00	ALL	NULL	NULL	NULL	NULL	3	Using where
1	PRIMARY	s01	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s02	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s03	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s04	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s05	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s06	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s07	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s08	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s09	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s10	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s11	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s12	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s13	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s14	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s15	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s16	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s17	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s18	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s19	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s20	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s21	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s22	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s23	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s24	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s25	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s26	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s27	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s28	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s29	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s30	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s31	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s32	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s33	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s34	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s35	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s36	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s37	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s38	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s39	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s40	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s41	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s42	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s43	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s44	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s45	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s46	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s47	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s48	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	s49	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m00	ALL	NULL	NULL	NULL	NULL	3	Using where
2	DEPENDENT SUBQUERY	m01	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m02	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m03	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m04	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m05	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m06	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m07	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m08	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m09	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m10	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m11	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m12	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m13	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m14	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m15	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m16	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m17	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m18	ALL	NULL	NULL	NULL	NULL	3	
2	DEPENDENT SUBQUERY	m19	ALL	NULL	NULL	NULL	NULL	3	Using where
select * from
t1 left join t2 on (t2.a= t1.a and t2.a in (select pk from t10)) 
where t1.a < 5;
a	b	a	b
0	0	0	0
1	1	1	1
2	2	2	2
prepare s1 from
' select * from
    t1 left join t2 on (t2.a= t1.a and t2.a in (select pk from t10))
  where t1.a < 5';
execute s1;
a	b	a	b
0	0	0	0
1	1	1	1
2	2	2	2
execute s1;
a	b	a	b
0	0	0	0
1	1	1	1
2	2	2	2
insert into t1 select (A.a + 10 * B.a),1 from t0 A, t0 B;
explain extended select * from t1 where a in (select pk from t10 where pk<3);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	103	100.00	Using where
1	PRIMARY	t10	eq_ref	PRIMARY	PRIMARY	4	test.t1.a	1	100.00	Using index
Warnings:
Note	1003	select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from (`test`.`t10`) join `test`.`t1` where ((`test`.`t10`.`pk` = `test`.`t1`.`a`) and (`test`.`t1`.`a` < 3))
drop table t0, t1;

--- New file ---
+++ mysql-test/r/subselect_sj2.result	07/05/22 23:53:40
drop table if exists t0, t1, t2, t3;
create table t0 (a int);
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
create table t1 (
a int, 
b int
);
insert into t1 values (1,1),(1,1),(2,2);
create table t2 (
a int,
b int,
key(b)
);
insert into t2 select a, a/2 from t0;
select * from t1;
a	b
1	1
1	1
2	2
select * from t2;
a	b
0	0
1	1
2	1
3	2
4	2
5	3
6	3
7	4
8	4
9	5
explain select * from t2 where b in (select a from t1);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	Start temporary
1	PRIMARY	t2	ref	b	b	5	test.t1.a	2	Using where; End temporary
select * from t2 where b in (select a from t1);
a	b
1	1
2	1
3	2
4	2
create table t3 (
a int, 
b int,
key(b),
pk1 char(200), pk2 char(200), pk3 char(200),
primary key(pk1, pk2, pk3)
) engine=innodb;
insert into t3 select a,a, a,a,a from t0;
explain select * from t3 where b in (select a from t1);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	Start temporary
1	PRIMARY	t3	ref	b	b	5	test.t1.a	1	Using where; End temporary
select * from t3 where b in (select a from t1);
a	b	pk1	pk2	pk3
1	1	1	1	1
2	2	2	2	2
set @save_max_heap_table_size= @@max_heap_table_size;
set max_heap_table_size=16384;
set @save_join_buffer_size = @@join_buffer_size;
set join_buffer_size= 8000;
drop table t3;
create table t3 (
a int, 
b int,
key(b),
pk1 char(200), pk2 char(200),
primary key(pk1, pk2)
) engine=innodb;
insert into t3 select 
A.a + 10*B.a, A.a + 10*B.a, A.a + 10*B.a, A.a + 10*B.a 
from t0 A, t0 B where B.a <5;
explain select * from t3 where b in (select a from t0);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t0	ALL	NULL	NULL	NULL	NULL	10	Start temporary
1	PRIMARY	t3	ref	b	b	5	test.t0.a	1	Using where; End temporary
select * from t3 where b in (select A.a+B.a from t0 A, t0 B where B.a<5);
a	b	pk1	pk2
0	0	0	0
1	1	1	1
2	2	2	2
3	3	3	3
4	4	4	4
5	5	5	5
6	6	6	6
7	7	7	7
8	8	8	8
9	9	9	9
10	10	10	10
11	11	11	11
12	12	12	12
13	13	13	13
set join_buffer_size= @save_join_buffer_size;
set max_heap_table_size= @save_max_heap_table_size;
explain select * from t1 where a in (select b from t2);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	
1	PRIMARY	t2	ref	b	b	5	test.t1.a	2	Using where; Using index; FirstMatch(t1)
select * from t1;
a	b
1	1
1	1
2	2
select * from t1 where a in (select b from t2);
a	b
1	1
1	1
2	2
drop table t1, t2, t3;
set @save_join_buffer_size = @@join_buffer_size;
set join_buffer_size= 8000;
create table t1 (a int, filler1 binary(200), filler2 binary(200));
insert into t1 select a, 'filler123456', 'filler123456' from t0;
insert into t1 select a+10, 'filler123456', 'filler123456' from t0;
create table t2 as select * from t1;
insert into t1 select a+20, 'filler123456', 'filler123456' from t0;
insert into t1 values (2, 'duplicate ok', 'duplicate ok');
insert into t1 values (18, 'duplicate ok', 'duplicate ok');
insert into t2 values (3, 'duplicate ok', 'duplicate ok');
insert into t2 values (19, 'duplicate ok', 'duplicate ok');
explain select 
a, mid(filler1, 1,10), length(filler1)=length(filler2) as Z 
from t1 ot where a in (select a from t2 it);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	it	ALL	NULL	NULL	NULL	NULL	22	Start temporary
1	PRIMARY	ot	ALL	NULL	NULL	NULL	NULL	32	Using where; End temporary
select 
a, mid(filler1, 1,10), length(filler1)=length(filler2) as Z 
from t1 ot where a in (select a from t2 it);
a	mid(filler1, 1,10)	Z
0	filler1234	1
1	filler1234	1
2	filler1234	1
3	filler1234	1
4	filler1234	1
5	filler1234	1
6	filler1234	1
7	filler1234	1
8	filler1234	1
9	filler1234	1
10	filler1234	1
11	filler1234	1
12	filler1234	1
13	filler1234	1
14	filler1234	1
15	filler1234	1
16	filler1234	1
17	filler1234	1
18	filler1234	1
19	filler1234	1
2	duplicate 	1
18	duplicate 	1
explain select 
a, mid(filler1, 1,10), length(filler1)=length(filler2) 
from t2 ot where a in (select a from t1 it);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	ot	ALL	NULL	NULL	NULL	NULL	22	Start temporary
1	PRIMARY	it	ALL	NULL	NULL	NULL	NULL	32	Using where; End temporary
select 
a, mid(filler1, 1,10), length(filler1)=length(filler2) 
from t2 ot where a in (select a from t1 it);
a	mid(filler1, 1,10)	length(filler1)=length(filler2)
0	filler1234	1
1	filler1234	1
2	filler1234	1
3	filler1234	1
4	filler1234	1
5	filler1234	1
6	filler1234	1
7	filler1234	1
8	filler1234	1
9	filler1234	1
10	filler1234	1
11	filler1234	1
12	filler1234	1
13	filler1234	1
14	filler1234	1
15	filler1234	1
16	filler1234	1
17	filler1234	1
18	filler1234	1
3	duplicate 	1
19	filler1234	1
19	duplicate 	1
insert into t1 select a+20, 'filler123456', 'filler123456' from t0;
insert into t1 select a+20, 'filler123456', 'filler123456' from t0;
explain select 
a, mid(filler1, 1,10), length(filler1)=length(filler2) as Z 
from t1 ot where a in (select a from t2 it);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	it	ALL	NULL	NULL	NULL	NULL	22	Start temporary
1	PRIMARY	ot	ALL	NULL	NULL	NULL	NULL	52	Using where; End temporary
select 
a, mid(filler1, 1,10), length(filler1)=length(filler2) as Z 
from t1 ot where a in (select a from t2 it);
a	mid(filler1, 1,10)	Z
0	filler1234	1
1	filler1234	1
2	filler1234	1
3	filler1234	1
4	filler1234	1
5	filler1234	1
6	filler1234	1
7	filler1234	1
8	filler1234	1
9	filler1234	1
10	filler1234	1
11	filler1234	1
12	filler1234	1
13	filler1234	1
14	filler1234	1
15	filler1234	1
16	filler1234	1
17	filler1234	1
18	filler1234	1
19	filler1234	1
2	duplicate 	1
18	duplicate 	1
explain select 
a, mid(filler1, 1,10), length(filler1)=length(filler2) 
from t2 ot where a in (select a from t1 it);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	ot	ALL	NULL	NULL	NULL	NULL	22	Start temporary
1	PRIMARY	it	ALL	NULL	NULL	NULL	NULL	52	Using where; End temporary
select 
a, mid(filler1, 1,10), length(filler1)=length(filler2) 
from t2 ot where a in (select a from t1 it);
a	mid(filler1, 1,10)	length(filler1)=length(filler2)
0	filler1234	1
1	filler1234	1
2	filler1234	1
3	filler1234	1
4	filler1234	1
5	filler1234	1
6	filler1234	1
7	filler1234	1
8	filler1234	1
9	filler1234	1
10	filler1234	1
11	filler1234	1
12	filler1234	1
13	filler1234	1
14	filler1234	1
15	filler1234	1
16	filler1234	1
17	filler1234	1
18	filler1234	1
3	duplicate 	1
19	filler1234	1
19	duplicate 	1
drop table t1, t2;
create table t1 (a int, b int, key(a));
create table t2 (a int, b int, key(a));
create table t3 (a int, b int, key(a));
insert into t1 select a,a from t0;
insert into t2 select a,a from t0;
insert into t3 select a,a from t0;
t2 and t3 must be use 'ref', not 'ALL':
explain select * 
from t0 where a in
(select t2.a+t3.a from t1 left join (t2 join t3) on t2.a=t1.a and t3.a=t1.a);
id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
1	PRIMARY	t0	ALL	NULL	NULL	NULL	NULL	10	Start temporary
1	PRIMARY	t1	index	NULL	a	5	NULL	10	Using index
1	PRIMARY	t2	ref	a	a	5	test.t1.a	1	Using index
1	PRIMARY	t3	ref	a	a	5	test.t1.a	1	Using where; Using index; End temporary
drop table t0, t1,t2,t3;

--- New file ---
+++ mysql-test/t/subselect_sj.test	07/05/22 23:53:40
#
# Nested Loops semi-join subquery evaluation tests
#
--disable_warnings
drop table if exists t0, t1, t2, t10, t11, t12;
--enable_warnings

#
# 1. Subqueries that are converted into semi-joins
#
create table t0 (a int);
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);

create table t1(a int, b int);
insert into t1 values (0,0),(1,1),(2,2);
create table t2 as select * from t1;

create table t11(a int, b int);

create table t10 (pk int, a int, primary key(pk));
insert into t10 select a,a from t0;
create table t12 like t10;
insert into t12 select * from t10;


--echo Flattened because of dependency, t10=func(t1)
explain select * from t1 where a in (select pk from t10);
select * from t1 where a in (select pk from t10);

--echo A confluent case of dependency
explain select * from t1 where a in (select a from t10 where pk=12);
select * from t1 where a in (select a from t10 where pk=12);

--echo An empty table inside
explain select * from t1 where a in (select a from t11);
select * from t1 where a in (select a from t11);

explain select * from t1 where a in (select pk from t10) and b in (select pk from t10);
select * from t1 where a in (select pk from t10) and b in (select pk from t10);

--echo flattening a nested subquery
explain select * from t1 where a in (select pk from t10 where t10.a in (select pk from t12));
select * from t1 where a in (select pk from t10 where t10.a in (select pk from t12));

--echo flattening subquery w/ several tables
explain extended select * from t1 where a in (select t10.pk from t10, t12 where t12.pk=t10.a);

--echo subqueries within outer joins go into ON expr.
explain extended
select * from t1 left join (t2 A, t2 B) on ( A.a= t1.a and B.a in (select pk from t10));

--echo t2 should be wrapped into OJ-nest, so we have "t1 LJ (t2 J t10)"
explain extended
select * from t1 left join t2 on (t2.a= t1.a and t2.a in (select pk from t10));

--echo we shouldn't flatten if we're going to get a join of > MAX_TABLES.
explain select * from 
  t1 s00, t1 s01,  t1 s02, t1 s03, t1 s04,t1 s05,t1 s06,t1 s07,t1 s08,t1 s09,
  t1 s10, t1 s11,  t1 s12, t1 s13, t1 s14,t1 s15,t1 s16,t1 s17,t1 s18,t1 s19,
  t1 s20, t1 s21,  t1 s22, t1 s23, t1 s24,t1 s25,t1 s26,t1 s27,t1 s28,t1 s29,
  t1 s30, t1 s31,  t1 s32, t1 s33, t1 s34,t1 s35,t1 s36,t1 s37,t1 s38,t1 s39,
  t1 s40, t1 s41,  t1 s42, t1 s43, t1 s44,t1 s45,t1 s46,t1 s47,t1 s48,t1 s49
where
  s00.a in (
  select m00.a from
    t1 m00, t1 m01,  t1 m02, t1 m03, t1 m04,t1 m05,t1 m06,t1 m07,t1 m08,t1 m09,
    t1 m10, t1 m11,  t1 m12, t1 m13, t1 m14,t1 m15,t1 m16,t1 m17,t1 m18,t1 m19
  );

select * from
  t1 left join t2 on (t2.a= t1.a and t2.a in (select pk from t10)) 
where t1.a < 5;

# 
# Prepared statements
#
prepare s1 from
  ' select * from
    t1 left join t2 on (t2.a= t1.a and t2.a in (select pk from t10))
  where t1.a < 5';
execute s1;
execute s1;

# Try I2O orders
insert into t1 select (A.a + 10 * B.a),1 from t0 A, t0 B;
explain extended select * from t1 where a in (select pk from t10 where pk<3);

drop table t0, t1;

--- New file ---
+++ mysql-test/t/subselect_sj2.test	07/05/22 23:53:40
#
# DuplicateElimination strategy test
#
--source include/have_innodb.inc
--disable_warnings
drop table if exists t0, t1, t2, t3;
--enable_warnings


create table t0 (a int);
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);

# First test simple cases: I20 order, no join buffering.

create table t1 (
  a int, 
  b int
);
insert into t1 values (1,1),(1,1),(2,2);

create table t2 (
  a int,
  b int,
  key(b)
);
insert into t2 select a, a/2 from t0;

select * from t1;
select * from t2;
explain select * from t2 where b in (select a from t1);
select * from t2 where b in (select a from t1);

# Try an InnoDB table with very long rowid
create table t3 (
   a int, 
   b int,
   key(b),
   pk1 char(200), pk2 char(200), pk3 char(200),
   primary key(pk1, pk2, pk3)
) engine=innodb;
insert into t3 select a,a, a,a,a from t0;

explain select * from t3 where b in (select a from t1);
select * from t3 where b in (select a from t1);

# Test overflow to MyISAM:
set @save_max_heap_table_size= @@max_heap_table_size;
set max_heap_table_size=16384;
set @save_join_buffer_size = @@join_buffer_size;
set join_buffer_size= 8000;

drop table t3;
create table t3 (
   a int, 
   b int,
   key(b),
   pk1 char(200), pk2 char(200),
   primary key(pk1, pk2)
) engine=innodb;
insert into t3 select 
  A.a + 10*B.a, A.a + 10*B.a, A.a + 10*B.a, A.a + 10*B.a 
from t0 A, t0 B where B.a <5;

explain select * from t3 where b in (select a from t0);
select * from t3 where b in (select A.a+B.a from t0 A, t0 B where B.a<5);

set join_buffer_size= @save_join_buffer_size;
set max_heap_table_size= @save_max_heap_table_size;

# O2I join orders, with shortcutting: 
explain select * from t1 where a in (select b from t2);
select * from t1;
select * from t1 where a in (select b from t2);

drop table t1, t2, t3;
# (no need for anything in range/index_merge/DS-MRR) 

#
# Test join buffering
#
set @save_join_buffer_size = @@join_buffer_size;
set join_buffer_size= 8000;

create table t1 (a int, filler1 binary(200), filler2 binary(200));
insert into t1 select a, 'filler123456', 'filler123456' from t0;
insert into t1 select a+10, 'filler123456', 'filler123456' from t0;

create table t2 as select * from t1;
insert into t1 select a+20, 'filler123456', 'filler123456' from t0;

insert into t1 values (2, 'duplicate ok', 'duplicate ok');
insert into t1 values (18, 'duplicate ok', 'duplicate ok');

insert into t2 values (3, 'duplicate ok', 'duplicate ok');
insert into t2 values (19, 'duplicate ok', 'duplicate ok');

explain select 
 a, mid(filler1, 1,10), length(filler1)=length(filler2) as Z 
from t1 ot where a in (select a from t2 it);
select 
 a, mid(filler1, 1,10), length(filler1)=length(filler2) as Z 
from t1 ot where a in (select a from t2 it);

explain select 
  a, mid(filler1, 1,10), length(filler1)=length(filler2) 
from t2 ot where a in (select a from t1 it);
select 
  a, mid(filler1, 1,10), length(filler1)=length(filler2) 
from t2 ot where a in (select a from t1 it);

# Now let the buffer overfill:
insert into t1 select a+20, 'filler123456', 'filler123456' from t0;
insert into t1 select a+20, 'filler123456', 'filler123456' from t0;

explain select 
 a, mid(filler1, 1,10), length(filler1)=length(filler2) as Z 
from t1 ot where a in (select a from t2 it);
select 
 a, mid(filler1, 1,10), length(filler1)=length(filler2) as Z 
from t1 ot where a in (select a from t2 it);

explain select 
  a, mid(filler1, 1,10), length(filler1)=length(filler2) 
from t2 ot where a in (select a from t1 it);
select 
  a, mid(filler1, 1,10), length(filler1)=length(filler2) 
from t2 ot where a in (select a from t1 it);

drop table t1, t2;

# Check ref access to tables inside the OJ nest inside the SJ nest
create table t1 (a int, b int, key(a));
create table t2 (a int, b int, key(a));
create table t3 (a int, b int, key(a));

insert into t1 select a,a from t0;
insert into t2 select a,a from t0;
insert into t3 select a,a from t0;

--echo t2 and t3 must be use 'ref', not 'ALL':
explain select * 
from t0 where a in
  (select t2.a+t3.a from t1 left join (t2 join t3) on t2.a=t1.a and t3.a=t1.a);

drop table t0, t1,t2,t3;




--- 1.3/sql/sql_array.h	2007-05-22 23:53:55 +04:00
+++ 1.4/sql/sql_array.h	2007-05-22 23:53:55 +04:00
@@ -53,6 +53,11 @@
     return array.elements;
   }
 
+  void clear()
+  {
+    array.elements= 0;
+  }
+
   ~Dynamic_array()
   {
     delete_dynamic(&array);
Thread
bk commit into 5.2 tree (sergefp:1.2456)Sergey Petrunia22 May