diff options
author | gopal.shankar@oracle.com <> | 2012-04-11 15:53:17 +0530 |
---|---|---|
committer | gopal.shankar@oracle.com <> | 2012-04-11 15:53:17 +0530 |
commit | 796fad1424dabed0d60cdbf353c8ce88c4066bb8 (patch) | |
tree | 5351d27e52955b31c1d107afd9480b37224c2b8d /sql/handler.cc | |
parent | d3782bffbdd2c5cdda3d6760acfaebfe9549aeef (diff) | |
download | mariadb-git-796fad1424dabed0d60cdbf353c8ce88c4066bb8.tar.gz |
Bug#11815557 60269: MYSQL SHOULD REJECT ATTEMPTS TO CREATE SYSTEM
TABLES IN INCORRECT ENGINE
PROBLEM:
CREATE/ALTER TABLE currently can move system tables like
mysql.db, user, host etc, to engines other than MyISAM. This is not
completely supported as of now, by mysqld. When some of system tables
like plugin, servers, event, func, *_priv, time_zone* are moved
to innodb, mysqld restart crashes. Currently system tables
can be moved to BLACKHOLE also!!!.
ANALYSIS:
The problem is that there is no check before creating or moving
a system table to some particular engine.
System tables are suppose to be residing in MyISAM. We can think
of restricting system tables to exist only in MyISAM. But, there could
be future needs of these system tables to be part of other engines
by design. For eg, NDB cluster expects some tables to be on innodb
or ndb engine. This calls for a solution, by which system
tables can be supported by any desired engine, with minimal effort.
FIX:
The solution provides a handlerton interface using which,
mysqld server can query particular storage engine handlerton for
system tables that it supports. This way each storage engine
layer can define their own system database and system tables.
The check_engine() function uses the new handlerton function
ha_check_if_supported_system_table() to check if db.tablename
provided in the DDL is supported by the SE.
Note: This fix has modified a test in help.test, which was moving
mysql.help_* to innodb. The primary intention of the test was not
to move them between engines.
Diffstat (limited to 'sql/handler.cc')
-rw-r--r-- | sql/handler.cc | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/sql/handler.cc b/sql/handler.cc index b6df46ed48d..c3c70109daa 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -45,6 +45,7 @@ #include "ha_partition.h" #endif + /* While we have legacy_db_type, we have this array to check for dups and to find handlerton from legacy_db_type. @@ -90,6 +91,83 @@ TYPELIB tx_isolation_typelib= {array_elements(tx_isolation_names)-1,"", static TYPELIB known_extensions= {0,"known_exts", NULL, NULL}; uint known_extensions_id= 0; +/** + Database name that hold most of mysqld system tables. + Current code assumes that, there exists only some + specific "database name" designated as system database. +*/ +const char* mysqld_system_database= "mysql"; + +// System tables that belong to mysqld_system_database. +st_system_tablename mysqld_system_tables[]= { + {mysqld_system_database, "db"}, + {mysqld_system_database, "user"}, + {mysqld_system_database, "host"}, + {mysqld_system_database, "func"}, + {mysqld_system_database, "proc"}, + {mysqld_system_database, "event"}, + {mysqld_system_database, "plugin"}, + {mysqld_system_database, "servers"}, + {mysqld_system_database, "procs_priv"}, + {mysqld_system_database, "tables_priv"}, + {mysqld_system_database, "proxies_priv"}, + {mysqld_system_database, "columns_priv"}, + {mysqld_system_database, "time_zone"}, + {mysqld_system_database, "time_zone_name"}, + {mysqld_system_database, "time_zone_leap_second"}, + {mysqld_system_database, "time_zone_transition"}, + {mysqld_system_database, "time_zone_transition_type"}, + {mysqld_system_database, "help_category"}, + {mysqld_system_database, "help_keyword"}, + {mysqld_system_database, "help_relation"}, + {mysqld_system_database, "help_topic"}, + {(const char *)NULL, (const char *)NULL} /* This must be at the end */ +}; + +/** + This static pointer holds list of system databases from SQL layer and + various SE's. The required memory is allocated once, and never freed. +*/ +static const char **known_system_databases= NULL; +static const char **ha_known_system_databases(); + +// Called for each SE to get SE specific system database. +static my_bool system_databases_handlerton(THD *unused, plugin_ref plugin, + void *arg); + +// Called for each SE to check if given db.table_name is a system table. +static my_bool check_engine_system_table_handlerton(THD *unused, + plugin_ref plugin, + void *arg); + +/** + Structure used by SE during check for system table. + This structure is passed to each SE handlerton and the status (OUT param) + is collected. +*/ +struct st_sys_tbl_chk_params +{ + const char *db; // IN param + const char *table_name; // IN param + bool is_sql_layer_system_table; // IN param + legacy_db_type db_type; // IN param + + enum enum_sys_tbl_chk_status + { + // db.table_name is not a supported system table. + NOT_KNOWN_SYSTEM_TABLE, + /* + db.table_name is a system table, + but may not be supported by SE. + */ + KNOWN_SYSTEM_TABLE, + /* + db.table_name is a system table, + and is supported by SE. + */ + SUPPORTED_SYSTEM_TABLE + }status; // OUT param +}; static plugin_ref ha_default_plugin(THD *thd) @@ -587,6 +665,14 @@ int ha_init() */ opt_using_transactions= total_ha>(ulong)opt_bin_log; savepoint_alloc_size+= sizeof(SAVEPOINT); + + /* + Initialize system database name cache. + This cache is used to do a quick check if a given + db.tablename is a system table. + */ + known_system_databases= ha_known_system_databases(); + DBUG_RETURN(error); } @@ -3784,6 +3870,227 @@ ha_check_if_table_exists(THD* thd, const char *db, const char *name, DBUG_RETURN(FALSE); } +/** + @brief Check if a given table is a system table. + + @details The primary purpose of introducing this function is to stop system + tables to be created or being moved to undesired storage engines. + + @todo There is another function called is_system_table_name() used by + get_table_category(), which is used to set TABLE_SHARE table_category. + It checks only a subset of table name like proc, event and time*. + We cannot use below function in get_table_category(), + as that affects locking mechanism. If we need to + unify these functions, we need to fix locking issues generated. + + @param hton Handlerton of new engine. + @param db Database name. + @param table_name Table name to be checked. + + @return Operation status + @retval true If the table name is a valid system table + or if its a valid user table. + + @retval false If the table name is a system table name + and does not belong to engine specified + in the command. +*/ +bool ha_check_if_supported_system_table(handlerton *hton, const char *db, + const char *table_name) +{ + DBUG_ENTER("ha_check_if_supported_system_table"); + st_sys_tbl_chk_params check_params; + bool is_system_database= false; + const char **names; + st_system_tablename *systab; + + // Check if we have a system database name in the command. + DBUG_ASSERT(known_system_databases != NULL); + names= known_system_databases; + while (names && *names) + { + if (strcmp(*names, db) == 0) + { + /* Used to compare later, will be faster */ + check_params.db= *names; + is_system_database= true; + break; + } + names++; + } + if (!is_system_database) + DBUG_RETURN(true); // It's a user table name. + + // Check if this is SQL layer system tables. + systab= mysqld_system_tables; + check_params.is_sql_layer_system_table= false; + while (systab && systab->db) + { + if (systab->db == check_params.db && + strcmp(systab->tablename, table_name) == 0) + { + check_params.is_sql_layer_system_table= true; + break; + } + systab++; + } + + // Check if this is a system table and if some engine supports it. + check_params.status= check_params.is_sql_layer_system_table ? + st_sys_tbl_chk_params::KNOWN_SYSTEM_TABLE : + st_sys_tbl_chk_params::NOT_KNOWN_SYSTEM_TABLE; + check_params.db_type= hton->db_type; + check_params.table_name= table_name; + plugin_foreach(NULL, check_engine_system_table_handlerton, + MYSQL_STORAGE_ENGINE_PLUGIN, &check_params); + + // SE does not support this system table. + if (check_params.status == st_sys_tbl_chk_params::KNOWN_SYSTEM_TABLE) + DBUG_RETURN(false); + + // It's a system table or a valid user table. + DBUG_RETURN(true); +} + +/** + @brief Called for each SE to check if given db, tablename is a system table. + + @details The primary purpose of introducing this function is to stop system + tables to be created or being moved to undesired storage engines. + + @param unused unused THD* + @param plugin Points to specific SE. + @param arg Is of type struct st_sys_tbl_chk_params. + + @note + args->status Indicates OUT param, + see struct st_sys_tbl_chk_params definition for more info. + + @return Operation status + @retval true There was a match found. + This will stop doing checks with other SE's. + + @retval false There was no match found. + Other SE's will be checked to find a match. +*/ +static my_bool check_engine_system_table_handlerton(THD *unused, + plugin_ref plugin, + void *arg) +{ + st_sys_tbl_chk_params *check_params= (st_sys_tbl_chk_params*) arg; + handlerton *hton= plugin_data(plugin, handlerton *); + + // Do we already know that the table is a system table? + if (check_params->status == st_sys_tbl_chk_params::KNOWN_SYSTEM_TABLE) + { + /* + If this is the same SE specified in the command, we can + simply ask the SE if it supports it stop the search regardless. + */ + if (hton->db_type == check_params->db_type) + { + if (hton->is_supported_system_table && + hton->is_supported_system_table(check_params->db, + check_params->table_name, + check_params->is_sql_layer_system_table)) + check_params->status= st_sys_tbl_chk_params::SUPPORTED_SYSTEM_TABLE; + return TRUE; + } + /* + If this is a different SE, there is no point in asking the SE + since we already know it's a system table and we don't care + if it is supported or not. + */ + return FALSE; + } + + /* + We don't yet know if the table is a system table or not. + We therefore must always ask the SE. + */ + if (hton->is_supported_system_table && + hton->is_supported_system_table(check_params->db, + check_params->table_name, + check_params->is_sql_layer_system_table)) + { + /* + If this is the same SE specified in the command, we know it's a + supported system table and can stop the search. + */ + if (hton->db_type == check_params->db_type) + { + check_params->status= st_sys_tbl_chk_params::SUPPORTED_SYSTEM_TABLE; + return TRUE; + } + else + check_params->status= st_sys_tbl_chk_params::KNOWN_SYSTEM_TABLE; + } + + return FALSE; +} + +/* + Prepare list of all known system database names + current we just have 'mysql' as system database name. + + Later ndbcluster, innodb SE's can define some new database + name which can store system tables specific to SE. +*/ +const char** ha_known_system_databases(void) +{ + I_List<i_string> found_databases; + const char **databases, **database; + + // Get mysqld system database name. + found_databases.push_back(new i_string(mysqld_system_database)); + + // Get system database names from every specific storage engine. + plugin_foreach(NULL, system_databases_handlerton, + MYSQL_STORAGE_ENGINE_PLUGIN, &found_databases); + + int element_count= 0; + I_List_iterator<i_string> iter(found_databases); + while (iter++) element_count++; + databases= (const char **) my_once_alloc(sizeof(char *)* + (element_count+1), + MYF(MY_WME | MY_FAE)); + DBUG_ASSERT(databases != NULL); + + database= databases; + i_string *tmp; + while ((tmp= found_databases.get())) + { + *database++= tmp->ptr; + delete tmp; + } + *database= NULL; // Last element. + + return databases; +} + +/** + @brief Fetch system database name specific to SE. + + @details This function is invoked by plugin_foreach() from + ha_known_system_databases(), for each storage engine. +*/ +static my_bool system_databases_handlerton(THD *unused, plugin_ref plugin, + void *arg) +{ + I_List<i_string> *found_databases= (I_List<i_string> *) arg; + const char *db; + + handlerton *hton= plugin_data(plugin, handlerton *); + if (hton->system_database) + { + db= hton->system_database(); + if (db) + found_databases->push_back(new i_string(db)); + } + + return FALSE; +} + void st_ha_check_opt::init() { |