mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 10:56:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1773 lines
		
	
	
	
		
			57 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1773 lines
		
	
	
	
		
			57 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (c) 2000, 2011, Oracle and/or its affiliates
 | |
|    Copyright (c) 2009, 2016, MariaDB
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
 | |
| 
 | |
| 
 | |
| /*
 | |
|   MyISAM MERGE tables
 | |
| 
 | |
|   A MyISAM MERGE table is kind of a union of zero or more MyISAM tables.
 | |
| 
 | |
|   Besides the normal form file (.frm) a MERGE table has a meta file
 | |
|   (.MRG) with a list of tables. These are paths to the MyISAM table
 | |
|   files. The last two components of the path contain the database name
 | |
|   and the table name respectively.
 | |
| 
 | |
|   When a MERGE table is open, there exists an TABLE object for the MERGE
 | |
|   table itself and a TABLE object for each of the MyISAM tables. For
 | |
|   abbreviated writing, I call the MERGE table object "parent" and the
 | |
|   MyISAM table objects "children".
 | |
| 
 | |
|   A MERGE table is almost always opened through open_and_lock_tables()
 | |
|   and hence through open_tables(). When the parent appears in the list
 | |
|   of tables to open, the initial open of the handler does nothing but
 | |
|   read the meta file and collect a list of TABLE_LIST objects for the
 | |
|   children. This list is attached to the handler object as
 | |
|   ha_myisammrg::children_l. The end of the children list is saved in
 | |
|   ha_myisammrg::children_last_l.
 | |
| 
 | |
|   Back in open_tables(), handler::extra(HA_EXTRA_ADD_CHILDREN_LIST) is
 | |
|   called. It updates each list member with the lock type and a back
 | |
|   pointer to the parent TABLE_LIST object TABLE_LIST::parent_l. The list
 | |
|   is then inserted in the list of tables to open, right behind the
 | |
|   parent. Consequently, open_tables() opens the children, one after the
 | |
|   other. The TABLE references of the TABLE_LIST objects are implicitly
 | |
|   set to the open tables by open_table(). The children are opened as
 | |
|   independent MyISAM tables, right as if they are used by the SQL
 | |
|   statement.
 | |
| 
 | |
|   When all tables from the statement query list are open,
 | |
|   handler::extra(HA_EXTRA_ATTACH_CHILDREN) is called. It "attaches" the
 | |
|   children to the parent. All required references between parent and
 | |
|   children are set up.
 | |
| 
 | |
|   The MERGE storage engine sets up an array with references to the
 | |
|   low-level MyISAM table objects (MI_INFO). It remembers the state of
 | |
|   the table in MYRG_INFO::children_attached.
 | |
| 
 | |
|   If necessary, the compatibility of parent and children is checked.
 | |
|   This check is necessary when any of the objects are reopened. This is
 | |
|   detected by comparing the current table def version against the
 | |
|   remembered child def version. On parent open, the list members are
 | |
|   initialized to an "impossible"/"undefined" version value. So the check
 | |
|   is always executed on the first attach.
 | |
| 
 | |
|   The version check is done in myisammrg_attach_children_callback(),
 | |
|   which is called for every child. ha_myisammrg::attach_children()
 | |
|   initializes 'need_compat_check' to FALSE and
 | |
|   myisammrg_attach_children_callback() sets it ot TRUE if a table
 | |
|   def version mismatches the remembered child def version.
 | |
| 
 | |
|   The children chain remains in the statement query list until the table
 | |
|   is closed or the children are detached. This is done so that the
 | |
|   children are locked by lock_tables().
 | |
| 
 | |
|   At statement end the children are detached. At the next statement
 | |
|   begin the open-add-attach sequence repeats. There is no exception for
 | |
|   LOCK TABLES. The fresh establishment of the parent-child relationship
 | |
|   before every statement catches numerous cases of ALTER/FLUSH/DROP/etc
 | |
|   of parent or children during LOCK TABLES.
 | |
| 
 | |
|   ---
 | |
| 
 | |
|   On parent open the storage engine structures are allocated and initialized.
 | |
|   They stay with the open table until its final close.
 | |
| */
 | |
| 
 | |
| #define MYSQL_SERVER 1
 | |
| #include <my_global.h>
 | |
| #include "sql_priv.h"
 | |
| #include "unireg.h"
 | |
| #include "sql_cache.h"                          // query_cache_*
 | |
| #include "sql_show.h"                           // append_identifier
 | |
| #include "sql_table.h"                         // build_table_filename
 | |
| #include <m_ctype.h>
 | |
| #include "../myisam/ha_myisam.h"
 | |
| #include "ha_myisammrg.h"
 | |
| #include "myrg_def.h"
 | |
| #include "thr_malloc.h"                         // init_sql_alloc
 | |
| #include "sql_class.h"                          // THD
 | |
| #include "debug_sync.h"
 | |
| 
 | |
| static handler *myisammrg_create_handler(handlerton *hton,
 | |
|                                          TABLE_SHARE *table,
 | |
|                                          MEM_ROOT *mem_root)
 | |
| {
 | |
|   return new (mem_root) ha_myisammrg(hton, table);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Constructor
 | |
| */
 | |
| 
 | |
| ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg)
 | |
|   :handler(hton, table_arg), file(0), is_cloned(0)
 | |
| {
 | |
|   init_sql_alloc(rg_key_memory_children, &children_mem_root,
 | |
|                  FN_REFLEN, 0, MYF(0));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Destructor
 | |
| */
 | |
| 
 | |
| ha_myisammrg::~ha_myisammrg(void)
 | |
| {
 | |
|   free_root(&children_mem_root, MYF(0));
 | |
| }
 | |
| 
 | |
| 
 | |
| static const char *ha_myisammrg_exts[] = {
 | |
|   MYRG_NAME_EXT,
 | |
|   NullS
 | |
| };
 | |
| extern int table2myisam(TABLE *table_arg, MI_KEYDEF **keydef_out,
 | |
|                         MI_COLUMNDEF **recinfo_out, uint *records_out);
 | |
| extern int check_definition(MI_KEYDEF *t1_keyinfo,
 | |
|                             MI_COLUMNDEF *t1_recinfo,
 | |
|                             uint t1_keys, uint t1_recs,
 | |
|                             MI_KEYDEF *t2_keyinfo,
 | |
|                             MI_COLUMNDEF *t2_recinfo,
 | |
|                             uint t2_keys, uint t2_recs, bool strict,
 | |
|                             TABLE *table_arg);
 | |
| static void split_file_name(const char *file_name,
 | |
| 			    LEX_STRING *db, LEX_STRING *name);
 | |
| 
 | |
| 
 | |
| extern "C" void myrg_print_wrong_table(const char *table_name)
 | |
| {
 | |
|   LEX_STRING db= {NULL, 0}, name;
 | |
|   char buf[FN_REFLEN];
 | |
|   split_file_name(table_name, &db, &name);
 | |
|   memcpy(buf, db.str, db.length);
 | |
|   buf[db.length]= '.';
 | |
|   memcpy(buf + db.length + 1, name.str, name.length);
 | |
|   buf[db.length + name.length + 1]= 0;
 | |
|   /*
 | |
|     Push an error to be reported as part of CHECK/REPAIR result-set.
 | |
|     Note that calling my_error() from handler is a hack which is kept
 | |
|     here to avoid refactoring. Normally engines should report errors
 | |
|     through return value which will be interpreted by caller using
 | |
|     handler::print_error() call.
 | |
|   */
 | |
|   my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), buf);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Callback function for open of a MERGE parent table.
 | |
| 
 | |
|   @param[in]    callback_param  data pointer as given to myrg_parent_open()
 | |
|                                 this is used to pass the handler handle
 | |
|   @param[in]    filename        file name of MyISAM table
 | |
|                                 without extension.
 | |
| 
 | |
|   @return status
 | |
|     @retval     0               OK
 | |
|     @retval     != 0            Error
 | |
| 
 | |
|   @detail
 | |
| 
 | |
|     This function adds a TABLE_LIST object for a MERGE child table to a
 | |
|     list of tables in the parent handler object. It is called for each
 | |
|     child table.
 | |
| 
 | |
|     The list of child TABLE_LIST objects is kept in the handler object
 | |
|     of the parent for the whole life time of the MERGE table. It is
 | |
|     inserted in the statement query list behind the MERGE parent
 | |
|     TABLE_LIST object when the MERGE table is opened. It is removed from
 | |
|     the statement query list at end of statement or at children detach.
 | |
| 
 | |
|     All memory used for the child TABLE_LIST objects and the strings
 | |
|     referred by it are taken from the parent
 | |
|     ha_myisammrg::children_mem_root. Thus they are all freed implicitly at
 | |
|     the final close of the table.
 | |
| 
 | |
|     children_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global
 | |
|     #             #               ^          #               ^
 | |
|     #             #               |          #               |
 | |
|     #             #               +--------- TABLE_LIST::prev_global
 | |
|     #             #                                          |
 | |
|     #       |<--- TABLE_LIST::prev_global                    |
 | |
|     #                                                        |
 | |
