summaryrefslogtreecommitdiffstats
path: root/storage/myisammrg/ha_myisammrg.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/myisammrg/ha_myisammrg.cc')
-rw-r--r--storage/myisammrg/ha_myisammrg.cc1785
1 files changed, 1785 insertions, 0 deletions
diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc
new file mode 100644
index 00000000..d37636ab
--- /dev/null
+++ b/storage/myisammrg/ha_myisammrg.cc
@@ -0,0 +1,1785 @@
+/* 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 reopend. 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.
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#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);
+}
+
+
+const char *ha_myisammrg::index_type(uint key_number)
+{
+ return ((table->key_info[key_number].flags & HA_FULLTEXT) ?
+ "FULLTEXT" :
+ (table->key_info[key_number].flags & HA_SPATIAL) ?
+ "SPATIAL" :
+ (table->key_info[key_number].algorithm == HA_KEY_ALG_RTREE) ?
+ "RTREE" :
+ "BTREE");
+}
+
+
+/**
+ 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;
+ char *db;
+ char *table_name;
+ size_t dirlen;
+ size_t db_length;
+ size_t table_name_length;
+ char dir_path[FN_REFLEN];
+ char name_buf[NAME_LEN];
+ 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_length= parent->s->db.length;
+ db= strmake_root(&ha_myrg->children_mem_root, parent->s->db.str, db_length);
+ /* Child table name is encoded in parent dot-MRG starting with 5.1.46. */
+ if (parent->s->mysql_version >= 50146)
+ {
+ table_name_length= filename_to_tablename(filename, name_buf,
+ sizeof(name_buf));
+ table_name= strmake_root(&ha_myrg->children_mem_root, name_buf,
+ table_name_length);
+ }
+ else
+ {
+ table_name_length= strlen(filename);
+ table_name= strmake_root(&ha_myrg->children_mem_root, filename,
+ table_name_length);
+ }
+ }
+ else
+ {
+ DBUG_ASSERT(strlen(filename) < sizeof(dir_path));
+ fn_format(dir_path, filename, "", "", 0);
+ /* Extract child table name and database name from filename. */
+ 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_length= filename_to_tablename(dir_path + dirlen, name_buf,
+ sizeof(name_buf));
+ table_name= strmake_root(&ha_myrg->children_mem_root, name_buf,
+ table_name_length);
+ dir_path[dirlen - 1]= 0;
+ dirlen= dirname_length(dir_path);
+ db_length= filename_to_tablename(dir_path + dirlen, name_buf, sizeof(name_buf));
+ db= strmake_root(&ha_myrg->children_mem_root, name_buf, db_length);
+ }
+ else
+ {
+ table_name_length= strlen(dir_path + dirlen);
+ table_name= strmake_root(&ha_myrg->children_mem_root, dir_path + dirlen,
+ table_name_length);
+ dir_path[dirlen - 1]= 0;
+ dirlen= dirname_length(dir_path);
+ db_length= strlen(dir_path + dirlen);
+ db= strmake_root(&ha_myrg->children_mem_root, dir_path + dirlen,
+ db_length);
+ }
+ }
+
+ if (! db || ! table_name)
+ DBUG_RETURN(1);
+
+ DBUG_PRINT("myrg", ("open: '%.*s'.'%.*s'", (int) db_length, db,
+ (int) table_name_length, table_name));
+
+ /* Convert to lowercase if required. */
+ if (lower_case_table_names && table_name_length)
+ {
+ /* purecov: begin tested */
+ table_name_length= my_casedn_str(files_charset_info, table_name);
+ /* purecov: end */
+ }
+
+ mrg_child_def= new (&ha_myrg->children_mem_root)
+ Mrg_child_def(db, db_length, table_name, 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;
+ }
+}
+
+/**
+ 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= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST));
+ 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, &param,
+ (my_bool *) &param.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 !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
+
+ 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;
+ 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
+ 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 !defined(BIG_TABLES) || 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, then (assuming underlying
+ tables have equal size, the only "simple" approach we can use)
+ retrieving X index records from a merge table will require N times more
+ disk seeks compared to doing the same on a MyISAM table with equal
+ number of records.
+ In the edge case (file_tables > myisam_block_size) we'll get
+ block_size==0, and index calculation code will act as if we need one
+ disk seek to retrieve one index tuple.
+
+ TODO: In 5.2 index scan cost calculation will be factored out into a
+ virtual function in class handler and we'll be able to remove this hack.
+ */
+ stats.block_size= 0;
+ if (file->tables)
+ stats.block_size= myisam_block_size / file->tables;
+
+ 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 opimizer 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= (TABLE_LIST *) thd->calloc(sizeof(TABLE_LIST))))
+ 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= (const char**) thd->alloc((ntables + 1) * sizeof(char*))))
+ 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");
+ 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 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;
+
+ 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;