List:Commits« Previous MessageNext Message »
From:Ingo Struewing Date:August 13 2007 9:50am
Subject:bk commit into 5.1 tree (istruewing:1.2585) BUG#26379
View as plain text  
Below is the list of changes that have just been committed into a local
5.1 repository of istruewing. When istruewing 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-08-13 09:49:54+02:00, istruewing@stripped +11 -0
  Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
              corrupts a MERGE table
  
  Not to be pushed. This is a partial implementation of a new
  MERGE table open approach. For intermediate review only.
  It replaces/supersedes the former patch entirely.
  Changed/added test cases not included for easier review.
  
  Changes over the former patch:
  
  - Renamed "finalize open (with cannot use option)" into "attach/detach children".
  
  - On parent open the storage engine structures are allocated and initialized.
    They stay with the open table until its final close.
  
  - Table layout conversion (table2myisam) is done on first attach only. The result
    is stored in the handler object for reuse with later attaches.
  
  - Added detach functionality to close_thread_table().
  
  - The patch survives the whole test suite + bug25038 + bug26379 + bug26867 + bug27660,
    except of the few places where a MERGE table is opened with open_ltable() or
    reopen_table(). Test cases: delayed (open_ltable), merge (ALTER/open_ltable),
    bug25700 (reopen_table). As suggested these are to be fixed in a second patch.
    myisam passes after reverting/fixing test and result for bug8306.
    However the test for bug26377 revealed an unexpected problem: FLUSH t1 after
    LOCK t1, m1(t1). The child is closed, but the parent doesn't notice it.
    It won't re-attach its children as long as LOCK TABLES is in effect.
  
  General patch description:
  
  When opening a MERGE table in open_tables() we do now add the
  child tables to the list of tables to be opened by open_tables().
  The children are not opened at this stage.
  When the last child is opened, we remove the children from the
  list again and attach the children to the parent. This behaves 
  similar to the old open. However it does not open the MyISAM
  tables directly, but grabs them from the already open children.
  When closing a MERGE table in close_thread_table() we detach the
  children only. Closing of the children is done implicitly because
  they are in thd->open_tables.

  include/my_base.h@stripped, 2007-08-13 09:49:47+02:00, istruewing@stripped +7 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added HA_EXTRA_ATTACH_CHILDREN and HA_EXTRA_DETACH_CHILDREN.

  include/myisammrg.h@stripped, 2007-08-13 09:49:47+02:00, istruewing@stripped +8 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added element 'children_attached' to MYRG_INFO.
    Added declarations for myrg_parent_open(),
    myrg_attach_children() and myrg_detach_children()
    for the new MERGE table open approach.

  sql/sql_base.cc@stripped, 2007-08-13 09:49:47+02:00, istruewing@stripped +174 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Defined new functions attach_merge_children() and detach_merge_children()
    for the new MERGE table open approach.
    Added calls of the new functions to close_thread_table() and open_tables()
    respectively.
    Added code for removing the children list from the statement list in case of
    a repetition in open_tables().

  sql/table.h@stripped, 2007-08-13 09:49:47+02:00, istruewing@stripped +4 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added elements for MERGE tables to TABLE and TABLE_LIST.

  storage/myisam/ha_myisam.cc@stripped, 2007-08-13 09:49:47+02:00, istruewing@stripped
+23 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added new function myisam_engine_handle() to extract a MI_INFO
    pointer from a handler object.
    Added an unrelated comment to the function comment of table2myisam().

  storage/myisam/ha_myisam.h@stripped, 2007-08-13 09:49:47+02:00, istruewing@stripped +4
-0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Declared new function myisam_engine_handle() as friend of class ha_myisam.

  storage/myisammrg/ha_myisammrg.cc@stripped, 2007-08-13 09:49:47+02:00,
istruewing@stripped +362 -33
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added mem_root initialization to ha_myisammrg constructor.
    Added ha_myisammrg destructor to free memroot table2myisam resources.
    Added callback functions to support parent open and children attach
    of MERGE tables.
    Changed ha_myisammrg::open() to initialize storage engine structures and
    create a list of child tables only. child tables are not opened.
    Added ha_myisammrg::attach_children(), which does now the main part
    of MERGE open. Moved keyinfo, recinfo, and recs to class ha_myisammrg.
    Added ha_myisammrg::detach_children().
    Added DBUG_ASSERT(children-attached) to many handler methods.
    Added calls to ::attach_children() and ::detach_children() to ::extra() on
    HA_EXTRA_ATTACH_CHILDREN and HA_EXTRA_DETACH_CHILDREN respectively.

  storage/myisammrg/ha_myisammrg.h@stripped, 2007-08-13 09:49:48+02:00,
istruewing@stripped +12 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added elements to class ha_myisammrg to support the new
    open approach.
    Changed empty destructor definition to a declaration. Implemented in ha_myisammrg.cc.
    Added declaration for methods attach_children() and detach_children().
    Added definition for method table_ptr() to use with callback functions.

  storage/myisammrg/myrg_close.c@stripped, 2007-08-13 09:49:48+02:00, istruewing@stripped
+23 -3
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Closing MyISAM tables only if attached. This should never happen in the server.
    It could happen on panic though.

  storage/myisammrg/myrg_extra.c@stripped, 2007-08-13 09:49:48+02:00, istruewing@stripped
+4 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Some ::extra() functions and ::reset() can be called when children are detached.

  storage/myisammrg/myrg_open.c@stripped, 2007-08-13 09:49:48+02:00, istruewing@stripped
+292 -7
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Kept old myrg_open() for MERGE use independent from MySQL.
    Removed an always true condition in myrg_open().
    Added myrg_parent_open(), myrg_attach_children, and myrg_detach_children()
    for the new MERGE table open approach.

diff -Nrup a/include/my_base.h b/include/my_base.h
--- a/include/my_base.h	2007-07-19 17:01:07 +02:00
+++ b/include/my_base.h	2007-08-13 09:49:47 +02:00
@@ -185,7 +185,13 @@ enum ha_extra_function {
     Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be
     executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY.
   */
-  HA_EXTRA_INSERT_WITH_UPDATE
+  HA_EXTRA_INSERT_WITH_UPDATE,
+  /*
+    Orders MERGE handler to attach or detach its child tables. Used at
+    begin and end of a statement.
+  */
+  HA_EXTRA_ATTACH_CHILDREN,
+  HA_EXTRA_DETACH_CHILDREN
 };
 
 	/* The following is parameter to ha_panic() */
diff -Nrup a/include/myisammrg.h b/include/myisammrg.h
--- a/include/myisammrg.h	2007-05-10 11:59:24 +02:00
+++ b/include/myisammrg.h	2007-08-13 09:49:47 +02:00
@@ -69,6 +69,7 @@ typedef struct st_myrg_info
   uint	 merge_insert_method;
   uint	 tables,options,reclength,keys;
   my_bool cache_in_use;
+  my_bool children_attached;
   LIST	 open_list;
   QUEUE  by_key;
   ulong *rec_per_key_part;			/* for sql optimizing */
@@ -80,6 +81,13 @@ typedef struct st_myrg_info
 extern int myrg_close(MYRG_INFO *file);
 extern int myrg_delete(MYRG_INFO *file,const uchar *buff);
 extern MYRG_INFO *myrg_open(const char *name,int mode,int wait_if_locked);
