mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-30 04:26:45 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			5400 lines
		
	
	
		
			158 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			5400 lines
		
	
	
		
			158 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
 | |
| 
 | |
|    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 Street, Suite 500, Boston, MA 02110-1335 USA */
 | |
| 
 | |
| /** @file handler.cc
 | |
| 
 | |
|     @brief
 | |
|   Handler-calling-functions
 | |
| */
 | |
| 
 | |
| #ifdef USE_PRAGMA_IMPLEMENTATION
 | |
| #pragma implementation				// gcc: Class implementation
 | |
| #endif
 | |
| 
 | |
| #include "sql_priv.h"
 | |
| #include "unireg.h"
 | |
| #include "rpl_handler.h"
 | |
| #include "sql_cache.h"                   // query_cache, query_cache_*
 | |
| #include "key.h"     // key_copy, key_unpack, key_cmp_if_same, key_cmp
 | |
| #include "sql_table.h"                   // build_table_filename
 | |
| #include "sql_parse.h"                          // check_stack_overrun
 | |
| #include "sql_acl.h"            // SUPER_ACL
 | |
| #include "sql_base.h"           // free_io_cache
 | |
| #include "discover.h"           // writefrm
 | |
| #include "log_event.h"          // *_rows_log_event
 | |
| #include "rpl_filter.h"
 | |
| #include <myisampack.h>
 | |
| #include "transaction.h"
 | |
| #include <errno.h>
 | |
| #include "probes_mysql.h"
 | |
| #include "debug_sync.h"         // DEBUG_SYNC
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
| #include "ha_partition.h"
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /*
 | |
|   While we have legacy_db_type, we have this array to
 | |
|   check for dups and to find handlerton from legacy_db_type.
 | |
|   Remove when legacy_db_type is finally gone
 | |
| */
 | |
| st_plugin_int *hton2plugin[MAX_HA];
 | |
| 
 | |
| static handlerton *installed_htons[128];
 | |
| 
 | |
| #define BITMAP_STACKBUF_SIZE (128/8)
 | |
| 
 | |
| KEY_CREATE_INFO default_key_create_info=
 | |
|   { HA_KEY_ALG_UNDEF, 0, {NullS, 0}, {NullS, 0} };
 | |
| 
 | |
| /* number of entries in handlertons[] */
 | |
| ulong total_ha= 0;
 | |
| /* number of storage engines (from handlertons[]) that support 2pc */
 | |
| ulong total_ha_2pc= 0;
 | |
| /* size of savepoint storage area (see ha_init) */
 | |
| ulong savepoint_alloc_size= 0;
 | |
| 
 | |
| static const LEX_STRING sys_table_aliases[]=
 | |
| {
 | |
|   { C_STRING_WITH_LEN("INNOBASE") },  { C_STRING_WITH_LEN("INNODB") },
 | |
|   { C_STRING_WITH_LEN("NDB") },       { C_STRING_WITH_LEN("NDBCLUSTER") },
 | |
|   { C_STRING_WITH_LEN("HEAP") },      { C_STRING_WITH_LEN("MEMORY") },
 | |
|   { C_STRING_WITH_LEN("MERGE") },     { C_STRING_WITH_LEN("MRG_MYISAM") },
 | |
|   {NullS, 0}
 | |
| };
 | |
| 
 | |
| const char *ha_row_type[] = {
 | |
|   "", "FIXED", "DYNAMIC", "COMPRESSED", "REDUNDANT", "COMPACT",
 | |
|   /* Reserved to be "PAGE" in future versions */ "?",
 | |
|   "?","?","?"
 | |
| };
 | |
| 
 | |
| const char *tx_isolation_names[] =
 | |
| { "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ", "SERIALIZABLE",
 | |
|   NullS};
 | |
| TYPELIB tx_isolation_typelib= {array_elements(tx_isolation_names)-1,"",
 | |
| 			       tx_isolation_names, NULL};
 | |
| 
 | |
| static TYPELIB known_extensions= {0,"known_exts", NULL, NULL};
 | |
| uint known_extensions_id= 0;
 | |
| 
 | |
| /**
 | |
|   Database name that hold most of mysqld system tables.
 | |
|   Current code assumes that, there exists only some
 | |
|   specific "database name" designated as system database.
 | |
| */
 | |
| const char* mysqld_system_database= "mysql";
 | |
| 
 | |
| // System tables that belong to mysqld_system_database.
 | |
| st_system_tablename mysqld_system_tables[]= {
 | |
|   {mysqld_system_database, "db"},
 | |
|   {mysqld_system_database, "user"},
 | |
|   {mysqld_system_database, "host"},
 | |
|   {mysqld_system_database, "func"},
 | |
|   {mysqld_system_database, "proc"},
 | |
|   {mysqld_system_database, "event"},
 | |
|   {mysqld_system_database, "plugin"},
 | |
|   {mysqld_system_database, "servers"},
 | |
|   {mysqld_system_database, "procs_priv"},
 | |
|   {mysqld_system_database, "tables_priv"},
 | |
|   {mysqld_system_database, "proxies_priv"},
 | |
|   {mysqld_system_database, "columns_priv"},
 | |
|   {mysqld_system_database, "time_zone"},
 | |
|   {mysqld_system_database, "time_zone_name"},
 | |
|   {mysqld_system_database, "time_zone_leap_second"},
 | |
|   {mysqld_system_database, "time_zone_transition"},
 | |
|   {mysqld_system_database, "time_zone_transition_type"},
 | |
|   {mysqld_system_database, "help_category"},
 | |
|   {mysqld_system_database, "help_keyword"},
 | |
|   {mysqld_system_database, "help_relation"},
 | |
|   {mysqld_system_database, "help_topic"},
 | |
|   {(const char *)NULL, (const char *)NULL} /* This must be at the end */
 | |
| };
 | |
| 
 | |
| /**
 | |
|   This static pointer holds list of system databases from SQL layer and
 | |
|   various SE's. The required memory is allocated once, and never freed.
 | |
| */
 | |
| static const char **known_system_databases= NULL;
 | |
| static const char **ha_known_system_databases();
 | |
| 
 | |
| // Called for each SE to get SE specific system database.
 | |
| static my_bool system_databases_handlerton(THD *unused, plugin_ref plugin,
 | |
|                                            void *arg);
 | |
| 
 | |
| // Called for each SE to check if given db.table_name is a system table.
 | |
| static my_bool check_engine_system_table_handlerton(THD *unused,
 | |
|                                                     plugin_ref plugin,
 | |
|                                                     void *arg);
 | |
| 
 | |
| /**
 | |
|   Structure used by SE during check for system table.
 | |
|   This structure is passed to each SE handlerton and the status (OUT param)
 | |
|   is collected.
 | |
| */
 | |
| struct st_sys_tbl_chk_params
 | |
| {
 | |
|   const char *db;                             // IN param
 | |
|   const char *table_name;                     // IN param
 | |
|   bool is_sql_layer_system_table;             // IN param
 | |
|   legacy_db_type db_type;                     // IN param
 | |
| 
 | |
|   enum enum_sys_tbl_chk_status
 | |
|   {
 | |
|     // db.table_name is not a supported system table.
 | |
|     NOT_KNOWN_SYSTEM_TABLE,
 | |
|     /*
 | |
|       db.table_name is a system table,
 | |
|       but may not be supported by SE.
 | |
|     */
 | |
|     KNOWN_SYSTEM_TABLE,
 | |
|     /*
 | |
|       db.table_name is a system table,
 | |
|       and is supported by SE.
 | |
|     */
 | |
|     SUPPORTED_SYSTEM_TABLE
 | |
|   }status;                                    // OUT param
 | |
| };
 | |
| 
 | |
| 
 | |
| static plugin_ref ha_default_plugin(THD *thd)
 | |
