diff options
Diffstat (limited to 'sql/handler.cc')
-rw-r--r-- | sql/handler.cc | 2724 |
1 files changed, 1907 insertions, 817 deletions
diff --git a/sql/handler.cc b/sql/handler.cc index bfb7e8c369f..3939873ec7d 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -13,154 +13,56 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +/** @file handler.cc -/* Handler-calling-functions */ + @brief + Handler-calling-functions +*/ #ifdef USE_PRAGMA_IMPLEMENTATION #pragma implementation // gcc: Class implementation #endif #include "mysql_priv.h" -#include "ha_heap.h" -#include "ha_myisam.h" -#include "ha_myisammrg.h" - - -/* - We have dummy hanldertons in case the handler has not been compiled - in. This will be removed in 5.1. -*/ -#ifdef HAVE_BERKELEY_DB -#include "ha_berkeley.h" -extern handlerton berkeley_hton; -#else -handlerton berkeley_hton = { "BerkeleyDB", SHOW_OPTION_NO, - "Supports transactions and page-level locking", DB_TYPE_BERKELEY_DB, NULL, - 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, HTON_NO_FLAGS }; -#endif -#ifdef HAVE_BLACKHOLE_DB -#include "ha_blackhole.h" -extern handlerton blackhole_hton; -#else -handlerton blackhole_hton = { "BLACKHOLE", SHOW_OPTION_NO, - "/dev/null storage engine (anything you write to it disappears)", - DB_TYPE_BLACKHOLE_DB, NULL, 0, 0, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - HTON_NO_FLAGS }; -#endif -#ifdef HAVE_EXAMPLE_DB -#include "examples/ha_example.h" -extern handlerton example_hton; -#else -handlerton example_hton = { "EXAMPLE", SHOW_OPTION_NO, - "Example storage engine", - DB_TYPE_EXAMPLE_DB, NULL, 0, 0, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - HTON_NO_FLAGS }; -#endif -#if defined(HAVE_ARCHIVE_DB) -#include "ha_archive.h" -extern handlerton archive_hton; -#else -handlerton archive_hton = { "ARCHIVE", SHOW_OPTION_NO, - "Archive storage engine", DB_TYPE_ARCHIVE_DB, NULL, 0, 0, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - HTON_NO_FLAGS }; -#endif -#ifdef HAVE_CSV_DB -#include "examples/ha_tina.h" -extern handlerton tina_hton; -#else -handlerton tina_hton = { "CSV", SHOW_OPTION_NO, "CSV storage engine", - DB_TYPE_CSV_DB, NULL, 0, 0, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - HTON_NO_FLAGS }; -#endif -#ifdef HAVE_INNOBASE_DB -#include "ha_innodb.h" -extern handlerton innobase_hton; -#else -handlerton innobase_hton = { "InnoDB", SHOW_OPTION_NO, - "Supports transactions, row-level locking, and foreign keys", - DB_TYPE_INNODB, NULL, 0, 0, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - HTON_NO_FLAGS }; -#endif -#ifdef HAVE_NDBCLUSTER_DB -#include "ha_ndbcluster.h" -extern handlerton ndbcluster_hton; -#else -handlerton ndbcluster_hton = { "ndbcluster", SHOW_OPTION_NO, - "Clustered, fault-tolerant, memory-based tables", - DB_TYPE_NDBCLUSTER, NULL, 0, 0, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - HTON_NO_FLAGS }; -#endif -#ifdef HAVE_FEDERATED_DB -#include "ha_federated.h" -extern handlerton federated_hton; -#else -handlerton federated_hton = { "FEDERATED", SHOW_OPTION_NO, - "Federated MySQL storage engine", DB_TYPE_FEDERATED_DB, NULL, 0, 0, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - HTON_NO_FLAGS }; -#endif +#include "rpl_filter.h" #include <myisampack.h> #include <errno.h> -extern handlerton myisam_hton; -extern handlerton myisammrg_hton; -extern handlerton heap_hton; -extern handlerton binlog_hton; +#ifdef WITH_PARTITION_STORAGE_ENGINE +#include "ha_partition.h" +#endif /* - Obsolete + 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 */ -handlerton isam_hton = { "ISAM", SHOW_OPTION_NO, "Obsolete storage engine", - DB_TYPE_ISAM, NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, HTON_NO_FLAGS }; +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} }; /* number of entries in handlertons[] */ -ulong total_ha; +ulong total_ha= 0; /* number of storage engines (from handlertons[]) that support 2pc */ -ulong total_ha_2pc; +ulong total_ha_2pc= 0; /* size of savepoint storage area (see ha_init) */ -ulong savepoint_alloc_size; +ulong savepoint_alloc_size= 0; -/* - This array is used for processing compiled in engines. -*/ -handlerton *sys_table_types[]= -{ - &myisam_hton, - &heap_hton, - &innobase_hton, - &berkeley_hton, - &blackhole_hton, - &example_hton, - &archive_hton, - &tina_hton, - &ndbcluster_hton, - &federated_hton, - &myisammrg_hton, - &binlog_hton, - &isam_hton, - NULL -}; - -struct show_table_alias_st sys_table_aliases[]= +static const LEX_STRING sys_table_aliases[]= { - {"INNOBASE", "InnoDB"}, - {"NDB", "NDBCLUSTER"}, - {"BDB", "BERKELEYDB"}, - {"HEAP", "MEMORY"}, - {"MERGE", "MRG_MYISAM"}, - {NullS, NullS} + { 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", "?","?","?" + "", "FIXED", "DYNAMIC", "COMPRESSED", "REDUNDANT", "COMPACT", "PAGE", "?","?","?" }; const char *tx_isolation_names[] = @@ -172,197 +74,213 @@ TYPELIB tx_isolation_typelib= {array_elements(tx_isolation_names)-1,"", static TYPELIB known_extensions= {0,"known_exts", NULL, NULL}; uint known_extensions_id= 0; -enum db_type ha_resolve_by_name(const char *name, uint namelen) + + +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) { - THD *thd= current_thd; - show_table_alias_st *table_alias; - handlerton **types; + plugin_ref plugin= ha_default_plugin(thd); + DBUG_ASSERT(plugin); + handlerton *hton= plugin_data(plugin, handlerton*); + DBUG_ASSERT(hton); + return hton; +} - if (thd && !my_strnncoll(&my_charset_latin1, - (const uchar *)name, namelen, - (const uchar *)"DEFAULT", 7)) - return (enum db_type) thd->variables.table_type; -retest: - for (types= sys_table_types; *types; types++) +/** @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))) { - if (!my_strnncoll(&my_charset_latin1, - (const uchar *)name, namelen, - (const uchar *)(*types)->name, strlen((*types)->name))) - return (enum db_type) (*types)->db_type; + 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->type; table_alias++) + for (table_alias= sys_table_aliases; table_alias->str; table_alias+= 2) { if (!my_strnncoll(&my_charset_latin1, - (const uchar *)name, namelen, - (const uchar *)table_alias->alias, - strlen(table_alias->alias))) + (const uchar *)name->str, name->length, + (const uchar *)table_alias->str, table_alias->length)) { - name= table_alias->type; - namelen= strlen(name); - goto retest; + name= table_alias + 1; + goto redo; } } - return DB_TYPE_UNKNOWN; + return NULL; } -const char *ha_get_storage_engine(enum db_type db_type) +plugin_ref ha_lock_engine(THD *thd, handlerton *hton) { - handlerton **types; - for (types= sys_table_types; *types; types++) + if (hton) { - if (db_type == (*types)->db_type) - return (*types)->name; + 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 "*NONE*"; + return NULL; } -bool ha_check_storage_engine_flag(enum db_type db_type, uint32 flag) +#ifdef NOT_USED +static handler *create_default(TABLE_SHARE *table, MEM_ROOT *mem_root) { - handlerton **types; - for (types= sys_table_types; *types; types++) - { - if (db_type == (*types)->db_type) - return test((*types)->flags & flag); - } - return FALSE; // No matching engine + handlerton *hton= ha_default_handlerton(current_thd); + return (hton && hton->create) ? hton->create(hton, table, mem_root) : NULL; } +#endif -my_bool ha_storage_engine_is_enabled(enum db_type database_type) +handlerton *ha_resolve_by_legacy_type(THD *thd, enum legacy_db_type db_type) { - handlerton **types; - for (types= sys_table_types; *types; types++) - { - if ((database_type == (*types)->db_type) && - ((*types)->state == SHOW_OPTION_YES)) - return TRUE; + 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; } - return FALSE; } -/* Use other database handler if databasehandler is not compiled in */ - -enum db_type ha_checktype(THD *thd, enum db_type database_type, +/** @brief + 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) { - if (ha_storage_engine_is_enabled(database_type)) - return database_type; + 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_get_storage_engine(database_type); + const char *engine_name= ha_resolve_storage_engine_name(hton); my_error(ER_FEATURE_DISABLED,MYF(0),engine_name,engine_name); } - return DB_TYPE_UNKNOWN; + return NULL; } switch (database_type) { #ifndef NO_HASH case DB_TYPE_HASH: - return (database_type); + return ha_resolve_by_legacy_type(thd, DB_TYPE_HASH); #endif case DB_TYPE_MRG_ISAM: - return (DB_TYPE_MRG_MYISAM); + return ha_resolve_by_legacy_type(thd, DB_TYPE_MRG_MYISAM); default: break; } - return ((enum db_type) thd->variables.table_type != DB_TYPE_UNKNOWN ? - (enum db_type) thd->variables.table_type : - ((enum db_type) global_system_variables.table_type != - DB_TYPE_UNKNOWN ? - (enum db_type) global_system_variables.table_type : DB_TYPE_MYISAM) - ); + return ha_default_handlerton(thd); } /* ha_checktype */ -handler *get_new_handler(TABLE *table, MEM_ROOT *alloc, enum db_type db_type) +handler *get_new_handler(TABLE_SHARE *share, MEM_ROOT *alloc, + handlerton *db_type) { - switch (db_type) { -#ifndef NO_HASH - case DB_TYPE_HASH: - return new (alloc) ha_hash(table); -#endif - case DB_TYPE_MRG_MYISAM: - case DB_TYPE_MRG_ISAM: - if (have_merge_db == SHOW_OPTION_YES) - return new (alloc) ha_myisammrg(table); - return NULL; -#ifdef HAVE_BERKELEY_DB - case DB_TYPE_BERKELEY_DB: - if (have_berkeley_db == SHOW_OPTION_YES) - return new (alloc) ha_berkeley(table); - return NULL; -#endif -#ifdef HAVE_INNOBASE_DB - case DB_TYPE_INNODB: - if (have_innodb == SHOW_OPTION_YES) - return new (alloc) ha_innobase(table); - return NULL; -#endif -#ifdef HAVE_EXAMPLE_DB - case DB_TYPE_EXAMPLE_DB: - if (have_example_db == SHOW_OPTION_YES) - return new (alloc) ha_example(table); - return NULL; -#endif -#if defined(HAVE_ARCHIVE_DB) - case DB_TYPE_ARCHIVE_DB: - if (have_archive_db == SHOW_OPTION_YES) - return new (alloc) ha_archive(table); - return NULL; -#endif -#ifdef HAVE_BLACKHOLE_DB - case DB_TYPE_BLACKHOLE_DB: - if (have_blackhole_db == SHOW_OPTION_YES) - return new (alloc) ha_blackhole(table); - return NULL; -#endif -#ifdef HAVE_FEDERATED_DB - case DB_TYPE_FEDERATED_DB: - if (have_federated_db == SHOW_OPTION_YES) - return new (alloc) ha_federated(table); - return NULL; -#endif -#ifdef HAVE_CSV_DB - case DB_TYPE_CSV_DB: - if (have_csv_db == SHOW_OPTION_YES) - return new (alloc) ha_tina(table); - return NULL; -#endif -#ifdef HAVE_NDBCLUSTER_DB - case DB_TYPE_NDBCLUSTER: - if (have_ndbcluster == SHOW_OPTION_YES) - return new (alloc) ha_ndbcluster(table); - return NULL; -#endif - case DB_TYPE_HEAP: - return new (alloc) ha_heap(table); - default: // should never happen + 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) { - enum db_type def=(enum db_type) current_thd->variables.table_type; - /* Try first with 'default table type' */ - if (db_type != def) - return get_new_handler(table, alloc, def); + if ((file= db_type->create(db_type, share, alloc))) + file->init(); + DBUG_RETURN(file); } - /* Fall back to MyISAM */ - case DB_TYPE_MYISAM: - return new (alloc) ha_myisam(table); + /* + 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->initialise_partition(current_thd->mem_root)) + { + delete partition; + partition= 0; + } + else + partition->init(); } + else + { + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(ha_partition)); + } + DBUG_RETURN(((handler*) partition)); } +#endif -/* + +/** @brief Register handler error messages for use with my_error(). SYNOPSIS @@ -372,7 +290,6 @@ handler *get_new_handler(TABLE *table, MEM_ROOT *alloc, enum db_type db_type) 0 OK != 0 Error */ - static int ha_init_errors(void) { #define SETMSG(nr, msg) errmsgs[(nr) - HA_ERR_FIRST]= (msg) @@ -421,6 +338,7 @@ static int ha_init_errors(void) SETMSG(HA_ERR_TABLE_EXIST, ER(ER_TABLE_EXISTS_ERROR)); SETMSG(HA_ERR_NO_CONNECTION, "Could not connect to storage engine"); SETMSG(HA_ERR_TABLE_DEF_CHANGED, ER(ER_TABLE_DEF_CHANGED)); + SETMSG(HA_ERR_FOREIGN_DUPLICATE_KEY, "FK constraint would lead to duplicate key"); SETMSG(HA_ERR_TABLE_NEEDS_UPGRADE, ER(ER_TABLE_NEEDS_UPGRADE)); SETMSG(HA_ERR_TABLE_READONLY, ER(ER_OPEN_AS_READONLY)); SETMSG(HA_ERR_AUTOINC_READ_FAILED, ER(ER_AUTOINC_READ_FAILED)); @@ -431,7 +349,7 @@ static int ha_init_errors(void) } -/* +/** @brief Unregister handler error messages. SYNOPSIS @@ -441,7 +359,6 @@ static int ha_init_errors(void) 0 OK != 0 Error */ - static int ha_finish_errors(void) { const char **errmsgs; @@ -449,40 +366,147 @@ static int ha_finish_errors(void) /* Allocate a pointer array for the error message strings. */ if (! (errmsgs= my_error_unregister(HA_ERR_FIRST, HA_ERR_LAST))) return 1; - my_free((gptr) errmsgs, MYF(0)); + my_free((uchar*) errmsgs, MYF(0)); return 0; } -static inline void ha_was_inited_ok(handlerton **ht) +int ha_finalize_handlerton(st_plugin_int *plugin) { - uint tmp= (*ht)->savepoint_offset; - (*ht)->savepoint_offset= savepoint_alloc_size; - savepoint_alloc_size+= tmp; - (*ht)->slot= total_ha++; - if ((*ht)->prepare) - total_ha_2pc++; + handlerton *hton= (handlerton *)plugin->data; + DBUG_ENTER("ha_finalize_handlerton"); + + 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)); + } + } + + my_free((uchar*)hton, MYF(0)); + + DBUG_RETURN(0); } -int ha_init() + +int ha_initialize_handlerton(st_plugin_int *plugin) { - int error= 0; - handlerton **types; - total_ha= savepoint_alloc_size= 0; + handlerton *hton; + DBUG_ENTER("ha_initialize_handlerton"); + DBUG_PRINT("plugin", ("initialize plugin: '%s'", plugin->name.str)); - if (ha_init_errors()) - return 1; + hton= (handlerton *)my_malloc(sizeof(handlerton), + MYF(MY_WME | MY_ZEROFILL)); + /* Historical Requirement */ + plugin->data= hton; // shortcut for the future + if (plugin->plugin->init) + { + if (plugin->plugin->init(hton)) + { + sql_print_error("Plugin '%s' init function returned error.", + plugin->name.str); + goto err; + } + } /* - We now initialize everything here. + the switch below and hton->state should be removed when + command-line options for plugins will be implemented */ - for (types= sys_table_types; *types; types++) - { - if (!(*types)->init || !(*types)->init()) - ha_was_inited_ok(types); - else - (*types)->state= SHOW_OPTION_DISABLED; + switch (hton->state) { + case SHOW_OPTION_NO: + break; + case SHOW_OPTION_YES: + { + uint tmp; + /* 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!"); + DBUG_RETURN(1); + } + 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; + } + installed_htons[hton->db_type]= hton; + tmp= hton->savepoint_offset; + hton->savepoint_offset= savepoint_alloc_size; + savepoint_alloc_size+= tmp; + hton->slot= total_ha++; + 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: + DBUG_RETURN(1); +} + +int ha_init() +{ + int error= 0; + DBUG_ENTER("ha_init"); + + if (ha_init_errors()) + DBUG_RETURN(1); DBUG_ASSERT(total_ha < MAX_HA); /* @@ -492,79 +516,69 @@ int ha_init() */ opt_using_transactions= total_ha>(ulong)opt_bin_log; savepoint_alloc_size+= sizeof(SAVEPOINT); - return error; + DBUG_RETURN(error); } - /* close, flush or restart databases */ - /* Ignore this for other databases than ours */ - -int ha_panic(enum ha_panic_function flag) +int ha_end() { - int error=0; -#ifndef NO_HASH - error|=h_panic(flag); /* fix hash */ -#endif -#ifdef HAVE_ISAM - error|=mrg_panic(flag); - error|=nisam_panic(flag); -#endif - error|=heap_panic(flag); - error|=mi_panic(flag); - error|=myrg_panic(flag); -#ifdef HAVE_BERKELEY_DB - if (have_berkeley_db == SHOW_OPTION_YES) - error|=berkeley_end(); -#endif -#ifdef HAVE_INNOBASE_DB - if (have_innodb == SHOW_OPTION_YES) - error|=innobase_end(); -#endif -#ifdef HAVE_NDBCLUSTER_DB - if (have_ndbcluster == SHOW_OPTION_YES) - error|=ndbcluster_end(); -#endif -#ifdef HAVE_FEDERATED_DB - if (have_federated_db == SHOW_OPTION_YES) - error|= federated_db_end(); -#endif -#if defined(HAVE_ARCHIVE_DB) - if (have_archive_db == SHOW_OPTION_YES) - error|= archive_db_end(); -#endif -#ifdef HAVE_CSV_DB - if (have_csv_db == SHOW_OPTION_YES) - error|= tina_end(); -#endif + 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; - return error; -} /* ha_panic */ + + 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) { -#ifdef HAVE_INNOBASE_DB - if (have_innodb == SHOW_OPTION_YES) - innobase_drop_database(path); -#endif -#ifdef HAVE_NDBCLUSTER_DB - if (have_ndbcluster == SHOW_OPTION_YES) - ndbcluster_drop_database(path); -#endif + 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 && hton->close_connection && + thd_get_ha_data(thd, hton)) + hton->close_connection(hton, thd); + return FALSE; } -/* don't bother to rollback here, it's done already */ + +/** @brief + don't bother to rollback here, it's done already +*/ void ha_close_connection(THD* thd) { - handlerton **types; - for (types= sys_table_types; *types; types++) - if (thd->ha_data[(*types)->slot]) - (*types)->close_connection(thd); + plugin_foreach(thd, closecon_handlerton, MYSQL_STORAGE_ENGINE_PLUGIN, 0); } /* ======================================================================== ======================= TRANSACTIONS ===================================*/ -/* +/** @brief Register a storage engine for a transaction DESCRIPTION @@ -606,7 +620,7 @@ void trans_register_ha(THD *thd, bool all, handlerton *ht_arg) DBUG_VOID_RETURN; } -/* +/** @brief RETURN 0 - ok 1 - error, transaction was rolled back @@ -623,10 +637,10 @@ int ha_prepare(THD *thd) for (; *ht; ht++) { int err; - statistic_increment(thd->status_var.ha_prepare_count,&LOCK_status); + status_var_increment(thd->status_var.ha_prepare_count); if ((*ht)->prepare) { - if ((err= (*(*ht)->prepare)(thd, all))) + if ((err= (*(*ht)->prepare)(*ht, thd, all))) { my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); ha_rollback_trans(thd, all); @@ -637,7 +651,8 @@ int ha_prepare(THD *thd) else { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), (*ht)->name); + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + ha_resolve_storage_engine_name(*ht)); } } } @@ -645,7 +660,7 @@ int ha_prepare(THD *thd) DBUG_RETURN(error); } -/* +/** @brief RETURN 0 - ok 1 - transaction was rolled back @@ -688,6 +703,19 @@ int ha_commit_trans(THD *thd, bool all) ha_rollback_trans(thd, all); DBUG_RETURN(1); } + + if ( is_real_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; + } + DBUG_EXECUTE_IF("crash_commit_before", abort();); /* Close all cursors that can not survive COMMIT */ @@ -699,12 +727,12 @@ int ha_commit_trans(THD *thd, bool all) for (; *ht && !error; ht++) { int err; - if ((err= (*(*ht)->prepare)(thd, all))) + if ((err= (*(*ht)->prepare)(*ht, thd, all))) { my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); error= 1; } - statistic_increment(thd->status_var.ha_prepare_count,&LOCK_status); + status_var_increment(thd->status_var.ha_prepare_count); } DBUG_EXECUTE_IF("crash_commit_after_prepare", abort();); if (error || (is_real_trans && xid && @@ -729,7 +757,7 @@ end: DBUG_RETURN(error); } -/* +/** @brief NOTE - this function does not care about global read lock. A caller should. */ @@ -746,12 +774,12 @@ int ha_commit_one_phase(THD *thd, bool all) for (ht=trans->ht; *ht; ht++) { int err; - if ((err= (*(*ht)->commit)(thd, all))) + if ((err= (*(*ht)->commit)(*ht, thd, all))) { my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); error=1; } - statistic_increment(thd->status_var.ha_commit_count,&LOCK_status); + status_var_increment(thd->status_var.ha_commit_count); *ht= 0; } trans->nht=0; @@ -802,12 +830,12 @@ int ha_rollback_trans(THD *thd, bool all) for (handlerton **ht=trans->ht; *ht; ht++) { int err; - if ((err= (*(*ht)->rollback)(thd, all))) + if ((err= (*(*ht)->rollback)(*ht, thd, all))) { // cannot happen my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err); error=1; } - statistic_increment(thd->status_var.ha_rollback_count,&LOCK_status); + status_var_increment(thd->status_var.ha_rollback_count); *ht= 0; } trans->nht=0; @@ -834,14 +862,14 @@ int ha_rollback_trans(THD *thd, bool all) 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->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)); DBUG_RETURN(error); } -/* +/** @brief This is used to commit or rollback a single statement depending on the value of error. Note that if the autocommit is on, then the following call inside InnoDB will commit or rollback the whole transaction (= the statement). The @@ -849,7 +877,6 @@ int ha_rollback_trans(THD *thd, bool all) the user has used LOCK TABLES then that mechanism does not know to do the commit. */ - int ha_autocommit_or_rollback(THD *thd, int error) { DBUG_ENTER("ha_autocommit_or_rollback"); @@ -873,21 +900,46 @@ int ha_autocommit_or_rollback(THD *thd, int error) } -int ha_commit_or_rollback_by_xid(XID *xid, bool commit) +struct xahton_st { + XID *xid; + int result; +}; + +static my_bool xacommit_handlerton(THD *unused1, plugin_ref plugin, + void *arg) { - handlerton **types; - int res= 1; + 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; +} - for (types= sys_table_types; *types; types++) +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) { - if ((*types)->state == SHOW_OPTION_YES && (*types)->recover) - { - if ((*(commit ? (*types)->commit_by_xid : - (*types)->rollback_by_xid))(xid)) - res= 0; - } + hton->rollback_by_xid(hton, ((struct xahton_st *)arg)->xid); + ((struct xahton_st *)arg)->result= 0; } - return res; + 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; } @@ -944,7 +996,7 @@ static char* xid_to_str(char *buf, XID *xid) } #endif -/* +/** @brief recover() step of xa NOTE @@ -963,99 +1015,122 @@ static char* xid_to_str(char *buf, XID *xid) in this case commit_list==0, tc_heuristic_recover == 0 there should be no prepared transactions in this case. */ -int ha_recover(HASH *commit_list) +struct xarecover_st { - int len, got, found_foreign_xids=0, found_my_xids=0; - handlerton **types; - XID *list=0; - bool dry_run=(commit_list==0 && tc_heuristic_recover==0); - DBUG_ENTER("ha_recover"); - - /* commit_list and tc_heuristic_recover cannot be set both */ - DBUG_ASSERT(commit_list==0 || tc_heuristic_recover==0); - /* if either is set, total_ha_2pc must be set too */ - DBUG_ASSERT(dry_run || total_ha_2pc>(ulong)opt_bin_log); - - if (total_ha_2pc <= (ulong)opt_bin_log) - DBUG_RETURN(0); - - if (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 - dry_run=FALSE; -#endif + int len, found_foreign_xids, found_my_xids; + XID *list; + HASH *commit_list; + bool dry_run; +}; - for (len= MAX_XID_LIST_SIZE ; list==0 && len > MIN_XID_LIST_SIZE; len/=2) - { - list=(XID *)my_malloc(len*sizeof(XID), MYF(0)); - } - if (!list) - { - sql_print_error(ER(ER_OUTOFMEMORY), len*sizeof(XID)); - DBUG_RETURN(1); - } +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; - for (types= sys_table_types; *types; types++) + if (hton->state == SHOW_OPTION_YES && hton->recover) { - if ((*types)->state != SHOW_OPTION_YES || !(*types)->recover) - continue; - while ((got=(*(*types)->recover)(list, len)) > 0 ) + while ((got= hton->recover(hton, info->list, info->len)) > 0 ) { sql_print_information("Found %d prepared transaction(s) in %s", - got, (*types)->name); + got, ha_resolve_storage_engine_name(hton)); for (int i=0; i < got; i ++) { - my_xid x=list[i].get_my_xid(); + 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, list+i)); + sql_print_information("ignore xid %s", xid_to_str(buf, info->list+i)); #endif - xid_cache_insert(list+i, XA_PREPARED); - found_foreign_xids++; + xid_cache_insert(info->list+i, XA_PREPARED); + info->found_foreign_xids++; continue; } - if (dry_run) + if (info->dry_run) { - found_my_xids++; + info->found_my_xids++; continue; } // recovery mode - if (commit_list ? - hash_search(commit_list, (byte *)&x, sizeof(x)) != 0 : + if (info->commit_list ? + 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, list+i)); + sql_print_information("commit xid %s", xid_to_str(buf, info->list+i)); #endif - (*(*types)->commit_by_xid)(list+i); + 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, list+i)); + sql_print_information("rollback xid %s", + xid_to_str(buf, info->list+i)); #endif - (*(*types)->rollback_by_xid)(list+i); + hton->rollback_by_xid(hton, info->list+i); } } - if (got < len) + if (got < info->len) break; } } - my_free((gptr)list, MYF(0)); - if (found_foreign_xids) - sql_print_warning("Found %d prepared XA transactions", found_foreign_xids); - if (dry_run && found_my_xids) + 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), info.len*sizeof(XID)); + DBUG_RETURN(1); + } + + plugin_foreach(NULL, xarecover_handlerton, + MYSQL_STORAGE_ENGINE_PLUGIN, &info); + + my_free((uchar*)info.list, MYF(0)); + 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 " @@ -1063,15 +1138,15 @@ int ha_recover(HASH *commit_list) "after a crash. You have to start mysqld with " "--tc-heuristic-recover switch to commit or rollback " "pending transactions.", - found_my_xids, opt_tc_log_file); + info.found_my_xids, opt_tc_log_file); DBUG_RETURN(1); } - if (commit_list) + if (info.commit_list) sql_print_information("Crash recovery finished."); DBUG_RETURN(0); } -/* +/** @brief return the list of XID's to a client, the same way SHOW commands do NOTE @@ -1120,7 +1195,7 @@ bool mysql_xa_recover(THD *thd) DBUG_RETURN(0); } -/* +/** @brief 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 @@ -1136,28 +1211,23 @@ bool mysql_xa_recover(THD *thd) thd: the thread handle of the current connection return value: always 0 */ - -int ha_release_temporary_latches(THD *thd) +static my_bool release_temporary_latches(THD *thd, plugin_ref plugin, + void *unused) { -#ifdef HAVE_INNOBASE_DB - if (opt_innodb) - innobase_release_temporary_latches(thd); -#endif - return 0; -} + handlerton *hton= plugin_data(plugin, handlerton *); + if (hton->state == SHOW_OPTION_YES && hton->release_temporary_latches) + hton->release_temporary_latches(hton, thd); -/* - Export statistics for different engines. Currently we use it only for - InnoDB. -*/ + return FALSE; +} -int ha_update_statistics() + +int ha_release_temporary_latches(THD *thd) { -#ifdef HAVE_INNOBASE_DB - if (opt_innodb) - innodb_export_status(); -#endif + plugin_foreach(thd, release_temporary_latches, MYSQL_STORAGE_ENGINE_PLUGIN, + NULL); + return 0; } @@ -1180,12 +1250,12 @@ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv) { int err; DBUG_ASSERT((*ht)->savepoint_set != 0); - if ((err= (*(*ht)->savepoint_rollback)(thd, (byte *)(sv+1)+(*ht)->savepoint_offset))) + 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; } - statistic_increment(thd->status_var.ha_savepoint_rollback_count,&LOCK_status); + status_var_increment(thd->status_var.ha_savepoint_rollback_count); trans->no_2pc|=(*ht)->prepare == 0; } /* @@ -1195,23 +1265,22 @@ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv) for (; *ht ; ht++) { int err; - if ((err= (*(*ht)->rollback)(thd, !thd->in_sub_stmt))) + if ((err= (*(*ht)->rollback)(*ht, thd, !thd->in_sub_stmt))) { // cannot happen my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err); error=1; } - statistic_increment(thd->status_var.ha_rollback_count,&LOCK_status); + status_var_increment(thd->status_var.ha_rollback_count); *ht=0; // keep it conveniently zero-filled } DBUG_RETURN(error); } -/* +/** @brief note, that 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; @@ -1229,12 +1298,12 @@ int ha_savepoint(THD *thd, SAVEPOINT *sv) error=1; break; } - if ((err= (*(*ht)->savepoint_set)(thd, (byte *)(sv+1)+(*ht)->savepoint_offset))) + 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; } - statistic_increment(thd->status_var.ha_savepoint_count,&LOCK_status); + status_var_increment(thd->status_var.ha_savepoint_count); } sv->nht=trans->nht; #endif /* USING_TRANSACTIONS */ @@ -1255,7 +1324,9 @@ int ha_release_savepoint(THD *thd, SAVEPOINT *sv) int err; if (!(*ht)->savepoint_release) continue; - if ((err= (*(*ht)->savepoint_release)(thd, (byte *)(sv+1)+(*ht)->savepoint_offset))) + 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; @@ -1265,47 +1336,90 @@ int ha_release_savepoint(THD *thd, SAVEPOINT *sv) } +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) { -#ifdef HAVE_INNOBASE_DB - if ((have_innodb == SHOW_OPTION_YES) && - !innobase_start_trx_and_assign_read_view(thd)) - return 0; -#endif + 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: */ - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, - "This MySQL server does not support any " - "consistent-read capable storage engine"); + 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; } -bool ha_flush_logs() +static my_bool flush_handlerton(THD *thd, plugin_ref plugin, + void *arg) { - bool result=0; -#ifdef HAVE_BERKELEY_DB - if ((have_berkeley_db == SHOW_OPTION_YES) && - berkeley_flush_logs()) - result=1; -#endif -#ifdef HAVE_INNOBASE_DB - if ((have_innodb == SHOW_OPTION_YES) && - innobase_flush_logs()) - result=1; -#endif - return result; + 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; +} + +static const char *check_lowercase_names(handler *file, const char *path, + char *tmp_path) +{ + if (lower_case_table_names != 2 || (file->ha_table_flags() & HA_FILE_BASED)) + 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; +} + + +/** @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, enum db_type table_type, const char *path, - const char *alias, bool generate_warning) +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]; @@ -1319,17 +1433,11 @@ int ha_delete_table(THD *thd, enum db_type table_type, const char *path, dummy_table.s= &dummy_share; /* DB_TYPE_UNKNOWN is used in ALTER TABLE when renaming only .frm files */ - if (table_type == DB_TYPE_UNKNOWN || - ! (file=get_new_handler(&dummy_table, thd->mem_root, table_type))) + if (table_type == NULL || + ! (file=get_new_handler((TABLE_SHARE*)0, thd->mem_root, table_type))) DBUG_RETURN(ENOENT); - if (lower_case_table_names == 2 && !(file->table_flags() & HA_FILE_BASED)) - { - /* Ensure that table handler get path in lower case */ - strmov(tmp_path, path); - my_casedn_str(files_charset_info, tmp_path); - path= tmp_path; - } + path= check_lowercase_names(file, path, tmp_path); if ((error= file->delete_table(path)) && generate_warning) { /* @@ -1353,9 +1461,16 @@ int ha_delete_table(THD *thd, enum db_type table_type, const char *path, thd->net.last_error[0]= 0; /* Fill up strucutures that print_error may need */ - dummy_table.s->path= path; + 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->table_share= &dummy_share; + file->table= &dummy_table; file->print_error(error, 0); strmake(new_error, thd->net.last_error, sizeof(buff)-1); @@ -1376,24 +1491,54 @@ int ha_delete_table(THD *thd, enum db_type table_type, const char *path, ****************************************************************************/ handler *handler::clone(MEM_ROOT *mem_root) { - handler *new_handler= get_new_handler(table, mem_root, table->s->db_type); - if (new_handler && !new_handler->ha_open(table->s->path, table->db_stat, + handler *new_handler= get_new_handler(table->s, mem_root, table->s->db_type()); + if (new_handler && !new_handler->ha_open(table, + table->s->normalized_path.str, + table->db_stat, HA_OPEN_IGNORE_IF_LOCKED)) return new_handler; return NULL; } - /* Open database-handler. Try O_RDONLY if can't open as O_RDWR */ - /* Don't wait for locks if not HA_OPEN_WAIT_IF_LOCKED is set */ -int handler::ha_open(const char *name, int mode, int test_if_locked) +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; +} + + +/** @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, table->s->db_type, table->db_stat, mode, - test_if_locked)); + 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))) { @@ -1406,7 +1551,7 @@ int handler::ha_open(const char *name, int mode, int test_if_locked) } if (error) { - my_errno=error; /* Safeguard */ + my_errno= error; /* Safeguard */ DBUG_PRINT("error",("error: %d errno: %d",error,errno)); } else @@ -1415,38 +1560,37 @@ int handler::ha_open(const char *name, int mode, int test_if_locked) table->db_stat|=HA_READ_ONLY; (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL - DBUG_ASSERT(alloc_root_inited(&table->mem_root)); - - if (!(ref= (byte*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2))) + if (!(ref= (uchar*) alloc_root(&table->mem_root, ALIGN_SIZE(ref_length)*2))) { close(); error=HA_ERR_OUT_OF_MEM; } else - dupp_ref=ref+ALIGN_SIZE(ref_length); + dup_ref=ref+ALIGN_SIZE(ref_length); + cached_table_flags= table_flags(); } DBUG_RETURN(error); } -/* + +/** @brief Read first row (only) from a table - This is never called for InnoDB or BDB tables, as these table types - has the HA_NOT_EXACT_COUNT set. + This is never called for InnoDB tables, as these table types + has the HA_STATS_RECORDS_IS_EXACT set. */ - -int handler::read_first_row(byte * buf, uint primary_key) +int handler::read_first_row(uchar * buf, uint primary_key) { register int error; DBUG_ENTER("handler::read_first_row"); - statistic_increment(current_thd->status_var.ha_read_first_count,&LOCK_status); + 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 (deleted < 10 || primary_key >= MAX_KEY || + if (stats.deleted < 10 || primary_key >= MAX_KEY || !(index_flags(primary_key, 0, 0) & HA_READ_ORDER)) { (void) ha_rnd_init(1); @@ -1456,25 +1600,29 @@ int handler::read_first_row(byte * buf, uint primary_key) else { /* Find the first row through the primary key */ - (void) ha_index_init(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 +/** @brief + 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 In most cases increment= offset= 1, in which case we get: 1,2,3,4,5,... If increment=10 and offset=5 and previous number is 1, we get: 1,5,15,25,35,... */ - inline ulonglong -next_insert_id(ulonglong nr,struct system_variables *variables) +compute_next_insert_id(ulonglong nr,struct system_variables *variables) { + if (variables->auto_increment_increment == 1) + return (nr+1); // optimization of the formula below nr= (((nr+ variables->auto_increment_increment - variables->auto_increment_offset)) / (ulonglong) variables->auto_increment_increment); @@ -1490,20 +1638,12 @@ void handler::adjust_next_insert_id_after_explicit_value(ulonglong nr) explicitely-specified value larger than this, we need to increase THD::next_insert_id to be greater than the explicit value. */ - THD *thd= table->in_use; - if (thd->clear_next_insert_id && (nr >= thd->next_insert_id)) - { - if (thd->variables.auto_increment_increment != 1) - nr= next_insert_id(nr, &thd->variables); - else - nr++; - thd->next_insert_id= nr; - DBUG_PRINT("info",("next_insert_id: %lu", (ulong) nr)); - } + 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 @@ -1518,7 +1658,6 @@ void handler::adjust_next_insert_id_after_explicit_value(ulonglong nr) RETURN The number X if it exists, "nr" otherwise. */ - inline ulonglong prev_insert_id(ulonglong nr, struct system_variables *variables) { @@ -1547,7 +1686,7 @@ prev_insert_id(ulonglong nr, struct system_variables *variables) Update the auto_increment field if necessary SYNOPSIS - update_auto_increment() + update_auto_increment() RETURN 0 ok @@ -1555,11 +1694,11 @@ prev_insert_id(ulonglong nr, struct system_variables *variables) get_auto_increment() was called and returned ~(ulonglong) 0 HA_ERR_AUTOINC_ERANGE storing value in field caused strict mode failure. - + IMPLEMENTATION - Updates columns with type NEXT_NUMBER if: + Updates the record's Field of type NEXT_NUMBER if: - If column value is set to NULL (in which case auto_increment_field_not_null is 0) @@ -1567,24 +1706,31 @@ prev_insert_id(ulonglong nr, struct system_variables *variables) 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: - There are two different cases when the above is true: + - 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. - - thd->next_insert_id == 0 (This is the normal case) - In this case we set the set the column for the first row to the value - next_insert_id(get_auto_increment(column))) which is normally - max-used-column-value +1. + - 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. - We call get_auto_increment() only for the first row in a multi-row - statement. For the following rows we generate new numbers based on the - last used number. + - 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. - - thd->next_insert_id != 0. This happens when we have read a statement - from the binary log or when one has used SET LAST_INSERT_ID=#. - - In this case we will set the column to the value of next_insert_id. - The next row will be given the id - next_insert_id(next_insert_id) + - 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 idea is that generated auto_increment values are predictable and independent of the column values in the table. This is needed to be @@ -1595,133 +1741,266 @@ prev_insert_id(ulonglong nr, struct system_variables *variables) inserts a column with a higher value than the last used one, we will start counting from the inserted value. - thd->next_insert_id is cleared after it's been used for a statement. + 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). + */ +#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; + ulonglong nr, nb_reserved_values; + bool append= FALSE; THD *thd= table->in_use; struct system_variables *variables= &thd->variables; - bool external_auto_increment= - table->file->table_flags() & HA_EXTERNAL_AUTO_INCREMENT; DBUG_ENTER("handler::update_auto_increment"); /* - We must save the previous value to be able to restore it if the - row was not inserted + next_insert_id is a "cursor" into the reserved interval, it may go greater + than the interval, but not smaller. */ - thd->prev_insert_id= thd->next_insert_id; + 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) { - /* Mark that we didn't generate a new value **/ - auto_increment_column_changed=0; + /* + 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 (external_auto_increment || !(nr= thd->next_insert_id)) - { - if ((nr= get_auto_increment()) == ~(ulonglong) 0) - DBUG_RETURN(HA_ERR_AUTOINC_READ_FAILED); // Mark failure - if (!external_auto_increment && variables->auto_increment_increment != 1) - nr= next_insert_id(nr-1, variables); - /* - Update next row based on the found value. This way we don't have to - call the handler for every generated auto-increment value on a - multi-row statement - */ - thd->next_insert_id= nr; + 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". + */ + uint nb_already_reserved_intervals= + thd->auto_inc_intervals_in_cur_stmt_for_binlog.nb_elements(); + 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). + */ + if (nb_already_reserved_intervals == 0 && + (estimation_rows_to_insert > 0)) + nb_desired_values= estimation_rows_to_insert; + else /* go with the increasing defaults */ + { + /* avoid overflow in formula, with this if() */ + if (nb_already_reserved_intervals <= AUTO_INC_DEFAULT_NB_MAX_BITS) + { + nb_desired_values= AUTO_INC_DEFAULT_NB_ROWS * + (1 << nb_already_reserved_intervals); + 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) 0) + 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")); + } } DBUG_PRINT("info",("auto_increment: %lu", (ulong) nr)); - /* Mark that we should clear next_insert_id before next stmt */ - thd->clear_next_insert_id= 1; - - if (likely(!table->next_number_field->store((longlong) nr, TRUE))) - thd->insert_id((ulonglong) nr); - else - if (thd->killed != THD::KILL_BAD_DATA) /* did we fail strict mode? */ + if (unlikely(table->next_number_field->store((longlong) nr, TRUE))) { /* - overflow of the field; we'll use the max value, however we try to - decrease it to honour auto_increment_* variables: + 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); - thd->insert_id(nr); if (unlikely(table->next_number_field->store((longlong) nr, TRUE))) - thd->insert_id(nr= table->next_number_field->val_int()); + nr= table->next_number_field->val_int(); + } + if (append) + { + auto_inc_interval_for_cur_row.replace(nr, nb_reserved_values, + variables->auto_increment_increment); + /* Row-based replication does not need to store intervals in binlog */ + if (!thd->current_stmt_binlog_row_based) + 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); } - else - DBUG_RETURN(HA_ERR_AUTOINC_ERANGE); /* - We can't set next_insert_id if the auto-increment key is not the - first key part, as there is no guarantee that the first parts will be in - sequence + 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. */ - if (!table->s->next_number_key_offset) - { - /* - Set next insert id to point to next auto-increment value to be able to - handle multi-row statements - This works even if auto_increment_increment > 1 - */ - thd->next_insert_id= next_insert_id(nr, variables); - } - else - thd->next_insert_id= 0; + 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)); - /* Mark that we generated a new value */ - auto_increment_column_changed=1; DBUG_RETURN(0); } -/* - restore_auto_increment - In case of error on write, we restore the last used next_insert_id value - because the previous value was not used. -*/ +/** @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. -void handler::restore_auto_increment() + 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() { - THD *thd= table->in_use; - if (thd->next_insert_id) - { - thd->next_insert_id= thd->prev_insert_id; - if (thd->next_insert_id == 0) - { - /* we didn't generate a value, engine will be called again */ - thd->clear_next_insert_id= 0; - } - } + 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; } -ulonglong handler::get_auto_increment() +/** @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); - index_init(table->s->next_number_index); - if (!table->s->next_number_key_offset) + 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 { - byte key[MAX_KEY_LENGTH]; + 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(table->record[1], key, table->s->next_number_key_offset, - HA_READ_PREFIX_LAST); + 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) @@ -1731,11 +2010,56 @@ ulonglong handler::get_auto_increment() val_int_offset(table->s->rec_buff_length)+1); index_end(); (void) extra(HA_EXTRA_NO_KEYREAD); - return nr; + *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); + 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(), table->key_info[key_nr].name); + } +} + + +/** @brief Print error that we got from handler function NOTE @@ -1744,7 +2068,6 @@ ulonglong handler::get_auto_increment() table->s->path table->alias */ - void handler::print_error(int error, myf errflag) { DBUG_ENTER("handler::print_error"); @@ -1774,30 +2097,35 @@ void handler::print_error(int error, myf errflag) uint key_nr=get_dup_key(error); if ((int) key_nr >= 0) { - /* Write the dupplicated key in the error message */ + 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); - - if (key_nr == MAX_KEY) + /* Table is opened and defined at this point */ + key_unpack(&str,table,(uint) key_nr); + max_length= (MYSQL_ERRMSG_SIZE- + (uint) strlen(ER(ER_FOREIGN_DUPLICATE_KEY))); + if (str.length() >= max_length) { - /* Key is unknown */ - str.copy("", 0, system_charset_info); - key_nr= (uint) -1; + str.length(max_length-4); + str.append(STRING_WITH_LEN("...")); } - else - { - key_unpack(&str,table,(uint) key_nr); - uint max_length=MYSQL_ERRMSG_SIZE-(uint) strlen(ER(ER_DUP_ENTRY)); - if (str.length() >= max_length) - { - str.length(max_length-4); - str.append(STRING_WITH_LEN("...")); - } - } - my_error(ER_DUP_ENTRY, MYF(0), str.c_ptr(), key_nr+1); + my_error(ER_FOREIGN_DUPLICATE_KEY, MYF(0), table_share->table_name.str, + str.c_ptr(), key_nr+1); DBUG_VOID_RETURN; } - textno=ER_DUP_KEY; + textno= ER_DUP_KEY; break; } case HA_ERR_NULL_IN_SPATIAL: @@ -1873,19 +2201,20 @@ void handler::print_error(int error, myf errflag) textno=ER_TABLE_DEF_CHANGED; break; case HA_ERR_NO_SUCH_TABLE: - { - /* - We have to use path to find database name instead of using - table->table_cache_key because if the table didn't exist, then - table_cache_key was not set up - */ - char *db; - char buff[FN_REFLEN]; - uint length= dirname_part(buff,table->s->path); - buff[length-1]=0; - db=buff+dirname_length(buff); - my_error(ER_NO_SUCH_TABLE, MYF(0), db, table->alias); + my_error(ER_NO_SUCH_TABLE, MYF(0), table_share->db.str, + table_share->table_name.str); + break; + 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; @@ -1919,12 +2248,12 @@ void handler::print_error(int error, myf errflag) DBUG_VOID_RETURN; } } - my_error(textno, errflag, table->alias, error); + my_error(textno, errflag, table_share->table_name.str, error); DBUG_VOID_RETURN; } -/* +/** @brief Return an error message specific to this handler SYNOPSIS @@ -1932,8 +2261,7 @@ void handler::print_error(int error, myf errflag) buf Pointer to String where to add error message Returns true if this is a temporary error - */ - +*/ bool handler::get_error_message(int error, String* buf) { return FALSE; @@ -1959,7 +2287,7 @@ int handler::ha_check_for_upgrade(HA_CHECK_OPT *check_opt) if (!keypart->fieldnr) continue; Field *field= table->field[keypart->fieldnr-1]; - if (field->type() == FIELD_TYPE_BLOB) + if (field->type() == MYSQL_TYPE_BLOB) { if (check_opt->sql_flags & TT_FOR_UPGRADE) check_opt->flags= T_MEDIUM; @@ -1981,7 +2309,7 @@ int handler::check_old_types() /* check for bad DECIMAL field */ for (field= table->field; (*field); field++) { - if ((*field)->type() == FIELD_TYPE_NEWDECIMAL) + if ((*field)->type() == MYSQL_TYPE_NEWDECIMAL) { return HA_ADMIN_NEEDS_ALTER; } @@ -1995,7 +2323,7 @@ int handler::check_old_types() } -static bool update_frm_version(TABLE *table, bool needs_lock) +static bool update_frm_version(TABLE *table) { char path[FN_REFLEN]; File file; @@ -2011,56 +2339,49 @@ static bool update_frm_version(TABLE *table, bool needs_lock) if (table->s->mysql_version == MYSQL_VERSION_ID) DBUG_RETURN(0); - strxnmov(path, sizeof(path)-1, mysql_data_home, "/", table->s->db, "/", - table->s->table_name, reg_ext, NullS); - if (!unpack_filename(path, path)) - DBUG_RETURN(1); - - if (needs_lock) - pthread_mutex_lock(&LOCK_open); + strxmov(path, table->s->normalized_path.str, reg_ext, NullS); if ((file= my_open(path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0) { uchar version[4]; - char *key= table->s->table_cache_key; - uint key_length= table->s->key_length; + char *key= table->s->table_cache_key.str; + uint key_length= table->s->table_cache_key.length; TABLE *entry; HASH_SEARCH_STATE state; int4store(version, MYSQL_VERSION_ID); - if ((result= my_pwrite(file,(byte*) version,4,51L,MYF_RW))) + if ((result= my_pwrite(file,(uchar*) version,4,51L,MYF_RW))) goto err; - for (entry=(TABLE*) hash_first(&open_cache,(byte*) key,key_length, &state); + for (entry=(TABLE*) hash_first(&open_cache,(uchar*) key,key_length, &state); entry; - entry= (TABLE*) hash_next(&open_cache,(byte*) key,key_length, &state)) + entry= (TABLE*) hash_next(&open_cache,(uchar*) key,key_length, &state)) entry->s->mysql_version= MYSQL_VERSION_ID; } err: if (file >= 0) VOID(my_close(file,MYF(MY_WME))); - if (needs_lock) - pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(result); } -/* Return key if error because of duplicated keys */ - +/** @brief + 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_FOUND_DUPP_UNIQUE || - error == HA_ERR_NULL_IN_SPATIAL) + 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) info(HA_STATUS_ERRKEY | HA_STATUS_NO_LOCK); DBUG_RETURN(table->file->errkey); } -/* +/** @brief Delete all files with extension from bas_ext() SYNOPSIS @@ -2076,7 +2397,6 @@ uint handler::get_dup_key(int error) didn't get any other errors than ENOENT # Error */ - int handler::delete_table(const char *name) { int error= 0; @@ -2085,7 +2405,7 @@ int handler::delete_table(const char *name) for (const char **ext=bas_ext(); *ext ; ext++) { - fn_format(buff, name, "", *ext, 2 | 4); + fn_format(buff, name, "", *ext, MY_UNPACK_FILENAME|MY_APPEND_EXT); if (my_delete_with_symlink(buff, MYF(0))) { if ((error= my_errno) != ENOENT) @@ -2115,7 +2435,14 @@ int handler::rename_table(const char * from, const char * to) } -/* +void handler::drop_table(const char *name) +{ + close(); + delete_table(name); +} + + +/** @brief Performs checks upon the table. SYNOPSIS @@ -2131,7 +2458,6 @@ int handler::rename_table(const char * from, const char * to) HA_ADMIN_NEEDS_ALTER Table has structures requiring ALTER TABLE HA_ADMIN_NOT_IMPLEMENTED */ - int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt) { int error; @@ -2152,7 +2478,7 @@ int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt) } if ((error= check(thd, check_opt))) return error; - return update_frm_version(table, 0); + return update_frm_version(table); } @@ -2161,11 +2487,11 @@ int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt) int result; if ((result= repair(thd, check_opt))) return result; - return update_frm_version(table, 0); + return update_frm_version(table); } -/* +/** @brief 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. @@ -2173,7 +2499,6 @@ int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt) 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; @@ -2194,7 +2519,7 @@ int ha_enable_transaction(THD *thd, bool on) DBUG_RETURN(error); } -int handler::index_next_same(byte *buf, const byte *key, uint keylen) +int handler::index_next_same(uchar *buf, const uchar *key, uint keylen) { int error; if (!(error=index_next(buf))) @@ -2209,70 +2534,102 @@ int handler::index_next_same(byte *buf, const byte *key, uint keylen) } +void handler::get_dynamic_partition_info(PARTITION_INFO *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 apropriate database-creator - Returns 1 if something got wrong -*/ +/** @brief + Initiates table-file and calls appropriate database-creator -int ha_create_table(const char *name, HA_CREATE_INFO *create_info, + NOTES + We must have a write lock on LOCK_open to be sure no other thread + interferes with table + + RETURN + 0 ok + 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; + 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(&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 (openfrm(current_thd, name,"",0,(uint) READ_ALL, 0, &table)) - DBUG_RETURN(1); if (update_create_info) - { update_create_info_from_table(create_info, &table); - } - if (lower_case_table_names == 2 && - !(table.file->table_flags() & HA_FILE_BASED)) - { - /* Ensure that handler gets name in lower case */ - strmov(name_buff, name); - my_casedn_str(files_charset_info, name_buff); - name= name_buff; - } - error=table.file->create(name,&table,create_info); - VOID(closefrm(&table)); + name= check_lowercase_names(table.file, share.path.str, name_buff); + + error= table.file->create(name, &table, create_info); + VOID(closefrm(&table, 0)); if (error) - my_error(ER_CANT_CREATE_TABLE, MYF(ME_BELL+ME_WAITTANG), name,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 and - if found, write the frm file to disk. +/** @brief + Try to discover table from engine + + NOTES + If found, write the frm file to disk. RETURN VALUES: - -1 : Table did not exists - 0 : Table created ok - > 0 : Error, table existed but could not be created + -1 Table did not exists + 0 Table created ok + > 0 Error, table existed but could not be created */ - -int ha_create_table_from_engine(THD* thd, - const char *db, - const char *name) +int ha_create_table_from_engine(THD* thd, const char *db, const char *name) { int error; - const void *frmblob; - uint frmlen; + uchar *frmblob; + size_t frmlen; char path[FN_REFLEN]; 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((char*) &create_info,sizeof(create_info)); + 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 */ @@ -2284,27 +2641,31 @@ int ha_create_table_from_engine(THD* thd, frmblob and frmlen are set, write the frm to disk */ - (void)strxnmov(path,FN_REFLEN,mysql_data_home,"/",db,"/",name,NullS); + (void)strxnmov(path,FN_REFLEN-1,mysql_data_home,FN_ROOTDIR, + db,FN_ROOTDIR,name,NullS); // Save the frm file error= writefrm(path, frmblob, frmlen); - my_free((char*) frmblob, MYF(0)); + my_free(frmblob, MYF(0)); if (error) DBUG_RETURN(2); - if (openfrm(thd, path,"",0,(uint) READ_ALL, 0, &table)) + init_tmp_table_share(&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; - if (lower_case_table_names == 2 && - !(table.file->table_flags() & HA_FILE_BASED)) - { - /* Ensure that handler gets name in lower case */ - my_casedn_str(files_charset_info, path); - } + check_lowercase_names(table.file, path, path); error=table.file->create(path,&table,&create_info); - VOID(closefrm(&table)); + VOID(closefrm(&table, 1)); DBUG_RETURN(error != 0); } @@ -2326,9 +2687,9 @@ void st_ha_check_opt::init() call to ha_init_key_cache() (probably out of memory) *****************************************************************************/ -/* Init a key cache if it has not been initied before */ - - +/** @brief + 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"); @@ -2336,8 +2697,8 @@ int ha_init_key_cache(const char *name, KEY_CACHE *key_cache) if (!key_cache->key_cache_inited) { pthread_mutex_lock(&LOCK_global_system_variables); - long tmp_buff_size= (long) key_cache->param_buff_size; - long tmp_block_size= (long) key_cache->param_block_size; + ulong tmp_buff_size= (ulong) 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; pthread_mutex_unlock(&LOCK_global_system_variables); @@ -2350,8 +2711,9 @@ int ha_init_key_cache(const char *name, KEY_CACHE *key_cache) } -/* Resize key cache */ - +/** @brief + Resize key cache +*/ int ha_resize_key_cache(KEY_CACHE *key_cache) { DBUG_ENTER("ha_resize_key_cache"); @@ -2372,8 +2734,9 @@ int ha_resize_key_cache(KEY_CACHE *key_cache) } -/* Change parameters for key cache (like size) */ - +/** @brief + Change parameters for key cache (like size) +*/ int ha_change_key_cache_param(KEY_CACHE *key_cache) { if (key_cache->key_cache_inited) @@ -2387,16 +2750,18 @@ int ha_change_key_cache_param(KEY_CACHE *key_cache) return 0; } -/* Free memory allocated by a key cache */ - +/** @brief + Free memory allocated by a key cache +*/ int ha_end_key_cache(KEY_CACHE *key_cache) { end_key_cache(key_cache, 1); // Can never fail return 0; } -/* Move all tables from one key cache to another one */ - +/** @brief + 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) { @@ -2405,7 +2770,7 @@ int ha_change_key_cache(KEY_CACHE *old_key_cache, } -/* +/** @brief Try to discover one table from handler(s) RETURN @@ -2413,47 +2778,94 @@ int ha_change_key_cache(KEY_CACHE *old_key_cache, 0 : OK. In this case *frmblob and *frmlen are set >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, - const void **frmblob, uint *frmlen) + 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); -#ifdef HAVE_NDBCLUSTER_DB - if (have_ndbcluster == SHOW_OPTION_YES) - error= ndbcluster_discover(thd, db, name, frmblob, frmlen); -#endif + + if (plugin_foreach(thd, discover_handlerton, + MYSQL_STORAGE_ENGINE_PLUGIN, &args)) + error= 0; + if (!error) - statistic_increment(thd->status_var.ha_discover_count,&LOCK_status); + 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 +/** @brief + Call this function in order to give the handler the possibility + 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<char> *files) + 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, dir)); -#ifdef HAVE_NDBCLUSTER_DB - if (have_ndbcluster == SHOW_OPTION_YES) - error= ndbcluster_find_files(thd, db, path, wild, dir, files); -#endif + 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 @@ -2463,21 +2875,182 @@ ha_find_files(THD *thd,const char *db,const char *path, # 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) { - int error= HA_ERR_NO_SUCH_TABLE; DBUG_ENTER("ha_table_exists_in_engine"); DBUG_PRINT("enter", ("db: %s, name: %s", db, name)); -#ifdef HAVE_NDBCLUSTER_DB - if (have_ndbcluster == SHOW_OPTION_YES) - error= ndbcluster_table_exists_in_engine(thd, db, name); -#endif - DBUG_PRINT("exit", ("error: %d", error)); - DBUG_RETURN(error); + 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 + +/** @brief Read the first row of a multi-range set. SYNOPSIS @@ -2501,7 +3074,6 @@ int ha_table_exists_in_engine(THD* thd, const char* db, const char* name) HA_ERR_END_OF_FILE No rows in range # 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) @@ -2511,13 +3083,16 @@ int handler::read_multi_range_first(KEY_MULTI_RANGE **found_range_p, 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.length ? + result= read_range_first(multi_range_curr->start_key.keypart_map ? &multi_range_curr->start_key : 0, - multi_range_curr->end_key.length ? + multi_range_curr->end_key.keypart_map ? &multi_range_curr->end_key : 0, test(multi_range_curr->range_flag & EQ_RANGE), multi_range_sorted); @@ -2531,7 +3106,7 @@ int handler::read_multi_range_first(KEY_MULTI_RANGE **found_range_p, } -/* +/** @brief Read the next row of a multi-range set. SYNOPSIS @@ -2549,7 +3124,6 @@ int handler::read_multi_range_first(KEY_MULTI_RANGE **found_range_p, HA_ERR_END_OF_FILE No (more) rows in range # Error code */ - int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p) { int result; @@ -2571,6 +3145,8 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p) } 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. @@ -2578,14 +3154,14 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p) 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_curr < multi_range_end; - multi_range_curr++) + for (; multi_range_curr < multi_range_end; multi_range_curr++) { - result= read_range_first(multi_range_curr->start_key.length ? + result= read_range_first(multi_range_curr->start_key.keypart_map ? &multi_range_curr->start_key : 0, - multi_range_curr->end_key.length ? + multi_range_curr->end_key.keypart_map ? &multi_range_curr->end_key : 0, test(multi_range_curr->range_flag & EQ_RANGE), multi_range_sorted); @@ -2602,7 +3178,7 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p) } -/* +/** @brief Read first row between two ranges. Store ranges for future calls to read_range_next @@ -2622,7 +3198,6 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p) HA_ERR_END_OF_FILE No rows in range # Error code */ - int handler::read_range_first(const key_range *start_key, const key_range *end_key, bool eq_range_arg, bool sorted) @@ -2644,12 +3219,12 @@ int handler::read_range_first(const key_range *start_key, if (!start_key) // Read first record result= index_first(table->record[0]); else - result= index_read(table->record[0], - start_key->key, - start_key->length, - start_key->flag); + 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) + DBUG_RETURN((result == HA_ERR_KEY_NOT_FOUND) ? HA_ERR_END_OF_FILE : result); @@ -2657,7 +3232,7 @@ int handler::read_range_first(const key_range *start_key, } -/* +/** @brief Read next row between two ranges. SYNOPSIS @@ -2671,7 +3246,6 @@ int handler::read_range_first(const key_range *start_key, HA_ERR_END_OF_FILE No rows in range # Error code */ - int handler::read_range_next() { int result; @@ -2691,13 +3265,13 @@ int handler::read_range_next() } -/* +/** @brief Compare if found key (in row) is over max-value SYNOPSIS compare_key range range to compare to row. May be 0 for no range - + NOTES See key.cc::key_cmp() for details @@ -2708,7 +3282,6 @@ int handler::read_range_next() -1 Key is less than range 1 Key is larger than range */ - int handler::compare_key(key_range *range) { int cmp; @@ -2720,19 +3293,23 @@ int handler::compare_key(key_range *range) return cmp; } -int handler::index_read_idx(byte * buf, uint index, const byte * key, - uint key_len, enum ha_rkey_function find_flag) + +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= ha_index_init(index); + int error, error1; + error= index_init(index, 0); if (!error) - error= index_read(buf, key, key_len, find_flag); - if (!error) - error= ha_index_end(); - return error; + { + error= index_read_map(buf, key, keypart_map, find_flag); + error1= index_end(); + } + return error ? error : error1; } -/* +/** @brief Returns a list of all known extensions. SYNOPSIS @@ -2746,41 +3323,49 @@ int handler::index_read_idx(byte * buf, uint index, const byte * key, RETURN VALUE 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) { - MEM_ROOT *mem_root= current_thd->mem_root; if (!known_extensions.type_names || mysys_usage_id != known_extensions_id) { - handlerton **types; List<char> found_exts; - List_iterator_fast<char> it(found_exts); const char **ext, *old_ext; known_extensions_id= mysys_usage_id; - found_exts.push_back((char*) triggers_file_ext); - found_exts.push_back((char*) trigname_file_ext); - for (types= sys_table_types; *types; types++) - { - if ((*types)->state == SHOW_OPTION_YES) - { - handler *file= get_new_handler(0, mem_root, - (enum db_type) (*types)->db_type); - 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); + 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); - it.rewind(); - } - delete file; - } - } ext= (const char **) my_once_alloc(sizeof(char *)* (found_exts.elements+1), MYF(MY_WME | MY_FAE)); @@ -2789,9 +3374,514 @@ TYPELIB *ha_known_exts(void) 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_fields(&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) + send_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 +*/ + +/* The Sun compiler cannot instantiate the template below if this is + declared static, but it works by putting it into an anonymous + namespace. */ +namespace { + 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->current_stmt_binlog_row_based && + table->s->cached_row_logging_check && + (thd->options & 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'. Either manually locked + (stored in THD::locked_tables) and automatically locked (stored + in THD::lock) are considered. + + RETURN VALUE + 0 All OK + 1 Failed to write all table maps + + SEE ALSO + THD::lock + THD::locked_tables +*/ +namespace +{ + int write_locked_table_maps(THD *thd) + { + DBUG_ENTER("write_locked_table_maps"); + DBUG_PRINT("enter", ("thd: 0x%lx thd->lock: 0x%lx thd->locked_tables: 0x%lx " + "thd->extra_lock: 0x%lx", + (long) thd, (long) thd->lock, + (long) thd->locked_tables, (long) thd->extra_lock)); + + if (thd->get_binlog_table_maps() == 0) + { + MYSQL_LOCK *locks[3]; + locks[0]= thd->extra_lock; + locks[1]= thd->lock; + locks[2]= thd->locked_tables; + 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)) + { + int const has_trans= 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); + } + + template<class RowsEventT> int + binlog_log_row(TABLE* table, + const uchar *before_record, + const uchar *after_record) + { + 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)))) + { + error= + RowsEventT::binlog_row_logging_function(thd, table, + table->file-> + has_transactions(), + &cols, table->s->fields, + before_record, + after_record); + } + if (!use_bitbuf) + bitmap_free(&cols); + } + } + return error ? HA_ERR_RBR_LOGGING_FAILED : 0; + } + + /* + Instantiate the versions we need for the above template function, + because we have -fno-implicit-template as compiling option. + */ + + template int + binlog_log_row<Write_rows_log_event>(TABLE *, const uchar *, const uchar *); + + template int + binlog_log_row<Delete_rows_log_event>(TABLE *, const uchar *, const uchar *); + + template int + binlog_log_row<Update_rows_log_event>(TABLE *, const uchar *, const uchar *); +} + + +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); + + /* + 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(); + 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); + DBUG_RETURN(reset()); +} + + +int handler::ha_write_row(uchar *buf) +{ + int error; + if (unlikely(error= write_row(buf))) + return error; + if (unlikely(error= binlog_log_row<Write_rows_log_event>(table, 0, buf))) + return error; + return 0; +} + + +int handler::ha_update_row(const uchar *old_data, uchar *new_data) +{ + int error; + + /* + 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]); + + if (unlikely(error= update_row(old_data, new_data))) + return error; + if (unlikely(error= binlog_log_row<Update_rows_log_event>(table, old_data, new_data))) + return error; + return 0; +} + +int handler::ha_delete_row(const uchar *buf) +{ + int error; + if (unlikely(error= delete_row(buf))) + return error; + if (unlikely(error= binlog_log_row<Delete_rows_log_event>(table, buf, 0))) + 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 && + my_delete(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 (my_stat(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((uchar*)iterator->buffer, MYF(MY_ALLOW_ZERO_PTR)); +} + + +/** @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) * + (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) - 1; + 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*/ |