+extern MYRG_INFO *myrg_parent_open(const char *parent_name,
+                                   int (*callback)(void*, const char*),
+                                   void *callback_param);
+extern int myrg_attach_children(MYRG_INFO *m_info, int handle_locking,
+                                MI_INFO *(*callback)(void*),
+                                void *callback_param);
+extern int myrg_detach_children(MYRG_INFO *m_info);
 extern int myrg_panic(enum ha_panic_function function);
 extern int myrg_rfirst(MYRG_INFO *file,uchar *buf,int inx);
 extern int myrg_rlast(MYRG_INFO *file,uchar *buf,int inx);
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc	2007-08-02 02:42:06 +02:00
+++ b/sql/sql_base.cc	2007-08-13 09:49:47 +02:00
@@ -1050,6 +1050,39 @@ bool close_cached_connection_tables(THD 
 }
 
 
+/**
+  @brief Detach MERGE children from the parent.
+
+  @note Detach must not touch the children in any way.
+    They may have been closed at ths point already.
+    All references to the children should be removed.
+
+  @param[in]    table           the TABLE object of the parent
+*/
+
+static void detach_merge_children(TABLE *table)
+{
+  TABLE_LIST *child_l;
+  DBUG_ENTER("detach_merge_children");
+  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+                      table->s->table_name.str, (long) table));
+  DBUG_PRINT("myrg", ("MERGE parent table, detach children"));
+
+  VOID(table->file->extra(HA_EXTRA_DETACH_CHILDREN));
+
+  /* Clear TABLE reference to force new assignment at next open.*/
+  for (child_l= table->child_l; ; child_l= child_l->next_global)
+  {
+    /* Do not DBUG_ASSERT(child_l->table); open_tables might be incomplete. */
+    child_l->table= NULL;
+    /* Break when this was the last child. */
+    if (&child_l->next_global == table->child_last_l)
+      break;
+  }
+  DBUG_VOID_RETURN;
+}
+
+
 /*
   Mark all tables in the list which were used by current substatement
   as free for reuse.
@@ -1265,8 +1298,20 @@ bool close_thread_table(THD *thd, TABLE 
   DBUG_ENTER("close_thread_table");
   DBUG_ASSERT(table->key_read == 0);
   DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
+  DBUG_PRINT("tcache", ("table: '%s' 0x%lx", table->s->table_name.str,
+                        (long) table));
 
   *table_ptr=table->next;
+  /*
+    When closing a MERGE parent table, detach its children first.
+    The children are in thd->open_tables and will be closed too.
+    In most cases they will be closed already at this point. They are
+    opened after the parent and thus stacked into thd->open_tables
+    before it. Hence detach must not touch the children in any way.
+  */
+  if (table->child_l)
+    detach_merge_children(table);
+
   if (table->needs_reopen_or_name_lock() ||
       thd->version != refresh_version || !table->db_stat)
   {
@@ -2495,6 +2540,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *
        table= (TABLE*) hash_next(&open_cache, (uchar*) key, key_length,
                                  &state))
   {
+    DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' 0x%lx", table->s->db.str,
+                          table->s->table_name.str, (long) table));
     /*
       Here we flush tables marked for flush.
       Normally, table->s->version contains the value of
@@ -2579,6 +2626,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   }
   if (table)
   {
+    DBUG_PRINT("tcache", ("unused table: '%s'.'%s' 0x%lx", table->s->db.str,
+                          table->s->table_name.str, (long) table));
     /* Unlink the table from "unused_tables" list. */
     if (table == unused_tables)
     {						// First unused
@@ -2660,7 +2709,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *
       VOID(pthread_mutex_unlock(&LOCK_open));
       DBUG_RETURN(0); // VIEW
     }
-    DBUG_PRINT("info", ("inserting table 0x%lx into the cache", (long) table));
+    DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache",
+                        table->s->db.str, table->s->table_name.str,
+                        (long) table));
     VOID(my_hash_insert(&open_cache,(uchar*) table));
   }
 