| {
 | |
|   if (thd->variables.table_plugin)
 | |
|     return thd->variables.table_plugin;
 | |
|   return my_plugin_lock(thd, &global_system_variables.table_plugin);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   Return the default storage engine handlerton for thread
 | |
| 
 | |
|   SYNOPSIS
 | |
|     ha_default_handlerton(thd)
 | |
|     thd         current thread
 | |
| 
 | |
|   RETURN
 | |
|     pointer to handlerton
 | |
| */
 | |
| handlerton *ha_default_handlerton(THD *thd)
 | |
| {
 | |
|   plugin_ref plugin= ha_default_plugin(thd);
 | |
|   DBUG_ASSERT(plugin);
 | |
|   handlerton *hton= plugin_data(plugin, handlerton*);
 | |
|   DBUG_ASSERT(hton);
 | |
|   return hton;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   Return the storage engine handlerton for the supplied name
 | |
|   
 | |
|   SYNOPSIS
 | |
|     ha_resolve_by_name(thd, name)
 | |
|     thd         current thread
 | |
|     name        name of storage engine
 | |
|   
 | |
|   RETURN
 | |
|     pointer to storage engine plugin handle
 | |
| */
 | |
| plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name)
 | |
| {
 | |
|   const LEX_STRING *table_alias;
 | |
|   plugin_ref plugin;
 | |
| 
 | |
| redo:
 | |
|   /* my_strnncoll is a macro and gcc doesn't do early expansion of macro */
 | |
|   if (thd && !my_charset_latin1.coll->strnncoll(&my_charset_latin1,
 | |
|                            (const uchar *)name->str, name->length,
 | |
|                            (const uchar *)STRING_WITH_LEN("DEFAULT"), 0))
 | |
|     return ha_default_plugin(thd);
 | |
| 
 | |
|   if ((plugin= my_plugin_lock_by_name(thd, name, MYSQL_STORAGE_ENGINE_PLUGIN)))
 | |
|   {
 | |
|     handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|     if (!(hton->flags & HTON_NOT_USER_SELECTABLE))
 | |
|       return plugin;
 | |
|       
 | |
|     /*
 | |
|       unlocking plugin immediately after locking is relatively low cost.
 | |
|     */
 | |
|     plugin_unlock(thd, plugin);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     We check for the historical aliases.
 | |
|   */
 | |
|   for (table_alias= sys_table_aliases; table_alias->str; table_alias+= 2)
 | |
|   {
 | |
|     if (!my_strnncoll(&my_charset_latin1,
 | |
|                       (const uchar *)name->str, name->length,
 | |
|                       (const uchar *)table_alias->str, table_alias->length))
 | |
|     {
 | |
|       name= table_alias + 1;
 | |
|       goto redo;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| plugin_ref ha_lock_engine(THD *thd, const handlerton *hton)
 | |
| {
 | |
|   if (hton)
 | |
|   {
 | |
|     st_plugin_int **plugin= hton2plugin + hton->slot;
 | |
|     
 | |
| #ifdef DBUG_OFF
 | |
|     return my_plugin_lock(thd, plugin);
 | |
| #else
 | |
|     return my_plugin_lock(thd, &plugin);
 | |
| #endif
 | |
|   }
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| handlerton *ha_resolve_by_legacy_type(THD *thd, enum legacy_db_type db_type)
 | |
| {
 | |
|   plugin_ref plugin;
 | |
|   switch (db_type) {
 | |
|   case DB_TYPE_DEFAULT:
 | |
|     return ha_default_handlerton(thd);
 | |
|   default:
 | |
|     if (db_type > DB_TYPE_UNKNOWN && db_type < DB_TYPE_DEFAULT &&
 | |
|         (plugin= ha_lock_engine(thd, installed_htons[db_type])))
 | |
|       return plugin_data(plugin, handlerton*);
 | |
|     /* fall through */
 | |
|   case DB_TYPE_UNKNOWN:
 | |
|     return NULL;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Use other database handler if databasehandler is not compiled in.
 | |
| */
 | |
| handlerton *ha_checktype(THD *thd, enum legacy_db_type database_type,
 | |
|                           bool no_substitute, bool report_error)
 | |
| {
 | |
|   handlerton *hton= ha_resolve_by_legacy_type(thd, database_type);
 | |
|   if (ha_storage_engine_is_enabled(hton))
 | |
|     return hton;
 | |
| 
 | |
|   if (no_substitute)
 | |
|   {
 | |
|     if (report_error)
 | |
|     {
 | |
|       const char *engine_name= ha_resolve_storage_engine_name(hton);
 | |
|       my_error(ER_FEATURE_DISABLED,MYF(0),engine_name,engine_name);
 | |
|     }
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   RUN_HOOK(transaction, after_rollback, (thd, FALSE));
 | |
| 
 | |
|   switch (database_type) {
 | |
|   case DB_TYPE_MRG_ISAM:
 | |
|     return ha_resolve_by_legacy_type(thd, DB_TYPE_MRG_MYISAM);
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   return ha_default_handlerton(thd);
 | |
| } /* ha_checktype */
 | |
| 
 | |
| 
 | |
| handler *get_new_handler(TABLE_SHARE *share, MEM_ROOT *alloc,
 | |
|                          handlerton *db_type)
 | |
| {
 | |
|   handler *file;
 | |
|   DBUG_ENTER("get_new_handler");
 | |
|   DBUG_PRINT("enter", ("alloc: 0x%lx", (long) alloc));
 | |
| 
 | |
|   if (db_type && db_type->state == SHOW_OPTION_YES && db_type->create)
 | |
|   {
 | |
|     if ((file= db_type->create(db_type, share, alloc)))
 | |
|       file->init();
 | |
|     DBUG_RETURN(file);
 | |
|   }
 | |
|   /*
 | |
|     Try the default table type
 | |
|     Here the call to current_thd() is ok as we call this function a lot of
 | |
|     times but we enter this branch very seldom.
 | |
|   */
 | |
|   DBUG_RETURN(get_new_handler(share, alloc, ha_default_handlerton(current_thd)));
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
| handler *get_ha_partition(partition_info *part_info)
 | |
| {
 | |
|   ha_partition *partition;
 | |
|   DBUG_ENTER("get_ha_partition");
 | |
|   if ((partition= new ha_partition(partition_hton, part_info)))
 | |
|   {
 | |
|     if (partition->initialize_partition(current_thd->mem_root))
 | |
|     {
 | |
|       delete partition;
 | |
|       partition= 0;
 | |
|     }
 | |
|     else
 | |
|       partition->init();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     my_error(ER_OUTOFMEMORY, MYF(0), static_cast<int>(sizeof(ha_partition)));
 | |
|   }
 | |
|   DBUG_RETURN(((handler*) partition));
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| static const char **handler_errmsgs;
 | |
| 
 | |
| C_MODE_START
 | |
| static const char **get_handler_errmsgs()
 | |
| {
 | |
|   return handler_errmsgs;
 | |
| }
 | |
| C_MODE_END
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Register handler error messages for use with my_error().
 | |
| 
 | |
|   @retval
 | |
|     0           OK
 | |
|   @retval
 | |
|     !=0         Error
 | |
| */
 | |
| 
 | |
| int ha_init_errors(void)
 | |
| {
 | |
| #define SETMSG(nr, msg) handler_errmsgs[(nr) - HA_ERR_FIRST]= (msg)
 | |
| 
 | |
|   /* Allocate a pointer array for the error message strings. */
 | |
|   /* Zerofill it to avoid uninitialized gaps. */
 | |
|   if (! (handler_errmsgs= (const char**) my_malloc(HA_ERR_ERRORS * sizeof(char*),
 | |
|                                                    MYF(MY_WME | MY_ZEROFILL))))
 | |
|     return 1;
 | |
| 
 | |
|   /* Set the dedicated error messages. */
 | |
|   SETMSG(HA_ERR_KEY_NOT_FOUND,          ER_DEFAULT(ER_KEY_NOT_FOUND));
 | |
|   SETMSG(HA_ERR_FOUND_DUPP_KEY,         ER_DEFAULT(ER_DUP_KEY));
 | |
|   SETMSG(HA_ERR_RECORD_CHANGED,         "Update wich is recoverable");
 | |
|   SETMSG(HA_ERR_WRONG_INDEX,            "Wrong index given to function");
 | |
|   SETMSG(HA_ERR_CRASHED,                ER_DEFAULT(ER_NOT_KEYFILE));
 | |
|   SETMSG(HA_ERR_WRONG_IN_RECORD,        ER_DEFAULT(ER_CRASHED_ON_USAGE));
 | |
|   SETMSG(HA_ERR_OUT_OF_MEM,             "Table handler out of memory");
 | |
|   SETMSG(HA_ERR_NOT_A_TABLE,            "Incorrect file format '%.64s'");
 | |
|   SETMSG(HA_ERR_WRONG_COMMAND,          "Command not supported");
 | |
|   SETMSG(HA_ERR_OLD_FILE,               ER_DEFAULT(ER_OLD_KEYFILE));
 | |
|   SETMSG(HA_ERR_NO_ACTIVE_RECORD,       "No record read in update");
 | |
|   SETMSG(HA_ERR_RECORD_DELETED,         "Intern record deleted");
 | |
|   SETMSG(HA_ERR_RECORD_FILE_FULL,       ER_DEFAULT(ER_RECORD_FILE_FULL));
 | |
|   SETMSG(HA_ERR_INDEX_FILE_FULL,        "No more room in index file '%.64s'");
 | |
|   SETMSG(HA_ERR_END_OF_FILE,            "End in next/prev/first/last");
 | |
|   SETMSG(HA_ERR_UNSUPPORTED,            ER_DEFAULT(ER_ILLEGAL_HA));
 | |
|   SETMSG(HA_ERR_TO_BIG_ROW,             "Too big row");
 | |
|   SETMSG(HA_WRONG_CREATE_OPTION,        "Wrong create option");
 | |
|   SETMSG(HA_ERR_FOUND_DUPP_UNIQUE,      ER_DEFAULT(ER_DUP_UNIQUE));
 | |
|   SETMSG(HA_ERR_UNKNOWN_CHARSET,        "Can't open charset");
 | |
|   SETMSG(HA_ERR_WRONG_MRG_TABLE_DEF,    ER_DEFAULT(ER_WRONG_MRG_TABLE));
 | |
|   SETMSG(HA_ERR_CRASHED_ON_REPAIR,      ER_DEFAULT(ER_CRASHED_ON_REPAIR));
 | |
|   SETMSG(HA_ERR_CRASHED_ON_USAGE,       ER_DEFAULT(ER_CRASHED_ON_USAGE));
 | |
|   SETMSG(HA_ERR_LOCK_WAIT_TIMEOUT,      ER_DEFAULT(ER_LOCK_WAIT_TIMEOUT));
 | |
|   SETMSG(HA_ERR_LOCK_TABLE_FULL,        ER_DEFAULT(ER_LOCK_TABLE_FULL));
 | |
|   SETMSG(HA_ERR_READ_ONLY_TRANSACTION,  ER_DEFAULT(ER_READ_ONLY_TRANSACTION));
 | |
|   SETMSG(HA_ERR_LOCK_DEADLOCK,          ER_DEFAULT(ER_LOCK_DEADLOCK));
 | |
|   SETMSG(HA_ERR_CANNOT_ADD_FOREIGN,     ER_DEFAULT(ER_CANNOT_ADD_FOREIGN));
 | |
|   SETMSG(HA_ERR_NO_REFERENCED_ROW,      ER_DEFAULT(ER_NO_REFERENCED_ROW_2));
 | |
|   SETMSG(HA_ERR_ROW_IS_REFERENCED,      ER_DEFAULT(ER_ROW_IS_REFERENCED_2));
 | |
|   SETMSG(HA_ERR_NO_SAVEPOINT,           "No savepoint with that name");
 | |
|   SETMSG(HA_ERR_NON_UNIQUE_BLOCK_SIZE,  "Non unique key block size");
 | |
|   SETMSG(HA_ERR_NO_SUCH_TABLE,          "No such table: '%.64s'");
 | |
|   SETMSG(HA_ERR_TABLE_EXIST,            ER_DEFAULT(ER_TABLE_EXISTS_ERROR));
 | |
|   SETMSG(HA_ERR_NO_CONNECTION,          "Could not connect to storage engine");
 | |
|   SETMSG(HA_ERR_TABLE_DEF_CHANGED,      ER_DEFAULT(ER_TABLE_DEF_CHANGED));
 | |
|   SETMSG(HA_ERR_FOREIGN_DUPLICATE_KEY,  "FK constraint would lead to duplicate key");
 | |
|   SETMSG(HA_ERR_TABLE_NEEDS_UPGRADE,    ER_DEFAULT(ER_TABLE_NEEDS_UPGRADE));
 | |
|   SETMSG(HA_ERR_TABLE_READONLY,         ER_DEFAULT(ER_OPEN_AS_READONLY));
 | |
|   SETMSG(HA_ERR_AUTOINC_READ_FAILED,    ER_DEFAULT(ER_AUTOINC_READ_FAILED));
 | |
|   SETMSG(HA_ERR_AUTOINC_ERANGE,         ER_DEFAULT(ER_WARN_DATA_OUT_OF_RANGE));
 | |
|   SETMSG(HA_ERR_TOO_MANY_CONCURRENT_TRXS, ER_DEFAULT(ER_TOO_MANY_CONCURRENT_TRXS));
 | |
|   SETMSG(HA_ERR_INDEX_COL_TOO_LONG,	ER_DEFAULT(ER_INDEX_COLUMN_TOO_LONG));
 | |
|   SETMSG(HA_ERR_INDEX_CORRUPT,		ER_DEFAULT(ER_INDEX_CORRUPT));
 | |
|   SETMSG(HA_ERR_TABLE_IN_FK_CHECK,	ER_DEFAULT(ER_TABLE_IN_FK_CHECK));
 | |
| 
 | |
|   /* Register the error messages for use with my_error(). */
 | |
|   return my_error_register(get_handler_errmsgs, HA_ERR_FIRST, HA_ERR_LAST);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Unregister handler error messages.
 | |
| 
 | |
|   @retval
 | |
|     0           OK
 | |
|   @retval
 | |
|     !=0         Error
 | |
| */
 | |
| static int ha_finish_errors(void)
 | |
| {
 | |
|   const char    **errmsgs;
 | |
| 
 | |
|   /* Allocate a pointer array for the error message strings. */
 | |
|   if (! (errmsgs= my_error_unregister(HA_ERR_FIRST, HA_ERR_LAST)))
 | |
|     return 1;
 | |
|   my_free(errmsgs);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_finalize_handlerton(st_plugin_int *plugin)
 | |
| {
 | |
|   handlerton *hton= (handlerton *)plugin->data;
 | |
|   DBUG_ENTER("ha_finalize_handlerton");
 | |
| 
 | |
|   /* hton can be NULL here, if ha_initialize_handlerton() failed. */
 | |
|   if (!hton)
 | |
|     goto end;
 | |
| 
 | |
|   switch (hton->state)
 | |
|   {
 | |
|   case SHOW_OPTION_NO:
 | |
|   case SHOW_OPTION_DISABLED:
 | |
|     break;
 | |
|   case SHOW_OPTION_YES:
 | |
|     if (installed_htons[hton->db_type] == hton)
 | |
|       installed_htons[hton->db_type]= NULL;
 | |
|     break;
 | |
|   };
 | |
| 
 | |
|   if (hton->panic)
 | |
|     hton->panic(hton, HA_PANIC_CLOSE);
 | |
| 
 | |
|   if (plugin->plugin->deinit)
 | |
|   {
 | |
|     /*
 | |
|       Today we have no defined/special behavior for uninstalling
 | |
|       engine plugins.
 | |
|     */
 | |
|     DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str));
 | |
|     if (plugin->plugin->deinit(NULL))
 | |
|     {
 | |
|       DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
 | |
|                              plugin->name.str));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     In case a plugin is uninstalled and re-installed later, it should
 | |
|     reuse an array slot. Otherwise the number of uninstall/install
 | |
|     cycles would be limited.
 | |
|   */
 | |
|   if (hton->slot != HA_SLOT_UNDEF)
 | |
|   {
 | |
|     /* Make sure we are not unpluging another plugin */
 | |
|     DBUG_ASSERT(hton2plugin[hton->slot] == plugin);
 | |
|     DBUG_ASSERT(hton->slot < MAX_HA);
 | |
|     hton2plugin[hton->slot]= NULL;
 | |
|   }
 | |
| 
 | |
|   my_free(hton);
 | |
| 
 | |
|  end:
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_initialize_handlerton(st_plugin_int *plugin)
 | |
| {
 | |
|   handlerton *hton;
 | |
|   DBUG_ENTER("ha_initialize_handlerton");
 | |
|   DBUG_PRINT("plugin", ("initialize plugin: '%s'", plugin->name.str));
 | |
| 
 | |
|   hton= (handlerton *)my_malloc(sizeof(handlerton),
 | |
|                                 MYF(MY_WME | MY_ZEROFILL));
 | |
| 
 | |
|   if (hton == NULL)
 | |
|   {
 | |
|     sql_print_error("Unable to allocate memory for plugin '%s' handlerton.",
 | |
|                     plugin->name.str);
 | |
|     goto err_no_hton_memory;
 | |
|   }
 | |
| 
 | |
|   hton->slot= HA_SLOT_UNDEF;
 | |
|   /* Historical Requirement */
 | |
|   plugin->data= hton; // shortcut for the future
 | |
|   if (plugin->plugin->init && plugin->plugin->init(hton))
 | |
|   {
 | |
|     sql_print_error("Plugin '%s' init function returned error.",
 | |
|                     plugin->name.str);
 | |
|     goto err;  
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     the switch below and hton->state should be removed when
 | |
|     command-line options for plugins will be implemented
 | |
|   */
 | |
|   switch (hton->state) {
 | |
|   case SHOW_OPTION_NO:
 | |
|     break;
 | |
|   case SHOW_OPTION_YES:
 | |
|     {
 | |
|       uint tmp;
 | |
|       ulong fslot;
 | |
|       /* now check the db_type for conflict */
 | |
|       if (hton->db_type <= DB_TYPE_UNKNOWN ||
 | |
|           hton->db_type >= DB_TYPE_DEFAULT ||
 | |
|           installed_htons[hton->db_type])
 | |
|       {
 | |
|         int idx= (int) DB_TYPE_FIRST_DYNAMIC;
 | |
| 
 | |
|         while (idx < (int) DB_TYPE_DEFAULT && installed_htons[idx])
 | |
|           idx++;
 | |
| 
 | |
|         if (idx == (int) DB_TYPE_DEFAULT)
 | |
|         {
 | |
|           sql_print_warning("Too many storage engines!");
 | |
|           goto err_deinit;
 | |
|         }
 | |
|         if (hton->db_type != DB_TYPE_UNKNOWN)
 | |
|           sql_print_warning("Storage engine '%s' has conflicting typecode. "
 | |
|                             "Assigning value %d.", plugin->plugin->name, idx);
 | |
|         hton->db_type= (enum legacy_db_type) idx;
 | |
|       }
 | |
| 
 | |
|       /*
 | |
|         In case a plugin is uninstalled and re-installed later, it should
 | |
|         reuse an array slot. Otherwise the number of uninstall/install
 | |
|         cycles would be limited. So look for a free slot.
 | |
|       */
 | |
|       DBUG_PRINT("plugin", ("total_ha: %lu", total_ha));
 | |
|       for (fslot= 0; fslot < total_ha; fslot++)
 | |
|       {
 | |
|         if (!hton2plugin[fslot])
 | |
|           break;
 | |
|       }
 | |
|       if (fslot < total_ha)
 | |
|         hton->slot= fslot;
 | |
|       else
 | |
|       {
 | |
|         if (total_ha >= MAX_HA)
 | |
|         {
 | |
|           sql_print_error("Too many plugins loaded. Limit is %lu. "
 | |
|                           "Failed on '%s'", (ulong) MAX_HA, plugin->name.str);
 | |
|           goto err_deinit;
 | |
|         }
 | |
|         hton->slot= total_ha++;
 | |
|       }
 | |
|       installed_htons[hton->db_type]= hton;
 | |
|       tmp= hton->savepoint_offset;
 | |
|       hton->savepoint_offset= savepoint_alloc_size;
 | |
|       savepoint_alloc_size+= tmp;
 | |
|       hton2plugin[hton->slot]=plugin;
 | |
|       if (hton->prepare)
 | |
|         total_ha_2pc++;
 | |
|       break;
 | |
|     }
 | |
|     /* fall through */
 | |
|   default:
 | |
|     hton->state= SHOW_OPTION_DISABLED;
 | |
|     break;
 | |
|   }
 | |
|   
 | |
|   /* 
 | |
|     This is entirely for legacy. We will create a new "disk based" hton and a 
 | |
|     "memory" hton which will be configurable longterm. We should be able to 
 | |
|     remove partition and myisammrg.
 | |
|   */
 | |
|   switch (hton->db_type) {
 | |
|   case DB_TYPE_HEAP:
 | |
|     heap_hton= hton;
 | |
|     break;
 | |
|   case DB_TYPE_MYISAM:
 | |
|     myisam_hton= hton;
 | |
|     break;
 | |
|   case DB_TYPE_PARTITION_DB:
 | |
|     partition_hton= hton;
 | |
|     break;
 | |
|   default:
 | |
|     break;
 | |
|   };
 | |
| 
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
| err_deinit:
 | |
|   /* 
 | |
|     Let plugin do its inner deinitialization as plugin->init() 
 | |
|     was successfully called before.
 | |
|   */
 | |
|   if (plugin->plugin->deinit)
 | |
|     (void) plugin->plugin->deinit(NULL);
 | |
|           
 | |
| err:
 | |
|   my_free(hton);
 | |
| err_no_hton_memory:
 | |
|   plugin->data= NULL;
 | |
|   DBUG_RETURN(1);
 | |
| }
 | |
| 
 | |
| int ha_init()
 | |
| {
 | |
|   int error= 0;
 | |
|   DBUG_ENTER("ha_init");
 | |
| 
 | |
|   DBUG_ASSERT(total_ha < MAX_HA);
 | |
|   /*
 | |
|     Check if there is a transaction-capable storage engine besides the
 | |
|     binary log (which is considered a transaction-capable storage engine in
 | |
|     counting total_ha)
 | |
|   */
 | |
|   opt_using_transactions= total_ha>(ulong)opt_bin_log;
 | |
|   savepoint_alloc_size+= sizeof(SAVEPOINT);
 | |
| 
 | |
|   /*
 | |
|     Initialize system database name cache.
 | |
|     This cache is used to do a quick check if a given
 | |
|     db.tablename is a system table.
 | |
|   */
 | |
|   known_system_databases= ha_known_system_databases();
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| int ha_end()
 | |
| {
 | |
|   int error= 0;
 | |
|   DBUG_ENTER("ha_end");
 | |
| 
 | |
| 
 | |
|   /* 
 | |
|     This should be eventualy based  on the graceful shutdown flag.
 | |
|     So if flag is equal to HA_PANIC_CLOSE, the deallocate
 | |
|     the errors.
 | |
|   */
 | |
|   if (ha_finish_errors())
 | |
|     error= 1;
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| static my_bool dropdb_handlerton(THD *unused1, plugin_ref plugin,
 | |
|                                  void *path)
 | |
| {
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->drop_database)
 | |
|     hton->drop_database(hton, (char *)path);
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| void ha_drop_database(char* path)
 | |
| {
 | |
|   plugin_foreach(NULL, dropdb_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, path);
 | |
| }
 | |
| 
 | |
| 
 | |
| static my_bool closecon_handlerton(THD *thd, plugin_ref plugin,
 | |
|                                    void *unused)
 | |
| {
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   /*
 | |
|     there's no need to rollback here as all transactions must
 | |
|     be rolled back already
 | |
|   */
 | |
|   if (hton->state == SHOW_OPTION_YES && thd_get_ha_data(thd, hton))
 | |
|   {
 | |
|     if (hton->close_connection)
 | |
|       hton->close_connection(hton, thd);
 | |
|     /* make sure ha_data is reset and ha_data_lock is released */
 | |
|     thd_set_ha_data(thd, hton, NULL);
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @note
 | |
|     don't bother to rollback here, it's done already
 | |
| */
 | |
| void ha_close_connection(THD* thd)
 | |
| {
 | |
|   plugin_foreach(thd, closecon_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, 0);
 | |
| }
 | |
| 
 | |
| /* ========================================================================
 | |
|  ======================= TRANSACTIONS ===================================*/
 | |
| 
 | |
| /**
 | |
|   Transaction handling in the server
 | |
|   ==================================
 | |
| 
 | |
|   In each client connection, MySQL maintains two transactional
 | |
|   states:
 | |
|   - a statement transaction,
 | |
|   - a standard, also called normal transaction.
 | |
| 
 | |
|   Historical note
 | |
|   ---------------
 | |
|   "Statement transaction" is a non-standard term that comes
 | |
|   from the times when MySQL supported BerkeleyDB storage engine.
 | |
| 
 | |
|   First of all, it should be said that in BerkeleyDB auto-commit
 | |
|   mode auto-commits operations that are atomic to the storage
 | |
|   engine itself, such as a write of a record, and are too
 | |
|   high-granular to be atomic from the application perspective
 | |
|   (MySQL). One SQL statement could involve many BerkeleyDB
 | |
|   auto-committed operations and thus BerkeleyDB auto-commit was of
 | |
|   little use to MySQL.
 | |
| 
 | |
|   Secondly, instead of SQL standard savepoints, BerkeleyDB
 | |
|   provided the concept of "nested transactions". In a nutshell,
 | |
|   transactions could be arbitrarily nested, but when the parent
 | |
|   transaction was committed or aborted, all its child (nested)
 | |
|   transactions were handled committed or aborted as well.
 | |
|   Commit of a nested transaction, in turn, made its changes
 | |
|   visible, but not durable: it destroyed the nested transaction,
 | |
|   all its changes would become available to the parent and
 | |
|   currently active nested transactions of this parent.
 | |
| 
 | |
|   So the mechanism of nested transactions was employed to
 | |
|   provide "all or nothing" guarantee of SQL statements
 | |
|   required by the standard.
 | |
|   A nested transaction would be created at start of each SQL
 | |
|   statement, and destroyed (committed or aborted) at statement
 | |
|   end. Such nested transaction was internally referred to as
 | |
|   a "statement transaction" and gave birth to the term.
 | |
| 
 | |
|   <Historical note ends>
 | |
| 
 | |
|   Since then a statement transaction is started for each statement
 | |
|   that accesses transactional tables or uses the binary log.  If
 | |
|   the statement succeeds, the statement transaction is committed.
 | |
|   If the statement fails, the transaction is rolled back. Commits
 | |
|   of statement transactions are not durable -- each such
 | |
|   transaction is nested in the normal transaction, and if the
 | |
|   normal transaction is rolled back, the effects of all enclosed
 | |
|   statement transactions are undone as well.  Technically,
 | |
|   a statement transaction can be viewed as a savepoint which is
 | |
|   maintained automatically in order to make effects of one
 | |
|   statement atomic.
 | |
| 
 | |
|   The normal transaction is started by the user and is ended
 | |
|   usually upon a user request as well. The normal transaction
 | |
|   encloses transactions of all statements issued between
 | |
|   its beginning and its end.
 | |
|   In autocommit mode, the normal transaction is equivalent
 | |
|   to the statement transaction.
 | |
| 
 | |
|   Since MySQL supports PSEA (pluggable storage engine
 | |
|   architecture), more than one transactional engine can be
 | |
|   active at a time. Hence transactions, from the server
 | |
|   point of view, are always distributed. In particular,
 | |
|   transactional state is maintained independently for each
 | |
|   engine. In order to commit a transaction the two phase
 | |
|   commit protocol is employed.
 | |
| 
 | |
|   Not all statements are executed in context of a transaction.
 | |
|   Administrative and status information statements do not modify
 | |
|   engine data, and thus do not start a statement transaction and
 | |
|   also have no effect on the normal transaction. Examples of such
 | |
|   statements are SHOW STATUS and RESET SLAVE.
 | |
| 
 | |
|   Similarly DDL statements are not transactional,
 | |
|   and therefore a transaction is [almost] never started for a DDL
 | |
|   statement. The difference between a DDL statement and a purely
 | |
|   administrative statement though is that a DDL statement always
 | |
|   commits the current transaction before proceeding, if there is
 | |
|   any.
 | |
| 
 | |
|   At last, SQL statements that work with non-transactional
 | |
|   engines also have no effect on the transaction state of the
 | |
|   connection. Even though they are written to the binary log,
 | |
|   and the binary log is, overall, transactional, the writes
 | |
|   are done in "write-through" mode, directly to the binlog
 | |
|   file, followed with a OS cache sync, in other words,
 | |
|   bypassing the binlog undo log (translog).
 | |
|   They do not commit the current normal transaction.
 | |
|   A failure of a statement that uses non-transactional tables
 | |
|   would cause a rollback of the statement transaction, but
 | |
|   in case there no non-transactional tables are used,
 | |
|   no statement transaction is started.
 | |
| 
 | |
|   Data layout
 | |
|   -----------
 | |
| 
 | |
|   The server stores its transaction-related data in
 | |
|   thd->transaction. This structure has two members of type
 | |
|   THD_TRANS. These members correspond to the statement and
 | |
|   normal transactions respectively:
 | |
| 
 | |
|   - thd->transaction.stmt contains a list of engines
 | |
|   that are participating in the given statement
 | |
|   - thd->transaction.all contains a list of engines that
 | |
|   have participated in any of the statement transactions started
 | |
|   within the context of the normal transaction.
 | |
|   Each element of the list contains a pointer to the storage
 | |
|   engine, engine-specific transactional data, and engine-specific
 | |
|   transaction flags.
 | |
| 
 | |
|   In autocommit mode thd->transaction.all is empty.
 | |
|   Instead, data of thd->transaction.stmt is
 | |
|   used to commit/rollback the normal transaction.
 | |
| 
 | |
|   The list of registered engines has a few important properties:
 | |
|   - no engine is registered in the list twice
 | |
|   - engines are present in the list a reverse temporal order --
 | |
|   new participants are always added to the beginning of the list.
 | |
| 
 | |
|   Transaction life cycle
 | |
|   ----------------------
 | |
| 
 | |
|   When a new connection is established, thd->transaction
 | |
|   members are initialized to an empty state.
 | |
|   If a statement uses any tables, all affected engines
 | |
|   are registered in the statement engine list. In
 | |
|   non-autocommit mode, the same engines are registered in
 | |
|   the normal transaction list.
 | |
|   At the end of the statement, the server issues a commit
 | |
|   or a roll back for all engines in the statement list.
 | |
|   At this point transaction flags of an engine, if any, are
 | |
|   propagated from the statement list to the list of the normal
 | |
|   transaction.
 | |
|   When commit/rollback is finished, the statement list is
 | |
|   cleared. It will be filled in again by the next statement,
 | |
|   and emptied again at the next statement's end.
 | |
| 
 | |
|   The normal transaction is committed in a similar way
 | |
|   (by going over all engines in thd->transaction.all list)
 | |
|   but at different times:
 | |
|   - upon COMMIT SQL statement is issued by the user
 | |
|   - implicitly, by the server, at the beginning of a DDL statement
 | |
|   or SET AUTOCOMMIT={0|1} statement.
 | |
| 
 | |
|   The normal transaction can be rolled back as well:
 | |
|   - if the user has requested so, by issuing ROLLBACK SQL
 | |
|   statement
 | |
|   - if one of the storage engines requested a rollback
 | |
|   by setting thd->transaction_rollback_request. This may
 | |
|   happen in case, e.g., when the transaction in the engine was
 | |
|   chosen a victim of the internal deadlock resolution algorithm
 | |
|   and rolled back internally. When such a situation happens, there
 | |
|   is little the server can do and the only option is to rollback
 | |
|   transactions in all other participating engines.  In this case
 | |
|   the rollback is accompanied by an error sent to the user.
 | |
| 
 | |
|   As follows from the use cases above, the normal transaction
 | |
|   is never committed when there is an outstanding statement
 | |
|   transaction. In most cases there is no conflict, since
 | |
|   commits of the normal transaction are issued by a stand-alone
 | |
|   administrative or DDL statement, thus no outstanding statement
 | |
|   transaction of the previous statement exists. Besides,
 | |
|   all statements that manipulate with the normal transaction
 | |
|   are prohibited in stored functions and triggers, therefore
 | |
|   no conflicting situation can occur in a sub-statement either.
 | |
|   The remaining rare cases when the server explicitly has
 | |
|   to commit the statement transaction prior to committing the normal
 | |
|   one cover error-handling scenarios (see for example
 | |
|   SQLCOM_LOCK_TABLES).
 | |
| 
 | |
|   When committing a statement or a normal transaction, the server
 | |
|   either uses the two-phase commit protocol, or issues a commit
 | |
|   in each engine independently. The two-phase commit protocol
 | |
|   is used only if:
 | |
|   - all participating engines support two-phase commit (provide
 | |
|     handlerton::prepare PSEA API call) and
 | |
|   - transactions in at least two engines modify data (i.e. are
 | |
|   not read-only).
 | |
| 
 | |
|   Note that the two phase commit is used for
 | |
|   statement transactions, even though they are not durable anyway.
 | |
|   This is done to ensure logical consistency of data in a multiple-
 | |
|   engine transaction.
 | |
|   For example, imagine that some day MySQL supports unique
 | |
|   constraint checks deferred till the end of statement. In such
 | |
|   case a commit in one of the engines may yield ER_DUP_KEY,
 | |
|   and MySQL should be able to gracefully abort statement
 | |
|   transactions of other participants.
 | |
| 
 | |
|   After the normal transaction has been committed,
 | |
|   thd->transaction.all list is cleared.
 | |
| 
 | |
|   When a connection is closed, the current normal transaction, if
 | |
|   any, is rolled back.
 | |
| 
 | |
|   Roles and responsibilities
 | |
|   --------------------------
 | |
| 
 | |
|   The server has no way to know that an engine participates in
 | |
|   the statement and a transaction has been started
 | |
|   in it unless the engine says so. Thus, in order to be
 | |
|   a part of a transaction, the engine must "register" itself.
 | |
|   This is done by invoking trans_register_ha() server call.
 | |
|   Normally the engine registers itself whenever handler::external_lock()
 | |
|   is called. trans_register_ha() can be invoked many times: if
 | |
|   an engine is already registered, the call does nothing.
 | |
|   In case autocommit is not set, the engine must register itself
 | |
|   twice -- both in the statement list and in the normal transaction
 | |
|   list.
 | |
|   In which list to register is a parameter of trans_register_ha().
 | |
| 
 | |
|   Note, that although the registration interface in itself is
 | |
|   fairly clear, the current usage practice often leads to undesired
 | |
|   effects. E.g. since a call to trans_register_ha() in most engines
 | |
|   is embedded into implementation of handler::external_lock(), some
 | |
|   DDL statements start a transaction (at least from the server
 | |
|   point of view) even though they are not expected to. E.g.
 | |
|   CREATE TABLE does not start a transaction, since
 | |
|   handler::external_lock() is never called during CREATE TABLE. But
 | |
|   CREATE TABLE ... SELECT does, since handler::external_lock() is
 | |
|   called for the table that is being selected from. This has no
 | |
|   practical effects currently, but must be kept in mind
 | |
|   nevertheless.
 | |
| 
 | |
|   Once an engine is registered, the server will do the rest
 | |
|   of the work.
 | |
| 
 | |
|   During statement execution, whenever any of data-modifying
 | |
|   PSEA API methods is used, e.g. handler::write_row() or
 | |
|   handler::update_row(), the read-write flag is raised in the
 | |
|   statement transaction for the involved engine.
 | |
|   Currently All PSEA calls are "traced", and the data can not be
 | |
|   changed in a way other than issuing a PSEA call. Important:
 | |
|   unless this invariant is preserved the server will not know that
 | |
|   a transaction in a given engine is read-write and will not
 | |
|   involve the two-phase commit protocol!
 | |
| 
 | |
|   At the end of a statement, server call trans_commit_stmt is
 | |
|   invoked. This call in turn invokes handlerton::prepare()
 | |
|   for every involved engine. Prepare is followed by a call
 | |
|   to handlerton::commit_one_phase() If a one-phase commit
 | |
|   will suffice, handlerton::prepare() is not invoked and
 | |
|   the server only calls handlerton::commit_one_phase().
 | |
|   At statement commit, the statement-related read-write
 | |
|   engine flag is propagated to the corresponding flag in the
 | |
|   normal transaction.  When the commit is complete, the list
 | |
|   of registered engines is cleared.
 | |
| 
 | |
|   Rollback is handled in a similar fashion.
 | |
| 
 | |
|   Additional notes on DDL and the normal transaction.
 | |
|   ---------------------------------------------------
 | |
| 
 | |
|   DDLs and operations with non-transactional engines
 | |
|   do not "register" in thd->transaction lists, and thus do not
 | |
|   modify the transaction state. Besides, each DDL in
 | |
|   MySQL is prefixed with an implicit normal transaction commit
 | |
|   (a call to trans_commit_implicit()), and thus leaves nothing
 | |
|   to modify.
 | |
|   However, as it has been pointed out with CREATE TABLE .. SELECT,
 | |
|   some DDL statements can start a *new* transaction.
 | |
| 
 | |
|   Behaviour of the server in this case is currently badly
 | |
|   defined.
 | |
|   DDL statements use a form of "semantic" logging
 | |
|   to maintain atomicity: if CREATE TABLE .. SELECT failed,
 | |
|   the newly created table is deleted.
 | |
|   In addition, some DDL statements issue interim transaction
 | |
|   commits: e.g. ALTER TABLE issues a commit after data is copied
 | |
|   from the original table to the internal temporary table. Other
 | |
|   statements, e.g. CREATE TABLE ... SELECT do not always commit
 | |
|   after itself.
 | |
|   And finally there is a group of DDL statements such as
 | |
|   RENAME/DROP TABLE that doesn't start a new transaction
 | |
|   and doesn't commit.
 | |
| 
 | |
|   This diversity makes it hard to say what will happen if
 | |
|   by chance a stored function is invoked during a DDL --
 | |
|   whether any modifications it makes will be committed or not
 | |
|   is not clear. Fortunately, SQL grammar of few DDLs allows
 | |
|   invocation of a stored function.
 | |
| 
 | |
|   A consistent behaviour is perhaps to always commit the normal
 | |
|   transaction after all DDLs, just like the statement transaction
 | |
|   is always committed at the end of all statements.
 | |
| */
 | |
| 
 | |
| /**
 | |
|   Register a storage engine for a transaction.
 | |
| 
 | |
|   Every storage engine MUST call this function when it starts
 | |
|   a transaction or a statement (that is it must be called both for the
 | |
|   "beginning of transaction" and "beginning of statement").
 | |
|   Only storage engines registered for the transaction/statement
 | |
|   will know when to commit/rollback it.
 | |
| 
 | |
|   @note
 | |
|     trans_register_ha is idempotent - storage engine may register many
 | |
|     times per transaction.
 | |
| 
 | |
| */
 | |
| void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
 | |
| {
 | |
|   THD_TRANS *trans;
 | |
|   Ha_trx_info *ha_info;
 | |
|   DBUG_ENTER("trans_register_ha");
 | |
|   DBUG_PRINT("enter",("%s", all ? "all" : "stmt"));
 | |
| 
 | |
|   if (all)
 | |
|   {
 | |
|     trans= &thd->transaction.all;
 | |
|     thd->server_status|= SERVER_STATUS_IN_TRANS;
 | |
|   }
 | |
|   else
 | |
|     trans= &thd->transaction.stmt;
 | |
| 
 | |
|   ha_info= thd->ha_data[ht_arg->slot].ha_info + static_cast<unsigned>(all);
 | |
| 
 | |
|   if (ha_info->is_started())
 | |
|     DBUG_VOID_RETURN; /* already registered, return */
 | |
| 
 | |
|   ha_info->register_ha(trans, ht_arg);
 | |
| 
 | |
|   trans->no_2pc|=(ht_arg->prepare==0);
 | |
|   if (thd->transaction.xid_state.xid.is_null())
 | |
|     thd->transaction.xid_state.xid.set(thd->query_id);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @retval
 | |
|     0   ok
 | |
|   @retval
 | |
|     1   error, transaction was rolled back
 | |
| */
 | |
| int ha_prepare(THD *thd)
 | |
| {
 | |
|   int error=0, all=1;
 | |
|   THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
 | |
|   Ha_trx_info *ha_info= trans->ha_list;
 | |
|   DBUG_ENTER("ha_prepare");
 | |
| 
 | |
|   if (ha_info)
 | |
|   {
 | |
|     for (; ha_info; ha_info= ha_info->next())
 | |
|     {
 | |
|       int err;
 | |
|       handlerton *ht= ha_info->ht();
 | |
|       status_var_increment(thd->status_var.ha_prepare_count);
 | |
|       if (ht->prepare)
 | |
|       {
 | |
|         if ((err= ht->prepare(ht, thd, all)))
 | |
|         {
 | |
|           my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
 | |
|           ha_rollback_trans(thd, all);
 | |
|           error=1;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
 | |
|                             ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
 | |
|                             ha_resolve_storage_engine_name(ht));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Check if we can skip the two-phase commit.
 | |
| 
 | |
|   A helper function to evaluate if two-phase commit is mandatory.
 | |
|   As a side effect, propagates the read-only/read-write flags
 | |
|   of the statement transaction to its enclosing normal transaction.
 | |
|   
 | |
|   If we have at least two engines with read-write changes we must
 | |
|   run a two-phase commit. Otherwise we can run several independent
 | |
|   commits as the only transactional engine has read-write changes
 | |
|   and others are read-only.
 | |
| 
 | |
|   @retval   0   All engines are read-only.
 | |
|   @retval   1   We have the only engine with read-write changes.
 | |
|   @retval   >1  More than one engine have read-write changes.
 | |
|                 Note: return value might NOT be the exact number of
 | |
|                 engines with read-write changes.
 | |
| */
 | |
| 
 | |
| static
 | |
| uint
 | |
| ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list,
 | |
|                                     bool all)
 | |
| {
 | |
|   /* The number of storage engines that have actual changes. */
 | |
|   unsigned rw_ha_count= 0;
 | |
|   Ha_trx_info *ha_info;
 | |
| 
 | |
|   for (ha_info= ha_list; ha_info; ha_info= ha_info->next())
 | |
|   {
 | |
|     if (ha_info->is_trx_read_write())
 | |
|       ++rw_ha_count;
 | |
| 
 | |
|     if (! all)
 | |
|     {
 | |
|       Ha_trx_info *ha_info_all= &thd->ha_data[ha_info->ht()->slot].ha_info[1];
 | |
|       DBUG_ASSERT(ha_info != ha_info_all);
 | |
|       /*
 | |
|         Merge read-only/read-write information about statement
 | |
|         transaction to its enclosing normal transaction. Do this
 | |
|         only if in a real transaction -- that is, if we know
 | |
|         that ha_info_all is registered in thd->transaction.all.
 | |
|         Since otherwise we only clutter the normal transaction flags.
 | |
|       */
 | |
|       if (ha_info_all->is_started()) /* FALSE if autocommit. */
 | |
|         ha_info_all->coalesce_trx_with(ha_info);
 | |
|     }
 | |
|     else if (rw_ha_count > 1)
 | |
|     {
 | |
|       /*
 | |
|         It is a normal transaction, so we don't need to merge read/write
 | |
|         information up, and the need for two-phase commit has been
 | |
|         already established. Break the loop prematurely.
 | |
|       */
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return rw_ha_count;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @retval
 | |
|     0   ok
 | |
|   @retval
 | |
|     1   transaction was rolled back
 | |
|   @retval
 | |
|     2   error during commit, data may be inconsistent
 | |
| 
 | |
|   @todo
 | |
|     Since we don't support nested statement transactions in 5.0,
 | |
|     we can't commit or rollback stmt transactions while we are inside
 | |
|     stored functions or triggers. So we simply do nothing now.
 | |
|     TODO: This should be fixed in later ( >= 5.1) releases.
 | |
| */
 | |
| int ha_commit_trans(THD *thd, bool all)
 | |
| {
 | |
|   int error= 0, cookie= 0;
 | |
|   /*
 | |
|     'all' means that this is either an explicit commit issued by
 | |
|     user, or an implicit commit issued by a DDL.
 | |
|   */
 | |
|   THD_TRANS *trans= all ? &thd->transaction.all : &thd->transaction.stmt;
 | |
|   /*
 | |
|     "real" is a nick name for a transaction for which a commit will
 | |
|     make persistent changes. E.g. a 'stmt' transaction inside a 'all'
 | |
|     transation is not 'real': even though it's possible to commit it,
 | |
|     the changes are not durable as they might be rolled back if the
 | |
|     enclosing 'all' transaction is rolled back.
 | |
|   */
 | |
|   bool is_real_trans= all || thd->transaction.all.ha_list == 0;
 | |
|   Ha_trx_info *ha_info= trans->ha_list;
 | |
|   my_xid xid= thd->transaction.xid_state.xid.get_my_xid();
 | |
|   DBUG_ENTER("ha_commit_trans");
 | |
| 
 | |
|   /*
 | |
|     We must not commit the normal transaction if a statement
 | |
|     transaction is pending. Otherwise statement transaction
 | |
|     flags will not get propagated to its normal transaction's
 | |
|     counterpart.
 | |
|   */
 | |
|   DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
 | |
|               trans == &thd->transaction.stmt);
 | |
| 
 | |
|   if (thd->in_sub_stmt)
 | |
|   {
 | |
|     DBUG_ASSERT(0);
 | |
|     /*
 | |
|       Since we don't support nested statement transactions in 5.0,
 | |
|       we can't commit or rollback stmt transactions while we are inside
 | |
|       stored functions or triggers. So we simply do nothing now.
 | |
|       TODO: This should be fixed in later ( >= 5.1) releases.
 | |
|     */
 | |
|     if (!all)
 | |
|       DBUG_RETURN(0);
 | |
|     /*
 | |
|       We assume that all statements which commit or rollback main transaction
 | |
|       are prohibited inside of stored functions or triggers. So they should
 | |
|       bail out with error even before ha_commit_trans() call. To be 100% safe
 | |
|       let us throw error in non-debug builds.
 | |
|     */
 | |
|     my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
 | |
|     DBUG_RETURN(2);
 | |
|   }
 | |
| 
 | |
|   if (ha_info)
 | |
|   {
 | |
|     uint rw_ha_count;
 | |
|     bool rw_trans;
 | |
|     MDL_request mdl_request;
 | |
| 
 | |
|     DBUG_EXECUTE_IF("crash_commit_before", DBUG_SUICIDE(););
 | |
| 
 | |
|     /* Close all cursors that can not survive COMMIT */
 | |
|     if (is_real_trans)                          /* not a statement commit */
 | |
|       thd->stmt_map.close_transient_cursors();
 | |
| 
 | |
|     rw_ha_count= ha_check_and_coalesce_trx_read_only(thd, ha_info, all);
 | |
|     /* rw_trans is TRUE when we in a transaction changing data */
 | |
|     rw_trans= is_real_trans && (rw_ha_count > 0);
 | |
| 
 | |
|     if (rw_trans)
 | |
|     {
 | |
|       /*
 | |
|         Acquire a metadata lock which will ensure that COMMIT is blocked
 | |
|         by an active FLUSH TABLES WITH READ LOCK (and vice versa:
 | |
|         COMMIT in progress blocks FTWRL).
 | |
| 
 | |
|         We allow the owner of FTWRL to COMMIT; we assume that it knows
 | |
|         what it does.
 | |
|       */
 | |
|       mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
 | |
|                        MDL_EXPLICIT);
 | |
| 
 | |
|       if (thd->mdl_context.acquire_lock(&mdl_request,
 | |
|                                         thd->variables.lock_wait_timeout))
 | |
|       {
 | |
|         ha_rollback_trans(thd, all);
 | |
|         DBUG_RETURN(1);
 | |
|       }
 | |
| 
 | |
|       DEBUG_SYNC(thd, "ha_commit_trans_after_acquire_commit_lock");
 | |
|     }
 | |
| 
 | |
|     if (rw_trans &&
 | |
|         opt_readonly &&
 | |
|         !(thd->security_ctx->master_access & SUPER_ACL) &&
 | |
|         !thd->slave_thread)
 | |
|     {
 | |
|       my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
 | |
|       ha_rollback_trans(thd, all);
 | |
|       error= 1;
 | |
|       goto end;
 | |
|     }
 | |
| 
 | |
|     if (!trans->no_2pc && (rw_ha_count > 1))
 | |
|     {
 | |
|       for (; ha_info && !error; ha_info= ha_info->next())
 | |
|       {
 | |
|         int err;
 | |
|         handlerton *ht= ha_info->ht();
 | |
|         /*
 | |
|           Do not call two-phase commit if this particular
 | |
|           transaction is read-only. This allows for simpler
 | |
|           implementation in engines that are always read-only.
 | |
|         */
 | |
|         if (! ha_info->is_trx_read_write())
 | |
|           continue;
 | |
|         /*
 | |
|           Sic: we know that prepare() is not NULL since otherwise
 | |
|           trans->no_2pc would have been set.
 | |
|         */
 | |
|         if ((err= ht->prepare(ht, thd, all)))
 | |
|         {
 | |
|           my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
 | |
|           error= 1;
 | |
|         }
 | |
|         status_var_increment(thd->status_var.ha_prepare_count);
 | |
|       }
 | |
|       DBUG_EXECUTE_IF("crash_commit_after_prepare", DBUG_SUICIDE(););
 | |
|       if (error || (is_real_trans && xid &&
 | |
|                     (error= !(cookie= tc_log->log_xid(thd, xid)))))
 | |
|       {
 | |
|         ha_rollback_trans(thd, all);
 | |
|         error= 1;
 | |
|         goto end;
 | |
|       }
 | |
|       DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_SUICIDE(););
 | |
|     }
 | |
|     error=ha_commit_one_phase(thd, all) ? (cookie ? 2 : 1) : 0;
 | |
|     DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_SUICIDE(););
 | |
|     if (cookie)
 | |
|       if(tc_log->unlog(cookie, xid))
 | |
|       {
 | |
|         error= 2;
 | |
|         goto end;
 | |
|       }
 | |
|     DBUG_EXECUTE_IF("crash_commit_after", DBUG_SUICIDE(););
 | |
|     RUN_HOOK(transaction, after_commit, (thd, FALSE));
 | |
| end:
 | |
|     if (rw_trans && mdl_request.ticket)
 | |
|     {
 | |
|       /*
 | |
|         We do not always immediately release transactional locks
 | |
|         after ha_commit_trans() (see uses of ha_enable_transaction()),
 | |
|         thus we release the commit blocker lock as soon as it's
 | |
|         not needed.
 | |
|       */
 | |
|       thd->mdl_context.release_lock(mdl_request.ticket);
 | |
|     }
 | |
|   }
 | |
|   /* Free resources and perform other cleanup even for 'empty' transactions. */
 | |
|   else if (is_real_trans)
 | |
|     thd->transaction.cleanup();
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @note
 | |
|   This function does not care about global read lock. A caller should.
 | |
| 
 | |
|   @param[in]  all  Is set in case of explicit commit
 | |
|                    (COMMIT statement), or implicit commit
 | |
|                    issued by DDL. Is not set when called
 | |
|                    at the end of statement, even if
 | |
|                    autocommit=1.
 | |
| */
 | |
| 
 | |
| int ha_commit_one_phase(THD *thd, bool all)
 | |
| {
 | |
|   int error=0;
 | |
|   THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
 | |
|   /*
 | |
|     "real" is a nick name for a transaction for which a commit will
 | |
|     make persistent changes. E.g. a 'stmt' transaction inside a 'all'
 | |
|     transaction is not 'real': even though it's possible to commit it,
 | |
|     the changes are not durable as they might be rolled back if the
 | |
|     enclosing 'all' transaction is rolled back.
 | |
|     We establish the value of 'is_real_trans' by checking
 | |
|     if it's an explicit COMMIT/BEGIN statement, or implicit
 | |
|     commit issued by DDL (all == TRUE), or if we're running
 | |
|     in autocommit mode (it's only in the autocommit mode
 | |
|     ha_commit_one_phase() can be called with an empty
 | |
|     transaction.all.ha_list, see why in trans_register_ha()).
 | |
|   */
 | |
|   bool is_real_trans=all || thd->transaction.all.ha_list == 0;
 | |
|   Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
 | |
|   DBUG_ENTER("ha_commit_one_phase");
 | |
| 
 | |
|   if (ha_info)
 | |
|   {
 | |
|     for (; ha_info; ha_info= ha_info_next)
 | |
|     {
 | |
|       int err;
 | |
|       handlerton *ht= ha_info->ht();
 | |
|       if ((err= ht->commit(ht, thd, all)))
 | |
|       {
 | |
|         my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
 | |
|         error=1;
 | |
|       }
 | |
|       status_var_increment(thd->status_var.ha_commit_count);
 | |
|       ha_info_next= ha_info->next();
 | |
|       ha_info->reset(); /* keep it conveniently zero-filled */
 | |
|     }
 | |
|     trans->ha_list= 0;
 | |
|     trans->no_2pc=0;
 | |
|     if (all)
 | |
|     {
 | |
| #ifdef HAVE_QUERY_CACHE
 | |
|       if (thd->transaction.changed_tables)
 | |
|         query_cache.invalidate(thd->transaction.changed_tables);
 | |
| #endif
 | |
|     }
 | |
|   }
 | |
|   /* Free resources and perform other cleanup even for 'empty' transactions. */
 | |
|   if (is_real_trans)
 | |
|     thd->transaction.cleanup();
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_rollback_trans(THD *thd, bool all)
 | |
| {
 | |
|   int error=0;
 | |
|   THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
 | |
|   Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
 | |
|   /*
 | |
|     "real" is a nick name for a transaction for which a commit will
 | |
|     make persistent changes. E.g. a 'stmt' transaction inside a 'all'
 | |
|     transaction is not 'real': even though it's possible to commit it,
 | |
|     the changes are not durable as they might be rolled back if the
 | |
|     enclosing 'all' transaction is rolled back.
 | |
|     We establish the value of 'is_real_trans' by checking
 | |
|     if it's an explicit COMMIT or BEGIN statement, or implicit
 | |
|     commit issued by DDL (in these cases all == TRUE),
 | |
|     or if we're running in autocommit mode (it's only in the autocommit mode
 | |
|     ha_commit_one_phase() is called with an empty
 | |
|     transaction.all.ha_list, see why in trans_register_ha()).
 | |
|   */
 | |
|   bool is_real_trans=all || thd->transaction.all.ha_list == 0;
 | |
|   DBUG_ENTER("ha_rollback_trans");
 | |
| 
 | |
|   /*
 | |
|     We must not rollback the normal transaction if a statement
 | |
|     transaction is pending.
 | |
|   */
 | |
|   DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
 | |
|               trans == &thd->transaction.stmt);
 | |
| 
 | |
|   if (thd->in_sub_stmt)
 | |
|   {
 | |
|     DBUG_ASSERT(0);
 | |
|     /*
 | |
|       If we are inside stored function or trigger we should not commit or
 | |
|       rollback current statement transaction. See comment in ha_commit_trans()
 | |
|       call for more information.
 | |
|     */
 | |
|     if (!all)
 | |
|       DBUG_RETURN(0);
 | |
|     my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   if (ha_info)
 | |
|   {
 | |
|     /* Close all cursors that can not survive ROLLBACK */
 | |
|     if (is_real_trans)                          /* not a statement commit */
 | |
|       thd->stmt_map.close_transient_cursors();
 | |
| 
 | |
|     for (; ha_info; ha_info= ha_info_next)
 | |
|     {
 | |
|       int err;
 | |
|       handlerton *ht= ha_info->ht();
 | |
|       if ((err= ht->rollback(ht, thd, all)))
 | |
|       { // cannot happen
 | |
|         my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
 | |
|         error=1;
 | |
|       }
 | |
|       status_var_increment(thd->status_var.ha_rollback_count);
 | |
|       ha_info_next= ha_info->next();
 | |
|       ha_info->reset(); /* keep it conveniently zero-filled */
 | |
|     }
 | |
|     trans->ha_list= 0;
 | |
|     trans->no_2pc=0;
 | |
|     if (is_real_trans && thd->transaction_rollback_request &&
 | |
|         thd->transaction.xid_state.xa_state != XA_NOTR)
 | |
|       thd->transaction.xid_state.rm_error= thd->stmt_da->sql_errno();
 | |
|   }
 | |
|   /* Always cleanup. Even if nht==0. There may be savepoints. */
 | |
|   if (is_real_trans)
 | |
|     thd->transaction.cleanup();
 | |
|   if (all)
 | |
|     thd->transaction_rollback_request= FALSE;
 | |
| 
 | |
|   /*
 | |
|     If a non-transactional table was updated, warn; don't warn if this is a
 | |
|     slave thread (because when a slave thread executes a ROLLBACK, it has
 | |
|     been read from the binary log, so it's 100% sure and normal to produce
 | |
|     error ER_WARNING_NOT_COMPLETE_ROLLBACK. If we sent the warning to the
 | |
|     slave SQL thread, it would not stop the thread but just be printed in
 | |
|     the error log; but we don't want users to wonder why they have this
 | |
|     message in the error log, so we don't send it.
 | |
|   */
 | |
|   if (is_real_trans && thd->transaction.all.modified_non_trans_table &&
 | |
|       !thd->slave_thread && thd->killed != THD::KILL_CONNECTION)
 | |
|     push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
 | |
|                  ER_WARNING_NOT_COMPLETE_ROLLBACK,
 | |
|                  ER(ER_WARNING_NOT_COMPLETE_ROLLBACK));
 | |
|   RUN_HOOK(transaction, after_rollback, (thd, FALSE));
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| struct xahton_st {
 | |
|   XID *xid;
 | |
|   int result;
 | |
| };
 | |
| 
 | |
| static my_bool xacommit_handlerton(THD *unused1, plugin_ref plugin,
 | |
|                                    void *arg)
 | |
| {
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->recover)
 | |
|   {
 | |
|     hton->commit_by_xid(hton, ((struct xahton_st *)arg)->xid);
 | |
|     ((struct xahton_st *)arg)->result= 0;
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static my_bool xarollback_handlerton(THD *unused1, plugin_ref plugin,
 | |
|                                      void *arg)
 | |
| {
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->recover)
 | |
|   {
 | |
|     hton->rollback_by_xid(hton, ((struct xahton_st *)arg)->xid);
 | |
|     ((struct xahton_st *)arg)->result= 0;
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| int ha_commit_or_rollback_by_xid(XID *xid, bool commit)
 | |
| {
 | |
|   struct xahton_st xaop;
 | |
|   xaop.xid= xid;
 | |
|   xaop.result= 1;
 | |
| 
 | |
|   plugin_foreach(NULL, commit ? xacommit_handlerton : xarollback_handlerton,
 | |
|                  MYSQL_STORAGE_ENGINE_PLUGIN, &xaop);
 | |
| 
 | |
|   return xaop.result;
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
| /**
 | |
|   @note
 | |
|     This does not need to be multi-byte safe or anything
 | |
| */
 | |
| static char* xid_to_str(char *buf, XID *xid)
 | |
| {
 | |
|   int i;
 | |
|   char *s=buf;
 | |
|   *s++='\'';
 | |
|   for (i=0; i < xid->gtrid_length+xid->bqual_length; i++)
 | |
|   {
 | |
|     uchar c=(uchar)xid->data[i];
 | |
|     /* is_next_dig is set if next character is a number */
 | |
|     bool is_next_dig= FALSE;
 | |
|     if (i < XIDDATASIZE)
 | |
|     {
 | |
|       char ch= xid->data[i+1];
 | |
|       is_next_dig= (ch >= '0' && ch <='9');
 | |
|     }
 | |
|     if (i == xid->gtrid_length)
 | |
|     {
 | |
|       *s++='\'';
 | |
|       if (xid->bqual_length)
 | |
|       {
 | |
|         *s++='.';
 | |
|         *s++='\'';
 | |
|       }
 | |
|     }
 | |
|     if (c < 32 || c > 126)
 | |
|     {
 | |
|       *s++='\\';
 | |
|       /*
 | |
|         If next character is a number, write current character with
 | |
|         3 octal numbers to ensure that the next number is not seen
 | |
|         as part of the octal number
 | |
|       */
 | |
|       if (c > 077 || is_next_dig)
 | |
|         *s++=_dig_vec_lower[c >> 6];
 | |
|       if (c > 007 || is_next_dig)
 | |
|         *s++=_dig_vec_lower[(c >> 3) & 7];
 | |
|       *s++=_dig_vec_lower[c & 7];
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (c == '\'' || c == '\\')
 | |
|         *s++='\\';
 | |
|       *s++=c;
 | |
|     }
 | |
|   }
 | |
|   *s++='\'';
 | |
|   *s=0;
 | |
|   return buf;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|   recover() step of xa.
 | |
| 
 | |
|   @note
 | |
|     there are three modes of operation:
 | |
|     - automatic recover after a crash
 | |
|     in this case commit_list != 0, tc_heuristic_recover==0
 | |
|     all xids from commit_list are committed, others are rolled back
 | |
|     - manual (heuristic) recover
 | |
|     in this case commit_list==0, tc_heuristic_recover != 0
 | |
|     DBA has explicitly specified that all prepared transactions should
 | |
|     be committed (or rolled back).
 | |
|     - no recovery (MySQL did not detect a crash)
 | |
|     in this case commit_list==0, tc_heuristic_recover == 0
 | |
|     there should be no prepared transactions in this case.
 | |
| */
 | |
| struct xarecover_st
 | |
| {
 | |
|   int len, found_foreign_xids, found_my_xids;
 | |
|   XID *list;
 | |
|   HASH *commit_list;
 | |
|   bool dry_run;
 | |
| };
 | |
| 
 | |
| static my_bool xarecover_handlerton(THD *unused, plugin_ref plugin,
 | |
|                                     void *arg)
 | |
| {
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   struct xarecover_st *info= (struct xarecover_st *) arg;
 | |
|   int got;
 | |
| 
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->recover)
 | |
|   {
 | |
|     while ((got= hton->recover(hton, info->list, info->len)) > 0 )
 | |
|     {
 | |
|       sql_print_information("Found %d prepared transaction(s) in %s",
 | |
|                             got, ha_resolve_storage_engine_name(hton));
 | |
|       for (int i=0; i < got; i ++)
 | |
|       {
 | |
|         my_xid x=info->list[i].get_my_xid();
 | |
|         if (!x) // not "mine" - that is generated by external TM
 | |
|         {
 | |
| #ifndef DBUG_OFF
 | |
|           char buf[XIDDATASIZE*4+6]; // see xid_to_str
 | |
|           sql_print_information("ignore xid %s", xid_to_str(buf, info->list+i));
 | |
| #endif
 | |
|           xid_cache_insert(info->list+i, XA_PREPARED);
 | |
|           info->found_foreign_xids++;
 | |
|           continue;
 | |
|         }
 | |
|         if (info->dry_run)
 | |
|         {
 | |
|           info->found_my_xids++;
 | |
|           continue;
 | |
|         }
 | |
|         // recovery mode
 | |
|         if (info->commit_list ?
 | |
|             my_hash_search(info->commit_list, (uchar *)&x, sizeof(x)) != 0 :
 | |
|             tc_heuristic_recover == TC_HEURISTIC_RECOVER_COMMIT)
 | |
|         {
 | |
| #ifndef DBUG_OFF
 | |
|           char buf[XIDDATASIZE*4+6]; // see xid_to_str
 | |
|           sql_print_information("commit xid %s", xid_to_str(buf, info->list+i));
 | |
| #endif
 | |
|           hton->commit_by_xid(hton, info->list+i);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
| #ifndef DBUG_OFF
 | |
|           char buf[XIDDATASIZE*4+6]; // see xid_to_str
 | |
|           sql_print_information("rollback xid %s",
 | |
|                                 xid_to_str(buf, info->list+i));
 | |
| #endif
 | |
|           hton->rollback_by_xid(hton, info->list+i);
 | |
|         }
 | |
|       }
 | |
|       if (got < info->len)
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| int ha_recover(HASH *commit_list)
 | |
| {
 | |
|   struct xarecover_st info;
 | |
|   DBUG_ENTER("ha_recover");
 | |
|   info.found_foreign_xids= info.found_my_xids= 0;
 | |
|   info.commit_list= commit_list;
 | |
|   info.dry_run= (info.commit_list==0 && tc_heuristic_recover==0);
 | |
|   info.list= NULL;
 | |
| 
 | |
|   /* commit_list and tc_heuristic_recover cannot be set both */
 | |
|   DBUG_ASSERT(info.commit_list==0 || tc_heuristic_recover==0);
 | |
|   /* if either is set, total_ha_2pc must be set too */
 | |
|   DBUG_ASSERT(info.dry_run || total_ha_2pc>(ulong)opt_bin_log);
 | |
| 
 | |
|   if (total_ha_2pc <= (ulong)opt_bin_log)
 | |
|     DBUG_RETURN(0);
 | |
| 
 | |
|   if (info.commit_list)
 | |
|     sql_print_information("Starting crash recovery...");
 | |
| 
 | |
| #ifndef WILL_BE_DELETED_LATER
 | |
|   /*
 | |
|     for now, only InnoDB supports 2pc. It means we can always safely
 | |
|     rollback all pending transactions, without risking inconsistent data
 | |
|   */
 | |
|   DBUG_ASSERT(total_ha_2pc == (ulong) opt_bin_log+1); // only InnoDB and binlog
 | |
|   tc_heuristic_recover= TC_HEURISTIC_RECOVER_ROLLBACK; // forcing ROLLBACK
 | |
|   info.dry_run=FALSE;
 | |
| #endif
 | |
| 
 | |
|   for (info.len= MAX_XID_LIST_SIZE ; 
 | |
|        info.list==0 && info.len > MIN_XID_LIST_SIZE; info.len/=2)
 | |
|   {
 | |
|     info.list=(XID *)my_malloc(info.len*sizeof(XID), MYF(0));
 | |
|   }
 | |
|   if (!info.list)
 | |
|   {
 | |
|     sql_print_error(ER(ER_OUTOFMEMORY),
 | |
|                     static_cast<int>(info.len*sizeof(XID)));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   plugin_foreach(NULL, xarecover_handlerton, 
 | |
|                  MYSQL_STORAGE_ENGINE_PLUGIN, &info);
 | |
| 
 | |
|   my_free(info.list);
 | |
|   if (info.found_foreign_xids)
 | |
|     sql_print_warning("Found %d prepared XA transactions", 
 | |
|                       info.found_foreign_xids);
 | |
|   if (info.dry_run && info.found_my_xids)
 | |
|   {
 | |
|     sql_print_error("Found %d prepared transactions! It means that mysqld was "
 | |
|                     "not shut down properly last time and critical recovery "
 | |
|                     "information (last binlog or %s file) was manually deleted "
 | |
|                     "after a crash. You have to start mysqld with "
 | |
|                     "--tc-heuristic-recover switch to commit or rollback "
 | |
|                     "pending transactions.",
 | |
|                     info.found_my_xids, opt_tc_log_file);
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   if (info.commit_list)
 | |
|     sql_print_information("Crash recovery finished.");
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   return the list of XID's to a client, the same way SHOW commands do.
 | |
| 
 | |
|   @note
 | |
|     I didn't find in XA specs that an RM cannot return the same XID twice,
 | |
|     so mysql_xa_recover does not filter XID's to ensure uniqueness.
 | |
|     It can be easily fixed later, if necessary.
 | |
| */
 | |
| bool mysql_xa_recover(THD *thd)
 | |
| {
 | |
|   List<Item> field_list;
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   int i=0;
 | |
|   XID_STATE *xs;
 | |
|   DBUG_ENTER("mysql_xa_recover");
 | |
| 
 | |
|   field_list.push_back(new Item_int("formatID", 0, MY_INT32_NUM_DECIMAL_DIGITS));
 | |
|   field_list.push_back(new Item_int("gtrid_length", 0, MY_INT32_NUM_DECIMAL_DIGITS));
 | |
|   field_list.push_back(new Item_int("bqual_length", 0, MY_INT32_NUM_DECIMAL_DIGITS));
 | |
|   field_list.push_back(new Item_empty_string("data",XIDDATASIZE));
 | |
| 
 | |
|   if (protocol->send_result_set_metadata(&field_list,
 | |
|                             Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   mysql_mutex_lock(&LOCK_xid_cache);
 | |
|   while ((xs= (XID_STATE*) my_hash_element(&xid_cache, i++)))
 | |
|   {
 | |
|     if (xs->xa_state==XA_PREPARED)
 | |
|     {
 | |
|       protocol->prepare_for_resend();
 | |
|       protocol->store_longlong((longlong)xs->xid.formatID, FALSE);
 | |
|       protocol->store_longlong((longlong)xs->xid.gtrid_length, FALSE);
 | |
|       protocol->store_longlong((longlong)xs->xid.bqual_length, FALSE);
 | |
|       protocol->store(xs->xid.data, xs->xid.gtrid_length+xs->xid.bqual_length,
 | |
|                       &my_charset_bin);
 | |
|       if (protocol->write())
 | |
|       {
 | |
|         mysql_mutex_unlock(&LOCK_xid_cache);
 | |
|         DBUG_RETURN(1);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&LOCK_xid_cache);
 | |
|   my_eof(thd);
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @details
 | |
|   This function should be called when MySQL sends rows of a SELECT result set
 | |
|   or the EOF mark to the client. It releases a possible adaptive hash index
 | |
|   S-latch held by thd in InnoDB and also releases a possible InnoDB query
 | |
|   FIFO ticket to enter InnoDB. To save CPU time, InnoDB allows a thd to
 | |
|   keep them over several calls of the InnoDB handler interface when a join
 | |
|   is executed. But when we let the control to pass to the client they have
 | |
|   to be released because if the application program uses mysql_use_result(),
 | |
|   it may deadlock on the S-latch if the application on another connection
 | |
|   performs another SQL query. In MySQL-4.1 this is even more important because
 | |
|   there a connection can have several SELECT queries open at the same time.
 | |
| 
 | |
|   @param thd           the thread handle of the current connection
 | |
| 
 | |
|   @return
 | |
|     always 0
 | |
| */
 | |
| 
 | |
| int ha_release_temporary_latches(THD *thd)
 | |
| {
 | |
|   Ha_trx_info *info;
 | |
| 
 | |
|   /*
 | |
|     Note that below we assume that only transactional storage engines
 | |
|     may need release_temporary_latches(). If this will ever become false,
 | |
|     we could iterate on thd->open_tables instead (and remove duplicates
 | |
|     as if (!seen[hton->slot]) { seen[hton->slot]=1; ... }).
 | |
|   */
 | |
|   for (info= thd->transaction.stmt.ha_list; info; info= info->next())
 | |
|   {
 | |
|     handlerton *hton= info->ht();
 | |
|     if (hton && hton->release_temporary_latches)
 | |
|         hton->release_temporary_latches(hton, thd);
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
 | |
| {
 | |
|   int error=0;
 | |
|   THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
 | |
|                                         &thd->transaction.all);
 | |
|   Ha_trx_info *ha_info, *ha_info_next;
 | |
| 
 | |
|   DBUG_ENTER("ha_rollback_to_savepoint");
 | |
| 
 | |
|   trans->no_2pc=0;
 | |
|   /*
 | |
|     rolling back to savepoint in all storage engines that were part of the
 | |
|     transaction when the savepoint was set
 | |
|   */
 | |
|   for (ha_info= sv->ha_list; ha_info; ha_info= ha_info->next())
 | |
|   {
 | |
|     int err;
 | |
|     handlerton *ht= ha_info->ht();
 | |
|     DBUG_ASSERT(ht);
 | |
|     DBUG_ASSERT(ht->savepoint_set != 0);
 | |
|     if ((err= ht->savepoint_rollback(ht, thd,
 | |
|                                      (uchar *)(sv+1)+ht->savepoint_offset)))
 | |
|     { // cannot happen
 | |
|       my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
 | |
|       error=1;
 | |
|     }
 | |
|     status_var_increment(thd->status_var.ha_savepoint_rollback_count);
 | |
|     trans->no_2pc|= ht->prepare == 0;
 | |
|   }
 | |
|   /*
 | |
|     rolling back the transaction in all storage engines that were not part of
 | |
|     the transaction when the savepoint was set
 | |
|   */
 | |
|   for (ha_info= trans->ha_list; ha_info != sv->ha_list;
 | |
|        ha_info= ha_info_next)
 | |
|   {
 | |
|     int err;
 | |
|     handlerton *ht= ha_info->ht();
 | |
|     if ((err= ht->rollback(ht, thd, !thd->in_sub_stmt)))
 | |
|     { // cannot happen
 | |
|       my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
 | |
|       error=1;
 | |
|     }
 | |
|     status_var_increment(thd->status_var.ha_rollback_count);
 | |
|     ha_info_next= ha_info->next();
 | |
|     ha_info->reset(); /* keep it conveniently zero-filled */
 | |
|   }
 | |
|   trans->ha_list= sv->ha_list;
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @note
 | |
|   according to the sql standard (ISO/IEC 9075-2:2003)
 | |
|   section "4.33.4 SQL-statements and transaction states",
 | |
|   SAVEPOINT is *not* transaction-initiating SQL-statement
 | |
| */
 | |
| int ha_savepoint(THD *thd, SAVEPOINT *sv)
 | |
| {
 | |
|   int error=0;
 | |
|   THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
 | |
|                                         &thd->transaction.all);
 | |
|   Ha_trx_info *ha_info= trans->ha_list;
 | |
|   DBUG_ENTER("ha_savepoint");
 | |
| 
 | |
|   for (; ha_info; ha_info= ha_info->next())
 | |
|   {
 | |
|     int err;
 | |
|     handlerton *ht= ha_info->ht();
 | |
|     DBUG_ASSERT(ht);
 | |
|     if (! ht->savepoint_set)
 | |
|     {
 | |
|       my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "SAVEPOINT");
 | |
|       error=1;
 | |
|       break;
 | |
|     }
 | |
|     if ((err= ht->savepoint_set(ht, thd, (uchar *)(sv+1)+ht->savepoint_offset)))
 | |
|     { // cannot happen
 | |
|       my_error(ER_GET_ERRNO, MYF(0), err);
 | |
|       error=1;
 | |
|     }
 | |
|     status_var_increment(thd->status_var.ha_savepoint_count);
 | |
|   }
 | |
|   /*
 | |
|     Remember the list of registered storage engines. All new
 | |
|     engines are prepended to the beginning of the list.
 | |
|   */
 | |
|   sv->ha_list= trans->ha_list;
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| int ha_release_savepoint(THD *thd, SAVEPOINT *sv)
 | |
| {
 | |
|   int error=0;
 | |
|   Ha_trx_info *ha_info= sv->ha_list;
 | |
|   DBUG_ENTER("ha_release_savepoint");
 | |
| 
 | |
|   for (; ha_info; ha_info= ha_info->next())
 | |
|   {
 | |
|     int err;
 | |
|     handlerton *ht= ha_info->ht();
 | |
|     /* Savepoint life time is enclosed into transaction life time. */
 | |
|     DBUG_ASSERT(ht);
 | |
|     if (!ht->savepoint_release)
 | |
|       continue;
 | |
|     if ((err= ht->savepoint_release(ht, thd,
 | |
|                                     (uchar *)(sv+1) + ht->savepoint_offset)))
 | |
|     { // cannot happen
 | |
|       my_error(ER_GET_ERRNO, MYF(0), err);
 | |
|       error=1;
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| static my_bool snapshot_handlerton(THD *thd, plugin_ref plugin,
 | |
|                                    void *arg)
 | |
| {
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->state == SHOW_OPTION_YES &&
 | |
|       hton->start_consistent_snapshot)
 | |
|   {
 | |
|     hton->start_consistent_snapshot(hton, thd);
 | |
|     *((bool *)arg)= false;
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| int ha_start_consistent_snapshot(THD *thd)
 | |
| {
 | |
|   bool warn= true;
 | |
| 
 | |
|   plugin_foreach(thd, snapshot_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, &warn);
 | |
| 
 | |
|   /*
 | |
|     Same idea as when one wants to CREATE TABLE in one engine which does not
 | |
|     exist:
 | |
|   */
 | |
|   if (warn)
 | |
|     push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
 | |
|                  "This MySQL server does not support any "
 | |
|                  "consistent-read capable storage engine");
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static my_bool flush_handlerton(THD *thd, plugin_ref plugin,
 | |
|                                 void *arg)
 | |
| {
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->flush_logs && 
 | |
|       hton->flush_logs(hton))
 | |
|     return TRUE;
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool ha_flush_logs(handlerton *db_type)
 | |
| {
 | |
|   if (db_type == NULL)
 | |
|   {
 | |
|     if (plugin_foreach(NULL, flush_handlerton,
 | |
|                           MYSQL_STORAGE_ENGINE_PLUGIN, 0))
 | |
|       return TRUE;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (db_type->state != SHOW_OPTION_YES ||
 | |
|         (db_type->flush_logs && db_type->flush_logs(db_type)))
 | |
|       return TRUE;
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief make canonical filename
 | |
| 
 | |
|   @param[in]  file     table handler
 | |
|   @param[in]  path     original path
 | |
|   @param[out] tmp_path buffer for canonized path
 | |
| 
 | |
|   @details Lower case db name and table name path parts for
 | |
|            non file based tables when lower_case_table_names
 | |
|            is 2 (store as is, compare in lower case).
 | |
|            Filesystem path prefix (mysql_data_home or tmpdir)
 | |
|            is left intact.
 | |
| 
 | |
|   @note tmp_path may be left intact if no conversion was
 | |
|         performed.
 | |
| 
 | |
|   @retval canonized path
 | |
| 
 | |
|   @todo This may be done more efficiently when table path
 | |
|         gets built. Convert this function to something like
 | |
|         ASSERT_CANONICAL_FILENAME.
 | |
| */
 | |
| const char *get_canonical_filename(handler *file, const char *path,
 | |
|                                    char *tmp_path)
 | |
| {
 | |
|   uint i;
 | |
|   if (lower_case_table_names != 2 || (file->ha_table_flags() & HA_FILE_BASED))
 | |
|     return path;
 | |
| 
 | |
|   for (i= 0; i <= mysql_tmpdir_list.max; i++)
 | |
|   {
 | |
|     if (is_prefix(path, mysql_tmpdir_list.list[i]))
 | |
|       return path;
 | |
|   }
 | |
| 
 | |
|   /* Ensure that table handler get path in lower case */
 | |
|   if (tmp_path != path)
 | |
|     strmov(tmp_path, path);
 | |
| 
 | |
|   /*
 | |
|     we only should turn into lowercase database/table part
 | |
|     so start the process after homedirectory
 | |
|   */
 | |
|   my_casedn_str(files_charset_info, tmp_path + mysql_data_home_len);
 | |
|   return tmp_path;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   An interceptor to hijack the text of the error message without
 | |
|   setting an error in the thread. We need the text to present it
 | |
|   in the form of a warning to the user.
 | |
| */
 | |
| 
 | |
| struct Ha_delete_table_error_handler: public Internal_error_handler
 | |
| {
 | |
| public:
 | |
|   virtual bool handle_condition(THD *thd,
 | |
|                                 uint sql_errno,
 | |
|                                 const char* sqlstate,
 | |
|                                 MYSQL_ERROR::enum_warning_level level,
 | |
|                                 const char* msg,
 | |
|                                 MYSQL_ERROR ** cond_hdl);
 | |
|   char buff[MYSQL_ERRMSG_SIZE];
 | |
| };
 | |
| 
 | |
| 
 | |
| bool
 | |
| Ha_delete_table_error_handler::
 | |
| handle_condition(THD *,
 | |
|                  uint,
 | |
|                  const char*,
 | |
|                  MYSQL_ERROR::enum_warning_level,
 | |
|                  const char* msg,
 | |
|                  MYSQL_ERROR ** cond_hdl)
 | |
| {
 | |
|   *cond_hdl= NULL;
 | |
|   /* Grab the error message */
 | |
|   strmake(buff, msg, sizeof(buff)-1);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   This should return ENOENT if the file doesn't exists.
 | |
|   The .frm file will be deleted only if we return 0 or ENOENT
 | |
| */
 | |
| int ha_delete_table(THD *thd, handlerton *table_type, const char *path,
 | |
|                     const char *db, const char *alias, bool generate_warning)
 | |
| {
 | |
|   handler *file;
 | |
|   char tmp_path[FN_REFLEN];
 | |
|   int error;
 | |
|   TABLE dummy_table;
 | |
|   TABLE_SHARE dummy_share;
 | |
|   DBUG_ENTER("ha_delete_table");
 | |
| 
 | |
|   bzero((char*) &dummy_table, sizeof(dummy_table));
 | |
|   bzero((char*) &dummy_share, sizeof(dummy_share));
 | |
|   dummy_table.s= &dummy_share;
 | |
| 
 | |
|   /* DB_TYPE_UNKNOWN is used in ALTER TABLE when renaming only .frm files */
 | |
|   if (table_type == NULL ||
 | |
|       ! (file=get_new_handler((TABLE_SHARE*)0, thd->mem_root, table_type)))
 | |
|     DBUG_RETURN(ENOENT);
 | |
| 
 | |
|   path= get_canonical_filename(file, path, tmp_path);
 | |
|   if ((error= file->ha_delete_table(path)) && generate_warning)
 | |
|   {
 | |
|     /*
 | |
|       Because file->print_error() use my_error() to generate the error message
 | |
|       we use an internal error handler to intercept it and store the text
 | |
|       in a temporary buffer. Later the message will be presented to user
 | |
|       as a warning.
 | |
|     */
 | |
|     Ha_delete_table_error_handler ha_delete_table_error_handler;
 | |
| 
 | |
|     /* Fill up strucutures that print_error may need */
 | |
|     dummy_share.path.str= (char*) path;
 | |
|     dummy_share.path.length= strlen(path);
 | |
|     dummy_share.db.str= (char*) db;
 | |
|     dummy_share.db.length= strlen(db);
 | |
|     dummy_share.table_name.str= (char*) alias;
 | |
|     dummy_share.table_name.length= strlen(alias);
 | |
|     dummy_table.alias= alias;
 | |
| 
 | |
|     file->change_table_ptr(&dummy_table, &dummy_share);
 | |
| 
 | |
|     thd->push_internal_handler(&ha_delete_table_error_handler);
 | |
|     file->print_error(error, 0);
 | |
| 
 | |
|     thd->pop_internal_handler();
 | |
| 
 | |
|     /*
 | |
|       XXX: should we convert *all* errors to warnings here?
 | |
|       What if the error is fatal?
 | |
|     */
 | |
|     push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, error,
 | |
|                 ha_delete_table_error_handler.buff);
 | |
|   }
 | |
|   delete file;
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| /****************************************************************************
 | |
| ** General handler functions
 | |
| ****************************************************************************/
 | |
| handler *handler::clone(const char *name, MEM_ROOT *mem_root)
 | |
| {
 | |
|   handler *new_handler= get_new_handler(table->s, mem_root, ht);
 | |
|   /*
 | |
|     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 &&
 | |
|      !(new_handler->ref= (uchar*) alloc_root(mem_root,
 | |
|                                              ALIGN_SIZE(ref_length)*2)))
 | |
|     new_handler= NULL;
 | |
|   /*
 | |
|     TODO: Implement a more efficient way to have more than one index open for
 | |
|     the same table instance. The ha_open call is not cachable for clone.
 | |
|   */
 | |
|   if (new_handler && new_handler->ha_open(table,
 | |
|                                           name,
 | |
|                                           table->db_stat,
 | |
|                                           HA_OPEN_IGNORE_IF_LOCKED))
 | |
|     new_handler= NULL;
 | |
| 
 | |
|   return new_handler;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| void handler::ha_statistic_increment(ulong SSV::*offset) const
 | |
| {
 | |
|   status_var_increment(table->in_use->status_var.*offset);
 | |
| }
 | |
| 
 | |
| void **handler::ha_data(THD *thd) const
 | |
| {
 | |
|   return thd_ha_data(thd, ht);
 | |
| }
 | |
| 
 | |
| THD *handler::ha_thd(void) const
 | |
| {
 | |
|   DBUG_ASSERT(!table || !table->in_use || table->in_use == current_thd);
 | |
|   return (table && table->in_use) ? table->in_use : current_thd;
 | |
| }
 | |
| 
 | |
| PSI_table_share *handler::ha_table_share_psi(const TABLE_SHARE *share) const
 | |
| {
 | |
|   return share->m_psi;
 | |
| }
 | |
| 
 | |
| /** @brief
 | |
|   Open database-handler.
 | |
| 
 | |
|   IMPLEMENTATION
 | |
|     Try O_RDONLY if cannot open as O_RDWR
 | |
|     Don't wait for locks if not HA_OPEN_WAIT_IF_LOCKED is set
 | |
| */
 | |
| int handler::ha_open(TABLE *table_arg, const char *name, int mode,
 | |
|                      int test_if_locked)
 | |
| {
 | |
|   int error;
 | |
|   DBUG_ENTER("handler::ha_open");
 | |
|   DBUG_PRINT("enter",
 | |
|              ("name: %s  db_type: %d  db_stat: %d  mode: %d  lock_test: %d",
 | |
|               name, ht->db_type, table_arg->db_stat, mode,
 | |
|               test_if_locked));
 | |
| 
 | |
|   table= table_arg;
 | |
|   DBUG_ASSERT(table->s == table_share);
 | |
|   DBUG_ASSERT(alloc_root_inited(&table->mem_root));
 | |
| 
 | |
|   if ((error=open(name,mode,test_if_locked)))
 | |
|   {
 | |
|     if ((error == EACCES || error == EROFS) && mode == O_RDWR &&
 | |
| 	(table->db_stat & HA_TRY_READ_ONLY))
 | |
|     {
 | |
|       table->db_stat|=HA_READ_ONLY;
 | |
|       error=open(name,O_RDONLY,test_if_locked);
 | |
|     }
 | |
|   }
 | |
|   if (error)
 | |
|   {
 | |
|     my_errno= error;                            /* Safeguard */
 | |
|     DBUG_PRINT("error",("error: %d  errno: %d",error,errno));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (table->s->db_options_in_use & HA_OPTION_READ_ONLY_DATA)
 | |
|       table->db_stat|=HA_READ_ONLY;
 | |
|     (void) extra(HA_EXTRA_NO_READCHECK);	// Not needed in SQL
 | |
| 
 | |
|     /* ref is already allocated for us if we're called from handler::clone() */
 | |
|     if (!ref && !(ref= (uchar*) alloc_root(&table->mem_root, 
 | |
|                                           ALIGN_SIZE(ref_length)*2)))
 | |
|     {
 | |
|       close();
 | |
|       error=HA_ERR_OUT_OF_MEM;
 | |
|     }
 | |
|     else
 | |
|       dup_ref=ref+ALIGN_SIZE(ref_length);
 | |
|     cached_table_flags= table_flags();
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Read first row (only) from a table.
 | |
| 
 | |
|   This is never called for InnoDB tables, as these table types
 | |
|   has the HA_STATS_RECORDS_IS_EXACT set.
 | |
| */
 | |
| int handler::read_first_row(uchar * buf, uint primary_key)
 | |
| {
 | |
|   register int error;
 | |
|   DBUG_ENTER("handler::read_first_row");
 | |
| 
 | |
|   ha_statistic_increment(&SSV::ha_read_first_count);
 | |
| 
 | |
|   /*
 | |
|     If there is very few deleted rows in the table, find the first row by
 | |
|     scanning the table.
 | |
|     TODO remove the test for HA_READ_ORDER
 | |
|   */
 | |
|   if (stats.deleted < 10 || primary_key >= MAX_KEY ||
 | |
|       !(index_flags(primary_key, 0, 0) & HA_READ_ORDER))
 | |
|   {
 | |
|     (void) ha_rnd_init(1);
 | |
|     while ((error= rnd_next(buf)) == HA_ERR_RECORD_DELETED) ;
 | |
|     (void) ha_rnd_end();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Find the first row through the primary key */
 | |
|     (void) ha_index_init(primary_key, 0);
 | |
|     error=index_first(buf);
 | |
|     (void) ha_index_end();
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Generate the next auto-increment number based on increment and offset.
 | |
|   computes the lowest number
 | |
|   - strictly greater than "nr"
 | |
|   - of the form: auto_increment_offset + N * auto_increment_increment
 | |
|   If overflow happened then return MAX_ULONGLONG value as an
 | |
|   indication of overflow.
 | |
|   In most cases increment= offset= 1, in which case we get:
 | |
|   @verbatim 1,2,3,4,5,... @endverbatim
 | |
|     If increment=10 and offset=5 and previous number is 1, we get:
 | |
|   @verbatim 1,5,15,25,35,... @endverbatim
 | |
| */
 | |
| inline ulonglong
 | |
| compute_next_insert_id(ulonglong nr,struct system_variables *variables)
 | |
| {
 | |
|   const ulonglong save_nr= nr;
 | |
| 
 | |
|   if (variables->auto_increment_increment == 1)
 | |
|     nr= nr + 1; // optimization of the formula below
 | |
|   else
 | |
|   {
 | |
|     nr= (((nr+ variables->auto_increment_increment -
 | |
|            variables->auto_increment_offset)) /
 | |
|          (ulonglong) variables->auto_increment_increment);
 | |
|     nr= (nr* (ulonglong) variables->auto_increment_increment +
 | |
|          variables->auto_increment_offset);
 | |
|   }
 | |
| 
 | |
|   if (unlikely(nr <= save_nr))
 | |
|     return ULONGLONG_MAX;
 | |
| 
 | |
|   return nr;
 | |
| }
 | |
| 
 | |
| 
 | |
| void handler::adjust_next_insert_id_after_explicit_value(ulonglong nr)
 | |
| {
 | |
|   /*
 | |
|     If we have set THD::next_insert_id previously and plan to insert an
 | |
|     explicitely-specified value larger than this, we need to increase
 | |
|     THD::next_insert_id to be greater than the explicit value.
 | |
|   */
 | |
|   if ((next_insert_id > 0) && (nr >= next_insert_id))
 | |
|     set_next_insert_id(compute_next_insert_id(nr, &table->in_use->variables));
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   Computes the largest number X:
 | |
|   - smaller than or equal to "nr"
 | |
|   - of the form: auto_increment_offset + N * auto_increment_increment
 | |
|   where N>=0.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     prev_insert_id
 | |
|       nr            Number to "round down"
 | |
|       variables     variables struct containing auto_increment_increment and
 | |
|                     auto_increment_offset
 | |
| 
 | |
|   RETURN
 | |
|     The number X if it exists, "nr" otherwise.
 | |
| */
 | |
| inline ulonglong
 | |
| prev_insert_id(ulonglong nr, struct system_variables *variables)
 | |
| {
 | |
|   if (unlikely(nr < variables->auto_increment_offset))
 | |
|   {
 | |
|     /*
 | |
|       There's nothing good we can do here. That is a pathological case, where
 | |
|       the offset is larger than the column's max possible value, i.e. not even
 | |
|       the first sequence value may be inserted. User will receive warning.
 | |
|     */
 | |
|     DBUG_PRINT("info",("auto_increment: nr: %lu cannot honour "
 | |
|                        "auto_increment_offset: %lu",
 | |
|                        (ulong) nr, variables->auto_increment_offset));
 | |
|     return nr;
 | |
|   }
 | |
|   if (variables->auto_increment_increment == 1)
 | |
|     return nr; // optimization of the formula below
 | |
|   nr= (((nr - variables->auto_increment_offset)) /
 | |
|        (ulonglong) variables->auto_increment_increment);
 | |
|   return (nr * (ulonglong) variables->auto_increment_increment +
 | |
|           variables->auto_increment_offset);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Update the auto_increment field if necessary.
 | |
| 
 | |
|   Updates columns with type NEXT_NUMBER if:
 | |
| 
 | |
|   - If column value is set to NULL (in which case
 | |
|     auto_increment_field_not_null is 0)
 | |
|   - If column is set to 0 and (sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO) is not
 | |
|     set. In the future we will only set NEXT_NUMBER fields if one sets them
 | |
|     to NULL (or they are not included in the insert list).
 | |
| 
 | |
|     In those cases, we check if the currently reserved interval still has
 | |
|     values we have not used. If yes, we pick the smallest one and use it.
 | |
|     Otherwise:
 | |
| 
 | |
|   - If a list of intervals has been provided to the statement via SET
 | |
|     INSERT_ID or via an Intvar_log_event (in a replication slave), we pick the
 | |
|     first unused interval from this list, consider it as reserved.
 | |
| 
 | |
|   - Otherwise we set the column for the first row to the value
 | |
|     next_insert_id(get_auto_increment(column))) which is usually
 | |
|     max-used-column-value+1.
 | |
|     We call get_auto_increment() for the first row in a multi-row
 | |
|     statement. get_auto_increment() will tell us the interval of values it
 | |
|     reserved for us.
 | |
| 
 | |
|   - In both cases, for the following rows we use those reserved values without
 | |
|     calling the handler again (we just progress in the interval, computing
 | |
|     each new value from the previous one). Until we have exhausted them, then
 | |
|     we either take the next provided interval or call get_auto_increment()
 | |
|     again to reserve a new interval.
 | |
| 
 | |
|   - In both cases, the reserved intervals are remembered in
 | |
|     thd->auto_inc_intervals_in_cur_stmt_for_binlog if statement-based
 | |
|     binlogging; the last reserved interval is remembered in
 | |
|     auto_inc_interval_for_cur_row. The number of reserved intervals is
 | |
|     remembered in auto_inc_intervals_count. It differs from the number of
 | |
|     elements in thd->auto_inc_intervals_in_cur_stmt_for_binlog() because the
 | |
|     latter list is cumulative over all statements forming one binlog event
 | |
|     (when stored functions and triggers are used), and collapses two
 | |
|     contiguous intervals in one (see its append() method).
 | |
| 
 | |
|     The idea is that generated auto_increment values are predictable and
 | |
|     independent of the column values in the table.  This is needed to be
 | |
|     able to replicate into a table that already has rows with a higher
 | |
|     auto-increment value than the one that is inserted.
 | |
| 
 | |
|     After we have already generated an auto-increment number and the user
 | |
|     inserts a column with a higher value than the last used one, we will
 | |
|     start counting from the inserted value.
 | |
| 
 | |
|     This function's "outputs" are: the table's auto_increment field is filled
 | |
|     with a value, thd->next_insert_id is filled with the value to use for the
 | |
|     next row, if a value was autogenerated for the current row it is stored in
 | |
|     thd->insert_id_for_cur_row, if get_auto_increment() was called
 | |
|     thd->auto_inc_interval_for_cur_row is modified, if that interval is not
 | |
|     present in thd->auto_inc_intervals_in_cur_stmt_for_binlog it is added to
 | |
|     this list.
 | |
| 
 | |
|   @todo
 | |
|     Replace all references to "next number" or NEXT_NUMBER to
 | |
|     "auto_increment", everywhere (see below: there is
 | |
|     table->auto_increment_field_not_null, and there also exists
 | |
|     table->next_number_field, it's not consistent).
 | |
| 
 | |
|   @retval
 | |
|     0	ok
 | |
|   @retval
 | |
|     HA_ERR_AUTOINC_READ_FAILED  get_auto_increment() was called and
 | |
|     returned ~(ulonglong) 0
 | |
|   @retval
 | |
|     HA_ERR_AUTOINC_ERANGE storing value in field caused strict mode
 | |
|     failure.
 | |
| */
 | |
| 
 | |
| #define AUTO_INC_DEFAULT_NB_ROWS 1 // Some prefer 1024 here
 | |
| #define AUTO_INC_DEFAULT_NB_MAX_BITS 16
 | |
| #define AUTO_INC_DEFAULT_NB_MAX ((1 << AUTO_INC_DEFAULT_NB_MAX_BITS) - 1)
 | |
| 
 | |
| int handler::update_auto_increment()
 | |
| {
 | |
|   ulonglong nr, nb_reserved_values;
 | |
|   bool append= FALSE;
 | |
|   THD *thd= table->in_use;
 | |
|   struct system_variables *variables= &thd->variables;
 | |
|   DBUG_ENTER("handler::update_auto_increment");
 | |
| 
 | |
|   /*
 | |
|     next_insert_id is a "cursor" into the reserved interval, it may go greater
 | |
|     than the interval, but not smaller.
 | |
|   */
 | |
|   DBUG_ASSERT(next_insert_id >= auto_inc_interval_for_cur_row.minimum());
 | |
| 
 | |
|   if ((nr= table->next_number_field->val_int()) != 0 ||
 | |
|       (table->auto_increment_field_not_null &&
 | |
|       thd->variables.sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO))
 | |
|   {
 | |
|     /*
 | |
|       Update next_insert_id if we had already generated a value in this
 | |
|       statement (case of INSERT VALUES(null),(3763),(null):
 | |
|       the last NULL needs to insert 3764, not the value of the first NULL plus
 | |
|       1).
 | |
|     */
 | |
|     adjust_next_insert_id_after_explicit_value(nr);
 | |
|     insert_id_for_cur_row= 0; // didn't generate anything
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   if ((nr= next_insert_id) >= auto_inc_interval_for_cur_row.maximum())
 | |
|   {
 | |
|     /* next_insert_id is beyond what is reserved, so we reserve more. */
 | |
|     const Discrete_interval *forced=
 | |
|       thd->auto_inc_intervals_forced.get_next();
 | |
|     if (forced != NULL)
 | |
|     {
 | |
|       nr= forced->minimum();
 | |
|       nb_reserved_values= forced->values();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         handler::estimation_rows_to_insert was set by
 | |
|         handler::ha_start_bulk_insert(); if 0 it means "unknown".
 | |
|       */
 | |
|       ulonglong nb_desired_values;
 | |
|       /*
 | |
|         If an estimation was given to the engine:
 | |
|         - use it.
 | |
|         - if we already reserved numbers, it means the estimation was
 | |
|         not accurate, then we'll reserve 2*AUTO_INC_DEFAULT_NB_ROWS the 2nd
 | |
|         time, twice that the 3rd time etc.
 | |
|         If no estimation was given, use those increasing defaults from the
 | |
|         start, starting from AUTO_INC_DEFAULT_NB_ROWS.
 | |
|         Don't go beyond a max to not reserve "way too much" (because
 | |
|         reservation means potentially losing unused values).
 | |
|         Note that in prelocked mode no estimation is given.
 | |
|       */
 | |
| 
 | |
|       if ((auto_inc_intervals_count == 0) && (estimation_rows_to_insert > 0))
 | |
|         nb_desired_values= estimation_rows_to_insert;
 | |
|       else if ((auto_inc_intervals_count == 0) &&
 | |
|                (thd->lex->many_values.elements > 0))
 | |
|       {
 | |
|         /*
 | |
|           For multi-row inserts, if the bulk inserts cannot be started, the
 | |
|           handler::estimation_rows_to_insert will not be set. But we still
 | |
|           want to reserve the autoinc values.
 | |
|         */
 | |
|         nb_desired_values= thd->lex->many_values.elements;
 | |
|       }
 | |
|       else /* go with the increasing defaults */
 | |
|       {
 | |
|         /* avoid overflow in formula, with this if() */
 | |
|         if (auto_inc_intervals_count <= AUTO_INC_DEFAULT_NB_MAX_BITS)
 | |
|         {
 | |
|           nb_desired_values= AUTO_INC_DEFAULT_NB_ROWS *
 | |
|             (1 << auto_inc_intervals_count);
 | |
|           set_if_smaller(nb_desired_values, AUTO_INC_DEFAULT_NB_MAX);
 | |
|         }
 | |
|         else
 | |
|           nb_desired_values= AUTO_INC_DEFAULT_NB_MAX;
 | |
|       }
 | |
|       /* This call ignores all its parameters but nr, currently */
 | |
|       get_auto_increment(variables->auto_increment_offset,
 | |
|                          variables->auto_increment_increment,
 | |
|                          nb_desired_values, &nr,
 | |
|                          &nb_reserved_values);
 | |
|       if (nr == ULONGLONG_MAX)
 | |
|         DBUG_RETURN(HA_ERR_AUTOINC_READ_FAILED);  // Mark failure
 | |
| 
 | |
|       /*
 | |
|         That rounding below should not be needed when all engines actually
 | |
|         respect offset and increment in get_auto_increment(). But they don't
 | |
|         so we still do it. Wonder if for the not-first-in-index we should do
 | |
|         it. Hope that this rounding didn't push us out of the interval; even
 | |
|         if it did we cannot do anything about it (calling the engine again
 | |
|         will not help as we inserted no row).
 | |
|       */
 | |
|       nr= compute_next_insert_id(nr-1, variables);
 | |
|     }
 | |
| 
 | |
|     if (table->s->next_number_keypart == 0)
 | |
|     {
 | |
|       /* We must defer the appending until "nr" has been possibly truncated */
 | |
|       append= TRUE;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         For such auto_increment there is no notion of interval, just a
 | |
|         singleton. The interval is not even stored in
 | |
|         thd->auto_inc_interval_for_cur_row, so we are sure to call the engine
 | |
|         for next row.
 | |
|       */
 | |
|       DBUG_PRINT("info",("auto_increment: special not-first-in-index"));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (unlikely(nr == ULONGLONG_MAX))
 | |
|       DBUG_RETURN(HA_ERR_AUTOINC_ERANGE); 
 | |
| 
 | |
|   DBUG_PRINT("info",("auto_increment: %lu", (ulong) nr));
 | |
| 
 | |
|   if (unlikely(table->next_number_field->store((longlong) nr, TRUE)))
 | |
|   {
 | |
|     /*
 | |
|       first test if the query was aborted due to strict mode constraints
 | |
|     */
 | |
|     if (thd->killed == THD::KILL_BAD_DATA)
 | |
|       DBUG_RETURN(HA_ERR_AUTOINC_ERANGE);
 | |
| 
 | |
|     /*
 | |
|       field refused this value (overflow) and truncated it, use the result of
 | |
|       the truncation (which is going to be inserted); however we try to
 | |
|       decrease it to honour auto_increment_* variables.
 | |
|       That will shift the left bound of the reserved interval, we don't
 | |
|       bother shifting the right bound (anyway any other value from this
 | |
|       interval will cause a duplicate key).
 | |
|     */
 | |
|     nr= prev_insert_id(table->next_number_field->val_int(), variables);
 | |
|     if (unlikely(table->next_number_field->store((longlong) nr, TRUE)))
 | |
|       nr= table->next_number_field->val_int();
 | |
|   }
 | |
|   if (append)
 | |
|   {
 | |
|     auto_inc_interval_for_cur_row.replace(nr, nb_reserved_values,
 | |
|                                           variables->auto_increment_increment);
 | |
|     auto_inc_intervals_count++;
 | |
|     /* Row-based replication does not need to store intervals in binlog */
 | |
|     if (mysql_bin_log.is_open() && !thd->is_current_stmt_binlog_format_row())
 | |
|         thd->auto_inc_intervals_in_cur_stmt_for_binlog.append(auto_inc_interval_for_cur_row.minimum(),
 | |
|                                                               auto_inc_interval_for_cur_row.values(),
 | |
|                                                               variables->auto_increment_increment);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Record this autogenerated value. If the caller then
 | |
|     succeeds to insert this value, it will call
 | |
|     record_first_successful_insert_id_in_cur_stmt()
 | |
|     which will set first_successful_insert_id_in_cur_stmt if it's not
 | |
|     already set.
 | |
|   */
 | |
|   insert_id_for_cur_row= nr;
 | |
|   /*
 | |
|     Set next insert id to point to next auto-increment value to be able to
 | |
|     handle multi-row statements.
 | |
|   */
 | |
|   set_next_insert_id(compute_next_insert_id(nr, variables));
 | |
| 
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   MySQL signal that it changed the column bitmap
 | |
| 
 | |
|   USAGE
 | |
|     This is for handlers that needs to setup their own column bitmaps.
 | |
|     Normally the handler should set up their own column bitmaps in
 | |
|     index_init() or rnd_init() and in any column_bitmaps_signal() call after
 | |
|     this.
 | |
| 
 | |
|     The handler is allowd to do changes to the bitmap after a index_init or
 | |
|     rnd_init() call is made as after this, MySQL will not use the bitmap
 | |
|     for any program logic checking.
 | |
| */
 | |
| void handler::column_bitmaps_signal()
 | |
| {
 | |
|   DBUG_ENTER("column_bitmaps_signal");
 | |
|   DBUG_PRINT("info", ("read_set: 0x%lx  write_set: 0x%lx", (long) table->read_set,
 | |
|                       (long) table->write_set));
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   Reserves an interval of auto_increment values from the handler.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     get_auto_increment()
 | |
|     offset              
 | |
|     increment
 | |
|     nb_desired_values   how many values we want
 | |
|     first_value         (OUT) the first value reserved by the handler
 | |
|     nb_reserved_values  (OUT) how many values the handler reserved
 | |
| 
 | |
|   offset and increment means that we want values to be of the form
 | |
|   offset + N * increment, where N>=0 is integer.
 | |
|   If the function sets *first_value to ~(ulonglong)0 it means an error.
 | |
|   If the function sets *nb_reserved_values to ULONGLONG_MAX it means it has
 | |
|   reserved to "positive infinite".
 | |
| */
 | |
| void handler::get_auto_increment(ulonglong offset, ulonglong increment,
 | |
|                                  ulonglong nb_desired_values,
 | |
|                                  ulonglong *first_value,
 | |
|                                  ulonglong *nb_reserved_values)
 | |
| {
 | |
|   ulonglong nr;
 | |
|   int error;
 | |
| 
 | |
|   (void) extra(HA_EXTRA_KEYREAD);
 | |
|   table->mark_columns_used_by_index_no_reset(table->s->next_number_index,
 | |
|                                         table->read_set);
 | |
|   column_bitmaps_signal();
 | |
|   index_init(table->s->next_number_index, 1);
 | |
|   if (table->s->next_number_keypart == 0)
 | |
|   {						// Autoincrement at key-start
 | |
|     error=index_last(table->record[1]);
 | |
|     /*
 | |
|       MySQL implicitely assumes such method does locking (as MySQL decides to
 | |
|       use nr+increment without checking again with the handler, in
 | |
|       handler::update_auto_increment()), so reserves to infinite.
 | |
|     */
 | |
|     *nb_reserved_values= ULONGLONG_MAX;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     uchar key[MAX_KEY_LENGTH];
 | |
|     key_copy(key, table->record[0],
 | |
|              table->key_info + table->s->next_number_index,
 | |
|              table->s->next_number_key_offset);
 | |
|     error= index_read_map(table->record[1], key,
 | |
|                           make_prev_keypart_map(table->s->next_number_keypart),
 | |
|                           HA_READ_PREFIX_LAST);
 | |
|     /*
 | |
|       MySQL needs to call us for next row: assume we are inserting ("a",null)
 | |
|       here, we return 3, and next this statement will want to insert
 | |
|       ("b",null): there is no reason why ("b",3+1) would be the good row to
 | |
|       insert: maybe it already exists, maybe 3+1 is too large...
 | |
|     */
 | |
|     *nb_reserved_values= 1;
 | |
|   }
 | |
| 
 | |
|   if (error)
 | |
|     nr=1;
 | |
|   else
 | |
|     nr= ((ulonglong) table->next_number_field->
 | |
|          val_int_offset(table->s->rec_buff_length)+1);
 | |
|   index_end();
 | |
|   (void) extra(HA_EXTRA_NO_KEYREAD);
 | |
|   *first_value= nr;
 | |
| }
 | |
| 
 | |
| 
 | |
| void handler::ha_release_auto_increment()
 | |
| {
 | |
|   release_auto_increment();
 | |
|   insert_id_for_cur_row= 0;
 | |
|   auto_inc_interval_for_cur_row.replace(0, 0, 0);
 | |
|   auto_inc_intervals_count= 0;
 | |
|   if (next_insert_id > 0)
 | |
|   {
 | |
|     next_insert_id= 0;
 | |
|     /*
 | |
|       this statement used forced auto_increment values if there were some,
 | |
|       wipe them away for other statements.
 | |
|     */
 | |
|     table->in_use->auto_inc_intervals_forced.empty();
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| void handler::print_keydup_error(uint key_nr, const char *msg)
 | |
| {
 | |
|   /* Write the duplicated key in the error message */
 | |
|   char key[MAX_KEY_LENGTH];
 | |
|   String str(key,sizeof(key),system_charset_info);
 | |
| 
 | |
|   if (key_nr == MAX_KEY)
 | |
|   {
 | |
|     /* Key is unknown */
 | |
|     str.copy("", 0, system_charset_info);
 | |
|     my_printf_error(ER_DUP_ENTRY, msg, MYF(0), str.c_ptr(), "*UNKNOWN*");
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Table is opened and defined at this point */
 | |
|     key_unpack(&str,table,(uint) key_nr);
 | |
|     uint max_length=MYSQL_ERRMSG_SIZE-(uint) strlen(msg);
 | |
|     if (str.length() >= max_length)
 | |
|     {
 | |
|       str.length(max_length-4);
 | |
|       str.append(STRING_WITH_LEN("..."));
 | |
|     }
 | |
|     my_printf_error(ER_DUP_ENTRY, msg,
 | |
| 		    MYF(0), str.c_ptr_safe(), table->key_info[key_nr].name);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Print error that we got from handler function.
 | |
| 
 | |
|   @note
 | |
|     In case of delete table it's only safe to use the following parts of
 | |
|     the 'table' structure:
 | |
|     - table->s->path
 | |
|     - table->alias
 | |
| */
 | |
| void handler::print_error(int error, myf errflag)
 | |
| {
 | |
|   DBUG_ENTER("handler::print_error");
 | |
|   DBUG_PRINT("enter",("error: %d",error));
 | |
| 
 | |
|   int textno=ER_GET_ERRNO;
 | |
|   switch (error) {
 | |
|   case EACCES:
 | |
|     textno=ER_OPEN_AS_READONLY;
 | |
|     break;
 | |
|   case EAGAIN:
 | |
|     textno=ER_FILE_USED;
 | |
|     break;
 | |
|   case ENOENT:
 | |
|     textno=ER_FILE_NOT_FOUND;
 | |
|     break;
 | |
|   case HA_ERR_KEY_NOT_FOUND:
 | |
|   case HA_ERR_NO_ACTIVE_RECORD:
 | |
|   case HA_ERR_RECORD_DELETED:
 | |
|   case HA_ERR_END_OF_FILE:
 | |
|     textno=ER_KEY_NOT_FOUND;
 | |
|     break;
 | |
|   case HA_ERR_WRONG_MRG_TABLE_DEF:
 | |
|     textno=ER_WRONG_MRG_TABLE;
 | |
|     break;
 | |
|   case HA_ERR_FOUND_DUPP_KEY:
 | |
|   {
 | |
|     uint key_nr=get_dup_key(error);
 | |
|     if ((int) key_nr >= 0)
 | |
|     {
 | |
|       print_keydup_error(key_nr, ER(ER_DUP_ENTRY_WITH_KEY_NAME));
 | |
|       DBUG_VOID_RETURN;
 | |
|     }
 | |
|     textno=ER_DUP_KEY;
 | |
|     break;
 | |
|   }
 | |
|   case HA_ERR_FOREIGN_DUPLICATE_KEY:
 | |
|   {
 | |
|     uint key_nr= get_dup_key(error);
 | |
|     if ((int) key_nr >= 0)
 | |
|     {
 | |
|       uint max_length;
 | |
|       /* Write the key in the error message */
 | |
|       char key[MAX_KEY_LENGTH];
 | |
|       String str(key,sizeof(key),system_charset_info);
 | |
|       /* Table is opened and defined at this point */
 | |
|       key_unpack(&str,table,0 /* Use 0 instead of key_nr because key_nr
 | |
|                  is a key number in the child FK table, not in our 'table'. See
 | |
| 		 Bug#12661768 UPDATE IGNORE CRASHES SERVER IF TABLE IS INNODB
 | |
| 		 AND IT IS PARENT FOR OTHER ONE
 | |
| 		 This bug gets a better fix in MySQL 5.6, but it is too risky
 | |
| 		 to get that in 5.1 and 5.5 (extending the handler interface
 | |
| 		 and adding new error message codes */);
 | |
|       max_length= (MYSQL_ERRMSG_SIZE-
 | |
|                    (uint) strlen(ER(ER_FOREIGN_DUPLICATE_KEY)));
 | |
|       if (str.length() >= max_length)
 | |
|       {
 | |
|         str.length(max_length-4);
 | |
|         str.append(STRING_WITH_LEN("..."));
 | |
|       }
 | |
|       my_error(ER_FOREIGN_DUPLICATE_KEY, MYF(0), table_share->table_name.str,
 | |
|         str.c_ptr_safe(), key_nr+1);
 | |
|       DBUG_VOID_RETURN;
 | |
|     }
 | |
|     textno= ER_DUP_KEY;
 | |
|     break;
 | |
|   }
 | |
|   case HA_ERR_NULL_IN_SPATIAL:
 | |
|     my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0));
 | |
|     DBUG_VOID_RETURN;
 | |
|   case HA_ERR_FOUND_DUPP_UNIQUE:
 | |
|     textno=ER_DUP_UNIQUE;
 | |
|     break;
 | |
|   case HA_ERR_RECORD_CHANGED:
 | |
|     textno=ER_CHECKREAD;
 | |
|     break;
 | |
|   case HA_ERR_CRASHED:
 | |
|     textno=ER_NOT_KEYFILE;
 | |
|     break;
 | |
|   case HA_ERR_WRONG_IN_RECORD:
 | |
|     textno= ER_CRASHED_ON_USAGE;
 | |
|     break;
 | |
|   case HA_ERR_CRASHED_ON_USAGE:
 | |
|     textno=ER_CRASHED_ON_USAGE;
 | |
|     break;
 | |
|   case HA_ERR_NOT_A_TABLE:
 | |
|     textno= error;
 | |
|     break;
 | |
|   case HA_ERR_CRASHED_ON_REPAIR:
 | |
|     textno=ER_CRASHED_ON_REPAIR;
 | |
|     break;
 | |
|   case HA_ERR_OUT_OF_MEM:
 | |
|     textno=ER_OUT_OF_RESOURCES;
 | |
|     break;
 | |
|   case HA_ERR_WRONG_COMMAND:
 | |
|     textno=ER_ILLEGAL_HA;
 | |
|     break;
 | |
|   case HA_ERR_OLD_FILE:
 | |
|     textno=ER_OLD_KEYFILE;
 | |
|     break;
 | |
|   case HA_ERR_UNSUPPORTED:
 | |
|     textno=ER_UNSUPPORTED_EXTENSION;
 | |
|     break;
 | |
|   case HA_ERR_RECORD_FILE_FULL:
 | |
|   case HA_ERR_INDEX_FILE_FULL:
 | |
|   {
 | |
|     textno=ER_RECORD_FILE_FULL;
 | |
|     /* Write the error message to error log */
 | |
|     errflag|= ME_NOREFRESH;
 | |
|     break;
 | |
|   }
 | |
|   case HA_ERR_LOCK_WAIT_TIMEOUT:
 | |
|     textno=ER_LOCK_WAIT_TIMEOUT;
 | |
|     break;
 | |
|   case HA_ERR_LOCK_TABLE_FULL:
 | |
|     textno=ER_LOCK_TABLE_FULL;
 | |
|     break;
 | |
|   case HA_ERR_LOCK_DEADLOCK:
 | |
|     textno=ER_LOCK_DEADLOCK;
 | |
|     break;
 | |
|   case HA_ERR_READ_ONLY_TRANSACTION:
 | |
|     textno=ER_READ_ONLY_TRANSACTION;
 | |
|     break;
 | |
|   case HA_ERR_CANNOT_ADD_FOREIGN:
 | |
|     textno=ER_CANNOT_ADD_FOREIGN;
 | |
|     break;
 | |
|   case HA_ERR_ROW_IS_REFERENCED:
 | |
|   {
 | |
|     String str;
 | |
|     get_error_message(error, &str);
 | |
|     my_error(ER_ROW_IS_REFERENCED_2, MYF(0), str.c_ptr_safe());
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
|   case HA_ERR_NO_REFERENCED_ROW:
 | |
|   {
 | |
|     String str;
 | |
|     get_error_message(error, &str);
 | |
|     my_error(ER_NO_REFERENCED_ROW_2, MYF(0), str.c_ptr_safe());
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
|   case HA_ERR_TABLE_DEF_CHANGED:
 | |
|     textno=ER_TABLE_DEF_CHANGED;
 | |
|     break;
 | |
|   case HA_ERR_NO_SUCH_TABLE:
 | |
|     my_error(ER_NO_SUCH_TABLE, MYF(0), table_share->db.str,
 | |
|              table_share->table_name.str);
 | |
|     DBUG_VOID_RETURN;
 | |
|   case HA_ERR_RBR_LOGGING_FAILED:
 | |
|     textno= ER_BINLOG_ROW_LOGGING_FAILED;
 | |
|     break;
 | |
|   case HA_ERR_DROP_INDEX_FK:
 | |
|   {
 | |
|     const char *ptr= "???";
 | |
|     uint key_nr= get_dup_key(error);
 | |
|     if ((int) key_nr >= 0)
 | |
|       ptr= table->key_info[key_nr].name;
 | |
|     my_error(ER_DROP_INDEX_FK, MYF(0), ptr);
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
|   case HA_ERR_TABLE_NEEDS_UPGRADE:
 | |
|     textno=ER_TABLE_NEEDS_UPGRADE;
 | |
|     break;
 | |
|   case HA_ERR_NO_PARTITION_FOUND:
 | |
|     textno=ER_WRONG_PARTITION_NAME;
 | |
|     break;
 | |
|   case HA_ERR_TABLE_READONLY:
 | |
|     textno= ER_OPEN_AS_READONLY;
 | |
|     break;
 | |
|   case HA_ERR_AUTOINC_READ_FAILED:
 | |
|     textno= ER_AUTOINC_READ_FAILED;
 | |
|     break;
 | |
|   case HA_ERR_AUTOINC_ERANGE:
 | |
|     textno= ER_WARN_DATA_OUT_OF_RANGE;
 | |
|     break;
 | |
|   case HA_ERR_TOO_MANY_CONCURRENT_TRXS:
 | |
|     textno= ER_TOO_MANY_CONCURRENT_TRXS;
 | |
|     break;
 | |
|   case HA_ERR_INDEX_COL_TOO_LONG:
 | |
|     textno= ER_INDEX_COLUMN_TOO_LONG;
 | |
|     break;
 | |
|   case HA_ERR_INDEX_CORRUPT:
 | |
|     textno= ER_INDEX_CORRUPT;
 | |
|     break;
 | |
|   case HA_ERR_UNDO_REC_TOO_BIG:
 | |
|     textno= ER_UNDO_RECORD_TOO_BIG;
 | |
|     break;
 | |
|   case HA_ERR_TABLE_IN_FK_CHECK:
 | |
|     textno= ER_TABLE_IN_FK_CHECK;
 | |
|     break;
 | |
|   default:
 | |
|     {
 | |
|       /* The error was "unknown" to this function.
 | |
| 	 Ask handler if it has got a message for this error */
 | |
|       bool temporary= FALSE;
 | |
|       String str;
 | |
|       temporary= get_error_message(error, &str);
 | |
|       if (!str.is_empty())
 | |
|       {
 | |
| 	const char* engine= table_type();
 | |
| 	if (temporary)
 | |
| 	  my_error(ER_GET_TEMPORARY_ERRMSG, MYF(0), error, str.ptr(), engine);
 | |
| 	else
 | |
| 	  my_error(ER_GET_ERRMSG, MYF(0), error, str.ptr(), engine);
 | |
|       }
 | |
|       else
 | |
| 	my_error(ER_GET_ERRNO,errflag,error);
 | |
|       DBUG_VOID_RETURN;
 | |
|     }
 | |
|   }
 | |
|   my_error(textno, errflag, table_share->table_name.str, error);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Return an error message specific to this handler.
 | |
| 
 | |
|   @param error  error code previously returned by handler
 | |
|   @param buf    pointer to String where to add error message
 | |
| 
 | |
|   @return
 | |
|     Returns true if this is a temporary error
 | |
| */
 | |
| bool handler::get_error_message(int error, String* buf)
 | |
| {
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check for incompatible collation changes.
 | |
|    
 | |
|   @retval
 | |
|     HA_ADMIN_NEEDS_UPGRADE   Table may have data requiring upgrade.
 | |
|   @retval
 | |
|     0                        No upgrade required.
 | |
| */
 | |
| 
 | |
| int handler::check_collation_compatibility()
 | |
| {
 | |
|   ulong mysql_version= table->s->mysql_version;
 | |
| 
 | |
|   if (mysql_version < 50124)
 | |
|   {
 | |
|     KEY *key= table->key_info;
 | |
|     KEY *key_end= key + table->s->keys;
 | |
|     for (; key < key_end; key++)
 | |
|     {
 | |
|       KEY_PART_INFO *key_part= key->key_part;
 | |
|       KEY_PART_INFO *key_part_end= key_part + key->key_parts;
 | |
|       for (; key_part < key_part_end; key_part++)
 | |
|       {
 | |
|         if (!key_part->fieldnr)
 | |
|           continue;
 | |
|         Field *field= table->field[key_part->fieldnr - 1];
 | |
|         uint cs_number= field->charset()->number;
 | |
|         if ((mysql_version < 50048 &&
 | |
|              (cs_number == 11 || /* ascii_general_ci - bug #29499, bug #27562 */
 | |
|               cs_number == 41 || /* latin7_general_ci - bug #29461 */
 | |
|               cs_number == 42 || /* latin7_general_cs - bug #29461 */
 | |
|               cs_number == 20 || /* latin7_estonian_cs - bug #29461 */
 | |
|               cs_number == 21 || /* latin2_hungarian_ci - bug #29461 */
 | |
|               cs_number == 22 || /* koi8u_general_ci - bug #29461 */
 | |
|               cs_number == 23 || /* cp1251_ukrainian_ci - bug #29461 */
 | |
|               cs_number == 26)) || /* cp1250_general_ci - bug #29461 */
 | |
|              (mysql_version < 50124 &&
 | |
|              (cs_number == 33 || /* utf8_general_ci - bug #27877 */
 | |
|               cs_number == 35))) /* ucs2_general_ci - bug #27877 */
 | |
|           return HA_ADMIN_NEEDS_UPGRADE;
 | |
|       }  
 | |
|     }  
 | |
|   }  
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| int handler::ha_check_for_upgrade(HA_CHECK_OPT *check_opt)
 | |
| {
 | |
|   int error;
 | |
|   KEY *keyinfo, *keyend;
 | |
|   KEY_PART_INFO *keypart, *keypartend;
 | |
| 
 | |
|   if (!table->s->mysql_version)
 | |
|   {
 | |
|     /* check for blob-in-key error */
 | |
|     keyinfo= table->key_info;
 | |
|     keyend= table->key_info + table->s->keys;
 | |
|     for (; keyinfo < keyend; keyinfo++)
 | |
|     {
 | |
|       keypart= keyinfo->key_part;
 | |
|       keypartend= keypart + keyinfo->key_parts;
 | |
|       for (; keypart < keypartend; keypart++)
 | |
|       {
 | |
|         if (!keypart->fieldnr)
 | |
|           continue;
 | |
|         Field *field= table->field[keypart->fieldnr-1];
 | |
|         if (field->type() == MYSQL_TYPE_BLOB)
 | |
|         {
 | |
|           if (check_opt->sql_flags & TT_FOR_UPGRADE)
 | |
|             check_opt->flags= T_MEDIUM;
 | |
|           return HA_ADMIN_NEEDS_CHECK;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (table->s->frm_version != FRM_VER_TRUE_VARCHAR)
 | |
|     return HA_ADMIN_NEEDS_ALTER;
 | |
| 
 | |
|   if ((error= check_collation_compatibility()))
 | |
|     return error;
 | |
|     
 | |
|   return check_for_upgrade(check_opt);
 | |
| }
 | |
| 
 | |
| 
 | |
| int handler::check_old_types()
 | |
| {
 | |
|   Field** field;
 | |
| 
 | |
|   if (!table->s->mysql_version)
 | |
|   {
 | |
|     /* check for bad DECIMAL field */
 | |
|     for (field= table->field; (*field); field++)
 | |
|     {
 | |
|       if ((*field)->type() == MYSQL_TYPE_NEWDECIMAL)
 | |
|       {
 | |
|         return HA_ADMIN_NEEDS_ALTER;
 | |
|       }
 | |
|       if ((*field)->type() == MYSQL_TYPE_VAR_STRING)
 | |
|       {
 | |
|         return HA_ADMIN_NEEDS_ALTER;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool update_frm_version(TABLE *table)
 | |
| {
 | |
|   char path[FN_REFLEN];
 | |
|   File file;
 | |
|   int result= 1;
 | |
|   DBUG_ENTER("update_frm_version");
 | |
| 
 | |
|   /*
 | |
|     No need to update frm version in case table was created or checked
 | |
|     by server with the same version. This also ensures that we do not
 | |
|     update frm version for temporary tables as this code doesn't support
 | |
|     temporary tables.
 | |
|   */
 | |
|   if (table->s->mysql_version == MYSQL_VERSION_ID)
 | |
|     DBUG_RETURN(0);
 | |
| 
 | |
|   strxmov(path, table->s->normalized_path.str, reg_ext, NullS);
 | |
| 
 | |
|   if ((file= mysql_file_open(key_file_frm,
 | |
|                              path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0)
 | |
|   {
 | |
|     uchar version[4];
 | |
| 
 | |
|     int4store(version, MYSQL_VERSION_ID);
 | |
| 
 | |
|     if ((result= mysql_file_pwrite(file, (uchar*) version, 4, 51L, MYF_RW)))
 | |
|       goto err;
 | |
| 
 | |
|     table->s->mysql_version= MYSQL_VERSION_ID;
 | |
|   }
 | |
| err:
 | |
|   if (file >= 0)
 | |
|     (void) mysql_file_close(file, MYF(MY_WME));
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @return
 | |
|     key if error because of duplicated keys
 | |
| */
 | |
| uint handler::get_dup_key(int error)
 | |
| {
 | |
|   DBUG_ENTER("handler::get_dup_key");
 | |
|   table->file->errkey  = (uint) -1;
 | |
|   if (error == HA_ERR_FOUND_DUPP_KEY || error == HA_ERR_FOREIGN_DUPLICATE_KEY ||
 | |
|       error == HA_ERR_FOUND_DUPP_UNIQUE || error == HA_ERR_NULL_IN_SPATIAL ||
 | |
|       error == HA_ERR_DROP_INDEX_FK)
 | |
|     table->file->info(HA_STATUS_ERRKEY | HA_STATUS_NO_LOCK);
 | |
|   DBUG_RETURN(table->file->errkey);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Delete all files with extension from bas_ext().
 | |
| 
 | |
|   @param name		Base name of table
 | |
| 
 | |
|   @note
 | |
|     We assume that the handler may return more extensions than
 | |
|     was actually used for the file.
 | |
| 
 | |
|   @retval
 | |
|     0   If we successfully deleted at least one file from base_ext and
 | |
|     didn't get any other errors than ENOENT
 | |
|   @retval
 | |
|     !0  Error
 | |
| */
 | |
| int handler::delete_table(const char *name)
 | |
| {
 | |
|   int saved_error= 0;
 | |
|   int error= 0;
 | |
|   int enoent_or_zero= ENOENT;                   // Error if no file was deleted
 | |
|   char buff[FN_REFLEN];
 | |
| 
 | |
|   for (const char **ext=bas_ext(); *ext ; ext++)
 | |
|   {
 | |
|     fn_format(buff, name, "", *ext, MY_UNPACK_FILENAME|MY_APPEND_EXT);
 | |
|     if (mysql_file_delete_with_symlink(key_file_misc, buff, MYF(0)))
 | |
|     {
 | |
|       if (my_errno != ENOENT)
 | |
|       {
 | |
|         /*
 | |
|           If error on the first existing file, return the error.
 | |
|           Otherwise delete as much as possible.
 | |
|         */
 | |
|         if (enoent_or_zero)
 | |
|           return my_errno;
 | |
| 	saved_error= my_errno;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|       enoent_or_zero= 0;                        // No error for ENOENT
 | |
|     error= enoent_or_zero;
 | |
|   }
 | |
|   return saved_error ? saved_error : error;
 | |
| }
 | |
| 
 | |
| 
 | |
| int handler::rename_table(const char * from, const char * to)
 | |
| {
 | |
|   int error= 0;
 | |
|   const char **ext, **start_ext;
 | |
|   start_ext= bas_ext();
 | |
|   for (ext= start_ext; *ext ; ext++)
 | |
|   {
 | |
|     if (rename_file_ext(from, to, *ext))
 | |
|     {
 | |
|       if ((error=my_errno) != ENOENT)
 | |
| 	break;
 | |
|       error= 0;
 | |
|     }
 | |
|   }
 | |
|   if (error)
 | |
|   {
 | |
|     /* Try to revert the rename. Ignore errors. */
 | |
|     for (; ext >= start_ext; ext--)
 | |
|       rename_file_ext(to, from, *ext);
 | |
|   }
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| 
 | |
| void handler::drop_table(const char *name)
 | |
| {
 | |
|   close();
 | |
|   delete_table(name);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Performs checks upon the table.
 | |
| 
 | |
|   @param thd                thread doing CHECK TABLE operation
 | |
|   @param check_opt          options from the parser
 | |
| 
 | |
|   @retval
 | |
|     HA_ADMIN_OK               Successful upgrade
 | |
|   @retval
 | |
|     HA_ADMIN_NEEDS_UPGRADE    Table has structures requiring upgrade
 | |
|   @retval
 | |
|     HA_ADMIN_NEEDS_ALTER      Table has structures requiring ALTER TABLE
 | |
|   @retval
 | |
|     HA_ADMIN_NOT_IMPLEMENTED
 | |
| */
 | |
| int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt)
 | |
| {
 | |
|   int error;
 | |
| 
 | |
|   if ((table->s->mysql_version >= MYSQL_VERSION_ID) &&
 | |
|       (check_opt->sql_flags & TT_FOR_UPGRADE))
 | |
|     return 0;
 | |
| 
 | |
|   if (table->s->mysql_version < MYSQL_VERSION_ID)
 | |
|   {
 | |
|     if ((error= check_old_types()))
 | |
|       return error;
 | |
|     error= ha_check_for_upgrade(check_opt);
 | |
|     if (error && (error != HA_ADMIN_NEEDS_CHECK))
 | |
|       return error;
 | |
|     if (!error && (check_opt->sql_flags & TT_FOR_UPGRADE))
 | |
|       return 0;
 | |
|   }
 | |
|   if ((error= check(thd, check_opt)))
 | |
|     return error;
 | |
|   return update_frm_version(table);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   A helper function to mark a transaction read-write,
 | |
|   if it is started.
 | |
| */
 | |
| 
 | |
| inline
 | |
| void
 | |
| handler::mark_trx_read_write()
 | |
| {
 | |
|   Ha_trx_info *ha_info= &ha_thd()->ha_data[ht->slot].ha_info[0];
 | |
|   /*
 | |
|     When a storage engine method is called, the transaction must
 | |
|     have been started, unless it's a DDL call, for which the
 | |
|     storage engine starts the transaction internally, and commits
 | |
|     it internally, without registering in the ha_list.
 | |
|     Unfortunately here we can't know know for sure if the engine
 | |
|     has registered the transaction or not, so we must check.
 | |
|   */
 | |
|   if (ha_info->is_started())
 | |
|   {
 | |
|     DBUG_ASSERT(has_transactions());
 | |
|     /*
 | |
|       table_share can be NULL in ha_delete_table(). See implementation
 | |
|       of standalone function ha_delete_table() in sql_base.cc.
 | |
|     */
 | |
|     if (table_share == NULL || table_share->tmp_table == NO_TMP_TABLE)
 | |
|       ha_info->set_trx_read_write();
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Repair table: public interface.
 | |
| 
 | |
|   @sa handler::repair()
 | |
| */
 | |
| 
 | |
| int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt)
 | |
| {
 | |
|   int result;
 | |
| 
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   result= repair(thd, check_opt);
 | |
|   DBUG_ASSERT(result == HA_ADMIN_NOT_IMPLEMENTED ||
 | |
|               ha_table_flags() & HA_CAN_REPAIR);
 | |
| 
 | |
|   if (result == HA_ADMIN_OK)
 | |
|     result= update_frm_version(table);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Bulk update row: public interface.
 | |
| 
 | |
|   @sa handler::bulk_update_row()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data,
 | |
|                             uint *dup_key_found)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return bulk_update_row(old_data, new_data, dup_key_found);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Delete all rows: public interface.
 | |
| 
 | |
|   @sa handler::delete_all_rows()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_delete_all_rows()
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return delete_all_rows();
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Truncate table: public interface.
 | |
| 
 | |
|   @sa handler::truncate()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_truncate()
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return truncate();
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Reset auto increment: public interface.
 | |
| 
 | |
|   @sa handler::reset_auto_increment()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_reset_auto_increment(ulonglong value)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return reset_auto_increment(value);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Optimize table: public interface.
 | |
| 
 | |
|   @sa handler::optimize()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return optimize(thd, check_opt);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Analyze table: public interface.
 | |
| 
 | |
|   @sa handler::analyze()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return analyze(thd, check_opt);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check and repair table: public interface.
 | |
| 
 | |
|   @sa handler::check_and_repair()
 | |
| */
 | |
| 
 | |
| bool
 | |
| handler::ha_check_and_repair(THD *thd)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return check_and_repair(thd);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Disable indexes: public interface.
 | |
| 
 | |
|   @sa handler::disable_indexes()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_disable_indexes(uint mode)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return disable_indexes(mode);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Enable indexes: public interface.
 | |
| 
 | |
|   @sa handler::enable_indexes()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_enable_indexes(uint mode)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return enable_indexes(mode);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Discard or import tablespace: public interface.
 | |
| 
 | |
|   @sa handler::discard_or_import_tablespace()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_discard_or_import_tablespace(my_bool discard)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return discard_or_import_tablespace(discard);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Prepare for alter: public interface.
 | |
| 
 | |
|   Called to prepare an *online* ALTER.
 | |
| 
 | |
|   @sa handler::prepare_for_alter()
 | |
| */
 | |
| 
 | |
| void
 | |
| handler::ha_prepare_for_alter()
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   prepare_for_alter();
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Rename table: public interface.
 | |
| 
 | |
|   @sa handler::rename_table()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_rename_table(const char *from, const char *to)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return rename_table(from, to);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Delete table: public interface.
 | |
| 
 | |
|   @sa handler::delete_table()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_delete_table(const char *name)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return delete_table(name);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Drop table in the engine: public interface.
 | |
| 
 | |
|   @sa handler::drop_table()
 | |
| */
 | |
| 
 | |
| void
 | |
| handler::ha_drop_table(const char *name)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return drop_table(name);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Create a table in the engine: public interface.
 | |
| 
 | |
|   @sa handler::create()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return create(name, form, info);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Create handler files for CREATE TABLE: public interface.
 | |
| 
 | |
|   @sa handler::create_handler_files()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_create_handler_files(const char *name, const char *old_name,
 | |
|                         int action_flag, HA_CREATE_INFO *info)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return create_handler_files(name, old_name, action_flag, info);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Change partitions: public interface.
 | |
| 
 | |
|   @sa handler::change_partitions()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_change_partitions(HA_CREATE_INFO *create_info,
 | |
|                      const char *path,
 | |
|                      ulonglong * const copied,
 | |
|                      ulonglong * const deleted,
 | |
|                      const uchar *pack_frm_data,
 | |
|                      size_t pack_frm_len)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return change_partitions(create_info, path, copied, deleted,
 | |
|                            pack_frm_data, pack_frm_len);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Drop partitions: public interface.
 | |
| 
 | |
|   @sa handler::drop_partitions()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_drop_partitions(const char *path)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return drop_partitions(path);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Rename partitions: public interface.
 | |
| 
 | |
|   @sa handler::rename_partitions()
 | |
| */
 | |
| 
 | |
| int
 | |
| handler::ha_rename_partitions(const char *path)
 | |
| {
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   return rename_partitions(path);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Tell the storage engine that it is allowed to "disable transaction" in the
 | |
|   handler. It is a hint that ACID is not required - it is used in NDB for
 | |
|   ALTER TABLE, for example, when data are copied to temporary table.
 | |
|   A storage engine may treat this hint any way it likes. NDB for example
 | |
|   starts to commit every now and then automatically.
 | |
|   This hint can be safely ignored.
 | |
| */
 | |
| int ha_enable_transaction(THD *thd, bool on)
 | |
| {
 | |
|   int error=0;
 | |
|   DBUG_ENTER("ha_enable_transaction");
 | |
|   DBUG_PRINT("enter", ("on: %d", (int) on));
 | |
| 
 | |
|   if ((thd->transaction.on= on))
 | |
|   {
 | |
|     /*
 | |
|       Now all storage engines should have transaction handling enabled.
 | |
|       But some may have it enabled all the time - "disabling" transactions
 | |
|       is an optimization hint that storage engine is free to ignore.
 | |
|       So, let's commit an open transaction (if any) now.
 | |
|     */
 | |
|     if (!(error= ha_commit_trans(thd, 0)))
 | |
|       error= trans_commit_implicit(thd);
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| int handler::index_next_same(uchar *buf, const uchar *key, uint keylen)
 | |
| {
 | |
|   int error;
 | |
|   DBUG_ENTER("index_next_same");
 | |
|   if (!(error=index_next(buf)))
 | |
|   {
 | |
|     my_ptrdiff_t ptrdiff= buf - table->record[0];
 | |
|     uchar *UNINIT_VAR(save_record_0);
 | |
|     KEY *UNINIT_VAR(key_info);
 | |
|     KEY_PART_INFO *UNINIT_VAR(key_part);
 | |
|     KEY_PART_INFO *UNINIT_VAR(key_part_end);
 | |
| 
 | |
|     /*
 | |
|       key_cmp_if_same() compares table->record[0] against 'key'.
 | |
|       In parts it uses table->record[0] directly, in parts it uses
 | |
|       field objects with their local pointers into table->record[0].
 | |
|       If 'buf' is distinct from table->record[0], we need to move
 | |
|       all record references. This is table->record[0] itself and
 | |
|       the field pointers of the fields used in this key.
 | |
|     */
 | |
|     if (ptrdiff)
 | |
|     {
 | |
|       save_record_0= table->record[0];
 | |
|       table->record[0]= buf;
 | |
|       key_info= table->key_info + active_index;
 | |
|       key_part= key_info->key_part;
 | |
|       key_part_end= key_part + key_info->key_parts;
 | |
|       for (; key_part < key_part_end; key_part++)
 | |
|       {
 | |
|         DBUG_ASSERT(key_part->field);
 | |
|         key_part->field->move_field_offset(ptrdiff);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (key_cmp_if_same(table, key, active_index, keylen))
 | |
|     {
 | |
|       table->status=STATUS_NOT_FOUND;
 | |
|       error=HA_ERR_END_OF_FILE;
 | |
|     }
 | |
| 
 | |
|     /* Move back if necessary. */
 | |
|     if (ptrdiff)
 | |
|     {
 | |
|       table->record[0]= save_record_0;
 | |
|       for (key_part= key_info->key_part; key_part < key_part_end; key_part++)
 | |
|         key_part->field->move_field_offset(-ptrdiff);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| void handler::get_dynamic_partition_info(PARTITION_STATS *stat_info,
 | |
|                                          uint part_id)
 | |
| {
 | |
|   info(HA_STATUS_CONST | HA_STATUS_TIME | HA_STATUS_VARIABLE |
 | |
|        HA_STATUS_NO_LOCK);
 | |
|   stat_info->records=              stats.records;
 | |
|   stat_info->mean_rec_length=      stats.mean_rec_length;
 | |
|   stat_info->data_file_length=     stats.data_file_length;
 | |
|   stat_info->max_data_file_length= stats.max_data_file_length;
 | |
|   stat_info->index_file_length=    stats.index_file_length;
 | |
|   stat_info->delete_length=        stats.delete_length;
 | |
|   stat_info->create_time=          stats.create_time;
 | |
|   stat_info->update_time=          stats.update_time;
 | |
|   stat_info->check_time=           stats.check_time;
 | |
|   stat_info->check_sum=            0;
 | |
|   if (table_flags() & (ulong) HA_HAS_CHECKSUM)
 | |
|     stat_info->check_sum= checksum();
 | |
|   return;
 | |
| }
 | |
| 
 | |
| 
 | |
| /****************************************************************************
 | |
| ** Some general functions that isn't in the handler class
 | |
| ****************************************************************************/
 | |
| 
 | |
| /**
 | |
|   Initiates table-file and calls appropriate database-creator.
 | |
| 
 | |
|   @retval
 | |
|    0  ok
 | |
|   @retval
 | |
|    1  error
 | |
| */
 | |
| int ha_create_table(THD *thd, const char *path,
 | |
|                     const char *db, const char *table_name,
 | |
|                     HA_CREATE_INFO *create_info,
 | |
| 		    bool update_create_info)
 | |
| {
 | |
|   int error= 1;
 | |
|   TABLE table;
 | |
|   char name_buff[FN_REFLEN];
 | |
|   const char *name;
 | |
|   TABLE_SHARE share;
 | |
|   DBUG_ENTER("ha_create_table");
 | |
|   
 | |
|   init_tmp_table_share(thd, &share, db, 0, table_name, path);
 | |
|   if (open_table_def(thd, &share, 0) ||
 | |
|       open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, 0, &table,
 | |
|                             TRUE))
 | |
|     goto err;
 | |
| 
 | |
|   if (update_create_info)
 | |
|     update_create_info_from_table(create_info, &table);
 | |
| 
 | |
|   name= get_canonical_filename(table.file, share.path.str, name_buff);
 | |
| 
 | |
|   error= table.file->ha_create(name, &table, create_info);
 | |
|   (void) closefrm(&table, 0);
 | |
|   if (error)
 | |
|   {
 | |
|     strxmov(name_buff, db, ".", table_name, NullS);
 | |
|     my_error(ER_CANT_CREATE_TABLE, MYF(ME_BELL+ME_WAITTANG), name_buff, error);
 | |
|   }
 | |
| err:
 | |
|   free_table_share(&share);
 | |
|   DBUG_RETURN(error != 0);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Try to discover table from engine.
 | |
| 
 | |
|   @note
 | |
|     If found, write the frm file to disk.
 | |
| 
 | |
|   @retval
 | |
|   -1    Table did not exists
 | |
|   @retval
 | |
|    0    Table created ok
 | |
|   @retval
 | |
|    > 0  Error, table existed but could not be created
 | |
| */
 | |
| int ha_create_table_from_engine(THD* thd, const char *db, const char *name)
 | |
| {
 | |
|   int error;
 | |
|   uchar *frmblob;
 | |
|   size_t frmlen;
 | |
|   char path[FN_REFLEN + 1];
 | |
|   HA_CREATE_INFO create_info;
 | |
|   TABLE table;
 | |
|   TABLE_SHARE share;
 | |
|   DBUG_ENTER("ha_create_table_from_engine");
 | |
|   DBUG_PRINT("enter", ("name '%s'.'%s'", db, name));
 | |
| 
 | |
|   bzero((uchar*) &create_info,sizeof(create_info));
 | |
|   if ((error= ha_discover(thd, db, name, &frmblob, &frmlen)))
 | |
|   {
 | |
|     /* Table could not be discovered and thus not created */
 | |
|     DBUG_RETURN(error);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Table exists in handler and could be discovered
 | |
|     frmblob and frmlen are set, write the frm to disk
 | |
|   */
 | |
| 
 | |
|   build_table_filename(path, sizeof(path) - 1, db, name, "", 0);
 | |
|   // Save the frm file
 | |
|   error= writefrm(path, frmblob, frmlen);
 | |
|   my_free(frmblob);
 | |
|   if (error)
 | |
|     DBUG_RETURN(2);
 | |
| 
 | |
|   init_tmp_table_share(thd, &share, db, 0, name, path);
 | |
|   if (open_table_def(thd, &share, 0))
 | |
|   {
 | |
|     DBUG_RETURN(3);
 | |
|   }
 | |
|   if (open_table_from_share(thd, &share, "" ,0, 0, 0, &table, FALSE))
 | |
|   {
 | |
|     free_table_share(&share);
 | |
|     DBUG_RETURN(3);
 | |
|   }
 | |
| 
 | |
|   update_create_info_from_table(&create_info, &table);
 | |
|   create_info.table_options|= HA_OPTION_CREATE_FROM_ENGINE;
 | |
| 
 | |
|   get_canonical_filename(table.file, path, path);
 | |
|   error=table.file->ha_create(path, &table, &create_info);
 | |
|   (void) closefrm(&table, 1);
 | |
| 
 | |
|   DBUG_RETURN(error != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Try to find a table in a storage engine. 
 | |
| 
 | |
|   @param db   Normalized table schema name
 | |
|   @param name Normalized table name.
 | |
|   @param[out] exists Only valid if the function succeeded.
 | |
| 
 | |
|   @retval TRUE   An error is found
 | |
|   @retval FALSE  Success, check *exists
 | |
| */
 | |
| 
 | |
| bool
 | |
| ha_check_if_table_exists(THD* thd, const char *db, const char *name,
 | |
|                          bool *exists)
 | |
| {
 | |
|   uchar *frmblob= NULL;
 | |
|   size_t frmlen;
 | |
|   DBUG_ENTER("ha_check_if_table_exists");
 | |
| 
 | |
|   *exists= ! ha_discover(thd, db, name, &frmblob, &frmlen);
 | |
|   if (*exists)
 | |
|     my_free(frmblob);
 | |
| 
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @brief Check if a given table is a system table.
 | |
| 
 | |
|   @details The primary purpose of introducing this function is to stop system
 | |
|   tables to be created or being moved to undesired storage engines.
 | |
| 
 | |
|   @todo There is another function called is_system_table_name() used by
 | |
|         get_table_category(), which is used to set TABLE_SHARE table_category.
 | |
|         It checks only a subset of table name like proc, event and time*.
 | |
|         We cannot use below function in get_table_category(),
 | |
|         as that affects locking mechanism. If we need to
 | |
|         unify these functions, we need to fix locking issues generated.
 | |
| 
 | |
|   @param   hton                  Handlerton of new engine.
 | |
|   @param   db                    Database name.
 | |
|   @param   table_name            Table name to be checked.
 | |
| 
 | |
|   @return Operation status
 | |
|     @retval  true                If the table name is a valid system table
 | |
|                                  or if its a valid user table.
 | |
| 
 | |
|     @retval  false               If the table name is a system table name
 | |
|                                  and does not belong to engine specified
 | |
|                                  in the command.
 | |
| */
 | |
| bool ha_check_if_supported_system_table(handlerton *hton, const char *db,
 | |
|                                         const char *table_name)
 | |
| {
 | |
|   DBUG_ENTER("ha_check_if_supported_system_table");
 | |
|   st_sys_tbl_chk_params check_params;
 | |
|   bool is_system_database= false;
 | |
|   const char **names;
 | |
|   st_system_tablename *systab;
 | |
| 
 | |
|   // Check if we have a system database name in the command.
 | |
|   DBUG_ASSERT(known_system_databases != NULL);
 | |
|   names= known_system_databases;
 | |
|   while (names && *names)
 | |
|   {
 | |
|     if (strcmp(*names, db) == 0)
 | |
|     {
 | |
|       /* Used to compare later, will be faster */
 | |
|       check_params.db= *names;
 | |
|       is_system_database= true;
 | |
|       break;
 | |
|     }
 | |
|     names++;
 | |
|   }
 | |
|   if (!is_system_database)
 | |
|     DBUG_RETURN(true); // It's a user table name.
 | |
| 
 | |
|   // Check if this is SQL layer system tables.
 | |
|   systab= mysqld_system_tables;
 | |
|   check_params.is_sql_layer_system_table= false;
 | |
|   while (systab && systab->db)
 | |
|   {
 | |
|     if (systab->db == check_params.db &&
 | |
|         strcmp(systab->tablename, table_name) == 0)
 | |
|     {
 | |
|       check_params.is_sql_layer_system_table= true;
 | |
|       break;
 | |
|     }
 | |
|     systab++;
 | |
|   }
 | |
| 
 | |
|   // Check if this is a system table and if some engine supports it.
 | |
|   check_params.status= check_params.is_sql_layer_system_table ?
 | |
|     st_sys_tbl_chk_params::KNOWN_SYSTEM_TABLE :
 | |
|     st_sys_tbl_chk_params::NOT_KNOWN_SYSTEM_TABLE;
 | |
|   check_params.db_type= hton->db_type;
 | |
|   check_params.table_name= table_name;
 | |
|   plugin_foreach(NULL, check_engine_system_table_handlerton,
 | |
|                  MYSQL_STORAGE_ENGINE_PLUGIN, &check_params);
 | |
| 
 | |
|   // SE does not support this system table.
 | |
|   if (check_params.status == st_sys_tbl_chk_params::KNOWN_SYSTEM_TABLE)
 | |
|     DBUG_RETURN(false);
 | |
| 
 | |
|   // It's a system table or a valid user table.
 | |
|   DBUG_RETURN(true);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @brief Called for each SE to check if given db, tablename is a system table.
 | |
| 
 | |
|   @details The primary purpose of introducing this function is to stop system
 | |
|   tables to be created or being moved to undesired storage engines.
 | |
| 
 | |
|   @param   unused  unused THD*
 | |
|   @param   plugin  Points to specific SE.
 | |
|   @param   arg     Is of type struct st_sys_tbl_chk_params.
 | |
| 
 | |
|   @note
 | |
|     args->status   Indicates OUT param,
 | |
|                    see struct st_sys_tbl_chk_params definition for more info.
 | |
| 
 | |
|   @return Operation status
 | |
|     @retval  true  There was a match found.
 | |
|                    This will stop doing checks with other SE's.
 | |
| 
 | |
|     @retval  false There was no match found.
 | |
|                    Other SE's will be checked to find a match.
 | |
| */
 | |
| static my_bool check_engine_system_table_handlerton(THD *unused,
 | |
|                                                     plugin_ref plugin,
 | |
|                                                     void *arg)
 | |
| {
 | |
|   st_sys_tbl_chk_params *check_params= (st_sys_tbl_chk_params*) arg;
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
| 
 | |
|   // Do we already know that the table is a system table?
 | |
|   if (check_params->status == st_sys_tbl_chk_params::KNOWN_SYSTEM_TABLE)
 | |
|   {
 | |
|     /*
 | |
|       If this is the same SE specified in the command, we can
 | |
|       simply ask the SE if it supports it stop the search regardless.
 | |
|     */
 | |
|     if (hton->db_type == check_params->db_type)
 | |
|     {
 | |
|       if (hton->is_supported_system_table &&
 | |
|           hton->is_supported_system_table(check_params->db,
 | |
|                                        check_params->table_name,
 | |
|                                        check_params->is_sql_layer_system_table))
 | |
|         check_params->status= st_sys_tbl_chk_params::SUPPORTED_SYSTEM_TABLE;
 | |
|       return TRUE;
 | |
|     }
 | |
|     /*
 | |
|       If this is a different SE, there is no point in asking the SE
 | |
|       since we already know it's a system table and we don't care
 | |
|       if it is supported or not.
 | |
|     */
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     We don't yet know if the table is a system table or not.
 | |
|     We therefore must always ask the SE.
 | |
|   */
 | |
|   if (hton->is_supported_system_table &&
 | |
|       hton->is_supported_system_table(check_params->db,
 | |
|                                       check_params->table_name,
 | |
|                                       check_params->is_sql_layer_system_table))
 | |
|   {
 | |
|     /*
 | |
|       If this is the same SE specified in the command, we know it's a
 | |
|       supported system table and can stop the search.
 | |
|     */
 | |
|     if (hton->db_type == check_params->db_type)
 | |
|     {
 | |
|       check_params->status= st_sys_tbl_chk_params::SUPPORTED_SYSTEM_TABLE;
 | |
|       return TRUE;
 | |
|     }
 | |
|     else
 | |
|       check_params->status= st_sys_tbl_chk_params::KNOWN_SYSTEM_TABLE;
 | |
|   }
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Prepare list of all known system database names
 | |
|   current we just have 'mysql' as system database name.
 | |
| 
 | |
|   Later ndbcluster, innodb SE's can define some new database
 | |
|   name which can store system tables specific to SE.
 | |
| */
 | |
| const char** ha_known_system_databases(void)
 | |
| {
 | |
|   I_List<i_string> found_databases;
 | |
|   const char **databases, **database;
 | |
| 
 | |
|   // Get mysqld system database name.
 | |
|   found_databases.push_back(new i_string(mysqld_system_database));
 | |
| 
 | |
|   // Get system database names from every specific storage engine.
 | |
|   plugin_foreach(NULL, system_databases_handlerton,
 | |
|                  MYSQL_STORAGE_ENGINE_PLUGIN, &found_databases);
 | |
| 
 | |
|   int element_count= 0;
 | |
|   I_List_iterator<i_string> iter(found_databases);
 | |
|   while (iter++) element_count++;
 | |
|   databases= (const char **) my_once_alloc(sizeof(char *)*
 | |
|                                            (element_count+1),
 | |
|                                            MYF(MY_WME | MY_FAE));
 | |
|   DBUG_ASSERT(databases != NULL);
 | |
| 
 | |
|   database= databases;
 | |
|   i_string *tmp;
 | |
|   while ((tmp= found_databases.get()))
 | |
|   {
 | |
|     *database++= tmp->ptr;
 | |
|     delete tmp;
 | |
|   }
 | |
|   *database= NULL; // Last element.
 | |
| 
 | |
|   return databases;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @brief Fetch system database name specific to SE.
 | |
| 
 | |
|   @details This function is invoked by plugin_foreach() from
 | |
|            ha_known_system_databases(), for each storage engine.
 | |
| */
 | |
| static my_bool system_databases_handlerton(THD *unused, plugin_ref plugin,
 | |
|                                            void *arg)
 | |
| {
 | |
|   I_List<i_string> *found_databases= (I_List<i_string> *) arg;
 | |
|   const char *db;
 | |
| 
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->system_database)
 | |
|   {
 | |
|     db= hton->system_database();
 | |
|     if (db)
 | |
|       found_databases->push_back(new i_string(db));
 | |
|   }
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| void st_ha_check_opt::init()
 | |
| {
 | |
|   flags= sql_flags= 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*****************************************************************************
 | |
|   Key cache handling.
 | |
| 
 | |
|   This code is only relevant for ISAM/MyISAM tables
 | |
| 
 | |
|   key_cache->cache may be 0 only in the case where a key cache is not
 | |
|   initialized or when we where not able to init the key cache in a previous
 | |
|   call to ha_init_key_cache() (probably out of memory)
 | |
| *****************************************************************************/
 | |
| 
 | |
| /**
 | |
|   Init a key cache if it has not been initied before.
 | |
| */
 | |
| int ha_init_key_cache(const char *name, KEY_CACHE *key_cache)
 | |
| {
 | |
|   DBUG_ENTER("ha_init_key_cache");
 | |
| 
 | |
|   if (!key_cache->key_cache_inited)
 | |
|   {
 | |
|     mysql_mutex_lock(&LOCK_global_system_variables);
 | |
|     size_t tmp_buff_size= (size_t) key_cache->param_buff_size;
 | |
|     uint tmp_block_size= (uint) key_cache->param_block_size;
 | |
|     uint division_limit= key_cache->param_division_limit;
 | |
|     uint age_threshold=  key_cache->param_age_threshold;
 | |
|     mysql_mutex_unlock(&LOCK_global_system_variables);
 | |
|     DBUG_RETURN(!init_key_cache(key_cache,
 | |
| 				tmp_block_size,
 | |
| 				tmp_buff_size,
 | |
| 				division_limit, age_threshold));
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Resize key cache.
 | |
| */
 | |
| int ha_resize_key_cache(KEY_CACHE *key_cache)
 | |
| {
 | |
|   DBUG_ENTER("ha_resize_key_cache");
 | |
| 
 | |
|   if (key_cache->key_cache_inited)
 | |
|   {
 | |
|     mysql_mutex_lock(&LOCK_global_system_variables);
 | |
|     size_t tmp_buff_size= (size_t) key_cache->param_buff_size;
 | |
|     long tmp_block_size= (long) key_cache->param_block_size;
 | |
|     uint division_limit= key_cache->param_division_limit;
 | |
|     uint age_threshold=  key_cache->param_age_threshold;
 | |
|     mysql_mutex_unlock(&LOCK_global_system_variables);
 | |
|     DBUG_RETURN(!resize_key_cache(key_cache, tmp_block_size,
 | |
| 				  tmp_buff_size,
 | |
| 				  division_limit, age_threshold));
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Change parameters for key cache (like size)
 | |
| */
 | |
| int ha_change_key_cache_param(KEY_CACHE *key_cache)
 | |
| {
 | |
|   if (key_cache->key_cache_inited)
 | |
|   {
 | |
|     mysql_mutex_lock(&LOCK_global_system_variables);
 | |
|     uint division_limit= key_cache->param_division_limit;
 | |
|     uint age_threshold=  key_cache->param_age_threshold;
 | |
|     mysql_mutex_unlock(&LOCK_global_system_variables);
 | |
|     change_key_cache_param(key_cache, division_limit, age_threshold);
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Move all tables from one key cache to another one.
 | |
| */
 | |
| int ha_change_key_cache(KEY_CACHE *old_key_cache,
 | |
| 			KEY_CACHE *new_key_cache)
 | |
| {
 | |
|   mi_change_key_cache(old_key_cache, new_key_cache);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Try to discover one table from handler(s).
 | |
| 
 | |
|   @retval
 | |
|     -1   Table did not exists
 | |
|   @retval
 | |
|     0   OK. In this case *frmblob and *frmlen are set
 | |
|   @retval
 | |
|     >0   error.  frmblob and frmlen may not be set
 | |
| */
 | |
| struct st_discover_args
 | |
| {
 | |
|   const char *db;
 | |
|   const char *name;
 | |
|   uchar **frmblob; 
 | |
|   size_t *frmlen;
 | |
| };
 | |
| 
 | |
| static my_bool discover_handlerton(THD *thd, plugin_ref plugin,
 | |
|                                    void *arg)
 | |
| {
 | |
|   st_discover_args *vargs= (st_discover_args *)arg;
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->discover &&
 | |
|       (!(hton->discover(hton, thd, vargs->db, vargs->name, 
 | |
|                         vargs->frmblob, 
 | |
|                         vargs->frmlen))))
 | |
|     return TRUE;
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| int ha_discover(THD *thd, const char *db, const char *name,
 | |
| 		uchar **frmblob, size_t *frmlen)
 | |
| {
 | |
|   int error= -1; // Table does not exist in any handler
 | |
|   DBUG_ENTER("ha_discover");
 | |
|   DBUG_PRINT("enter", ("db: %s, name: %s", db, name));
 | |
|   st_discover_args args= {db, name, frmblob, frmlen};
 | |
| 
 | |
|   if (is_prefix(name,tmp_file_prefix)) /* skip temporary tables */
 | |
|     DBUG_RETURN(error);
 | |
| 
 | |
|   if (plugin_foreach(thd, discover_handlerton,
 | |
|                  MYSQL_STORAGE_ENGINE_PLUGIN, &args))
 | |
|     error= 0;
 | |
| 
 | |
|   if (!error)
 | |
|     status_var_increment(thd->status_var.ha_discover_count);
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Call this function in order to give the handler the possiblity
 | |
|   to ask engine if there are any new tables that should be written to disk
 | |
|   or any dropped tables that need to be removed from disk
 | |
| */
 | |
| struct st_find_files_args
 | |
| {
 | |
|   const char *db;
 | |
|   const char *path;
 | |
|   const char *wild;
 | |
|   bool dir;
 | |
|   List<LEX_STRING> *files;
 | |
| };
 | |
| 
 | |
| static my_bool find_files_handlerton(THD *thd, plugin_ref plugin,
 | |
|                                    void *arg)
 | |
| {
 | |
|   st_find_files_args *vargs= (st_find_files_args *)arg;
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
| 
 | |
| 
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->find_files)
 | |
|       if (hton->find_files(hton, thd, vargs->db, vargs->path, vargs->wild, 
 | |
|                           vargs->dir, vargs->files))
 | |
|         return TRUE;
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| int
 | |
| ha_find_files(THD *thd,const char *db,const char *path,
 | |
| 	      const char *wild, bool dir, List<LEX_STRING> *files)
 | |
| {
 | |
|   int error= 0;
 | |
|   DBUG_ENTER("ha_find_files");
 | |
|   DBUG_PRINT("enter", ("db: '%s'  path: '%s'  wild: '%s'  dir: %d", 
 | |
| 		       db, path, wild ? wild : "NULL", dir));
 | |
|   st_find_files_args args= {db, path, wild, dir, files};
 | |
| 
 | |
|   plugin_foreach(thd, find_files_handlerton,
 | |
|                  MYSQL_STORAGE_ENGINE_PLUGIN, &args);
 | |
|   /* The return value is not currently used */
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Ask handler if the table exists in engine.
 | |
|   @retval
 | |
|     HA_ERR_NO_SUCH_TABLE     Table does not exist
 | |
|   @retval
 | |
|     HA_ERR_TABLE_EXIST       Table exists
 | |
|   @retval
 | |
|     \#                  Error code
 | |
| */
 | |
| struct st_table_exists_in_engine_args
 | |
| {
 | |
|   const char *db;
 | |
|   const char *name;
 | |
|   int err;
 | |
| };
 | |
| 
 | |
| static my_bool table_exists_in_engine_handlerton(THD *thd, plugin_ref plugin,
 | |
|                                    void *arg)
 | |
| {
 | |
|   st_table_exists_in_engine_args *vargs= (st_table_exists_in_engine_args *)arg;
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
| 
 | |
|   int err= HA_ERR_NO_SUCH_TABLE;
 | |
| 
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->table_exists_in_engine)
 | |
|     err = hton->table_exists_in_engine(hton, thd, vargs->db, vargs->name);
 | |
| 
 | |
|   vargs->err = err;
 | |
|   if (vargs->err == HA_ERR_TABLE_EXIST)
 | |
|     return TRUE;
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| int ha_table_exists_in_engine(THD* thd, const char* db, const char* name)
 | |
| {
 | |
|   DBUG_ENTER("ha_table_exists_in_engine");
 | |
|   DBUG_PRINT("enter", ("db: %s, name: %s", db, name));
 | |
|   st_table_exists_in_engine_args args= {db, name, HA_ERR_NO_SUCH_TABLE};
 | |
|   plugin_foreach(thd, table_exists_in_engine_handlerton,
 | |
|                  MYSQL_STORAGE_ENGINE_PLUGIN, &args);
 | |
|   DBUG_PRINT("exit", ("error: %d", args.err));
 | |
|   DBUG_RETURN(args.err);
 | |
| }
 | |
| 
 | |
| #ifdef HAVE_NDB_BINLOG
 | |
| /*
 | |
|   TODO: change this into a dynamic struct
 | |
|   List<handlerton> does not work as
 | |
|   1. binlog_end is called when MEM_ROOT is gone
 | |
|   2. cannot work with thd MEM_ROOT as memory should be freed
 | |
| */
 | |
| #define MAX_HTON_LIST_ST 63
 | |
| struct hton_list_st
 | |
| {
 | |
|   handlerton *hton[MAX_HTON_LIST_ST];
 | |
|   uint sz;
 | |
| };
 | |
| 
 | |
| struct binlog_func_st
 | |
| {
 | |
|   enum_binlog_func fn;
 | |
|   void *arg;
 | |
| };
 | |
| 
 | |
| /** @brief
 | |
|   Listing handlertons first to avoid recursive calls and deadlock
 | |
| */
 | |
| static my_bool binlog_func_list(THD *thd, plugin_ref plugin, void *arg)
 | |
| {
 | |
|   hton_list_st *hton_list= (hton_list_st *)arg;
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->binlog_func)
 | |
|   {
 | |
|     uint sz= hton_list->sz;
 | |
|     if (sz == MAX_HTON_LIST_ST-1)
 | |
|     {
 | |
|       /* list full */
 | |
|       return FALSE;
 | |
|     }
 | |
|     hton_list->hton[sz]= hton;
 | |
|     hton_list->sz= sz+1;
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static my_bool binlog_func_foreach(THD *thd, binlog_func_st *bfn)
 | |
| {
 | |
|   hton_list_st hton_list;
 | |
|   uint i, sz;
 | |
| 
 | |
|   hton_list.sz= 0;
 | |
|   plugin_foreach(thd, binlog_func_list,
 | |
|                  MYSQL_STORAGE_ENGINE_PLUGIN, &hton_list);
 | |
| 
 | |
|   for (i= 0, sz= hton_list.sz; i < sz ; i++)
 | |
|     hton_list.hton[i]->binlog_func(hton_list.hton[i], thd, bfn->fn, bfn->arg);
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| int ha_reset_logs(THD *thd)
 | |
| {
 | |
|   binlog_func_st bfn= {BFN_RESET_LOGS, 0};
 | |
|   binlog_func_foreach(thd, &bfn);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| void ha_reset_slave(THD* thd)
 | |
| {
 | |
|   binlog_func_st bfn= {BFN_RESET_SLAVE, 0};
 | |
|   binlog_func_foreach(thd, &bfn);
 | |
| }
 | |
| 
 | |
| void ha_binlog_wait(THD* thd)
 | |
| {
 | |
|   binlog_func_st bfn= {BFN_BINLOG_WAIT, 0};
 | |
|   binlog_func_foreach(thd, &bfn);
 | |
| }
 | |
| 
 | |
| int ha_binlog_end(THD* thd)
 | |
| {
 | |
|   binlog_func_st bfn= {BFN_BINLOG_END, 0};
 | |
|   binlog_func_foreach(thd, &bfn);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| int ha_binlog_index_purge_file(THD *thd, const char *file)
 | |
| {
 | |
|   binlog_func_st bfn= {BFN_BINLOG_PURGE_FILE, (void *)file};
 | |
|   binlog_func_foreach(thd, &bfn);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| struct binlog_log_query_st
 | |
| {
 | |
|   enum_binlog_command binlog_command;
 | |
|   const char *query;
 | |
|   uint query_length;
 | |
|   const char *db;
 | |
|   const char *table_name;
 | |
| };
 | |
| 
 | |
| static my_bool binlog_log_query_handlerton2(THD *thd,
 | |
|                                             handlerton *hton,
 | |
|                                             void *args)
 | |
| {
 | |
|   struct binlog_log_query_st *b= (struct binlog_log_query_st*)args;
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->binlog_log_query)
 | |
|     hton->binlog_log_query(hton, thd,
 | |
|                            b->binlog_command,
 | |
|                            b->query,
 | |
|                            b->query_length,
 | |
|                            b->db,
 | |
|                            b->table_name);
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static my_bool binlog_log_query_handlerton(THD *thd,
 | |
|                                            plugin_ref plugin,
 | |
|                                            void *args)
 | |
| {
 | |
|   return binlog_log_query_handlerton2(thd, plugin_data(plugin, handlerton *), args);
 | |
| }
 | |
| 
 | |
| void ha_binlog_log_query(THD *thd, handlerton *hton,
 | |
|                          enum_binlog_command binlog_command,
 | |
|                          const char *query, uint query_length,
 | |
|                          const char *db, const char *table_name)
 | |
| {
 | |
|   struct binlog_log_query_st b;
 | |
|   b.binlog_command= binlog_command;
 | |
|   b.query= query;
 | |
|   b.query_length= query_length;
 | |
|   b.db= db;
 | |
|   b.table_name= table_name;
 | |
|   if (hton == 0)
 | |
|     plugin_foreach(thd, binlog_log_query_handlerton,
 | |
|                    MYSQL_STORAGE_ENGINE_PLUGIN, &b);
 | |
|   else
 | |
|     binlog_log_query_handlerton2(thd, hton, &b);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|   Read the first row of a multi-range set.
 | |
| 
 | |
|   @param found_range_p       Returns a pointer to the element in 'ranges' that
 | |
|                              corresponds to the returned row.
 | |
|   @param ranges              An array of KEY_MULTI_RANGE range descriptions.
 | |
|   @param range_count         Number of ranges in 'ranges'.
 | |
|   @param sorted	      If result should be sorted per key.
 | |
|   @param buffer              A HANDLER_BUFFER for internal handler usage.
 | |
| 
 | |
|   @note
 | |
|     - Record is read into table->record[0].
 | |
|     - *found_range_p returns a valid value only if read_multi_range_first()
 | |
|     returns 0.
 | |
|     - Sorting is done within each range. If you want an overall sort, enter
 | |
|     'ranges' with sorted ranges.
 | |
| 
 | |
|   @retval
 | |
|     0			OK, found a row
 | |
|   @retval
 | |
|     HA_ERR_END_OF_FILE	No rows in range
 | |
|   @retval
 | |
|     \#			Error code
 | |
| */
 | |
| int handler::read_multi_range_first(KEY_MULTI_RANGE **found_range_p,
 | |
|                                     KEY_MULTI_RANGE *ranges, uint range_count,
 | |
|                                     bool sorted, HANDLER_BUFFER *buffer)
 | |
| {
 | |
|   int result= HA_ERR_END_OF_FILE;
 | |
|   DBUG_ENTER("handler::read_multi_range_first");
 | |
|   multi_range_sorted= sorted;
 | |
|   multi_range_buffer= buffer;
 | |
| 
 | |
|   table->mark_columns_used_by_index_no_reset(active_index, table->read_set);
 | |
|   table->column_bitmaps_set(table->read_set, table->write_set);
 | |
| 
 | |
|   for (multi_range_curr= ranges, multi_range_end= ranges + range_count;
 | |
|        multi_range_curr < multi_range_end;
 | |
|        multi_range_curr++)
 | |
|   {
 | |
|     result= read_range_first(multi_range_curr->start_key.keypart_map ?
 | |
|                              &multi_range_curr->start_key : 0,
 | |
|                              multi_range_curr->end_key.keypart_map ?
 | |
|                              &multi_range_curr->end_key : 0,
 | |
|                              test(multi_range_curr->range_flag & EQ_RANGE),
 | |
|                              multi_range_sorted);
 | |
|     if (result != HA_ERR_END_OF_FILE)
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   *found_range_p= multi_range_curr;
 | |
|   DBUG_PRINT("exit",("result %d", result));
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Read the next row of a multi-range set.
 | |
| 
 | |
|   @param found_range_p       Returns a pointer to the element in 'ranges' that
 | |
|                              corresponds to the returned row.
 | |
| 
 | |
|   @note
 | |
|     - Record is read into table->record[0].
 | |
|     - *found_range_p returns a valid value only if read_multi_range_next()
 | |
|     returns 0.
 | |
| 
 | |
|   @retval
 | |
|     0			OK, found a row
 | |
|   @retval
 | |
|     HA_ERR_END_OF_FILE	No (more) rows in range
 | |
|   @retval
 | |
|     \#			Error code
 | |
| */
 | |
| int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p)
 | |
| {
 | |
|   int UNINIT_VAR(result);
 | |
|   DBUG_ENTER("handler::read_multi_range_next");
 | |
| 
 | |
|   /* We should not be called after the last call returned EOF. */
 | |
|   DBUG_ASSERT(multi_range_curr < multi_range_end);
 | |
| 
 | |
|   do
 | |
|   {
 | |
|     /* Save a call if there can be only one row in range. */
 | |
|     if (multi_range_curr->range_flag != (UNIQUE_RANGE | EQ_RANGE))
 | |
|     {
 | |
|       result= read_range_next();
 | |
| 
 | |
|       /* On success or non-EOF errors jump to the end. */
 | |
|       if (result != HA_ERR_END_OF_FILE)
 | |
|         break;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (was_semi_consistent_read())
 | |
|         goto scan_it_again;
 | |
|       /*
 | |
|         We need to set this for the last range only, but checking this
 | |
|         condition is more expensive than just setting the result code.
 | |
|       */
 | |
|       result= HA_ERR_END_OF_FILE;
 | |
|     }
 | |
| 
 | |
|     multi_range_curr++;
 | |
| scan_it_again:
 | |
|     /* Try the next range(s) until one matches a record. */
 | |
|     for (; multi_range_curr < multi_range_end; multi_range_curr++)
 | |
|     {
 | |
|       result= read_range_first(multi_range_curr->start_key.keypart_map ?
 | |
|                                &multi_range_curr->start_key : 0,
 | |
|                                multi_range_curr->end_key.keypart_map ?
 | |
|                                &multi_range_curr->end_key : 0,
 | |
|                                test(multi_range_curr->range_flag & EQ_RANGE),
 | |
|                                multi_range_sorted);
 | |
|       if (result != HA_ERR_END_OF_FILE)
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
|   while ((result == HA_ERR_END_OF_FILE) &&
 | |
|          (multi_range_curr < multi_range_end));
 | |
| 
 | |
|   *found_range_p= multi_range_curr;
 | |
|   DBUG_PRINT("exit",("handler::read_multi_range_next: result %d", result));
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Read first row between two ranges.
 | |
|   Store ranges for future calls to read_range_next.
 | |
| 
 | |
|   @param start_key		Start key. Is 0 if no min range
 | |
|   @param end_key		End key.  Is 0 if no max range
 | |
|   @param eq_range_arg	        Set to 1 if start_key == end_key
 | |
|   @param sorted		Set to 1 if result should be sorted per key
 | |
| 
 | |
|   @note
 | |
|     Record is read into table->record[0]
 | |
| 
 | |
|   @retval
 | |
|     0			Found row
 | |
|   @retval
 | |
|     HA_ERR_END_OF_FILE	No rows in range
 | |
|   @retval
 | |
|     \#			Error code
 | |
| */
 | |
| int handler::read_range_first(const key_range *start_key,
 | |
| 			      const key_range *end_key,
 | |
| 			      bool eq_range_arg, bool sorted)
 | |
| {
 | |
|   int result;
 | |
|   DBUG_ENTER("handler::read_range_first");
 | |
| 
 | |
|   eq_range= eq_range_arg;
 | |
|   end_range= 0;
 | |
|   if (end_key)
 | |
|   {
 | |
|     end_range= &save_end_range;
 | |
|     save_end_range= *end_key;
 | |
|     key_compare_result_on_equal= ((end_key->flag == HA_READ_BEFORE_KEY) ? 1 :
 | |
| 				  (end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0);
 | |
|   }
 | |
|   range_key_part= table->key_info[active_index].key_part;
 | |
| 
 | |
|   if (!start_key)			// Read first record
 | |
|     result= index_first(table->record[0]);
 | |
|   else
 | |
|     result= index_read_map(table->record[0],
 | |
|                            start_key->key,
 | |
|                            start_key->keypart_map,
 | |
|                            start_key->flag);
 | |
|   if (result)
 | |
|     DBUG_RETURN((result == HA_ERR_KEY_NOT_FOUND) 
 | |
| 		? HA_ERR_END_OF_FILE
 | |
| 		: result);
 | |
| 
 | |
|   if (compare_key(end_range) <= 0)
 | |
|   {
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       The last read row does not fall in the range. So request
 | |
|       storage engine to release row lock if possible.
 | |
|     */
 | |
|     unlock_row();
 | |
|     DBUG_RETURN(HA_ERR_END_OF_FILE);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Read next row between two ranges.
 | |
| 
 | |
|   @note
 | |
|     Record is read into table->record[0]
 | |
| 
 | |
|   @retval
 | |
|     0			Found row
 | |
|   @retval
 | |
|     HA_ERR_END_OF_FILE	No rows in range
 | |
|   @retval
 | |
|     \#			Error code
 | |
| */
 | |
| int handler::read_range_next()
 | |
| {
 | |
|   int result;
 | |
|   DBUG_ENTER("handler::read_range_next");
 | |
| 
 | |
|   if (eq_range)
 | |
|   {
 | |
|     /* We trust that index_next_same always gives a row in range */
 | |
|     DBUG_RETURN(index_next_same(table->record[0],
 | |
|                                 end_range->key,
 | |
|                                 end_range->length));
 | |
|   }
 | |
|   result= index_next(table->record[0]);
 | |
|   if (result)
 | |
|     DBUG_RETURN(result);
 | |
| 
 | |
|   if (compare_key(end_range) <= 0)
 | |
|   {
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       The last read row does not fall in the range. So request
 | |
|       storage engine to release row lock if possible.
 | |
|     */
 | |
|     unlock_row();
 | |
|     DBUG_RETURN(HA_ERR_END_OF_FILE);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Compare if found key (in row) is over max-value.
 | |
| 
 | |
|   @param range		range to compare to row. May be 0 for no range
 | |
| 
 | |
|   @seealso
 | |
|     key.cc::key_cmp()
 | |
| 
 | |
|   @return
 | |
|     The return value is SIGN(key_in_row - range_key):
 | |
| 
 | |
|     - 0   : Key is equal to range or 'range' == 0 (no range)
 | |
|     - -1  : Key is less than range
 | |
|     - 1   : Key is larger than range
 | |
| */
 | |
| int handler::compare_key(key_range *range)
 | |
| {
 | |
|   int cmp;
 | |
|   if (!range)
 | |
|     return 0;					// No max range
 | |
|   cmp= key_cmp(range_key_part, range->key, range->length);
 | |
|   if (!cmp)
 | |
|     cmp= key_compare_result_on_equal;
 | |
|   return cmp;
 | |
| }
 | |
| 
 | |
| 
 | |
| int handler::index_read_idx_map(uchar * buf, uint index, const uchar * key,
 | |
|                                 key_part_map keypart_map,
 | |
|                                 enum ha_rkey_function find_flag)
 | |
| {
 | |
|   int error, error1;
 | |
|   error= index_init(index, 0);
 | |
|   if (!error)
 | |
|   {
 | |
|     error= index_read_map(buf, key, keypart_map, find_flag);
 | |
|     error1= index_end();
 | |
|   }
 | |
|   return error ?  error : error1;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Returns a list of all known extensions.
 | |
| 
 | |
|     No mutexes, worst case race is a minor surplus memory allocation
 | |
|     We have to recreate the extension map if mysqld is restarted (for example
 | |
|     within libmysqld)
 | |
| 
 | |
|   @retval
 | |
|     pointer		pointer to TYPELIB structure
 | |
| */
 | |
| static my_bool exts_handlerton(THD *unused, plugin_ref plugin,
 | |
|                                void *arg)
 | |
| {
 | |
|   List<char> *found_exts= (List<char> *) arg;
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   handler *file;
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->create &&
 | |
|       (file= hton->create(hton, (TABLE_SHARE*) 0, current_thd->mem_root)))
 | |
|   {
 | |
|     List_iterator_fast<char> it(*found_exts);
 | |
|     const char **ext, *old_ext;
 | |
| 
 | |
|     for (ext= file->bas_ext(); *ext; ext++)
 | |
|     {
 | |
|       while ((old_ext= it++))
 | |
|       {
 | |
|         if (!strcmp(old_ext, *ext))
 | |
| 	  break;
 | |
|       }
 | |
|       if (!old_ext)
 | |
|         found_exts->push_back((char *) *ext);
 | |
| 
 | |
|       it.rewind();
 | |
|     }
 | |
|     delete file;
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| TYPELIB *ha_known_exts(void)
 | |
| {
 | |
|   if (!known_extensions.type_names || mysys_usage_id != known_extensions_id)
 | |
|   {
 | |
|     List<char> found_exts;
 | |
|     const char **ext, *old_ext;
 | |
| 
 | |
|     known_extensions_id= mysys_usage_id;
 | |
|     found_exts.push_back((char*) TRG_EXT);
 | |
|     found_exts.push_back((char*) TRN_EXT);
 | |
| 
 | |
|     plugin_foreach(NULL, exts_handlerton,
 | |
|                    MYSQL_STORAGE_ENGINE_PLUGIN, &found_exts);
 | |
| 
 | |
|     ext= (const char **) my_once_alloc(sizeof(char *)*
 | |
|                                        (found_exts.elements+1),
 | |
|                                        MYF(MY_WME | MY_FAE));
 | |
| 
 | |
|     DBUG_ASSERT(ext != 0);
 | |
|     known_extensions.count= found_exts.elements;
 | |
|     known_extensions.type_names= ext;
 | |
| 
 | |
|     List_iterator_fast<char> it(found_exts);
 | |
|     while ((old_ext= it++))
 | |
|       *ext++= old_ext;
 | |
|     *ext= 0;
 | |
|   }
 | |
|   return &known_extensions;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool stat_print(THD *thd, const char *type, uint type_len,
 | |
|                        const char *file, uint file_len,
 | |
|                        const char *status, uint status_len)
 | |
| {
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   protocol->prepare_for_resend();
 | |
|   protocol->store(type, type_len, system_charset_info);
 | |
|   protocol->store(file, file_len, system_charset_info);
 | |
|   protocol->store(status, status_len, system_charset_info);
 | |
|   if (protocol->write())
 | |
|     return TRUE;
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| static my_bool showstat_handlerton(THD *thd, plugin_ref plugin,
 | |
|                                    void *arg)
 | |
| {
 | |
|   enum ha_stat_type stat= *(enum ha_stat_type *) arg;
 | |
|   handlerton *hton= plugin_data(plugin, handlerton *);
 | |
|   if (hton->state == SHOW_OPTION_YES && hton->show_status &&
 | |
|       hton->show_status(hton, thd, stat_print, stat))
 | |
|     return TRUE;
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat)
 | |
| {
 | |
|   List<Item> field_list;
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   bool result;
 | |
| 
 | |
|   field_list.push_back(new Item_empty_string("Type",10));
 | |
|   field_list.push_back(new Item_empty_string("Name",FN_REFLEN));
 | |
|   field_list.push_back(new Item_empty_string("Status",10));
 | |
| 
 | |
|   if (protocol->send_result_set_metadata(&field_list,
 | |
|                             Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
 | |
|     return TRUE;
 | |
| 
 | |
|   if (db_type == NULL)
 | |
|   {
 | |
|     result= plugin_foreach(thd, showstat_handlerton,
 | |
|                            MYSQL_STORAGE_ENGINE_PLUGIN, &stat);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (db_type->state != SHOW_OPTION_YES)
 | |
|     {
 | |
|       const LEX_STRING *name=&hton2plugin[db_type->slot]->name;
 | |
|       result= stat_print(thd, name->str, name->length,
 | |
|                          "", 0, "DISABLED", 8) ? 1 : 0;
 | |
|     }
 | |
|     else
 | |
|       result= db_type->show_status &&
 | |
|               db_type->show_status(db_type, thd, stat_print, stat) ? 1 : 0;
 | |
|   }
 | |
| 
 | |
|   if (!result)
 | |
|     my_eof(thd);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Function to check if the conditions for row-based binlogging is
 | |
|   correct for the table.
 | |
| 
 | |
|   A row in the given table should be replicated if:
 | |
|   - Row-based replication is enabled in the current thread
 | |
|   - The binlog is enabled
 | |
|   - It is not a temporary table
 | |
|   - The binary log is open
 | |
|   - The database the table resides in shall be binlogged (binlog_*_db rules)
 | |
|   - table is not mysql.event
 | |
| */
 | |
| 
 | |
| static bool check_table_binlog_row_based(THD *thd, TABLE *table)
 | |
| {
 | |
|   if (table->s->cached_row_logging_check == -1)
 | |
|   {
 | |
|     int const check(table->s->tmp_table == NO_TMP_TABLE &&
 | |
|                     binlog_filter->db_ok(table->s->db.str));
 | |
|     table->s->cached_row_logging_check= check;
 | |
|   }
 | |
| 
 | |
|   DBUG_ASSERT(table->s->cached_row_logging_check == 0 ||
 | |
|               table->s->cached_row_logging_check == 1);
 | |
| 
 | |
|   return (thd->is_current_stmt_binlog_format_row() &&
 | |
|           table->s->cached_row_logging_check &&
 | |
|           (thd->variables.option_bits & OPTION_BIN_LOG) &&
 | |
|           mysql_bin_log.is_open());
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|    Write table maps for all (manually or automatically) locked tables
 | |
|    to the binary log.
 | |
| 
 | |
|    SYNOPSIS
 | |
|      write_locked_table_maps()
 | |
|        thd     Pointer to THD structure
 | |
| 
 | |
|    DESCRIPTION
 | |
|        This function will generate and write table maps for all tables
 | |
|        that are locked by the thread 'thd'.
 | |
| 
 | |
|    RETURN VALUE
 | |
|        0   All OK
 | |
|        1   Failed to write all table maps
 | |
| 
 | |
|    SEE ALSO
 | |
|        THD::lock
 | |
| */
 | |
| 
 | |
| static int write_locked_table_maps(THD *thd)
 | |
| {
 | |
|   DBUG_ENTER("write_locked_table_maps");
 | |
|   DBUG_PRINT("enter", ("thd: 0x%lx  thd->lock: 0x%lx "
 | |
|                        "thd->extra_lock: 0x%lx",
 | |
|                        (long) thd, (long) thd->lock, (long) thd->extra_lock));
 | |
| 
 | |
|   DBUG_PRINT("debug", ("get_binlog_table_maps(): %d", thd->get_binlog_table_maps()));
 | |
| 
 | |
|   if (thd->get_binlog_table_maps() == 0)
 | |
|   {
 | |
|     MYSQL_LOCK *locks[2];
 | |
|     locks[0]= thd->extra_lock;
 | |
|     locks[1]= thd->lock;
 | |
|     for (uint i= 0 ; i < sizeof(locks)/sizeof(*locks) ; ++i )
 | |
|     {
 | |
|       MYSQL_LOCK const *const lock= locks[i];
 | |
|       if (lock == NULL)
 | |
|         continue;
 | |
| 
 | |
|       TABLE **const end_ptr= lock->table + lock->table_count;
 | |
|       for (TABLE **table_ptr= lock->table ; 
 | |
|            table_ptr != end_ptr ;
 | |
|            ++table_ptr)
 | |
|       {
 | |
|         TABLE *const table= *table_ptr;
 | |
|         DBUG_PRINT("info", ("Checking table %s", table->s->table_name.str));
 | |
|         if (table->current_lock == F_WRLCK &&
 | |
|             check_table_binlog_row_based(thd, table))
 | |
|         {
 | |
|           /*
 | |
|             We need to have a transactional behavior for SQLCOM_CREATE_TABLE
 | |
|             (e.g. CREATE TABLE... SELECT * FROM TABLE) in order to keep a
 | |
|             compatible behavior with the STMT based replication even when
 | |
|             the table is not transactional. In other words, if the operation
 | |
|             fails while executing the insert phase nothing is written to the
 | |
|             binlog.
 | |
| 
 | |
|             Note that at this point, we check the type of a set of tables to
 | |
|             create the table map events. In the function binlog_log_row(),
 | |
|             which calls the current function, we check the type of the table
 | |
|             of the current row.
 | |
|           */
 | |
|           bool const has_trans= thd->lex->sql_command == SQLCOM_CREATE_TABLE ||
 | |
|                                 table->file->has_transactions();
 | |
|           int const error= thd->binlog_write_table_map(table, has_trans);
 | |
|           /*
 | |
|             If an error occurs, it is the responsibility of the caller to
 | |
|             roll back the transaction.
 | |
|           */
 | |
|           if (unlikely(error))
 | |
|             DBUG_RETURN(1);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| typedef bool Log_func(THD*, TABLE*, bool, MY_BITMAP*,
 | |
|                       uint, const uchar*, const uchar*);
 | |
| 
 | |
| static int binlog_log_row(TABLE* table,
 | |
|                           const uchar *before_record,
 | |
|                           const uchar *after_record,
 | |
|                           Log_func *log_func)
 | |
| {
 | |
|   if (table->no_replicate)
 | |
|     return 0;
 | |
|   bool error= 0;
 | |
|   THD *const thd= table->in_use;
 | |
| 
 | |
|   if (check_table_binlog_row_based(thd, table))
 | |
|   {
 | |
|     MY_BITMAP cols;
 | |
|     /* Potential buffer on the stack for the bitmap */
 | |
|     uint32 bitbuf[BITMAP_STACKBUF_SIZE/sizeof(uint32)];
 | |
|     uint n_fields= table->s->fields;
 | |
|     my_bool use_bitbuf= n_fields <= sizeof(bitbuf)*8;
 | |
| 
 | |
|     /*
 | |
|       If there are no table maps written to the binary log, this is
 | |
|       the first row handled in this statement. In that case, we need
 | |
|       to write table maps for all locked tables to the binary log.
 | |
|     */
 | |
|     if (likely(!(error= bitmap_init(&cols,
 | |
|                                     use_bitbuf ? bitbuf : NULL,
 | |
|                                     (n_fields + 7) & ~7UL,
 | |
|                                     FALSE))))
 | |
|     {
 | |
|       bitmap_set_all(&cols);
 | |
|       if (likely(!(error= write_locked_table_maps(thd))))
 | |
|       {
 | |
|         /*
 | |
|           We need to have a transactional behavior for SQLCOM_CREATE_TABLE
 | |
|           (i.e. CREATE TABLE... SELECT * FROM TABLE) in order to keep a
 | |
|           compatible behavior with the STMT based replication even when
 | |
|           the table is not transactional. In other words, if the operation
 | |
|           fails while executing the insert phase nothing is written to the
 | |
|           binlog.
 | |
|         */
 | |
|         bool const has_trans= thd->lex->sql_command == SQLCOM_CREATE_TABLE ||
 | |
|                              table->file->has_transactions();
 | |
|         error= (*log_func)(thd, table, has_trans, &cols, table->s->fields,
 | |
|                            before_record, after_record);
 | |
|       }
 | |
|       if (!use_bitbuf)
 | |
|         bitmap_free(&cols);
 | |
|     }
 | |
|   }
 | |
|   return error ? HA_ERR_RBR_LOGGING_FAILED : 0;
 | |
| }
 | |
| 
 | |
| int handler::ha_external_lock(THD *thd, int lock_type)
 | |
| {
 | |
|   DBUG_ENTER("handler::ha_external_lock");
 | |
|   /*
 | |
|     Whether this is lock or unlock, this should be true, and is to verify that
 | |
|     if get_auto_increment() was called (thus may have reserved intervals or
 | |
|     taken a table lock), ha_release_auto_increment() was too.
 | |
|   */
 | |
|   DBUG_ASSERT(next_insert_id == 0);
 | |
| 
 | |
|   if (MYSQL_HANDLER_RDLOCK_START_ENABLED() ||
 | |
|       MYSQL_HANDLER_WRLOCK_START_ENABLED() ||
 | |
|       MYSQL_HANDLER_UNLOCK_START_ENABLED())
 | |
|   {
 | |
|     if (lock_type == F_RDLCK)
 | |
|     {
 | |
|       MYSQL_HANDLER_RDLOCK_START(table_share->db.str,
 | |
|                                  table_share->table_name.str);
 | |
|     }
 | |
|     else if (lock_type == F_WRLCK)
 | |
|     {
 | |
|       MYSQL_HANDLER_WRLOCK_START(table_share->db.str,
 | |
|                                  table_share->table_name.str);
 | |
|     }
 | |
|     else if (lock_type == F_UNLCK)
 | |
|     {
 | |
|       MYSQL_HANDLER_UNLOCK_START(table_share->db.str,
 | |
|                                  table_share->table_name.str);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     We cache the table flags if the locking succeeded. Otherwise, we
 | |
|     keep them as they were when they were fetched in ha_open().
 | |
|   */
 | |
|   int error= external_lock(thd, lock_type);
 | |
| 
 | |
|   if (error == 0)
 | |
|     cached_table_flags= table_flags();
 | |
| 
 | |
|   if (MYSQL_HANDLER_RDLOCK_DONE_ENABLED() ||
 | |
|       MYSQL_HANDLER_WRLOCK_DONE_ENABLED() ||
 | |
|       MYSQL_HANDLER_UNLOCK_DONE_ENABLED())
 | |
|   {
 | |
|     if (lock_type == F_RDLCK)
 | |
|     {
 | |
|       MYSQL_HANDLER_RDLOCK_DONE(error);
 | |
|     }
 | |
|     else if (lock_type == F_WRLCK)
 | |
|     {
 | |
|       MYSQL_HANDLER_WRLOCK_DONE(error);
 | |
|     }
 | |
|     else if (lock_type == F_UNLCK)
 | |
|     {
 | |
|       MYSQL_HANDLER_UNLOCK_DONE(error);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   Check handler usage and reset state of file to after 'open'
 | |
| */
 | |
| int handler::ha_reset()
 | |
| {
 | |
|   DBUG_ENTER("ha_reset");
 | |
|   /* Check that we have called all proper deallocation functions */
 | |
|   DBUG_ASSERT((uchar*) table->def_read_set.bitmap +
 | |
|               table->s->column_bitmap_size ==
 | |
|               (uchar*) table->def_write_set.bitmap);
 | |
|   DBUG_ASSERT(bitmap_is_set_all(&table->s->all_set));
 | |
|   DBUG_ASSERT(table->key_read == 0);
 | |
|   /* ensure that ha_index_end / ha_rnd_end has been called */
 | |
|   DBUG_ASSERT(inited == NONE);
 | |
|   /* Free cache used by filesort */
 | |
|   free_io_cache(table);
 | |
|   /* reset the bitmaps to point to defaults */
 | |
|   table->default_column_bitmaps();
 | |
|   pushed_cond= NULL;
 | |
|   DBUG_RETURN(reset());
 | |
| }
 | |
| 
 | |
| 
 | |
| int handler::ha_write_row(uchar *buf)
 | |
| {
 | |
|   int error;
 | |
|   Log_func *log_func= Write_rows_log_event::binlog_row_logging_function;
 | |
|   DBUG_ENTER("handler::ha_write_row");
 | |
| 
 | |
|   MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str);
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   error= write_row(buf);
 | |
|   MYSQL_INSERT_ROW_DONE(error);
 | |
|   if (unlikely(error))
 | |
|     DBUG_RETURN(error);
 | |
| 
 | |
|   if (unlikely(error= binlog_log_row(table, 0, buf, log_func)))
 | |
|     DBUG_RETURN(error); /* purecov: inspected */
 | |
| 
 | |
|   DEBUG_SYNC_C("ha_write_row_end");
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| int handler::ha_update_row(const uchar *old_data, uchar *new_data)
 | |
| {
 | |
|   int error;
 | |
|   Log_func *log_func= Update_rows_log_event::binlog_row_logging_function;
 | |
| 
 | |
|   /*
 | |
|     Some storage engines require that the new record is in record[0]
 | |
|     (and the old record is in record[1]).
 | |
|    */
 | |
|   DBUG_ASSERT(new_data == table->record[0]);
 | |
| 
 | |
|   MYSQL_UPDATE_ROW_START(table_share->db.str, table_share->table_name.str);
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   error= update_row(old_data, new_data);
 | |
|   MYSQL_UPDATE_ROW_DONE(error);
 | |
|   if (unlikely(error))
 | |
|     return error;
 | |
|   if (unlikely(error= binlog_log_row(table, old_data, new_data, log_func)))
 | |
|     return error;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| int handler::ha_delete_row(const uchar *buf)
 | |
| {
 | |
|   int error;
 | |
|   Log_func *log_func= Delete_rows_log_event::binlog_row_logging_function;
 | |
| 
 | |
|   MYSQL_DELETE_ROW_START(table_share->db.str, table_share->table_name.str);
 | |
|   mark_trx_read_write();
 | |
| 
 | |
|   error= delete_row(buf);
 | |
|   MYSQL_DELETE_ROW_DONE(error);
 | |
|   if (unlikely(error))
 | |
|     return error;
 | |
|   if (unlikely(error= binlog_log_row(table, buf, 0, log_func)))
 | |
|     return error;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   use_hidden_primary_key() is called in case of an update/delete when
 | |
|   (table_flags() and HA_PRIMARY_KEY_REQUIRED_FOR_DELETE) is defined
 | |
|   but we don't have a primary key
 | |
| */
 | |
| void handler::use_hidden_primary_key()
 | |
| {
 | |
|   /* fallback to use all columns in the table to identify row */
 | |
|   table->use_all_columns();
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   Dummy function which accept information about log files which is not need
 | |
|   by handlers
 | |
| */
 | |
| void signal_log_not_needed(struct handlerton, char *log_file)
 | |
| {
 | |
|   DBUG_ENTER("signal_log_not_needed");
 | |
|   DBUG_PRINT("enter", ("logfile '%s'", log_file));
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef TRANS_LOG_MGM_EXAMPLE_CODE
 | |
| /*
 | |
|   Example of transaction log management functions based on assumption that logs
 | |
|   placed into a directory
 | |
| */
 | |
| #include <my_dir.h>
 | |
| #include <my_sys.h>
 | |
| int example_of_iterator_using_for_logs_cleanup(handlerton *hton)
 | |
| {
 | |
|   void *buffer;
 | |
|   int res= 1;
 | |
|   struct handler_iterator iterator;
 | |
|   struct handler_log_file_data data;
 | |
| 
 | |
|   if (!hton->create_iterator)
 | |
|     return 1; /* iterator creator is not supported */
 | |
| 
 | |
|   if ((*hton->create_iterator)(hton, HA_TRANSACTLOG_ITERATOR, &iterator) !=
 | |
|       HA_ITERATOR_OK)
 | |
|   {
 | |
|     /* error during creation of log iterator or iterator is not supported */
 | |
|     return 1;
 | |
|   }
 | |
|   while((*iterator.next)(&iterator, (void*)&data) == 0)
 | |
|   {
 | |
|     printf("%s\n", data.filename.str);
 | |
|     if (data.status == HA_LOG_STATUS_FREE &&
 | |
|         mysql_file_delete(INSTRUMENT_ME,
 | |
|                           data.filename.str, MYF(MY_WME)))
 | |
|       goto err;
 | |
|   }
 | |
|   res= 0;
 | |
| err:
 | |
|   (*iterator.destroy)(&iterator);
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Here we should get info from handler where it save logs but here is
 | |
|   just example, so we use constant.
 | |
|   IMHO FN_ROOTDIR ("/") is safe enough for example, because nobody has
 | |
|   rights on it except root and it consist of directories only at lest for
 | |
|   *nix (sorry, can't find windows-safe solution here, but it is only example).
 | |
| */
 | |
| #define fl_dir FN_ROOTDIR
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   Dummy function to return log status should be replaced by function which
 | |
|   really detect the log status and check that the file is a log of this
 | |
|   handler.
 | |
| */
 | |
| enum log_status fl_get_log_status(char *log)
 | |
| {
 | |
|   MY_STAT stat_buff;
 | |
|   if (mysql_file_stat(INSTRUMENT_ME, log, &stat_buff, MYF(0)))
 | |
|     return HA_LOG_STATUS_INUSE;
 | |
|   return HA_LOG_STATUS_NOSUCHLOG;
 | |
| }
 | |
| 
 | |
| 
 | |
| struct fl_buff
 | |
| {
 | |
|   LEX_STRING *names;
 | |
|   enum log_status *statuses;
 | |
|   uint32 entries;
 | |
|   uint32 current;
 | |
| };
 | |
| 
 | |
| 
 | |
| int fl_log_iterator_next(struct handler_iterator *iterator,
 | |
|                           void *iterator_object)
 | |
| {
 | |
|   struct fl_buff *buff= (struct fl_buff *)iterator->buffer;
 | |
|   struct handler_log_file_data *data=
 | |
|     (struct handler_log_file_data *) iterator_object;
 | |
|   if (buff->current >= buff->entries)
 | |
|     return 1;
 | |
|   data->filename= buff->names[buff->current];
 | |
|   data->status= buff->statuses[buff->current];
 | |
|   buff->current++;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| void fl_log_iterator_destroy(struct handler_iterator *iterator)
 | |
| {
 | |
|   my_free(iterator->buffer);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @brief
 | |
|   returns buffer, to be assigned in handler_iterator struct
 | |
| */
 | |
| enum handler_create_iterator_result
 | |
| fl_log_iterator_buffer_init(struct handler_iterator *iterator)
 | |
| {
 | |
|   MY_DIR *dirp;
 | |
|   struct fl_buff *buff;
 | |
|   char *name_ptr;
 | |
|   uchar *ptr;
 | |
|   FILEINFO *file;
 | |
|   uint32 i;
 | |
| 
 | |
|   /* to be able to make my_free without crash in case of error */
 | |
|   iterator->buffer= 0;
 | |
| 
 | |
|   if (!(dirp = my_dir(fl_dir, MYF(0))))
 | |
|   {
 | |
|     return HA_ITERATOR_ERROR;
 | |
|   }
 | |
|   if ((ptr= (uchar*)my_malloc(ALIGN_SIZE(sizeof(fl_buff)) +
 | |
|                              ((ALIGN_SIZE(sizeof(LEX_STRING)) +
 | |
|                                sizeof(enum log_status) +
 | |
|                                + FN_REFLEN + 1) *
 | |
|                               (uint) dirp->number_off_files),
 | |
|                              MYF(0))) == 0)
 | |
|   {
 | |
|     return HA_ITERATOR_ERROR;
 | |
|   }
 | |
|   buff= (struct fl_buff *)ptr;
 | |
|   buff->entries= buff->current= 0;
 | |
|   ptr= ptr + (ALIGN_SIZE(sizeof(fl_buff)));
 | |
|   buff->names= (LEX_STRING*) (ptr);
 | |
|   ptr= ptr + ((ALIGN_SIZE(sizeof(LEX_STRING)) *
 | |
|                (uint) dirp->number_off_files));
 | |
|   buff->statuses= (enum log_status *)(ptr);
 | |
|   name_ptr= (char *)(ptr + (sizeof(enum log_status) *
 | |
|                             (uint) dirp->number_off_files));
 | |
|   for (i=0 ; i < (uint) dirp->number_off_files  ; i++)
 | |
|   {
 | |
|     enum log_status st;
 | |
|     file= dirp->dir_entry + i;
 | |
|     if ((file->name[0] == '.' &&
 | |
|          ((file->name[1] == '.' && file->name[2] == '\0') ||
 | |
|             file->name[1] == '\0')))
 | |
|       continue;
 | |
|     if ((st= fl_get_log_status(file->name)) == HA_LOG_STATUS_NOSUCHLOG)
 | |
|       continue;
 | |
|     name_ptr= strxnmov(buff->names[buff->entries].str= name_ptr,
 | |
|                        FN_REFLEN, fl_dir, file->name, NullS);
 | |
|     buff->names[buff->entries].length= (name_ptr -
 | |
|                                         buff->names[buff->entries].str);
 | |
|     buff->statuses[buff->entries]= st;
 | |
|     buff->entries++;
 | |
|   }
 | |
| 
 | |
|   iterator->buffer= buff;
 | |
|   iterator->next= &fl_log_iterator_next;
 | |
|   iterator->destroy= &fl_log_iterator_destroy;
 | |
|   return HA_ITERATOR_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* An example of a iterator creator */
 | |
| enum handler_create_iterator_result
 | |
| fl_create_iterator(enum handler_iterator_type type,
 | |
|                    struct handler_iterator *iterator)
 | |
| {
 | |
|   switch(type) {
 | |
|   case HA_TRANSACTLOG_ITERATOR:
 | |
|     return fl_log_iterator_buffer_init(iterator);
 | |
|   default:
 | |
|     return HA_ITERATOR_UNSUPPORTED;
 | |
|   }
 | |
| }
 | |
| #endif /*TRANS_LOG_MGM_EXAMPLE_CODE*/
 |