|     children_last_l -----------------------------------------+
 | |
| */
 | |
| 
 | |
| CPP_UNNAMED_NS_START
 | |
| 
 | |
| extern "C" int myisammrg_parent_open_callback(void *callback_param,
 | |
|                                               const char *filename)
 | |
| {
 | |
|   ha_myisammrg  *ha_myrg= (ha_myisammrg*) callback_param;
 | |
|   TABLE         *parent= ha_myrg->table_ptr();
 | |
|   Mrg_child_def *mrg_child_def;
 | |
|   LEX_STRING    db, table_name;
 | |
|   char          dir_path[FN_REFLEN];
 | |
|   DBUG_ENTER("myisammrg_parent_open_callback");
 | |
| 
 | |
|   /*
 | |
|     Depending on MySQL version, filename may be encoded by table name to
 | |
|     file name encoding or not. Always encoded if parent table is created
 | |
|     by 5.1.46+. Encoded if parent is created by 5.1.6+ and child table is
 | |
|     in different database.
 | |
|   */
 | |
|   if (!has_path(filename))
 | |
|   {
 | |
|     /* Child is in the same database as parent. */
 | |
|     db= ha_myrg->make_child_ident(parent->s->db);
 | |
|     /* Child table name is encoded in parent dot-MRG starting with 5.1.46. */
 | |
|     table_name= (parent->s->mysql_version >= 50146) ?
 | |
|       ha_myrg->make_child_ident_filename_to_tablename(filename,
 | |
|                                                       lower_case_table_names) :
 | |
|       ha_myrg->make_child_ident_opt_casedn(Lex_cstring_strlen(filename),
 | |
|                                            lower_case_table_names);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     DBUG_ASSERT(strlen(filename) < sizeof(dir_path));
 | |
|     fn_format(dir_path, filename, "", "", 0);
 | |
|     /* Extract child table name and database name from filename. */
 | |
|     size_t dirlen= dirname_length(dir_path);
 | |
|     /* Child db/table name is encoded in parent dot-MRG starting with 5.1.6. */
 | |
|     if (parent->s->mysql_version >= 50106)
 | |
|     {
 | |
|       table_name= ha_myrg->make_child_ident_filename_to_tablename(
 | |
|                                                      dir_path + dirlen,
 | |
|                                                      lower_case_table_names);
 | |
|       dir_path[dirlen - 1]= 0;
 | |
|       dirlen= dirname_length(dir_path);
 | |
|       db= ha_myrg->make_child_ident_filename_to_tablename(dir_path + dirlen,
 | |
|                                                           false);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       table_name= ha_myrg->make_child_ident_opt_casedn(
 | |
|                                          Lex_cstring_strlen(dir_path + dirlen),
 | |
|                                          lower_case_table_names);
 | |
|       dir_path[dirlen - 1]= 0;
 | |
|       dirlen= dirname_length(dir_path);
 | |
|       db= ha_myrg->make_child_ident(Lex_cstring_strlen(dir_path + dirlen));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (! db.str || ! table_name.str)
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   DBUG_PRINT("myrg", ("open: '%.*s'.'%.*s'", (int) db.length, db.str,
 | |
|                       (int) table_name.length, table_name.str));
 | |
| 
 | |
|   mrg_child_def= new (&ha_myrg->children_mem_root)
 | |
|                  Mrg_child_def(db.str, db.length,
 | |
|                                table_name.str, table_name.length);
 | |
| 
 | |
|   if (! mrg_child_def ||
 | |
|       ha_myrg->child_def_list.push_back(mrg_child_def,
 | |
|                                         &ha_myrg->children_mem_root))
 | |
|   {
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| CPP_UNNAMED_NS_END
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Set external_ref for the child MyISAM tables. They need this to be set in
 | |
|   order to check for killed status.
 | |
| */
 | |
| static void myrg_set_external_ref(MYRG_INFO *m_info, void *ext_ref_arg)
 | |
| {
 | |
|   int i;
 | |
|   for (i= 0; i < (int)m_info->tables; i++)
 | |
|   {
 | |
|     m_info->open_tables[i].table->external_ref= ext_ref_arg;
 | |
|   }
 | |
| }
 | |
| 
 | |
| IO_AND_CPU_COST ha_myisammrg::rnd_pos_time(ha_rows rows)
 | |
| {
 | |
|   IO_AND_CPU_COST cost= handler::rnd_pos_time(rows);
 | |
|   /*
 | |
|     Row data is not cached. costs.row_lookup_cost includes the cost of
 | |
|     the reading the row from system (probably cached by the OS).
 | |
|   */
 | |
|   cost.io= 0;
 | |
|   return cost;
 | |
| }
 | |
| 
 | |
| IO_AND_CPU_COST ha_myisammrg::keyread_time(uint index, ulong ranges,
 | |
|                                            ha_rows rows,
 | |
|                                            ulonglong blocks)
 | |
| {
 | |
|   IO_AND_CPU_COST cost= handler::keyread_time(index, ranges, rows, blocks);
 | |
|   if (!blocks)
 | |
|   {
 | |
|     cost.io*= file->tables;
 | |
|     cost.cpu*= file->tables;
 | |
|   }
 | |
|   /* Add the cost of having to do a key lookup in all trees */
 | |
|   if (file->tables)
 | |
|     cost.cpu+= (file->tables-1) * (ranges * KEY_LOOKUP_COST);
 | |
|   return cost;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Open a MERGE parent table, but not its children.
 | |
| 
 | |
|   @param[in]    name               MERGE table path name
 | |
|   @param[in]    mode               read/write mode, unused
 | |
|   @param[in]    test_if_locked_arg open flags
 | |
| 
 | |
|   @return       status
 | |
|   @retval     0                    OK
 | |
|   @retval     -1                   Error, my_errno gives reason
 | |
| 
 | |
|   @detail
 | |
|   This function initializes the MERGE storage engine structures
 | |
|   and adds a child list of TABLE_LIST to the parent handler.
 | |
| */
 | |
| 
 | |
| int ha_myisammrg::open(const char *name, int mode __attribute__((unused)),
 | |
|                        uint test_if_locked_arg)
 | |
| {
 | |
|   DBUG_ENTER("ha_myisammrg::open");
 | |
|   DBUG_PRINT("myrg", ("name: '%s'  table: %p", name, table));
 | |
|   DBUG_PRINT("myrg", ("test_if_locked_arg: %u", test_if_locked_arg));
 | |
| 
 | |
|   /* Must not be used when table is open. */
 | |
|   DBUG_ASSERT(!this->file);
 | |
| 
 | |
|   /* Save for later use. */
 | |
|   test_if_locked= test_if_locked_arg;
 | |
| 
 | |
|   /* In case this handler was open and closed before, free old data. */
 | |
|   free_root(&this->children_mem_root, MYF(MY_MARK_BLOCKS_FREE));
 | |
| 
 | |
|   /*
 | |
|     Initialize variables that are used, modified, and/or set by
 | |
|     myisammrg_parent_open_callback().
 | |
|     'children_l' is the head of the children chain.
 | |
|     'children_last_l' points to the end of the children chain.
 | |
|     'my_errno' is set by myisammrg_parent_open_callback() in
 | |
|     case of an error.
 | |
|   */
 | |
|   children_l= NULL;
 | |
|   children_last_l= NULL;
 | |
|   child_def_list.empty();
 | |
|   my_errno= 0;
 | |
| 
 | |
|   /* retrieve children table list. */
 | |
|   if (is_cloned)
 | |
|   {
 | |
|     /*
 | |
|       Open and attaches the MyISAM tables,that are under the MERGE table 
 | |
|       parent, on the MyISAM storage engine interface directly within the
 | |
|       MERGE engine. The new MyISAM table instances, as well as the MERGE 
 | |
|       clone itself, are not visible in the table cache. This is not a 
 | |
|       problem because all locking is handled by the original MERGE table
 | |
|       from which this is cloned of.
 | |
|     */
 | |
|     if (!(file= myrg_open(name, table->db_stat,  HA_OPEN_IGNORE_IF_LOCKED)))
 | |
|     {
 | |
|       DBUG_PRINT("error", ("my_errno %d", my_errno));
 | |
|       DBUG_RETURN(my_errno ? my_errno : -1); 
 | |
|     }
 | |
| 
 | |
|     file->children_attached= TRUE;
 | |
|     myrg_set_external_ref(file, (void*)table);
 | |
| 
 | |
|     info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
 | |
|   }
 | |
|   else if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this)))
 | |
|   {
 | |
|     /* purecov: begin inspected */
 | |
|     DBUG_PRINT("error", ("my_errno %d", my_errno));
 | |
|     DBUG_RETURN(my_errno ? my_errno : -1);
 | |
|     /* purecov: end */
 | |
|   }
 | |
|   DBUG_PRINT("myrg", ("MYRG_INFO: %p  child tables: %u",
 | |
|                       file, file->tables));
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Add list of MERGE children to a TABLE_LIST chain.
 | |
| 
 | |
|   @return status
 | |
|     @retval     0               OK
 | |
|     @retval     != 0            Error
 | |
| 
 | |
|   @detail
 | |
|     When a MERGE parent table has just been opened, insert the
 | |
|     TABLE_LIST chain from the MERGE handler into the table list used for
 | |
|     opening tables for this statement. This lets the children be opened
 | |
|     too.
 | |
| */
 | |
| 
 | |
| int ha_myisammrg::add_children_list(void)
 | |
| {
 | |
|   TABLE_LIST  *parent_l= this->table->pos_in_table_list;
 | |
|   THD  *thd= table->in_use;
 | |
|   List_iterator_fast<Mrg_child_def> it(child_def_list);
 | |
|   Mrg_child_def *mrg_child_def;
 | |
|   DBUG_ENTER("ha_myisammrg::add_children_list");
 | |
|   DBUG_PRINT("myrg", ("table: '%s'.'%s' %p", this->table->s->db.str,
 | |
|                       this->table->s->table_name.str, this->table));
 | |
| 
 | |
|   /* Must call this with open table. */
 | |
|   DBUG_ASSERT(this->file);
 | |
| 
 | |
|   /* Ignore this for empty MERGE tables (UNION=()). */
 | |
|   if (!this->file->tables)
 | |
|   {
 | |
|     DBUG_PRINT("myrg", ("empty merge table union"));
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   /* Must not call this with attached children. */
 | |
|   DBUG_ASSERT(!this->file->children_attached);
 | |
| 
 | |
|   /* Must not call this with children list in place. */
 | |
|   DBUG_ASSERT(this->children_l == NULL);
 | |
| 
 | |
|   /*
 | |
|     Prevent inclusion of another MERGE table, which could make infinite
 | |
|     recursion.
 | |
|   */
 | |
|   if (parent_l->parent_l)
 | |
|   {
 | |
|     my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), parent_l->alias.str);
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   while ((mrg_child_def= it++))
 | |
|   {
 | |
|     TABLE_LIST  *child_l;
 | |
|     LEX_CSTRING db;
 | |
|     LEX_CSTRING table_name;
 | |
| 
 | |
|     child_l= thd->alloc<TABLE_LIST>(1);
 | |
|     db.str= (char*) thd->memdup(mrg_child_def->db.str, mrg_child_def->db.length+1);
 | |
|     db.length= mrg_child_def->db.length;
 | |
|     table_name.str= (char*) thd->memdup(mrg_child_def->name.str,
 | |
|                                         mrg_child_def->name.length+1);
 | |
|     table_name.length= mrg_child_def->name.length;
 | |
| 
 | |
|     if (child_l == NULL || db.str == NULL || table_name.str == NULL)
 | |
|       DBUG_RETURN(1);
 | |
| 
 | |
|     child_l->init_one_table(&db, &table_name, 0, parent_l->lock_type);
 | |
|     /* Set parent reference. Used to detect MERGE in children list. */
 | |
|     child_l->parent_l= parent_l;
 | |
|     /* Copy select_lex. Used in unique_table() at least. */
 | |
|     child_l->select_lex= parent_l->select_lex;
 | |
|     /* Set the expected table version, to not cause spurious re-prepare. */
 | |
|     child_l->set_table_ref_id(mrg_child_def->get_child_table_ref_type(),
 | |
|                               mrg_child_def->get_child_def_version());
 | |
|     /*
 | |
|       Copy parent's prelocking attribute to allow opening of child
 | |
|       temporary residing in the prelocking list.
 | |
|     */
 | |
|     child_l->prelocking_placeholder= parent_l->prelocking_placeholder;
 | |
|     /*
 | |
|       For statements which acquire a SNW metadata lock on a parent table and
 | |
|       then later try to upgrade it to an X lock (e.g. ALTER TABLE), SNW
 | |
|       locks should be also taken on the children tables.
 | |
| 
 | |
|       Otherwise we end up in a situation where the thread trying to upgrade SNW
 | |
|       to X lock on the parent also holds a SR metadata lock and a read
 | |
|       thr_lock.c lock on the child. As a result, another thread might be
 | |
|       blocked on the thr_lock.c lock for the child after successfully acquiring
 | |
|       a SR or SW metadata lock on it. If at the same time this second thread
 | |
|       has a shared metadata lock on the parent table or there is some other
 | |
|       thread which has a shared metadata lock on the parent and is waiting for
 | |
|       this second thread, we get a deadlock. This deadlock cannot be properly
 | |
|       detected by the MDL subsystem as part of the waiting happens within
 | |
|       thr_lock.c. By taking SNW locks on the child tables we ensure that any
 | |
|       thread which waits for a thread doing SNW -> X upgrade, does this within
 | |
|       the MDL subsystem and thus potential deadlocks are exposed to the deadlock
 | |
|       detector.
 | |
| 
 | |
|       We don't do the same thing for SNRW locks as this would allow
 | |
|       DDL on implicitly locked underlying tables of a MERGE table.
 | |
|     */
 | |
|     if (! thd->locked_tables_mode &&
 | |
|         parent_l->mdl_request.type == MDL_SHARED_UPGRADABLE)
 | |
|       child_l->mdl_request.set_type(MDL_SHARED_NO_WRITE);
 | |
|     /* Link TABLE_LIST object into the children list. */
 | |
|     if (this->children_last_l)
 | |
|       child_l->prev_global= this->children_last_l;
 | |
|     else
 | |
|     {
 | |
|       /* Initialize children_last_l when handling first child. */
 | |
|       this->children_last_l= &this->children_l;
 | |
|     }
 | |
|     *this->children_last_l= child_l;
 | |
|     this->children_last_l= &child_l->next_global;
 | |
|   }
 | |
| 
 | |
|   /* Insert children into the table list. */
 | |
|   if (parent_l->next_global)
 | |
|     parent_l->next_global->prev_global= this->children_last_l;
 | |
|   *this->children_last_l= parent_l->next_global;
 | |
|   parent_l->next_global= this->children_l;
 | |
|   this->children_l->prev_global= &parent_l->next_global;
 | |
|   /*
 | |
|     We have to update LEX::query_tables_last if children are added to
 | |
|     the tail of the table list in order to be able correctly add more
 | |
|     elements to it (e.g. as part of prelocking process).
 | |
|   */
 | |
|   if (thd->lex->query_tables_last == &parent_l->next_global)
 | |
|     thd->lex->query_tables_last= this->children_last_l;
 | |
|   /*
 | |
|     The branch below works only when re-executing a prepared
 | |
|     statement or a stored routine statement:
 | |
|     We've just modified query_tables_last. Keep it in sync with
 | |
|     query_tables_last_own, if it was set by the prelocking code.
 | |
|     This ensures that the check that prohibits double updates (*)
 | |
|     can correctly identify what tables belong to the main statement.
 | |
|     (*) A double update is, e.g. when a user issues UPDATE t1 and
 | |
|     t1 has an AFTER UPDATE trigger that also modifies t1.
 | |
|   */
 | |
|   if (thd->lex->query_tables_own_last == &parent_l->next_global)
 | |
|     thd->lex->query_tables_own_last= this->children_last_l;
 | |
| 
 | |
| end:
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   A context of myrg_attach_children() callback.
 | |
| */
 | |
| 
 | |
| class Mrg_attach_children_callback_param
 | |
| {
 | |
| public:
 | |
|   /**
 | |
|     'need_compat_check' is set by myisammrg_attach_children_callback()
 | |
|     if a child fails the table def version check.
 | |
|   */
 | |
|   bool need_compat_check;
 | |
|   /** TABLE_LIST identifying this merge parent. */
 | |
|   TABLE_LIST *parent_l;
 | |
|   /** Iterator position, the current child to attach. */
 | |
|   TABLE_LIST *next_child_attach;
 | |
|   List_iterator_fast<Mrg_child_def> def_it;
 | |
|   Mrg_child_def *mrg_child_def;
 | |
| public:
 | |
|   Mrg_attach_children_callback_param(TABLE_LIST *parent_l_arg,
 | |
|                                      TABLE_LIST *first_child,
 | |
|                                      List<Mrg_child_def> &child_def_list)
 | |
|     :need_compat_check(FALSE),
 | |
|     parent_l(parent_l_arg),
 | |
|     next_child_attach(first_child),
 | |
|     def_it(child_def_list),
 | |
|     mrg_child_def(def_it++)
 | |
|   {}
 | |
|   void next()
 | |
|   {
 | |
|     next_child_attach= next_child_attach->next_global;
 | |
|     if (next_child_attach && next_child_attach->parent_l != parent_l)
 | |
|       next_child_attach= NULL;
 | |
|     if (mrg_child_def)
 | |
|       mrg_child_def= def_it++;
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Callback function for attaching a MERGE child table.
 | |
| 
 | |
|   @param[in]    callback_param  data pointer as given to myrg_attach_children()
 | |
|                                 this is used to pass the handler handle
 | |
| 
 | |
|   @return       pointer to open MyISAM table structure
 | |
|     @retval     !=NULL                  OK, returning pointer
 | |
|     @retval     NULL,                   Error.
 | |
| 
 | |
|   @detail
 | |
|     This function retrieves the MyISAM table handle from the
 | |
|     next child table. It is called for each child table.
 | |
| */
 | |
| 
 | |
| CPP_UNNAMED_NS_START
 | |
| 
 | |
| extern "C" MI_INFO *myisammrg_attach_children_callback(void *callback_param)
 | |
| {
 | |
|   Mrg_attach_children_callback_param *param=
 | |
|     (Mrg_attach_children_callback_param*) callback_param;
 | |
|   TABLE         *parent= param->parent_l->table;
 | |
|   TABLE         *child;
 | |
|   TABLE_LIST    *child_l= param->next_child_attach;
 | |
|   Mrg_child_def *mrg_child_def= param->mrg_child_def;
 | |
|   MI_INFO       *myisam= NULL;
 | |
|   DBUG_ENTER("myisammrg_attach_children_callback");
 | |
| 
 | |
|   /*
 | |
|     Number of children in the list and MYRG_INFO::tables_count,
 | |
|     which is used by caller of this function, should always match.
 | |
|   */
 | |
|   DBUG_ASSERT(child_l);
 | |
| 
 | |
|   child= child_l->table;
 | |
|   /* Prepare for next child. */
 | |
|   param->next();
 | |
| 
 | |
|   /*
 | |
|     When MERGE table is opened for CHECK or REPAIR TABLE statements,
 | |
|     failure to open any of underlying tables is ignored until this moment
 | |
|     (this is needed to provide complete list of the problematic underlying
 | |
|     tables in CHECK/REPAIR TABLE output).
 | |
|     Here we detect such a situation and report an appropriate error.
 | |
|   */
 | |
|   if (! child)
 | |
|   {
 | |
|     DBUG_PRINT("error", ("failed to open underlying table '%s'.'%s'",
 | |
|                          child_l->db.str, child_l->table_name.str));
 | |
|     /*
 | |
|       This should only happen inside of CHECK/REPAIR TABLE or
 | |
|       for the tables added by the pre-locking code.
 | |
|     */
 | |
|     DBUG_ASSERT(current_thd->open_options & HA_OPEN_FOR_REPAIR ||
 | |
|                 child_l->prelocking_placeholder);
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Do a quick compatibility check. The table def version is set when
 | |
|     the table share is created. The child def version is copied
 | |
|     from the table def version after a successful compatibility check.
 | |
|     We need to repeat the compatibility check only if a child is opened
 | |
|     from a different share than last time it was used with this MERGE
 | |
|     table.
 | |
|   */
 | |
|   DBUG_PRINT("myrg", ("table_def_version last: %lu  current: %lu",
 | |
|                       (ulong) mrg_child_def->get_child_def_version(),
 | |
|                       (ulong) child->s->get_table_def_version()));
 | |
|   if (mrg_child_def->get_child_def_version() != child->s->get_table_def_version())
 | |
|     param->need_compat_check= TRUE;
 | |
| 
 | |
|   /*
 | |
|     If child is temporary, parent must be temporary as well. Other
 | |
|     parent/child combinations are allowed. This check must be done for
 | |
|     every child on every open because the table def version can overlap
 | |
|     between temporary and non-temporary tables. We need to detect the
 | |
|     case where a non-temporary table has been replaced with a temporary
 | |
|     table of the same version. Or vice versa. A very unlikely case, but
 | |
|     it could happen. (Note that the condition was different from
 | |
|     5.1.23/6.0.4(Bug#19627) to 5.5.6 (Bug#36171): child->s->tmp_table !=
 | |
|     parent->s->tmp_table. Tables were required to have the same status.)
 | |
|   */
 | |
|   if (child->s->tmp_table && !parent->s->tmp_table)
 | |
|   {
 | |
|     DBUG_PRINT("error", ("temporary table mismatch parent: %d  child: %d",
 | |
|                          parent->s->tmp_table, child->s->tmp_table));
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   /* Extract the MyISAM table structure pointer from the handler object. */
 | |
|   if ((child->file->ht->db_type != DB_TYPE_MYISAM) ||
 | |
|       !(myisam= ((ha_myisam*) child->file)->file_ptr()))
 | |
|   {
 | |
|     DBUG_PRINT("error", ("no MyISAM handle for child table: '%s'.'%s' %p",
 | |
|                          child->s->db.str, child->s->table_name.str,
 | |
|                          child));
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("myrg", ("MyISAM handle: %p", myisam));
 | |
| 
 | |
|  end:
 | |
| 
 | |
|   if (!myisam &&
 | |
|       (current_thd->open_options & HA_OPEN_FOR_REPAIR))
 | |
|   {
 | |
|     char buf[2*NAME_LEN + 1 + 1];
 | |
|     strxnmov(buf, sizeof(buf) - 1, child_l->db.str, ".",
 | |
|              child_l->table_name.str, NULL);
 | |
|     /*
 | |
|       Push an error to be reported as part of CHECK/REPAIR result-set.
 | |
|       Note that calling my_error() from handler is a hack which is kept
 | |
|       here to avoid refactoring. Normally engines should report errors
 | |
|       through return value which will be interpreted by caller using
 | |
|       handler::print_error() call.
 | |
|     */
 | |
|     my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), buf);
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(myisam);
 | |
| }
 | |
| 
 | |
| CPP_UNNAMED_NS_END
 | |
| 
 | |
| /**
 | |
|    Returns a cloned instance of the current handler.
 | |
| 
 | |
|    @return A cloned handler instance.
 | |
|  */
 | |
| handler *ha_myisammrg::clone(const char *name, MEM_ROOT *mem_root)
 | |
| {
 | |
|   MYRG_TABLE    *u_table,*newu_table;
 | |
|   ha_myisammrg *new_handler= 
 | |
|     (ha_myisammrg*) get_new_handler(table->s, mem_root, table->s->db_type());
 | |
|   if (!new_handler)
 | |
|     return NULL;
 | |
|   
 | |
|   /* Inform ha_myisammrg::open() that it is a cloned handler */
 | |
|   new_handler->is_cloned= TRUE;
 | |
|   /*
 | |
|     Allocate handler->ref here because otherwise ha_open will allocate it
 | |
|     on this->table->mem_root and we will not be able to reclaim that memory 
 | |
|     when the clone handler object is destroyed.
 | |
|   */
 | |
|   if (!(new_handler->ref= (uchar*) alloc_root(mem_root, ALIGN_SIZE(ref_length)*2)))
 | |
|   {
 | |
|     delete new_handler;
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   if (new_handler->ha_open(table, name, table->db_stat,
 | |
|                            HA_OPEN_IGNORE_IF_LOCKED))
 | |
|   {
 | |
|     delete new_handler;
 | |
|     return NULL;
 | |
|   }
 | |
|  
 | |
|   /*
 | |
|     Iterate through the original child tables and
 | |
|     copy the state into the cloned child tables.
 | |
|     We need to do this because all the child tables
 | |
|     can be involved in delete.
 | |
|   */
 | |
|   newu_table= new_handler->file->open_tables;
 | |
|   for (u_table= file->open_tables; u_table < file->end_table; u_table++)
 | |
|   {
 | |
|     newu_table->table->state= u_table->table->state;
 | |
|     newu_table++;
 | |
|   }
 | |
| 
 | |
|   return new_handler;
 | |
|  }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Attach children to a MERGE table.
 | |
| 
 | |
|   @return status
 | |
|     @retval     0               OK
 | |
|     @retval     != 0            Error, my_errno gives reason
 | |
| 
 | |
|   @detail
 | |
|     Let the storage engine attach its children through a callback
 | |
|     function. Check table definitions for consistency.
 | |
| 
 | |
|   @note
 | |
|     Special thd->open_options may be in effect. We can make use of
 | |
|     them in attach. I.e. we use HA_OPEN_FOR_REPAIR to report the names
 | |
|     of mismatching child tables. We cannot transport these options in
 | |
|     ha_myisammrg::test_if_locked because they may change after the
 | |
|     parent is opened. The parent is kept open in the table cache over
 | |
|     multiple statements and can be used by other threads. Open options
 | |
|     can change over time.
 | |
| */
 | |
| 
 | |
| int ha_myisammrg::attach_children(void)
 | |
| {
 | |
|   MYRG_TABLE    *u_table;
 | |
|   MI_COLUMNDEF  *recinfo;
 | |
|   MI_KEYDEF     *keyinfo;
 | |
|   uint          recs;
 | |
|   uint          keys= table->s->keys;
 | |
|   TABLE_LIST   *parent_l= table->pos_in_table_list;
 | |
|   int           error;
 | |
|   Mrg_attach_children_callback_param param(parent_l, this->children_l, child_def_list);
 | |
|   DBUG_ENTER("ha_myisammrg::attach_children");
 | |
|   DBUG_PRINT("myrg", ("table: '%s'.'%s' %p", table->s->db.str,
 | |
|                       table->s->table_name.str, table));
 | |
|   DBUG_PRINT("myrg", ("test_if_locked: %u", this->test_if_locked));
 | |
| 
 | |
|   /* Must call this with open table. */
 | |
|   DBUG_ASSERT(this->file);
 | |
| 
 | |
|   /*
 | |
|     A MERGE table with no children (empty union) is always seen as
 | |
|     attached internally.
 | |
|   */
 | |
|   if (!this->file->tables)
 | |
|   {
 | |
|     DBUG_PRINT("myrg", ("empty merge table union"));
 | |
|     goto end;
 | |
|   }
 | |
|   DBUG_PRINT("myrg", ("child tables: %u", this->file->tables));
 | |
| 
 | |
|   /* Must not call this with attached children. */
 | |
|   DBUG_ASSERT(!this->file->children_attached);
 | |
| 
 | |
|   DEBUG_SYNC(current_thd, "before_myisammrg_attach");
 | |
|   /* Must call this with children list in place. */
 | |
|   DBUG_ASSERT(this->table->pos_in_table_list->next_global == this->children_l);
 | |
| 
 | |
|   if (myrg_attach_children(this->file, this->test_if_locked |
 | |
|                            current_thd->open_options,
 | |
|                            myisammrg_attach_children_callback, ¶m,
 | |
|                            (my_bool *) ¶m.need_compat_check))
 | |
|   {
 | |
|     error= my_errno;
 | |
|     goto err;
 | |
|   }
 | |
|   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))
 | |
|     myrg_extra(file,HA_EXTRA_NO_WAIT_LOCK,0);
 | |
|   info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
 | |
|   if (!(test_if_locked & HA_OPEN_WAIT_IF_LOCKED))
 | |
|     myrg_extra(file,HA_EXTRA_WAIT_LOCK,0);
 | |
| 
 | |
|   /*
 | |
|     The compatibility check is required only if one or more children do
 | |
|     not match their table def version from the last check. This will
 | |
|     always happen at the first attach because the reference child def
 | |
|     version is initialized to 'undefined' at open.
 | |
|   */
 | |
|   DBUG_PRINT("myrg", ("need_compat_check: %d", param.need_compat_check));
 | |
|   if (param.need_compat_check)
 | |
|   {
 | |
|     TABLE_LIST *child_l;
 | |
| 
 | |
|     if (table->s->reclength != stats.mean_rec_length && stats.mean_rec_length)
 | |
|     {
 | |
|       DBUG_PRINT("error",("reclength: %lu  mean_rec_length: %lu",
 | |
|                           table->s->reclength, stats.mean_rec_length));
 | |
|       if (test_if_locked & HA_OPEN_FOR_REPAIR)
 | |
|       {
 | |
|         /* purecov: begin inspected */
 | |
|         myrg_print_wrong_table(file->open_tables->table->filename);
 | |
|         /* purecov: end */
 | |
|       }
 | |
|       error= HA_ERR_WRONG_MRG_TABLE_DEF;
 | |
|       goto err;
 | |
|     }
 | |
|     /*
 | |
|       Both recinfo and keyinfo are allocated by my_multi_malloc(), thus
 | |
|       only recinfo must be freed.
 | |
|     */
 | |
|     if ((error= table2myisam(table, &keyinfo, &recinfo, &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++)
 | |
|     {
 | |
|       if (check_definition(keyinfo, recinfo, keys, recs,
 | |
|                            u_table->table->s->keyinfo, u_table->table->s->rec,
 | |
|                            u_table->table->s->base.keys,
 | |
|                            u_table->table->s->base.fields, false, NULL))
 | |
|       {
 | |
|         DBUG_PRINT("error", ("table definition mismatch: '%s'",
 | |
|                              u_table->table->filename));
 | |
|         error= HA_ERR_WRONG_MRG_TABLE_DEF;
 | |
|         if (!(this->test_if_locked & HA_OPEN_FOR_REPAIR))
 | |
|         {
 | |
|           my_free(recinfo);
 | |
|           goto err;
 | |
|         }
 | |
|         /* purecov: begin inspected */
 | |
|         myrg_print_wrong_table(u_table->table->filename);
 | |
|         /* purecov: end */
 | |
|       }
 | |
|     }
 | |
|     my_free(recinfo);
 | |
|     if (error == HA_ERR_WRONG_MRG_TABLE_DEF)
 | |
|       goto err; /* purecov: inspected */
 | |
| 
 | |
|     List_iterator_fast<Mrg_child_def> def_it(child_def_list);
 | |
|     DBUG_ASSERT(this->children_l);
 | |
|     for (child_l= this->children_l; ; child_l= child_l->next_global)
 | |
|     {
 | |
|       Mrg_child_def *mrg_child_def= def_it++;
 | |
|       mrg_child_def->set_child_def_version(
 | |
|         child_l->table->s->get_table_ref_type(),
 | |
|         child_l->table->s->get_table_def_version());
 | |
| 
 | |
|       if (&child_l->next_global == this->children_last_l)
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| #if 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
 | |
| 
 | |
|  end:
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
| err:
 | |
|   DBUG_PRINT("error", ("attaching MERGE children failed: %d", error));
 | |
|   print_error(error, MYF(0));
 | |
|   detach_children();
 | |
|   DBUG_RETURN(my_errno= error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Detach all children from a MERGE table and from the query list of tables.
 | |
| 
 | |
|   @return status
 | |
|     @retval     0               OK
 | |
|     @retval     != 0            Error, my_errno gives reason
 | |
| 
 | |
|   @note
 | |
|     Detach must not touch the child TABLE objects in any way.
 | |
|     They may have been closed at ths point already.
 | |
|     All references to the children should be removed.
 | |
| */
 | |
| 
 | |
| int ha_myisammrg::detach_children(void)
 | |
| {
 | |
|   TABLE_LIST *child_l;
 | |
|   DBUG_ENTER("ha_myisammrg::detach_children");
 | |
| 
 | |
|   /* Must call this with open table. */
 | |
|   DBUG_ASSERT(this->file);
 | |
| 
 | |
|   /* A MERGE table with no children (empty union) cannot be detached. */
 | |
|   if (!this->file->tables)
 | |
|   {
 | |
|     DBUG_PRINT("myrg", ("empty merge table union"));
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (this->children_l)
 | |
|   {
 | |
|     THD *thd= table->in_use;
 | |
| 
 | |
|     /* Clear TABLE references. */
 | |
|     for (child_l= this->children_l; ; child_l= child_l->next_global)
 | |
|     {
 | |
|       /*
 | |
|         Do not DBUG_ASSERT(child_l->table); open_tables might be
 | |
|         incomplete.
 | |
| 
 | |
|         Clear the table reference.
 | |
|       */
 | |
|       child_l->table= NULL;
 | |
|       /* Similarly, clear the ticket reference. */
 | |
|       child_l->mdl_request.ticket= NULL;
 | |
| 
 | |
|       /* Break when this was the last child. */
 | |
|       if (&child_l->next_global == this->children_last_l)
 | |
|         break;
 | |
|     }
 | |
|     /*
 | |
|       Remove children from the table list. This won't fail if called
 | |
|       twice. The list is terminated after removal.
 | |
| 
 | |
|       If the parent is LEX::query_tables_own_last and pre-locked tables
 | |
|       follow (tables used by stored functions or triggers), the children
 | |
|       are inserted behind the parent and before the pre-locked tables. But
 | |
|       we do not adjust LEX::query_tables_own_last. The pre-locked tables
 | |
|       could have chopped off the list by clearing
 | |
|       *LEX::query_tables_own_last. This did also chop off the children. If
 | |
|       we would copy the reference from *this->children_last_l in this
 | |
|       case, we would put the chopped off pre-locked tables back to the
 | |
|       list. So we refrain from copying it back, if the destination has
 | |
|       been set to NULL meanwhile.
 | |
|     */
 | |
|     if (this->children_l->prev_global && *this->children_l->prev_global)
 | |
|       *this->children_l->prev_global= *this->children_last_l;
 | |
|     if (*this->children_last_l)
 | |
|       (*this->children_last_l)->prev_global= this->children_l->prev_global;
 | |
| 
 | |
|     /*
 | |
|       If table elements being removed are at the end of table list we
 | |
|       need to adjust LEX::query_tables_last member to point to the
 | |
|       new last element of the list.
 | |
|     */
 | |
|     if (thd->lex->query_tables_last == this->children_last_l)
 | |
|       thd->lex->query_tables_last= this->children_l->prev_global;
 | |
| 
 | |
|     /*
 | |
|       If the statement requires prelocking, and prelocked
 | |
|       tables were added right after merge children, modify the
 | |
|       last own table pointer to point at prev_global of the merge
 | |
|       parent.
 | |
|     */
 | |
|     if (thd->lex->query_tables_own_last == this->children_last_l)
 | |
|       thd->lex->query_tables_own_last= this->children_l->prev_global;
 | |
| 
 | |
|     /* Terminate child list. So it cannot be tried to remove again. */
 | |
|     *this->children_last_l= NULL;
 | |
|     this->children_l->prev_global= NULL;
 | |
| 
 | |
|     /* Forget about the children, we don't own their memory. */
 | |
|     this->children_l= NULL;
 | |
|     this->children_last_l= NULL;
 | |
|   }
 | |
| 
 | |
|   if (!this->file->children_attached)
 | |
|   {
 | |
|     DBUG_PRINT("myrg", ("merge children are already detached"));
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (myrg_detach_children(this->file))
 | |
|   {
 | |
|     /* purecov: begin inspected */
 | |
|     print_error(my_errno, MYF(0));
 | |
|     DBUG_RETURN(my_errno ? my_errno : -1);
 | |
|     /* purecov: end */
 | |
|   }
 | |
| 
 | |
|  end:
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Close a MERGE parent table, but not its children.
 | |
| 
 | |
|   @return status
 | |
|     @retval     0               OK
 | |
|     @retval     != 0            Error, my_errno gives reason
 | |
| 
 | |
|   @note
 | |
|     The children are expected to be closed separately by the caller.
 | |
| */
 | |
| 
 | |
| int ha_myisammrg::close(void)
 | |
| {
 | |
|   int rc;
 | |
|   DBUG_ENTER("ha_myisammrg::close");
 | |
|   /*
 | |
|     There are cases where children are not explicitly detached before
 | |
|     close. detach_children() protects itself against double detach.
 | |
|   */
 | |
|   if (!is_cloned)
 | |
|     detach_children();
 | |
| 
 | |
|   rc= myrg_close(file);
 | |
|   file= 0;
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::write_row(const uchar * buf)
 | |
| {
 | |
|   DBUG_ENTER("ha_myisammrg::write_row");
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
| 
 | |
|   if (file->merge_insert_method == MERGE_INSERT_DISABLED || !file->tables)
 | |
|     DBUG_RETURN(HA_ERR_TABLE_READONLY);
 | |
| 
 | |
|   if (table->next_number_field && buf == table->record[0])
 | |
|   {
 | |
|     int error;
 | |
|     if ((error= update_auto_increment()))
 | |
|       DBUG_RETURN(error); /* purecov: inspected */
 | |
|   }
 | |
|   DBUG_RETURN(myrg_write(file,buf));
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::update_row(const uchar * old_data, const uchar * new_data)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   return myrg_update(file,old_data,new_data);
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::delete_row(const uchar * buf)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   return myrg_delete(file,buf);
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::index_read_map(uchar * buf, const uchar * key,
 | |
|                                  key_part_map keypart_map,
 | |
|                                  enum ha_rkey_function find_flag)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   int error=myrg_rkey(file,buf,active_index, key, keypart_map, find_flag);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::index_read_idx_map(uchar * buf, uint index, const uchar * key,
 | |
|                                      key_part_map keypart_map,
 | |
|                                      enum ha_rkey_function find_flag)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   int error=myrg_rkey(file,buf,index, key, keypart_map, find_flag);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::index_read_last_map(uchar *buf, const uchar *key,
 | |
|                                       key_part_map keypart_map)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   int error=myrg_rkey(file,buf,active_index, key, keypart_map,
 | |
| 		      HA_READ_PREFIX_LAST);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::index_next(uchar * buf)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   int error=myrg_rnext(file,buf,active_index);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::index_prev(uchar * buf)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   int error=myrg_rprev(file,buf, active_index);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::index_first(uchar * buf)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   int error=myrg_rfirst(file, buf, active_index);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::index_last(uchar * buf)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   int error=myrg_rlast(file, buf, active_index);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::index_next_same(uchar * buf,
 | |
|                                   const uchar *key __attribute__((unused)),
 | |
|                                   uint length __attribute__((unused)))
 | |
| {
 | |
|   int error;
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   do
 | |
|   {
 | |
|     error= myrg_rnext_same(file,buf);
 | |
|   } while (error == HA_ERR_RECORD_DELETED);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| 
 | |
| 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);
 | |
|   int error=myrg_rrnd(file, buf, HA_OFFSET_ERROR);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_myisammrg::rnd_pos(uchar * buf, uchar *pos)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   int error=myrg_rrnd(file, buf, my_get_ptr(pos,ref_length));
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| }
 | |
| 
 | |
| 
 | |
| ha_rows ha_myisammrg::records_in_range(uint inx,
 | |
|                                        const key_range *min_key,
 | |
|                                        const key_range *max_key,
 | |
|                                        page_range *pages)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   return (ha_rows) myrg_records_in_range(file, (int) inx, min_key, max_key,
 | |
|                                          pages);
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_myisammrg::delete_all_rows()
 | |
| {
 | |
|   int err= 0;
 | |
|   MYRG_TABLE *table;
 | |
|   DBUG_ENTER("ha_myisammrg::delete_all_rows");
 | |
| 
 | |
|   for (table= file->open_tables; table != file->end_table; table++)
 | |
|   {
 | |
|     if ((err= mi_delete_all_rows(table->table)))
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(err);
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_myisammrg::info(uint flag)
 | |
| {
 | |
|   MYMERGE_INFO mrg_info;
 | |
|   (void) myrg_status(file,&mrg_info,flag);
 | |
|   /*
 | |
|     The following fails if one has not compiled MySQL with -DBIG_TABLES
 | |
|     and one has more than 2^32 rows in the merge tables.
 | |
|   */
 | |
|   stats.records = (ha_rows) mrg_info.records;
 | |
|   stats.deleted = (ha_rows) mrg_info.deleted;
 | |
| #if SIZEOF_OFF_T == 4
 | |
|   if ((mrg_info.records >= (ulonglong) 1 << 32) ||
 | |
|       (mrg_info.deleted >= (ulonglong) 1 << 32))
 | |
|     table->s->crashed= 1;
 | |
| #endif
 | |
|   stats.data_file_length= mrg_info.data_file_length;
 | |
|   if (mrg_info.errkey >= (int) table_share->keys)
 | |
|   {
 | |
|     /*
 | |
|      If value of errkey is higher than the number of keys
 | |
|      on the table set errkey to MAX_KEY. This will be
 | |
|      treated as unknown key case and error message generator
 | |
|      won't try to locate key causing segmentation fault.
 | |
|     */
 | |
|     mrg_info.errkey= MAX_KEY;
 | |
|   }
 | |
|   table->s->keys_in_use.set_prefix(table->s->keys);
 | |
|   stats.mean_rec_length= mrg_info.reclength;
 | |
|   
 | |
|   /*
 | |
|     The handler::block_size is used all over the code in index scan cost
 | |
|     calculations. It is used to get number of disk seeks required to
 | |
|     retrieve a number of index tuples.
 | |
|     If the merge table has N underlying tables, there will be
 | |
|     N more disk seeks compared to a scanning a normal MyISAM table.
 | |
|     The number of bytes read is the rougly the same for a normal MyISAM
 | |
|     and a MyISAM merge tables.
 | |
|   */
 | |
|   stats.block_size= myisam_block_size;
 | |
| 
 | |
|   stats.update_time= 0;
 | |
| #if SIZEOF_OFF_T > 4
 | |
|   ref_length=6;					// Should be big enough
 | |
| #else
 | |
|   ref_length=4;					// Can't be > than my_off_t
 | |
| #endif
 | |
|   if (flag & HA_STATUS_CONST)
 | |
|   {
 | |
|     if (table->s->key_parts && mrg_info.rec_per_key)
 | |
|     {
 | |
| #ifdef HAVE_valgrind
 | |
|       /*
 | |
|         valgrind may be unhappy about it, because optimizer may access values
 | |
|         between file->keys and table->key_parts, that will be uninitialized.
 | |
|         It's safe though, because even if optimizer will decide to use a key
 | |
|         with such a number, it'll be an error later anyway.
 | |
|       */
 | |
|       bzero((char*) table->key_info[0].rec_per_key,
 | |
|             sizeof(table->key_info[0].rec_per_key[0]) * table->s->key_parts);
 | |
| #endif
 | |
|       memcpy((char*) table->key_info[0].rec_per_key,
 | |
| 	     (char*) mrg_info.rec_per_key,
 | |
|              sizeof(table->key_info[0].rec_per_key[0]) *
 | |
|              MY_MIN(file->keys, table->s->key_parts));
 | |
|     }
 | |
|   }
 | |
|   if (flag & HA_STATUS_ERRKEY)
 | |
|   {
 | |
|     errkey= mrg_info.errkey;
 | |
|     my_store_ptr(dup_ref, ref_length, mrg_info.dupp_key_pos);
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_myisammrg::extra(enum ha_extra_function operation)
 | |
| {
 | |
|   if (operation == HA_EXTRA_ADD_CHILDREN_LIST)
 | |
|   {
 | |
|     int rc= add_children_list();
 | |
|     return(rc);
 | |
|   }
 | |
|   else 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_IS_ATTACHED_CHILDREN)
 | |
|   {
 | |
|     /* For the upper layer pretend empty MERGE union is never attached. */
 | |
|     return(file && file->tables && file->children_attached);
 | |
|   }
 | |
|   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 ||
 | |
|       operation == HA_EXTRA_PREPARE_FOR_DROP ||
 | |
|       operation == HA_EXTRA_PREPARE_FOR_RENAME)
 | |
|     return 0;
 | |
|   if (operation == HA_EXTRA_MMAP && !opt_myisam_use_mmap)
 | |
|     return 0;
 | |
|   return myrg_extra(file,operation,0);
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::reset(void)
 | |
| {
 | |
|   /* This is normally called with detached children. */
 | |
|   return myrg_reset(file);
 | |
| }
 | |
| 
 | |
| /* To be used with WRITE_CACHE, EXTRA_CACHE and BULK_INSERT_BEGIN */
 | |
| 
 | |
| int ha_myisammrg::extra_opt(enum ha_extra_function operation, ulong cache_size)
 | |
| {
 | |
|   DBUG_ASSERT(this->file->children_attached);
 | |
|   return myrg_extra(file, operation, (void*) &cache_size);
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::external_lock(THD *thd, int lock_type)
 | |
| {
 | |
|   /*
 | |
|     This can be called with no children attached. E.g. FLUSH TABLES
 | |
|     unlocks and re-locks tables under LOCK TABLES, but it does not open
 | |
|     them first. So they are detached all the time. But locking of the
 | |
|     children should work anyway because thd->open_tables is not changed
 | |
|     during FLUSH TABLES.
 | |
| 
 | |
|     If this handler instance has been cloned, we still must call
 | |
|     myrg_lock_database().
 | |
|   */
 | |
|   if (is_cloned)
 | |
|     return myrg_lock_database(file, lock_type);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| uint ha_myisammrg::lock_count(void) const
 | |
| {
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| THR_LOCK_DATA **ha_myisammrg::store_lock(THD *thd,
 | |
| 					 THR_LOCK_DATA **to,
 | |
| 					 enum thr_lock_type lock_type)
 | |
| {
 | |
|   MYRG_TABLE *open_table;
 | |
| 
 | |
|   /*
 | |
|     This method can be called while another thread is attaching the
 | |
|     children. If the processor reorders instructions or write to memory,
 | |
|     'children_attached' could be set before 'open_tables' has all the
 | |
|     pointers to the children. Use of a mutex here and in
 | |
|     myrg_attach_children() forces consistent data.
 | |
|   */
 | |
|   mysql_mutex_lock(&this->file->mutex);
 | |
| 
 | |
|   /*
 | |
|     When MERGE table is open, but not yet attached, other threads
 | |
|     could flush it, which means calling mysql_lock_abort_for_thread()
 | |
|     on this threads TABLE. 'children_attached' is FALSE in this
 | |
|     situation. Since the table is not locked, return no lock data.
 | |
|   */
 | |
|   if (!this->file->children_attached)
 | |
|     goto end; /* purecov: tested */
 | |
| 
 | |
|   for (open_table=file->open_tables ;
 | |
|        open_table != file->end_table ;
 | |
|        open_table++)
 | |
|     open_table->table->lock.priority|= THR_LOCK_MERGE_PRIV;
 | |
| 
 | |
|  end:
 | |
|   mysql_mutex_unlock(&this->file->mutex);
 | |
|   return to;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Find out database name and table name from a filename */
 | |
| 
 | |
| static void split_file_name(const char *file_name,
 | |
| 			    LEX_STRING *db, LEX_STRING *name)
 | |
| {
 | |
|   size_t dir_length, prefix_length;
 | |
|   char buff[FN_REFLEN];
 | |
| 
 | |
|   db->length= 0;
 | |
|   strmake_buf(buff, file_name);
 | |
|   dir_length= dirname_length(buff);
 | |
|   if (dir_length > 1)
 | |
|   {
 | |
|     /* Get database */
 | |
|     buff[dir_length-1]= 0;			// Remove end '/'
 | |
|     prefix_length= dirname_length(buff);
 | |
|     db->str= (char*) file_name+ prefix_length;
 | |
|     db->length= dir_length - prefix_length -1;
 | |
|   }
 | |
|   name->str= (char*) file_name+ dir_length;
 | |
|   name->length= (uint) (fn_ext(name->str) - name->str);
 | |
| }
 | |
| 
 | |
| 
 | |
| void ha_myisammrg::update_create_info(HA_CREATE_INFO *create_info)
 | |
| {
 | |
|   DBUG_ENTER("ha_myisammrg::update_create_info");
 | |
| 
 | |
|   if (!(create_info->used_fields & HA_CREATE_USED_UNION))
 | |
|   {
 | |
|     TABLE_LIST *child_table, *end= NULL;
 | |
|     THD *thd=ha_thd();
 | |
| 
 | |
|     if (children_l != NULL)
 | |
|     {
 | |
|       for (child_table= children_l;; child_table= child_table->next_global)
 | |
|       {
 | |
|         TABLE_LIST *ptr;
 | |
| 
 | |
|         if (!(ptr= thd->calloc<TABLE_LIST>(1)))
 | |
|           DBUG_VOID_RETURN;
 | |
| 
 | |
|         if (!(ptr->table_name.str= thd->strmake(child_table->table_name.str,
 | |
|                                                 child_table->table_name.length)))
 | |
|           DBUG_VOID_RETURN;
 | |
|         ptr->table_name.length= child_table->table_name.length;
 | |
|         if (child_table->db.str && !(ptr->db.str= thd->strmake(child_table->db.str,
 | |
|                                                                child_table->db.length)))
 | |
|           DBUG_VOID_RETURN;
 | |
|         ptr->db.length= child_table->db.length;
 | |
| 
 | |
|         if (create_info->merge_list)
 | |
|           end->next_local= ptr;
 | |
|         else
 | |
|           create_info->merge_list= ptr;
 | |
|         end= ptr;
 | |
| 
 | |
|         if (&child_table->next_global == children_last_l)
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (!(create_info->used_fields & HA_CREATE_USED_INSERT_METHOD))
 | |
|   {
 | |
|     create_info->merge_insert_method = file->merge_insert_method;
 | |
|   }
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_myisammrg::create_mrg(const char *name, HA_CREATE_INFO *create_info)
 | |
| {
 | |
|   char buff[FN_REFLEN];
 | |
|   const char **table_names, **pos;
 | |
|   TABLE_LIST *tables= create_info->merge_list;
 | |
|   THD *thd= ha_thd();
 | |
|   size_t dirlgt= dirname_length(name);
 | |
|   uint ntables= 0;
 | |
|   DBUG_ENTER("ha_myisammrg::create_mrg");
 | |
| 
 | |
|   for (tables= create_info->merge_list; tables; tables= tables->next_local)
 | |
|     ntables++;
 | |
| 
 | |
|   /* Allocate a table_names array in thread mem_root. */
 | |
|   if (!(pos= table_names= thd->alloc<const char*>(ntables + 1)))
 | |
|     DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */
 | |
| 
 | |
|   /* Create child path names. */
 | |
|   for (tables= create_info->merge_list; tables; tables= tables->next_local)
 | |
|   {
 | |
|     const char *table_name= buff;
 | |
| 
 | |
|     /*
 | |
|       Construct the path to the MyISAM table. Try to meet two conditions:
 | |
|       1.) Allow to include MyISAM tables from different databases, and
 | |
|       2.) allow for moving DATADIR around in the file system.
 | |
|       The first means that we need paths in the .MRG file. The second
 | |
|       means that we should not have absolute paths in the .MRG file.
 | |
|       The best, we can do, is to use 'mysql_data_home', which is '.'
 | |
|       in mysqld and may be an absolute path in an embedded server.
 | |
|       This means that it might not be possible to move the DATADIR of
 | |
|       an embedded server without changing the paths in the .MRG file.
 | |
| 
 | |
|       Do the same even for temporary tables. MERGE children are now
 | |
|       opened through the table cache. They are opened by db.table_name,
 | |
|       not by their path name.
 | |
|     */
 | |
|     size_t length= build_table_filename(buff, sizeof(buff),
 | |
|                                       tables->db.str, tables->table_name.str, "", 0);
 | |
|     /*
 | |
|       If a MyISAM table is in the same directory as the MERGE table,
 | |
|       we use the table name without a path. This means that the
 | |
|       DATADIR can easily be moved even for an embedded server as long
 | |
|       as the MyISAM tables are from the same database as the MERGE table.
 | |
|     */
 | |
|     if ((dirname_length(buff) == dirlgt) && ! memcmp(buff, name, dirlgt))
 | |
|     {
 | |
|       table_name+= dirlgt;
 | |
|       length-= dirlgt;
 | |
|     }
 | |
|     if (!(table_name= thd->strmake(table_name, length)))
 | |
|       DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */
 | |
| 
 | |
|     *pos++= table_name;
 | |
|   }
 | |
|   *pos=0;
 | |
| 
 | |
|   /* Create a MERGE meta file from the table_names array. */
 | |
|   int res= myrg_create(name, table_names, create_info->merge_insert_method, 0);
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_myisammrg::create(const char *name, TABLE *form,
 | |
| 			 HA_CREATE_INFO *create_info)
 | |
| {
 | |
|   char buff[FN_REFLEN];
 | |
|   DBUG_ENTER("ha_myisammrg::create");
 | |
|   if (form->s->total_keys > form->s->keys)
 | |
|   {
 | |
|     my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), "MERGE", "VECTOR");
 | |
|     DBUG_RETURN(HA_ERR_UNSUPPORTED);
 | |
|   }
 | |
|   fn_format(buff, name, "", MYRG_NAME_EXT, MY_UNPACK_FILENAME | MY_APPEND_EXT);
 | |
|   int res= create_mrg(buff, create_info);
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| void ha_myisammrg::append_create_info(String *packet)
 | |
| {
 | |
|   const char *current_db;
 | |
|   size_t db_length;
 | |
|   THD *thd= current_thd;
 | |
|   TABLE_LIST *open_table, *first;
 | |
| 
 | |
|   if (file->merge_insert_method != MERGE_INSERT_DISABLED)
 | |
|   {
 | |
|     const char *type;
 | |
|     packet->append(STRING_WITH_LEN(" INSERT_METHOD="));
 | |
|     type= get_type(&merge_insert_method,file->merge_insert_method-1);
 | |
|     packet->append(type, strlen(type));
 | |
|   }
 | |
|   /*
 | |
|     There is no sence adding UNION clause in case there is no underlying
 | |
|     tables specified.
 | |
|   */
 | |
|   if (file->open_tables == file->end_table)
 | |
|     return;
 | |
|   packet->append(STRING_WITH_LEN(" UNION=("));
 | |
| 
 | |
|   current_db= table->s->db.str;
 | |
|   db_length=  table->s->db.length;
 | |
| 
 | |
|   for (first= open_table= children_l;;
 | |
|        open_table= open_table->next_global)
 | |
|   {
 | |
|     LEX_CSTRING db= open_table->db;
 | |
| 
 | |
|     if (open_table != first)
 | |
|       packet->append(',');
 | |
|     /* Report database for mapped table if it isn't in current database */
 | |
|     if (db.length &&
 | |
| 	(db_length != db.length ||
 | |
| 	 strncmp(current_db, db.str, db.length)))
 | |
|     {
 | |
|       append_identifier(thd, packet, db.str, db.length);
 | |
|       packet->append('.');
 | |
|     }
 | |
|     append_identifier(thd, packet, &open_table->table_name);
 | |
|     if (&open_table->next_global == children_last_l)
 | |
|       break;
 | |
|   }
 | |
|   packet->append(')');
 | |
| }
 | |
| 
 | |
| 
 | |
| enum_alter_inplace_result
 | |
| ha_myisammrg::check_if_supported_inplace_alter(TABLE *altered_table,
 | |
|                                                Alter_inplace_info *ha_alter_info)
 | |
| {
 | |
|   /*
 | |
|     We always support inplace ALTER in the new API, because old
 | |
|     HA_NO_COPY_ON_ALTER table_flags() hack prevents non-inplace ALTER anyway.
 | |
|   */
 | |
|   return HA_ALTER_INPLACE_EXCLUSIVE_LOCK;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool ha_myisammrg::inplace_alter_table(TABLE *altered_table,
 | |
|                                        Alter_inplace_info *ha_alter_info)
 | |
| {
 | |
|   char tmp_path[FN_REFLEN];
 | |
|   const char *name= table->s->normalized_path.str;
 | |
|   DBUG_ENTER("ha_myisammrg::inplace_alter_table");
 | |
|   fn_format(tmp_path, name, "", MYRG_NAME_TMPEXT, MY_UNPACK_FILENAME | MY_APPEND_EXT);
 | |
|   int res= create_mrg(tmp_path, ha_alter_info->create_info);
 | |
|   if (res)
 | |
|     mysql_file_delete(rg_key_file_MRG, tmp_path, MYF(0));
 | |
|   else
 | |
|   {
 | |
|     char path[FN_REFLEN];
 | |
|     fn_format(path, name, "", MYRG_NAME_EXT, MY_UNPACK_FILENAME | MY_APPEND_EXT);
 | |
|     if (mysql_file_rename(rg_key_file_MRG, tmp_path, path, MYF(0)))
 | |
|     {
 | |
|       res= my_errno;
 | |
|       mysql_file_delete(rg_key_file_MRG, tmp_path, MYF(0));
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| int ha_myisammrg::check(THD* thd, HA_CHECK_OPT* check_opt)
 | |
| {
 | |
|   return this->file->children_attached ? HA_ADMIN_OK : HA_ADMIN_CORRUPT;
 | |
| }
 | |
| 
 | |
| 
 | |
| ha_rows ha_myisammrg::records()
 | |
| {
 | |
|   return myrg_records(file);
 | |
| }
 | |
| 
 | |
| uint ha_myisammrg::count_query_cache_dependant_tables(uint8 *tables_type)
 | |
| {
 | |
|   MYRG_INFO *file = myrg_info();
 | |
|   /*
 | |
|     Here should be following statement
 | |
|   (*tables_type)|= HA_CACHE_TBL_NONTRANSACT;
 | |
|     but it has no effect because HA_CACHE_TBL_NONTRANSACT is 0
 | |
|   */
 | |
|   return (uint)(file->end_table - file->open_tables);
 | |
| }
 | |
| 
 | |
| 
 | |
| my_bool ha_myisammrg::register_query_cache_dependant_tables(THD *thd
 | |
|                                           __attribute__((unused)),
 | |
|                                           Query_cache *cache,
 | |
|                                           Query_cache_block_table **block_table,
 | |
|                                           uint *n)
 | |
| {
 | |
|   MYRG_INFO *file =myrg_info();
 | |
|   DBUG_ENTER("ha_myisammrg::register_query_cache_dependant_tables");
 | |
| 
 | |
|   for (MYRG_TABLE *table =file->open_tables;
 | |
|        table != file->end_table ;
 | |
|        table++)
 | |
|   {
 | |
|     char key[MAX_DBKEY_LENGTH];
 | |
|     uint32 db_length;
 | |
|     uint key_length= cache->filename_2_table_key(key, table->table->filename,
 | |
|                                                  &db_length);
 | |
|     (++(*block_table))->n= ++(*n);
 | |
|     /*
 | |
|       There are not callback function for for MyISAM, and engine data
 | |
|     */
 | |
|     if (!cache->insert_table(thd, key_length, key, (*block_table),
 | |
|                              db_length, 0,
 | |
|                              table_cache_type(),
 | |
|                              0, 0, TRUE))
 | |
|       DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| void ha_myisammrg::set_lock_type(enum thr_lock_type lock)
 | |
| {
 | |
|   handler::set_lock_type(lock);
 | |
|   if (children_l != NULL)
 | |
|   {
 | |
|     for (TABLE_LIST *child_table= children_l;;
 | |
|          child_table= child_table->next_global)
 | |
|     {
 | |
|       child_table->lock_type=
 | |
|         child_table->table->reginfo.lock_type= lock;
 | |
| 
 | |
|       if (&child_table->next_global == children_last_l)
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| extern int myrg_panic(enum ha_panic_function flag);
 | |
| int myisammrg_panic(handlerton *hton, ha_panic_function flag)
 | |
| {
 | |
|   return myrg_panic(flag);
 | |
| }
 | |
| 
 | |
| static void myisammrg_update_optimizer_costs(OPTIMIZER_COSTS *costs)
 | |
| {
 | |
|   myisam_update_optimizer_costs(costs);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int myisammrg_init(void *p)
 | |
| {
 | |
|   handlerton *myisammrg_hton;
 | |
| 
 | |
|   myisammrg_hton= (handlerton *)p;
 | |
| 
 | |
| #ifdef HAVE_PSI_INTERFACE
 | |
|   init_myisammrg_psi_keys();
 | |
| #endif
 | |
| 
 | |
|   myisammrg_hton->db_type= DB_TYPE_MRG_MYISAM;
 | |
|   myisammrg_hton->create= myisammrg_create_handler;
 | |
|   myisammrg_hton->panic= myisammrg_panic;
 | |
|   myisammrg_hton->flags= HTON_NO_PARTITION;
 | |
|   myisammrg_hton->tablefile_extensions= ha_myisammrg_exts;
 | |
|   myisammrg_hton->update_optimizer_costs= myisammrg_update_optimizer_costs;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| struct st_mysql_storage_engine myisammrg_storage_engine=
 | |
| { MYSQL_HANDLERTON_INTERFACE_VERSION };
 | |
| 
 | |
| maria_declare_plugin(myisammrg)
 | |
| {
 | |
|   MYSQL_STORAGE_ENGINE_PLUGIN,
 | |
|   &myisammrg_storage_engine,
 | |
|   "MRG_MyISAM",
 | |
|   "MySQL AB",
 | |
|   "Collection of identical MyISAM tables",
 | |
|   PLUGIN_LICENSE_GPL,
 | |
|   myisammrg_init, /* Plugin Init */
 | |
|   NULL, /* Plugin Deinit */
 | |
|   0x0100, /* 1.0 */
 | |
|   NULL,                       /* status variables                */
 | |
|   NULL,                       /* system variables                */
 | |
|   "1.0",                      /* string version */
 | |
|   MariaDB_PLUGIN_MATURITY_STABLE /* maturity */
 | |
| }
 | |
| maria_declare_plugin_end;
 | 