@@ -2996,6 +3047,8 @@ void close_old_data_files(THD *thd, TABL
       found=1;
       if (table->db_stat)
       {
+        if (table->child_l)
+          detach_merge_children(table);
 	if (morph_locks)
 	{
           /*
@@ -3462,6 +3515,97 @@ err:
 }
 
 
+/**
+  @brief Attach MERGE children to the parent.
+
+  @detail When a MERGE parent table has just been opened, insert the
+    TABLE_LIST chain from the MERGE handle into the table list used for
+    opening tables for this statement. This lets the children be opened
+    too.
+
+    When the last MERGE child has just been opened, let the handler
+    attach the MyISAM tables to the MERGE table. Remove MERGE TABLE_LIST
+    chain from the statement list so that it cannot be changed or freed.
+
+  @param[in]    tlist           the TABLE_LIST object just opened
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error
+*/
+
+static int attach_merge_children(TABLE_LIST *tlist)
+{
+  DBUG_ENTER("attach_merge_children");
+  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", tlist->table->s->db.str,
+                      tlist->table->s->table_name.str, (long) tlist->table));
+
+  if (tlist->table->child_l)
+  {
+    /* 'table' is a MERGE parent table. */
+    TABLE *parent= tlist->table;
+    TABLE_LIST *child_l;
+    DBUG_PRINT("myrg", ("MERGE parent opened, add children to list"));
+
+    /* Fix children.*/
+    for (child_l= parent->child_l; ; child_l= child_l->next_global)
+    {
+      DBUG_ASSERT(!child_l->table);
+      /* Set lock type. */
+      child_l->lock_type= tlist->lock_type;
+      /* Set parent reference. */
+      child_l->parent_l= tlist;
+      /* Break when this was the last child. */
+      if (&child_l->next_global == parent->child_last_l)
+        break;
+    }
+
+    /* Insert children into the table list. */
+    *parent->child_last_l= tlist->next_global;
+    tlist->next_global= parent->child_l;
+    /*
+      Do not fix the prev_global pointers. We will remove the
+      chain soon anyway.
+    */
+  }
+  else if (tlist->parent_l && (&tlist->next_global ==
+                               tlist->parent_l->table->child_last_l))
+  {
+    /* 'tlist' lists the last MERGE child, attach children to parent. */
+    TABLE *parent= tlist->parent_l->table;
+    int error;
+    DBUG_PRINT("myrg", ("last MERGE child opened, attach children"));
+
+    error= parent->file->extra(HA_EXTRA_ATTACH_CHILDREN);
+
+    /*
+      Remove children from the table list. Even in case of an error.
+      This should prevent tampering with them.
+    */
+    tlist->parent_l->next_global= *parent->child_last_l;
+    /*
+      Do not fix the last childs next_global pointer. It is needed for
+      stepping to the next table in the enclosing loop in open_tables().
+      Do not fix prev_global pointers. We did not set them.
+    */
+
+    if (error)
+    {
+      DBUG_PRINT("error", ("attaching MERGE children failed: %d", my_errno));
+      parent->file->print_error(error, MYF(0));
+      DBUG_RETURN(1);
+    }
+    /*
+      Note that we have the cildren in the thd->open_tables list
+      at this point.
+    */
+  }
+  else
+    DBUG_PRINT("myrg", ("neither MERGE parent nor last MERGE child opened"));
+  DBUG_RETURN(0);
+}
+
+
 /*
   Open all tables in list
 
@@ -3552,6 +3696,9 @@ int open_tables(THD *thd, TABLE_LIST **s
   */
   for (tables= *start; tables ;tables= tables->next_global)
   {
+    DBUG_PRINT("tcache", ("opening table: '%s'.'%s'  item: 0x%lx",
+                          tables->db, tables->table_name, (long) tables));
+
     safe_to_ignore_table= FALSE;
 
     if (tables->lock_type == TL_WRITE_DEFAULT)
@@ -3608,6 +3755,11 @@ int open_tables(THD *thd, TABLE_LIST **s
       else
         tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
     }
+    else
+      DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
+                            tables->table->s->db.str,
+                            tables->table->s->table_name.str,
+                            (long) tables->table));
 
     if (!tables->table)
     {
@@ -3657,6 +3809,18 @@ int open_tables(THD *thd, TABLE_LIST **s
         */
         if (query_tables_last_own)
           thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
+        /*
+          If in a MERGE table open, we need to remove the children list
+          from statement table list before restarting. Otherwise the list
+          will be inserted another time.
+        */
+        if (tables->parent_l)
+        {
+          TABLE_LIST *parent_l= tables->parent_l;
+          /* The parent table should be correctly open at this point. */
+          DBUG_ASSERT(parent_l->table);
+          parent_l->next_global= *parent_l->table->child_last_l;
+        }
         close_tables_for_reopen(thd, start);
 	goto restart;
       }
@@ -3705,6 +3869,15 @@ int open_tables(THD *thd, TABLE_LIST **s
     if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables)
       tables->table->reginfo.lock_type=tables->lock_type;
     tables->table->grant= tables->grant;
+
+    /* Attach MERGE children if not locked already. */
+    if ((tables->table->child_l || tables->parent_l) &&
+        !(thd->locked_tables || thd->prelocked_mode) &&
+        attach_merge_children(tables))
+    {
+      result= -1;
+      goto err;
+    }
 
 process_view_routines:
     /*
diff -Nrup a/sql/table.h b/sql/table.h
--- a/sql/table.h	2007-07-31 21:45:27 +02:00
+++ b/sql/table.h	2007-08-13 09:49:47 +02:00
@@ -453,6 +453,8 @@ struct st_table {
   struct st_table *open_next, **open_prev;	/* Link to open tables */
 #endif
   struct st_table *next, *prev;
+  TABLE_LIST      *child_l;             /* Set in MERGE parent tables */
+  TABLE_LIST      **child_last_l;       /* Set in MERGE parent tables */
 
   THD	*in_use;                        /* Which thread uses this */
   Field **field;			/* Pointer to fields */
@@ -972,6 +974,8 @@ struct TABLE_LIST
     (non-zero only for merged underlying tables of a view).
   */
   TABLE_LIST	*referencing_view;
+  /* Set in MERGE child tables. */
+  TABLE_LIST    *parent_l;
   /*
     Security  context (non-zero only for tables which belong
     to view with SQL SECURITY DEFINER)
diff -Nrup a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc
--- a/storage/myisam/ha_myisam.cc	2007-07-27 02:29:31 +02:00
+++ b/storage/myisam/ha_myisam.cc	2007-08-13 09:49:47 +02:00
@@ -119,6 +119,10 @@ static void mi_check_print_msg(MI_CHECK 
     definition for further use in mi_create or for a check for underlying
     table conformance in merge engine.
 
+    The caller needs to free *recinfo_out after use. Since *recinfo_out
+    and *keydef_out are allocated with a my_multi_malloc, *keydef_out
+    is freed automatically when *recinfo_out is freed.
+
   RETURN VALUE
     0  OK
     !0 error code
@@ -2118,3 +2122,22 @@ my_bool ha_myisam::register_query_cache_
   return TRUE;
 }
 #endif
+
+
+/**
+  @brief Extract the MyISAM table structure pointer from a handler object.
+
+  @param[in]    handler_handle  pointer to handler object
+
+  @return MyISAM table structure pointer
+    @retval     NULL            if handler has not a MyISAM table open
+*/
+
+MI_INFO *myisam_engine_handle(handler *handler_handle)
+{
+  DBUG_ENTER("myisam_engine_handle");
+  if (strcmp(handler_handle->table_type(), "MyISAM"))
+    DBUG_RETURN(NULL);
+  DBUG_RETURN(((ha_myisam*) handler_handle)->file);
+}
+
diff -Nrup a/storage/myisam/ha_myisam.h b/storage/myisam/ha_myisam.h
--- a/storage/myisam/ha_myisam.h	2007-07-27 02:29:32 +02:00
+++ b/storage/myisam/ha_myisam.h	2007-08-13 09:49:47 +02:00
@@ -33,6 +33,8 @@ extern ulong myisam_sort_buffer_size;
 extern TYPELIB myisam_recover_typelib;
 extern ulong myisam_recover_options;
 
+extern MI_INFO *myisam_engine_handle(handler *handler_handle);
+
 class ha_myisam: public handler
 {
   MI_INFO *file;
@@ -139,4 +141,6 @@ class ha_myisam: public handler
                                      *engine_callback,
                                      ulonglong *engine_data);
 #endif
+
+  friend MI_INFO *myisam_engine_handle(handler *handler_handle);
 };
diff -Nrup a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc
--- a/storage/myisammrg/ha_myisammrg.cc	2007-06-14 13:19:44 +02:00
+++ b/storage/myisammrg/ha_myisammrg.cc	2007-08-13 09:49:47 +02:00
@@ -22,6 +22,7 @@
 #include "mysql_priv.h"
 #include <mysql/plugin.h>
 #include <m_ctype.h>
+#include "../myisam/ha_myisam.h"
 #include "ha_myisammrg.h"
 #include "myrg_def.h"
 
@@ -38,10 +39,34 @@ static handler *myisammrg_create_handler
 }
 
 
+/**
+  @brief Constructor
+
+  @detail Initialize pointers that will reference allocated memory
+    or be checked for NULL/non-NULL.
+*/
+
 ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg)
-  :handler(hton, table_arg), file(0)
+  :handler(hton, table_arg), file(0), recinfo(0)
 {}
 
+
+/**
+  @brief Destructor
+
+  @detail Free allocated memory.
+*/
+
+ha_myisammrg::~ha_myisammrg(void)
+{
+  /*
+    Both recinfo and keyinfo are allocated by my_multi_malloc(),
+    thus only recinfo must be freed.
+  */
+  my_free((uchar*) recinfo, MYF(MY_ALLOW_ZERO_PTR));
+}
+
+
 static const char *ha_myisammrg_exts[] = {
   ".MRG",
   NullS
@@ -89,25 +114,238 @@ const char *ha_myisammrg::index_type(uin
 }
 
 
-int ha_myisammrg::open(const char *name, int mode, uint test_if_locked)
+/**
+  @brief Callback function for open of a MERGE parent table.
+
+  @detail This function adds a TABLE_LIST object for a MERGE child table
+    to a list of tables of the parent TABLE object. It is called for
+    each child table.
+
+    The list of child TABLE_LIST objects is kept in the TABLE object of
+    the parent for the whole life time of the MERGE table. It is
+    inserted in the statement list behind the MERGE parent TABLE_LIST
+    object when the MERGE table is opened. It is removed from the
+    statement list after the last child is opened.
+
+    All memeory used for the child TABLE_LIST objects and the strings
+    referred by it are taken from the parent TABLE::mem_root. Thus they
+    are all freed implicitly at the final close of the table.
+
+    TABLE::child_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global
+    #                 #               ^          #               ^
+    #                 #               |          #               |
+    #                 #               +--------- TABLE_LIST::prev_global
+    #                 #                                          |
+    #           |<--- TABLE_LIST::prev_global                    |
+    #                                                            |
+    TABLE::child_last_l -----------------------------------------+
+
+  @param[in]    callback_param  data pointer as given to myrg_parent_open()
+  @param[in]    filename        file name of MyISAM table
+                                without extension.
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error
+*/
+
+static int myisammrg_parent_open_callback(void *callback_param,
+                                          const char *filename)
+{
+  ha_myisammrg  *ha_myrg;
+  TABLE         *parent;
+  TABLE_LIST    *child_l;
+  const char    *db;
+  const char    *table_name;
+  uint          dirlen;
+  char          dir_path[FN_REFLEN];
+  DBUG_ENTER("myisammrg_parent_open_callback");
+
+  /* Extract child table name and database name from filename. */
+  dirlen= dirname_length(filename);
+  if (dirlen >= FN_REFLEN)
+  {
+    DBUG_PRINT("error", ("name too long: '%.64s'", filename));
+    my_errno= ENAMETOOLONG;
+    DBUG_RETURN(1);
+  }
+  table_name= filename + dirlen;
+  dirlen--; /* Strip off trailing '/'. */
+  memcpy(dir_path, filename, dirlen);
+  dir_path[dirlen]= '\0';
+  db= base_name(dir_path);
+  dirlen-= db - dir_path; /* This is now the length of 'db'. */
+  DBUG_PRINT("myrg", ("open: '%s'.'%s'", db, table_name));
+
+  ha_myrg= (ha_myisammrg*) callback_param;
+  parent= ha_myrg->table_ptr();
+
+  /* Get a TABLE_LIST object. */
+  if (!(child_l= (TABLE_LIST*) alloc_root(&parent->mem_root,
+                                          sizeof(TABLE_LIST))))
+  {
+    DBUG_PRINT("error", ("my_malloc error: %d", my_errno));
+    DBUG_RETURN(1);
+  }
+  bzero((char*) child_l, sizeof(TABLE_LIST));
+
+  /* Set database (schema) name. */
+  child_l->db_length= dirlen;
+  child_l->db= strmake_root(&parent->mem_root, db, dirlen);
+  /* Set table name. */
+  child_l->table_name_length= strlen(table_name);
+  child_l->table_name= strmake_root(&parent->mem_root, table_name,
+                                    child_l->table_name_length);
+  /* Convert to lowercase if required. */
+  if (lower_case_table_names && child_l->table_name_length)
+    child_l->table_name_length= my_casedn_str(files_charset_info,
+                                              child_l->table_name);
+  /* Set alias. */
+  child_l->alias= child_l->table_name;
+
+  /* Link TABLE_LIST object into the parent list. */
+  if (!parent->child_last_l)
+  {
+    /* Initialize parent->child_last_l when handling first child. */
+    parent->child_last_l= &parent->child_l;
+  }
+  *parent->child_last_l= child_l;
+  child_l->prev_global= parent->child_last_l;
+  parent->child_last_l= &child_l->next_global;
+
+  DBUG_RETURN(0);
+}
+
+
+/**
+  @brief Callback function for attaching a MERGE child table.
+
+  @detail This function retrieves the MyISAM table handle from the
+    next child table. It is called for each child table.
+
+  @param[in]    callback_param      data pointer as given to
+                                    myrg_attach_children()
+
+  @return pointer to open MyISAM table structure
+    @retval     NULL on end of child tables, my_errno == 0
+    @retval     NULL on error, my_errno != 0
+*/
+
+static MI_INFO *myisammrg_attach_children_callback(void *callback_param)
+{
+  ha_myisammrg  *ha_myrg;
+  TABLE         *parent;
+  TABLE_LIST    *child_l;
+  MI_INFO       *myisam;
+  DBUG_ENTER("myisammrg_attach_children_callback");
+
+  my_errno= 0;
+  ha_myrg= (ha_myisammrg*) callback_param;
+  parent= ha_myrg->table_ptr();
+
+  /* Get child list item. */
+  child_l= ha_myrg->next_child_attach;
+  if (!child_l)
+  {
+    DBUG_PRINT("myrg", ("No more children to attach"));
+    DBUG_RETURN(NULL);
+  }
+  /*
+    Prepare for next child. Used as child_l in next call to this function.
+    We cannot rely on a NULL-terminated chain.
+  */
+  if (&child_l->next_global == parent->child_last_l)
+  {
+    DBUG_PRINT("myrg", ("attaching last child"));
+    ha_myrg->next_child_attach= NULL;
+  }
+  else
+    ha_myrg->next_child_attach= child_l->next_global;
+
+  /* Extract the MyISAM table structure pointer from the handler object. */
+  if (!(myisam= myisam_engine_handle(child_l->table->file)))
+  {
+    DBUG_PRINT("error", ("no MyISAM handle for table: '%s' 0x%lx",
+                         child_l->table->s->table_name.str,
+                         (long) child_l->table));
+    my_errno= HA_ERR_WRONG_MRG_TABLE_DEF;
+  }
+  DBUG_PRINT("myrg", ("MyISAM handle: 0x%lx  my_errno: %d",
+                      (long) myisam, my_errno));
+  DBUG_RETURN(my_errno ? NULL : myisam);
+}
+
+
+/**
+  @brief Open a MERGE parent table, not its children.
+
+  @detail This function initializes the MERGE storage engine structures
+    and adds a children list of TABLE_LIST to the parent TABLE.
+
+  @param[in]    name            MERGE table path name
+  @param[in]    mode            read/write mode, unused
+  @param[in]    test_if_locked  open flags
+
+  @return pointer to opened MyISAM table structure
+    @retval     NULL on end of child tables, my_errno == 0
+    @retval     NULL on error, my_errno != 0
+*/
+
+int ha_myisammrg::open(const char *name, int mode __attribute__((unused)),
+                       uint test_if_locked)
+{
+  DBUG_ENTER("ha_myisammrg::open");
+  DBUG_PRINT("myrg", ("name: '%s'  table: 0x%lx", name, (long) table));
+
+  /* Save for later use. */
+  this->test_if_locked= test_if_locked;
+
+  /* retrieve children table list. */
+  my_errno= 0;
+  if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this)))
+  {
+    DBUG_PRINT("error", ("my_errno %d", my_errno));
+    DBUG_RETURN(my_errno ? my_errno : -1);
+  }
+  DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx", (long) file));
+  DBUG_RETURN(0);
+}
+
+
+/**
+  @brief Attach children to a MERGE table.
+
+  @detail Let the storage engine attach its children through a callback
+    function. Check table definitions for consistency.
+
+  @note The required conversion table2myisam is done on the first
+    attach only. Its result (recs, recinfo, keyinfo) is stored in
+    ha_myisammrg for reuse with later attaches.
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error, my_errno gives reason
+*/
+
+int ha_myisammrg::attach_children(void)
 {
-  MI_KEYDEF *keyinfo;
-  MI_COLUMNDEF *recinfo;
   MYRG_TABLE *u_table;
-  uint recs;
   uint keys= table->s->keys;
   int error;
-  char name_buff[FN_REFLEN];
-
-  DBUG_PRINT("info", ("ha_myisammrg::open"));
-  if (!(file=myrg_open(fn_format(name_buff,name,"","",
-                                 MY_UNPACK_FILENAME|MY_APPEND_EXT),
-                       mode, test_if_locked)))
+  DBUG_ENTER("ha_myisammrg::attach_children");
+  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+                      table->s->table_name.str, (long) table));
+  DBUG_ASSERT(!this->file->children_attached);
+
+  next_child_attach= table->child_l;
+  my_errno= 0;
+  if (myrg_attach_children(this->file, this->test_if_locked,
+                           myisammrg_attach_children_callback, this))
   {
-    DBUG_PRINT("info", ("ha_myisammrg::open exit %d", my_errno));
-    return (my_errno ? my_errno : -1);
+    DBUG_PRINT("error", ("my_errno %d", my_errno));
+    DBUG_RETURN(my_errno ? my_errno : -1);
   }
-  DBUG_PRINT("info", ("ha_myisammrg::open myrg_extrafunc..."));
+  DBUG_PRINT("myrg", ("calling myrg_extrafunc"));
   myrg_extrafunc(file, query_cache_invalidate_by_MyISAM_filename_ref);
   if (!(test_if_locked == HA_OPEN_WAIT_IF_LOCKED ||
 	test_if_locked == HA_OPEN_ABORT_IF_LOCKED))
@@ -125,13 +363,21 @@ int ha_myisammrg::open(const char *name,
     error= HA_ERR_WRONG_MRG_TABLE_DEF;
     goto err;
   }
-  if ((error= table2myisam(table, &keyinfo, &recinfo, &recs)))
+  if (!this->recinfo)
   {
-    /* purecov: begin inspected */
-    DBUG_PRINT("error", ("Failed to convert TABLE object to MyISAM "
-                         "key and column definition"));
-    goto err;
-    /* purecov: end */
+    /*
+      Both recinfo and keyinfo are allocated by my_multi_malloc(), thus
+      only recinfo must be freed.
+    */
+    if ((error= table2myisam(table, &this->keyinfo,
+                             &this->recinfo, &this->recs)))
+    {
+      /* purecov: begin inspected */
+      DBUG_PRINT("error", ("failed to convert TABLE object to MyISAM "
+                           "key and column definition"));
+      goto err;
+      /* purecov: end */
+    }
   }
   for (u_table= file->open_tables; u_table < file->end_table; u_table++)
   {
@@ -140,41 +386,81 @@ int ha_myisammrg::open(const char *name,
                          u_table->table->s->base.keys,
                          u_table->table->s->base.fields, false))
     {
+      DBUG_PRINT("error", ("table definition mismatch: '%s'",
+                           u_table->table->filename));
       error= HA_ERR_WRONG_MRG_TABLE_DEF;
-      if (test_if_locked & HA_OPEN_FOR_REPAIR)
-        myrg_print_wrong_table(u_table->table->filename);
-      else
-      {
-        my_free((uchar*) recinfo, MYF(0));
+      if (!(this->test_if_locked & HA_OPEN_FOR_REPAIR))
         goto err;
-      }
+      myrg_print_wrong_table(u_table->table->filename);
     }
   }
-  my_free((uchar*) recinfo, MYF(0));
   if (error == HA_ERR_WRONG_MRG_TABLE_DEF)
     goto err;
 #if !defined(BIG_TABLES) || SIZEOF_OFF_T == 4
   /* Merge table has more than 2G rows */
   if (table->s->crashed)
   {
+    DBUG_PRINT("error", ("MERGE table marked crashed"));
     error= HA_ERR_WRONG_MRG_TABLE_DEF;
     goto err;
   }
 #endif
-  return (0);
+  DBUG_RETURN(0);
+
 err:
-  myrg_close(file);
-  file=0;
-  return (my_errno= error);
+  myrg_detach_children(file);
+  DBUG_RETURN(my_errno= error);
 }
 
+
+/**
+  @brief Detach all children from a MERGE table.
+
+  @note Detach must not touch the children in any way.
+    They may have been closed at ths point already.
+    All references to the children should be removed.
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error, my_errno gives reason
+*/
+
+int ha_myisammrg::detach_children(void)
+{
+  DBUG_ENTER("ha_myisammrg::detach_children");
+  /* Do not test children_attached. After a failed attach it may be FALSE. */
+  if (myrg_detach_children(this->file))
+  {
+    DBUG_PRINT("error", ("my_errno %d", my_errno));
+    DBUG_RETURN(my_errno ? my_errno : -1);
+  }
+  DBUG_RETURN(0);
+}
+
+
+/**
+  @brief Close a MERGE parent table, not its children.
+
+  @note The children are expected to be closed separately by the caller.
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error, my_errno gives reason
+*/
+
 int ha_myisammrg::close(void)
 {
-  return myrg_close(file);
+  int rc;
+  DBUG_ENTER("ha_myisammrg::close");
+  DBUG_ASSERT(!this->file->children_attached);
+  rc= myrg_close(file);
+  file= 0;
+  DBUG_RETURN(rc);
 }
 
 int ha_myisammrg::write_row(uchar * buf)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_write_count);
 
   if (file->merge_insert_method == MERGE_INSERT_DISABLED || !file->tables)
@@ -193,6 +479,7 @@ int ha_myisammrg::write_row(uchar * buf)
 
 int ha_myisammrg::update_row(const uchar * old_data, uchar * new_data)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_update_count);
   if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE)
     table->timestamp_field->set_time();
@@ -201,6 +488,7 @@ int ha_myisammrg::update_row(const uchar
 
 int ha_myisammrg::delete_row(const uchar * buf)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_delete_count);
   return myrg_delete(file,buf);
 }
@@ -209,6 +497,7 @@ int ha_myisammrg::index_read(uchar * buf
                              key_part_map keypart_map,
                              enum ha_rkey_function find_flag)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_key_count);
   int error=myrg_rkey(file,buf,active_index, key, keypart_map, find_flag);
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -219,6 +508,7 @@ int ha_myisammrg::index_read_idx(uchar *
 				 key_part_map keypart_map,
                                  enum ha_rkey_function find_flag)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_key_count);
   int error=myrg_rkey(file,buf,index, key, keypart_map, find_flag);
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -228,6 +518,7 @@ int ha_myisammrg::index_read_idx(uchar *
 int ha_myisammrg::index_read_last(uchar * buf, const uchar * key,
                                   key_part_map keypart_map)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_key_count);
   int error=myrg_rkey(file,buf,active_index, key, keypart_map,
 		      HA_READ_PREFIX_LAST);
@@ -237,6 +528,7 @@ int ha_myisammrg::index_read_last(uchar 
 
 int ha_myisammrg::index_next(uchar * buf)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_next_count);
   int error=myrg_rnext(file,buf,active_index);
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -245,6 +537,7 @@ int ha_myisammrg::index_next(uchar * buf
 
 int ha_myisammrg::index_prev(uchar * buf)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_prev_count);
   int error=myrg_rprev(file,buf, active_index);
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -253,6 +546,7 @@ int ha_myisammrg::index_prev(uchar * buf
 
 int ha_myisammrg::index_first(uchar * buf)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_first_count);
   int error=myrg_rfirst(file, buf, active_index);
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -261,6 +555,7 @@ int ha_myisammrg::index_first(uchar * bu
 
 int ha_myisammrg::index_last(uchar * buf)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_last_count);
   int error=myrg_rlast(file, buf, active_index);
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -271,6 +566,7 @@ int ha_myisammrg::index_next_same(uchar 
                                   const uchar *key __attribute__((unused)),
                                   uint length __attribute__((unused)))
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_next_count);
   int error=myrg_rnext_same(file,buf);
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -280,12 +576,14 @@ int ha_myisammrg::index_next_same(uchar 
 
 int ha_myisammrg::rnd_init(bool scan)
 {
+  DBUG_ASSERT(this->file->children_attached);
   return myrg_reset(file);
 }
 
 
 int ha_myisammrg::rnd_next(uchar *buf)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_rnd_next_count);
   int error=myrg_rrnd(file, buf, HA_OFFSET_ERROR);
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -295,6 +593,7 @@ int ha_myisammrg::rnd_next(uchar *buf)
 
 int ha_myisammrg::rnd_pos(uchar * buf, uchar *pos)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ha_statistic_increment(&SSV::ha_read_rnd_count);
   int error=myrg_rrnd(file, buf, my_get_ptr(pos,ref_length));
   table->status=error ? STATUS_NOT_FOUND: 0;
@@ -303,6 +602,7 @@ int ha_myisammrg::rnd_pos(uchar * buf, u
 
 void ha_myisammrg::position(const uchar *record)
 {
+  DBUG_ASSERT(this->file->children_attached);
   ulonglong row_position= myrg_position(file);
   my_store_ptr(ref, ref_length, (my_off_t) row_position);
 }
@@ -311,6 +611,7 @@ void ha_myisammrg::position(const uchar 
 ha_rows ha_myisammrg::records_in_range(uint inx, key_range *min_key,
                                        key_range *max_key)
 {
+  DBUG_ASSERT(this->file->children_attached);
   return (ha_rows) myrg_records_in_range(file, (int) inx, min_key, max_key);
 }
 
@@ -318,6 +619,7 @@ ha_rows ha_myisammrg::records_in_range(u
 int ha_myisammrg::info(uint flag)
 {
   MYMERGE_INFO mrg_info;
+  DBUG_ASSERT(this->file->children_attached);
   (void) myrg_status(file,&mrg_info,flag);
   /*
     The following fails if one has not compiled MySQL with -DBIG_TABLES
@@ -387,6 +689,23 @@ int ha_myisammrg::info(uint flag)
 
 int ha_myisammrg::extra(enum ha_extra_function operation)
 {
+  if (operation == HA_EXTRA_ATTACH_CHILDREN)
+  {
+    int rc= attach_children();
+    if (!rc)
+      (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL
+    return(rc);
+  }
+  else if (operation == HA_EXTRA_DETACH_CHILDREN)
+  {
+    /*
+      Note that detach must not touch the children in any way.
+      They may have been closed at ths point already.
+    */
+    int rc= detach_children();
+    return(rc);
+  }
+
   /* As this is just a mapping, we don't have to force the underlying
      tables to be closed */
   if (operation == HA_EXTRA_FORCE_REOPEN ||
@@ -404,6 +723,7 @@ int ha_myisammrg::reset(void)
 
 int ha_myisammrg::extra_opt(enum ha_extra_function operation, ulong cache_size)
 {
+  DBUG_ASSERT(this->file->children_attached);
   if ((specialflag & SPECIAL_SAFE_MODE) && operation == HA_EXTRA_WRITE_CACHE)
     return 0;
   return myrg_extra(file, operation, (void*) &cache_size);
@@ -411,12 +731,17 @@ int ha_myisammrg::extra_opt(enum ha_extr
 
 int ha_myisammrg::external_lock(THD *thd, int lock_type)
 {
+  /* Unlocking can be tried with detached children, e.g close_old_data_files. */
+  DBUG_ASSERT(this->file->children_attached || (lock_type == F_UNLCK));
+  if (!file->children_attached)
+    return 0;
   return myrg_lock_database(file,lock_type);
 }
 
 uint ha_myisammrg::lock_count(void) const
 {
-  return file->tables;
+  /* This can be called with detached children, e.g close_old_data_files. */
+  return file->children_attached ? file->tables : 0;
 }
 
 
@@ -425,6 +750,10 @@ THR_LOCK_DATA **ha_myisammrg::store_lock
 					 enum thr_lock_type lock_type)
 {
   MYRG_TABLE *open_table;
+
+  /* This can be called with detached children, e.g close_old_data_files. */
+  if (!file->children_attached)
+    return to;
 
   for (open_table=file->open_tables ;
        open_table != file->end_table ;
diff -Nrup a/storage/myisammrg/ha_myisammrg.h b/storage/myisammrg/ha_myisammrg.h
--- a/storage/myisammrg/ha_myisammrg.h	2007-06-18 12:09:22 +02:00
+++ b/storage/myisammrg/ha_myisammrg.h	2007-08-13 09:49:48 +02:00
@@ -27,8 +27,16 @@ class ha_myisammrg: public handler
   MYRG_INFO *file;
 
  public:
+  uint          test_if_locked;         /* flags from ::open() */
+  TABLE_LIST    *next_child_attach;     /* next child to attach */
+
+  /* For checking table definitions. */
+  MI_COLUMNDEF  *recinfo;
+  MI_KEYDEF     *keyinfo;
+  uint          recs;
+
   ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg);
-  ~ha_myisammrg() {}
+  ~ha_myisammrg();
   const char *table_type() const { return "MRG_MyISAM"; }
   const char **bas_ext() const;
   const char *index_type(uint key_number);
@@ -53,6 +61,8 @@ class ha_myisammrg: public handler
   { return ulonglong2double(stats.data_file_length) / IO_SIZE + file->tables; }
 
   int open(const char *name, int mode, uint test_if_locked);
+  int attach_children(void);
+  int detach_children(void);
   int close(void);
   int write_row(uchar * buf);
   int update_row(const uchar * old_data, uchar * new_data);
@@ -84,6 +94,7 @@ class ha_myisammrg: public handler
   void update_create_info(HA_CREATE_INFO *create_info);
   void append_create_info(String *packet);
   MYRG_INFO *myrg_info() { return file; }
+  TABLE *table_ptr() { return table; }
   bool check_if_incompatible_data(HA_CREATE_INFO *info, uint table_changes);
   int check(THD* thd, HA_CHECK_OPT* check_opt);
 };
diff -Nrup a/storage/myisammrg/myrg_close.c b/storage/myisammrg/myrg_close.c
--- a/storage/myisammrg/myrg_close.c	2007-05-10 11:59:33 +02:00
+++ b/storage/myisammrg/myrg_close.c	2007-08-13 09:49:48 +02:00
@@ -23,9 +23,29 @@ int myrg_close(MYRG_INFO *info)
   MYRG_TABLE *file;
   DBUG_ENTER("myrg_close");
 
-  for (file=info->open_tables ; file != info->end_table ; file++)
-    if ((new_error=mi_close(file->table)))
-      error=new_error;
+  /*
+    Assume that info->children_attached means that this is called from
+    direct use of MERGE, not from a MySQL server. In this case the
+    children must be closed and info->rec_per_key_part is part of the
+    'info' multi_alloc.
+    If info->children_attached is false, this is called from a MySQL
+    server. Children are closed independently but info->rec_per_key_part
+    must be freed.
+    Just in case of a server panic (myrg_panic()) info->children_attached
+    might be true. We would close the children though they should be
+    closed independently and info->rec_per_key_part is not freed.
+    This should be acceptable for a panic.
+  */
+  if (info->children_attached)
+  {
+    for (file=info->open_tables ; file != info->end_table ; file++)
+      if ((new_error=mi_close(file->table)))
+        error=new_error;
+      else
+        file->table= NULL;
+  }
+  else
+    my_free((uchar*) info->rec_per_key_part, MYF(MY_ALLOW_ZERO_PTR));
   delete_queue(&info->by_key);
   pthread_mutex_lock(&THR_LOCK_open);
   myrg_open_list=list_delete(myrg_open_list,&info->open_list);
diff -Nrup a/storage/myisammrg/myrg_extra.c b/storage/myisammrg/myrg_extra.c
--- a/storage/myisammrg/myrg_extra.c	2007-03-28 20:05:08 +02:00
+++ b/storage/myisammrg/myrg_extra.c	2007-08-13 09:49:48 +02:00
@@ -29,6 +29,8 @@ int myrg_extra(MYRG_INFO *info,enum ha_e
   DBUG_ENTER("myrg_extra");
   DBUG_PRINT("info",("function: %lu", (ulong) function));
 
+  if (!info->children_attached)
+    DBUG_RETURN(1);
   if (function == HA_EXTRA_CACHE)
   {
     info->cache_in_use=1;
@@ -73,6 +75,8 @@ int myrg_reset(MYRG_INFO *info)
   MYRG_TABLE *file;
   DBUG_ENTER("myrg_reset");
 
+  if (!info->children_attached)
+    DBUG_RETURN(1);
   info->cache_in_use=0;
   info->current_table=0;
   info->last_used_table= info->open_tables;
diff -Nrup a/storage/myisammrg/myrg_open.c b/storage/myisammrg/myrg_open.c
--- a/storage/myisammrg/myrg_open.c	2007-06-07 10:39:11 +02:00
+++ b/storage/myisammrg/myrg_open.c	2007-08-13 09:49:48 +02:00
@@ -107,13 +107,11 @@ MYRG_INFO *myrg_open(const char *name, i
                                            key_parts*sizeof(long),
                                            MYF(MY_WME|MY_ZEROFILL))))
         goto err;
-      if (files)
-      {
-        m_info->open_tables=(MYRG_TABLE *) (m_info+1);
-        m_info->rec_per_key_part=(ulong *) (m_info->open_tables+files);
-        m_info->tables= files;
-        files= 0;
-      }
+      DBUG_ASSERT(files);
+      m_info->open_tables=(MYRG_TABLE *) (m_info+1);
+      m_info->rec_per_key_part=(ulong *) (m_info->open_tables+files);
+      m_info->tables= files;
+      files= 0;
       m_info->reclength=isam->s->base.reclength;
       min_keys= isam->s->base.keys;
       errpos=3;
@@ -163,6 +161,7 @@ MYRG_INFO *myrg_open(const char *name, i
   /* this works ok if the table list is empty */
   m_info->end_table=m_info->open_tables+files;
   m_info->last_used_table=m_info->open_tables;
+  m_info->children_attached= TRUE;
 
   VOID(my_close(fd,MYF(0)));
   end_io_cache(&file);
@@ -189,3 +188,289 @@ err:
   my_errno=save_errno;
   DBUG_RETURN (NULL);
 }
+
+
+/**
+  @brief Open parent table of a MyISAM MERGE table.
+
+  @detail Open MERGE meta file to get the table name paths for the child
+    tables. Count the children. Allocate and initialize MYRG_INFO
+    structure. Call a callback function for each child table.
+
+  @param[in]    parent_name     merge table name path as "database/table"
+  @param[in]    callback        function to call for each child table
+  @param[in]    callback_param  data pointer to give to the callback
+
+  @return MYRG_INFO pointer
+    @retval     != NULL         OK
+    @retval     NULL            Error
+*/
+
+MYRG_INFO *myrg_parent_open(const char *parent_name,
+                            int (*callback)(void*, const char*),
+                            void *callback_param)
+{
+  MYRG_INFO *m_info;
+  int       rc;
+  int       errpos;
+  int       save_errno;
+  int       insert_method;
+  uint      length;
+  uint      dir_length;
+  uint      child_count;
+  size_t    name_buff_length;
+  File      fd;
+  IO_CACHE  file_cache;
+  char      parent_name_buff[FN_REFLEN * 2];
+  char      child_name_buff[FN_REFLEN];
+  DBUG_ENTER("myrg_parent_open");
+
+  rc= 1;
+  errpos= 0;
+  bzero((char*) &file_cache, sizeof(file_cache));
+
+  /* Open MERGE meta file. */
+  if ((fd= my_open(fn_format(parent_name_buff, parent_name, "", MYRG_NAME_EXT,
+                             MY_UNPACK_FILENAME|MY_APPEND_EXT),
+                   O_RDONLY | O_SHARE, MYF(0))) < 0)
+    goto err;
+  errpos= 1;
+
+  if (init_io_cache(&file_cache, fd, 4 * IO_SIZE, READ_CACHE, 0, 0,
+                    MYF(MY_WME | MY_NABP)))
+    goto err;
+  errpos= 2;
+
+  /* Count children. Determine insert method. */
+  child_count= 0;
+  insert_method= 0;
+  while ((length= my_b_gets(&file_cache, child_name_buff, FN_REFLEN - 1)))
+  {
+    /* Remove line terminator. */
+    if (child_name_buff[length - 1] == '\n')
+      child_name_buff[--length]= '\0';
+
+    /* Skip empty lines. */
+    if (!child_name_buff[0])
+      continue;
+
+    /* Skip comments, but evaluate insert method. */
+    if (child_name_buff[0] == '#')
+    {
+      if (!strncmp(child_name_buff + 1, "INSERT_METHOD=", 14))
+      {
+        /* Compare buffer with global methods list: merge_insert_method. */
+        insert_method= find_type(child_name_buff + 15,
+                                 &merge_insert_method, 2);
+      }
+      continue;
+    }
+
+    /* Count the child. */
+    child_count++;
+  }
+
+  /* Allocate MERGE parent table structure. */
+  if (!(m_info= (MYRG_INFO*) my_malloc(sizeof(MYRG_INFO) +
+                                       child_count * sizeof(MYRG_TABLE),
+                                       MYF(MY_WME | MY_ZEROFILL))))
+    goto err;
+  errpos= 3;
+  m_info->open_tables= (MYRG_TABLE*) (m_info + 1);
+  m_info->tables= child_count;
+  m_info->merge_insert_method= insert_method > 0 ? insert_method : 0;
+  /* This works even if the table list is empty. */
+  m_info->end_table= m_info->open_tables + child_count;
+
+  /* Call callback for each child. */
+  dir_length= dirname_part(parent_name_buff, parent_name, &name_buff_length);
+  my_b_seek(&file_cache, 0);
+  while ((length= my_b_gets(&file_cache, child_name_buff, FN_REFLEN - 1)))
+  {
+    /* Remove line terminator. */
+    if (child_name_buff[length - 1] == '\n')
+      child_name_buff[--length]= '\0';
+
+    /* Skip empty lines and comments. */
+    if (!child_name_buff[0] || (child_name_buff[0] == '#'))
+      continue;
+
+    if (!has_path(child_name_buff))
+    {
+      VOID(strmake(parent_name_buff + dir_length, child_name_buff,
+                   sizeof(parent_name_buff) - 1 - dir_length));
+      VOID(cleanup_dirname(child_name_buff, parent_name_buff));
+    }
+    else
+      fn_format(child_name_buff, child_name_buff, "", "", 0);
+    DBUG_PRINT("info", ("child: '%s'", child_name_buff));
+
+    /* Callback registers child with handler table. */
+    if ((rc= (*callback)(callback_param, child_name_buff)))
+      goto err;
+  }
+
+  end_io_cache(&file_cache);
+  VOID(my_close(fd, MYF(0)));
+
+  m_info->open_list.data= (void*) m_info;
+  pthread_mutex_lock(&THR_LOCK_open);
+  myrg_open_list= list_add(myrg_open_list, &m_info->open_list);
+  pthread_mutex_unlock(&THR_LOCK_open);
+
+  DBUG_RETURN(m_info);
+
+err:
+  save_errno= my_errno;
+  switch (errpos) {
+  case 3:
+    my_free((char*) m_info, MYF(0));
+    /* Fall through */
+  case 2:
+    end_io_cache(&file_cache);
+    /* Fall through */
+  case 1:
+    VOID(my_close(fd, MYF(0)));
+  }
+  my_errno= save_errno;
+  DBUG_RETURN (NULL);
+}
+
+
+/**
+  @brief Attach children to a MyISAM MERGE parent table.
+
+  @detail Call a callback function for each child table.
+    The callback returns the MyISAM table handle of the child table.
+    Check table definition match.
+
+  @param[in]    m_info          MERGE parent table structure
+  @param[in]    handle_locking  if contains HA_OPEN_FOR_REPAIR, warn about
+                                incompatible child tables, but continue
+  @param[in]    callback        function to call for each child table
+  @param[in]    callback_param  data pointer to give to the callback
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error
+*/
+
+int myrg_attach_children(MYRG_INFO *m_info, int handle_locking,
+                         MI_INFO *(*callback)(void*),
+                         void *callback_param)
+{
+  ulonglong  file_offset;
+  MI_INFO    *myisam;
+  int        rc;
+  int        errpos;
+  int        save_errno;
+  uint       idx;
+  uint       child_nr;
+  uint       key_parts;
+  uint       min_keys;
+  DBUG_ENTER("myrg_attach_children");
+
+  rc= 1;
+  errpos= 0;
+  file_offset= 0;
+  LINT_INIT(key_parts);
+  min_keys= 0;
+  child_nr= 0;
+  while ((myisam= (*callback)(callback_param)))
+  {
+    DBUG_ASSERT(child_nr < m_info->tables);
+
+    /* Special handling when the first child is attached. */
+    if (!child_nr)
+    {
+      m_info->reclength= myisam->s->base.reclength;
+      min_keys=  myisam->s->base.keys;
+      key_parts= myisam->s->base.key_parts;
+      if (!m_info->rec_per_key_part)
+      {
+        if(!(m_info->rec_per_key_part= (ulong*)
+             my_malloc(key_parts * sizeof(long), MYF(MY_WME))))
+          goto err;
+        errpos= 1;
+      }
+    }
+
+    /* Add MyISAM table info. */
+    m_info->open_tables[child_nr].table= myisam;
+    m_info->open_tables[child_nr].file_offset= (my_off_t) file_offset;
+    file_offset+= myisam->state->data_file_length;
+
+    /* Check table definition match. */
+    if (m_info->reclength != myisam->s->base.reclength)
+    {
+      my_errno= HA_ERR_WRONG_MRG_TABLE_DEF;
+      if (handle_locking & HA_OPEN_FOR_REPAIR)
+      {
+        myrg_print_wrong_table(myisam->filename);
+        continue;
+      }
+      goto err;
+    }
+
+    m_info->options|= myisam->s->options;
+    m_info->records+= myisam->state->records;
+    m_info->del+= myisam->state->del;
+    m_info->data_file_length+= myisam->state->data_file_length;
+    if (min_keys > myisam->s->base.keys)
+      min_keys= myisam->s->base.keys;
+    for (idx= 0; idx < key_parts; idx++)
+      m_info->rec_per_key_part[idx]+= (myisam->s->state.rec_per_key_part[idx] /
+                                       m_info->tables);
+    child_nr++;
+  }
+
+  if (my_errno == HA_ERR_WRONG_MRG_TABLE_DEF)
+    goto err;
+  if (sizeof(my_off_t) == 4 && file_offset > (ulonglong) (ulong) ~0L)
+  {
+    my_errno= HA_ERR_RECORD_FILE_FULL;
+    goto err;
+  }
+  /* Don't mark table readonly, for ALTER TABLE ... UNION=(...) to work */
+  m_info->options&= ~(HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA);
+  m_info->keys= min_keys;
+  m_info->last_used_table= m_info->open_tables;
+  m_info->children_attached= TRUE;
+  DBUG_RETURN(0);
+
+err:
+  save_errno= my_errno;
+  switch (errpos) {
+  case 1:
+    my_free((char*) m_info->rec_per_key_part, MYF(0));
+  }
+  my_errno= save_errno;
+  DBUG_RETURN(1);
+}
+
+
+/**
+  @brief Detach children from a MyISAM MERGE parent table.
+
+  @param[in]    m_info          MERGE parent table structure
+
+  @note Detach must not touch the children in any way.
+    They may have been closed at ths point already.
+    All references to the children should be removed.
+
+  @return status
+    @retval     0               OK
+*/
+
+int myrg_detach_children(MYRG_INFO *m_info)
+{
+  DBUG_ENTER("myrg_detach_children");
+  m_info->children_attached= FALSE;
+  bzero((char*) m_info->open_tables, m_info->tables * sizeof(MYRG_TABLE));
+  m_info->records= 0;
+  m_info->del= 0;
+  m_info->data_file_length= 0;
+  m_info->options= 0;
+  DBUG_RETURN(0);
+}
+
Thread
bk commit into 5.1 tree (istruewing:1.2585) BUG#26379Ingo Struewing13 Aug