diff options
Diffstat (limited to 'sql/sql_show.cc')
-rw-r--r-- | sql/sql_show.cc | 10669 |
1 files changed, 10669 insertions, 0 deletions
diff --git a/sql/sql_show.cc b/sql/sql_show.cc new file mode 100644 index 00000000..32b29468 --- /dev/null +++ b/sql/sql_show.cc @@ -0,0 +1,10669 @@ +/* Copyright (c) 2000, 2015, Oracle and/or its affiliates. + Copyright (c) 2009, 2023, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + + +/* Function with list databases, tables or fields */ + +#include "sql_plugin.h" // SHOW_MY_BOOL +#include "sql_priv.h" +#include "unireg.h" +#include "sql_acl.h" // fill_schema_*_privileges +#include "sql_select.h" // For select_describe +#include "sql_base.h" // close_tables_for_reopen +#include "create_options.h" +#include "sql_show.h" +#include "sql_table.h" // filename_to_tablename, + // primary_key_name, + // build_table_filename +#include "sql_view.h" +#include "repl_failsafe.h" +#include "sql_parse.h" // check_access, check_table_access +#include "sql_partition.h" // partition_element +#include "sql_derived.h" // mysql_derived_prepare, + // mysql_handle_derived, +#include "sql_db.h" // check_db_dir_existence, load_db_opt_by_name +#include "sql_time.h" // interval_type_to_name +#include "tztime.h" // struct Time_zone +#include "sql_acl.h" // TABLE_ACLS, check_grant, DB_ACLS, acl_get, + // check_grant_db +#include "sp.h" +#include "sp_head.h" +#include "sp_pcontext.h" +#include "set_var.h" +#include "sql_trigger.h" +#include "sql_derived.h" +#include "sql_statistics.h" +#include "sql_connect.h" +#include "authors.h" +#include "contributors.h" +#include "sql_partition.h" +#ifdef HAVE_EVENT_SCHEDULER +#include "events.h" +#include "event_data_objects.h" +#endif +#include <my_dir.h> +#include "lock.h" // MYSQL_OPEN_IGNORE_FLUSH +#include "debug_sync.h" +#include "keycaches.h" +#include "ha_sequence.h" +#ifdef WITH_PARTITION_STORAGE_ENGINE +#include "ha_partition.h" +#endif +#include "transaction.h" +#include "opt_trace.h" +#include "my_cpu.h" +#include "key.h" + +#include "lex_symbol.h" +#define KEYWORD_SIZE 64 + +extern SYMBOL symbols[]; +extern size_t symbols_length; + +extern SYMBOL sql_functions[]; +extern size_t sql_functions_length; + +extern Native_func_registry_array native_func_registry_array; + +enum enum_i_s_events_fields +{ + ISE_EVENT_CATALOG= 0, + ISE_EVENT_SCHEMA, + ISE_EVENT_NAME, + ISE_DEFINER, + ISE_TIME_ZONE, + ISE_EVENT_BODY, + ISE_EVENT_DEFINITION, + ISE_EVENT_TYPE, + ISE_EXECUTE_AT, + ISE_INTERVAL_VALUE, + ISE_INTERVAL_FIELD, + ISE_SQL_MODE, + ISE_STARTS, + ISE_ENDS, + ISE_STATUS, + ISE_ON_COMPLETION, + ISE_CREATED, + ISE_LAST_ALTERED, + ISE_LAST_EXECUTED, + ISE_EVENT_COMMENT, + ISE_ORIGINATOR, + ISE_CLIENT_CS, + ISE_CONNECTION_CL, + ISE_DB_CL +}; + + +static const LEX_CSTRING trg_action_time_type_names[]= +{ + { STRING_WITH_LEN("BEFORE") }, + { STRING_WITH_LEN("AFTER") } +}; + +static const LEX_CSTRING trg_event_type_names[]= +{ + { STRING_WITH_LEN("INSERT") }, + { STRING_WITH_LEN("UPDATE") }, + { STRING_WITH_LEN("DELETE") } +}; + + +LEX_CSTRING DATA_clex_str= { STRING_WITH_LEN("DATA") }; +LEX_CSTRING INDEX_clex_str= { STRING_WITH_LEN("INDEX") }; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +static const char *grant_names[]={ + "select","insert","update","delete","create","drop","reload","shutdown", + "process","file","grant","references","index","alter"}; + +static TYPELIB grant_types = { sizeof(grant_names)/sizeof(char **), + "grant_types", + grant_names, NULL}; +#endif + +/* Match the values of enum ha_choice */ +static const LEX_CSTRING ha_choice_values[]= +{ + { STRING_WITH_LEN("") }, + { STRING_WITH_LEN("0") }, + { STRING_WITH_LEN("1") } +}; + +static void store_key_options(THD *, String *, TABLE *, KEY *); + +static int show_create_view(THD *thd, TABLE_LIST *table, String *buff); +static int show_create_sequence(THD *thd, TABLE_LIST *table_list, + String *packet); + +static const LEX_CSTRING *view_algorithm(TABLE_LIST *table); + +bool get_lookup_field_values(THD *, COND *, bool, TABLE_LIST *, + LOOKUP_FIELD_VALUES *); + +/** + Try to lock a mutex, but give up after a short while to not cause deadlocks + + The loop is short, as the mutex we are trying to lock are mutex the should + never be locked a long time, just over a few instructions. + + @return 0 ok + @return 1 error +*/ + +static bool trylock_short(mysql_mutex_t *mutex) +{ + uint i; + for (i= 0 ; i < 100 ; i++) + { + if (!mysql_mutex_trylock(mutex)) + return 0; + LF_BACKOFF(); + } + return 1; +} + + +/*************************************************************************** +** List all table types supported +***************************************************************************/ + + +static bool is_show_command(THD *thd) +{ + return sql_command_flags[thd->lex->sql_command] & CF_STATUS_COMMAND; +} + +static int make_version_string(char *buf, int buf_length, uint version) +{ + return (int)my_snprintf(buf, buf_length, "%d.%d", version>>8,version&0xff); +} + + +static const LEX_CSTRING maturity_name[]={ + { STRING_WITH_LEN("Unknown") }, + { STRING_WITH_LEN("Experimental") }, + { STRING_WITH_LEN("Alpha") }, + { STRING_WITH_LEN("Beta") }, + { STRING_WITH_LEN("Gamma") }, + { STRING_WITH_LEN("Stable") }}; + + +static my_bool show_plugins(THD *thd, plugin_ref plugin, + void *arg) +{ + TABLE *table= (TABLE*) arg; + struct st_maria_plugin *plug= plugin_decl(plugin); + struct st_plugin_dl *plugin_dl= plugin_dlib(plugin); + CHARSET_INFO *cs= system_charset_info; + char version_buf[20]; + + restore_record(table, s->default_values); + + table->field[0]->store(plugin_name(plugin)->str, + plugin_name(plugin)->length, cs); + + table->field[1]->store(version_buf, + make_version_string(version_buf, sizeof(version_buf), plug->version), + cs); + + switch (plugin_state(plugin)) { + case PLUGIN_IS_DELETED: + table->field[2]->store(STRING_WITH_LEN("DELETED"), cs); + break; + case PLUGIN_IS_UNINITIALIZED: + table->field[2]->store(STRING_WITH_LEN("INACTIVE"), cs); + break; + case PLUGIN_IS_READY: + table->field[2]->store(STRING_WITH_LEN("ACTIVE"), cs); + break; + case PLUGIN_IS_DISABLED: + table->field[2]->store(STRING_WITH_LEN("DISABLED"), cs); + break; + case PLUGIN_IS_DYING: + table->field[2]->store(STRING_WITH_LEN("INACTIVE"), cs); + break; + case PLUGIN_IS_FREED: // filtered in fill_plugins, used in fill_all_plugins + table->field[2]->store(STRING_WITH_LEN("NOT INSTALLED"), cs); + break; + default: + DBUG_ASSERT(0); + } + + table->field[3]->store(plugin_type_names[plug->type].str, + plugin_type_names[plug->type].length, + cs); + table->field[4]->store(version_buf, + make_version_string(version_buf, sizeof(version_buf), + *(uint *)plug->info), cs); + + if (plugin_dl) + { + table->field[5]->store(plugin_dl->dl.str, plugin_dl->dl.length, cs); + table->field[5]->set_notnull(); + table->field[6]->store(version_buf, + make_version_string(version_buf, sizeof(version_buf), + plugin_dl->mariaversion), + cs); + table->field[6]->set_notnull(); + } + else + { + table->field[5]->set_null(); + table->field[6]->set_null(); + } + + + if (plug->author) + { + table->field[7]->store(plug->author, strlen(plug->author), cs); + table->field[7]->set_notnull(); + } + else + table->field[7]->set_null(); + + if (plug->descr) + { + table->field[8]->store(plug->descr, strlen(plug->descr), cs); + table->field[8]->set_notnull(); + } + else + table->field[8]->set_null(); + + switch (plug->license) { + case PLUGIN_LICENSE_GPL: + table->field[9]->store(PLUGIN_LICENSE_GPL_STRING, + strlen(PLUGIN_LICENSE_GPL_STRING), cs); + break; + case PLUGIN_LICENSE_BSD: + table->field[9]->store(PLUGIN_LICENSE_BSD_STRING, + strlen(PLUGIN_LICENSE_BSD_STRING), cs); + break; + default: + table->field[9]->store(PLUGIN_LICENSE_PROPRIETARY_STRING, + strlen(PLUGIN_LICENSE_PROPRIETARY_STRING), cs); + break; + } + + table->field[10]->store( + global_plugin_typelib_names[plugin_load_option(plugin)], + strlen(global_plugin_typelib_names[plugin_load_option(plugin)]), + cs); + + if (plug->maturity <= MariaDB_PLUGIN_MATURITY_STABLE) + table->field[11]->store(maturity_name[plug->maturity].str, + maturity_name[plug->maturity].length, + cs); + else + table->field[11]->store("Unknown", 7, cs); + + if (plug->version_info) + { + table->field[12]->store(plug->version_info, + strlen(plug->version_info), cs); + table->field[12]->set_notnull(); + } + else + table->field[12]->set_null(); + + return schema_table_store_record(thd, table); +} + + +int fill_plugins(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_plugins"); + TABLE *table= tables->table; + + if (plugin_foreach_with_mask(thd, show_plugins, MYSQL_ANY_PLUGIN, + ~PLUGIN_IS_FREED, table)) + DBUG_RETURN(1); + + DBUG_RETURN(0); +} + + +int fill_all_plugins(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_all_plugins"); + TABLE *table= tables->table; + LOOKUP_FIELD_VALUES lookup; + + if (get_lookup_field_values(thd, cond, true, tables, &lookup)) + DBUG_RETURN(0); + + if (lookup.db_value.str && !lookup.db_value.str[0]) + DBUG_RETURN(0); // empty string never matches a valid SONAME + + MY_DIR *dirp= my_dir(opt_plugin_dir, MY_THREAD_SPECIFIC); + if (!dirp) + { + my_error(ER_CANT_READ_DIR, MYF(0), opt_plugin_dir, my_errno); + DBUG_RETURN(1); + } + + if (!lookup.db_value.str) + plugin_dl_foreach(thd, 0, show_plugins, table); + + const char *wstr= lookup.db_value.str, *wend= wstr + lookup.db_value.length; + for (size_t i=0; i < dirp->number_of_files; i++) + { + FILEINFO *file= dirp->dir_entry+i; + LEX_CSTRING dl= { file->name, strlen(file->name) }; + const char *dlend= dl.str + dl.length; + const size_t so_ext_len= sizeof(SO_EXT) - 1; + + if (strcasecmp(dlend - so_ext_len, SO_EXT)) + continue; + + if (lookup.db_value.str) + { + if (lookup.wild_db_value) + { + if (files_charset_info->wildcmp(dl.str, dlend, wstr, wend, + wild_prefix, wild_one, wild_many)) + continue; + } + else + { + if (files_charset_info->strnncoll(dl.str, dl.length, + lookup.db_value.str, lookup.db_value.length)) + continue; + } + } + + plugin_dl_foreach(thd, &dl, show_plugins, table); + thd->clear_error(); + } + + my_dirend(dirp); + DBUG_RETURN(0); +} + + +/*************************************************************************** +** List all Authors. +** If you can update it, you get to be in it :) +***************************************************************************/ + +bool mysqld_show_authors(THD *thd) +{ + List<Item> field_list; + Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("mysqld_show_authors"); + + field_list.push_back(new (mem_root) Item_empty_string(thd, "Name", 40), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Location", 40), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Comment", 512), + mem_root); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + show_table_authors_st *authors; + for (authors= show_table_authors; authors->name; authors++) + { + protocol->prepare_for_resend(); + protocol->store(authors->name, strlen(authors->name), system_charset_info); + protocol->store(authors->location, strlen(authors->location), + system_charset_info); + protocol->store(authors->comment, strlen(authors->comment), + system_charset_info); + if (protocol->write()) + DBUG_RETURN(TRUE); + } + my_eof(thd); + DBUG_RETURN(FALSE); +} + + +/*************************************************************************** +** List all Contributors. +** Please get permission before updating +***************************************************************************/ + +bool mysqld_show_contributors(THD *thd) +{ + List<Item> field_list; + Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("mysqld_show_contributors"); + + field_list.push_back(new (mem_root) Item_empty_string(thd, "Name", 40), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Location", 40), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Comment", 512), + mem_root); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + show_table_contributors_st *contributors; + for (contributors= show_table_contributors; contributors->name; contributors++) + { + protocol->prepare_for_resend(); + protocol->store(contributors->name, strlen(contributors->name), + system_charset_info); + protocol->store(contributors->location, strlen(contributors->location), + system_charset_info); + protocol->store(contributors->comment, strlen(contributors->comment), + system_charset_info); + if (protocol->write()) + DBUG_RETURN(TRUE); + } + my_eof(thd); + DBUG_RETURN(FALSE); +} + + +/*************************************************************************** + List all privileges supported +***************************************************************************/ + +struct show_privileges_st { + const char *privilege; + const char *context; + const char *comment; +}; + +static struct show_privileges_st sys_privileges[]= +{ + {"Alter", "Tables", "To alter the table"}, + {"Alter routine", "Functions,Procedures", "To alter or drop stored functions/procedures"}, + {"Create", "Databases,Tables,Indexes", "To create new databases and tables"}, + {"Create routine","Databases","To use CREATE FUNCTION/PROCEDURE"}, + {"Create temporary tables","Databases","To use CREATE TEMPORARY TABLE"}, + {"Create view", "Tables", "To create new views"}, + {"Create user", "Server Admin", "To create new users"}, + {"Delete", "Tables", "To delete existing rows"}, + {"Delete history", "Tables", "To delete versioning table historical rows"}, + {"Drop", "Databases,Tables", "To drop databases, tables, and views"}, +#ifdef HAVE_EVENT_SCHEDULER + {"Event","Server Admin","To create, alter, drop and execute events"}, +#endif + {"Execute", "Functions,Procedures", "To execute stored routines"}, + {"File", "File access on server", "To read and write files on the server"}, + {"Grant option", "Databases,Tables,Functions,Procedures", "To give to other users those privileges you possess"}, + {"Index", "Tables", "To create or drop indexes"}, + {"Insert", "Tables", "To insert data into tables"}, + {"Lock tables","Databases","To use LOCK TABLES (together with SELECT privilege)"}, + {"Process", "Server Admin", "To view the plain text of currently executing queries"}, + {"Proxy", "Server Admin", "To make proxy user possible"}, + {"References", "Databases,Tables", "To have references on tables"}, + {"Reload", "Server Admin", "To reload or refresh tables, logs and privileges"}, + {"Binlog admin", "Server", "To purge binary logs"}, + {"Binlog monitor", "Server", "To use SHOW BINLOG STATUS and SHOW BINARY LOG"}, + {"Binlog replay", "Server", "To use BINLOG (generated by mariadb-binlog)"}, + {"Replication master admin", "Server", "To monitor connected slaves"}, + {"Replication slave admin", "Server", "To start/stop slave and apply binlog events"}, + {"Slave monitor", "Server", "To use SHOW SLAVE STATUS and SHOW RELAYLOG EVENTS"}, + {"Replication slave","Server Admin","To read binary log events from the master"}, + {"Select", "Tables", "To retrieve rows from table"}, + {"Show databases","Server Admin","To see all databases with SHOW DATABASES"}, + {"Show view","Tables","To see views with SHOW CREATE VIEW"}, + {"Shutdown","Server Admin", "To shut down the server"}, + {"Super","Server Admin","To use KILL thread, SET GLOBAL, CHANGE MASTER, etc."}, + {"Trigger","Tables", "To use triggers"}, + {"Create tablespace", "Server Admin", "To create/alter/drop tablespaces"}, + {"Update", "Tables", "To update existing rows"}, + {"Set user","Server", "To create views and stored routines with a different definer"}, + {"Federated admin", "Server", "To execute the CREATE SERVER, ALTER SERVER, DROP SERVER statements"}, + {"Connection admin", "Server", "To bypass connection limits and kill other users' connections"}, + {"Read_only admin", "Server", "To perform write operations even if @@read_only=ON"}, + {"Usage","Server Admin","No privileges - allow connect only"}, + {NullS, NullS, NullS} +}; + +bool mysqld_show_privileges(THD *thd) +{ + List<Item> field_list; + Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("mysqld_show_privileges"); + + field_list.push_back(new (mem_root) Item_empty_string(thd, "Privilege", 10), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Context", 15), + mem_root); + field_list.push_back(new (mem_root) Item_empty_string(thd, "Comment", + NAME_CHAR_LEN), + mem_root); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + show_privileges_st *privilege= sys_privileges; + for (privilege= sys_privileges; privilege->privilege ; privilege++) + { + protocol->prepare_for_resend(); + protocol->store(privilege->privilege, strlen(privilege->privilege), + system_charset_info); + protocol->store(privilege->context, strlen(privilege->context), + system_charset_info); + protocol->store(privilege->comment, strlen(privilege->comment), + system_charset_info); + if (protocol->write()) + DBUG_RETURN(TRUE); + } + my_eof(thd); + DBUG_RETURN(FALSE); +} + + +/** Hash of LEX_STRINGs used to search for ignored db directories. */ +static HASH ignore_db_dirs_hash; + +/** + An array of LEX_STRING pointers to collect the options at + option parsing time. +*/ +static DYNAMIC_ARRAY ignore_db_dirs_array; + +/** + A value for the read only system variable to show a list of + ignored directories. +*/ +char *opt_ignore_db_dirs= NULL; + +/** + This flag is ON if: + - the list of ignored directories is not empty + + - and some of the ignored directory names + need no tablename-to-filename conversion. + Otherwise, if the name of the directory contains + unconditional characters like '+' or '.', they + never can match the database directory name. So the + db_name_is_in_ignore_db_dirs_list() can just return at once. +*/ +static bool skip_ignored_dir_check= TRUE; + +/** + Sets up the data structures for collection of directories at option + processing time. + We need to collect the directories in an array first, because + we need the character sets initialized before setting up the hash. + + @return state + @retval TRUE failed + @retval FALSE success +*/ + +bool +ignore_db_dirs_init() +{ + return my_init_dynamic_array(key_memory_ignored_db, &ignore_db_dirs_array, + sizeof(LEX_STRING *), 0, 0, MYF(0)); +} + + +/** + Retrieves the key (the string itself) from the LEX_STRING hash members. + + Needed by hash_init(). + + @param data the data element from the hash + @param out len_ret Placeholder to return the length of the key + @param unused + @return a pointer to the key +*/ + +static uchar * +db_dirs_hash_get_key(const uchar *data, size_t *len_ret, + my_bool __attribute__((unused))) +{ + LEX_CSTRING *e= (LEX_CSTRING *) data; + + *len_ret= e->length; + return (uchar *) e->str; +} + + +/** + Wrap a directory name into a LEX_STRING and push it to the array. + + Called at option processing time for each --ignore-db-dir option. + + @param path the name of the directory to push + @return state + @retval TRUE failed + @retval FALSE success +*/ + +bool push_ignored_db_dir(const char *path) +{ + LEX_CSTRING *new_elt; + char *new_elt_buffer; + size_t path_len= strlen(path); + + if (!path_len || path_len >= FN_REFLEN) + return true; + + // No need to normalize, it's only a directory name, not a path. + if (!my_multi_malloc(key_memory_ignored_db, MYF(0), + &new_elt, sizeof(LEX_STRING), + &new_elt_buffer, path_len + 1, + NullS)) + return true; + new_elt->str= new_elt_buffer; + memcpy(new_elt_buffer, path, path_len); + new_elt_buffer[path_len]= 0; + new_elt->length= path_len; + return insert_dynamic(&ignore_db_dirs_array, (uchar*) &new_elt); +} + + +/** + Clean up the directory ignore options accumulated so far. + + Called at option processing time for each --ignore-db-dir option + with an empty argument. +*/ + +void +ignore_db_dirs_reset() +{ + LEX_CSTRING **elt; + while (NULL!= (elt= (LEX_CSTRING **) pop_dynamic(&ignore_db_dirs_array))) + if (elt && *elt) + my_free(*elt); +} + + +/** + Free the directory ignore option variables. + + Called at server shutdown. +*/ + +void +ignore_db_dirs_free() +{ + if (opt_ignore_db_dirs) + { + my_free(opt_ignore_db_dirs); + opt_ignore_db_dirs= NULL; + } + ignore_db_dirs_reset(); + delete_dynamic(&ignore_db_dirs_array); + my_hash_free(&ignore_db_dirs_hash); +} + + +/** + Initialize the ignore db directories hash and status variable from + the options collected in the array. + + Called when option processing is over and the server's in-memory + structures are fully initialized. + + @return state + @retval TRUE failed + @retval FALSE success +*/ + +static void dispose_db_dir(void *ptr) +{ + my_free(ptr); +} + + +/* + Append an element into @@ignore_db_dirs + + This is a function to be called after regular option processing has been + finalized. +*/ + +void ignore_db_dirs_append(const char *dirname_arg) +{ + char *new_entry_buf; + LEX_STRING *new_entry; + size_t len= strlen(dirname_arg); + + if (!my_multi_malloc(PSI_INSTRUMENT_ME, MYF(0), + &new_entry, sizeof(LEX_STRING), + &new_entry_buf, len + 1, + NullS)) + return; + + memcpy(new_entry_buf, dirname_arg, len+1); + new_entry->str = new_entry_buf; + new_entry->length= len; + + if (my_hash_insert(&ignore_db_dirs_hash, (uchar *)new_entry)) + { + // Either the name is already there or out-of-memory. + my_free(new_entry); + return; + } + + // Append the name to the option string. + size_t curlen= strlen(opt_ignore_db_dirs); + // Add one for comma and one for \0. + size_t newlen= curlen + len + 1 + 1; + char *new_db_dirs; + if (!(new_db_dirs= (char*)my_malloc(PSI_INSTRUMENT_ME, newlen, MYF(0)))) + { + // This is not a critical condition + return; + } + + memcpy(new_db_dirs, opt_ignore_db_dirs, curlen); + if (curlen != 0) + new_db_dirs[curlen]=','; + memcpy(new_db_dirs + (curlen + ((curlen!=0)?1:0)), dirname_arg, len+1); + + if (opt_ignore_db_dirs) + my_free(opt_ignore_db_dirs); + opt_ignore_db_dirs= new_db_dirs; +} + +bool +ignore_db_dirs_process_additions() +{ + ulong i; + size_t len; + char *ptr; + LEX_CSTRING *dir; + + skip_ignored_dir_check= TRUE; + + if (my_hash_init(key_memory_ignored_db, &ignore_db_dirs_hash, + lower_case_table_names ? character_set_filesystem : + &my_charset_bin, 0, 0, 0, db_dirs_hash_get_key, + dispose_db_dir, HASH_UNIQUE)) + return true; + + /* len starts from 1 because of the terminating zero. */ + len= 1; + for (i= 0; i < ignore_db_dirs_array.elements; i++) + { + get_dynamic(&ignore_db_dirs_array, (uchar *) &dir, i); + len+= dir->length + 1; // +1 for the comma + if (skip_ignored_dir_check) + { + char buff[FN_REFLEN]; + (void) tablename_to_filename(dir->str, buff, sizeof(buff)); + skip_ignored_dir_check= strcmp(dir->str, buff) != 0; + } + } + + /* No delimiter for the last directory. */ + if (len > 1) + len--; + + /* +1 the terminating zero */ + ptr= opt_ignore_db_dirs= (char *) my_malloc(key_memory_ignored_db, len + 1, + MYF(0)); + if (!ptr) + return true; + + /* Make sure we have an empty string to start with. */ + *ptr= 0; + + for (i= 0; i < ignore_db_dirs_array.elements; i++) + { + get_dynamic(&ignore_db_dirs_array, (uchar *) &dir, i); + if (my_hash_insert(&ignore_db_dirs_hash, (uchar *)dir)) + { + /* ignore duplicates from the config file */ + if (my_hash_search(&ignore_db_dirs_hash, (uchar *)dir->str, dir->length)) + { + sql_print_warning("Duplicate ignore-db-dir directory name '%.*s' " + "found in the config file(s). Ignoring the duplicate.", + (int) dir->length, dir->str); + my_free(dir); + goto continue_loop; + } + + return true; + } + ptr= strnmov(ptr, dir->str, dir->length); + *(ptr++)= ','; + +continue_loop: + /* + Set the transferred array element to NULL to avoid double free + in case of error. + */ + dir= NULL; + set_dynamic(&ignore_db_dirs_array, (uchar *) &dir, i); + } + + if (ptr > opt_ignore_db_dirs) + { + ptr--; + DBUG_ASSERT(*ptr == ','); + } + + /* make sure the string is terminated */ + DBUG_ASSERT(ptr - opt_ignore_db_dirs <= (ptrdiff_t) len); + *ptr= 0; + + /* + It's OK to empty the array here as the allocated elements are + referenced through the hash now. + */ + reset_dynamic(&ignore_db_dirs_array); + + return false; +} + + +/** + Check if a directory name is in the hash of ignored directories. + + @return search result + @retval TRUE found + @retval FALSE not found +*/ + +static inline bool +is_in_ignore_db_dirs_list(const char *directory) +{ + return ignore_db_dirs_hash.records && + NULL != my_hash_search(&ignore_db_dirs_hash, (const uchar *) directory, + strlen(directory)); +} + + +/** + Check if a database name is in the hash of ignored directories. + + @return search result + @retval TRUE found + @retval FALSE not found +*/ + +bool +db_name_is_in_ignore_db_dirs_list(const char *directory) +{ + char buff[FN_REFLEN]; + uint buff_len; + + if (skip_ignored_dir_check) + return 0; + + buff_len= tablename_to_filename(directory, buff, sizeof(buff)); + + return my_hash_search(&ignore_db_dirs_hash, (uchar *) buff, buff_len)!=NULL; +} + +enum find_files_result { + FIND_FILES_OK, + FIND_FILES_OOM, + FIND_FILES_DIR +}; + +/* + find_files() - find files in a given directory. + + SYNOPSIS + find_files() + thd thread handler + files put found files in this list + db database name to search tables in + or NULL to search for databases + path path to database + wild filter for found files + + RETURN + FIND_FILES_OK success + FIND_FILES_OOM out of memory error + FIND_FILES_DIR no such directory, or directory can't be read +*/ + + +static find_files_result +find_files(THD *thd, Dynamic_array<LEX_CSTRING*> *files, LEX_CSTRING *db, + const char *path, const LEX_CSTRING *wild) +{ + MY_DIR *dirp; + Discovered_table_list tl(thd, files, wild); + DBUG_ENTER("find_files"); + + if (!(dirp = my_dir(path, MY_THREAD_SPECIFIC | (db ? 0 : MY_WANT_STAT)))) + { + if (my_errno == ENOENT && db) + my_error(ER_BAD_DB_ERROR, MYF(0), db->str); + else + my_error(ER_CANT_READ_DIR, MYF(0), path, my_errno); + DBUG_RETURN(FIND_FILES_DIR); + } + + if (!db) /* Return databases */ + { + for (size_t i=0; i < dirp->number_of_files; i++) + { + FILEINFO *file= dirp->dir_entry+i; +#ifdef USE_SYMDIR + char *ext; + char buff[FN_REFLEN]; + if (my_use_symdir && !strcmp(ext=fn_ext(file->name), ".sym")) + { + /* Only show the sym file if it points to a directory */ + char *end; + *ext=0; /* Remove extension */ + unpack_dirname(buff, file->name); + end= strend(buff); + if (end != buff && end[-1] == FN_LIBCHAR) + end[-1]= 0; // Remove end FN_LIBCHAR + if (!mysql_file_stat(key_file_misc, buff, file->mystat, MYF(0))) + continue; + } +#endif + if (!MY_S_ISDIR(file->mystat->st_mode)) + continue; + + if (is_in_ignore_db_dirs_list(file->name)) + continue; + + if (tl.add_file(file->name)) + goto err; + } + } + else + { + if (ha_discover_table_names(thd, db, dirp, &tl, false)) + goto err; + } + if (is_show_command(thd)) + tl.sort(); +#ifndef DBUG_OFF + else + { + /* + sort_desc() is used to find easier unstable mtr tests that query + INFORMATION_SCHEMA.{SCHEMATA|TABLES} without a proper ORDER BY. + This can be removed in some release after 10.3 (e.g. in 10.4). + */ + tl.sort_desc(); + } +#endif + + DBUG_PRINT("info",("found: %zu files", files->elements())); + my_dirend(dirp); + + DBUG_RETURN(FIND_FILES_OK); + +err: + my_dirend(dirp); + DBUG_RETURN(FIND_FILES_OOM); +} + + +/** + An Internal_error_handler that suppresses errors regarding views' + underlying tables that occur during privilege checking within SHOW CREATE + VIEW commands. This happens in the cases when + + - A view's underlying table (e.g. referenced in its SELECT list) does not + exist. There should not be an error as no attempt was made to access it + per se. + + - Access is denied for some table, column, function or stored procedure + such as mentioned above. This error gets raised automatically, since we + can't untangle its access checking from that of the view itself. + */ +class Show_create_error_handler : public Internal_error_handler { + + TABLE_LIST *m_top_view; + bool m_handling; + Security_context *m_sctx; + + char m_view_access_denied_message[MYSQL_ERRMSG_SIZE]; + char *m_view_access_denied_message_ptr; + +public: + + /** + Creates a new Show_create_error_handler for the particular security + context and view. + + @thd Thread context, used for security context information if needed. + @top_view The view. We do not verify at this point that top_view is in + fact a view since, alas, these things do not stay constant. + */ + explicit Show_create_error_handler(THD *thd, TABLE_LIST *top_view) : + m_top_view(top_view), m_handling(FALSE), + m_view_access_denied_message_ptr(NULL) + { + + m_sctx= MY_TEST(m_top_view->security_ctx) ? + m_top_view->security_ctx : thd->security_ctx; + } + + /** + Lazy instantiation of 'view access denied' message. The purpose of the + Show_create_error_handler is to hide details of underlying tables for + which we have no privileges behind ER_VIEW_INVALID messages. But this + obviously does not apply if we lack privileges on the view itself. + Unfortunately the information about for which table privilege checking + failed is not available at this point. The only way for us to check is by + reconstructing the actual error message and see if it's the same. + */ + char* get_view_access_denied_message(THD *thd) + { + if (!m_view_access_denied_message_ptr) + { + m_view_access_denied_message_ptr= m_view_access_denied_message; + my_snprintf(m_view_access_denied_message, MYSQL_ERRMSG_SIZE, + ER_THD(thd, ER_TABLEACCESS_DENIED_ERROR), "SHOW VIEW", + m_sctx->priv_user, + m_sctx->host_or_ip, + m_top_view->get_db_name(), m_top_view->get_table_name()); + } + return m_view_access_denied_message_ptr; + } + + bool handle_condition(THD *thd, uint sql_errno, const char * /* sqlstate */, + Sql_condition::enum_warning_level *level, + const char *message, Sql_condition ** /* cond_hdl */) + { + /* + The handler does not handle the errors raised by itself. + At this point we know if top_view is really a view. + */ + if (m_handling || !m_top_view->view) + return FALSE; + + m_handling= TRUE; + + bool is_handled; + + switch (sql_errno) + { + case ER_TABLEACCESS_DENIED_ERROR: + if (!strcmp(get_view_access_denied_message(thd), message)) + { + /* Access to top view is not granted, don't interfere. */ + is_handled= FALSE; + break; + } + /* fall through */ + case ER_COLUMNACCESS_DENIED_ERROR: + case ER_VIEW_NO_EXPLAIN: /* Error was anonymized, ignore all the same. */ + case ER_PROCACCESS_DENIED_ERROR: + is_handled= TRUE; + break; + + case ER_BAD_FIELD_ERROR: + case ER_SP_DOES_NOT_EXIST: + case ER_NO_SUCH_TABLE: + case ER_NO_SUCH_TABLE_IN_ENGINE: + /* Established behavior: warn if underlying tables, columns, or functions + are missing. */ + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_VIEW_INVALID, + ER_THD(thd, ER_VIEW_INVALID), + m_top_view->get_db_name(), + m_top_view->get_table_name()); + is_handled= TRUE; + break; + + default: + is_handled= FALSE; + } + + m_handling= FALSE; + return is_handled; + } +}; + + +/* + Return metadata for CREATE command for table or view + + @param thd Thread handler + @param table_list Table / view + @param field_list resulting list of fields + @param buffer resulting CREATE statement + + @return + @retval 0 OK + @retval 1 Error + +*/ + +bool +mysqld_show_create_get_fields(THD *thd, TABLE_LIST *table_list, + List<Item> *field_list, String *buffer) +{ + bool error= TRUE; + LEX *lex= thd->lex; + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("mysqld_show_create_get_fields"); + DBUG_PRINT("enter",("db: %s table: %s",table_list->db.str, + table_list->table_name.str)); + + if (lex->table_type == TABLE_TYPE_VIEW) + { + if (check_table_access(thd, SELECT_ACL, table_list, FALSE, 1, FALSE)) + { + DBUG_PRINT("debug", ("check_table_access failed")); + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), + "SHOW", thd->security_ctx->priv_user, + thd->security_ctx->host_or_ip, + table_list->db.str, table_list->alias.str); + goto exit; + } + DBUG_PRINT("debug", ("check_table_access succeeded")); + + /* Ignore temporary tables if this is "SHOW CREATE VIEW" */ + table_list->open_type= OT_BASE_ONLY; + } + else + { + /* + Temporary tables should be opened for SHOW CREATE TABLE, but not + for SHOW CREATE VIEW. + */ + if (thd->open_temporary_tables(table_list)) + goto exit; + + /* + The fact that check_some_access() returned FALSE does not mean that + access is granted. We need to check if table_list->grant.privilege + contains any table-specific privilege. + */ + DBUG_PRINT("debug", ("table_list->grant.privilege: %llx", + (longlong) (table_list->grant.privilege))); + if (check_some_access(thd, SHOW_CREATE_TABLE_ACLS, table_list) || + (table_list->grant.privilege & SHOW_CREATE_TABLE_ACLS) == NO_ACL) + { + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), + "SHOW", thd->security_ctx->priv_user, + thd->security_ctx->host_or_ip, + table_list->db.str, table_list->alias.str); + goto exit; + } + } + /* Access is granted. Execute the command. */ + + /* We want to preserve the tree for views. */ + lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_VIEW; + + { + /* + Use open_tables() directly rather than + open_normal_and_derived_tables(). This ensures that + close_thread_tables() is not called if open tables fails and the + error is ignored. This allows us to handle broken views nicely. + */ + uint counter; + Show_create_error_handler view_error_suppressor(thd, table_list); + thd->push_internal_handler(&view_error_suppressor); + bool open_error= + open_tables(thd, &table_list, &counter, + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL) || + mysql_handle_derived(lex, DT_INIT | DT_PREPARE); + thd->pop_internal_handler(); + if (unlikely(open_error && (thd->killed || thd->is_error()))) + goto exit; + } + + /* TODO: add environment variables show when it become possible */ + if (lex->table_type == TABLE_TYPE_VIEW && !table_list->view) + { + my_error(ER_WRONG_OBJECT, MYF(0), + table_list->db.str, table_list->table_name.str, "VIEW"); + goto exit; + } + else if (lex->table_type == TABLE_TYPE_SEQUENCE && + (!table_list->table || + table_list->table->s->table_type != TABLE_TYPE_SEQUENCE)) + { + my_error(ER_NOT_SEQUENCE, MYF(0), + table_list->db.str, table_list->table_name.str); + goto exit; + } + + buffer->length(0); + + if (table_list->view) + buffer->set_charset(table_list->view_creation_ctx->get_client_cs()); + + if ((table_list->view ? + show_create_view(thd, table_list, buffer) : + lex->table_type == TABLE_TYPE_SEQUENCE ? + show_create_sequence(thd, table_list, buffer) : + show_create_table(thd, table_list, buffer, NULL, WITHOUT_DB_NAME))) + goto exit; + + if (table_list->view) + { + field_list->push_back(new (mem_root) + Item_empty_string(thd, "View", NAME_CHAR_LEN), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Create View", + MY_MAX(buffer->length(),1024)), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "character_set_client", + MY_CS_CHARACTER_SET_NAME_SIZE), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "collation_connection", + MY_CS_COLLATION_NAME_SIZE), + mem_root); + } + else + { + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Table", NAME_CHAR_LEN), + mem_root); + // 1024 is for not to confuse old clients + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Create Table", + MY_MAX(buffer->length(),1024)), + mem_root); + } + error= FALSE; + +exit: + DBUG_RETURN(error); +} + + +/* + Return CREATE command for table or view + + @param thd Thread handler + @param table_list Table / view + + @return + @retval 0 OK + @retval 1 Error + + @notes + table_list->db and table_list->table_name are kept unchanged to + not cause problems with SP. +*/ + +bool +mysqld_show_create(THD *thd, TABLE_LIST *table_list) +{ + Protocol *protocol= thd->protocol; + char buff[2048]; + String buffer(buff, sizeof(buff), system_charset_info); + List<Item> field_list; + bool error= TRUE; + DBUG_ENTER("mysqld_show_create"); + DBUG_PRINT("enter",("db: %s table: %s",table_list->db.str, + table_list->table_name.str)); + + /* + Metadata locks taken during SHOW CREATE should be released when + the statmement completes as it is an information statement. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); + + TABLE_LIST archive; + + if (mysqld_show_create_get_fields(thd, table_list, &field_list, &buffer)) + goto exit; + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + goto exit; + + protocol->prepare_for_resend(); + if (table_list->view) + protocol->store(&table_list->view_name, system_charset_info); + else + { + if (table_list->schema_table) + protocol->store(table_list->schema_table->table_name, + strlen(table_list->schema_table->table_name), + system_charset_info); + else + protocol->store(table_list->table->alias.ptr(), + table_list->table->alias.length(), + system_charset_info); + } + + if (table_list->view) + { + buffer.set_charset(table_list->view_creation_ctx->get_client_cs()); + protocol->store(&buffer); + + protocol->store(&table_list->view_creation_ctx->get_client_cs()->cs_name, + system_charset_info); + + protocol->store(&table_list->view_creation_ctx->get_connection_cl()-> + coll_name, system_charset_info); + } + else + protocol->store(&buffer); + + if (protocol->write()) + goto exit; + + error= FALSE; + my_eof(thd); + +exit: + close_thread_tables(thd); + /* Release any metadata locks taken during SHOW CREATE. */ + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + DBUG_RETURN(error); +} + + +void mysqld_show_create_db_get_fields(THD *thd, List<Item> *field_list) +{ + MEM_ROOT *mem_root= thd->mem_root; + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Database", NAME_CHAR_LEN), + mem_root); + field_list->push_back(new (mem_root) + Item_empty_string(thd, "Create Database", 1024), + mem_root); +} + + +bool mysqld_show_create_db(THD *thd, LEX_CSTRING *dbname, + LEX_CSTRING *orig_dbname, + const DDL_options_st &options) +{ + char buff[2048+DATABASE_COMMENT_MAXLEN]; + String buffer(buff, sizeof(buff), system_charset_info); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *sctx= thd->security_ctx; + privilege_t db_access(NO_ACL); +#endif + Schema_specification_st create; + Protocol *protocol=thd->protocol; + List<Item> field_list; + DBUG_ENTER("mysql_show_create_db"); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (test_all_bits(sctx->master_access, DB_ACLS)) + db_access=DB_ACLS; + else + db_access= acl_get_all3(sctx, dbname->str, FALSE) | + sctx->master_access; + + if (!(db_access & DB_ACLS) && check_grant_db(thd,dbname->str)) + { + status_var_increment(thd->status_var.access_denied_errors); + my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), + sctx->priv_user, sctx->host_or_ip, dbname->str); + general_log_print(thd,COM_INIT_DB,ER_THD(thd, ER_DBACCESS_DENIED_ERROR), + sctx->priv_user, sctx->host_or_ip, orig_dbname->str); + DBUG_RETURN(TRUE); + } +#endif + if (is_infoschema_db(dbname)) + { + *dbname= INFORMATION_SCHEMA_NAME; + create.default_table_charset= system_charset_info; + create.schema_comment= NULL; + } + else + { + if (check_db_dir_existence(dbname->str)) + { + my_error(ER_BAD_DB_ERROR, MYF(0), dbname->str); + DBUG_RETURN(TRUE); + } + + load_db_opt_by_name(thd, dbname->str, &create); + } + + mysqld_show_create_db_get_fields(thd, &field_list); + + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + protocol->prepare_for_resend(); + protocol->store(orig_dbname->str, orig_dbname->length, system_charset_info); + buffer.length(0); + buffer.append(STRING_WITH_LEN("CREATE DATABASE ")); + if (options.if_not_exists()) + buffer.append(STRING_WITH_LEN("/*!32312 IF NOT EXISTS*/ ")); + append_identifier(thd, &buffer, dbname); + + if (create.default_table_charset) + { + buffer.append(STRING_WITH_LEN(" /*!40100")); + buffer.append(STRING_WITH_LEN(" DEFAULT CHARACTER SET ")); + buffer.append(create.default_table_charset->cs_name); + if (Charset(create.default_table_charset).can_have_collate_clause()) + { + buffer.append(STRING_WITH_LEN(" COLLATE ")); + buffer.append(create.default_table_charset->coll_name); + } + buffer.append(STRING_WITH_LEN(" */")); + } + + if (create.schema_comment) + { + buffer.append(STRING_WITH_LEN(" COMMENT ")); + append_unescaped(&buffer, create.schema_comment->str, + create.schema_comment->length); + } + protocol->store(buffer.ptr(), buffer.length(), buffer.charset()); + + if (protocol->write()) + DBUG_RETURN(TRUE); + my_eof(thd); + DBUG_RETURN(FALSE); +} + + + +/**************************************************************************** + Return only fields for API mysql_list_fields + Use "show table wildcard" in mysql instead of this +****************************************************************************/ + +void +mysqld_list_fields(THD *thd, TABLE_LIST *table_list, const char *wild) +{ + TABLE *table; + DBUG_ENTER("mysqld_list_fields"); + DBUG_PRINT("enter",("table: %s", table_list->table_name.str)); + + if (open_normal_and_derived_tables(thd, table_list, + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL, + DT_INIT | DT_PREPARE)) + DBUG_VOID_RETURN; + table= table_list->table; + + List<Field> field_list; + + Field **ptr,*field; + for (ptr=table->field ; (field= *ptr); ptr++) + { + if (!wild || !wild[0] || + !wild_case_compare(system_charset_info, field->field_name.str,wild)) + field_list.push_back(field); + } + restore_record(table, s->default_values); // Get empty record + table->use_all_columns(); + if (thd->protocol->send_list_fields(&field_list, table_list)) + DBUG_VOID_RETURN; + my_eof(thd); + DBUG_VOID_RETURN; +} + +/* + Go through all character combinations and ensure that sql_lex.cc can + parse it as an identifier. + + SYNOPSIS + require_quotes() + name attribute name + name_length length of name + + RETURN + # Pointer to conflicting character + 0 No conflicting character +*/ + +static const char *require_quotes(const char *name, uint name_length) +{ + bool pure_digit= TRUE; + const char *end= name + name_length; + + for (; name < end ; name++) + { + uchar chr= (uchar) *name; + int length= system_charset_info->charlen(name, end); + if (length == 1 && !system_charset_info->ident_map[chr]) + return name; + if (length == 1 && (chr < '0' || chr > '9')) + pure_digit= FALSE; + } + if (pure_digit) + return name; + return 0; +} + + +/** + Convert and quote the given identifier if needed and append it to the + target string. If the given identifier is empty, it will be quoted. + @thd thread handler + @packet target string + @name the identifier to be appended + @length length of the appending identifier + + @return + 0 success + 1 error +*/ + +bool +append_identifier(THD *thd, String *packet, const char *name, size_t length) +{ + const char *name_end; + char quote_char; + int q= get_quote_char_for_identifier(thd, name, length); + + if (q == EOF) + return packet->append(name, length, packet->charset()); + + /* + The identifier must be quoted as it includes a quote character or + it's a keyword + */ + + /* + Special code for swe7. It encodes the letter "E WITH ACUTE" on + the position 0x60, where backtick normally resides. + In swe7 we cannot append 0x60 using system_charset_info, + because it cannot be converted to swe7 and will be replaced to + question mark '?'. Use &my_charset_bin to avoid this. + It will prevent conversion and will append the backtick as is. + */ + CHARSET_INFO *quote_charset= q == 0x60 && + (packet->charset()->state & MY_CS_NONASCII) && + packet->charset()->mbmaxlen == 1 ? + &my_charset_bin : system_charset_info; + + (void) packet->reserve(length*2 + 2); + quote_char= (char) q; + if (packet->append("e_char, 1, quote_charset)) + return true; + + for (name_end= name+length ; name < name_end ; ) + { + uchar chr= (uchar) *name; + int char_length= system_charset_info->charlen(name, name_end); + /* + charlen can return 0 and negative numbers on a wrong multibyte + sequence. It is possible when upgrading from 4.0, + and identifier contains some accented characters. + The manual says it does not work. So we'll just + change char_length to 1 not to hang in the endless loop. + */ + if (char_length <= 0) + char_length= 1; + if (char_length == 1 && chr == (uchar) quote_char && + packet->append("e_char, 1, quote_charset)) + return true; + if (packet->append(name, char_length, system_charset_info)) + return true; + name+= char_length; + } + return packet->append("e_char, 1, quote_charset); +} + + +/* + Get the quote character for displaying an identifier. + + SYNOPSIS + get_quote_char_for_identifier() + thd Thread handler + name name to quote + length length of name + + IMPLEMENTATION + Force quoting in the following cases: + - name is empty (for one, it is possible when we use this function for + quoting user and host names for DEFINER clause); + - name is a keyword; + - name includes a special character; + Otherwise identifier is quoted only if the option OPTION_QUOTE_SHOW_CREATE + is set. + + RETURN + EOF No quote character is needed + # Quote character +*/ + +int get_quote_char_for_identifier(THD *thd, const char *name, size_t length) +{ + if (length && + !is_keyword(name,(uint)length) && + !require_quotes(name, (uint)length) && + !(thd->variables.option_bits & OPTION_QUOTE_SHOW_CREATE)) + return EOF; + if (thd->variables.sql_mode & MODE_ANSI_QUOTES) + return '"'; + return '`'; +} + + +/* Append directory name (if exists) to CREATE INFO */ + +static void append_directory(THD *thd, String *packet, LEX_CSTRING *dir_type, + const char *filename) +{ + if (filename && !(thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)) + { + size_t length= dirname_length(filename); + packet->append(' '); + packet->append(dir_type); + packet->append(STRING_WITH_LEN(" DIRECTORY='")); +#ifdef _WIN32 + /* Convert \ to / to be able to create table on unix */ + char *winfilename= (char*) thd->memdup(filename, length); + char *pos, *end; + for (pos= winfilename, end= pos+length ; pos < end ; pos++) + { + if (*pos == '\\') + *pos = '/'; + } + filename= winfilename; +#endif + packet->append(filename, length); + packet->append('\''); + } +} + + +#define LIST_PROCESS_HOST_LEN 64 + + +/** + Print "ON UPDATE" clause of a field into a string. + + @param timestamp_field Pointer to timestamp field of a table. + @param field The field to generate ON UPDATE clause for. + @bool lcase Whether to print in lower case. + @return false on success, true on error. +*/ +static bool print_on_update_clause(Field *field, String *val, bool lcase) +{ + DBUG_ASSERT(val->charset()->mbminlen == 1); + val->length(0); + if (field->has_update_default_function()) + { + if (lcase) + val->append(STRING_WITH_LEN("on update ")); + else + val->append(STRING_WITH_LEN("ON UPDATE ")); + val->append(STRING_WITH_LEN("current_timestamp")); + if (field->decimals() > 0) + val->append_parenthesized(field->decimals()); + else + val->append(STRING_WITH_LEN("()")); + return true; + } + return false; +} + + +static bool get_field_default_value(THD *thd, Field *field, String *def_value, + bool quoted) +{ + bool has_default; + enum enum_field_types field_type= field->type(); + + has_default= (field->default_value || + (!(field->flags & NO_DEFAULT_VALUE_FLAG) && + !field->vers_sys_field() && + field->unireg_check != Field::NEXT_NUMBER)); + + def_value->length(0); + if (has_default) + { + StringBuffer<MAX_FIELD_WIDTH> str(field->charset()); + if (field->default_value) + { + field->default_value->print(&str); + if (field->default_value->expr->need_parentheses_in_default()) + { + def_value->set_charset(&my_charset_utf8mb4_general_ci); + def_value->append('('); + def_value->append(str); + def_value->append(')'); + } + else + def_value->append(str); + } + else if (!field->is_null()) + { // Not null by default + if (field_type == MYSQL_TYPE_BIT) + { + str.qs_append('b'); + str.qs_append('\''); + str.qs_append(field->val_int(), 2); + str.qs_append('\''); + quoted= 0; + } + else + { + field->val_str(&str); + if (!field->str_needs_quotes()) + quoted= 0; + } + if (str.length()) + { + StringBuffer<MAX_FIELD_WIDTH> def_val; + uint dummy_errors; + /* convert to system_charset_info == utf8 */ + def_val.copy(str.ptr(), str.length(), field->charset(), + system_charset_info, &dummy_errors); + if (quoted) + append_unescaped(def_value, def_val.ptr(), def_val.length()); + else + def_value->append(def_val); + } + else if (quoted) + def_value->set(STRING_WITH_LEN("''"), system_charset_info); + } + else if (field->maybe_null() && quoted) + def_value->set(STRING_WITH_LEN("NULL"), system_charset_info); // Null as default + else + return 0; + + } + return has_default; +} + + +/** + Appends list of options to string + + @param thd thread handler + @param packet string to append + @param opt list of options + @param check_options print all used options + @param rules list of known options +*/ + +static void append_create_options(THD *thd, String *packet, + engine_option_value *opt, + bool check_options, + ha_create_table_option *rules) +{ + bool in_comment= false; + for(; opt; opt= opt->next) + { + if (check_options) + { + if (is_engine_option_known(opt, rules)) + { + if (in_comment) + packet->append(STRING_WITH_LEN(" */")); + in_comment= false; + } + else + { + if (!in_comment) + packet->append(STRING_WITH_LEN(" /*")); + in_comment= true; + } + } + + DBUG_ASSERT(opt->value.str); + packet->append(' '); + append_identifier(thd, packet, &opt->name); + packet->append('='); + if (opt->quoted_value) + append_unescaped(packet, opt->value.str, opt->value.length); + else + packet->append(&opt->value); + } + if (in_comment) + packet->append(STRING_WITH_LEN(" */")); +} + +/** + Add table options to end of CREATE statement + + @param schema_table 1 if schema table + @param sequence 1 if sequence. If sequence, we flush out options + not relevant for sequences. +*/ + +static void add_table_options(THD *thd, TABLE *table, + Table_specification_st *create_info_arg, + bool schema_table, bool sequence, + String *packet) +{ + sql_mode_t sql_mode= thd->variables.sql_mode; + TABLE_SHARE *share= table->s; + handlerton *hton; + HA_CREATE_INFO create_info; + bool check_options= (!(sql_mode & MODE_IGNORE_BAD_TABLE_OPTIONS) && + (!create_info_arg || + create_info_arg->used_fields & + HA_CREATE_PRINT_ALL_OPTIONS)); + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->part_info) + hton= table->part_info->default_engine_type; + else +#endif + hton= table->file->ht; + + bzero((char*) &create_info, sizeof(create_info)); + /* Allow update_create_info to update row type, page checksums and options */ + create_info.row_type= share->row_type; + create_info.page_checksum= share->page_checksum; + create_info.options= share->db_create_options; + table->file->update_create_info(&create_info); + + /* + IF check_create_info + THEN add ENGINE only if it was used when creating the table + */ + if (!create_info_arg || + (create_info_arg->used_fields & HA_CREATE_USED_ENGINE)) + { + LEX_CSTRING *engine_name= table->file->engine_name(); + + if (sql_mode & (MODE_MYSQL323 | MODE_MYSQL40)) + packet->append(STRING_WITH_LEN(" TYPE=")); + else + packet->append(STRING_WITH_LEN(" ENGINE=")); + + packet->append(engine_name->str, engine_name->length); + } + + if (sequence) + goto end_options; + + /* + Add AUTO_INCREMENT=... if there is an AUTO_INCREMENT column, + and NEXT_ID > 1 (the default). We must not print the clause + for engines that do not support this as it would break the + import of dumps, but as of this writing, the test for whether + AUTO_INCREMENT columns are allowed and wether AUTO_INCREMENT=... + is supported is identical, !(file->table_flags() & HA_NO_AUTO_INCREMENT)) + Because of that, we do not explicitly test for the feature, + but may extrapolate its existence from that of an AUTO_INCREMENT column. + */ + + if (create_info.auto_increment_value > 1) + { + packet->append(STRING_WITH_LEN(" AUTO_INCREMENT=")); + packet->append_ulonglong(create_info.auto_increment_value); + } + + if (share->table_charset && !(sql_mode & (MODE_MYSQL323 | MODE_MYSQL40)) && + share->table_type != TABLE_TYPE_SEQUENCE) + { + /* + IF check_create_info + THEN add DEFAULT CHARSET only if it was used when creating the table + */ + if (!create_info_arg || + (create_info_arg->used_fields & HA_CREATE_USED_DEFAULT_CHARSET)) + { + packet->append(STRING_WITH_LEN(" DEFAULT CHARSET=")); + packet->append(share->table_charset->cs_name); + if (Charset(table->s->table_charset).can_have_collate_clause()) + { + packet->append(STRING_WITH_LEN(" COLLATE=")); + packet->append(table->s->table_charset->coll_name); + } + } + } + + if (share->min_rows) + { + packet->append(STRING_WITH_LEN(" MIN_ROWS=")); + packet->append_ulonglong(share->min_rows); + } + + if (share->max_rows && !schema_table && !sequence) + { + packet->append(STRING_WITH_LEN(" MAX_ROWS=")); + packet->append_ulonglong(share->max_rows); + } + + if (share->avg_row_length) + { + packet->append(STRING_WITH_LEN(" AVG_ROW_LENGTH=")); + packet->append_ulonglong(share->avg_row_length); + } + + if (create_info.options & HA_OPTION_PACK_KEYS) + packet->append(STRING_WITH_LEN(" PACK_KEYS=1")); + if (create_info.options & HA_OPTION_NO_PACK_KEYS) + packet->append(STRING_WITH_LEN(" PACK_KEYS=0")); + if (share->db_create_options & HA_OPTION_STATS_PERSISTENT) + packet->append(STRING_WITH_LEN(" STATS_PERSISTENT=1")); + if (share->db_create_options & HA_OPTION_NO_STATS_PERSISTENT) + packet->append(STRING_WITH_LEN(" STATS_PERSISTENT=0")); + if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON) + packet->append(STRING_WITH_LEN(" STATS_AUTO_RECALC=1")); + else if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF) + packet->append(STRING_WITH_LEN(" STATS_AUTO_RECALC=0")); + if (share->stats_sample_pages != 0) + { + packet->append(STRING_WITH_LEN(" STATS_SAMPLE_PAGES=")); + packet->append_ulonglong(share->stats_sample_pages); + } + + /* We use CHECKSUM, instead of TABLE_CHECKSUM, for backward compatibility */ + if (create_info.options & HA_OPTION_CHECKSUM) + packet->append(STRING_WITH_LEN(" CHECKSUM=1")); + if (create_info.page_checksum != HA_CHOICE_UNDEF) + { + packet->append(STRING_WITH_LEN(" PAGE_CHECKSUM=")); + packet->append(ha_choice_values[create_info.page_checksum]); + } + if (create_info.options & HA_OPTION_DELAY_KEY_WRITE) + packet->append(STRING_WITH_LEN(" DELAY_KEY_WRITE=1")); + if (create_info.row_type != ROW_TYPE_DEFAULT) + { + packet->append(STRING_WITH_LEN(" ROW_FORMAT=")); + packet->append(&ha_row_type[(uint) create_info.row_type]); + } + if (share->transactional != HA_CHOICE_UNDEF) + { + bool do_comment= !table->file->has_transactional_option() && check_options; + if (do_comment) + packet->append(STRING_WITH_LEN(" /*")); + packet->append(STRING_WITH_LEN(" TRANSACTIONAL=")); + packet->append(ha_choice_values[(uint) share->transactional]); + if (do_comment) + packet->append(STRING_WITH_LEN(" */")); + } + if (share->table_type == TABLE_TYPE_SEQUENCE) + packet->append(STRING_WITH_LEN(" SEQUENCE=1")); + if (table->s->key_block_size) + { + packet->append(STRING_WITH_LEN(" KEY_BLOCK_SIZE=")); + packet->append_ulonglong(table->s->key_block_size); + } + table->file->append_create_info(packet); + +end_options: + if (share->comment.length) + { + packet->append(STRING_WITH_LEN(" COMMENT=")); + append_unescaped(packet, share->comment.str, share->comment.length); + } + if (share->connect_string.length) + { + packet->append(STRING_WITH_LEN(" CONNECTION=")); + append_unescaped(packet, share->connect_string.str, share->connect_string.length); + } + append_create_options(thd, packet, share->option_list, check_options, + hton->table_options); + append_directory(thd, packet, &DATA_clex_str, create_info.data_file_name); + append_directory(thd, packet, &INDEX_clex_str, create_info.index_file_name); +} + +static void append_period(THD *thd, String *packet, const LEX_CSTRING &start, + const LEX_CSTRING &end, const LEX_CSTRING &period, + bool ident) +{ + packet->append(STRING_WITH_LEN(",\n PERIOD FOR ")); + if (ident) + append_identifier(thd, packet, period.str, period.length); + else + packet->append(period); + packet->append(STRING_WITH_LEN(" (")); + append_identifier(thd, packet, start.str, start.length); + packet->append(STRING_WITH_LEN(", ")); + append_identifier(thd, packet, end.str, end.length); + packet->append(STRING_WITH_LEN(")")); +} + +int show_create_table(THD *thd, TABLE_LIST *table_list, String *packet, + Table_specification_st *create_info_arg, + enum_with_db_name with_db_name) +{ + return show_create_table_ex(thd, table_list, NULL, NULL, packet, + create_info_arg, with_db_name); +} + +/* + Build a CREATE TABLE statement for a table. + + SYNOPSIS + show_create_table() + thd The thread + table_list A list containing one table to write statement + for. + force_db If not NULL, database name to use in the CREATE + TABLE statement. + force_name If not NULL, table name to use in the CREATE TABLE + statement. if NULL, the name from table_list will be + used. + packet Pointer to a string where statement will be + written. + create_info_arg Pointer to create information that can be used + to tailor the format of the statement. Can be + NULL, in which case only SQL_MODE is considered + when building the statement. + with_db_name Add database name to table name + + NOTE + Currently always return 0, but might return error code in the + future. + + RETURN + 0 OK + */ + +int show_create_table_ex(THD *thd, TABLE_LIST *table_list, + const char *force_db, const char *force_name, + String *packet, + Table_specification_st *create_info_arg, + enum_with_db_name with_db_name) +{ + List<Item> field_list; + char tmp[MAX_FIELD_WIDTH], *for_str, def_value_buf[MAX_FIELD_WIDTH]; + LEX_CSTRING alias; + String type; + String def_value; + Field **ptr,*field; + uint primary_key; + KEY *key_info; + TABLE *table= table_list->table; + TABLE_SHARE *share= table->s; + TABLE_SHARE::period_info_t &period= share->period; + sql_mode_t sql_mode= thd->variables.sql_mode; + bool explicit_fields= false; + bool foreign_db_mode= sql_mode & (MODE_POSTGRESQL | MODE_ORACLE | + MODE_MSSQL | MODE_DB2 | + MODE_MAXDB | MODE_ANSI); + bool limited_mysql_mode= sql_mode & (MODE_NO_FIELD_OPTIONS | MODE_MYSQL323 | + MODE_MYSQL40); + bool show_table_options= !(sql_mode & MODE_NO_TABLE_OPTIONS) && + !foreign_db_mode; + bool check_options= !(sql_mode & MODE_IGNORE_BAD_TABLE_OPTIONS) && + !create_info_arg; + handlerton *hton; + int error= 0; + DBUG_ENTER("show_create_table"); + DBUG_PRINT("enter",("table: %s", table->s->table_name.str)); + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->part_info) + hton= table->part_info->default_engine_type; + else +#endif + hton= table->file->ht; + + restore_record(table, s->default_values); // Get empty record + + packet->append(STRING_WITH_LEN("CREATE ")); + if (create_info_arg && + ((create_info_arg->or_replace() && + !create_info_arg->or_replace_slave_generated()) || + create_info_arg->table_was_deleted)) + packet->append(STRING_WITH_LEN("OR REPLACE ")); + if (share->tmp_table) + packet->append(STRING_WITH_LEN("TEMPORARY ")); + packet->append(STRING_WITH_LEN("TABLE ")); + if (create_info_arg && create_info_arg->if_not_exists()) + packet->append(STRING_WITH_LEN("IF NOT EXISTS ")); + + if (force_name) + { + if (force_db) + { + append_identifier(thd, packet, force_db, strlen(force_db)); + packet->append(STRING_WITH_LEN(".")); + } + append_identifier(thd, packet, force_name, strlen(force_name)); + } + else + { + if (table_list->schema_table) + { + alias.str= table_list->schema_table->table_name; + alias.length= strlen(alias.str); + } + else + { + if (lower_case_table_names == 2) + { + alias.str= table->alias.c_ptr(); + alias.length= table->alias.length(); + } + else + alias= share->table_name; + } + + /* + Print the database before the table name if told to do that. The + database name is only printed in the event that it is different + from the current database. The main reason for doing this is to + avoid having to update gazillions of tests and result files, but + it also saves a few bytes of the binary log. + */ + if (with_db_name == WITH_DB_NAME) + { + const LEX_CSTRING *const db= + table_list->schema_table ? &INFORMATION_SCHEMA_NAME : &table->s->db; + if (!thd->db.str || cmp(db, &thd->db)) + { + append_identifier(thd, packet, db); + packet->append(STRING_WITH_LEN(".")); + } + } + + append_identifier(thd, packet, &alias); + } + + packet->append(STRING_WITH_LEN(" (\n")); + /* + We need this to get default values from the table + We have to restore the read_set if we are called from insert in case + of row based replication. + */ + MY_BITMAP *old_map= tmp_use_all_columns(table, &table->read_set); + + bool not_the_first_field= false; + for (ptr=table->field ; (field= *ptr); ptr++) + { + + uint flags = field->flags; + + if (field->invisible > INVISIBLE_USER) + continue; + if (not_the_first_field) + packet->append(STRING_WITH_LEN(",\n")); + + not_the_first_field= true; + packet->append(STRING_WITH_LEN(" ")); + append_identifier(thd, packet, &field->field_name); + packet->append(' '); + + const Type_handler *th= field->type_handler(); + const Schema *implied_schema= Schema::find_implied(thd); + if (th != implied_schema->map_data_type(thd, th)) + { + packet->append(th->schema()->name(), system_charset_info); + packet->append(STRING_WITH_LEN("."), system_charset_info); + } + type.set(tmp, sizeof(tmp), system_charset_info); + field->sql_type(type); + packet->append(type.ptr(), type.length(), system_charset_info); + + if (field->has_charset() && !(sql_mode & (MODE_MYSQL323 | MODE_MYSQL40))) + { + if (field->charset() != share->table_charset) + { + packet->append(STRING_WITH_LEN(" CHARACTER SET ")); + packet->append(field->charset()->cs_name); + if (Charset(field->charset()).can_have_collate_clause()) + { + packet->append(STRING_WITH_LEN(" COLLATE ")); + packet->append(field->charset()->coll_name); + } + } + } + + if (field->vcol_info) + { + StringBuffer<MAX_FIELD_WIDTH> str(&my_charset_utf8mb4_general_ci); + field->vcol_info->print(&str); + packet->append(STRING_WITH_LEN(" GENERATED ALWAYS AS (")); + packet->append(str); + packet->append(STRING_WITH_LEN(")")); + if (field->vcol_info->stored_in_db) + packet->append(STRING_WITH_LEN(" STORED")); + else + packet->append(STRING_WITH_LEN(" VIRTUAL")); + if (field->invisible == INVISIBLE_USER) + { + packet->append(STRING_WITH_LEN(" INVISIBLE")); + } + } + else + { + if (field->flags & VERS_ROW_START) + { + packet->append(STRING_WITH_LEN(" GENERATED ALWAYS AS ROW START")); + } + else if (field->flags & VERS_ROW_END) + { + packet->append(STRING_WITH_LEN(" GENERATED ALWAYS AS ROW END")); + } + else if (flags & NOT_NULL_FLAG) + packet->append(STRING_WITH_LEN(" NOT NULL")); + else if (field->type() == MYSQL_TYPE_TIMESTAMP) + { + /* + TIMESTAMP field require explicit NULL flag, because unlike + all other fields they are treated as NOT NULL by default. + */ + packet->append(STRING_WITH_LEN(" NULL")); + } + + if (field->invisible == INVISIBLE_USER) + { + packet->append(STRING_WITH_LEN(" INVISIBLE")); + } + def_value.set(def_value_buf, sizeof(def_value_buf), system_charset_info); + if (get_field_default_value(thd, field, &def_value, 1)) + { + packet->append(STRING_WITH_LEN(" DEFAULT ")); + packet->append(def_value.ptr(), def_value.length(), system_charset_info); + } + + if (field->vers_update_unversioned()) + { + packet->append(STRING_WITH_LEN(" WITHOUT SYSTEM VERSIONING")); + } + + if (!limited_mysql_mode && + print_on_update_clause(field, &def_value, false)) + { + packet->append(STRING_WITH_LEN(" ")); + packet->append(def_value); + } + + if (field->unireg_check == Field::NEXT_NUMBER && + !(sql_mode & MODE_NO_FIELD_OPTIONS)) + packet->append(STRING_WITH_LEN(" AUTO_INCREMENT")); + } + + if (field->comment.length) + { + packet->append(STRING_WITH_LEN(" COMMENT ")); + append_unescaped(packet, field->comment.str, field->comment.length); + } + + append_create_options(thd, packet, field->option_list, check_options, + hton->field_options); + + if (field->check_constraint) + { + StringBuffer<MAX_FIELD_WIDTH> str(&my_charset_utf8mb4_general_ci); + field->check_constraint->print(&str); + packet->append(STRING_WITH_LEN(" CHECK (")); + packet->append(str); + packet->append(STRING_WITH_LEN(")")); + } + + } + + if (period.name) + { + append_period(thd, packet, + period.start_field(share)->field_name, + period.end_field(share)->field_name, + period.name, true); + } + + key_info= table->s->key_info; + primary_key= share->primary_key; + + for (uint i=0 ; i < share->keys ; i++,key_info++) + { + if (key_info->flags & HA_INVISIBLE_KEY) + continue; + KEY_PART_INFO *key_part= key_info->key_part; + bool found_primary=0; + packet->append(STRING_WITH_LEN(",\n ")); + + if (i == primary_key && !strcmp(key_info->name.str, primary_key_name.str)) + { + found_primary=1; + /* + No space at end, because a space will be added after where the + identifier would go, but that is not added for primary key. + */ + packet->append(STRING_WITH_LEN("PRIMARY KEY")); + } + else if (key_info->flags & HA_NOSAME) + packet->append(STRING_WITH_LEN("UNIQUE KEY ")); + else if (key_info->flags & HA_FULLTEXT) + packet->append(STRING_WITH_LEN("FULLTEXT KEY ")); + else if (key_info->flags & HA_SPATIAL) + packet->append(STRING_WITH_LEN("SPATIAL KEY ")); + else + packet->append(STRING_WITH_LEN("KEY ")); + + if (!found_primary) + append_identifier(thd, packet, &key_info->name); + + packet->append(STRING_WITH_LEN(" (")); + + uint key_parts= key_info->user_defined_key_parts; + if (key_info->without_overlaps) + key_parts-= 2; + + for (uint j=0 ; j < key_parts ; j++,key_part++) + { + Field *field= key_part->field; + if (field->invisible > INVISIBLE_USER) + continue; + + if (j) + packet->append(','); + + if (key_part->field) + append_identifier(thd, packet, &key_part->field->field_name); + if (key_part->field && + (key_part->length != + table->field[key_part->fieldnr-1]->key_length() && + !(key_info->flags & (HA_FULLTEXT | HA_SPATIAL)))) + { + packet->append_parenthesized((long) key_part->length / + key_part->field->charset()->mbmaxlen); + } + if (table->file->index_flags(i, j, 0) & HA_READ_ORDER && + key_part->key_part_flag & HA_REVERSE_SORT) /* same in SHOW KEYS */ + packet->append(STRING_WITH_LEN(" DESC")); + } + + if (key_info->without_overlaps) + { + packet->append(','); + append_identifier(thd, packet, &share->period.name); + packet->append(STRING_WITH_LEN(" WITHOUT OVERLAPS")); + } + + packet->append(')'); + store_key_options(thd, packet, table, &table->key_info[i]); + if (key_info->parser) + { + LEX_CSTRING *parser_name= plugin_name(key_info->parser); + packet->append(STRING_WITH_LEN(" /*!50100 WITH PARSER ")); + append_identifier(thd, packet, parser_name); + packet->append(STRING_WITH_LEN(" */ ")); + } + append_create_options(thd, packet, key_info->option_list, check_options, + hton->index_options); + } + + if (table->versioned()) + { + const Field *fs = table->vers_start_field(); + const Field *fe = table->vers_end_field(); + DBUG_ASSERT(fs); + DBUG_ASSERT(fe); + explicit_fields= fs->invisible < INVISIBLE_SYSTEM; + DBUG_ASSERT(!explicit_fields || fe->invisible < INVISIBLE_SYSTEM); + if (explicit_fields) + { + append_period(thd, packet, fs->field_name, fe->field_name, + table->s->vers.name, false); + } + else + { + DBUG_ASSERT(fs->invisible == INVISIBLE_SYSTEM); + DBUG_ASSERT(fe->invisible == INVISIBLE_SYSTEM); + } + } + + /* + Get possible foreign key definitions stored in InnoDB and append them + to the CREATE TABLE statement + */ + + if ((for_str= table->file->get_foreign_key_create_info())) + { + packet->append(for_str, strlen(for_str)); + table->file->free_foreign_key_create_info(for_str); + } + + /* Add table level check constraints */ + if (share->table_check_constraints) + { + StringBuffer<MAX_FIELD_WIDTH> str(&my_charset_utf8mb4_general_ci); + for (uint i= share->field_check_constraints; + i < share->table_check_constraints ; i++) + { + Virtual_column_info *check= table->check_constraints[i]; + // period constraint is implicit + if (share->period.constr_name.streq(check->name)) + continue; + + str.set_buffer_if_not_allocated(&my_charset_utf8mb4_general_ci); + str.length(0); // Print appends to str + check->print(&str); + + packet->append(STRING_WITH_LEN(",\n ")); + if (check->name.str) + { + packet->append(STRING_WITH_LEN("CONSTRAINT ")); + append_identifier(thd, packet, &check->name); + } + packet->append(STRING_WITH_LEN(" CHECK (")); + packet->append(str); + packet->append(STRING_WITH_LEN(")")); + } + } + + packet->append(STRING_WITH_LEN("\n)")); + if (show_table_options) + add_table_options(thd, table, create_info_arg, + table_list->schema_table != 0, 0, packet); + + if (!DBUG_IF("sysvers_hide") && table->versioned()) + packet->append(STRING_WITH_LEN(" WITH SYSTEM VERSIONING")); + +#ifdef WITH_PARTITION_STORAGE_ENGINE + { + if (table->part_info && + !((table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION) && + table->part_info->is_auto_partitioned)) + { + /* + Partition syntax for CREATE TABLE is at the end of the syntax. + */ + uint part_syntax_len; + char *part_syntax; + if ((part_syntax= generate_partition_syntax(thd, table->part_info, + &part_syntax_len, + show_table_options, + NULL, NULL))) + { + packet->append('\n'); + if (packet->append(part_syntax, part_syntax_len)) + error= 1; + } + } + } +#endif + tmp_restore_column_map(&table->read_set, old_map); + DBUG_RETURN(error); +} + + +static void store_key_options(THD *thd, String *packet, TABLE *table, + KEY *key_info) +{ + bool limited_mysql_mode= (thd->variables.sql_mode & + (MODE_NO_FIELD_OPTIONS | MODE_MYSQL323 | + MODE_MYSQL40)) != 0; + bool foreign_db_mode= (thd->variables.sql_mode & (MODE_POSTGRESQL | + MODE_ORACLE | + MODE_MSSQL | + MODE_DB2 | + MODE_MAXDB | + MODE_ANSI)) != 0; + char *end, buff[32]; + + if (!(thd->variables.sql_mode & MODE_NO_KEY_OPTIONS) && + !limited_mysql_mode && !foreign_db_mode) + { + + if (key_info->algorithm == HA_KEY_ALG_BTREE) + packet->append(STRING_WITH_LEN(" USING BTREE")); + + if (key_info->algorithm == HA_KEY_ALG_HASH || + key_info->algorithm == HA_KEY_ALG_LONG_HASH) + packet->append(STRING_WITH_LEN(" USING HASH")); + + /* send USING only in non-default case: non-spatial rtree */ + if ((key_info->algorithm == HA_KEY_ALG_RTREE) && + !(key_info->flags & HA_SPATIAL)) + packet->append(STRING_WITH_LEN(" USING RTREE")); + + if ((key_info->flags & HA_USES_BLOCK_SIZE) && + table->s->key_block_size != key_info->block_size) + { + packet->append(STRING_WITH_LEN(" KEY_BLOCK_SIZE=")); + end= longlong10_to_str(key_info->block_size, buff, 10); + packet->append(buff, (uint) (end - buff)); + } + DBUG_ASSERT(MY_TEST(key_info->flags & HA_USES_COMMENT) == + (key_info->comment.length > 0)); + if (key_info->flags & HA_USES_COMMENT) + { + packet->append(STRING_WITH_LEN(" COMMENT ")); + append_unescaped(packet, key_info->comment.str, + key_info->comment.length); + } + + if (key_info->is_ignored) + packet->append(STRING_WITH_LEN(" IGNORED")); + } +} + + +void view_store_options(THD *thd, TABLE_LIST *table, String *buff) +{ + if (table->algorithm != VIEW_ALGORITHM_INHERIT) + { + buff->append(STRING_WITH_LEN("ALGORITHM=")); + buff->append(view_algorithm(table)); + } + buff->append(' '); + append_definer(thd, buff, &table->definer.user, &table->definer.host); + if (table->view_suid) + buff->append(STRING_WITH_LEN("SQL SECURITY DEFINER ")); + else + buff->append(STRING_WITH_LEN("SQL SECURITY INVOKER ")); +} + + +/** + Returns ALGORITHM clause of a view +*/ + +static const LEX_CSTRING *view_algorithm(TABLE_LIST *table) +{ + static const LEX_CSTRING undefined= { STRING_WITH_LEN("UNDEFINED") }; + static const LEX_CSTRING merge= { STRING_WITH_LEN("MERGE") }; + static const LEX_CSTRING temptable= { STRING_WITH_LEN("TEMPTABLE") }; + switch (table->algorithm) { + case VIEW_ALGORITHM_TMPTABLE: + return &temptable; + case VIEW_ALGORITHM_MERGE: + return &merge; + default: + DBUG_ASSERT(0); // never should happen + /* fall through */ + case VIEW_ALGORITHM_UNDEFINED: + return &undefined; + } +} + + +static bool append_at_host(THD *thd, String *buffer, const LEX_CSTRING *host) +{ + if (!host->str || !host->str[0]) + return false; + return + buffer->append('@') || + append_identifier(thd, buffer, host); +} + + +/* + Append DEFINER clause to the given buffer. + + SYNOPSIS + append_definer() + thd [in] thread handle + buffer [inout] buffer to hold DEFINER clause + definer_user [in] user name part of definer + definer_host [in] host name part of definer +*/ + +bool append_definer(THD *thd, String *buffer, const LEX_CSTRING *definer_user, + const LEX_CSTRING *definer_host) +{ + return + buffer->append(STRING_WITH_LEN("DEFINER=")) || + append_identifier(thd, buffer, definer_user) || + append_at_host(thd, buffer, definer_host) || + buffer->append(' '); +} + + +static int show_create_view(THD *thd, TABLE_LIST *table, String *buff) +{ + my_bool compact_view_name= TRUE; + my_bool foreign_db_mode= (thd->variables.sql_mode & (MODE_POSTGRESQL | + MODE_ORACLE | + MODE_MSSQL | + MODE_DB2 | + MODE_MAXDB | + MODE_ANSI)) != 0; + + if (!thd->db.str || cmp(&thd->db, &table->view_db)) + /* + print compact view name if the view belongs to the current database + */ + compact_view_name= table->compact_view_format= FALSE; + else + { + /* + Compact output format for view body can be used + if this view only references table inside it's own db + */ + TABLE_LIST *tbl; + table->compact_view_format= TRUE; + for (tbl= thd->lex->query_tables; + tbl; + tbl= tbl->next_global) + { + if (!tbl->is_derived() && + cmp(&table->view_db, tbl->view ? &tbl->view_db : &tbl->db)) + { + table->compact_view_format= FALSE; + break; + } + } + } + + buff->append(STRING_WITH_LEN("CREATE ")); + if (!foreign_db_mode) + { + view_store_options(thd, table, buff); + } + buff->append(STRING_WITH_LEN("VIEW ")); + if (!compact_view_name) + { + append_identifier(thd, buff, &table->view_db); + buff->append('.'); + } + append_identifier(thd, buff, &table->view_name); + buff->append(STRING_WITH_LEN(" AS ")); + + /* + We can't just use table->query, because our SQL_MODE may trigger + a different syntax, like when ANSI_QUOTES is defined. + */ + table->view->unit.print(buff, enum_query_type(QT_VIEW_INTERNAL | + QT_ITEM_ORIGINAL_FUNC_NULLIF | + QT_NO_WRAPPERS_FOR_TVC_IN_VIEW)); + + if (table->with_check != VIEW_CHECK_NONE) + { + if (table->with_check == VIEW_CHECK_LOCAL) + buff->append(STRING_WITH_LEN(" WITH LOCAL CHECK OPTION")); + else + buff->append(STRING_WITH_LEN(" WITH CASCADED CHECK OPTION")); + } + return 0; +} + + +static int show_create_sequence(THD *thd, TABLE_LIST *table_list, + String *packet) +{ + TABLE *table= table_list->table; + SEQUENCE *seq= table->s->sequence; + LEX_CSTRING alias; + sql_mode_t sql_mode= thd->variables.sql_mode; + bool foreign_db_mode= sql_mode & (MODE_POSTGRESQL | MODE_ORACLE | + MODE_MSSQL | MODE_DB2 | + MODE_MAXDB | MODE_ANSI); + bool show_table_options= !(sql_mode & MODE_NO_TABLE_OPTIONS) && + !foreign_db_mode; + + if (lower_case_table_names == 2) + { + alias.str= table->alias.c_ptr(); + alias.length= table->alias.length(); + } + else + alias= table->s->table_name; + + packet->append(STRING_WITH_LEN("CREATE SEQUENCE ")); + append_identifier(thd, packet, &alias); + packet->append(STRING_WITH_LEN(" start with ")); + packet->append_longlong(seq->start); + packet->append(STRING_WITH_LEN(" minvalue ")); + packet->append_longlong(seq->min_value); + packet->append(STRING_WITH_LEN(" maxvalue ")); + packet->append_longlong(seq->max_value); + packet->append(STRING_WITH_LEN(" increment by ")); + packet->append_longlong(seq->increment); + if (seq->cache) + { + packet->append(STRING_WITH_LEN(" cache ")); + packet->append_longlong(seq->cache); + } + else + packet->append(STRING_WITH_LEN(" nocache")); + if (seq->cycle) + packet->append(STRING_WITH_LEN(" cycle")); + else + packet->append(STRING_WITH_LEN(" nocycle")); + + if (show_table_options) + add_table_options(thd, table, 0, 0, 1, packet); + return 0; +} + + +/**************************************************************************** + Return info about all processes + returns for each thread: thread id, user, host, db, command, info +****************************************************************************/ + +class thread_info :public ilink { +public: + static void *operator new(size_t size, MEM_ROOT *mem_root) throw () + { return alloc_root(mem_root, size); } + static void operator delete(void *ptr __attribute__((unused)), + size_t size __attribute__((unused))) + { TRASH_FREE(ptr, size); } + static void operator delete(void *, MEM_ROOT *){} + + my_thread_id thread_id; + uint32 os_thread_id; + ulonglong start_time; + uint command; + const char *user,*host,*db,*proc_info,*state_info; + CSET_STRING query_string; + double progress; +}; + +static const char *thread_state_info(THD *tmp) +{ +#ifndef EMBEDDED_LIBRARY + if (tmp->net.reading_or_writing) + { + if (tmp->net.reading_or_writing == 2) + return "Writing to net"; + if (tmp->get_command() == COM_SLEEP) + return ""; + return "Reading from net"; + } +#else + if (tmp->get_command() == COM_SLEEP) + return ""; +#endif + + if (tmp->proc_info) + return tmp->proc_info; + + /* Check if we are waiting on a condition */ + if (!trylock_short(&tmp->LOCK_thd_kill)) + { + /* mysys_var is protected by above mutex */ + bool cond= tmp->mysys_var && tmp->mysys_var->current_cond; + mysql_mutex_unlock(&tmp->LOCK_thd_kill); + if (cond) + return "Waiting on cond"; + } + return NULL; +} + + +struct list_callback_arg +{ + list_callback_arg(const char *u, THD *t, ulong m): + user(u), thd(t), max_query_length(m) {} + I_List<thread_info> thread_infos; + const char *user; + THD *thd; + ulong max_query_length; +}; + + +static my_bool list_callback(THD *tmp, list_callback_arg *arg) +{ + + Security_context *tmp_sctx= tmp->security_ctx; + bool got_thd_data; + if ((tmp->vio_ok() || tmp->system_thread) && + (!arg->user || (!tmp->system_thread && + tmp_sctx->user && !strcmp(tmp_sctx->user, arg->user)))) + { + thread_info *thd_info= new (arg->thd->mem_root) thread_info; + + thd_info->thread_id=tmp->thread_id; + thd_info->os_thread_id=tmp->os_thread_id; + thd_info->user= arg->thd->strdup(tmp_sctx->user ? tmp_sctx->user : + (tmp->system_thread ? + "system user" : "unauthenticated user")); + if (tmp->peer_port && (tmp_sctx->host || tmp_sctx->ip) && + arg->thd->security_ctx->host_or_ip[0]) + { + if ((thd_info->host= (char*) arg->thd->alloc(LIST_PROCESS_HOST_LEN+1))) + my_snprintf((char *) thd_info->host, LIST_PROCESS_HOST_LEN, + "%s:%u", tmp_sctx->host_or_ip, tmp->peer_port); + } + else + thd_info->host= arg->thd->strdup(tmp_sctx->host_or_ip[0] ? + tmp_sctx->host_or_ip : + tmp_sctx->host ? tmp_sctx->host : ""); + thd_info->command=(int) tmp->get_command(); + + if ((got_thd_data= !trylock_short(&tmp->LOCK_thd_data))) + { + /* This is an approximation */ + thd_info->proc_info= (char*) (tmp->killed >= KILL_QUERY ? + "Killed" : 0); + + /* The following variables are only safe to access under a lock */ + thd_info->db= 0; + if (tmp->db.str) + thd_info->db= arg->thd->strmake(tmp->db.str, tmp->db.length); + + if (tmp->query()) + { + uint length= MY_MIN(arg->max_query_length, tmp->query_length()); + char *q= arg->thd->strmake(tmp->query(),length); + /* Safety: in case strmake failed, we set length to 0. */ + thd_info->query_string= + CSET_STRING(q, q ? length : 0, tmp->query_charset()); + } + + /* + Progress report. We need to do this under a lock to ensure that all + is from the same stage. + */ + if (tmp->progress.max_counter) + { + uint max_stage= MY_MAX(tmp->progress.max_stage, 1); + thd_info->progress= (((tmp->progress.stage / (double) max_stage) + + ((tmp->progress.counter / + (double) tmp->progress.max_counter) / + (double) max_stage)) * + 100.0); + set_if_smaller(thd_info->progress, 100); + } + else + thd_info->progress= 0.0; + } + else + { + thd_info->proc_info= "Busy"; + thd_info->progress= 0.0; + thd_info->db= ""; + } + + thd_info->state_info= thread_state_info(tmp); + thd_info->start_time= tmp->start_utime; + ulonglong utime_after_query_snapshot= tmp->utime_after_query; + if (thd_info->start_time < utime_after_query_snapshot) + thd_info->start_time= utime_after_query_snapshot; // COM_SLEEP + + if (got_thd_data) + mysql_mutex_unlock(&tmp->LOCK_thd_data); + arg->thread_infos.append(thd_info); + } + return 0; +} + + +void mysqld_list_processes(THD *thd,const char *user, bool verbose) +{ + Item *field; + List<Item> field_list; + list_callback_arg arg(user, thd, + verbose ? thd->variables.max_allowed_packet : + PROCESS_LIST_WIDTH); + Protocol *protocol= thd->protocol; + MEM_ROOT *mem_root= thd->mem_root; + DBUG_ENTER("mysqld_list_processes"); + + field_list.push_back(new (mem_root) + Item_int(thd, "Id", 0, MY_INT32_NUM_DECIMAL_DIGITS), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "User", + USERNAME_CHAR_LENGTH), + mem_root); + field_list.push_back(new (mem_root) + Item_empty_string(thd, "Host", + LIST_PROCESS_HOST_LEN), + mem_root); + field_list.push_back(field=new (mem_root) + Item_empty_string(thd, "db", NAME_CHAR_LEN), + mem_root); + field->set_maybe_null();; + field_list.push_back(new (mem_root) Item_empty_string(thd, "Command", 16), + mem_root); + field_list.push_back(field= new (mem_root) + Item_return_int(thd, "Time", 7, MYSQL_TYPE_LONG), + mem_root); + field->unsigned_flag= 0; + field_list.push_back(field=new (mem_root) + Item_empty_string(thd, "State", 30), + mem_root); + field->set_maybe_null();; + field_list.push_back(field=new (mem_root) + Item_empty_string(thd, "Info", arg.max_query_length), + mem_root); + field->set_maybe_null();; + if (!(thd->variables.old_behavior & OLD_MODE_NO_PROGRESS_INFO)) + { + field_list.push_back(field= new (mem_root) + Item_float(thd, "Progress", 0.0, 3, 7), + mem_root); + field->base_flags&= ~item_base_t::MAYBE_NULL; + } + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_VOID_RETURN; + + if (thd->killed) + DBUG_VOID_RETURN; + + server_threads.iterate(list_callback, &arg); + + ulonglong now= microsecond_interval_timer(); + + while (thread_info *thd_info= arg.thread_infos.get()) + { + protocol->prepare_for_resend(); + protocol->store(thd_info->thread_id); + protocol->store(thd_info->user, strlen(thd_info->user), system_charset_info); + protocol->store(thd_info->host, strlen(thd_info->host), system_charset_info); + protocol->store_string_or_null(thd_info->db, system_charset_info); + if (thd_info->proc_info) + protocol->store(thd_info->proc_info, strlen(thd_info->proc_info), + system_charset_info); + else + protocol->store(&command_name[thd_info->command], system_charset_info); + if (thd_info->start_time && now > thd_info->start_time) + protocol->store_long((now - thd_info->start_time) / HRTIME_RESOLUTION); + else + protocol->store_null(); + protocol->store_string_or_null(thd_info->state_info, system_charset_info); + if (thd_info->query_string.length()) + protocol->store(thd_info->query_string.str(), + thd_info->query_string.length(), + thd_info->query_string.charset()); + else + protocol->store_null(); + if (!(thd->variables.old_behavior & OLD_MODE_NO_PROGRESS_INFO)) + protocol->store_double(thd_info->progress, 3); + if (protocol->write()) + break; /* purecov: inspected */ + } + my_eof(thd); + DBUG_VOID_RETURN; +} + + +/* + Produce EXPLAIN data. + + This function is APC-scheduled to be run in the context of the thread that + we're producing EXPLAIN for. +*/ + +void Show_explain_request::call_in_target_thread() +{ + Query_arena backup_arena; + bool printed_anything= FALSE; + + /* + Change the arena because JOIN::print_explain and co. are going to allocate + items. Let them allocate them on our arena. + */ + target_thd->set_n_backup_active_arena((Query_arena*)request_thd, + &backup_arena); + + query_str.copy(target_thd->query(), + target_thd->query_length(), + target_thd->query_charset()); + + DBUG_ASSERT(current_thd == target_thd); + + /* + When producing JSON output, one should not change current_thd. + (If one does that, they will hit an assert when printing constant item + fields). + */ + if (target_thd->lex->print_explain(explain_buf, 0 /* explain flags*/, + is_analyze, is_json_format, + &printed_anything)) + { + failed_to_produce= TRUE; + } + + if (!printed_anything) + failed_to_produce= TRUE; + + target_thd->restore_active_arena((Query_arena*)request_thd, &backup_arena); +} + + +int select_result_explain_buffer::send_data(List<Item> &items) +{ + int res; + THD *cur_thd= current_thd; + DBUG_ENTER("select_result_explain_buffer::send_data"); + + /* + Switch to the receiveing thread, so that we correctly count memory used + by it. This is needed as it's the receiving thread that will free the + memory. + (TODO: Now that we don't change current_thd in + Show_explain_request::call_in_target_thread, is this necessary anymore?) + */ + set_current_thd(thd); + fill_record(thd, dst_table, dst_table->field, items, TRUE, FALSE); + res= dst_table->file->ha_write_tmp_row(dst_table->record[0]); + set_current_thd(cur_thd); + DBUG_RETURN(MY_TEST(res)); +} + +bool select_result_text_buffer::send_result_set_metadata(List<Item> &fields, + uint flag) +{ + n_columns= fields.elements; + return append_row(fields, true /*send item names */); +} + + +int select_result_text_buffer::send_data(List<Item> &items) +{ + return append_row(items, false /*send item values */); +} + +int select_result_text_buffer::append_row(List<Item> &items, bool send_names) +{ + List_iterator<Item> it(items); + Item *item; + char **row; + int column= 0; + + if (!(row= (char**) thd->alloc(sizeof(char*) * n_columns)) || + rows.push_back(row, thd->mem_root)) + return true; + + StringBuffer<32> buf; + + while ((item= it++)) + { + DBUG_ASSERT(column < n_columns); + const char *data_ptr; + char *ptr; + size_t data_len; + + buf.set_buffer_if_not_allocated(&my_charset_bin); + if (send_names) + { + DBUG_ASSERT(strlen(item->name.str) == item->name.length); + data_ptr= item->name.str; + data_len= item->name.length; + } + else + { + String *res; + res= item->val_str(&buf); + if (item->null_value) + { + data_ptr= "NULL"; + data_len=4; + } + else + { + data_ptr= res->c_ptr_safe(); + data_len= res->length(); + } + } + + if (!(ptr= (char*) thd->memdup(data_ptr, data_len + 1))) + return true; + row[column]= ptr; + + column++; + } + return false; +} + + +void select_result_text_buffer::save_to(String *res) +{ + List_iterator<char*> it(rows); + char **row; + res->append(STRING_WITH_LEN("#\n")); + while ((row= it++)) + { + res->append(STRING_WITH_LEN("# explain: ")); + for (int i=0; i < n_columns; i++) + { + if (i) + res->append('\t'); + res->append(row[i], strlen(row[i])); + } + res->append('\n'); + } + res->append(STRING_WITH_LEN("#\n")); +} + + +/* + Store the SHOW EXPLAIN/SHOW ANALYZE output in the temporary table. +*/ + +int fill_show_explain_or_analyze(THD *thd, TABLE_LIST *table, COND *cond, + bool json_format, bool is_analyze) +{ + const char *calling_user; + THD *tmp; + my_thread_id thread_id; + DBUG_ENTER("fill_show_explain_or_analyze"); + + DBUG_ASSERT(cond==NULL); + thread_id= thd->lex->value_list.head()->val_int(); + calling_user= (thd->security_ctx->master_access & PRIV_STMT_SHOW_EXPLAIN) ? + NullS : thd->security_ctx->priv_user; + + if ((tmp= find_thread_by_id(thread_id))) + { + Security_context *tmp_sctx= tmp->security_ctx; + MEM_ROOT explain_mem_root, *save_mem_root; + + /* + If calling_user==NULL, calling thread has SUPER or PROCESS + privilege, and so can do SHOW EXPLAIN/SHOW ANALYZE on any user. + + if calling_user!=NULL, he's only allowed to view + SHOW EXPLAIN/SHOW ANALYZE on his own threads. + */ + if (calling_user && (!tmp_sctx->user || strcmp(calling_user, + tmp_sctx->user))) + { + my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESS"); + mysql_mutex_unlock(&tmp->LOCK_thd_kill); + DBUG_RETURN(1); + } + + if (tmp == thd) + { + mysql_mutex_unlock(&tmp->LOCK_thd_kill); + my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0)); + DBUG_RETURN(1); + } + + bool bres; + /* + Ok we've found the thread of interest and it won't go away because + we're holding its LOCK_thd_kill. Post it a SHOW EXPLAIN/SHOW ANALYZE + request. + */ + bool timed_out; + int timeout_sec= 30; + Show_explain_request explain_req; + explain_req.is_json_format= json_format; + select_result_explain_buffer *explain_buf; + + if (!(explain_buf= new (thd->mem_root) + select_result_explain_buffer(thd, table->table))) + DBUG_RETURN(1); + + explain_req.is_analyze= is_analyze; + explain_req.explain_buf= explain_buf; + explain_req.target_thd= tmp; + explain_req.request_thd= thd; + explain_req.failed_to_produce= FALSE; + + /* + Do not use default memroot as this is only to be used by the + target thread (It's marked as thread MY_THREAD_SPECIFIC). + */ + init_sql_alloc(key_memory_thd_main_mem_root, + &explain_mem_root, 0, 8000, MYF(0)); + save_mem_root= thd->mem_root; + thd->mem_root= &explain_mem_root; + + /* Ok, we have a lock on target->LOCK_thd_kill, can call: */ + bres= tmp->apc_target.make_apc_call(thd, &explain_req, timeout_sec, + &timed_out); + thd->mem_root= save_mem_root; + + if (bres || explain_req.failed_to_produce) + { + if (thd->killed) + thd->send_kill_message(); + else if (timed_out) + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + else + my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0)); + + bres= TRUE; + } + else + { + /* + Push the query string as a warning. The query may be in a different + charset than the charset that's used for error messages, so, convert it + if needed. + */ + CHARSET_INFO *fromcs= explain_req.query_str.charset(); + CHARSET_INFO *tocs= error_message_charset_info; + char *warning_text; + if (!my_charset_same(fromcs, tocs)) + { + uint conv_length= 1 + tocs->mbmaxlen * explain_req.query_str.length() / + fromcs->mbminlen; + uint dummy_errors; + char *to, *p; + if (!(to= (char*)thd->alloc(conv_length + 1))) + DBUG_RETURN(1); + p= to; + p+= copy_and_convert(to, conv_length, tocs, + explain_req.query_str.c_ptr(), + explain_req.query_str.length(), fromcs, + &dummy_errors); + *p= 0; + warning_text= to; + } + else + warning_text= explain_req.query_str.c_ptr_safe(); + + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_YES, warning_text); + } + free_root(&explain_mem_root, MYF(0)); + DBUG_RETURN(bres); + } + my_error(ER_NO_SUCH_THREAD, MYF(0), (ulong) thread_id); + DBUG_RETURN(1); +} + + +int fill_show_explain_tabular(THD *thd, TABLE_LIST *table, COND *cond) +{ + return fill_show_explain_or_analyze( + thd, table, cond, FALSE /* json_format */, FALSE /* is_analyze */); +} + + +int fill_show_explain_json(THD *thd, TABLE_LIST *table, COND *cond) +{ + return fill_show_explain_or_analyze( + thd, table, cond, TRUE /* json_format */, FALSE /* is_analyze */); +} + + +int fill_show_analyze_tabular(THD * thd, TABLE_LIST * table, COND * cond) +{ + return fill_show_explain_or_analyze( + thd, table, cond, FALSE /* json_format */, TRUE /* is_analyze */); +} + + +int fill_show_analyze_json(THD * thd, TABLE_LIST * table, COND * cond) +{ + return fill_show_explain_or_analyze( + thd, table, cond, TRUE /* json_format */, TRUE /* is_analyze */); +} + + +struct processlist_callback_arg +{ + processlist_callback_arg(THD *thd_arg, TABLE *table_arg): + thd(thd_arg), table(table_arg), unow(microsecond_interval_timer()) {} + THD *thd; + TABLE *table; + ulonglong unow; +}; + + +static my_bool processlist_callback(THD *tmp, processlist_callback_arg *arg) +{ + Security_context *tmp_sctx= tmp->security_ctx; + CHARSET_INFO *cs= system_charset_info; + const char *val; + ulonglong max_counter; + bool got_thd_data; + char *user= + arg->thd->security_ctx->master_access & PRIV_STMT_SHOW_PROCESSLIST ? + NullS : arg->thd->security_ctx->priv_user; + + if ((!tmp->vio_ok() && !tmp->system_thread) || + (user && (tmp->system_thread || !tmp_sctx->user || + strcmp(tmp_sctx->user, user)))) + return 0; + + restore_record(arg->table, s->default_values); + /* ID */ + arg->table->field[0]->store((longlong) tmp->thread_id, TRUE); + /* USER */ + val= tmp_sctx->user ? tmp_sctx->user : + (tmp->system_thread ? "system user" : "unauthenticated user"); + arg->table->field[1]->store(val, strlen(val), cs); + /* HOST */ + if (tmp->peer_port && (tmp_sctx->host || tmp_sctx->ip) && + arg->thd->security_ctx->host_or_ip[0]) + { + char host[LIST_PROCESS_HOST_LEN + 1]; + my_snprintf(host, LIST_PROCESS_HOST_LEN, "%s:%u", + tmp_sctx->host_or_ip, tmp->peer_port); + arg->table->field[2]->store(host, strlen(host), cs); + } + else + arg->table->field[2]->store(tmp_sctx->host_or_ip, + strlen(tmp_sctx->host_or_ip), cs); + + if ((got_thd_data= !trylock_short(&tmp->LOCK_thd_data))) + { + /* DB */ + if (tmp->db.str) + { + arg->table->field[3]->store(tmp->db.str, tmp->db.length, cs); + arg->table->field[3]->set_notnull(); + } + } + + /* COMMAND */ + if ((val= (char *) (!got_thd_data ? "Busy" : + (tmp->killed >= KILL_QUERY ? + "Killed" : 0)))) + arg->table->field[4]->store(val, strlen(val), cs); + else + arg->table->field[4]->store(command_name[tmp->get_command()].str, + command_name[tmp->get_command()].length, cs); + + /* MYSQL_TIME */ + ulonglong utime= tmp->start_utime; + ulonglong utime_after_query_snapshot= tmp->utime_after_query; + if (utime < utime_after_query_snapshot) + utime= utime_after_query_snapshot; // COM_SLEEP + utime= utime && utime < arg->unow ? arg->unow - utime : 0; + + arg->table->field[5]->store(utime / HRTIME_RESOLUTION, TRUE); + + if (got_thd_data) + { + if (tmp->query()) + { + arg->table->field[7]->store(tmp->query(), + MY_MIN(PROCESS_LIST_INFO_WIDTH, + tmp->query_length()), cs); + arg->table->field[7]->set_notnull(); + + /* INFO_BINARY */ + arg->table->field[16]->store(tmp->query(), + MY_MIN(PROCESS_LIST_INFO_WIDTH, + tmp->query_length()), + &my_charset_bin); + arg->table->field[16]->set_notnull(); + } + + /* + Progress report. We need to do this under a lock to ensure that all + is from the same stage. + */ + if ((max_counter= tmp->progress.max_counter)) + { + arg->table->field[9]->store((longlong) tmp->progress.stage + 1, 1); + arg->table->field[10]->store((longlong) tmp->progress.max_stage, 1); + arg->table->field[11]->store((double) tmp->progress.counter / + (double) max_counter*100.0); + } + mysql_mutex_unlock(&tmp->LOCK_thd_data); + } + + /* STATE */ + if ((val= thread_state_info(tmp))) + { + arg->table->field[6]->store(val, strlen(val), cs); + arg->table->field[6]->set_notnull(); + } + + /* TIME_MS */ + arg->table->field[8]->store((double)(utime / (HRTIME_RESOLUTION / 1000.0))); + + /* + This may become negative if we free a memory allocated by another + thread in this thread. However it's better that we notice it eventually + than hide it. + */ + arg->table->field[12]->store((longlong) tmp->status_var.local_memory_used, + FALSE); + arg->table->field[13]->store((longlong) tmp->status_var.max_local_memory_used, + FALSE); + arg->table->field[14]->store((longlong) tmp->get_examined_row_count(), TRUE); + + /* QUERY_ID */ + arg->table->field[15]->store(tmp->query_id, TRUE); + + arg->table->field[17]->store(tmp->os_thread_id); + + if (schema_table_store_record(arg->thd, arg->table)) + return 1; + return 0; +} + + +int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) +{ + processlist_callback_arg arg(thd, tables->table); + DBUG_ENTER("fill_schema_processlist"); + DEBUG_SYNC(thd,"fill_schema_processlist_after_unow"); + if (!thd->killed && + server_threads.iterate(processlist_callback, &arg)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + +/***************************************************************************** + Status functions +*****************************************************************************/ + +DYNAMIC_ARRAY all_status_vars; +static bool status_vars_inited= 0; +ulonglong status_var_array_version= 0; + +C_MODE_START +static int show_var_cmp(const void *var1, const void *var2) +{ + return strcasecmp(((SHOW_VAR*)var1)->name, ((SHOW_VAR*)var2)->name); +} +C_MODE_END + +/* + deletes all the SHOW_UNDEF elements from the array and calls + delete_dynamic() if it's completely empty. +*/ +static void shrink_var_array(DYNAMIC_ARRAY *array) +{ + uint a,b; + SHOW_VAR *all= dynamic_element(array, 0, SHOW_VAR *); + + for (a= b= 0; b < array->elements; b++) + if (all[b].type != SHOW_UNDEF) + all[a++]= all[b]; + if (a) + { + bzero(all+a, sizeof(SHOW_VAR)); // writing NULL-element to the end + array->elements= a; + } + else // array is completely empty - delete it + delete_dynamic(array); + status_var_array_version++; +} + +/* + Adds an array of SHOW_VAR entries to the output of SHOW STATUS + + SYNOPSIS + add_status_vars(SHOW_VAR *list) + list - an array of SHOW_VAR entries to add to all_status_vars + the last entry must be {0,0,SHOW_UNDEF} + + NOTE + The handling of all_status_vars[] is completely internal, it's allocated + automatically when something is added to it, and deleted completely when + the last entry is removed. + + As a special optimization, if add_status_vars() is called before + init_status_vars(), it assumes "startup mode" - neither concurrent access + to the array nor SHOW STATUS are possible (thus it skips locks and qsort) + + The last entry of the all_status_vars[] should always be {0,0,SHOW_UNDEF} +*/ +int add_status_vars(SHOW_VAR *list) +{ + int res= 0; + if (status_vars_inited) + mysql_rwlock_wrlock(&LOCK_all_status_vars); + if (!all_status_vars.buffer && // array is not allocated yet - do it now + my_init_dynamic_array(PSI_INSTRUMENT_ME, &all_status_vars, + sizeof(SHOW_VAR), 250, 50, MYF(0))) + { + res= 1; + goto err; + } + while (list->name) + res|= insert_dynamic(&all_status_vars, (uchar*)list++); + res|= insert_dynamic(&all_status_vars, (uchar*)list); // appending NULL-element + all_status_vars.elements--; // but next insert_dynamic should overwite it + if (status_vars_inited) + sort_dynamic(&all_status_vars, show_var_cmp); + status_var_array_version++; +err: + if (status_vars_inited) + mysql_rwlock_unlock(&LOCK_all_status_vars); + return res; +} + +/* + Make all_status_vars[] usable for SHOW STATUS + + NOTE + See add_status_vars(). Before init_status_vars() call, add_status_vars() + works in a special fast "startup" mode. Thus init_status_vars() + should be called as late as possible but before enabling multi-threading. +*/ +void init_status_vars() +{ + status_vars_inited=1; + sort_dynamic(&all_status_vars, show_var_cmp); + status_var_array_version++; +} + +void reset_status_vars() +{ + SHOW_VAR *ptr= (SHOW_VAR*) all_status_vars.buffer; + SHOW_VAR *last= ptr + all_status_vars.elements; + for (; ptr < last; ptr++) + { + /* Note that SHOW_LONG_NOFLUSH variables are not reset */ + if (ptr->type == SHOW_LONG) + *(ulong*) ptr->value= 0; + } +} + +/* + catch-all cleanup function, cleans up everything no matter what + + DESCRIPTION + This function is not strictly required if all add_status_vars/ + remove_status_vars are properly paired, but it's a safety measure that + deletes everything from the all_status_vars[] even if some + remove_status_vars were forgotten +*/ +void free_status_vars() +{ + delete_dynamic(&all_status_vars); + status_var_array_version++; +} + +/* + Removes an array of SHOW_VAR entries from the output of SHOW STATUS + + SYNOPSIS + remove_status_vars(SHOW_VAR *list) + list - an array of SHOW_VAR entries to remove to all_status_vars + the last entry must be {0,0,SHOW_UNDEF} + + NOTE + there's lots of room for optimizing this, especially in non-sorted mode, + but nobody cares - it may be called only in case of failed plugin + initialization in the mysqld startup. +*/ + +void remove_status_vars(SHOW_VAR *list) +{ + if (status_vars_inited) + { + mysql_rwlock_wrlock(&LOCK_all_status_vars); + SHOW_VAR *all= dynamic_element(&all_status_vars, 0, SHOW_VAR *); + + for (; list->name; list++) + { + int first= 0, last= ((int) all_status_vars.elements) - 1; + for ( ; first <= last; ) + { + int res, middle= (first + last) / 2; + if ((res= show_var_cmp(list, all + middle)) < 0) + last= middle - 1; + else if (res > 0) + first= middle + 1; + else + { + all[middle].type= SHOW_UNDEF; + break; + } + } + } + shrink_var_array(&all_status_vars); + mysql_rwlock_unlock(&LOCK_all_status_vars); + } + else + { + SHOW_VAR *all= dynamic_element(&all_status_vars, 0, SHOW_VAR *); + uint i; + for (; list->name; list++) + { + for (i= 0; i < all_status_vars.elements; i++) + { + if (show_var_cmp(list, all+i)) + continue; + all[i].type= SHOW_UNDEF; + break; + } + } + shrink_var_array(&all_status_vars); + } +} + +/* Current version of the all_status_vars. */ +ulonglong get_status_vars_version(void) +{ + return status_var_array_version; +} + +/** + A union holding a pointer to a type that can be referred by a status variable. + */ +union Any_pointer { + const void *as_void; + const uchar *as_uchar; + const char *as_char; + const char ** as_charptr; + const double *as_double; + const int * as_int; + const uint * as_uint; + const long *as_long; + const longlong *as_longlong; + const bool *as_bool; + const my_bool *as_my_bool; + const sys_var *as_sys_var; + const system_status_var *as_system_status_var; + const ha_rows *as_ha_rows; + const LEX_STRING *as_lex_cstring; + const SHOW_COMP_OPTION *as_show_comp_options; + intptr as_intptr; + Atomic_counter<uint32_t>* as_atomic_counter; +}; + +/** + @brief Returns the value of a system or a status variable. + + @param thd [in] The handle of the current THD. + @param variable [in] Details of the variable. + @param value_type [in] Variable type. + @param show_type [in] Variable show type. + @param status_var [in] Status variable pointer + @param charset [out] Character set of the value. + @param buff [in,out] Buffer to store the value. + (Needs to have enough memory + to hold the value of variable.) + @param length [out] Length of the value. + + @return Pointer to the value buffer. +*/ + +const char* get_one_variable(THD *thd, + const SHOW_VAR *variable, + enum_var_type value_type, SHOW_TYPE show_type, + system_status_var *status_var, + const CHARSET_INFO **charset, char *buff, + size_t *length) +{ + Any_pointer value, status_var_value; + value.as_void= variable->value; + status_var_value.as_system_status_var= status_var; + const char *pos= buff; + const char *end= buff; + + + if (show_type == SHOW_SYS) + { + const sys_var *var= value.as_sys_var; + show_type= var->show_type(); + value.as_uchar= var->value_ptr(thd, value_type, &null_clex_str); + *charset= var->charset(thd); + } + + /* + note that value may be == buff. All SHOW_xxx code below + should still work in this case + */ + switch (show_type) { + case SHOW_DOUBLE_STATUS: + value.as_char= status_var_value.as_char + value.as_intptr; + /* fall through */ + case SHOW_DOUBLE: + /* 6 is the default precision for '%f' in sprintf() */ + end= buff + my_fcvt(*value.as_double, 6, buff, NULL); + break; + case SHOW_LONG_STATUS: + value.as_char= status_var_value.as_char + value.as_intptr; + /* fall through */ + case SHOW_ULONG: + case SHOW_LONG_NOFLUSH: // the difference lies in refresh_status() +#ifndef _WIN64 + case SHOW_SIZE_T: +#endif + end= int10_to_str(*value.as_long, buff, 10); + break; + case SHOW_LONGLONG_STATUS: + value.as_char= status_var_value.as_char + value.as_intptr; + /* fall through */ + case SHOW_ULONGLONG: +#ifdef _WIN64 + case SHOW_SIZE_T: +#endif + end= longlong10_to_str(*value.as_longlong, buff, 10); + break; + case SHOW_HA_ROWS: + end= longlong10_to_str((longlong) *value.as_ha_rows, buff, 10); + break; + case SHOW_BOOL: + end= strmov(buff, *value.as_bool ? "ON" : "OFF"); + break; + case SHOW_MY_BOOL: + end= strmov(buff, *value.as_my_bool ? "ON" : "OFF"); + break; + case SHOW_UINT32_STATUS: + value.as_char= status_var_value.as_char + value.as_intptr; + /* fall through */ + case SHOW_UINT: + end= int10_to_str((long) *value.as_uint, buff, 10); + break; + case SHOW_SINT: + end= int10_to_str((long) *value.as_int, buff, -10); + break; + case SHOW_SLONG: + end= int10_to_str(*value.as_long, buff, -10); + break; + case SHOW_SLONGLONG: + end= longlong10_to_str(*value.as_longlong, buff, -10); + break; + case SHOW_HAVE: + { + pos= show_comp_option_name[(int) *value.as_show_comp_options]; + end= strend(pos); + break; + } + case SHOW_CHAR: + { + if (!(pos= value.as_char)) + pos= ""; + end= strend(pos); + break; + } + case SHOW_CHAR_PTR: + { + if (!(pos= *value.as_charptr)) + pos= ""; + + end= strend(pos); + break; + } + case SHOW_LEX_STRING: + { + if (!(pos= value.as_lex_cstring->str)) + end= pos= ""; + else + end= pos + value.as_lex_cstring->length; + break; + } + case SHOW_ATOMIC_COUNTER_UINT32_T: + end= int10_to_str(static_cast<long>(*value.as_atomic_counter), buff, 10); + break; + case SHOW_UNDEF: + break; // Return empty string + case SHOW_SYS: // Cannot happen + default: + DBUG_ASSERT(0); + break; + } + + *length= (size_t) (end - pos); + return pos; +} + + +static bool show_status_array(THD *thd, const char *wild, + SHOW_VAR *variables, + enum enum_var_type scope, + struct system_status_var *status_var, + const char *prefix, TABLE *table, + bool ucase_names, + COND *cond) +{ + my_aligned_storage<SHOW_VAR_FUNC_BUFF_SIZE, MY_ALIGNOF(long)> buffer; + char * const buff= buffer.data; + char *prefix_end; + char name_buffer[NAME_CHAR_LEN]; + int len; + SHOW_VAR tmp, *var; + bool res= FALSE; + CHARSET_INFO *charset= system_charset_info; + DBUG_ENTER("show_status_array"); + + prefix_end=strnmov(name_buffer, prefix, sizeof(name_buffer)-1); + if (*prefix) + *prefix_end++= '_'; + len=(int)(name_buffer + sizeof(name_buffer) - prefix_end); + +#ifdef WITH_WSREP + bool is_wsrep_var= FALSE; + /* + This is a workaround for lp:1306875 (PBX) to skip switching of wsrep + status variable name's first letter to uppercase. This is an optimization + for status variables defined under wsrep plugin. + TODO: remove once lp:1306875 has been addressed. + */ + if (*prefix && !my_strcasecmp(system_charset_info, prefix, "wsrep")) + { + is_wsrep_var= TRUE; + } +#endif /* WITH_WSREP */ + + for (; variables->name; variables++) + { + bool wild_checked= false; + strnmov(prefix_end, variables->name, len); + name_buffer[sizeof(name_buffer)-1]=0; /* Safety */ + +#ifdef WITH_WSREP + /* + If the prefix is NULL, that means we are looking into the status variables + defined directly under mysqld.cc. Do not capitalize wsrep status variable + names until lp:1306875 has been fixed. + TODO: remove once lp:1306875 has been addressed. + */ + if (!(*prefix) && !strncasecmp(name_buffer, "wsrep", strlen("wsrep"))) + { + is_wsrep_var= TRUE; + } +#endif /* WITH_WSREP */ + + if (ucase_names) + my_caseup_str(system_charset_info, name_buffer); + else + { + my_casedn_str(system_charset_info, name_buffer); + DBUG_ASSERT(name_buffer[0] >= 'a'); + DBUG_ASSERT(name_buffer[0] <= 'z'); + + // WSREP_TODO: remove once lp:1306875 has been addressed. + if (IF_WSREP(is_wsrep_var == FALSE, 1) && + status_var) + name_buffer[0]-= 'a' - 'A'; + } + + + restore_record(table, s->default_values); + table->field[0]->store(name_buffer, strlen(name_buffer), + system_charset_info); + + /* + Compare name for types that can't return arrays. We do this to not + calculate the value for function variables that we will not access + */ + if ((variables->type != SHOW_FUNC && variables->type != SHOW_ARRAY)) + { + if (wild && wild[0] && wild_case_compare(system_charset_info, + name_buffer, wild)) + continue; + wild_checked= 1; // Avoid checking it again + } + + /* + if var->type is SHOW_FUNC or SHOW_SIMPLE_FUNC, call the function. + Repeat as necessary, if new var is again one of the above + */ + for (var=variables; var->type == SHOW_FUNC || + var->type == SHOW_SIMPLE_FUNC; var= &tmp) + ((mysql_show_var_func)(var->value))(thd, &tmp, buff, + status_var, scope); + + SHOW_TYPE show_type=var->type; + if (show_type == SHOW_ARRAY) + { + show_status_array(thd, wild, (SHOW_VAR *) var->value, scope, + status_var, name_buffer, table, ucase_names, cond); + } + else + { + if ((wild_checked || + !(wild && wild[0] && wild_case_compare(system_charset_info, + name_buffer, wild))) && + (!cond || cond->val_int())) + { + const char *pos; // We assign a lot of const's + size_t length; + + if (show_type == SHOW_SYS) + mysql_mutex_lock(&LOCK_global_system_variables); + else if (show_type >= SHOW_LONG_STATUS && scope == OPT_GLOBAL) + calc_sum_of_all_status_if_needed(status_var); + + pos= get_one_variable(thd, var, scope, show_type, status_var, + &charset, buff, &length); + + if (table->field[1]->field_length) + thd->count_cuted_fields= CHECK_FIELD_WARN; + table->field[1]->store(pos, (uint32) length, charset); + thd->count_cuted_fields= CHECK_FIELD_IGNORE; + table->field[1]->set_notnull(); + if (show_type == SHOW_SYS) + mysql_mutex_unlock(&LOCK_global_system_variables); + + + if (schema_table_store_record(thd, table)) + { + res= TRUE; + goto end; + } + thd->get_stmt_da()->inc_current_row_for_warning(); + } + } + } +end: + DBUG_RETURN(res); +} + +/* + collect status for all running threads + Return number of threads used +*/ + +struct calc_sum_callback_arg +{ + calc_sum_callback_arg(STATUS_VAR *to_arg): to(to_arg), count(0) {} + STATUS_VAR *to; + uint count; +}; + + +static my_bool calc_sum_callback(THD *thd, calc_sum_callback_arg *arg) +{ + arg->count++; + if (!thd->status_in_global) + { + add_to_status(arg->to, &thd->status_var); + arg->to->local_memory_used+= thd->status_var.local_memory_used; + } + if (thd->get_command() != COM_SLEEP) + arg->to->threads_running++; + return 0; +} + + +uint calc_sum_of_all_status(STATUS_VAR *to) +{ + calc_sum_callback_arg arg(to); + DBUG_ENTER("calc_sum_of_all_status"); + + to->local_memory_used= 0; + /* Add to this status from existing threads */ + server_threads.iterate(calc_sum_callback, &arg); + DBUG_RETURN(arg.count); +} + + +/* This is only used internally, but we need it here as a forward reference */ +extern ST_SCHEMA_TABLE schema_tables[]; + +/* + Store record to I_S table, convert HEAP table + to MyISAM if necessary + + SYNOPSIS + schema_table_store_record() + thd thread handler + table Information schema table to be updated + + RETURN + 0 success + 1 error +*/ + +bool schema_table_store_record(THD *thd, TABLE *table) +{ + int error; + + if (unlikely(thd->killed)) + { + thd->send_kill_message(); + return 1; + } + + if (unlikely((error= table->file->ha_write_tmp_row(table->record[0])))) + { + TMP_TABLE_PARAM *param= table->pos_in_table_list->schema_table_param; + if (unlikely(create_internal_tmp_table_from_heap(thd, table, + param->start_recinfo, + ¶m->recinfo, error, 0, + NULL))) + + return 1; + } + return 0; +} + + +static int make_table_list(THD *thd, SELECT_LEX *sel, + LEX_CSTRING *db_name, LEX_CSTRING *table_name) +{ + Table_ident *table_ident; + table_ident= new Table_ident(thd, db_name, table_name, 1); + if (!sel->add_table_to_list(thd, table_ident, 0, 0, TL_READ, MDL_SHARED_READ)) + return 1; + return 0; +} + + +/** + @brief Get lookup value from the part of 'WHERE' condition + + @details This function gets lookup value from + the part of 'WHERE' condition if it's possible and + fill appropriate lookup_field_vals struct field + with this value. + + @param[in] thd thread handler + @param[in] item_func part of WHERE condition + @param[in] table I_S table + @param[in, out] lookup_field_vals Struct which holds lookup values + + @return + 0 success + 1 error, there can be no matching records for the condition +*/ + +bool get_lookup_value(THD *thd, Item_func *item_func, + TABLE_LIST *table, + LOOKUP_FIELD_VALUES *lookup_field_vals) +{ + ST_SCHEMA_TABLE *schema_table= table->schema_table; + ST_FIELD_INFO *field_info= schema_table->fields_info; + const char *field_name1= schema_table->idx_field1 >= 0 ? + field_info[schema_table->idx_field1].name().str : ""; + const char *field_name2= schema_table->idx_field2 >= 0 ? + field_info[schema_table->idx_field2].name().str : ""; + + if (item_func->functype() == Item_func::EQ_FUNC || + item_func->functype() == Item_func::EQUAL_FUNC) + { + int idx_field, idx_val; + char tmp[MAX_FIELD_WIDTH]; + String *tmp_str, str_buff(tmp, sizeof(tmp), system_charset_info); + Item_field *item_field; + CHARSET_INFO *cs= system_charset_info; + + if (item_func->arguments()[0]->real_item()->type() == Item::FIELD_ITEM && + item_func->arguments()[1]->const_item()) + { + idx_field= 0; + idx_val= 1; + } + else if (item_func->arguments()[1]->real_item()->type() == Item::FIELD_ITEM && + item_func->arguments()[0]->const_item()) + { + idx_field= 1; + idx_val= 0; + } + else + return 0; + + item_field= (Item_field*) item_func->arguments()[idx_field]->real_item(); + if (table->table != item_field->field->table) + return 0; + tmp_str= item_func->arguments()[idx_val]->val_str(&str_buff); + + /* impossible value */ + if (!tmp_str) + return 1; + + /* Lookup value is database name */ + if (!cs->strnncollsp(field_name1, strlen(field_name1), + item_field->field_name.str, + item_field->field_name.length)) + { + thd->make_lex_string(&lookup_field_vals->db_value, + tmp_str->ptr(), tmp_str->length()); + } + /* Lookup value is table name */ + else if (!cs->strnncollsp(field_name2, + strlen(field_name2), + item_field->field_name.str, + item_field->field_name.length)) + { + thd->make_lex_string(&lookup_field_vals->table_value, + tmp_str->ptr(), tmp_str->length()); + } + } + return 0; +} + + +/** + @brief Calculates lookup values from 'WHERE' condition + + @details This function calculates lookup value(database name, table name) + from 'WHERE' condition if it's possible and + fill lookup_field_vals struct fields with these values. + + @param[in] thd thread handler + @param[in] cond WHERE condition + @param[in] table I_S table + @param[in, out] lookup_field_vals Struct which holds lookup values + + @return + 0 success + 1 error, there can be no matching records for the condition +*/ + +bool calc_lookup_values_from_cond(THD *thd, COND *cond, TABLE_LIST *table, + LOOKUP_FIELD_VALUES *lookup_field_vals) +{ + if (!cond) + return 0; + + if (cond->type() == Item::COND_ITEM) + { + if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) + { + List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); + Item *item; + while ((item= li++)) + { + if (item->type() == Item::FUNC_ITEM) + { + if (get_lookup_value(thd, (Item_func*)item, table, lookup_field_vals)) + return 1; + } + else + { + if (calc_lookup_values_from_cond(thd, item, table, lookup_field_vals)) + return 1; + } + } + } + return 0; + } + else if (cond->type() == Item::FUNC_ITEM && + get_lookup_value(thd, (Item_func*) cond, table, lookup_field_vals)) + return 1; + return 0; +} + + +bool uses_only_table_name_fields(Item *item, TABLE_LIST *table) +{ + if (item->type() == Item::FUNC_ITEM) + { + Item_func *item_func= (Item_func*)item; + for (uint i=0; i<item_func->argument_count(); i++) + { + if (!uses_only_table_name_fields(item_func->arguments()[i], table)) + return 0; + } + } + else if (item->type() == Item::ROW_ITEM) + { + Item_row *item_row= static_cast<Item_row*>(item); + for (uint i= 0; i < item_row->cols(); i++) + { + if (!uses_only_table_name_fields(item_row->element_index(i), table)) + return 0; + } + } + else if (item->type() == Item::FIELD_ITEM) + { + Item_field *item_field= (Item_field*)item; + CHARSET_INFO *cs= system_charset_info; + ST_SCHEMA_TABLE *schema_table= table->schema_table; + ST_FIELD_INFO *field_info= schema_table->fields_info; + const char *field_name1= schema_table->idx_field1 >= 0 ? + field_info[schema_table->idx_field1].name().str : ""; + const char *field_name2= schema_table->idx_field2 >= 0 ? + field_info[schema_table->idx_field2].name().str : ""; + if (table->table != item_field->field->table || + (cs->strnncollsp(field_name1, strlen(field_name1), + item_field->field_name.str, + item_field->field_name.length) && + cs->strnncollsp(field_name2, strlen(field_name2), + item_field->field_name.str, + item_field->field_name.length))) + return 0; + } + else if (item->type() == Item::EXPR_CACHE_ITEM) + { + Item_cache_wrapper *tmp= static_cast<Item_cache_wrapper*>(item); + return uses_only_table_name_fields(tmp->get_orig_item(), table); + } + else if (item->type() == Item::REF_ITEM) + return uses_only_table_name_fields(item->real_item(), table); + + if (item->real_type() == Item::SUBSELECT_ITEM && !item->const_item()) + return 0; + + return 1; +} + + +COND *make_cond_for_info_schema(THD *thd, COND *cond, TABLE_LIST *table) +{ + if (!cond) + return (COND*) 0; + if (cond->type() == Item::COND_ITEM) + { + if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) + { + /* Create new top level AND item */ + Item_cond_and *new_cond=new (thd->mem_root) Item_cond_and(thd); + if (!new_cond) + return (COND*) 0; + List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); + Item *item; + while ((item=li++)) + { + Item *fix= make_cond_for_info_schema(thd, item, table); + if (fix) + new_cond->argument_list()->push_back(fix, thd->mem_root); + } + switch (new_cond->argument_list()->elements) { + case 0: + return (COND*) 0; + case 1: + return new_cond->argument_list()->head(); + default: + new_cond->quick_fix_field(); + return new_cond; + } + } + else + { // Or list + Item_cond_or *new_cond= new (thd->mem_root) Item_cond_or(thd); + if (!new_cond) + return (COND*) 0; + List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); + Item *item; + while ((item=li++)) + { + Item *fix=make_cond_for_info_schema(thd, item, table); + if (!fix) + return (COND*) 0; + new_cond->argument_list()->push_back(fix, thd->mem_root); + } + new_cond->quick_fix_field(); + new_cond->top_level_item(); + return new_cond; + } + } + + if (!uses_only_table_name_fields(cond, table)) + return (COND*) 0; + return cond; +} + + +/** + @brief Calculate lookup values(database name, table name) + + @details This function calculates lookup values(database name, table name) + from 'WHERE' condition or wild values (for 'SHOW' commands only) + from LEX struct and fill lookup_field_vals struct field + with these values. + + @param[in] thd thread handler + @param[in] cond WHERE condition + @param[in] tables I_S table + @param[in, out] lookup_field_values Struct which holds lookup values + + @return + 0 success + 1 error, there can be no matching records for the condition +*/ + +bool get_lookup_field_values(THD *thd, COND *cond, bool fix_table_name_case, + TABLE_LIST *tables, + LOOKUP_FIELD_VALUES *lookup_field_values) +{ + LEX *lex= thd->lex; + String *wild= lex->wild; + bool rc= 0; + + bzero((char*) lookup_field_values, sizeof(LOOKUP_FIELD_VALUES)); + switch (lex->sql_command) { + case SQLCOM_SHOW_PLUGINS: + if (lex->ident.str) + { + thd->make_lex_string(&lookup_field_values->db_value, + lex->ident.str, lex->ident.length); + break; + } + /* fall through */ + case SQLCOM_SHOW_GENERIC: + case SQLCOM_SHOW_DATABASES: + if (wild) + { + thd->make_lex_string(&lookup_field_values->db_value, + wild->ptr(), wild->length()); + lookup_field_values->wild_db_value= 1; + } + break; + case SQLCOM_SHOW_TABLES: + case SQLCOM_SHOW_TABLE_STATUS: + case SQLCOM_SHOW_TRIGGERS: + case SQLCOM_SHOW_EVENTS: + thd->make_lex_string(&lookup_field_values->db_value, + lex->first_select_lex()->db.str, + lex->first_select_lex()->db.length); + if (wild) + { + thd->make_lex_string(&lookup_field_values->table_value, + wild->ptr(), wild->length()); + lookup_field_values->wild_table_value= 1; + } + break; + default: + /* + The "default" is for queries over I_S. + All previous cases handle SHOW commands. + */ + rc= calc_lookup_values_from_cond(thd, cond, tables, lookup_field_values); + break; + } + + if (lower_case_table_names && !rc) + { + /* + We can safely do in-place upgrades here since all of the above cases + are allocating a new memory buffer for these strings. + */ + if (lookup_field_values->db_value.str && lookup_field_values->db_value.str[0]) + my_casedn_str(system_charset_info, + (char*) lookup_field_values->db_value.str); + if (fix_table_name_case && + lookup_field_values->table_value.str && + lookup_field_values->table_value.str[0]) + my_casedn_str(system_charset_info, + (char*) lookup_field_values->table_value.str); + } + + return rc; +} + + +enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table) +{ + return (enum enum_schema_tables) (schema_table - &schema_tables[0]); +} + + +/* + Create db names list. Information schema name always is first in list + + SYNOPSIS + make_db_list() + thd thread handler + files list of db names + wild wild string + idx_field_vals idx_field_vals->db_name contains db name or + wild string + + RETURN + zero success + non-zero error +*/ + +static int make_db_list(THD *thd, Dynamic_array<LEX_CSTRING*> *files, + LOOKUP_FIELD_VALUES *lookup_field_vals) +{ + if (lookup_field_vals->wild_db_value) + { + /* + This part of code is only for SHOW DATABASES command. + idx_field_vals->db_value can be 0 when we don't use + LIKE clause (see also get_index_field_values() function) + */ + if (!lookup_field_vals->db_value.str || + !wild_case_compare(system_charset_info, + INFORMATION_SCHEMA_NAME.str, + lookup_field_vals->db_value.str)) + { + if (files->append_val(&INFORMATION_SCHEMA_NAME)) + return 1; + } + return find_files(thd, files, 0, mysql_data_home, + &lookup_field_vals->db_value); + } + + + /* + If we have db lookup value we just add it to list and + exit from the function. + We don't do this for database names longer than the maximum + name length. + */ + if (lookup_field_vals->db_value.str) + { + if (lookup_field_vals->db_value.length > NAME_LEN) + { + /* + Impossible value for a database name, + found in a WHERE DATABASE_NAME = 'xxx' clause. + */ + return 0; + } + + if (is_infoschema_db(&lookup_field_vals->db_value)) + { + if (files->append_val(&INFORMATION_SCHEMA_NAME)) + return 1; + return 0; + } + if (files->append_val(&lookup_field_vals->db_value)) + return 1; + return 0; + } + + /* + Create list of existing databases. It is used in case + of select from information schema table + */ + if (files->append_val(&INFORMATION_SCHEMA_NAME)) + return 1; + return find_files(thd, files, 0, mysql_data_home, &null_clex_str); +} + + +struct st_add_schema_table +{ + Dynamic_array<LEX_CSTRING*> *files; + const char *wild; +}; + + +static my_bool add_schema_table(THD *thd, plugin_ref plugin, + void* p_data) +{ + LEX_CSTRING *file_name= 0; + st_add_schema_table *data= (st_add_schema_table *)p_data; + Dynamic_array<LEX_CSTRING*> *file_list= data->files; + const char *wild= data->wild; + ST_SCHEMA_TABLE *schema_table= plugin_data(plugin, ST_SCHEMA_TABLE *); + DBUG_ENTER("add_schema_table"); + + if (schema_table->hidden) + DBUG_RETURN(0); + if (wild) + { + if (lower_case_table_names) + { + if (wild_case_compare(files_charset_info, + schema_table->table_name, + wild)) + DBUG_RETURN(0); + } + else if (wild_compare(schema_table->table_name, wild, 0)) + DBUG_RETURN(0); + } + + if ((file_name= thd->make_clex_string(schema_table->table_name, + strlen(schema_table->table_name))) && + !file_list->append(file_name)) + DBUG_RETURN(0); + DBUG_RETURN(1); +} + + +int schema_tables_add(THD *thd, Dynamic_array<LEX_CSTRING*> *files, + const char *wild) +{ + LEX_CSTRING *file_name; + ST_SCHEMA_TABLE *tmp_schema_table= schema_tables; + st_add_schema_table add_data; + DBUG_ENTER("schema_tables_add"); + + for (; tmp_schema_table->table_name; tmp_schema_table++) + { + if (tmp_schema_table->hidden) + continue; + if (wild) + { + if (lower_case_table_names) + { + if (wild_case_compare(files_charset_info, + tmp_schema_table->table_name, + wild)) + continue; + } + else if (wild_compare(tmp_schema_table->table_name, wild, 0)) + continue; + } + if ((file_name= + thd->make_clex_string(tmp_schema_table->table_name, + strlen(tmp_schema_table->table_name))) && + !files->append(file_name)) + continue; + DBUG_RETURN(1); + } + + add_data.files= files; + add_data.wild= wild; + if (plugin_foreach(thd, add_schema_table, + MYSQL_INFORMATION_SCHEMA_PLUGIN, &add_data)) + DBUG_RETURN(1); + + DBUG_RETURN(0); +} + + +/** + @brief Create table names list + + @details The function creates the list of table names in + database + + @param[in] thd thread handler + @param[in] table_names List of table names in database + @param[in] lex pointer to LEX struct + @param[in] lookup_field_vals pointer to LOOKUP_FIELD_VALUE struct + @param[in] db_name database name + + @return Operation status + @retval 0 ok + @retval 1 fatal error + @retval 2 Not fatal error; Safe to ignore this file list +*/ + +static int +make_table_name_list(THD *thd, Dynamic_array<LEX_CSTRING*> *table_names, + LEX *lex, LOOKUP_FIELD_VALUES *lookup_field_vals, + LEX_CSTRING *db_name) +{ + char path[FN_REFLEN + 1]; + build_table_filename(path, sizeof(path) - 1, db_name->str, "", "", 0); + if (!lookup_field_vals->wild_table_value && + lookup_field_vals->table_value.str) + { + if (check_table_name(lookup_field_vals->table_value.str, + lookup_field_vals->table_value.length, + false)) + { + /* + Impossible value for a table name, + found in a WHERE TABLE_NAME = 'xxx' clause. + */ + return 0; + } + if (db_name == &INFORMATION_SCHEMA_NAME) + { + LEX_CSTRING *name; + ST_SCHEMA_TABLE *schema_table= + find_schema_table(thd, &lookup_field_vals->table_value); + if (schema_table && !schema_table->hidden) + { + if (!(name= thd->make_clex_string(schema_table->table_name, + strlen(schema_table->table_name))) || + table_names->append(name)) + return 1; + } + } + else + { + if (table_names->append_val(&lookup_field_vals->table_value)) + return 1; + } + return 0; + } + + /* + This call will add all matching the wildcards (if specified) IS tables + to the list + */ + if (db_name == &INFORMATION_SCHEMA_NAME) + return (schema_tables_add(thd, table_names, + lookup_field_vals->table_value.str)); + + if (check_db_name((LEX_STRING*)db_name)) + return 0; // Impossible TABLE_SCHEMA name + + find_files_result res= find_files(thd, table_names, db_name, path, + &lookup_field_vals->table_value); + if (res != FIND_FILES_OK) + { + /* + Downgrade errors about problems with database directory to + warnings if this is not a 'SHOW' command. Another thread + may have dropped database, and we may still have a name + for that directory. + */ + if (res == FIND_FILES_DIR) + { + if (is_show_command(thd)) + return 1; + thd->clear_error(); + return 2; + } + return 1; + } + return 0; +} + + +static void get_table_engine_for_i_s(THD *thd, char *buf, TABLE_LIST *tl, + LEX_CSTRING *db, LEX_CSTRING *table) +{ + LEX_CSTRING engine_name= { buf, 0 }; + + if (thd->get_stmt_da()->sql_errno() == ER_UNKNOWN_STORAGE_ENGINE) + { + char path[FN_REFLEN]; + build_table_filename(path, sizeof(path) - 1, + db->str, table->str, reg_ext, 0); + if (dd_frm_type(thd, path, &engine_name, NULL, NULL) == TABLE_TYPE_NORMAL) + tl->option= engine_name.str; + } +} + + +/** + Fill I_S table with data obtained by performing full-blown table open. + + @param thd Thread handler. + @param is_show_fields_or_keys Indicates whether it is a legacy SHOW + COLUMNS or SHOW KEYS statement. + @param table TABLE object for I_S table to be filled. + @param schema_table I_S table description structure. + @param orig_db_name Database name. + @param orig_table_name Table name. + @param open_tables_state_backup Open_tables_state object which is used + to save/restore original status of + variables related to open tables state. + @param can_deadlock Indicates that deadlocks are possible + due to metadata locks, so to avoid + them we should not wait in case if + conflicting lock is present. + + @retval FALSE - Success. + @retval TRUE - Failure. +*/ +static bool +fill_schema_table_by_open(THD *thd, MEM_ROOT *mem_root, + bool is_show_fields_or_keys, + TABLE *table, ST_SCHEMA_TABLE *schema_table, + LEX_CSTRING *orig_db_name, + LEX_CSTRING *orig_table_name, + Open_tables_backup *open_tables_state_backup, + bool can_deadlock) +{ + Query_arena i_s_arena(mem_root, Query_arena::STMT_CONVENTIONAL_EXECUTION), + backup_arena, *old_arena; + LEX *old_lex= thd->lex, temp_lex, *lex; + LEX_CSTRING db_name, table_name; + TABLE_LIST *table_list; + bool result= true; + DBUG_ENTER("fill_schema_table_by_open"); + + /* + When a view is opened its structures are allocated on a permanent + statement arena and linked into the LEX tree for the current statement + (this happens even in cases when view is handled through TEMPTABLE + algorithm). + + To prevent this process from unnecessary hogging of memory in the permanent + arena of our I_S query and to avoid damaging its LEX we use temporary + arena and LEX for table/view opening. + + Use temporary arena instead of statement permanent arena. Also make + it active arena and save original one for successive restoring. + */ + old_arena= thd->stmt_arena; + thd->stmt_arena= &i_s_arena; + thd->set_n_backup_active_arena(&i_s_arena, &backup_arena); + + /* Prepare temporary LEX. */ + thd->lex= lex= &temp_lex; + lex_start(thd); + lex->sql_command= old_lex->sql_command; + + /* Disable constant subquery evaluation as we won't be locking tables. */ + lex->context_analysis_only= CONTEXT_ANALYSIS_ONLY_VIEW; + + /* + Some of process_table() functions rely on wildcard being passed from + old LEX (or at least being initialized). + */ + lex->wild= old_lex->wild; + + /* + Since make_table_list() might change database and table name passed + to it (if lower_case_table_names) we create copies of orig_db_name and + orig_table_name here. These copies are used for make_table_list() + while unaltered values are passed to process_table() functions. + */ + if (!thd->make_lex_string(&db_name, + orig_db_name->str, orig_db_name->length) || + !thd->make_lex_string(&table_name, + orig_table_name->str, orig_table_name->length)) + goto end; + + /* + Create table list element for table to be open. Link it with the + temporary LEX. The latter is required to correctly open views and + produce table describing their structure. + */ + if (make_table_list(thd, lex->first_select_lex(), &db_name, &table_name)) + goto end; + + table_list= lex->first_select_lex()->table_list.first; + + if (is_show_fields_or_keys) + { + /* + Restore thd->temporary_tables to be able to process + temporary tables (only for 'show index' & 'show columns'). + This should be changed when processing of temporary tables for + I_S tables will be done. + */ + thd->temporary_tables= open_tables_state_backup->temporary_tables; + } + else + { + /* + Apply optimization flags for table opening which are relevant for + this I_S table. We can't do this for SHOW COLUMNS/KEYS because of + backward compatibility. + */ + table_list->i_s_requested_object= schema_table->i_s_requested_object; + } + + DBUG_ASSERT(thd->lex == lex); + result= open_tables_only_view_structure(thd, table_list, can_deadlock); + + DEBUG_SYNC(thd, "after_open_table_ignore_flush"); + + /* + XXX: show_table_list has a flag i_is_requested, + and when it's set, open_normal_and_derived_tables() + can return an error without setting an error message + in THD, which is a hack. This is why we have to + check for res, then for thd->is_error() and only then + for thd->main_da.sql_errno(). + + Again we don't do this for SHOW COLUMNS/KEYS because + of backward compatibility. + */ + if (!is_show_fields_or_keys && result && thd->is_error() && + (thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE || + thd->get_stmt_da()->sql_errno() == ER_WRONG_OBJECT || + thd->get_stmt_da()->sql_errno() == ER_NOT_SEQUENCE)) + { + /* + Hide error for a non-existing table. + For example, this error can occur when we use a where condition + with a db name and table, but the table does not exist or + there is a view with the same name. + */ + result= false; + thd->clear_error(); + } + else + { + char buf[NAME_CHAR_LEN + 1]; + if (unlikely(thd->is_error())) + get_table_engine_for_i_s(thd, buf, table_list, &db_name, &table_name); + + result= schema_table->process_table(thd, table_list, + table, result, + orig_db_name, + orig_table_name); + } + + +end: + lex->unit.cleanup(); + + /* Restore original LEX value, statement's arena and THD arena values. */ + lex_end(thd->lex); + + // Free items, before restoring backup_arena below. + DBUG_ASSERT(i_s_arena.free_list == NULL); + thd->free_items(); + + /* + For safety reset list of open temporary tables before closing + all tables open within this Open_tables_state. + */ + thd->temporary_tables= NULL; + + close_thread_tables(thd); + /* + Release metadata lock we might have acquired. + See comment in fill_schema_table_from_frm() for details. + */ + thd->mdl_context.rollback_to_savepoint(open_tables_state_backup->mdl_system_tables_svp); + + thd->lex= old_lex; + + thd->stmt_arena= old_arena; + thd->restore_active_arena(&i_s_arena, &backup_arena); + + DBUG_RETURN(result); +} + + +/** + @brief Fill I_S table for SHOW TABLE NAMES commands + + @param[in] thd thread handler + @param[in] table TABLE struct for I_S table + @param[in] db_name database name + @param[in] table_name table name + + @return Operation status + @retval 0 success + @retval 1 error +*/ + +static int fill_schema_table_names(THD *thd, TABLE_LIST *tables, + LEX_CSTRING *db_name, + LEX_CSTRING *table_name) +{ + TABLE *table= tables->table; + if (db_name == &INFORMATION_SCHEMA_NAME) + { + table->field[3]->store(STRING_WITH_LEN("SYSTEM VIEW"), + system_charset_info); + } + else if (tables->table_open_method != SKIP_OPEN_TABLE) + { + CHARSET_INFO *cs= system_charset_info; + handlerton *hton; + bool is_sequence; + + if (ha_table_exists(thd, db_name, table_name, NULL, NULL, + &hton, &is_sequence)) + { + if (hton == view_pseudo_hton) + table->field[3]->store(STRING_WITH_LEN("VIEW"), cs); + else if (is_sequence) + table->field[3]->store(STRING_WITH_LEN("SEQUENCE"), cs); + else + table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), cs); + } + else + table->field[3]->store(STRING_WITH_LEN("ERROR"), cs); + + if (unlikely(thd->is_error() && + thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE)) + { + thd->clear_error(); + return 0; + } + } + if (unlikely(schema_table_store_record(thd, table))) + return 1; + return 0; +} + + +/** + @brief Get open table method + + @details The function calculates the method which will be used + for table opening: + SKIP_OPEN_TABLE - do not open table + OPEN_FRM_ONLY - open FRM file only + OPEN_FULL_TABLE - open FRM, data, index files + @param[in] tables I_S table table_list + @param[in] schema_table I_S table struct + @param[in] schema_table_idx I_S table index + + @return return a set of flags + @retval SKIP_OPEN_TABLE | OPEN_FRM_ONLY | OPEN_FULL_TABLE +*/ + +uint get_table_open_method(TABLE_LIST *tables, + ST_SCHEMA_TABLE *schema_table, + enum enum_schema_tables schema_table_idx) +{ + /* + determine which method will be used for table opening + */ + if (schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) + { + Field **ptr, *field; + uint table_open_method= 0, field_indx= 0; + uint star_table_open_method= OPEN_FULL_TABLE; + bool used_star= true; // true if '*' is used in select + for (ptr=tables->table->field; (field= *ptr) ; ptr++) + { + const ST_FIELD_INFO &def= schema_table->fields_info[field_indx]; + star_table_open_method= + MY_MIN(star_table_open_method, (uint) def.open_method()); + if (bitmap_is_set(tables->table->read_set, field->field_index)) + { + used_star= false; + table_open_method|= (uint) def.open_method(); + } + field_indx++; + } + if (used_star) + return star_table_open_method; + return table_open_method; + } + /* I_S tables which use get_all_tables but can not be optimized */ + return (uint) OPEN_FULL_TABLE; +} + + +/** + Try acquire high priority share metadata lock on a table (with + optional wait for conflicting locks to go away). + + @param thd Thread context. + @param mdl_request Pointer to memory to be used for MDL_request + object for a lock request. + @param table Table list element for the table + @param can_deadlock Indicates that deadlocks are possible due to + metadata locks, so to avoid them we should not + wait in case if conflicting lock is present. + + @note This is an auxiliary function to be used in cases when we want to + access table's description by looking up info in TABLE_SHARE without + going through full-blown table open. + @note This function assumes that there are no other metadata lock requests + in the current metadata locking context. + + @retval FALSE No error, if lock was obtained TABLE_LIST::mdl_request::ticket + is set to non-NULL value. + @retval TRUE Some error occurred (probably thread was killed). +*/ + +static bool +try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, + bool can_deadlock) +{ + bool error; + MDL_REQUEST_INIT(&table->mdl_request, MDL_key::TABLE, table->db.str, + table->table_name.str, MDL_SHARED_HIGH_PRIO, + MDL_TRANSACTION); + + if (can_deadlock) + { + /* + When .FRM is being open in order to get data for an I_S table, + we might have some tables not only open but also locked. + E.g. this happens when a SHOW or I_S statement is run + under LOCK TABLES or inside a stored function. + By waiting for the conflicting metadata lock to go away we + might create a deadlock which won't entirely belong to the + MDL subsystem and thus won't be detectable by this subsystem's + deadlock detector. To avoid such situation, when there are + other locked tables, we prefer not to wait on a conflicting + lock. + */ + error= thd->mdl_context.try_acquire_lock(&table->mdl_request); + } + else + error= thd->mdl_context.acquire_lock(&table->mdl_request, + thd->variables.lock_wait_timeout); + + return error; +} + + +/** + @brief Fill I_S table with data from FRM file only + + @param[in] thd thread handler + @param[in] table TABLE struct for I_S table + @param[in] schema_table I_S table struct + @param[in] db_name database name + @param[in] table_name table name + @param[in] schema_table_idx I_S table index + @param[in] open_tables_state_backup Open_tables_state object which is used + to save/restore original state of metadata + locks. + @param[in] can_deadlock Indicates that deadlocks are possible + due to metadata locks, so to avoid + them we should not wait in case if + conflicting lock is present. + + @return Operation status + @retval 0 Table is processed and we can continue + with new table + @retval 1 It's view and we have to use + open_tables function for this table +*/ + +static int fill_schema_table_from_frm(THD *thd, MEM_ROOT *mem_root, + TABLE *table, + ST_SCHEMA_TABLE *schema_table, + LEX_CSTRING *db_name, + LEX_CSTRING *table_name, + Open_tables_backup *open_tables_state_backup, + bool can_deadlock) +{ + TABLE_SHARE *share; + TABLE tbl; + TABLE_LIST table_list; + uint res= 0; + char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; + Query_arena i_s_arena(mem_root, Query_arena::STMT_CONVENTIONAL_EXECUTION); + Query_arena backup_arena, *old_arena; + bool i_s_arena_active= false; + + bzero((char*) &table_list, sizeof(TABLE_LIST)); + bzero((char*) &tbl, sizeof(TABLE)); + + DBUG_ASSERT(db_name->length <= NAME_LEN); + DBUG_ASSERT(table_name->length <= NAME_LEN); + + if (lower_case_table_names) + { + /* + In lower_case_table_names > 0 metadata locking and table definition + cache subsystems require normalized (lowercased) database and table + names as input. + */ + strmov(db_name_buff, db_name->str); + strmov(table_name_buff, table_name->str); + table_list.db.length= my_casedn_str(files_charset_info, db_name_buff); + table_list.table_name.length= my_casedn_str(files_charset_info, table_name_buff); + table_list.db.str= db_name_buff; + table_list.table_name.str= table_name_buff; + } + else + { + table_list.table_name= *table_name; + table_list.db= *db_name; + } + + /* + TODO: investigate if in this particular situation we can get by + simply obtaining internal lock of the data-dictionary + instead of obtaining full-blown metadata lock. + */ + if (try_acquire_high_prio_shared_mdl_lock(thd, &table_list, can_deadlock)) + { + /* + Some error occurred (most probably we have been killed while + waiting for conflicting locks to go away), let the caller to + handle the situation. + */ + return 1; + } + + if (! table_list.mdl_request.ticket) + { + /* + We are in situation when we have encountered conflicting metadata + lock and deadlocks can occur due to waiting for it to go away. + So instead of waiting skip this table with an appropriate warning. + */ + DBUG_ASSERT(can_deadlock); + + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_WARN_I_S_SKIPPED_TABLE, + ER_THD(thd, ER_WARN_I_S_SKIPPED_TABLE), + table_list.db.str, table_list.table_name.str); + return 0; + } + + if (schema_table->i_s_requested_object & OPEN_TRIGGER_ONLY) + { + init_sql_alloc(key_memory_table_triggers_list, + &tbl.mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0)); + if (!Table_triggers_list::check_n_load(thd, db_name, + table_name, &tbl, 1)) + { + table_list.table= &tbl; + res= schema_table->process_table(thd, &table_list, table, + res, db_name, table_name); + delete tbl.triggers; + } + free_root(&tbl.mem_root, MYF(0)); + goto end; + } + + old_arena= thd->stmt_arena; + thd->stmt_arena= &i_s_arena; + thd->set_n_backup_active_arena(&i_s_arena, &backup_arena); + i_s_arena_active= true; + + share= tdc_acquire_share(thd, &table_list, GTS_TABLE | GTS_VIEW); + if (!share) + { + if (thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE || + thd->get_stmt_da()->sql_errno() == ER_WRONG_OBJECT || + thd->get_stmt_da()->sql_errno() == ER_NOT_SEQUENCE) + { + res= 0; + } + else + { + char buf[NAME_CHAR_LEN + 1]; + get_table_engine_for_i_s(thd, buf, &table_list, db_name, table_name); + + res= schema_table->process_table(thd, &table_list, table, + true, db_name, table_name); + } + goto end; + } + + if (share->is_view) + { + if (schema_table->i_s_requested_object & OPEN_TABLE_ONLY) + { + /* skip view processing */ + res= 0; + goto end_share; + } + else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL) + { + /* + tell get_all_tables() to fall back to + open_normal_and_derived_tables() + */ + res= 1; + goto end_share; + } + + if (mysql_make_view(thd, share, &table_list, true)) + goto end_share; + table_list.view= (LEX*) share->is_view; + res= schema_table->process_table(thd, &table_list, table, + res, db_name, table_name); + goto end_share; + } + + if (!open_table_from_share(thd, share, table_name, 0, + (EXTRA_RECORD | OPEN_FRM_FILE_ONLY), + thd->open_options, &tbl, FALSE)) + { + tbl.s= share; + table_list.table= &tbl; + table_list.view= (LEX*) share->is_view; + res= schema_table->process_table(thd, &table_list, table, + res, db_name, table_name); + closefrm(&tbl); + } + + +end_share: + tdc_release_share(share); + +end: + /* + Release metadata lock we might have acquired. + + Without this step metadata locks acquired for each table processed + will be accumulated. In situation when a lot of tables are processed + by I_S query this will result in transaction with too many metadata + locks. As result performance of acquisition of new lock will suffer. + + Of course, the fact that we don't hold metadata lock on tables which + were processed till the end of I_S query makes execution less isolated + from concurrent DDL. Consequently one might get 'dirty' results from + such a query. But we have never promised serializability of I_S queries + anyway. + + We don't have any tables open since we took backup, so rolling back to + savepoint is safe. + */ + DBUG_ASSERT(thd->open_tables == NULL); + + thd->mdl_context.rollback_to_savepoint(open_tables_state_backup->mdl_system_tables_svp); + + if (i_s_arena_active) + { + thd->stmt_arena= old_arena; + thd->restore_active_arena(&i_s_arena, &backup_arena); + i_s_arena.free_items(); + } + + if (!thd->is_fatal_error) + thd->clear_error(); + return res; +} + + +class Warnings_only_error_handler : public Internal_error_handler +{ +public: + bool handle_condition(THD *thd, uint sql_errno, const char* sqlstate, + Sql_condition::enum_warning_level *level, + const char* msg, Sql_condition ** cond_hdl) + { + if (sql_errno == ER_TRG_NO_DEFINER || sql_errno == ER_TRG_NO_CREATION_CTX + || sql_errno == ER_PARSE_ERROR) + return true; + + if (*level != Sql_condition::WARN_LEVEL_ERROR) + return false; + + if (likely(!thd->get_stmt_da()->is_error())) + thd->get_stmt_da()->set_error_status(sql_errno, msg, sqlstate, *cond_hdl); + return true; // handled! + } +}; + +/** + @brief Fill I_S tables whose data are retrieved + from frm files and storage engine + + @details The information schema tables are internally represented as + temporary tables that are filled at query execution time. + Those I_S tables whose data are retrieved + from frm files and storage engine are filled by the function + get_all_tables(). + + @note This function assumes optimize_for_get_all_tables() has been + run for the table and produced a "read plan" in + tables->is_table_read_plan. + + @param[in] thd thread handler + @param[in] tables I_S table + @param[in] cond 'WHERE' condition + + @return Operation status + @retval 0 success + @retval 1 error +*/ + +int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("get_all_tables"); + LEX *lex= thd->lex; + TABLE *table= tables->table; + TABLE_LIST table_acl_check; + SELECT_LEX *lsel= tables->schema_select_lex; + ST_SCHEMA_TABLE *schema_table= tables->schema_table; + IS_table_read_plan *plan= tables->is_table_read_plan; + enum enum_schema_tables schema_table_idx; + Dynamic_array<LEX_CSTRING*> db_names(PSI_INSTRUMENT_MEM); + Item *partial_cond= plan->partial_cond; + int error= 1; + Open_tables_backup open_tables_state_backup; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *sctx= thd->security_ctx; +#endif + uint table_open_method= tables->table_open_method; + bool can_deadlock; + MEM_ROOT tmp_mem_root; + /* + We're going to open FRM files for tables. + In case of VIEWs that contain stored function calls, + these stored functions will be parsed and put to the SP cache. + + Suppose we have a view containing a stored function call: + CREATE VIEW v1 AS SELECT f1() AS c1; + and now we're running: + SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=f1(); + If a parallel thread invalidates the cache, + e.g. by creating or dropping some stored routine, + the SELECT query will re-parse f1() when processing "v1" + and replace the outdated cached version of f1() to a new one. + But the old version of f1() is referenced from the m_sp member + of the Item_func_sp instances used in the WHERE condition. + We cannot destroy it. To avoid such clashes, let's remember + all old routines into a temporary SP cache collection + and process tables with a new empty temporary SP cache collection. + Then restore to the old SP cache collection at the end. + */ + Sp_caches old_sp_caches; + + old_sp_caches.sp_caches_swap(*thd); + + bzero(&tmp_mem_root, sizeof(tmp_mem_root)); + + /* + In cases when SELECT from I_S table being filled by this call is + part of statement which also uses other tables or is being executed + under LOCK TABLES or is part of transaction which also uses other + tables waiting for metadata locks which happens below might result + in deadlocks. + To avoid them we don't wait if conflicting metadata lock is + encountered and skip table with emitting an appropriate warning. + */ + can_deadlock= thd->mdl_context.has_locks(); + + /* + We should not introduce deadlocks even if we already have some + tables open and locked, since we won't lock tables which we will + open and will ignore pending exclusive metadata locks for these + tables by using high-priority requests for shared metadata locks. + */ + thd->reset_n_backup_open_tables_state(&open_tables_state_backup); + + schema_table_idx= get_schema_table_idx(schema_table); + /* + this branch processes SHOW FIELDS, SHOW INDEXES commands. + see sql_parse.cc, prepare_schema_table() function where + this values are initialized + */ + if (lsel && lsel->table_list.first) + { + error= fill_schema_table_by_open(thd, thd->mem_root, TRUE, + table, schema_table, + &lsel->table_list.first->db, + &lsel->table_list.first->table_name, + &open_tables_state_backup, + can_deadlock); + goto err; + } + + if (plan->no_rows) + { + error= 0; + goto err; + } + + if (lex->describe) + { + /* EXPLAIN SELECT */ + error= 0; + goto err; + } + + bzero((char*) &table_acl_check, sizeof(table_acl_check)); + + if (make_db_list(thd, &db_names, &plan->lookup_field_vals)) + goto err; + + /* Use tmp_mem_root to allocate data for opened tables */ + init_alloc_root(PSI_INSTRUMENT_ME, &tmp_mem_root, SHOW_ALLOC_BLOCK_SIZE, + SHOW_ALLOC_BLOCK_SIZE, MY_THREAD_SPECIFIC); + + for (size_t i=0; i < db_names.elements(); i++) + { + LEX_CSTRING *db_name= db_names.at(i); + DBUG_ASSERT(db_name->length <= NAME_LEN); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (!(check_access(thd, SELECT_ACL, db_name->str, + &thd->col_access, NULL, 0, 1) || + (!thd->col_access && check_grant_db(thd, db_name->str))) || + sctx->master_access & (DB_ACLS | SHOW_DB_ACL) || + acl_get_all3(sctx, db_name->str, 0)) +#endif + { + Dynamic_array<LEX_CSTRING*> table_names(PSI_INSTRUMENT_MEM); + int res= make_table_name_list(thd, &table_names, lex, + &plan->lookup_field_vals, db_name); + if (unlikely(res == 2)) /* Not fatal error, continue */ + continue; + if (unlikely(res)) + goto err; + + for (size_t i=0; i < table_names.elements(); i++) + { + LEX_CSTRING *table_name= table_names.at(i); + DBUG_ASSERT(table_name->length <= NAME_LEN); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (!(thd->col_access & TABLE_ACLS)) + { + table_acl_check.db= *db_name; + table_acl_check.table_name= *table_name; + table_acl_check.grant.privilege= thd->col_access; + if (check_grant(thd, TABLE_ACLS, &table_acl_check, TRUE, 1, TRUE)) + continue; + } +#endif + restore_record(table, s->default_values); + table->field[schema_table->idx_field1]-> + store(db_name->str, db_name->length, system_charset_info); + table->field[schema_table->idx_field2]-> + store(table_name->str, table_name->length, system_charset_info); + + if (!partial_cond || partial_cond->val_int()) + { + /* + If table is I_S.tables and open_table_method is 0 (eg SKIP_OPEN) + we can skip table opening and we don't have lookup value for + table name or lookup value is wild string(table name list is + already created by make_table_name_list() function). + */ + if (!table_open_method && schema_table_idx == SCH_TABLES && + (!plan->lookup_field_vals.table_value.length || + plan->lookup_field_vals.wild_table_value)) + { + table->field[0]->store(STRING_WITH_LEN("def"), system_charset_info); + if (schema_table_store_record(thd, table)) + goto err; /* Out of space in temporary table */ + continue; + } + + /* SHOW TABLE NAMES command */ + if (schema_table_idx == SCH_TABLE_NAMES) + { + if (fill_schema_table_names(thd, tables, db_name, table_name)) + continue; + } + else if (schema_table_idx == SCH_TRIGGERS && + db_name == &INFORMATION_SCHEMA_NAME) + { + continue; + } + else + { + if (!(table_open_method & ~OPEN_FRM_ONLY) && + db_name != &INFORMATION_SCHEMA_NAME) + { + if (!fill_schema_table_from_frm(thd, &tmp_mem_root, + table, schema_table, + db_name, table_name, + &open_tables_state_backup, + can_deadlock)) + continue; + } + + if (thd->killed == ABORT_QUERY) + { + error= 0; + goto err; + } + if (thd->is_fatal_error) + goto err; + + DEBUG_SYNC(thd, "before_open_in_get_all_tables"); + if (fill_schema_table_by_open(thd, &tmp_mem_root, FALSE, + table, schema_table, + db_name, table_name, + &open_tables_state_backup, + can_deadlock)) + goto err; + free_root(&tmp_mem_root, MY_MARK_BLOCKS_FREE); + } + } + if (thd->killed == ABORT_QUERY) + { + error= 0; + goto err; + } + } + } + } + + error= 0; +err: + thd->restore_backup_open_tables_state(&open_tables_state_backup); + free_root(&tmp_mem_root, 0); + + /* + Now restore to the saved SP cache collection + and clear the temporary SP cache collection. + */ + old_sp_caches.sp_caches_swap(*thd); + old_sp_caches.sp_caches_clear(); + + DBUG_RETURN(error); +} + + +bool store_schema_schemata(THD* thd, TABLE *table, LEX_CSTRING *db_name, + CHARSET_INFO *cs, LEX_CSTRING *schema_comment= NULL) +{ + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), system_charset_info); + table->field[1]->store(db_name, system_charset_info); + table->field[2]->store(&cs->cs_name, system_charset_info); + table->field[3]->store(&cs->coll_name, system_charset_info); + if (schema_comment) + table->field[5]->store(schema_comment->str, schema_comment->length, + system_charset_info); + return schema_table_store_record(thd, table); +} + + +/* + Check if the specified database exists on disk. + + @param dbname - the database name + @retval true - on error, the database directory does not exists + @retval false - on success, the database directory exists +*/ +static bool verify_database_directory_exists(const LEX_CSTRING &dbname) +{ + DBUG_ENTER("verify_database_directory_exists"); + char path[FN_REFLEN + 16]; + uint path_len; + MY_STAT stat_info; + if (!dbname.str[0]) + DBUG_RETURN(true); // Empty database name: does not exist. + path_len= build_table_filename(path, sizeof(path) - 1, dbname.str, "", "", 0); + path[path_len - 1]= 0; + if (!mysql_file_stat(key_file_misc, path, &stat_info, MYF(0))) + DBUG_RETURN(true); // The database directory was not found: does not exist. + DBUG_RETURN(false); // The database directory was found. +} + + +int fill_schema_schemata(THD *thd, TABLE_LIST *tables, COND *cond) +{ + /* + TODO: fill_schema_shemata() is called when new client is connected. + Returning error status in this case leads to client hangup. + */ + + LOOKUP_FIELD_VALUES lookup_field_vals; + Dynamic_array<LEX_CSTRING*> db_names(PSI_INSTRUMENT_MEM); + Schema_specification_st create; + TABLE *table= tables->table; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *sctx= thd->security_ctx; +#endif + DBUG_ENTER("fill_schema_shemata"); + + if (get_lookup_field_values(thd, cond, true, tables, &lookup_field_vals)) + DBUG_RETURN(0); + DBUG_PRINT("INDEX VALUES",("db_name: %s table_name: %s", + lookup_field_vals.db_value.str, + lookup_field_vals.table_value.str)); + if (make_db_list(thd, &db_names, &lookup_field_vals)) + DBUG_RETURN(1); + + /* + If we have lookup db value we should check that the database exists + */ + if(lookup_field_vals.db_value.str && !lookup_field_vals.wild_db_value && + (!db_names.elements() /* The database name was too long */|| + (db_names.at(0) != &INFORMATION_SCHEMA_NAME && + verify_database_directory_exists(lookup_field_vals.db_value)))) + DBUG_RETURN(0); + + for (size_t i=0; i < db_names.elements(); i++) + { + LEX_CSTRING *db_name= db_names.at(i); + DBUG_ASSERT(db_name->length <= NAME_LEN); + if (db_name == &INFORMATION_SCHEMA_NAME) + { + if (store_schema_schemata(thd, table, db_name, + system_charset_info)) + DBUG_RETURN(1); + continue; + } +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (sctx->master_access & (DB_ACLS | SHOW_DB_ACL) || + acl_get_all3(sctx, db_name->str, false) || + !check_grant_db(thd, db_name->str)) +#endif + { + load_db_opt_by_name(thd, db_name->str, &create); + if (store_schema_schemata(thd, table, db_name, + create.default_table_charset, + create.schema_comment)) + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + + +static int get_schema_tables_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + const char *tmp_buff; + MYSQL_TIME time; + int info_error= 0; + CHARSET_INFO *cs= system_charset_info; + DBUG_ENTER("get_schema_tables_record"); + + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[2]->store(table_name->str, table_name->length, cs); + + if (res) + { + /* There was a table open error, so set the table type and return */ + if (tables->view) + table->field[3]->store(STRING_WITH_LEN("VIEW"), cs); + else if (tables->schema_table) + table->field[3]->store(STRING_WITH_LEN("SYSTEM VIEW"), cs); + else + table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), cs); + + if (tables->option) + { + table->field[4]->store(tables->option, strlen(tables->option), cs); + table->field[4]->set_notnull(); + } + goto err; + } + + if (tables->view) + { + table->field[3]->store(STRING_WITH_LEN("VIEW"), cs); + table->field[20]->store(STRING_WITH_LEN("VIEW"), cs); + } + else + { + char option_buff[512]; + String str(option_buff,sizeof(option_buff), system_charset_info); + TABLE *show_table= tables->table; + TABLE_SHARE *share= show_table->s; + handler *file= show_table->db_stat ? show_table->file : 0; + handlerton *tmp_db_type= share->db_type(); +#ifdef WITH_PARTITION_STORAGE_ENGINE + bool is_partitioned= FALSE; +#endif + + if (share->tmp_table == SYSTEM_TMP_TABLE) + table->field[3]->store(STRING_WITH_LEN("SYSTEM VIEW"), cs); + else if (share->table_type == TABLE_TYPE_SEQUENCE) + table->field[3]->store(STRING_WITH_LEN("SEQUENCE"), cs); + else + { + DBUG_ASSERT(share->tmp_table == NO_TMP_TABLE); + if (share->versioned) + table->field[3]->store(STRING_WITH_LEN("SYSTEM VERSIONED"), cs); + else + table->field[3]->store(STRING_WITH_LEN("BASE TABLE"), cs); + } + + for (uint i= 4; i < table->s->fields; i++) + { + if (i == 7 || (i > 12 && i < 17) || i == 18) + continue; + table->field[i]->set_notnull(); + } + + /* Collect table info from the table share */ + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (share->db_type() == partition_hton && + share->partition_info_str_len) + { + tmp_db_type= plugin_hton(share->default_part_plugin); + is_partitioned= TRUE; + } +#endif + + tmp_buff= (char *) ha_resolve_storage_engine_name(tmp_db_type); + table->field[4]->store(tmp_buff, strlen(tmp_buff), cs); + table->field[5]->store((longlong) share->frm_version, TRUE); + + str.length(0); + + if (share->min_rows) + { + str.qs_append(STRING_WITH_LEN(" min_rows=")); + str.qs_append(share->min_rows); + } + + if (share->max_rows) + { + str.qs_append(STRING_WITH_LEN(" max_rows=")); + str.qs_append(share->max_rows); + } + + if (share->avg_row_length) + { + str.qs_append(STRING_WITH_LEN(" avg_row_length=")); + str.qs_append(share->avg_row_length); + } + + if (share->db_create_options & HA_OPTION_PACK_KEYS) + str.qs_append(STRING_WITH_LEN(" pack_keys=1")); + + if (share->db_create_options & HA_OPTION_NO_PACK_KEYS) + str.qs_append(STRING_WITH_LEN(" pack_keys=0")); + + if (share->db_create_options & HA_OPTION_STATS_PERSISTENT) + str.qs_append(STRING_WITH_LEN(" stats_persistent=1")); + + if (share->db_create_options & HA_OPTION_NO_STATS_PERSISTENT) + str.qs_append(STRING_WITH_LEN(" stats_persistent=0")); + + if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_ON) + str.qs_append(STRING_WITH_LEN(" stats_auto_recalc=1")); + else if (share->stats_auto_recalc == HA_STATS_AUTO_RECALC_OFF) + str.qs_append(STRING_WITH_LEN(" stats_auto_recalc=0")); + + if (share->stats_sample_pages != 0) + { + str.qs_append(STRING_WITH_LEN(" stats_sample_pages=")); + str.qs_append(share->stats_sample_pages); + } + + /* We use CHECKSUM, instead of TABLE_CHECKSUM, for backward compatibility */ + if (share->db_create_options & HA_OPTION_CHECKSUM) + str.qs_append(STRING_WITH_LEN(" checksum=1")); + + if (share->page_checksum != HA_CHOICE_UNDEF) + { + str.qs_append(STRING_WITH_LEN(" page_checksum=")); + str.qs_append(&ha_choice_values[(uint) share->page_checksum]); + } + + if (share->db_create_options & HA_OPTION_DELAY_KEY_WRITE) + str.qs_append(STRING_WITH_LEN(" delay_key_write=1")); + + if (share->row_type != ROW_TYPE_DEFAULT) + { + str.qs_append(STRING_WITH_LEN(" row_format=")); + str.qs_append(&ha_row_type[(uint) share->row_type]); + } + + if (share->key_block_size) + { + str.qs_append(STRING_WITH_LEN(" key_block_size=")); + str.qs_append(share->key_block_size); + } + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (is_partitioned) + str.qs_append(STRING_WITH_LEN(" partitioned")); +#endif + + /* + Write transactional=0|1 for tables where the user has specified the + option or for tables that supports both transactional and non + transactional tables + */ + if (share->transactional != HA_CHOICE_UNDEF || + (share->db_type() && + share->db_type()->flags & HTON_TRANSACTIONAL_AND_NON_TRANSACTIONAL && + file)) + { + uint choice= share->transactional; + if (choice == HA_CHOICE_UNDEF) + choice= ((file->ha_table_flags() & + (HA_NO_TRANSACTIONS | HA_CRASH_SAFE)) == + HA_NO_TRANSACTIONS ? + HA_CHOICE_NO : HA_CHOICE_YES); + + str.qs_append(STRING_WITH_LEN(" transactional=")); + str.qs_append(&ha_choice_values[choice]); + } + append_create_options(thd, &str, share->option_list, false, 0); + + if (file) + { + HA_CREATE_INFO create_info; + create_info.init(); + file->update_create_info(&create_info); + append_directory(thd, &str, &DATA_clex_str, create_info.data_file_name); + append_directory(thd, &str, &INDEX_clex_str, create_info.index_file_name); + } + + if (str.length()) + table->field[19]->store(str.ptr()+1, str.length()-1, cs); + + LEX_CSTRING tmp_str; + if (share->table_charset) + tmp_str= share->table_charset->coll_name; + else + tmp_str= { STRING_WITH_LEN("default") }; + table->field[17]->store(&tmp_str, cs); + + if (share->comment.str) + table->field[20]->store(&share->comment, cs); + + /* Collect table info from the storage engine */ + + if (file) + { + /* If info() fails, then there's nothing else to do */ + if (unlikely((info_error= file->info(HA_STATUS_VARIABLE | + HA_STATUS_TIME | + HA_STATUS_VARIABLE_EXTRA | + HA_STATUS_AUTO)) != 0)) + { + file->print_error(info_error, MYF(0)); + goto err; + } + + enum row_type row_type = file->get_row_type(); + switch (row_type) { + case ROW_TYPE_NOT_USED: + case ROW_TYPE_DEFAULT: + tmp_buff= ((share->db_options_in_use & + HA_OPTION_COMPRESS_RECORD) ? "Compressed" : + (share->db_options_in_use & HA_OPTION_PACK_RECORD) ? + "Dynamic" : "Fixed"); + break; + case ROW_TYPE_FIXED: + tmp_buff= "Fixed"; + break; + case ROW_TYPE_DYNAMIC: + tmp_buff= "Dynamic"; + break; + case ROW_TYPE_COMPRESSED: + tmp_buff= "Compressed"; + break; + case ROW_TYPE_REDUNDANT: + tmp_buff= "Redundant"; + break; + case ROW_TYPE_COMPACT: + tmp_buff= "Compact"; + break; + case ROW_TYPE_PAGE: + tmp_buff= "Page"; + break; + } + + table->field[6]->store(tmp_buff, strlen(tmp_buff), cs); + + if (!tables->schema_table) + { + table->field[7]->store((longlong) file->stats.records, TRUE); + table->field[7]->set_notnull(); + } + table->field[8]->store((longlong) file->stats.mean_rec_length, TRUE); + table->field[9]->store((longlong) file->stats.data_file_length, TRUE); + if (file->stats.max_data_file_length) + { + table->field[10]->store((longlong) file->stats.max_data_file_length, + TRUE); + table->field[10]->set_notnull(); + } + table->field[11]->store((longlong) file->stats.index_file_length, TRUE); + if (file->stats.max_index_file_length) + { + table->field[21]->store((longlong) file->stats.max_index_file_length, + TRUE); + table->field[21]->set_notnull(); + } + table->field[12]->store((longlong) file->stats.delete_length, TRUE); + if (show_table->found_next_number_field) + { + table->field[13]->store((longlong) file->stats.auto_increment_value, + TRUE); + table->field[13]->set_notnull(); + } + if (file->stats.create_time) + { + thd->variables.time_zone->gmt_sec_to_TIME(&time, + (my_time_t) file->stats.create_time); + table->field[14]->store_time(&time); + table->field[14]->set_notnull(); + } + if (file->stats.update_time) + { + thd->variables.time_zone->gmt_sec_to_TIME(&time, + (my_time_t) file->stats.update_time); + table->field[15]->store_time(&time); + table->field[15]->set_notnull(); + } + if (file->stats.check_time) + { + thd->variables.time_zone->gmt_sec_to_TIME(&time, + (my_time_t) file->stats.check_time); + table->field[16]->store_time(&time); + table->field[16]->set_notnull(); + } + if ((file->ha_table_flags() & + (HA_HAS_OLD_CHECKSUM | HA_HAS_NEW_CHECKSUM)) && + !file->stats.checksum_null) + { + table->field[18]->store((longlong) file->stats.checksum, TRUE); + table->field[18]->set_notnull(); + } + } + /* If table is a temporary table */ + LEX_CSTRING tmp= { STRING_WITH_LEN("N") }; + if (show_table->s->tmp_table != NO_TMP_TABLE) + tmp.str= "Y"; + table->field[22]->store(tmp.str, tmp.length, cs); + } + +err: + if (unlikely(res || info_error)) + { + /* + If an error was encountered, push a warning, set the TABLE COMMENT + column with the error text, and clear the error so that the operation + can continue. + */ + const char *error= thd->get_stmt_da()->message(); + table->field[20]->store(error, strlen(error), cs); + + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), error); + thd->clear_error(); + } + + DBUG_RETURN(schema_table_store_record(thd, table)); +} + + +/** + @brief Store field characteristics into appropriate I_S table columns + + @param[in] table I_S table + @param[in] field processed field + @param[in] cs I_S table charset + @param[in] offset offset from beginning of table + to DATE_TYPE column in I_S table + + @return void +*/ + +static void store_column_type(TABLE *table, Field *field, CHARSET_INFO *cs, + uint offset) +{ + const char *tmp_buff; + char column_type_buff[MAX_FIELD_WIDTH]; + String column_type(column_type_buff, sizeof(column_type_buff), cs); + + field->sql_type(column_type); + /* DTD_IDENTIFIER column */ + table->field[offset + 8]->store(column_type.ptr(), column_type.length(), cs); + table->field[offset + 8]->set_notnull(); + /* + DATA_TYPE column: + MySQL column type has the following format: + base_type [(dimension)] [unsigned] [zerofill]. + For DATA_TYPE column we extract only base type. + */ + tmp_buff= strchr(column_type.c_ptr_safe(), '('); + if (!tmp_buff) + /* + if there is no dimention part then check the presence of + [unsigned] [zerofill] attributes and cut them of if exist. + */ + tmp_buff= strchr(column_type.c_ptr_safe(), ' '); + table->field[offset]->store(column_type.ptr(), + (tmp_buff ? (uint)(tmp_buff - column_type.ptr()) : + column_type.length()), cs); + + Information_schema_character_attributes cattr= + field->information_schema_character_attributes(); + if (cattr.has_char_length()) + { + /* CHARACTER_MAXIMUM_LENGTH column*/ + table->field[offset + 1]->store((longlong) cattr.char_length(), true); + table->field[offset + 1]->set_notnull(); + } + if (cattr.has_octet_length()) + { + /* CHARACTER_OCTET_LENGTH column */ + table->field[offset + 2]->store((longlong) cattr.octet_length(), true); + table->field[offset + 2]->set_notnull(); + } + + /* + Calculate field_length and decimals. + They are set to -1 if they should not be set (we should return NULL) + */ + + Information_schema_numeric_attributes num= + field->information_schema_numeric_attributes(); + + switch (field->type()) { + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATETIME: + /* DATETIME_PRECISION column */ + table->field[offset + 5]->store((longlong) field->decimals(), TRUE); + table->field[offset + 5]->set_notnull(); + break; + default: + break; + } + + /* NUMERIC_PRECISION column */ + if (num.has_precision()) + { + table->field[offset + 3]->store((longlong) num.precision(), true); + table->field[offset + 3]->set_notnull(); + + /* NUMERIC_SCALE column */ + if (num.has_scale()) + { + table->field[offset + 4]->store((longlong) num.scale(), true); + table->field[offset + 4]->set_notnull(); + } + } + if (field->has_charset()) + { + /* CHARACTER_SET_NAME column*/ + table->field[offset + 6]->store(&field->charset()->cs_name, cs); + table->field[offset + 6]->set_notnull(); + /* COLLATION_NAME column */ + table->field[offset + 7]->store(&field->charset()->coll_name, cs); + table->field[offset + 7]->set_notnull(); + } +} + + +/* + Print DATA_TYPE independently from sql_mode. + It's only a brief human-readable description, without attributes, + so it should not be used by client programs to generate SQL scripts. +*/ +static bool print_anchor_data_type(const Spvar_definition *def, + String *data_type) +{ + if (def->column_type_ref()) + return data_type->append(STRING_WITH_LEN("TYPE OF")); + if (def->is_table_rowtype_ref()) + return data_type->append(STRING_WITH_LEN("ROW TYPE OF")); + /* + "ROW TYPE OF cursor" is not possible yet. + May become possible when we add package-wide cursors. + */ + DBUG_ASSERT(0); + return false; +} + + +/* + DTD_IDENTIFIER is the full data type description with attributes. + It can be used by client programs to generate SQL scripts. + Let's print it according to the current sql_mode. + It will make output in line with the value in mysql.proc.param_list, + so both I_S.XXX.DTD_IDENTIFIER and mysql.proc.param_list use the same notation: + default or Oracle, according to the sql_mode at the SP creation time. + The caller must make sure to set thd->variables.sql_mode to the routine sql_mode. +*/ +static bool print_anchor_dtd_identifier(THD *thd, const Spvar_definition *def, + String *dtd_identifier) +{ + if (def->column_type_ref()) + return (thd->variables.sql_mode & MODE_ORACLE) ? + def->column_type_ref()->append_to(thd, dtd_identifier) || + dtd_identifier->append(STRING_WITH_LEN("%TYPE")) : + dtd_identifier->append(STRING_WITH_LEN("TYPE OF ")) || + def->column_type_ref()->append_to(thd, dtd_identifier); + if (def->is_table_rowtype_ref()) + return (thd->variables.sql_mode & MODE_ORACLE) ? + def->table_rowtype_ref()->append_to(thd, dtd_identifier) || + dtd_identifier->append(STRING_WITH_LEN("%ROWTYPE")) : + dtd_identifier->append(STRING_WITH_LEN("ROW TYPE OF ")) || + def->table_rowtype_ref()->append_to(thd, dtd_identifier); + DBUG_ASSERT(0); // See comments in print_anchor_data_type() + return false; +} + + +/* + Set columns DATA_TYPE and DTD_IDENTIFIER from an SP variable definition +*/ +static void store_variable_type(THD *thd, const sp_variable *spvar, + TABLE *tmptbl, + TABLE_SHARE *tmpshare, + CHARSET_INFO *cs, + TABLE *table, uint offset) +{ + if (spvar->field_def.is_explicit_data_type()) + { + if (spvar->field_def.is_row()) + { + // Explicit ROW + table->field[offset]->store(STRING_WITH_LEN("ROW"), cs); + table->field[offset]->set_notnull(); + // Perhaps eventually we need to print all ROW elements in DTD_IDENTIFIER + table->field[offset + 8]->store(STRING_WITH_LEN("ROW"), cs); + table->field[offset + 8]->set_notnull(); + } + else + { + // Explicit scalar data type + Field *field= spvar->field_def.make_field(tmpshare, thd->mem_root, + &spvar->name); + field->table= tmptbl; + tmptbl->in_use= thd; + store_column_type(table, field, cs, offset); + } + } + else + { + StringBuffer<128> data_type(cs), dtd_identifier(cs); + + if (print_anchor_data_type(&spvar->field_def, &data_type)) + { + table->field[offset]->store(STRING_WITH_LEN("ERROR"), cs); // EOM? + table->field[offset]->set_notnull(); + } + else + { + DBUG_ASSERT(data_type.length()); + table->field[offset]->store(data_type.ptr(), data_type.length(), cs); + table->field[offset]->set_notnull(); + } + + if (print_anchor_dtd_identifier(thd, &spvar->field_def, &dtd_identifier)) + { + table->field[offset + 8]->store(STRING_WITH_LEN("ERROR"), cs); // EOM? + table->field[offset + 8]->set_notnull(); + } + else + { + DBUG_ASSERT(dtd_identifier.length()); + table->field[offset + 8]->store(dtd_identifier.ptr(), + dtd_identifier.length(), cs); + table->field[offset + 8]->set_notnull(); + } + } +} + + +static int get_schema_column_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + LEX *lex= thd->lex; + const char *wild= lex->wild ? lex->wild->ptr() : NullS; + CHARSET_INFO *cs= system_charset_info; + TABLE *show_table; + Field **ptr, *field; + int count; + bool quoted_defaults= lex->sql_command != SQLCOM_SHOW_FIELDS; + DBUG_ENTER("get_schema_column_record"); + + if (res) + { + if (lex->sql_command != SQLCOM_SHOW_FIELDS) + { + /* + I.e. we are in SELECT FROM INFORMATION_SCHEMA.COLUMS + rather than in SHOW COLUMNS + */ + if (thd->is_error()) + convert_error_to_warning(thd); + res= 0; + } + DBUG_RETURN(res); + } + show_table= tables->table; + count= 0; + ptr= show_table->field; + show_table->use_all_columns(); // Required for default + restore_record(show_table, s->default_values); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + check_access(thd, SELECT_ACL, db_name->str, + &tables->grant.privilege, 0, 0, MY_TEST(tables->schema_table)); + if (is_temporary_table(tables)) + { + tables->grant.privilege|= TMP_TABLE_ACLS; + } +#endif + + for (; (field= *ptr) ; ptr++) + { + if(field->invisible > INVISIBLE_USER) + continue; + uchar *pos; + char tmp[MAX_FIELD_WIDTH]; + String type(tmp,sizeof(tmp), system_charset_info); + + DEBUG_SYNC(thd, "get_schema_column"); + + if (wild && wild[0] && + wild_case_compare(system_charset_info, field->field_name.str, wild)) + continue; + + count++; + /* Get default row, with all NULL fields set to NULL */ + restore_record(table, s->default_values); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + ulonglong col_access= + get_column_grant(thd, &tables->grant, db_name->str, table_name->str, + field->field_name.str) & COL_ACLS; + + if (!col_access && !tables->schema_table) + continue; + + char *end= tmp; + for (uint bitnr=0; col_access ; col_access>>=1,bitnr++) + { + if (col_access & 1) + { + *end++=','; + end=strmov(end,grant_types.type_names[bitnr]); + } + } + table->field[18]->store(tmp+1,end == tmp ? 0 : (uint) (end-tmp-1), cs); + +#endif + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[2]->store(table_name->str, table_name->length, cs); + table->field[3]->store(field->field_name.str, field->field_name.length, + cs); + table->field[4]->store((longlong) count, TRUE); + + if (get_field_default_value(thd, field, &type, quoted_defaults)) + { + table->field[5]->store(type.ptr(), type.length(), cs); + table->field[5]->set_notnull(); + } + pos=(uchar*) ((field->flags & NOT_NULL_FLAG) ? "NO" : "YES"); + table->field[6]->store((const char*) pos, + strlen((const char*) pos), cs); + store_column_type(table, field, cs, 7); + pos=(uchar*) ((field->flags & PRI_KEY_FLAG) ? "PRI" : + (field->flags & UNIQUE_KEY_FLAG) ? "UNI" : + (field->flags & MULTIPLE_KEY_FLAG) ? "MUL":""); + table->field[16]->store((const char*) pos, + strlen((const char*) pos), cs); + + StringBuffer<256> buf; + if (field->unireg_check == Field::NEXT_NUMBER) + buf.set(STRING_WITH_LEN("auto_increment"),cs); + if (print_on_update_clause(field, &type, true)) + buf.set(type.ptr(), type.length(),cs); + if (field->vcol_info) + { + String gen_s(tmp,sizeof(tmp), system_charset_info); + gen_s.length(0); + field->vcol_info->print(&gen_s); + table->field[21]->store(gen_s.ptr(), gen_s.length(), cs); + table->field[21]->set_notnull(); + table->field[20]->store(STRING_WITH_LEN("ALWAYS"), cs); + + if (field->vcol_info->stored_in_db) + buf.set(STRING_WITH_LEN("STORED GENERATED"), cs); + else + buf.set(STRING_WITH_LEN("VIRTUAL GENERATED"), cs); + } + else if (field->flags & VERS_SYSTEM_FIELD) + { + if (field->flags & VERS_ROW_START) + { + table->field[21]->store(STRING_WITH_LEN("ROW START"), cs); + buf.set(STRING_WITH_LEN("STORED GENERATED"), cs); + } + else + { + table->field[21]->store(STRING_WITH_LEN("ROW END"), cs); + buf.set(STRING_WITH_LEN("STORED GENERATED"), cs); + } + table->field[21]->set_notnull(); + table->field[20]->store(STRING_WITH_LEN("ALWAYS"), cs); + } + else + table->field[20]->store(STRING_WITH_LEN("NEVER"), cs); + /*Invisible can coexist with auto_increment and virtual */ + if (field->invisible == INVISIBLE_USER) + { + if (buf.length()) + buf.append(STRING_WITH_LEN(", ")); + buf.append(STRING_WITH_LEN("INVISIBLE"),cs); + } + if (field->vers_update_unversioned()) + { + if (buf.length()) + buf.append(STRING_WITH_LEN(", ")); + buf.append(STRING_WITH_LEN("WITHOUT SYSTEM VERSIONING"), cs); + } + table->field[17]->store(buf.ptr(), buf.length(), cs); + table->field[19]->store(field->comment.str, field->comment.length, cs); + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +int fill_schema_charsets(THD *thd, TABLE_LIST *tables, COND *cond) +{ + CHARSET_INFO **cs; + const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; + TABLE *table= tables->table; + CHARSET_INFO *scs= system_charset_info; + + for (cs= all_charsets ; + cs < all_charsets + array_elements(all_charsets) ; + cs++) + { + CHARSET_INFO *tmp_cs= cs[0]; + if (tmp_cs && (tmp_cs->state & MY_CS_PRIMARY) && + (tmp_cs->state & MY_CS_AVAILABLE) && + !(tmp_cs->state & MY_CS_HIDDEN) && + !(wild && wild[0] && + wild_case_compare(scs, tmp_cs->cs_name.str,wild))) + { + const char *comment; + restore_record(table, s->default_values); + table->field[0]->store(&tmp_cs->cs_name, scs); + table->field[1]->store(&tmp_cs->coll_name, scs); + comment= tmp_cs->comment ? tmp_cs->comment : ""; + table->field[2]->store(comment, strlen(comment), scs); + table->field[3]->store((longlong) tmp_cs->mbmaxlen, TRUE); + if (schema_table_store_record(thd, table)) + return 1; + } + } + return 0; +} + + +static my_bool iter_schema_engines(THD *thd, plugin_ref plugin, + void *ptable) +{ + TABLE *table= (TABLE *) ptable; + handlerton *hton= plugin_hton(plugin); + const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; + CHARSET_INFO *scs= system_charset_info; + handlerton *default_type= ha_default_handlerton(thd); + DBUG_ENTER("iter_schema_engines"); + + + /* Disabled plugins */ + if (plugin_state(plugin) != PLUGIN_IS_READY) + { + + struct st_maria_plugin *plug= plugin_decl(plugin); + if (!(wild && wild[0] && + wild_case_compare(scs, plug->name,wild))) + { + restore_record(table, s->default_values); + table->field[0]->store(plug->name, strlen(plug->name), scs); + table->field[1]->store(STRING_WITH_LEN("NO"), scs); + table->field[2]->store(plug->descr, strlen(plug->descr), scs); + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); + } + + if (!(hton->flags & HTON_HIDDEN)) + { + LEX_CSTRING *name= plugin_name(plugin); + if (!(wild && wild[0] && + wild_case_compare(scs, name->str,wild))) + { + LEX_CSTRING yesno[2]= {{ STRING_WITH_LEN("NO") }, + { STRING_WITH_LEN("YES") }}; + LEX_CSTRING *tmp; + const char *option_name= default_type != hton ? yesno[1].str + : "DEFAULT"; + restore_record(table, s->default_values); + + table->field[0]->store(name->str, name->length, scs); + table->field[1]->store(option_name, strlen(option_name), scs); + table->field[2]->store(plugin_decl(plugin)->descr, + strlen(plugin_decl(plugin)->descr), scs); + tmp= &yesno[MY_TEST(hton->commit && !(hton->flags & HTON_NO_ROLLBACK))]; + table->field[3]->store(tmp->str, tmp->length, scs); + table->field[3]->set_notnull(); + tmp= &yesno[MY_TEST(hton->prepare)]; + table->field[4]->store(tmp->str, tmp->length, scs); + table->field[4]->set_notnull(); + tmp= &yesno[MY_TEST(hton->savepoint_set)]; + table->field[5]->store(tmp->str, tmp->length, scs); + table->field[5]->set_notnull(); + + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + +int fill_schema_engines(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_schema_engines"); + if (plugin_foreach_with_mask(thd, iter_schema_engines, + MYSQL_STORAGE_ENGINE_PLUGIN, + ~(PLUGIN_IS_FREED | PLUGIN_IS_DYING), + tables->table)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + +int fill_schema_collation(THD *thd, TABLE_LIST *tables, COND *cond) +{ + CHARSET_INFO **cs; + const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; + TABLE *table= tables->table; + CHARSET_INFO *scs= system_charset_info; + for (cs= all_charsets ; + cs < all_charsets + array_elements(all_charsets) ; + cs++ ) + { + CHARSET_INFO **cl; + CHARSET_INFO *tmp_cs= cs[0]; + if (!tmp_cs || !(tmp_cs->state & MY_CS_AVAILABLE) || + (tmp_cs->state & MY_CS_HIDDEN) || + !(tmp_cs->state & MY_CS_PRIMARY)) + continue; + for (cl= all_charsets; + cl < all_charsets + array_elements(all_charsets) ; + cl ++) + { + CHARSET_INFO *tmp_cl= cl[0]; + if (!tmp_cl || !(tmp_cl->state & MY_CS_AVAILABLE) || + !my_charset_same(tmp_cs, tmp_cl)) + continue; + if (!(wild && wild[0] && + wild_case_compare(scs, tmp_cl->coll_name.str, wild))) + { + LEX_CSTRING context_collation_name= + tmp_cl->get_collation_name(MY_COLLATION_NAME_MODE_CONTEXT); + LEX_CSTRING full_collation_name= + tmp_cl->get_collation_name(MY_COLLATION_NAME_MODE_FULL); + bool is_context= cmp(context_collation_name, full_collation_name); + /* + Some collations are applicable to multiple character sets. + Display them only once, with the short name (without the + character set prefix). + */ + if (is_context && + cmp(tmp_cl->cs_name, Lex_cstring(STRING_WITH_LEN("utf8mb4")))) + continue; + restore_record(table, s->default_values); + table->field[0]->store(context_collation_name, scs); + if (is_context) + { + table->field[1]->set_null(); // CHARACTER_SET_NAME + table->field[2]->set_null(); // ID + table->field[3]->set_null(); // IS_DEFAULT + } + else + { + table->field[1]->set_notnull(); // CHARACTER_SET_NAME + table->field[1]->store(tmp_cl->cs_name, scs); + table->field[2]->set_notnull(); // ID + table->field[2]->store((longlong) tmp_cl->number, TRUE); + table->field[3]->set_notnull(); // IS_DEFAULT + table->field[3]->store( + Show::Yes_or_empty::value(tmp_cl->default_flag()), scs); + } + table->field[4]->store( + Show::Yes_or_empty::value(tmp_cl->compiled_flag()), scs); + table->field[5]->store((longlong) tmp_cl->strxfrm_multiply, TRUE); + if (schema_table_store_record(thd, table)) + return 1; + } + } + } + return 0; +} + + +int fill_schema_coll_charset_app(THD *thd, TABLE_LIST *tables, COND *cond) +{ + CHARSET_INFO **cs; + TABLE *table= tables->table; + CHARSET_INFO *scs= system_charset_info; + for (cs= all_charsets ; + cs < all_charsets + array_elements(all_charsets) ; + cs++ ) + { + CHARSET_INFO **cl; + CHARSET_INFO *tmp_cs= cs[0]; + if (!tmp_cs || !(tmp_cs->state & MY_CS_AVAILABLE) || + !(tmp_cs->state & MY_CS_PRIMARY)) + continue; + for (cl= all_charsets; + cl < all_charsets + array_elements(all_charsets) ; + cl ++) + { + CHARSET_INFO *tmp_cl= cl[0]; + if (!tmp_cl || !(tmp_cl->state & MY_CS_AVAILABLE) || + (tmp_cl->state & MY_CS_HIDDEN) || + !my_charset_same(tmp_cs,tmp_cl)) + continue; + restore_record(table, s->default_values); + LEX_CSTRING context_collation_name= + tmp_cl->get_collation_name(MY_COLLATION_NAME_MODE_CONTEXT); + LEX_CSTRING full_collation_name= + tmp_cl->get_collation_name(MY_COLLATION_NAME_MODE_FULL); + table->field[0]->store(context_collation_name, scs); + table->field[1]->store(&tmp_cl->cs_name, scs); + table->field[2]->store(full_collation_name, scs); + table->field[3]->store(tmp_cl->number); + table->field[4]->store( + Show::Yes_or_empty::value(tmp_cl->default_flag()), scs); + if (schema_table_store_record(thd, table)) + return 1; + } + } + return 0; +} + + +static inline void copy_field_as_string(Field *to_field, Field *from_field) +{ + char buff[MAX_FIELD_WIDTH]; + String tmp_str(buff, sizeof(buff), system_charset_info); + from_field->val_str(&tmp_str); + to_field->store(tmp_str.ptr(), tmp_str.length(), system_charset_info); +} + + +/** + @brief When scanning mysql.proc check if we should skip this record or even + stop the scan + + @param name_field_charset mysql.proc.name field charset info + @param lookup values from the WHERE clause which are + used for the index lookup + @param db mysql.proc.db field value of + the current record + @param name mysql.proc.name field value of + the current record + + @return Result + @retval -1 The record is match (do further processing) + @retval 0 Skip this record, it doesn't match. + @retval HA_ERR_END_OF_FILE Stop scanning, no further matches possible +*/ + +int check_proc_record(const CHARSET_INFO *name_field_charset, + const LOOKUP_FIELD_VALUES *lookup, + const LEX_CSTRING &db, + const LEX_CSTRING &name) +{ + if (lookup->db_value.str && cmp(lookup->db_value, db)) + { + /* + We have the name of target database. If we got a non-matching + record, this means we've finished reading matching mysql.proc records + */ + return HA_ERR_END_OF_FILE; + } + + if (lookup->table_value.str) + { + if ((my_ci_strnncoll(name_field_charset, + (const uchar *) lookup->table_value.str, + lookup->table_value.length, + (const uchar *) name.str, name.length, 0))) + { + /* Routine name doesn't match. */ + if (lookup->db_value.str) + { + /* + We're using index lookup. A non-matching record means we've + finished reading matches. + */ + return HA_ERR_END_OF_FILE; + } + else + { + /* The routine name doesn't match, but we're scanning all databases */ + return 0; /* Continue scanning */ + } + } + } + return -1; /* This is a match */ +} + +/** + @brief Store record into I_S.PARAMETERS table + + @param[in] thd thread handler + @param[in] table I_S table + @param[in] proc_table 'mysql.proc' table + @param[in] full_access if 1 user has privileges on the routine + @param[in] sp_user user in 'user@host' format + + @return Operation status + @retval 0 ok + @retval != 0 error / HA_ERR_END_OF_FILE + (if there are no more + matching records) +*/ + +int store_schema_params(THD *thd, TABLE *table, TABLE *proc_table, + LOOKUP_FIELD_VALUES *lookup, bool full_access, + const char *sp_user) +{ + TABLE_SHARE share; + TABLE tbl; + CHARSET_INFO *cs= system_charset_info; + LEX_CSTRING definer, params, returns= empty_clex_str; + LEX_CSTRING db, name; + char path[FN_REFLEN]; + sp_head *sp; + const Sp_handler *sph; + bool free_sp_head; + bool error= 0; + sql_mode_t sql_mode; + int rc; + DBUG_ENTER("store_schema_params"); + + bzero((char*) &tbl, sizeof(TABLE)); + (void) build_table_filename(path, sizeof(path), "", "", "", 0); + init_tmp_table_share(thd, &share, "", 0, "", path); + + proc_table->field[MYSQL_PROC_FIELD_DB]->val_str_nopad(thd->mem_root, &db); + proc_table->field[MYSQL_PROC_FIELD_NAME]->val_str_nopad(thd->mem_root, &name); + + CHARSET_INFO *name_cs= proc_table->field[MYSQL_PROC_FIELD_NAME]->charset(); + + if ((rc= check_proc_record(name_cs, lookup, db, name)) != -1) + DBUG_RETURN(rc); /* either HA_ERR_END_OF_FILE or 0 if name didn't match */ + + proc_table->field[MYSQL_PROC_FIELD_DEFINER]->val_str_nopad(thd->mem_root, &definer); + sql_mode= (sql_mode_t) proc_table->field[MYSQL_PROC_FIELD_SQL_MODE]->val_int(); + sph= Sp_handler::handler_mysql_proc((enum_sp_type) + proc_table->field[MYSQL_PROC_MYSQL_TYPE]-> + val_int()); + if (!sph || sph->type() == SP_TYPE_PACKAGE || + sph->type() == SP_TYPE_PACKAGE_BODY) + DBUG_RETURN(0); + + if (!full_access) + full_access= !strcmp(sp_user, definer.str); + if (!full_access && + check_some_routine_access(thd, db.str, name.str, sph)) + DBUG_RETURN(0); + + proc_table->field[MYSQL_PROC_FIELD_PARAM_LIST]->val_str_nopad(thd->mem_root, + ¶ms); + if (sph->type() == SP_TYPE_FUNCTION) + proc_table->field[MYSQL_PROC_FIELD_RETURNS]->val_str_nopad(thd->mem_root, + &returns); + sp= sph->sp_load_for_information_schema(thd, proc_table, db, name, + params, returns, sql_mode, + &free_sp_head); + if (sp) + { + Field *field; + LEX_CSTRING tmp_string; + Sql_mode_save sql_mode_backup(thd); + thd->variables.sql_mode= sql_mode; + + if (sph->type() == SP_TYPE_FUNCTION) + { + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db, cs); + table->field[2]->store(name, cs); + table->field[3]->store((longlong) 0, TRUE); + proc_table->field[MYSQL_PROC_MYSQL_TYPE]->val_str_nopad(thd->mem_root, + &tmp_string); + table->field[15]->store(tmp_string, cs); + field= sp->m_return_field_def.make_field(&share, thd->mem_root, + &empty_clex_str); + field->table= &tbl; + tbl.in_use= thd; + store_column_type(table, field, cs, 6); + if (schema_table_store_record(thd, table)) + { + free_table_share(&share); + if (free_sp_head) + sp_head::destroy(sp); + DBUG_RETURN(1); + } + } + + sp_pcontext *spcont= sp->get_parse_context(); + uint params= spcont->context_var_count(); + for (uint i= 0 ; i < params ; i++) + { + const char *tmp_buff; + sp_variable *spvar= spcont->find_variable(i); + switch (spvar->mode) { + case sp_variable::MODE_IN: + tmp_buff= "IN"; + break; + case sp_variable::MODE_OUT: + tmp_buff= "OUT"; + break; + case sp_variable::MODE_INOUT: + tmp_buff= "INOUT"; + break; + default: + tmp_buff= ""; + break; + } + + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db, cs); + table->field[2]->store(name, cs); + table->field[3]->store((longlong) i + 1, TRUE); + table->field[4]->store(tmp_buff, strlen(tmp_buff), cs); + table->field[4]->set_notnull(); + table->field[5]->store(spvar->name.str, spvar->name.length, cs); + table->field[5]->set_notnull(); + proc_table->field[MYSQL_PROC_MYSQL_TYPE]->val_str_nopad(thd->mem_root, + &tmp_string); + table->field[15]->store(tmp_string, cs); + + store_variable_type(thd, spvar, &tbl, &share, cs, table, 6); + if (schema_table_store_record(thd, table)) + { + error= 1; + break; + } + } + if (free_sp_head) + sp_head::destroy(sp); + } + free_table_share(&share); + DBUG_RETURN(error); +} + + +int store_schema_proc(THD *thd, TABLE *table, TABLE *proc_table, + LOOKUP_FIELD_VALUES *lookup, bool full_access, + const char *sp_user) +{ + LEX *lex= thd->lex; + CHARSET_INFO *cs= system_charset_info; + const Sp_handler *sph; + LEX_CSTRING db, name, definer, returns= empty_clex_str; + const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; + int rc; + + proc_table->field[MYSQL_PROC_FIELD_DB]->val_str_nopad(thd->mem_root, &db); + proc_table->field[MYSQL_PROC_FIELD_NAME]->val_str_nopad(thd->mem_root, &name); + + CHARSET_INFO *name_cs= proc_table->field[MYSQL_PROC_FIELD_NAME]->charset(); + if ((rc= check_proc_record(name_cs, lookup, db, name)) != -1) + return rc; /* either HA_ERR_END_OF_FILE or 0 if name didn't match */ + + proc_table->field[MYSQL_PROC_FIELD_DEFINER]->val_str_nopad(thd->mem_root, &definer); + sph= Sp_handler::handler_mysql_proc((enum_sp_type) + proc_table->field[MYSQL_PROC_MYSQL_TYPE]-> + val_int()); + if (!sph) + return 0; + + if (!full_access) + full_access= !strcmp(sp_user, definer.str); + if (!full_access && + check_some_routine_access(thd, db.str, name.str, sph)) + return 0; + + if (!is_show_command(thd) || + sph == Sp_handler::handler(lex->sql_command)) + { + restore_record(table, s->default_values); + if (!wild || !wild[0] || !wild_case_compare(system_charset_info, + name.str, wild)) + { + int enum_idx= (int) proc_table->field[MYSQL_PROC_FIELD_ACCESS]->val_int(); + table->field[3]->store(name, cs); + + copy_field_as_string(table->field[0], + proc_table->field[MYSQL_PROC_FIELD_SPECIFIC_NAME]); + table->field[1]->store(STRING_WITH_LEN("def"), cs); + table->field[2]->store(db, cs); + copy_field_as_string(table->field[4], + proc_table->field[MYSQL_PROC_MYSQL_TYPE]); + + if (sph->type() == SP_TYPE_FUNCTION) + { + sp_head *sp; + bool free_sp_head; + proc_table->field[MYSQL_PROC_FIELD_RETURNS]->val_str_nopad(thd->mem_root, + &returns); + sp= sph->sp_load_for_information_schema(thd, proc_table, + db, name, + empty_clex_str /*params*/, + returns, + (ulong) proc_table-> + field[MYSQL_PROC_FIELD_SQL_MODE]-> + val_int(), + &free_sp_head); + if (sp) + { + char path[FN_REFLEN]; + TABLE_SHARE share; + TABLE tbl; + Field *field; + + bzero((char*) &tbl, sizeof(TABLE)); + (void) build_table_filename(path, sizeof(path), "", "", "", 0); + init_tmp_table_share(thd, &share, "", 0, "", path); + field= sp->m_return_field_def.make_field(&share, thd->mem_root, + &empty_clex_str); + field->table= &tbl; + tbl.in_use= thd; + store_column_type(table, field, cs, 5); + free_table_share(&share); + if (free_sp_head) + sp_head::destroy(sp); + } + } + + if (full_access) + { + copy_field_as_string(table->field[15], + proc_table->field[MYSQL_PROC_FIELD_BODY_UTF8]); + table->field[15]->set_notnull(); + } + table->field[14]->store(STRING_WITH_LEN("SQL"), cs); + table->field[18]->store(STRING_WITH_LEN("SQL"), cs); + copy_field_as_string(table->field[19], + proc_table->field[MYSQL_PROC_FIELD_DETERMINISTIC]); + table->field[20]->store(sp_data_access_name[enum_idx].str, + sp_data_access_name[enum_idx].length , cs); + copy_field_as_string(table->field[22], + proc_table->field[MYSQL_PROC_FIELD_SECURITY_TYPE]); + + proc_table->field[MYSQL_PROC_FIELD_CREATED]-> + save_in_field(table->field[23]); + proc_table->field[MYSQL_PROC_FIELD_MODIFIED]-> + save_in_field(table->field[24]); + + copy_field_as_string(table->field[25], + proc_table->field[MYSQL_PROC_FIELD_SQL_MODE]); + copy_field_as_string(table->field[26], + proc_table->field[MYSQL_PROC_FIELD_COMMENT]); + + table->field[27]->store(definer, cs); + copy_field_as_string(table->field[28], + proc_table-> + field[MYSQL_PROC_FIELD_CHARACTER_SET_CLIENT]); + copy_field_as_string(table->field[29], + proc_table-> + field[MYSQL_PROC_FIELD_COLLATION_CONNECTION]); + copy_field_as_string(table->field[30], + proc_table->field[MYSQL_PROC_FIELD_DB_COLLATION]); + + return schema_table_store_record(thd, table); + } + } + return 0; +} + + +int fill_schema_proc(THD *thd, TABLE_LIST *tables, COND *cond) +{ + TABLE *proc_table; + TABLE_LIST proc_tables; + int res= 0; + TABLE *table= tables->table; + bool full_access; + char definer[USER_HOST_BUFF_SIZE]; + enum enum_schema_tables schema_table_idx= + get_schema_table_idx(tables->schema_table); + DBUG_ENTER("fill_schema_proc"); + + strxmov(definer, thd->security_ctx->priv_user, "@", + thd->security_ctx->priv_host, NullS); + /* We use this TABLE_LIST instance only for checking of privileges. */ + bzero((char*) &proc_tables,sizeof(proc_tables)); + proc_tables.db= MYSQL_SCHEMA_NAME; + proc_tables.table_name= MYSQL_PROC_NAME; + proc_tables.alias= MYSQL_PROC_NAME; + proc_tables.lock_type= TL_READ; + full_access= !check_table_access(thd, SELECT_ACL, &proc_tables, FALSE, + 1, TRUE); + + LOOKUP_FIELD_VALUES lookup; + if (get_lookup_field_values(thd, cond, false, tables, &lookup)) + { + // There can be no matching records for the condition + DBUG_RETURN(0); + } + + start_new_trans new_trans(thd); + + if (!(proc_table= open_proc_table_for_read(thd))) + { + new_trans.restore_old_transaction(); + DBUG_RETURN(1); + } + + /* Disable padding temporarily so it doesn't break the query */ + ulonglong sql_mode_was = thd->variables.sql_mode; + thd->variables.sql_mode &= ~MODE_PAD_CHAR_TO_FULL_LENGTH; + + if (proc_table->file->ha_index_init(0, 1)) + { + res= 1; + goto err; + } + + if (lookup.db_value.str) + { + KEY *keyinfo= proc_table->key_info; + uint keylen= keyinfo->key_part[0].length; + key_part_map keypart_map= 1; + enum ha_rkey_function find_flag= HA_READ_PREFIX; + const auto sp_name_len= NAME_LEN * 2 + 1 /*for type*/; + StringBuffer<sp_name_len> keybuf; + keybuf.alloc(proc_table->key_info->key_length); + keybuf.length(proc_table->key_info->key_length); + proc_table->field[0]->store(lookup.db_value.str, lookup.db_value.length, + system_charset_info); + if (lookup.table_value.str) + { + proc_table->field[1]->store(lookup.table_value.str, + lookup.table_value.length, + system_charset_info); + keylen+= keyinfo->key_part[1].length; + keypart_map= 3; + find_flag= HA_READ_KEY_EXACT; + } + key_copy((uchar*)keybuf.ptr(), proc_table->record[0], keyinfo, keylen, 0); + res= proc_table->file->ha_index_read_map(proc_table->record[0], + (const uchar*) keybuf.ptr(), + keypart_map, find_flag); + } + else + res= proc_table->file->ha_index_first(proc_table->record[0]); + + if (res) + goto err; + + res= schema_table_idx == SCH_PROCEDURES ? + store_schema_proc(thd, table, proc_table, &lookup, full_access,definer) : + store_schema_params(thd, table, proc_table, &lookup, full_access, definer); + while (!res && !proc_table->file->ha_index_next(proc_table->record[0])) + { + res= schema_table_idx == SCH_PROCEDURES ? + store_schema_proc(thd, table, proc_table, &lookup, full_access, definer) : + store_schema_params(thd, table, proc_table, &lookup, full_access, definer); + } + +err: + if (proc_table->file->inited) + (void) proc_table->file->ha_index_end(); + + if (res == HA_ERR_END_OF_FILE || res == HA_ERR_KEY_NOT_FOUND) + res= 0; + + thd->commit_whole_transaction_and_close_tables(); + new_trans.restore_old_transaction(); + + thd->variables.sql_mode = sql_mode_was; + DBUG_RETURN(res); +} + + +static int get_schema_stat_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + CHARSET_INFO *cs= system_charset_info; + DBUG_ENTER("get_schema_stat_record"); + if (res) + { + if (thd->lex->sql_command != SQLCOM_SHOW_KEYS) + { + /* + I.e. we are in SELECT FROM INFORMATION_SCHEMA.STATISTICS + rather than in SHOW KEYS + */ + if (unlikely(thd->is_error())) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); + res= 0; + } + DBUG_RETURN(res); + } + else if (!tables->view) + { + TABLE *show_table= tables->table; + KEY *key_info=show_table->s->key_info; + if (show_table->file) + { + (void) read_statistics_for_tables(thd, tables, false); + show_table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK | + HA_STATUS_CONST | HA_STATUS_TIME); + set_statistics_for_table(thd, show_table); + } + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + bool need_column_checks= false; + /* we know that the table or at least some of the columns have + necessary privileges, but the caller didn't pass down the GRANT_INFO + object, so we have to rediscover everything again :( */ + if (!(thd->col_access & TABLE_ACLS)) + { + check_grant(thd, SELECT_ACL, tables, 0, 1, 1); + + if (!(tables->grant.privilege & TABLE_ACLS)) + need_column_checks= true; + } +#endif + + for (uint i=0 ; i < show_table->s->keys ; i++,key_info++) + { + if ((key_info->flags & HA_INVISIBLE_KEY) && + !DBUG_IF("test_invisible_index")) + continue; + KEY_PART_INFO *key_part= key_info->key_part; + LEX_CSTRING *str; + LEX_CSTRING unknown= {STRING_WITH_LEN("?unknown field?") }; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (need_column_checks) + { + uint j; + for (j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++) + { + uint access= get_column_grant(thd, &tables->grant, db_name->str, + table_name->str, + key_part->field->field_name.str); + + if (!access) + break; + } + if (j != key_info->user_defined_key_parts) + continue; + key_part= key_info->key_part; + } +#endif + + for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++) + { + if (key_part->field->invisible >= INVISIBLE_SYSTEM && + !DBUG_IF("test_completely_invisible")) + { + /* + NOTE: we will get SEQ_IN_INDEX gap inside the result if this key_part + is not last (currently not possible). Though nothing is wrong with + that probably. + */ + continue; + } + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[2]->store(table_name->str, table_name->length, cs); + table->field[3]->store((longlong) ((key_info->flags & + HA_NOSAME) ? 0 : 1), TRUE); + table->field[4]->store(db_name->str, db_name->length, cs); + table->field[5]->store(key_info->name.str, key_info->name.length, cs); + table->field[6]->store((longlong) (j+1), TRUE); + str= (key_part->field ? &key_part->field->field_name : + &unknown); + table->field[7]->store(str->str, str->length, cs); + if (show_table->file) + { + if (show_table->file->index_flags(i, j, 0) & HA_READ_ORDER) + { + table->field[8]->store(((key_part->key_part_flag & + HA_REVERSE_SORT) ? + "D" : "A"), 1, cs); + table->field[8]->set_notnull(); + } + if (key_info->algorithm == HA_KEY_ALG_LONG_HASH) + table->field[13]->store(STRING_WITH_LEN("HASH"), cs); + else + { + /* + We have to use table key information to get the key statistics + from table as key_info points to TABLE_SHARE which has no + statistics. + */ + KEY *key_info= show_table->key_info + i; + if (key_info->rec_per_key[j]) + { + ha_rows records= (ha_rows) ((double) show_table->stat_records() / + key_info->actual_rec_per_key(j)); + table->field[9]->store((longlong) records, TRUE); + table->field[9]->set_notnull(); + } + const char *tmp= show_table->file->index_type(i); + table->field[13]->store(tmp, strlen(tmp), cs); + } + } + if (!(key_info->flags & HA_FULLTEXT) && + (key_part->field && + key_part->length != + show_table->s->field[key_part->fieldnr-1]->key_length())) + { + table->field[10]->store((longlong) key_part->length / + key_part->field->charset()->mbmaxlen, TRUE); + table->field[10]->set_notnull(); + } + uint flags= key_part->field ? key_part->field->flags : 0; + const char *pos=(char*) ((flags & NOT_NULL_FLAG) ? "" : "YES"); + table->field[12]->store(pos, strlen(pos), cs); + if (!show_table->s->keys_in_use.is_set(i)) + table->field[14]->store(STRING_WITH_LEN("disabled"), cs); + else + table->field[14]->store("", 0, cs); + table->field[14]->set_notnull(); + DBUG_ASSERT(MY_TEST(key_info->flags & HA_USES_COMMENT) == + (key_info->comment.length > 0)); + if (key_info->flags & HA_USES_COMMENT) + table->field[15]->store(key_info->comment.str, + key_info->comment.length, cs); + + // IGNORED column + const char *is_ignored= key_info->is_ignored ? "YES" : "NO"; + table->field[16]->store(is_ignored, strlen(is_ignored), cs); + table->field[16]->set_notnull(); + + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + } + } + DBUG_RETURN(res); +} + + +static int get_schema_views_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + CHARSET_INFO *cs= system_charset_info; + char definer[USER_HOST_BUFF_SIZE]; + uint definer_len; + bool updatable_view; + DBUG_ENTER("get_schema_views_record"); + + if (tables->view) + { + Security_context *sctx= thd->security_ctx; + if (!tables->allowed_show) + { + if (!my_strcasecmp(system_charset_info, tables->definer.user.str, + sctx->priv_user) && + !my_strcasecmp(system_charset_info, tables->definer.host.str, + sctx->priv_host)) + tables->allowed_show= TRUE; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + else + { + if ((thd->col_access & (SHOW_VIEW_ACL|SELECT_ACL)) == + (SHOW_VIEW_ACL|SELECT_ACL)) + tables->allowed_show= TRUE; + else + { + TABLE_LIST table_list; + table_list.reset(); + table_list.db= tables->db; + table_list.table_name= tables->table_name; + table_list.grant.privilege= thd->col_access; + privilege_t view_access(get_table_grant(thd, &table_list)); + if ((view_access & (SHOW_VIEW_ACL|SELECT_ACL)) == + (SHOW_VIEW_ACL|SELECT_ACL)) + tables->allowed_show= TRUE; + } + } +#endif + } + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[2]->store(table_name->str, table_name->length, cs); + + if (tables->allowed_show) + { + table->field[3]->store(tables->view_body_utf8.str, + tables->view_body_utf8.length, + cs); + } + + if (tables->with_check != VIEW_CHECK_NONE) + { + if (tables->with_check == VIEW_CHECK_LOCAL) + table->field[4]->store(STRING_WITH_LEN("LOCAL"), cs); + else + table->field[4]->store(STRING_WITH_LEN("CASCADED"), cs); + } + else + table->field[4]->store(STRING_WITH_LEN("NONE"), cs); + + /* + Only try to fill in the information about view updatability + if it is requested as part of the top-level query (i.e. + it's select * from i_s.views, as opposed to, say, select + security_type from i_s.views). Do not try to access the + underlying tables if there was an error when opening the + view: all underlying tables are released back to the table + definition cache on error inside open_normal_and_derived_tables(). + If a field is not assigned explicitly, it defaults to NULL. + */ + if (res == FALSE && + table->pos_in_table_list->table_open_method & OPEN_FULL_TABLE) + { + updatable_view= 0; + if (tables->algorithm != VIEW_ALGORITHM_TMPTABLE) + { + /* + We should use tables->view->select_lex.item_list here + and can not use Field_iterator_view because the view + always uses temporary algorithm during opening for I_S + and TABLE_LIST fields 'field_translation' + & 'field_translation_end' are uninitialized is this + case. + */ + List<Item> *fields= &tables->view->first_select_lex()->item_list; + List_iterator<Item> it(*fields); + Item *item; + Item_field *field; + /* + check that at least one column in view is updatable + */ + while ((item= it++)) + { + if ((field= item->field_for_view_update()) && field->field && + !field->field->table->pos_in_table_list->schema_table) + { + updatable_view= 1; + break; + } + } + if (updatable_view && !tables->view->can_be_merged()) + updatable_view= 0; + } + if (updatable_view) + table->field[5]->store(STRING_WITH_LEN("YES"), cs); + else + table->field[5]->store(STRING_WITH_LEN("NO"), cs); + } + + definer_len= (uint)(strxmov(definer, tables->definer.user.str, "@", + tables->definer.host.str, NullS) - definer); + table->field[6]->store(definer, definer_len, cs); + if (tables->view_suid) + table->field[7]->store(STRING_WITH_LEN("DEFINER"), cs); + else + table->field[7]->store(STRING_WITH_LEN("INVOKER"), cs); + + table->field[8]->store(&tables->view_creation_ctx->get_client_cs()->cs_name, + cs); + table->field[9]->store(&tables->view_creation_ctx-> + get_connection_cl()->coll_name, cs); + table->field[10]->store(view_algorithm(tables), cs); + + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + if (unlikely(res && thd->is_error())) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + } + if (res) + thd->clear_error(); + DBUG_RETURN(0); +} + + +static bool +store_constraints(THD *thd, TABLE *table, const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name, const char *key_name, + size_t key_len, const char *con_type, size_t con_len) +{ + CHARSET_INFO *cs= system_charset_info; + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[2]->store(key_name, key_len, cs); + table->field[3]->store(db_name->str, db_name->length, cs); + table->field[4]->store(table_name->str, table_name->length, cs); + table->field[5]->store(con_type, con_len, cs); + return schema_table_store_record(thd, table); +} + +static int get_check_constraints_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + DBUG_ENTER("get_check_constraints_record"); + if (res) + { + if (thd->is_error()) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); + DBUG_RETURN(0); + } + if (!tables->view) + { + StringBuffer<MAX_FIELD_WIDTH> str(system_charset_info); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + TABLE_LIST table_acl_check; + bzero((char*) &table_acl_check, sizeof(table_acl_check)); + + if (!(thd->col_access & TABLE_ACLS)) + { + table_acl_check.db= *db_name; + table_acl_check.table_name= *table_name; + table_acl_check.grant.privilege= thd->col_access; + if (check_grant(thd, TABLE_ACLS, &table_acl_check, FALSE, 1, TRUE)) + DBUG_RETURN(res); + } +#endif + for (uint i= 0; i < tables->table->s->table_check_constraints; i++) + { + Virtual_column_info *check= tables->table->check_constraints[i]; + table->field[0]->store(STRING_WITH_LEN("def"), system_charset_info); + table->field[3]->store(check->name.str, check->name.length, + system_charset_info); + const char *tmp_buff; + tmp_buff= (check->get_vcol_type() == VCOL_CHECK_FIELD ? + "Column" : "Table"); + table->field[4]->store(tmp_buff, strlen(tmp_buff), system_charset_info); + /* Make sure the string is empty between each print. */ + str.length(0); + check->print(&str); + table->field[5]->store(str.ptr(), str.length(), system_charset_info); + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + } + DBUG_RETURN(res); +} + +static int get_schema_constraints_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + DBUG_ENTER("get_schema_constraints_record"); + if (res) + { + if (unlikely(thd->is_error())) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); + DBUG_RETURN(0); + } + else if (!tables->view) + { +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* need any non-SELECT privilege on the table or any of its columns */ + const privilege_t need= TABLE_ACLS & ~SELECT_ACL; + if (!(thd->col_access & need)) + { + /* we know that the table or at least some of the columns have + necessary privileges, but the caller didn't pass down the GRANT_INFO + object, so we have to rediscover everything again :( */ + check_grant(thd, SELECT_ACL, tables, 0, 1, 1); + + if (!(tables->grant.all_privilege() & need)) + DBUG_RETURN(0); + } +#endif + + List<FOREIGN_KEY_INFO> f_key_list; + TABLE *show_table= tables->table; + KEY *key_info=show_table->s->key_info; + uint primary_key= show_table->s->primary_key; + show_table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK | + HA_STATUS_TIME); + for (uint i=0 ; i < show_table->s->keys ; i++, key_info++) + { + if (i == primary_key && !strcmp(key_info->name.str, + primary_key_name.str)) + { + if (store_constraints(thd, table, db_name, table_name, + key_info->name.str, key_info->name.length, + STRING_WITH_LEN("PRIMARY KEY"))) + DBUG_RETURN(1); + } + else if (key_info->flags & HA_NOSAME) + { + if (store_constraints(thd, table, db_name, table_name, + key_info->name.str, key_info->name.length, + STRING_WITH_LEN("UNIQUE"))) + DBUG_RETURN(1); + } + } + + // Table check constraints + for (uint i = 0; i < show_table->s->table_check_constraints; i++) + { + Virtual_column_info *check = show_table->check_constraints[i]; + + if (store_constraints(thd, table, db_name, table_name, + check->name.str, check->name.length, + STRING_WITH_LEN("CHECK"))) + DBUG_RETURN(1); + } + + show_table->file->get_foreign_key_list(thd, &f_key_list); + FOREIGN_KEY_INFO *f_key_info; + List_iterator_fast<FOREIGN_KEY_INFO> it(f_key_list); + while ((f_key_info=it++)) + { + if (store_constraints(thd, table, db_name, table_name, + f_key_info->foreign_id->str, + strlen(f_key_info->foreign_id->str), + STRING_WITH_LEN("FOREIGN KEY"))) + DBUG_RETURN(1); + } + } + DBUG_RETURN(res); +} + + +static bool store_trigger(THD *thd, Trigger *trigger, + TABLE *table, const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + CHARSET_INFO *cs= system_charset_info; + LEX_CSTRING sql_mode_rep; + MYSQL_TIME timestamp; + char definer_holder[USER_HOST_BUFF_SIZE]; + LEX_STRING definer_buffer; + LEX_CSTRING trigger_stmt, trigger_body; + definer_buffer.str= definer_holder; + + trigger->get_trigger_info(&trigger_stmt, &trigger_body, &definer_buffer); + + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[2]->store(trigger->name.str, trigger->name.length, cs); + table->field[3]->store(trg_event_type_names[trigger->event].str, + trg_event_type_names[trigger->event].length, cs); + table->field[4]->store(STRING_WITH_LEN("def"), cs); + table->field[5]->store(db_name->str, db_name->length, cs); + table->field[6]->store(table_name->str, table_name->length, cs); + table->field[7]->store(trigger->action_order); + table->field[9]->store(trigger_body.str, trigger_body.length, cs); + table->field[10]->store(STRING_WITH_LEN("ROW"), cs); + table->field[11]->store(trg_action_time_type_names[trigger->action_time].str, + trg_action_time_type_names[trigger->action_time].length, cs); + table->field[14]->store(STRING_WITH_LEN("OLD"), cs); + table->field[15]->store(STRING_WITH_LEN("NEW"), cs); + + if (trigger->hr_create_time.val) + { + /* timestamp is in microseconds */ + table->field[16]->set_notnull(); + thd->variables.time_zone-> + gmt_sec_to_TIME(×tamp, + (my_time_t) hrtime_to_time(trigger->hr_create_time)); + timestamp.second_part= hrtime_sec_part(trigger->hr_create_time); + table->field[16]->store_time_dec(×tamp, 2); + } + + sql_mode_string_representation(thd, trigger->sql_mode, &sql_mode_rep); + table->field[17]->store(sql_mode_rep.str, sql_mode_rep.length, cs); + table->field[18]->store(definer_buffer.str, definer_buffer.length, cs); + table->field[19]->store(&trigger->client_cs_name, cs); + table->field[20]->store(&trigger->connection_cl_name, cs); + table->field[21]->store(&trigger->db_cl_name, cs); + + return schema_table_store_record(thd, table); +} + + +static int get_schema_triggers_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + DBUG_ENTER("get_schema_triggers_record"); + /* + res can be non zero value when processed table is a view or + error happened during opening of processed table. + */ + if (res) + { + if (unlikely(thd->is_error())) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); + DBUG_RETURN(0); + } + if (!tables->view && tables->table->triggers) + { + Table_triggers_list *triggers= tables->table->triggers; + int event, timing; + + if (check_table_access(thd, TRIGGER_ACL, tables, FALSE, 1, TRUE)) + goto ret; + + for (event= 0; event < (int)TRG_EVENT_MAX; event++) + { + for (timing= 0; timing < (int)TRG_ACTION_MAX; timing++) + { + Trigger *trigger; + for (trigger= triggers-> + get_trigger((enum trg_event_type) event, + (enum trg_action_time_type) timing) ; + trigger; + trigger= trigger->next) + { + if (store_trigger(thd, trigger, table, db_name, table_name)) + DBUG_RETURN(1); + } + } + } + } +ret: + DBUG_RETURN(0); +} + + +static void +store_key_column_usage(TABLE *table, const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name, const char *key_name, + size_t key_len, const char *con_type, size_t con_len, + longlong idx) +{ + CHARSET_INFO *cs= system_charset_info; + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[2]->store(key_name, key_len, cs); + table->field[3]->store(STRING_WITH_LEN("def"), cs); + table->field[4]->store(db_name->str, db_name->length, cs); + table->field[5]->store(table_name->str, table_name->length, cs); + table->field[6]->store(con_type, con_len, cs); + table->field[7]->store((longlong) idx, TRUE); +} + + +static int get_schema_key_column_usage_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + DBUG_ENTER("get_schema_key_column_usage_record"); + if (res) + { + if (unlikely(thd->is_error())) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); + DBUG_RETURN(0); + } + else if (!tables->view) + { + List<FOREIGN_KEY_INFO> f_key_list; + TABLE *show_table= tables->table; + KEY *key_info=show_table->s->key_info; + uint primary_key= show_table->s->primary_key; + show_table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK | + HA_STATUS_TIME); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + bool need_column_checks= false; + /* we know that the table or at least some of the columns have + necessary privileges, but the caller didn't pass down the GRANT_INFO + object, so we have to rediscover everything again :( */ + if (!(thd->col_access & TABLE_ACLS)) + { + check_grant(thd, SELECT_ACL, tables, 0, 1, 1); + + if (!(tables->grant.privilege & TABLE_ACLS)) + need_column_checks= true; + } +#endif + + for (uint i=0 ; i < show_table->s->keys ; i++, key_info++) + { + if (i != primary_key && !(key_info->flags & HA_NOSAME)) + continue; + uint f_idx= 0; + KEY_PART_INFO *key_part= key_info->key_part; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (need_column_checks) + { + uint j; + for (j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++) + { + uint access= get_column_grant(thd, &tables->grant, db_name->str, + table_name->str, + key_part->field->field_name.str); + + if (!access) + break; + } + if (j != key_info->user_defined_key_parts) + continue; + key_part= key_info->key_part; + } +#endif + + for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++) + { + f_idx++; + restore_record(table, s->default_values); + store_key_column_usage(table, db_name, table_name, + key_info->name.str, key_info->name.length, + key_part->field->field_name.str, + key_part->field->field_name.length, + (longlong) f_idx); + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + } + + show_table->file->get_foreign_key_list(thd, &f_key_list); + FOREIGN_KEY_INFO *f_key_info; + List_iterator_fast<FOREIGN_KEY_INFO> fkey_it(f_key_list); + while ((f_key_info= fkey_it++)) + { + LEX_CSTRING *f_info; + LEX_CSTRING *r_info; + List_iterator_fast<LEX_CSTRING> it(f_key_info->foreign_fields), + it1(f_key_info->referenced_fields); + uint f_idx= 0; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (need_column_checks) + { + while ((r_info= it1++)) + { + uint access= get_column_grant(thd, &tables->grant, db_name->str, + table_name->str, r_info->str); + + if (!access) + break; + } + if (!it1.at_end()) + continue; + it1.rewind(); + } +#endif + while ((f_info= it++)) + { + r_info= it1++; + f_idx++; + restore_record(table, s->default_values); + store_key_column_usage(table, db_name, table_name, + f_key_info->foreign_id->str, + f_key_info->foreign_id->length, + f_info->str, f_info->length, + (longlong) f_idx); + table->field[8]->store((longlong) f_idx, TRUE); + table->field[8]->set_notnull(); + table->field[9]->store(f_key_info->referenced_db->str, + f_key_info->referenced_db->length, + system_charset_info); + table->field[9]->set_notnull(); + table->field[10]->store(f_key_info->referenced_table->str, + f_key_info->referenced_table->length, + system_charset_info); + table->field[10]->set_notnull(); + table->field[11]->store(r_info->str, r_info->length, + system_charset_info); + table->field[11]->set_notnull(); + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + } + } + DBUG_RETURN(res); +} + + +#ifdef WITH_PARTITION_STORAGE_ENGINE +static void collect_partition_expr(THD *thd, List<const char> &field_list, + String *str) +{ + List_iterator<const char> part_it(field_list); + ulong no_fields= field_list.elements; + const char *field_str; + str->length(0); + while ((field_str= part_it++)) + { + append_identifier(thd, str, field_str, strlen(field_str)); + if (--no_fields != 0) + str->append(','); + } + return; +} + +#endif + + +static void store_schema_partitions_record(THD *thd, TABLE *schema_table, + TABLE *showing_table, + partition_element *part_elem, + handler *file, uint part_id) +{ + TABLE* table= schema_table; + CHARSET_INFO *cs= system_charset_info; + PARTITION_STATS stat_info; + MYSQL_TIME time; + file->get_dynamic_partition_info(&stat_info, part_id); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[12]->store((longlong) stat_info.records, TRUE); + table->field[13]->store((longlong) stat_info.mean_rec_length, TRUE); + table->field[14]->store((longlong) stat_info.data_file_length, TRUE); + if (stat_info.max_data_file_length) + { + table->field[15]->store((longlong) stat_info.max_data_file_length, TRUE); + table->field[15]->set_notnull(); + } + table->field[16]->store((longlong) stat_info.index_file_length, TRUE); + table->field[17]->store((longlong) stat_info.delete_length, TRUE); + if (stat_info.create_time) + { + thd->variables.time_zone->gmt_sec_to_TIME(&time, + (my_time_t)stat_info.create_time); + table->field[18]->store_time(&time); + table->field[18]->set_notnull(); + } + if (stat_info.update_time) + { + thd->variables.time_zone->gmt_sec_to_TIME(&time, + (my_time_t)stat_info.update_time); + table->field[19]->store_time(&time); + table->field[19]->set_notnull(); + } + if (stat_info.check_time) + { + thd->variables.time_zone->gmt_sec_to_TIME(&time, + (my_time_t)stat_info.check_time); + table->field[20]->store_time(&time); + table->field[20]->set_notnull(); + } + if (file->ha_table_flags() & (HA_HAS_OLD_CHECKSUM | HA_HAS_NEW_CHECKSUM)) + { + table->field[21]->store((longlong) stat_info.check_sum, TRUE); + table->field[21]->set_notnull(); + } + if (part_elem) + { + if (part_elem->part_comment) + table->field[22]->store(part_elem->part_comment, + strlen(part_elem->part_comment), cs); + else + table->field[22]->store(STRING_WITH_LEN(""), cs); + if (part_elem->nodegroup_id != UNDEF_NODEGROUP) + table->field[23]->store((longlong) part_elem->nodegroup_id, TRUE); + else + table->field[23]->store(STRING_WITH_LEN("default"), cs); + + table->field[24]->set_notnull(); + table->field[24]->set_null(); // Tablespace + } + return; +} + +#ifdef WITH_PARTITION_STORAGE_ENGINE +static int get_partition_column_description(THD *thd, partition_info *part_info, + part_elem_value *list_value, String &tmp_str) +{ + uint num_elements= part_info->part_field_list.elements; + uint i; + DBUG_ENTER("get_partition_column_description"); + + for (i= 0; i < num_elements; i++) + { + part_column_list_val *col_val= &list_value->col_val_array[i]; + if (col_val->max_value) + tmp_str.append(STRING_WITH_LEN("MAXVALUE")); + else if (col_val->null_value) + tmp_str.append(NULL_clex_str); + else + { + Item *item= col_val->item_expression; + StringBuffer<MAX_KEY_LENGTH> val; + const Field *field= part_info->part_field_array[i]; + const Type_handler *th= field->type_handler(); + th->partition_field_append_value(&val, item, + field->charset(), + PARTITION_VALUE_PRINT_MODE_SHOW); + tmp_str.append(val); + } + if (i != num_elements - 1) + tmp_str.append(','); + } + DBUG_RETURN(0); +} +#endif /* WITH_PARTITION_STORAGE_ENGINE */ + +static int get_schema_partitions_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + CHARSET_INFO *cs= system_charset_info; + char buff[61]; + String tmp_res(buff, sizeof(buff), cs); + String tmp_str; + TABLE *show_table= tables->table; + handler *file; +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info; +#endif + DBUG_ENTER("get_schema_partitions_record"); + + if (res) + { + if (unlikely(thd->is_error())) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); + DBUG_RETURN(0); + } + file= show_table->file; +#ifdef WITH_PARTITION_STORAGE_ENGINE + part_info= show_table->part_info; + if (part_info) + { + partition_element *part_elem; + List_iterator<partition_element> part_it(part_info->partitions); + uint part_pos= 0, part_id= 0; + + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[2]->store(table_name->str, table_name->length, cs); + + + /* Partition method*/ + switch (part_info->part_type) { + case RANGE_PARTITION: + case LIST_PARTITION: + tmp_res.length(0); + if (part_info->part_type == RANGE_PARTITION) + tmp_res.append(STRING_WITH_LEN("RANGE")); + else + tmp_res.append(STRING_WITH_LEN("LIST")); + if (part_info->column_list) + tmp_res.append(STRING_WITH_LEN(" COLUMNS")); + table->field[7]->store(tmp_res.ptr(), tmp_res.length(), cs); + break; + case HASH_PARTITION: + tmp_res.length(0); + if (part_info->linear_hash_ind) + tmp_res.append(STRING_WITH_LEN("LINEAR ")); + if (part_info->list_of_part_fields) + tmp_res.append(STRING_WITH_LEN("KEY")); + else + tmp_res.append(STRING_WITH_LEN("HASH")); + table->field[7]->store(tmp_res.ptr(), tmp_res.length(), cs); + break; + case VERSIONING_PARTITION: + table->field[7]->store(STRING_WITH_LEN("SYSTEM_TIME"), cs); + break; + default: + DBUG_ASSERT(0); + my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATAL)); + DBUG_RETURN(1); + } + table->field[7]->set_notnull(); + + /* Partition expression */ + if (part_info->part_expr) + { + StringBuffer<STRING_BUFFER_USUAL_SIZE> str(cs); + part_info->part_expr->print_for_table_def(&str); + table->field[9]->store(str.ptr(), str.length(), str.charset()); + } + else if (part_info->list_of_part_fields) + { + collect_partition_expr(thd, part_info->part_field_list, &tmp_str); + table->field[9]->store(tmp_str.ptr(), tmp_str.length(), cs); + } + table->field[9]->set_notnull(); + + if (part_info->is_sub_partitioned()) + { + /* Subpartition method */ + tmp_res.length(0); + if (part_info->linear_hash_ind) + tmp_res.append(STRING_WITH_LEN("LINEAR ")); + if (part_info->list_of_subpart_fields) + tmp_res.append(STRING_WITH_LEN("KEY")); + else + tmp_res.append(STRING_WITH_LEN("HASH")); + table->field[8]->store(tmp_res.ptr(), tmp_res.length(), cs); + table->field[8]->set_notnull(); + + /* Subpartition expression */ + if (part_info->subpart_expr) + { + StringBuffer<STRING_BUFFER_USUAL_SIZE> str(cs); + part_info->subpart_expr->print_for_table_def(&str); + table->field[10]->store(str.ptr(), str.length(), str.charset()); + } + else if (part_info->list_of_subpart_fields) + { + collect_partition_expr(thd, part_info->subpart_field_list, &tmp_str); + table->field[10]->store(tmp_str.ptr(), tmp_str.length(), cs); + } + table->field[10]->set_notnull(); + } + + while ((part_elem= part_it++)) + { + table->field[3]->store(part_elem->partition_name, + strlen(part_elem->partition_name), cs); + table->field[3]->set_notnull(); + /* PARTITION_ORDINAL_POSITION */ + table->field[5]->store((longlong) ++part_pos, TRUE); + table->field[5]->set_notnull(); + + /* Partition description */ + if (part_info->part_type == RANGE_PARTITION) + { + if (part_info->column_list) + { + List_iterator<part_elem_value> list_val_it(part_elem->list_val_list); + part_elem_value *list_value= list_val_it++; + tmp_str.length(0); + if (get_partition_column_description(thd, part_info, list_value, + tmp_str)) + DBUG_RETURN(1); + table->field[11]->store(tmp_str.ptr(), tmp_str.length(), cs); + } + else + { + if (part_elem->range_value != LONGLONG_MAX) + table->field[11]->store((longlong) part_elem->range_value, FALSE); + else + table->field[11]->store(STRING_WITH_LEN("MAXVALUE"), cs); + } + table->field[11]->set_notnull(); + } + else if (part_info->part_type == LIST_PARTITION) + { + List_iterator<part_elem_value> list_val_it(part_elem->list_val_list); + part_elem_value *list_value; + uint num_items= part_elem->list_val_list.elements; + tmp_str.length(0); + tmp_res.length(0); + if (part_elem->has_null_value) + { + tmp_str.append(NULL_clex_str); + if (num_items > 0) + tmp_str.append(','); + } + while ((list_value= list_val_it++)) + { + if (part_info->column_list) + { + if (part_info->part_field_list.elements > 1U) + tmp_str.append(STRING_WITH_LEN("(")); + if (get_partition_column_description(thd, part_info, list_value, + tmp_str)) + DBUG_RETURN(1); + if (part_info->part_field_list.elements > 1U) + tmp_str.append(')'); + } + else + { + if (!list_value->unsigned_flag) + tmp_res.set(list_value->value, cs); + else + tmp_res.set((ulonglong)list_value->value, cs); + tmp_str.append(tmp_res); + } + if (--num_items != 0) + tmp_str.append(','); + } + table->field[11]->store(tmp_str.ptr(), tmp_str.length(), cs); + table->field[11]->set_notnull(); + } + else if (part_info->part_type == VERSIONING_PARTITION) + { + if (part_elem == part_info->vers_info->now_part) + { + table->field[11]->store(STRING_WITH_LEN("CURRENT"), cs); + table->field[11]->set_notnull(); + } + else if (part_info->vers_info->interval.is_set()) + { + Timeval tv((my_time_t) part_elem->range_value, 0); + table->field[11]->store_timestamp_dec(tv, AUTO_SEC_PART_DIGITS); + table->field[11]->set_notnull(); + } + } + + if (part_elem->subpartitions.elements) + { + List_iterator<partition_element> sub_it(part_elem->subpartitions); + partition_element *subpart_elem; + uint subpart_pos= 0; + + while ((subpart_elem= sub_it++)) + { + table->field[4]->store(subpart_elem->partition_name, + strlen(subpart_elem->partition_name), cs); + table->field[4]->set_notnull(); + /* SUBPARTITION_ORDINAL_POSITION */ + table->field[6]->store((longlong) ++subpart_pos, TRUE); + table->field[6]->set_notnull(); + + store_schema_partitions_record(thd, table, show_table, subpart_elem, + file, part_id); + part_id++; + if(schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + } + else + { + store_schema_partitions_record(thd, table, show_table, part_elem, + file, part_id); + part_id++; + if(schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); + } + else +#endif + { + store_schema_partitions_record(thd, table, show_table, 0, file, 0); + if(schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +#ifdef HAVE_EVENT_SCHEDULER +/* + Loads an event from mysql.event and copies it's data to a row of + I_S.EVENTS + + Synopsis + copy_event_to_schema_table() + thd Thread + sch_table The schema table (information_schema.event) + event_table The event table to use for loading (mysql.event). + + Returns + 0 OK + 1 Error +*/ + +int +copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) +{ + const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; + CHARSET_INFO *scs= system_charset_info; + MYSQL_TIME time; + Event_timed et; + DBUG_ENTER("copy_event_to_schema_table"); + + restore_record(sch_table, s->default_values); + + if (et.load_from_row(thd, event_table)) + { + my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), "mysql", "event"); + DBUG_RETURN(1); + } + + if (!(!wild || !wild[0] || !wild_case_compare(scs, et.name.str, wild))) + DBUG_RETURN(0); + + /* + Skip events in schemas one does not have access to. The check is + optimized. It's guaranteed in case of SHOW EVENTS that the user + has access. + */ + if (thd->lex->sql_command != SQLCOM_SHOW_EVENTS && + check_access(thd, EVENT_ACL, et.dbname.str, NULL, NULL, 0, 1)) + DBUG_RETURN(0); + + sch_table->field[ISE_EVENT_CATALOG]->store(STRING_WITH_LEN("def"), scs); + sch_table->field[ISE_EVENT_SCHEMA]-> + store(et.dbname.str, et.dbname.length,scs); + sch_table->field[ISE_EVENT_NAME]-> + store(et.name.str, et.name.length, scs); + sch_table->field[ISE_DEFINER]-> + store(et.definer.str, et.definer.length, scs); + const String *tz_name= et.time_zone->get_name(); + sch_table->field[ISE_TIME_ZONE]-> + store(tz_name->ptr(), tz_name->length(), scs); + sch_table->field[ISE_EVENT_BODY]-> + store(STRING_WITH_LEN("SQL"), scs); + sch_table->field[ISE_EVENT_DEFINITION]->store( + et.body_utf8.str, et.body_utf8.length, scs); + + /* SQL_MODE */ + { + LEX_CSTRING sql_mode; + sql_mode_string_representation(thd, et.sql_mode, &sql_mode); + sch_table->field[ISE_SQL_MODE]-> + store(sql_mode.str, sql_mode.length, scs); + } + + int not_used=0; + + if (et.expression) + { + String show_str; + /* type */ + sch_table->field[ISE_EVENT_TYPE]->store(STRING_WITH_LEN("RECURRING"), scs); + + if (Events::reconstruct_interval_expression(&show_str, et.interval, + et.expression)) + DBUG_RETURN(1); + + sch_table->field[ISE_INTERVAL_VALUE]->set_notnull(); + sch_table->field[ISE_INTERVAL_VALUE]-> + store(show_str.ptr(), show_str.length(), scs); + + LEX_CSTRING *ival= &interval_type_to_name[et.interval]; + sch_table->field[ISE_INTERVAL_FIELD]->set_notnull(); + sch_table->field[ISE_INTERVAL_FIELD]->store(ival->str, ival->length, scs); + + /* starts & ends . STARTS is always set - see sql_yacc.yy */ + et.time_zone->gmt_sec_to_TIME(&time, et.starts); + sch_table->field[ISE_STARTS]->set_notnull(); + sch_table->field[ISE_STARTS]->store_time(&time); + + if (!et.ends_null) + { + et.time_zone->gmt_sec_to_TIME(&time, et.ends); + sch_table->field[ISE_ENDS]->set_notnull(); + sch_table->field[ISE_ENDS]->store_time(&time); + } + } + else + { + /* type */ + sch_table->field[ISE_EVENT_TYPE]->store(STRING_WITH_LEN("ONE TIME"), scs); + + et.time_zone->gmt_sec_to_TIME(&time, et.execute_at); + sch_table->field[ISE_EXECUTE_AT]->set_notnull(); + sch_table->field[ISE_EXECUTE_AT]->store_time(&time); + } + + /* status */ + + switch (et.status) + { + case Event_parse_data::ENABLED: + sch_table->field[ISE_STATUS]->store(STRING_WITH_LEN("ENABLED"), scs); + break; + case Event_parse_data::SLAVESIDE_DISABLED: + sch_table->field[ISE_STATUS]->store(STRING_WITH_LEN("SLAVESIDE_DISABLED"), + scs); + break; + case Event_parse_data::DISABLED: + sch_table->field[ISE_STATUS]->store(STRING_WITH_LEN("DISABLED"), scs); + break; + default: + DBUG_ASSERT(0); + } + sch_table->field[ISE_ORIGINATOR]->store(et.originator, TRUE); + + /* on_completion */ + if (et.on_completion == Event_parse_data::ON_COMPLETION_DROP) + sch_table->field[ISE_ON_COMPLETION]-> + store(STRING_WITH_LEN("NOT PRESERVE"), scs); + else + sch_table->field[ISE_ON_COMPLETION]-> + store(STRING_WITH_LEN("PRESERVE"), scs); + + number_to_datetime_or_date(et.created, 0, &time, 0, ¬_used); + DBUG_ASSERT(not_used==0); + sch_table->field[ISE_CREATED]->store_time(&time); + + number_to_datetime_or_date(et.modified, 0, &time, 0, ¬_used); + DBUG_ASSERT(not_used==0); + sch_table->field[ISE_LAST_ALTERED]->store_time(&time); + + if (et.last_executed) + { + et.time_zone->gmt_sec_to_TIME(&time, et.last_executed); + sch_table->field[ISE_LAST_EXECUTED]->set_notnull(); + sch_table->field[ISE_LAST_EXECUTED]->store_time(&time); + } + + sch_table->field[ISE_EVENT_COMMENT]-> + store(et.comment.str, et.comment.length, scs); + + sch_table->field[ISE_CLIENT_CS]->set_notnull(); + sch_table->field[ISE_CLIENT_CS]->store(&et.creation_ctx->get_client_cs()-> + cs_name, scs); + sch_table->field[ISE_CONNECTION_CL]->set_notnull(); + sch_table->field[ISE_CONNECTION_CL]->store(&et.creation_ctx-> + get_connection_cl()->coll_name, + scs); + sch_table->field[ISE_DB_CL]->set_notnull(); + sch_table->field[ISE_DB_CL]->store(&et.creation_ctx->get_db_cl()->coll_name, + scs); + + if (schema_table_store_record(thd, sch_table)) + DBUG_RETURN(1); + + DBUG_RETURN(0); +} +#endif + +int fill_open_tables(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_open_tables"); + const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; + TABLE *table= tables->table; + CHARSET_INFO *cs= system_charset_info; + OPEN_TABLE_LIST *open_list; + if (!(open_list= list_open_tables(thd, thd->lex->first_select_lex()->db.str, + wild)) + && thd->is_fatal_error) + DBUG_RETURN(1); + + for (; open_list ; open_list=open_list->next) + { + restore_record(table, s->default_values); + table->field[0]->store(open_list->db, strlen(open_list->db), cs); + table->field[1]->store(open_list->table, strlen(open_list->table), cs); + table->field[2]->store((longlong) open_list->in_use, TRUE); + table->field[3]->store((longlong) open_list->locked, TRUE); + if (unlikely(schema_table_store_record(thd, table))) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_variables"); + int res= 0; + LEX *lex= thd->lex; + const char *wild= lex->wild ? lex->wild->ptr() : NullS; + enum enum_schema_tables schema_table_idx= + get_schema_table_idx(tables->schema_table); + enum enum_var_type scope= OPT_SESSION; + bool upper_case_names= lex->sql_command != SQLCOM_SHOW_VARIABLES; + bool sorted_vars= lex->sql_command == SQLCOM_SHOW_VARIABLES; + + if ((sorted_vars && lex->option_type == OPT_GLOBAL) || + schema_table_idx == SCH_GLOBAL_VARIABLES) + scope= OPT_GLOBAL; + + COND *partial_cond= make_cond_for_info_schema(thd, cond, tables); + + mysql_prlock_rdlock(&LOCK_system_variables_hash); + + /* + Avoid recursive LOCK_system_variables_hash acquisition in + intern_sys_var_ptr() by pre-syncing dynamic session variables. + */ + if (scope == OPT_SESSION && + (!thd->variables.dynamic_variables_ptr || + global_system_variables.dynamic_variables_head > + thd->variables.dynamic_variables_head)) + sync_dynamic_session_variables(thd, true); + + res= show_status_array(thd, wild, enumerate_sys_vars(thd, sorted_vars, scope), + scope, NULL, "", tables->table, + upper_case_names, partial_cond); + mysql_prlock_unlock(&LOCK_system_variables_hash); + DBUG_RETURN(res); +} + +int add_symbol_to_table(const char* name, TABLE* table){ + DBUG_ENTER("add_symbol_to_table"); + + size_t length= strlen(name); + + // If you've added a new SQL keyword longer than KEYWORD_SIZE, + // please increase the defined max length + DBUG_ASSERT(length < KEYWORD_SIZE); + + restore_record(table, s->default_values); + table->field[0]->set_notnull(); + table->field[0]->store(name, length, + system_charset_info); + if (schema_table_store_record(table->in_use, table)) + DBUG_RETURN(1); + + DBUG_RETURN(0); +} + +int fill_i_s_keywords(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_i_s_keywords"); + + TABLE *table= tables->table; + + for (uint i= 0; i < symbols_length; i++){ + const char *name= symbols[i].name; + if (add_symbol_to_table(name, table)) + DBUG_RETURN(1); + } + + DBUG_RETURN(0); +} + +int fill_i_s_sql_functions(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_i_s_sql_functions"); + + TABLE *table= tables->table; + + for (uint i= 0; i < sql_functions_length; i++) + if (add_symbol_to_table(sql_functions[i].name, table)) + DBUG_RETURN(1); + + for (uint i= 0; i < native_func_registry_array.count(); i++) + if (add_symbol_to_table(native_func_registry_array.element(i).name.str, + table)) + DBUG_RETURN(1); + + DBUG_RETURN(0); +} + + +int fill_status(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_status"); + LEX *lex= thd->lex; + const char *wild= lex->wild ? lex->wild->ptr() : NullS; + int res= 0; + STATUS_VAR *tmp1, tmp; + enum enum_schema_tables schema_table_idx= + get_schema_table_idx(tables->schema_table); + enum enum_var_type scope; + bool upper_case_names= lex->sql_command != SQLCOM_SHOW_STATUS; + + if (lex->sql_command == SQLCOM_SHOW_STATUS) + { + scope= lex->option_type; + if (scope == OPT_GLOBAL) + tmp1= &tmp; + else + tmp1= thd->initial_status_var; + } + else if (schema_table_idx == SCH_GLOBAL_STATUS) + { + scope= OPT_GLOBAL; + tmp1= &tmp; + } + else + { + scope= OPT_SESSION; + tmp1= &thd->status_var; + } + + COND *partial_cond= make_cond_for_info_schema(thd, cond, tables); + // Evaluate and cache const subqueries now, before the mutex. + if (partial_cond) + partial_cond->val_int(); + + tmp.local_memory_used= 0; // meaning tmp was not populated yet + + mysql_rwlock_rdlock(&LOCK_all_status_vars); + res= show_status_array(thd, wild, + (SHOW_VAR *)all_status_vars.buffer, + scope, tmp1, "", tables->table, + upper_case_names, partial_cond); + mysql_rwlock_unlock(&LOCK_all_status_vars); + DBUG_RETURN(res); +} + + +/* + Fill and store records into I_S.referential_constraints table + + SYNOPSIS + get_referential_constraints_record() + thd thread handle + tables table list struct(processed table) + table I_S table + res 1 means the error during opening of the processed table + 0 means processed table is opened without error + base_name db name + file_name table name + + RETURN + 0 ok + # error +*/ + +static int +get_referential_constraints_record(THD *thd, TABLE_LIST *tables, + TABLE *table, bool res, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name) +{ + CHARSET_INFO *cs= system_charset_info; + LEX_CSTRING *s; + DBUG_ENTER("get_referential_constraints_record"); + + if (res) + { + if (unlikely(thd->is_error())) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->message()); + thd->clear_error(); + DBUG_RETURN(0); + } + if (!tables->view) + { + List<FOREIGN_KEY_INFO> f_key_list; + TABLE *show_table= tables->table; + show_table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK | + HA_STATUS_TIME); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* need any non-SELECT privilege on the table or any of its columns */ + const privilege_t need= TABLE_ACLS & ~SELECT_ACL; + if (!(thd->col_access & need)) + { + /* we know that the table or at least some of the columns have + necessary privileges, but the caller didn't pass down the GRANT_INFO + object, so we have to rediscover everything again :( */ + check_grant(thd, SELECT_ACL, tables, 0, 1, 1); + + if (!(tables->grant.all_privilege() & need)) + DBUG_RETURN(0); + } +#endif + + show_table->file->get_foreign_key_list(thd, &f_key_list); + FOREIGN_KEY_INFO *f_key_info; + List_iterator_fast<FOREIGN_KEY_INFO> it(f_key_list); + while ((f_key_info= it++)) + { + restore_record(table, s->default_values); + table->field[0]->store(STRING_WITH_LEN("def"), cs); + table->field[1]->store(db_name->str, db_name->length, cs); + table->field[9]->store(table_name->str, table_name->length, cs); + table->field[2]->store(f_key_info->foreign_id->str, + f_key_info->foreign_id->length, cs); + table->field[3]->store(STRING_WITH_LEN("def"), cs); + table->field[4]->store(f_key_info->referenced_db->str, + f_key_info->referenced_db->length, cs); + bool show_ref_table= true; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* need any non-SELECT privilege on the table or any of its columns */ + if (!(thd->col_access & need)) + { + TABLE_LIST table_acl_check; + bzero((char*) &table_acl_check, sizeof(table_acl_check)); + table_acl_check.db= *f_key_info->referenced_db; + table_acl_check.table_name= *f_key_info->referenced_table; + table_acl_check.grant.privilege= thd->col_access; + check_grant(thd, SELECT_ACL, &table_acl_check, 0, 1, 1); + + if (!(table_acl_check.grant.all_privilege() & need)) + show_ref_table= false; + } +#endif + if (show_ref_table) + { + table->field[10]->set_notnull(); + table->field[10]->store(f_key_info->referenced_table->str, + f_key_info->referenced_table->length, cs); + } + if (f_key_info->referenced_key_name) + { + table->field[5]->store(f_key_info->referenced_key_name->str, + f_key_info->referenced_key_name->length, cs); + table->field[5]->set_notnull(); + } + else + table->field[5]->set_null(); + table->field[6]->store(STRING_WITH_LEN("NONE"), cs); + s= fk_option_name(f_key_info->update_method); + table->field[7]->store(s->str, s->length, cs); + s= fk_option_name(f_key_info->delete_method); + table->field[8]->store(s->str, s->length, cs); + if (schema_table_store_record(thd, table)) + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + +struct schema_table_ref +{ + const char *table_name; + ST_SCHEMA_TABLE *schema_table; +}; + +/* + Find schema_tables elment by name + + SYNOPSIS + find_schema_table_in_plugin() + thd thread handler + plugin plugin + table_name table name + + RETURN + 0 table not found + 1 found the schema table +*/ +static my_bool find_schema_table_in_plugin(THD *thd, plugin_ref plugin, + void* p_table) +{ + schema_table_ref *p_schema_table= (schema_table_ref *)p_table; + const char* table_name= p_schema_table->table_name; + ST_SCHEMA_TABLE *schema_table= plugin_data(plugin, ST_SCHEMA_TABLE *); + DBUG_ENTER("find_schema_table_in_plugin"); + + if (!my_strcasecmp(system_charset_info, + schema_table->table_name, + table_name)) + { + my_plugin_lock(thd, plugin); + p_schema_table->schema_table= schema_table; + DBUG_RETURN(1); + } + + DBUG_RETURN(0); +} + + +/* + Find schema_tables element by name + + SYNOPSIS + find_schema_table() + thd thread handler + table_name table name + + RETURN + 0 table not found + # pointer to 'schema_tables' element +*/ + +ST_SCHEMA_TABLE *find_schema_table(THD *thd, const LEX_CSTRING *table_name, + bool *in_plugin) +{ + schema_table_ref schema_table_a; + ST_SCHEMA_TABLE *schema_table= schema_tables; + DBUG_ENTER("find_schema_table"); + + *in_plugin= false; + for (; schema_table->table_name; schema_table++) + { + if (!my_strcasecmp(system_charset_info, + schema_table->table_name, + table_name->str)) + DBUG_RETURN(schema_table); + } + + *in_plugin= true; + schema_table_a.table_name= table_name->str; + if (plugin_foreach(thd, find_schema_table_in_plugin, + MYSQL_INFORMATION_SCHEMA_PLUGIN, &schema_table_a)) + DBUG_RETURN(schema_table_a.schema_table); + + DBUG_RETURN(NULL); +} + + +ST_SCHEMA_TABLE *get_schema_table(enum enum_schema_tables schema_table_idx) +{ + return &schema_tables[schema_table_idx]; +} + +/** + Create information_schema table using schema_table data. + + @note + For MYSQL_TYPE_DECIMAL fields only, the field_length member has encoded + into it two numbers, based on modulus of base-10 numbers. In the ones + position is the number of decimals. Tens position is unused. In the + hundreds and thousands position is a two-digit decimal number representing + length. Encode this value with (length*100)+decimals , where + 0<decimals<10 and 0<=length<100 . + + @param + thd thread handler + + @param table_list Used to pass I_S table information(fields info, tables + parameters etc) and table name. + + @retval \# Pointer to created table + @retval NULL Can't create table +*/ + +TABLE *create_schema_table(THD *thd, TABLE_LIST *table_list) +{ + uint field_count= 0; + TABLE *table; + ST_SCHEMA_TABLE *schema_table= table_list->schema_table; + ST_FIELD_INFO *fields= schema_table->fields_info; + bool need_all_fields= table_list->schema_table_reformed || // SHOW command + thd->lex->only_view_structure(); // need table structure + bool keep_row_order; + TMP_TABLE_PARAM *tmp_table_param; + SELECT_LEX *select_lex; + DBUG_ENTER("create_schema_table"); + + for (; !fields->end_marker(); fields++) + field_count++; + + tmp_table_param = new (thd->mem_root) TMP_TABLE_PARAM; + tmp_table_param->init(); + tmp_table_param->table_charset= system_charset_info; + tmp_table_param->field_count= field_count; + tmp_table_param->schema_table= 1; + select_lex= table_list->select_lex; + keep_row_order= is_show_command(thd); + if (!(table= + create_tmp_table_for_schema(thd, tmp_table_param, *schema_table, + (select_lex->options | + thd->variables.option_bits | + TMP_TABLE_ALL_COLUMNS), + table_list->alias, !need_all_fields, + keep_row_order))) + DBUG_RETURN(0); + my_bitmap_map* bitmaps= + (my_bitmap_map*) thd->alloc(bitmap_buffer_size(field_count)); + my_bitmap_init(&table->def_read_set, bitmaps, field_count); + table->read_set= &table->def_read_set; + bitmap_clear_all(table->read_set); + table_list->schema_table_param= tmp_table_param; + DBUG_RETURN(table); +} + + +/* + For old SHOW compatibility. It is used when + old SHOW doesn't have generated column names + Make list of fields for SHOW + + SYNOPSIS + make_old_format() + thd thread handler + schema_table pointer to 'schema_tables' element + + RETURN + 1 error + 0 success +*/ + +static int make_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) +{ + ST_FIELD_INFO *field_info= schema_table->fields_info; + Name_resolution_context *context= &thd->lex->first_select_lex()->context; + for (; !field_info->end_marker(); field_info++) + { + if (field_info->old_name().str) + { + LEX_CSTRING field_name= field_info->name(); + Item_field *field= new (thd->mem_root) + Item_field(thd, context, field_name); + if (field) + { + field->set_name(thd, field_info->old_name()); + if (add_item_to_list(thd, field)) + return 1; + } + } + } + return 0; +} + + +int make_schemata_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) +{ + char tmp[128]; + LEX *lex= thd->lex; + SELECT_LEX *sel= lex->current_select; + Name_resolution_context *context= &sel->context; + + if (!sel->item_list.elements) + { + ST_FIELD_INFO *field_info= &schema_table->fields_info[1]; + String buffer(tmp,sizeof(tmp), system_charset_info); + Item_field *field= new (thd->mem_root) Item_field(thd, context, + field_info->name()); + if (!field || add_item_to_list(thd, field)) + return 1; + buffer.length(0); + buffer.append(field_info->old_name()); + if (lex->wild && lex->wild->ptr()) + { + buffer.append(STRING_WITH_LEN(" (")); + buffer.append(*lex->wild); + buffer.append(')'); + } + field->set_name(thd, &buffer); + } + return 0; +} + + +int make_table_names_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) +{ + char tmp[128]; + String buffer(tmp, sizeof(tmp), system_charset_info); + LEX *lex= thd->lex; + Name_resolution_context *context= &lex->first_select_lex()->context; + ST_FIELD_INFO *field_info= &schema_table->fields_info[2]; + LEX_CSTRING field_name= field_info->name(); + + buffer.length(0); + buffer.append(field_info->old_name()); + buffer.append(&lex->first_select_lex()->db); + if (lex->wild && lex->wild->ptr()) + { + buffer.append(STRING_WITH_LEN(" (")); + buffer.append(*lex->wild); + buffer.append(')'); + } + Item_field *field= new (thd->mem_root) Item_field(thd, context, field_name); + if (add_item_to_list(thd, field)) + return 1; + field->set_name(thd, &buffer); + if (thd->lex->verbose) + { + field_info= &schema_table->fields_info[3]; + field= new (thd->mem_root) Item_field(thd, context, field_info->name()); + if (add_item_to_list(thd, field)) + return 1; + field->set_name(thd, field_info->old_name()); + } + return 0; +} + + +int make_columns_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) +{ + int fields_arr[]= {3, 15, 14, 6, 16, 5, 17, 18, 19, -1}; + int *field_num= fields_arr; + ST_FIELD_INFO *field_info; + Name_resolution_context *context= &thd->lex->first_select_lex()->context; + + for (; *field_num >= 0; field_num++) + { + field_info= &schema_table->fields_info[*field_num]; + if (!thd->lex->verbose && (*field_num == 14 || + *field_num == 18 || + *field_num == 19)) + continue; + Item_field *field= new (thd->mem_root) Item_field(thd, context, + field_info->name()); + if (field) + { + field->set_name(thd, field_info->old_name()); + if (add_item_to_list(thd, field)) + return 1; + } + } + return 0; +} + + +int make_character_sets_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) +{ + int fields_arr[]= {0, 2, 1, 3, -1}; + int *field_num= fields_arr; + ST_FIELD_INFO *field_info; + Name_resolution_context *context= &thd->lex->first_select_lex()->context; + + for (; *field_num >= 0; field_num++) + { + field_info= &schema_table->fields_info[*field_num]; + Item_field *field= new (thd->mem_root) Item_field(thd, context, + field_info->name()); + if (field) + { + field->set_name(thd, field_info->old_name()); + if (add_item_to_list(thd, field)) + return 1; + } + } + return 0; +} + + +int make_proc_old_format(THD *thd, ST_SCHEMA_TABLE *schema_table) +{ + int fields_arr[]= {2, 3, 4, 27, 24, 23, 22, 26, 28, 29, 30, -1}; + int *field_num= fields_arr; + ST_FIELD_INFO *field_info; + Name_resolution_context *context= &thd->lex->first_select_lex()->context; + + for (; *field_num >= 0; field_num++) + { + field_info= &schema_table->fields_info[*field_num]; + Item_field *field= new (thd->mem_root) Item_field(thd, context, + field_info->name()); + if (field) + { + field->set_name(thd, field_info->old_name()); + if (add_item_to_list(thd, field)) + return 1; + } + } + return 0; +} + + +/* + Create information_schema table + + SYNOPSIS + mysql_schema_table() + thd thread handler + lex pointer to LEX + table_list pointer to table_list + + RETURN + 0 success + 1 error +*/ + +int mysql_schema_table(THD *thd, LEX *lex, TABLE_LIST *table_list) +{ + TABLE *table; + DBUG_ENTER("mysql_schema_table"); + if (!(table= create_schema_table(thd, table_list))) + DBUG_RETURN(1); + table->s->tmp_table= SYSTEM_TMP_TABLE; + table->grant.privilege= SELECT_ACL; + /* + This test is necessary to make + case insensitive file systems + + upper case table names(information schema tables) + + views + working correctly + */ + if (table_list->schema_table_name.str) + table->alias_name_used= my_strcasecmp(table_alias_charset, + table_list->schema_table_name.str, + table_list->alias.str); + table_list->table= table; + table->next= thd->derived_tables; + thd->derived_tables= table; + table_list->select_lex->options |= OPTION_SCHEMA_TABLE; + lex->safe_to_cache_query= 0; + + if (table_list->schema_table_reformed) // show command + { + SELECT_LEX *sel= lex->current_select; + Item *item; + Field_translator *transl, *org_transl; + + if (table_list->field_translation) + { + Field_translator *end= table_list->field_translation_end; + for (transl= table_list->field_translation; transl < end; transl++) + { + if (transl->item->fix_fields_if_needed(thd, &transl->item)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); + } + List_iterator_fast<Item> it(sel->item_list); + if (!(transl= + (Field_translator*)(thd->stmt_arena-> + alloc(sel->item_list.elements * + sizeof(Field_translator))))) + { + DBUG_RETURN(1); + } + for (org_transl= transl; (item= it++); transl++) + { + transl->item= item; + transl->name= item->name; + if (item->fix_fields_if_needed(thd, &transl->item)) + DBUG_RETURN(1); + } + table_list->field_translation= org_transl; + table_list->field_translation_end= transl; + } + + DBUG_RETURN(0); +} + + +/* + Generate select from information_schema table + + SYNOPSIS + make_schema_select() + thd thread handler + sel pointer to SELECT_LEX + schema_table_idx index of 'schema_tables' element + + RETURN + 0 success + 1 error +*/ + +int make_schema_select(THD *thd, SELECT_LEX *sel, + ST_SCHEMA_TABLE *schema_table) +{ + LEX_CSTRING db, table; + DBUG_ENTER("make_schema_select"); + DBUG_PRINT("enter", ("mysql_schema_select: %s", schema_table->table_name)); + /* + We have to make non const db_name & table_name + because of lower_case_table_names + */ + if (!thd->make_lex_string(&db, INFORMATION_SCHEMA_NAME.str, + INFORMATION_SCHEMA_NAME.length)) + DBUG_RETURN(1); + + if (!thd->make_lex_string(&table, schema_table->table_name, + strlen(schema_table->table_name))) + DBUG_RETURN(1); + + if (schema_table->old_format(thd, schema_table)) + DBUG_RETURN(1); + + if (!sel->add_table_to_list(thd, new Table_ident(thd, &db, &table, 0), + 0, 0, TL_READ, MDL_SHARED_READ)) + DBUG_RETURN(1); + + sel->table_list.first->schema_table_reformed= 1; + DBUG_RETURN(0); +} + + +/* + Optimize reading from an I_S table. + + @detail + This function prepares a plan for populating an I_S table with + get_all_tables(). + + The plan is in IS_table_read_plan structure, it is saved in + tables->is_table_read_plan. + + @return + false - Ok + true - Out Of Memory + +*/ + +static bool optimize_for_get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) +{ + SELECT_LEX *lsel= tables->schema_select_lex; + ST_SCHEMA_TABLE *schema_table= tables->schema_table; + enum enum_schema_tables schema_table_idx; + IS_table_read_plan *plan; + DBUG_ENTER("get_all_tables"); + + if (!(plan= new IS_table_read_plan())) + DBUG_RETURN(1); + + tables->is_table_read_plan= plan; + + schema_table_idx= get_schema_table_idx(schema_table); + tables->table_open_method= get_table_open_method(tables, schema_table, + schema_table_idx); + DBUG_PRINT("open_method", ("%d", tables->table_open_method)); + + /* + this branch processes SHOW FIELDS, SHOW INDEXES commands. + see sql_parse.cc, prepare_schema_table() function where + this values are initialized + */ + if (lsel && lsel->table_list.first) + { + /* These do not need to have a query plan */ + plan->trivial_show_command= true; + goto end; + } + + if (get_lookup_field_values(thd, cond, true, tables, + &plan->lookup_field_vals)) + { + plan->no_rows= true; + goto end; + } + + DBUG_PRINT("info",("db_name='%s', table_name='%s'", + plan->lookup_field_vals.db_value.str, + plan->lookup_field_vals.table_value.str)); + + if (!plan->lookup_field_vals.wild_db_value && + !plan->lookup_field_vals.wild_table_value) + { + /* + if lookup value is empty string then + it's impossible table name or db name + */ + if ((plan->lookup_field_vals.db_value.str && + !plan->lookup_field_vals.db_value.str[0]) || + (plan->lookup_field_vals.table_value.str && + !plan->lookup_field_vals.table_value.str[0])) + { + plan->no_rows= true; + goto end; + } + } + + if (plan->has_db_lookup_value() && plan->has_table_lookup_value()) + plan->partial_cond= 0; + else + plan->partial_cond= make_cond_for_info_schema(thd, cond, tables); + +end: + DBUG_RETURN(0); +} + + +bool optimize_schema_tables_memory_usage(List<TABLE_LIST> &tables) +{ + DBUG_ENTER("optimize_schema_tables_memory_usage"); + + List_iterator<TABLE_LIST> tli(tables); + + while (TABLE_LIST *table_list= tli++) + { + if (!table_list->schema_table) + continue; + + TABLE *table= table_list->table; + THD *thd=table->in_use; + + if (!thd->fill_information_schema_tables()) + continue; + + if (!table->is_created()) + { + TMP_TABLE_PARAM *p= table_list->schema_table_param; + TMP_ENGINE_COLUMNDEF *from_recinfo, *to_recinfo; + DBUG_ASSERT(table->s->keys == 0); + DBUG_ASSERT(table->s->uniques == 0); + + uchar *cur= table->field[0]->ptr; + /* first recinfo could be a NULL bitmap, not an actual Field */ + from_recinfo= to_recinfo= p->start_recinfo + (cur != table->record[0]); + for (uint i=0; i < table->s->fields; i++, from_recinfo++) + { + Field *field= table->field[i]; + DBUG_ASSERT(field->vcol_info == 0); + DBUG_ASSERT(from_recinfo->length); + DBUG_ASSERT(from_recinfo->length == field->pack_length_in_rec()); + if (bitmap_is_set(table->read_set, i)) + { + field->move_field(cur); + field->reset(); + *to_recinfo++= *from_recinfo; + cur+= from_recinfo->length; + } + else + { + field= new (thd->mem_root) Field_string(cur, 0, field->null_ptr, + field->null_bit, Field::NONE, + &field->field_name, field->dtcollation()); + field->init(table); + field->field_index= i; + DBUG_ASSERT(field->pack_length_in_rec() == 0); + table->field[i]= field; + } + } + if ((table->s->reclength= (ulong)(cur - table->record[0])) == 0) + { + /* all fields were optimized away. Force a non-0-length row */ + table->s->reclength= to_recinfo->length= 1; + to_recinfo->type= FIELD_NORMAL; + to_recinfo++; + } + store_record(table, s->default_values); + p->recinfo= to_recinfo; + + // TODO switch from Aria to Memory if all blobs were optimized away? + if (instantiate_tmp_table(table, p->keyinfo, p->start_recinfo, &p->recinfo, + table_list->select_lex->options | thd->variables.option_bits)) + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + + +/* + This is the optimizer part of get_schema_tables_result(). +*/ + +bool optimize_schema_tables_reads(JOIN *join) +{ + THD *thd= join->thd; + DBUG_ENTER("optimize_schema_tables_reads"); + + JOIN_TAB *tab; + for (tab= first_linear_tab(join, WITHOUT_BUSH_ROOTS, WITH_CONST_TABLES); + tab; + tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) + { + if (!tab->table || !tab->table->pos_in_table_list) + continue; + + TABLE_LIST *table_list= tab->table->pos_in_table_list; + if (table_list->schema_table && thd->fill_information_schema_tables()) + { + /* A value of 0 indicates a dummy implementation */ + if (table_list->schema_table->fill_table == 0) + continue; + + /* skip I_S optimizations specific to get_all_tables */ + if (table_list->schema_table->fill_table != get_all_tables) + continue; + + Item *cond= tab->select_cond; + if (tab->cache_select && tab->cache_select->cond) + { + /* + If join buffering is used, we should use the condition that is + attached to the join cache. Cache condition has a part of WHERE that + can be checked when we're populating this table. + join_tab->select_cond is of no interest, because it only has + conditions that depend on both this table and previous tables in the + join order. + */ + cond= tab->cache_select->cond; + } + if (optimize_for_get_all_tables(thd, table_list, cond)) + DBUG_RETURN(1); // Handle OOM + } + } + DBUG_RETURN(0); +} + + +/* + Fill temporary schema tables before SELECT + + SYNOPSIS + get_schema_tables_result() + join join which use schema tables + executed_place place where I_S table processed + + SEE ALSO + The optimization part is done by get_schema_tables_result(). This function + is run on query execution. + + RETURN + FALSE success + TRUE error +*/ + +bool get_schema_tables_result(JOIN *join, + enum enum_schema_table_state executed_place) +{ + THD *thd= join->thd; + LEX *lex= thd->lex; + bool result= 0; + PSI_stage_info org_stage; + DBUG_ENTER("get_schema_tables_result"); + + Warnings_only_error_handler err_handler; + thd->push_internal_handler(&err_handler); + thd->backup_stage(&org_stage); + THD_STAGE_INFO(thd, stage_filling_schema_table); + + JOIN_TAB *tab; + for (tab= first_linear_tab(join, WITHOUT_BUSH_ROOTS, WITH_CONST_TABLES); + tab; + tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) + { + if (!tab->table || !tab->table->pos_in_table_list) + break; + + TABLE_LIST *table_list= tab->table->pos_in_table_list; + if (table_list->schema_table && thd->fill_information_schema_tables()) + { + /* + I_S tables only need to be re-populated if make_cond_for_info_schema() + preserves outer fields + */ + bool is_subselect= &lex->unit != lex->current_select->master_unit() && + lex->current_select->master_unit()->item && + tab->select_cond && + tab->select_cond->used_tables() & OUTER_REF_TABLE_BIT; + + /* A value of 0 indicates a dummy implementation */ + if (table_list->schema_table->fill_table == 0) + continue; + + /* + Do not fill in tables thare are marked as JT_CONST as these will never + be read and they also don't have a tab->read_record.table set! + This can happen with queries like + SELECT * FROM t1 LEFT JOIN (t1 AS t1b JOIN INFORMATION_SCHEMA.ROUTINES) + ON (t1b.a IS NULL); + */ + if (tab->type == JT_CONST) + continue; + + /* skip I_S optimizations specific to get_all_tables */ + if (lex->describe && + (table_list->schema_table->fill_table != get_all_tables)) + continue; + + /* + If schema table is already processed and the statement is not a + subselect then we don't need to fill this table again. If schema table + is already processed and schema_table_state != executed_place then + table is already processed and we should skip second data processing. + */ + if (table_list->schema_table_state && + (!is_subselect || table_list->schema_table_state != executed_place)) + continue; + + /* + if table is used in a subselect and + table has been processed earlier with the same + 'executed_place' value then we should refresh the table. + */ + if (table_list->schema_table_state && is_subselect) + { + table_list->table->file->extra(HA_EXTRA_NO_CACHE); + table_list->table->file->extra(HA_EXTRA_RESET_STATE); + table_list->table->file->ha_delete_all_rows(); + table_list->table->null_row= 0; + } + else + table_list->table->file->stats.records= 0; + + Item *cond= tab->select_cond; + if (tab->cache_select && tab->cache_select->cond) + { + /* + If join buffering is used, we should use the condition that is + attached to the join cache. Cache condition has a part of WHERE that + can be checked when we're populating this table. + join_tab->select_cond is of no interest, because it only has + conditions that depend on both this table and previous tables in the + join order. + */ + cond= tab->cache_select->cond; + } + + Switch_to_definer_security_ctx backup_ctx(thd, table_list); + Check_level_instant_set check_level_save(thd, CHECK_FIELD_IGNORE); + if (table_list->schema_table->fill_table(thd, table_list, cond)) + { + result= 1; + join->error= 1; + tab->read_record.table->file= table_list->table->file; + table_list->schema_table_state= executed_place; + break; + } + tab->read_record.table->file= table_list->table->file; + table_list->schema_table_state= executed_place; + } + } + thd->pop_internal_handler(); + if (unlikely(thd->is_error())) + { + /* + This hack is here, because I_S code uses thd->clear_error() a lot. + Which means, a Warnings_only_error_handler cannot handle the error + corectly as it does not know whether an error is real (e.g. caused + by tab->select_cond->val_int()) or will be cleared later. + Thus it ignores all errors, and the real one (that is, the error + that was not cleared) is pushed now. + + It also means that an audit plugin cannot process the error correctly + either. See also thd->clear_error() + */ + thd->get_stmt_da()->push_warning(thd, thd->get_stmt_da()->sql_errno(), + thd->get_stmt_da()->get_sqlstate(), + Sql_condition::WARN_LEVEL_ERROR, + thd->get_stmt_da()->message()); + } + else if (result) + my_error(ER_UNKNOWN_ERROR, MYF(0)); + THD_STAGE_INFO(thd, org_stage); + DBUG_RETURN(result); +} + +struct run_hton_fill_schema_table_args +{ + TABLE_LIST *tables; + COND *cond; +}; + +static my_bool run_hton_fill_schema_table(THD *thd, plugin_ref plugin, + void *arg) +{ + struct run_hton_fill_schema_table_args *args= + (run_hton_fill_schema_table_args *) arg; + handlerton *hton= plugin_hton(plugin); + if (hton->fill_is_table) + hton->fill_is_table(hton, thd, args->tables, args->cond, + get_schema_table_idx(args->tables->schema_table)); + return false; +} + +int hton_fill_schema_table(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("hton_fill_schema_table"); + + struct run_hton_fill_schema_table_args args; + args.tables= tables; + args.cond= cond; + + plugin_foreach(thd, run_hton_fill_schema_table, + MYSQL_STORAGE_ENGINE_PLUGIN, &args); + + DBUG_RETURN(0); +} + + +static +int store_key_cache_table_record(THD *thd, TABLE *table, + const char *name, size_t name_length, + KEY_CACHE *key_cache, + uint partitions, uint partition_no) +{ + KEY_CACHE_STATISTICS keycache_stats; + uint err; + DBUG_ENTER("store_key_cache_table_record"); + + get_key_cache_statistics(key_cache, partition_no, &keycache_stats); + + if (!key_cache->key_cache_inited || keycache_stats.mem_size == 0) + DBUG_RETURN(0); + + restore_record(table, s->default_values); + table->field[0]->store(name, name_length, system_charset_info); + if (partitions == 0) + table->field[1]->set_null(); + else + { + table->field[1]->set_notnull(); + table->field[1]->store((long) partitions, TRUE); + } + + if (partition_no == 0) + table->field[2]->set_null(); + else + { + table->field[2]->set_notnull(); + table->field[2]->store((long) partition_no, TRUE); + } + table->field[3]->store(keycache_stats.mem_size, TRUE); + table->field[4]->store(keycache_stats.block_size, TRUE); + table->field[5]->store(keycache_stats.blocks_used, TRUE); + table->field[6]->store(keycache_stats.blocks_unused, TRUE); + table->field[7]->store(keycache_stats.blocks_changed, TRUE); + table->field[8]->store(keycache_stats.read_requests, TRUE); + table->field[9]->store(keycache_stats.reads, TRUE); + table->field[10]->store(keycache_stats.write_requests, TRUE); + table->field[11]->store(keycache_stats.writes, TRUE); + + err= schema_table_store_record(thd, table); + DBUG_RETURN(err); +} + +int run_fill_key_cache_tables(const char *name, KEY_CACHE *key_cache, void *p) +{ + DBUG_ENTER("run_fill_key_cache_tables"); + + if (!key_cache->key_cache_inited) + DBUG_RETURN(0); + + TABLE *table= (TABLE *)p; + THD *thd= table->in_use; + uint partitions= key_cache->partitions; + size_t namelen= strlen(name); + DBUG_ASSERT(partitions <= MAX_KEY_CACHE_PARTITIONS); + + if (partitions) + { + for (uint i= 0; i < partitions; i++) + { + if (store_key_cache_table_record(thd, table, name, namelen, + key_cache, partitions, i+1)) + DBUG_RETURN(1); + } + } + + if (store_key_cache_table_record(thd, table, name, namelen, + key_cache, partitions, 0)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + +int fill_key_cache_tables(THD *thd, TABLE_LIST *tables, COND *cond) +{ + DBUG_ENTER("fill_key_cache_tables"); + + int res= process_key_caches(run_fill_key_cache_tables, tables->table); + + DBUG_RETURN(res); +} + + +namespace Show { + +ST_FIELD_INFO schema_fields_info[]= +{ + Column("CATALOG_NAME", Catalog(), NOT_NULL), + Column("SCHEMA_NAME", Name(), NOT_NULL, "Database"), + Column("DEFAULT_CHARACTER_SET_NAME", CSName(), NOT_NULL), + Column("DEFAULT_COLLATION_NAME", CLName(), NOT_NULL), + Column("SQL_PATH", Varchar(FN_REFLEN), NULLABLE), + Column("SCHEMA_COMMENT", Varchar(DATABASE_COMMENT_MAXLEN), NOT_NULL), + CEnd() +}; + + +ST_FIELD_INFO tables_fields_info[]= +{ + Column("TABLE_CATALOG", Catalog(), NOT_NULL), + Column("TABLE_SCHEMA", Name(), NOT_NULL), + Column("TABLE_NAME", Name(), NOT_NULL, "Name"), + Column("TABLE_TYPE", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("ENGINE", Name(), NULLABLE, "Engine", OPEN_FRM_ONLY), + Column("VERSION", ULonglong(), NULLABLE, "Version", OPEN_FRM_ONLY), + Column("ROW_FORMAT", Varchar(10), NULLABLE, "Row_format", OPEN_FULL_TABLE), + Column("TABLE_ROWS", ULonglong(), NULLABLE, "Rows", OPEN_FULL_TABLE), + Column("AVG_ROW_LENGTH", ULonglong(), NULLABLE, "Avg_row_length", + OPEN_FULL_TABLE), + Column("DATA_LENGTH", ULonglong(), NULLABLE, "Data_length",OPEN_FULL_TABLE), + Column("MAX_DATA_LENGTH", ULonglong(), NULLABLE, "Max_data_length", + OPEN_FULL_TABLE), + Column("INDEX_LENGTH", ULonglong(), NULLABLE, "Index_length",OPEN_FULL_TABLE), + Column("DATA_FREE", ULonglong(), NULLABLE, "Data_free", OPEN_FULL_TABLE), + Column("AUTO_INCREMENT", ULonglong(), NULLABLE, "Auto_increment", + OPEN_FULL_TABLE), + Column("CREATE_TIME", Datetime(0), NULLABLE, "Create_time",OPEN_FULL_TABLE), + Column("UPDATE_TIME", Datetime(0), NULLABLE, "Update_time",OPEN_FULL_TABLE), + Column("CHECK_TIME", Datetime(0), NULLABLE, "Check_time", OPEN_FULL_TABLE), + Column("TABLE_COLLATION", CLName(), NULLABLE, "Collation", OPEN_FRM_ONLY), + Column("CHECKSUM", ULonglong(), NULLABLE, "Checksum", OPEN_FULL_TABLE), + Column("CREATE_OPTIONS", Varchar(2048),NULLABLE, "Create_options", + OPEN_FULL_TABLE), + Column("TABLE_COMMENT", Varchar(TABLE_COMMENT_MAXLEN), + NOT_NULL, "Comment", OPEN_FRM_ONLY), + Column("MAX_INDEX_LENGTH",ULonglong(), NULLABLE, "Max_index_length", + OPEN_FULL_TABLE), + Column("TEMPORARY", Varchar(1), NULLABLE, "Temporary", OPEN_FRM_ONLY), + CEnd() +}; + + +ST_FIELD_INFO columns_fields_info[]= +{ + Column("TABLE_CATALOG", Catalog(), NOT_NULL, OPEN_FRM_ONLY), + Column("TABLE_SCHEMA", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("TABLE_NAME", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("COLUMN_NAME", Name(), NOT_NULL, "Field", OPEN_FRM_ONLY), + Column("ORDINAL_POSITION", ULonglong(), NOT_NULL, OPEN_FRM_ONLY), + Column("COLUMN_DEFAULT", Longtext(MAX_FIELD_VARCHARLENGTH), + NULLABLE, "Default",OPEN_FRM_ONLY), + Column("IS_NULLABLE", Yes_or_empty(), NOT_NULL, "Null", OPEN_FRM_ONLY), + Column("DATA_TYPE", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("CHARACTER_MAXIMUM_LENGTH",ULonglong(), NULLABLE, OPEN_FRM_ONLY), + Column("CHARACTER_OCTET_LENGTH", ULonglong(), NULLABLE, OPEN_FRM_ONLY), + Column("NUMERIC_PRECISION", ULonglong(), NULLABLE, OPEN_FRM_ONLY), + Column("NUMERIC_SCALE", ULonglong(), NULLABLE, OPEN_FRM_ONLY), + Column("DATETIME_PRECISION", ULonglong(), NULLABLE, OPEN_FRM_ONLY), + Column("CHARACTER_SET_NAME", CSName(), NULLABLE, OPEN_FRM_ONLY), + Column("COLLATION_NAME", CLName(), NULLABLE, "Collation", OPEN_FRM_ONLY), + Column("COLUMN_TYPE", Longtext(65535), NOT_NULL, "Type", OPEN_FRM_ONLY), + Column("COLUMN_KEY", Varchar(3), NOT_NULL, "Key", OPEN_FRM_ONLY), + Column("EXTRA", Varchar(80), NOT_NULL, "Extra", OPEN_FRM_ONLY), + Column("PRIVILEGES", Varchar(80), NOT_NULL, "Privileges", OPEN_FRM_ONLY), + Column("COLUMN_COMMENT", Varchar(COLUMN_COMMENT_MAXLEN), NOT_NULL, "Comment", + OPEN_FRM_ONLY), + Column("IS_GENERATED", Varchar(6), NOT_NULL, OPEN_FRM_ONLY), + Column("GENERATION_EXPRESSION", Longtext(MAX_FIELD_VARCHARLENGTH), + NULLABLE, OPEN_FRM_ONLY), + CEnd() +}; + + +ST_FIELD_INFO charsets_fields_info[]= +{ + Column("CHARACTER_SET_NAME", CSName(), NOT_NULL, "Charset"), + Column("DEFAULT_COLLATE_NAME", CLName(), NOT_NULL, "Default collation"), + Column("DESCRIPTION", Varchar(60), NOT_NULL, "Description"), + Column("MAXLEN", SLonglong(3), NOT_NULL, "Maxlen"), + CEnd() +}; + + +ST_FIELD_INFO collation_fields_info[]= +{ + Column("COLLATION_NAME", CLName(), NOT_NULL, "Collation"), + Column("CHARACTER_SET_NAME", CSName(), NULLABLE, "Charset"), + Column("ID", SLonglong(MY_INT32_NUM_DECIMAL_DIGITS), NULLABLE, "Id"), + Column("IS_DEFAULT", Yes_or_empty(), NULLABLE, "Default"), + Column("IS_COMPILED", Yes_or_empty(), NOT_NULL, "Compiled"), + Column("SORTLEN", SLonglong(3), NOT_NULL, "Sortlen"), + CEnd() +}; + + +ST_FIELD_INFO applicable_roles_fields_info[]= +{ + Column("GRANTEE", Userhost(), NOT_NULL), + Column("ROLE_NAME", Varchar(USERNAME_CHAR_LENGTH), NOT_NULL), + Column("IS_GRANTABLE", Yes_or_empty(), NOT_NULL), + Column("IS_DEFAULT", Yes_or_empty(), NULLABLE), + CEnd() +}; + + +ST_FIELD_INFO enabled_roles_fields_info[]= +{ + Column("ROLE_NAME", Varchar(USERNAME_CHAR_LENGTH), NULLABLE), + CEnd() +}; + +ST_FIELD_INFO keywords_field_info[]= +{ + Column("WORD", Varchar(KEYWORD_SIZE), NULLABLE), + CEnd() +}; + +ST_FIELD_INFO sql_functions_field_info[]= +{ + Column("FUNCTION", Varchar(KEYWORD_SIZE), NULLABLE), + CEnd() +}; + + +ST_FIELD_INFO engines_fields_info[]= +{ + Column("ENGINE", Varchar(64), NOT_NULL, "Engine"), + Column("SUPPORT", Varchar(8), NOT_NULL, "Support"), + Column("COMMENT", Varchar(160), NOT_NULL, "Comment"), + Column("TRANSACTIONS", Varchar(3), NULLABLE, "Transactions"), + Column("XA", Varchar(3), NULLABLE, "XA"), + Column("SAVEPOINTS", Varchar(3), NULLABLE, "Savepoints"), + CEnd() +}; + + +ST_FIELD_INFO events_fields_info[]= +{ + // QQ: shouldn't EVENT_CATALOG be Catalog() like in all other places? + Column("EVENT_CATALOG", Name(), NOT_NULL), + Column("EVENT_SCHEMA", Name(), NOT_NULL, "Db"), + Column("EVENT_NAME", Name(), NOT_NULL, "Name"), + Column("DEFINER", Definer(), NOT_NULL, "Definer"), + Column("TIME_ZONE", Varchar(64), NOT_NULL, "Time zone"), + Column("EVENT_BODY", Varchar(8), NOT_NULL), + Column("EVENT_DEFINITION", Longtext(65535), NOT_NULL), + Column("EVENT_TYPE", Varchar(9), NOT_NULL, "Type"), + Column("EXECUTE_AT", Datetime(0), NULLABLE, "Execute at"), + Column("INTERVAL_VALUE", Varchar(256),NULLABLE, "Interval value"), + Column("INTERVAL_FIELD", Varchar(18), NULLABLE, "Interval field"), + Column("SQL_MODE", SQLMode(), NOT_NULL), + Column("STARTS", Datetime(0), NULLABLE, "Starts"), + Column("ENDS", Datetime(0), NULLABLE, "Ends"), + Column("STATUS", Varchar(18), NOT_NULL, "Status"), + Column("ON_COMPLETION", Varchar(12), NOT_NULL), + Column("CREATED", Datetime(0), NOT_NULL), + Column("LAST_ALTERED", Datetime(0), NOT_NULL), + Column("LAST_EXECUTED", Datetime(0), NULLABLE), + Column("EVENT_COMMENT", Name(), NOT_NULL), + Column("ORIGINATOR", SLonglong(10),NOT_NULL,"Originator"), + Column("CHARACTER_SET_CLIENT", CSName(), NOT_NULL, "character_set_client"), + Column("COLLATION_CONNECTION", CLName(), NOT_NULL, "collation_connection"), + Column("DATABASE_COLLATION", CLName(), NOT_NULL, "Database Collation"), + CEnd() +}; + + + +ST_FIELD_INFO coll_charset_app_fields_info[]= +{ + Column("COLLATION_NAME", CLName(), NOT_NULL), + Column("CHARACTER_SET_NAME", CSName(), NOT_NULL), + Column("FULL_COLLATION_NAME",CLName(), NOT_NULL), + Column("ID", SLonglong(MY_INT32_NUM_DECIMAL_DIGITS), NOT_NULL), + Column("IS_DEFAULT", Yes_or_empty(), NOT_NULL), + CEnd() +}; + + +ST_FIELD_INFO proc_fields_info[]= +{ + Column("SPECIFIC_NAME", Name(), NOT_NULL), + Column("ROUTINE_CATALOG", Catalog(), NOT_NULL), + Column("ROUTINE_SCHEMA", Name(), NOT_NULL, "Db"), + Column("ROUTINE_NAME", Name(), NOT_NULL, "Name"), + Column("ROUTINE_TYPE", Varchar(13),NOT_NULL, "Type"), + Column("DATA_TYPE", Name(), NOT_NULL), + Column("CHARACTER_MAXIMUM_LENGTH",SLong(21), NULLABLE), + Column("CHARACTER_OCTET_LENGTH", SLong(21), NULLABLE), + Column("NUMERIC_PRECISION", SLong(21), NULLABLE), + Column("NUMERIC_SCALE", SLong(21), NULLABLE), + Column("DATETIME_PRECISION", ULonglong(), NULLABLE, OPEN_FRM_ONLY), + Column("CHARACTER_SET_NAME", Varchar(64),NULLABLE), + Column("COLLATION_NAME", Varchar(64),NULLABLE), + Column("DTD_IDENTIFIER", Longtext(65535), NULLABLE), + Column("ROUTINE_BODY", Varchar(8), NOT_NULL), + Column("ROUTINE_DEFINITION", Longtext(65535), NULLABLE), + Column("EXTERNAL_NAME", Name(), NULLABLE), + Column("EXTERNAL_LANGUAGE", Name(), NULLABLE), + Column("PARAMETER_STYLE", Varchar(8), NOT_NULL), + Column("IS_DETERMINISTIC", Varchar(3), NOT_NULL), + Column("SQL_DATA_ACCESS", Name(), NOT_NULL), + Column("SQL_PATH", Name(), NULLABLE), + Column("SECURITY_TYPE", Varchar(7), NOT_NULL, "Security_type"), + Column("CREATED", Datetime(0), NOT_NULL, "Created"), + Column("LAST_ALTERED", Datetime(0), NOT_NULL, "Modified"), + Column("SQL_MODE", SQLMode(), NOT_NULL), + Column("ROUTINE_COMMENT", Longtext(65535), NOT_NULL, "Comment"), + Column("DEFINER", Definer(), NOT_NULL, "Definer"), + Column("CHARACTER_SET_CLIENT", CSName(), NOT_NULL, "character_set_client"), + Column("COLLATION_CONNECTION", CLName(), NOT_NULL, "collation_connection"), + Column("DATABASE_COLLATION", CLName(), NOT_NULL, "Database Collation"), + CEnd() +}; + + +ST_FIELD_INFO stat_fields_info[]= +{ + Column("TABLE_CATALOG", Catalog(), NOT_NULL, OPEN_FRM_ONLY), + Column("TABLE_SCHEMA", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("TABLE_NAME", Name(), NOT_NULL, "Table", OPEN_FRM_ONLY), + Column("NON_UNIQUE", SLonglong(1),NOT_NULL, "Non_unique", OPEN_FRM_ONLY), + Column("INDEX_SCHEMA", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("INDEX_NAME", Name(), NOT_NULL, "Key_name", OPEN_FRM_ONLY), + Column("SEQ_IN_INDEX", SLonglong(2),NOT_NULL, "Seq_in_index",OPEN_FRM_ONLY), + Column("COLUMN_NAME", Name(), NOT_NULL, "Column_name", OPEN_FRM_ONLY), + Column("COLLATION", Varchar(1), NULLABLE, "Collation", OPEN_FULL_TABLE), + Column("CARDINALITY", SLonglong(), NULLABLE, "Cardinality", OPEN_FULL_TABLE), + Column("SUB_PART", SLonglong(3),NULLABLE, "Sub_part", OPEN_FRM_ONLY), + Column("PACKED", Varchar(10), NULLABLE, "Packed", OPEN_FRM_ONLY), + Column("NULLABLE", Varchar(3), NOT_NULL, "Null", OPEN_FRM_ONLY), + Column("INDEX_TYPE", Varchar(16), NOT_NULL, "Index_type", OPEN_FULL_TABLE), + Column("COMMENT", Varchar(16), NULLABLE, "Comment", OPEN_FRM_ONLY), + Column("INDEX_COMMENT", Varchar(INDEX_COMMENT_MAXLEN), + NOT_NULL, "Index_comment",OPEN_FRM_ONLY), + Column("IGNORED", Varchar(3), NOT_NULL, "Ignored", OPEN_FRM_ONLY), + CEnd() +}; + + +ST_FIELD_INFO view_fields_info[]= +{ + Column("TABLE_CATALOG", Catalog(), NOT_NULL, OPEN_FRM_ONLY), + Column("TABLE_SCHEMA", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("TABLE_NAME", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("VIEW_DEFINITION", Longtext(65535), NOT_NULL, OPEN_FRM_ONLY), + Column("CHECK_OPTION", Varchar(8), NOT_NULL, OPEN_FRM_ONLY), + Column("IS_UPDATABLE", Yes_or_empty(), NOT_NULL, OPEN_FULL_TABLE), + Column("DEFINER", Definer(), NOT_NULL, OPEN_FRM_ONLY), + Column("SECURITY_TYPE", Varchar(7), NOT_NULL, OPEN_FRM_ONLY), + Column("CHARACTER_SET_CLIENT", CSName(), NOT_NULL, OPEN_FRM_ONLY), + Column("COLLATION_CONNECTION", CLName(), NOT_NULL, OPEN_FRM_ONLY), + Column("ALGORITHM", Varchar(10),NOT_NULL, OPEN_FRM_ONLY), + CEnd() +}; + + +ST_FIELD_INFO user_privileges_fields_info[]= +{ + Column("GRANTEE", Userhost(), NOT_NULL), + Column("TABLE_CATALOG", Catalog(), NOT_NULL), + Column("PRIVILEGE_TYPE", Name(), NOT_NULL), + Column("IS_GRANTABLE", Yes_or_empty(), NOT_NULL), + CEnd() +}; + + +ST_FIELD_INFO schema_privileges_fields_info[]= +{ + Column("GRANTEE", Userhost(), NOT_NULL), + Column("TABLE_CATALOG", Catalog(), NOT_NULL), + Column("TABLE_SCHEMA", Name(), NOT_NULL), + Column("PRIVILEGE_TYPE", Name(), NOT_NULL), + Column("IS_GRANTABLE", Yes_or_empty(), NOT_NULL), + CEnd() +}; + + +ST_FIELD_INFO table_privileges_fields_info[]= +{ + Column("GRANTEE", Userhost(), NOT_NULL), + Column("TABLE_CATALOG", Catalog(), NOT_NULL), + Column("TABLE_SCHEMA", Name(), NOT_NULL), + Column("TABLE_NAME", Name(), NOT_NULL), + Column("PRIVILEGE_TYPE", Name(), NOT_NULL), + Column("IS_GRANTABLE", Yes_or_empty(), NOT_NULL), + CEnd() +}; + + +ST_FIELD_INFO column_privileges_fields_info[]= +{ + Column("GRANTEE", Userhost(), NOT_NULL), + Column("TABLE_CATALOG", Catalog(), NOT_NULL), + Column("TABLE_SCHEMA", Name(), NOT_NULL), + Column("TABLE_NAME", Name(), NOT_NULL), + Column("COLUMN_NAME", Name(), NOT_NULL), + Column("PRIVILEGE_TYPE", Name(), NOT_NULL), + Column("IS_GRANTABLE", Yes_or_empty(), NOT_NULL), + CEnd() +}; + + +ST_FIELD_INFO table_constraints_fields_info[]= +{ + Column("CONSTRAINT_CATALOG", Catalog(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_TYPE", Name(), NOT_NULL, OPEN_FULL_TABLE), + CEnd() +}; + + +ST_FIELD_INFO key_column_usage_fields_info[]= +{ + Column("CONSTRAINT_CATALOG", Catalog(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_CATALOG", Catalog(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("COLUMN_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("ORDINAL_POSITION", SLonglong(10), NOT_NULL, OPEN_FULL_TABLE), + Column("POSITION_IN_UNIQUE_CONSTRAINT", SLonglong(10), NULLABLE, OPEN_FULL_TABLE), + Column("REFERENCED_TABLE_SCHEMA", Name(), NULLABLE, OPEN_FULL_TABLE), + Column("REFERENCED_TABLE_NAME", Name(), NULLABLE, OPEN_FULL_TABLE), + Column("REFERENCED_COLUMN_NAME", Name(), NULLABLE, OPEN_FULL_TABLE), + CEnd() +}; + + +ST_FIELD_INFO table_names_fields_info[]= +{ + Column("TABLE_CATALOG", Catalog(), NOT_NULL), + Column("TABLE_SCHEMA", Name(), NOT_NULL), + Column("TABLE_NAME", Varchar(NAME_CHAR_LEN + MYSQL50_TABLE_NAME_PREFIX_LENGTH), + NOT_NULL, "Tables_in_"), + Column("TABLE_TYPE", Name(), NOT_NULL, "Table_type", OPEN_FRM_ONLY), + CEnd() +}; + + +ST_FIELD_INFO open_tables_fields_info[]= +{ + Column("Database", Name(), NOT_NULL, "Database"), + Column("Table", Name(), NOT_NULL, "Table"), + Column("In_use", SLonglong(1), NOT_NULL, "In_use"), + Column("Name_locked", SLonglong(4), NOT_NULL, "Name_locked"), + CEnd() +}; + + +ST_FIELD_INFO triggers_fields_info[]= +{ + Column("TRIGGER_CATALOG", Catalog(), NOT_NULL, OPEN_FRM_ONLY), + Column("TRIGGER_SCHEMA", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("TRIGGER_NAME", Name(), NOT_NULL, "Trigger", OPEN_FRM_ONLY), + Column("EVENT_MANIPULATION", Varchar(6), NOT_NULL, "Event", OPEN_FRM_ONLY), + Column("EVENT_OBJECT_CATALOG", Catalog(), NOT_NULL, OPEN_FRM_ONLY), + Column("EVENT_OBJECT_SCHEMA", Name(), NOT_NULL, OPEN_FRM_ONLY), + Column("EVENT_OBJECT_TABLE", Name(), NOT_NULL, "Table", OPEN_FRM_ONLY), + Column("ACTION_ORDER", SLonglong(4), NOT_NULL, OPEN_FRM_ONLY), + Column("ACTION_CONDITION", Longtext(65535), NULLABLE, OPEN_FRM_ONLY), + Column("ACTION_STATEMENT", Longtext(65535), NOT_NULL, "Statement",OPEN_FRM_ONLY), + Column("ACTION_ORIENTATION", Varchar(9), NOT_NULL, OPEN_FRM_ONLY), + Column("ACTION_TIMING", Varchar(6), NOT_NULL, "Timing", OPEN_FRM_ONLY), + Column("ACTION_REFERENCE_OLD_TABLE",Name(), NULLABLE, OPEN_FRM_ONLY), + Column("ACTION_REFERENCE_NEW_TABLE",Name(), NULLABLE, OPEN_FRM_ONLY), + Column("ACTION_REFERENCE_OLD_ROW",Varchar(3),NOT_NULL, OPEN_FRM_ONLY), + Column("ACTION_REFERENCE_NEW_ROW",Varchar(3),NOT_NULL, OPEN_FRM_ONLY), + /* 2 here indicates 2 decimals */ + Column("CREATED", Datetime(2), NULLABLE, "Created", OPEN_FRM_ONLY), + Column("SQL_MODE", SQLMode(), NOT_NULL, "sql_mode", OPEN_FRM_ONLY), + Column("DEFINER", Definer(), NOT_NULL, "Definer", OPEN_FRM_ONLY), + Column("CHARACTER_SET_CLIENT", CSName(), NOT_NULL, "character_set_client", + OPEN_FRM_ONLY), + Column("COLLATION_CONNECTION", CLName(), NOT_NULL, "collation_connection", + OPEN_FRM_ONLY), + Column("DATABASE_COLLATION", CLName(), NOT_NULL, "Database Collation", + OPEN_FRM_ONLY), + CEnd() +}; + + +ST_FIELD_INFO partitions_fields_info[]= +{ + Column("TABLE_CATALOG", Catalog(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("PARTITION_NAME", Name(), NULLABLE, OPEN_FULL_TABLE), + Column("SUBPARTITION_NAME", Name(), NULLABLE, OPEN_FULL_TABLE), + Column("PARTITION_ORDINAL_POSITION", ULonglong(), NULLABLE, OPEN_FULL_TABLE), + Column("SUBPARTITION_ORDINAL_POSITION",ULonglong(),NULLABLE, OPEN_FULL_TABLE), + Column("PARTITION_METHOD", Varchar(18), NULLABLE, OPEN_FULL_TABLE), + Column("SUBPARTITION_METHOD", Varchar(12), NULLABLE, OPEN_FULL_TABLE), + Column("PARTITION_EXPRESSION", Longtext(65535), NULLABLE, OPEN_FULL_TABLE), + Column("SUBPARTITION_EXPRESSION", Longtext(65535), NULLABLE, OPEN_FULL_TABLE), + Column("PARTITION_DESCRIPTION", Longtext(65535), NULLABLE, OPEN_FULL_TABLE), + Column("TABLE_ROWS", ULonglong(), NOT_NULL, OPEN_FULL_TABLE), + Column("AVG_ROW_LENGTH", ULonglong(), NOT_NULL, OPEN_FULL_TABLE), + Column("DATA_LENGTH", ULonglong(), NOT_NULL, OPEN_FULL_TABLE), + Column("MAX_DATA_LENGTH", ULonglong(), NULLABLE, OPEN_FULL_TABLE), + Column("INDEX_LENGTH", ULonglong(), NOT_NULL, OPEN_FULL_TABLE), + Column("DATA_FREE", ULonglong(), NOT_NULL, OPEN_FULL_TABLE), + Column("CREATE_TIME", Datetime(0), NULLABLE, OPEN_FULL_TABLE), + Column("UPDATE_TIME", Datetime(0), NULLABLE, OPEN_FULL_TABLE), + Column("CHECK_TIME", Datetime(0), NULLABLE, OPEN_FULL_TABLE), + Column("CHECKSUM", ULonglong(), NULLABLE, OPEN_FULL_TABLE), + Column("PARTITION_COMMENT", Varchar(80), NOT_NULL, OPEN_FULL_TABLE), + Column("NODEGROUP", Varchar(12), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLESPACE_NAME", Name(), NULLABLE, OPEN_FULL_TABLE), + CEnd() +}; + + +ST_FIELD_INFO variables_fields_info[]= +{ + Column("VARIABLE_NAME", Varchar(64), NOT_NULL, "Variable_name"), + Column("VARIABLE_VALUE", Varchar(2048), NOT_NULL, "Value"), + CEnd() +}; + + +ST_FIELD_INFO sysvars_fields_info[]= +{ + Column("VARIABLE_NAME", Name(), NOT_NULL), + Column("SESSION_VALUE", Varchar(2048), NULLABLE), + Column("GLOBAL_VALUE", Varchar(2048), NULLABLE), + Column("GLOBAL_VALUE_ORIGIN", Name(), NOT_NULL), + Column("DEFAULT_VALUE", Varchar(2048), NULLABLE), + Column("VARIABLE_SCOPE", Name(), NOT_NULL), + Column("VARIABLE_TYPE", Name(), NOT_NULL), + Column("VARIABLE_COMMENT", Varchar(TABLE_COMMENT_MAXLEN), NOT_NULL), + Column("NUMERIC_MIN_VALUE", Varchar(MY_INT64_NUM_DECIMAL_DIGITS), NULLABLE), + Column("NUMERIC_MAX_VALUE", Varchar(MY_INT64_NUM_DECIMAL_DIGITS), NULLABLE), + Column("NUMERIC_BLOCK_SIZE", Varchar(MY_INT64_NUM_DECIMAL_DIGITS), NULLABLE), + Column("ENUM_VALUE_LIST", Longtext(65535), NULLABLE), + Column("READ_ONLY", Yes_or_empty(), NOT_NULL), + Column("COMMAND_LINE_ARGUMENT",Name(), NULLABLE), + Column("GLOBAL_VALUE_PATH", Varchar(2048), NULLABLE), + CEnd() +}; + + +ST_FIELD_INFO processlist_fields_info[]= +{ + Column("ID", SLonglong(4), NOT_NULL, "Id"), + Column("USER", Varchar(USERNAME_CHAR_LENGTH), NOT_NULL, "User"), + Column("HOST", Varchar(LIST_PROCESS_HOST_LEN),NOT_NULL, "Host"), + Column("DB", Name(), NULLABLE, "Db"), + Column("COMMAND", Varchar(16), NOT_NULL, "Command"), + Column("TIME", SLong(7), NOT_NULL, "Time"), + Column("STATE", Varchar(64), NULLABLE, "State"), + Column("INFO", Longtext(PROCESS_LIST_INFO_WIDTH), + NULLABLE, "Info"), + Column("TIME_MS", Decimal(100 * (MY_INT64_NUM_DECIMAL_DIGITS + 1) + 3), + NOT_NULL, "Time_ms"), + Column("STAGE", STiny(2), NOT_NULL, "Stage"), + Column("MAX_STAGE", STiny(2), NOT_NULL, "Max_stage"), + Column("PROGRESS", Decimal(703), NOT_NULL, "Progress"), + Column("MEMORY_USED", SLonglong(7), NOT_NULL, "Memory_used"), + Column("MAX_MEMORY_USED",SLonglong(7), NOT_NULL, "Max_memory_used"), + Column("EXAMINED_ROWS", SLong(7), NOT_NULL, "Examined_rows"), + Column("QUERY_ID", SLonglong(4), NOT_NULL), + Column("INFO_BINARY",Blob(PROCESS_LIST_INFO_WIDTH),NULLABLE, "Info_binary"), + Column("TID", SLonglong(4), NOT_NULL, "Tid"), + CEnd() +}; + + +ST_FIELD_INFO plugin_fields_info[]= +{ + Column("PLUGIN_NAME", Name(), NOT_NULL, "Name"), + Column("PLUGIN_VERSION", Varchar(20), NOT_NULL), + Column("PLUGIN_STATUS", Varchar(16), NOT_NULL, "Status"), + Column("PLUGIN_TYPE", Varchar(80), NOT_NULL, "Type"), + Column("PLUGIN_TYPE_VERSION", Varchar(20), NOT_NULL), + Column("PLUGIN_LIBRARY", Name(), NULLABLE, "Library"), + Column("PLUGIN_LIBRARY_VERSION", Varchar(20), NULLABLE), + Column("PLUGIN_AUTHOR", Name(), NULLABLE), + Column("PLUGIN_DESCRIPTION", Longtext(65535), NULLABLE), + Column("PLUGIN_LICENSE", Varchar(80), NOT_NULL, "License"), + Column("LOAD_OPTION", Varchar(64), NOT_NULL), + Column("PLUGIN_MATURITY", Varchar(12), NOT_NULL), + Column("PLUGIN_AUTH_VERSION", Varchar(80), NULLABLE), + CEnd() +}; + +ST_FIELD_INFO files_fields_info[]= +{ + Column("FILE_ID", SLonglong(4), NOT_NULL), + Column("FILE_NAME", Varchar(FN_REFLEN),NULLABLE), + Column("FILE_TYPE", Varchar(20), NOT_NULL), + Column("TABLESPACE_NAME", Name(), NULLABLE), + Column("TABLE_CATALOG", Name(), NOT_NULL), + Column("TABLE_SCHEMA", Name(), NULLABLE), + Column("TABLE_NAME", Name(), NULLABLE), + Column("LOGFILE_GROUP_NAME", Name(), NULLABLE), + Column("LOGFILE_GROUP_NUMBER",SLonglong(4), NULLABLE), + Column("ENGINE", Name(), NOT_NULL), + Column("FULLTEXT_KEYS", Name(), NULLABLE), + Column("DELETED_ROWS", SLonglong(4), NULLABLE), + Column("UPDATE_COUNT", SLonglong(4), NULLABLE), + Column("FREE_EXTENTS", SLonglong(4), NULLABLE), + Column("TOTAL_EXTENTS", SLonglong(4), NULLABLE), + Column("EXTENT_SIZE", SLonglong(4), NOT_NULL), + Column("INITIAL_SIZE", ULonglong(), NULLABLE), + Column("MAXIMUM_SIZE", ULonglong(), NULLABLE), + Column("AUTOEXTEND_SIZE", ULonglong(), NULLABLE), + Column("CREATION_TIME", Datetime(0), NULLABLE), + Column("LAST_UPDATE_TIME", Datetime(0), NULLABLE), + Column("LAST_ACCESS_TIME", Datetime(0), NULLABLE), + Column("RECOVER_TIME", SLonglong(4), NULLABLE), + Column("TRANSACTION_COUNTER", SLonglong(4), NULLABLE), + Column("VERSION", ULonglong(), NULLABLE, "Version"), + Column("ROW_FORMAT", Varchar(10), NULLABLE, "Row_format"), + Column("TABLE_ROWS", ULonglong(), NULLABLE, "Rows"), + Column("AVG_ROW_LENGTH", ULonglong(), NULLABLE, "Avg_row_length"), + Column("DATA_LENGTH", ULonglong(), NULLABLE, "Data_length"), + Column("MAX_DATA_LENGTH", ULonglong(), NULLABLE, "Max_data_length"), + Column("INDEX_LENGTH", ULonglong(), NULLABLE, "Index_length"), + Column("DATA_FREE", ULonglong(), NULLABLE, "Data_free"), + Column("CREATE_TIME", Datetime(0), NULLABLE, "Create_time"), + Column("UPDATE_TIME", Datetime(0), NULLABLE, "Update_time"), + Column("CHECK_TIME", Datetime(0), NULLABLE, "Check_time"), + Column("CHECKSUM", ULonglong(), NULLABLE, "Checksum"), + Column("STATUS", Varchar(20), NOT_NULL), + Column("EXTRA", Varchar(255), NULLABLE), + CEnd() +}; + +}; // namespace Show + + +void init_fill_schema_files_row(TABLE* table) +{ + int i; + for(i=0; !Show::files_fields_info[i].end_marker(); i++) + table->field[i]->set_null(); + + table->field[IS_FILES_STATUS]->set_notnull(); + table->field[IS_FILES_STATUS]->store("NORMAL", 6, system_charset_info); +} + + +namespace Show { + +ST_FIELD_INFO referential_constraints_fields_info[]= +{ + Column("CONSTRAINT_CATALOG", Catalog(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("UNIQUE_CONSTRAINT_CATALOG", Catalog(), NOT_NULL, OPEN_FULL_TABLE), + Column("UNIQUE_CONSTRAINT_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("UNIQUE_CONSTRAINT_NAME", Name(), NULLABLE, OPEN_FULL_TABLE), + Column("MATCH_OPTION", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("UPDATE_RULE", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("DELETE_RULE", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("REFERENCED_TABLE_NAME", Name(), NULLABLE, OPEN_FULL_TABLE), + CEnd() +}; + + +ST_FIELD_INFO parameters_fields_info[]= +{ + Column("SPECIFIC_CATALOG", Catalog(), NOT_NULL, OPEN_FULL_TABLE), + Column("SPECIFIC_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("SPECIFIC_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("ORDINAL_POSITION", SLong(21), NOT_NULL, OPEN_FULL_TABLE), + Column("PARAMETER_MODE", Varchar(5), NULLABLE, OPEN_FULL_TABLE), + Column("PARAMETER_NAME", Name(), NULLABLE, OPEN_FULL_TABLE), + Column("DATA_TYPE", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("CHARACTER_MAXIMUM_LENGTH",SLong(21), NULLABLE, OPEN_FULL_TABLE), + Column("CHARACTER_OCTET_LENGTH", SLong(21), NULLABLE, OPEN_FULL_TABLE), + Column("NUMERIC_PRECISION", SLong(21), NULLABLE, OPEN_FULL_TABLE), + Column("NUMERIC_SCALE", SLong(21), NULLABLE, OPEN_FULL_TABLE), + Column("DATETIME_PRECISION", ULonglong(), NULLABLE, OPEN_FRM_ONLY), + Column("CHARACTER_SET_NAME", Varchar(64), NULLABLE, OPEN_FULL_TABLE), + Column("COLLATION_NAME", Varchar(64), NULLABLE, OPEN_FULL_TABLE), + Column("DTD_IDENTIFIER", Longtext(65535), NOT_NULL, OPEN_FULL_TABLE), + Column("ROUTINE_TYPE", Varchar(9), NOT_NULL, OPEN_FULL_TABLE), + CEnd() +}; + + +ST_FIELD_INFO tablespaces_fields_info[]= +{ + Column("TABLESPACE_NAME", Name(), NOT_NULL), + Column("ENGINE", Name(), NOT_NULL), + Column("TABLESPACE_TYPE", Name(), NULLABLE), + Column("LOGFILE_GROUP_NAME", Name(), NULLABLE), + Column("EXTENT_SIZE", ULonglong(), NULLABLE), + Column("AUTOEXTEND_SIZE", ULonglong(), NULLABLE), + Column("MAXIMUM_SIZE", ULonglong(), NULLABLE), + Column("NODEGROUP_ID", ULonglong(), NULLABLE), + Column("TABLESPACE_COMMENT", Varchar(2048), NULLABLE), + CEnd() +}; + + +ST_FIELD_INFO keycache_fields_info[]= +{ + Column("KEY_CACHE_NAME",Varchar(NAME_LEN),NOT_NULL), + Column("SEGMENTS", ULong(3), NULLABLE), + Column("SEGMENT_NUMBER", ULong(3), NULLABLE), + Column("FULL_SIZE", ULonglong(), NOT_NULL), + Column("BLOCK_SIZE", ULonglong(), NOT_NULL), + Column("USED_BLOCKS", ULonglong(), NOT_NULL, "Key_blocks_used"), + Column("UNUSED_BLOCKS", ULonglong(), NOT_NULL, "Key_blocks_unused"), + Column("DIRTY_BLOCKS", ULonglong(), NOT_NULL, "Key_blocks_not_flushed"), + Column("READ_REQUESTS", ULonglong(), NOT_NULL, "Key_read_requests"), + Column("READS", ULonglong(), NOT_NULL, "Key_reads"), + Column("WRITE_REQUESTS", ULonglong(), NOT_NULL, "Key_write_requests"), + Column("WRITES", ULonglong(), NOT_NULL, "Key_writes"), + CEnd() +}; + + +ST_FIELD_INFO show_explain_tabular_fields_info[]= +{ + Column("id", SLonglong(3), NULLABLE, "id"), + Column("select_type", Varchar(19), NOT_NULL, "select_type"), + Column("table", Name(), NULLABLE, "table"), + Column("type", Varchar(15), NULLABLE, "type"), + Column("possible_keys",Varchar(NAME_CHAR_LEN*MAX_KEY), NULLABLE, "possible_keys"), + Column("key", Varchar(NAME_CHAR_LEN*MAX_KEY), NULLABLE, "key"), + Column("key_len", Varchar(NAME_CHAR_LEN*MAX_KEY), NULLABLE, "key_len"), + Column("ref", Varchar(NAME_CHAR_LEN*MAX_REF_PARTS),NULLABLE, "ref"), + Column("rows", SLonglong(10), NULLABLE, "rows"), + Column("Extra", Varchar(255), NOT_NULL, "Extra"), + CEnd() +}; + + +ST_FIELD_INFO show_explain_json_fields_info[]= +{ + Column("EXPLAIN", Longtext(MAX_FIELD_VARCHARLENGTH), NOT_NULL, "SHOW EXPLAIN"), + CEnd() +}; + + +ST_FIELD_INFO show_analyze_tabular_fields_info[]= +{ + Column("id", SLonglong(3), NULLABLE, "id"), + Column("select_type", Varchar(19), NOT_NULL, "select_type"), + Column("table", Name(), NULLABLE, "table"), + Column("type", Varchar(15), NULLABLE, "type"), + Column("possible_keys",Varchar(NAME_CHAR_LEN*MAX_KEY), NULLABLE, "possible_keys"), + Column("key", Varchar(NAME_CHAR_LEN*MAX_KEY), NULLABLE, "key"), + Column("key_len", Varchar(NAME_CHAR_LEN*MAX_KEY), NULLABLE, "key_len"), + Column("ref", Varchar(NAME_CHAR_LEN*MAX_REF_PARTS),NULLABLE, "ref"), + Column("rows", SLonglong(10), NULLABLE, "rows"), + Column("r_rows", Varchar(NAME_CHAR_LEN), NULLABLE, "r_rows"), + + /* Fields of type DECIMAL(5,2) to represent percentage. + See Show::Type::decimal_precision() and Show::Type::decimal_scale() to learn + how 502 converts to precision and scale (5 and 2)*/ + Column("filtered", Decimal(502), NULLABLE, "filtered"), + Column("r_filtered", Decimal(502), NULLABLE, "r_filtered"), + Column("Extra", Varchar(255), NOT_NULL, "Extra"), + CEnd() +}; + + +ST_FIELD_INFO show_analyze_json_fields_info[]= { + Column("ANALYZE", Longtext(MAX_FIELD_VARCHARLENGTH), NOT_NULL, "SHOW ANALYZE"), + CEnd()}; + + +ST_FIELD_INFO check_constraints_fields_info[]= +{ + Column("CONSTRAINT_CATALOG", Catalog(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_SCHEMA", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("TABLE_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("CONSTRAINT_NAME", Name(), NOT_NULL, OPEN_FULL_TABLE), + Column("LEVEL", Varchar(6),NOT_NULL, OPEN_FULL_TABLE), + Column("CHECK_CLAUSE", Longtext(MAX_FIELD_VARCHARLENGTH), + NOT_NULL, OPEN_FULL_TABLE), + CEnd() +}; + +}; // namespace Show + + +namespace Show { + +/** For creating fields of information_schema.OPTIMIZER_TRACE */ +extern ST_FIELD_INFO optimizer_trace_info[]; + +} //namespace Show + +/* + Description of ST_FIELD_INFO in sql_i_s.h + + Make sure that the order of schema_tables and enum_schema_tables are the same. + +*/ + +ST_SCHEMA_TABLE schema_tables[]= +{ + {"ALL_PLUGINS", Show::plugin_fields_info, 0, + fill_all_plugins, make_old_format, 0, 5, -1, 0, 0}, + {"APPLICABLE_ROLES", Show::applicable_roles_fields_info, 0, + fill_schema_applicable_roles, 0, 0, -1, -1, 0, 0}, + {"CHARACTER_SETS", Show::charsets_fields_info, 0, + fill_schema_charsets, make_character_sets_old_format, 0, -1, -1, 0, 0}, + {"CHECK_CONSTRAINTS", Show::check_constraints_fields_info, 0, + get_all_tables, 0, + get_check_constraints_record, 1, 2, 0, OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, + {"COLLATIONS", Show::collation_fields_info, 0, + fill_schema_collation, make_old_format, 0, -1, -1, 0, 0}, + {"COLLATION_CHARACTER_SET_APPLICABILITY", Show::coll_charset_app_fields_info, + 0, fill_schema_coll_charset_app, 0, 0, -1, -1, 0, 0}, + {"COLUMNS", Show::columns_fields_info, 0, + get_all_tables, make_columns_old_format, get_schema_column_record, 1, 2, 0, + OPTIMIZE_I_S_TABLE|OPEN_VIEW_FULL}, + {"COLUMN_PRIVILEGES", Show::column_privileges_fields_info, 0, + fill_schema_column_privileges, 0, 0, -1, -1, 0, 0}, + {"ENABLED_ROLES", Show::enabled_roles_fields_info, 0, + fill_schema_enabled_roles, 0, 0, -1, -1, 0, 0}, + {"ENGINES", Show::engines_fields_info, 0, + fill_schema_engines, make_old_format, 0, -1, -1, 0, 0}, +#ifdef HAVE_EVENT_SCHEDULER + {"EVENTS", Show::events_fields_info, 0, + Events::fill_schema_events, make_old_format, 0, -1, -1, 0, 0}, +#else + {"EVENTS", Show::events_fields_info, 0, + 0, make_old_format, 0, -1, -1, 0, 0}, +#endif + {"EXPLAIN", Show::show_explain_tabular_fields_info, 0, + fill_show_explain_tabular, make_old_format, 0, -1, -1, + TRUE /*hidden*/ , 0}, + {"EXPLAIN_JSON", Show::show_explain_json_fields_info, 0, + fill_show_explain_json, make_old_format, 0, -1, -1, + TRUE /*hidden*/ , 0}, + {"ANALYZE", Show::show_analyze_tabular_fields_info, 0, + fill_show_analyze_tabular, make_old_format, 0, -1, -1, + TRUE /*hidden*/, 0}, + {"ANALYZE_JSON", Show::show_analyze_json_fields_info, 0, + fill_show_analyze_json, make_old_format, 0, -1, -1, + TRUE /*hidden*/, 0}, + {"FILES", Show::files_fields_info, 0, + hton_fill_schema_table, 0, 0, -1, -1, 0, 0}, + {"GLOBAL_STATUS", Show::variables_fields_info, 0, + fill_status, make_old_format, 0, 0, -1, 0, 0}, + {"GLOBAL_VARIABLES", Show::variables_fields_info, 0, + fill_variables, make_old_format, 0, 0, -1, 0, 0}, + {"KEYWORDS", Show::keywords_field_info, 0, + fill_i_s_keywords, 0, 0, -1, -1, 0, 0}, + {"KEY_CACHES", Show::keycache_fields_info, 0, + fill_key_cache_tables, 0, 0, -1,-1, 0, 0}, + {"KEY_COLUMN_USAGE", Show::key_column_usage_fields_info, 0, + get_all_tables, 0, get_schema_key_column_usage_record, 4, 5, 0, + OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, + {"OPEN_TABLES", Show::open_tables_fields_info, 0, + fill_open_tables, make_old_format, 0, -1, -1, 1, 0}, + {"OPTIMIZER_TRACE", Show::optimizer_trace_info, 0, + fill_optimizer_trace_info, NULL, NULL, -1, -1, false, 0}, + {"PARAMETERS", Show::parameters_fields_info, 0, + fill_schema_proc, 0, 0, 1, 2, 0, 0}, + {"PARTITIONS", Show::partitions_fields_info, 0, + get_all_tables, 0, get_schema_partitions_record, 1, 2, 0, + OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, + {"PLUGINS", Show::plugin_fields_info, 0, + fill_plugins, make_old_format, 0, -1, -1, 0, 0}, + {"PROCESSLIST", Show::processlist_fields_info, 0, + fill_schema_processlist, make_old_format, 0, -1, -1, 0, 0}, + {"PROFILING", Show::query_profile_statistics_info, 0, + fill_query_profile_statistics_info, make_profile_table_for_show, + NULL, -1, -1, false, 0}, + {"REFERENTIAL_CONSTRAINTS", Show::referential_constraints_fields_info, + 0, get_all_tables, 0, get_referential_constraints_record, + 1, 9, 0, OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, + {"ROUTINES", Show::proc_fields_info, 0, + fill_schema_proc, make_proc_old_format, 0, 2, 3, 0, 0}, + {"SCHEMATA", Show::schema_fields_info, 0, + fill_schema_schemata, make_schemata_old_format, 0, 1, -1, 0, 0}, + {"SCHEMA_PRIVILEGES", Show::schema_privileges_fields_info, 0, + fill_schema_schema_privileges, 0, 0, -1, -1, 0, 0}, + {"SESSION_STATUS", Show::variables_fields_info, 0, + fill_status, make_old_format, 0, 0, -1, 0, 0}, + {"SESSION_VARIABLES", Show::variables_fields_info, 0, + fill_variables, make_old_format, 0, 0, -1, 0, 0}, + {"STATISTICS", Show::stat_fields_info, 0, + get_all_tables, make_old_format, get_schema_stat_record, 1, 2, 0, + OPEN_TABLE_ONLY|OPTIMIZE_I_S_TABLE}, + {"SQL_FUNCTIONS", Show::sql_functions_field_info, 0, + fill_i_s_sql_functions, 0, 0, -1, -1, 0, 0}, + {"SYSTEM_VARIABLES", Show::sysvars_fields_info, 0, + fill_sysvars, make_old_format, 0, 0, -1, 0, 0}, + {"TABLES", Show::tables_fields_info, 0, + get_all_tables, make_old_format, get_schema_tables_record, 1, 2, 0, + OPTIMIZE_I_S_TABLE}, + {"TABLESPACES", Show::tablespaces_fields_info, 0, + hton_fill_schema_table, 0, 0, -1, -1, 0, 0}, + {"TABLE_CONSTRAINTS", Show::table_constraints_fields_info, 0, + get_all_tables, 0, get_schema_constraints_record, 3, 4, 0, + OPTIMIZE_I_S_TABLE|OPEN_TABLE_ONLY}, + {"TABLE_NAMES", Show::table_names_fields_info, 0, + get_all_tables, make_table_names_old_format, 0, 1, 2, 1, OPTIMIZE_I_S_TABLE}, + {"TABLE_PRIVILEGES", Show::table_privileges_fields_info, 0, + fill_schema_table_privileges, 0, 0, -1, -1, 0, 0}, + {"TRIGGERS", Show::triggers_fields_info, 0, + get_all_tables, make_old_format, get_schema_triggers_record, 5, 6, 0, + OPEN_TRIGGER_ONLY|OPTIMIZE_I_S_TABLE}, + {"USER_PRIVILEGES", Show::user_privileges_fields_info, 0, + fill_schema_user_privileges, 0, 0, -1, -1, 0, 0}, + {"VIEWS", Show::view_fields_info, 0, + get_all_tables, 0, get_schema_views_record, 1, 2, 0, + OPEN_VIEW_ONLY|OPTIMIZE_I_S_TABLE}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +}; + + +int initialize_schema_table(st_plugin_int *plugin) +{ + ST_SCHEMA_TABLE *schema_table; + DBUG_ENTER("initialize_schema_table"); + + if (!(schema_table= (ST_SCHEMA_TABLE *)my_malloc(key_memory_ST_SCHEMA_TABLE, + sizeof(ST_SCHEMA_TABLE), + MYF(MY_WME | MY_ZEROFILL)))) + DBUG_RETURN(1); + /* Historical Requirement */ + plugin->data= schema_table; // shortcut for the future + if (plugin->plugin->init) + { + schema_table->idx_field1= -1, + schema_table->idx_field2= -1; + + /* Make the name available to the init() function. */ + schema_table->table_name= plugin->name.str; + + if (plugin->plugin->init(schema_table)) + { + sql_print_error("Plugin '%s' init function returned error.", + plugin->name.str); + plugin->data= NULL; + my_free(schema_table); + DBUG_RETURN(1); + } + + if (!schema_table->old_format) + for (ST_FIELD_INFO *f= schema_table->fields_info; !f->end_marker(); f++) + if (f->old_name().str && f->old_name().str[0]) + { + schema_table->old_format= make_old_format; + break; + } + + /* Make sure the plugin name is not set inside the init() function. */ + schema_table->table_name= plugin->name.str; + } + DBUG_RETURN(0); +} + +int finalize_schema_table(st_plugin_int *plugin) +{ + int deinit_status= 0; + ST_SCHEMA_TABLE *schema_table= (ST_SCHEMA_TABLE *)plugin->data; + DBUG_ENTER("finalize_schema_table"); + + if (schema_table) + { + if (plugin->plugin->deinit) + deinit_status= plugin->plugin->deinit(NULL); + my_free(schema_table); + } + DBUG_RETURN(deinit_status); +} + + +/** + Output trigger information (SHOW CREATE TRIGGER) to the client. + + @param thd Thread context. + @param trigger Trigger to dump + + @return Operation status + @retval TRUE Error. + @retval FALSE Success. +*/ + +static bool show_create_trigger_impl(THD *thd, Trigger *trigger) +{ + int ret_code; + Protocol *p= thd->protocol; + List<Item> fields; + LEX_CSTRING trg_sql_mode_str, trg_body; + LEX_CSTRING trg_sql_original_stmt; + LEX_STRING trg_definer; + CHARSET_INFO *trg_client_cs; + MEM_ROOT *mem_root= thd->mem_root; + char definer_holder[USER_HOST_BUFF_SIZE]; + trg_definer.str= definer_holder; + + /* + TODO: Check privileges here. This functionality will be added by + implementation of the following WL items: + - WL#2227: New privileges for new objects + - WL#3482: Protect SHOW CREATE PROCEDURE | FUNCTION | VIEW | TRIGGER + properly + + SHOW TRIGGERS and I_S.TRIGGERS will be affected too. + */ + + /* Prepare trigger "object". */ + + trigger->get_trigger_info(&trg_sql_original_stmt, &trg_body, &trg_definer); + sql_mode_string_representation(thd, trigger->sql_mode, &trg_sql_mode_str); + + /* Resolve trigger client character set. */ + myf utf8_flag= thd->get_utf8_flag(); + if (resolve_charset(trigger->client_cs_name.str, NULL, &trg_client_cs, + MYF(utf8_flag))) + return TRUE; + + /* Send header. */ + + fields.push_back(new (mem_root) Item_empty_string(thd, "Trigger", NAME_LEN), + mem_root); + fields.push_back(new (mem_root) + Item_empty_string(thd, "sql_mode", (uint)trg_sql_mode_str.length), + mem_root); + + { + /* + NOTE: SQL statement field must be not less than 1024 in order not to + confuse old clients. + */ + + Item_empty_string *stmt_fld= + new (mem_root) Item_empty_string(thd, "SQL Original Statement", + (uint)MY_MAX(trg_sql_original_stmt.length, + 1024)); + + stmt_fld->set_maybe_null(); + + fields.push_back(stmt_fld, mem_root); + } + + fields.push_back(new (mem_root) + Item_empty_string(thd, "character_set_client", + MY_CS_CHARACTER_SET_NAME_SIZE), + mem_root); + + fields.push_back(new (mem_root) + Item_empty_string(thd, "collation_connection", + MY_CS_COLLATION_NAME_SIZE), + mem_root); + + fields.push_back(new (mem_root) + Item_empty_string(thd, "Database Collation", + MY_CS_COLLATION_NAME_SIZE), + mem_root); + + static const Datetime zero_datetime(Datetime::zero()); + Item_datetime_literal *tmp= (new (mem_root) + Item_datetime_literal(thd, &zero_datetime, 2)); + tmp->set_name(thd, Lex_cstring(STRING_WITH_LEN("Created"))); + fields.push_back(tmp, mem_root); + + if (p->send_result_set_metadata(&fields, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + return TRUE; + + /* Send data. */ + + p->prepare_for_resend(); + + p->store(trigger->name.str, + trigger->name.length, + system_charset_info); + + p->store(trg_sql_mode_str.str, + trg_sql_mode_str.length, + system_charset_info); + + p->store(trg_sql_original_stmt.str, + trg_sql_original_stmt.length, + trg_client_cs); + + p->store(&trigger->client_cs_name, system_charset_info); + + p->store(&trigger->connection_cl_name, system_charset_info); + + p->store(&trigger->db_cl_name, system_charset_info); + + if (trigger->hr_create_time.val) + { + MYSQL_TIME timestamp; + thd->variables.time_zone-> + gmt_sec_to_TIME(×tamp, + (my_time_t) + hrtime_to_time(trigger->hr_create_time)); + timestamp.second_part= hrtime_sec_part(trigger->hr_create_time); + p->store_datetime(×tamp, 2); + } + else + p->store_null(); + + + ret_code= p->write(); + + if (!ret_code) + my_eof(thd); + + return ret_code != 0; +} + + +/** + Read TRN and TRG files to obtain base table name for the specified + trigger name and construct TABE_LIST object for the base table. + + @param thd Thread context. + @param trg_name Trigger name. + + @return TABLE_LIST object corresponding to the base table. + + TODO: This function is a copy&paste from add_table_to_list() and + sp_add_to_query_tables(). The problem is that in order to be compatible + with Stored Programs (Prepared Statements), we should not touch thd->lex. + The "source" functions also add created TABLE_LIST object to the + thd->lex->query_tables. + + The plan to eliminate this copy&paste is to: + + - get rid of sp_add_to_query_tables() and use Lex::add_table_to_list(). + Only add_table_to_list() must be used to add tables from the parser + into Lex::query_tables list. + + - do not update Lex::query_tables in add_table_to_list(). +*/ + +static +TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name) +{ + char trn_path_buff[FN_REFLEN]; + LEX_CSTRING trn_path= { trn_path_buff, 0 }; + LEX_CSTRING db; + LEX_CSTRING tbl_name; + TABLE_LIST *table; + + build_trn_path(thd, trg_name, (LEX_STRING*) &trn_path); + + if (check_trn_exists(&trn_path)) + { + my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); + return NULL; + } + + if (load_table_name_for_trigger(thd, trg_name, &trn_path, &tbl_name)) + return NULL; + + /* We need to reset statement table list to be PS/SP friendly. */ + if (!(table= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST)))) + return NULL; + + db= trg_name->m_db; + + db.str= thd->strmake(db.str, db.length); + if (lower_case_table_names) + db.length= my_casedn_str(files_charset_info, (char*) db.str); + + tbl_name.str= thd->strmake(tbl_name.str, tbl_name.length); + + if (db.str == NULL || tbl_name.str == NULL) + return NULL; + + table->init_one_table(&db, &tbl_name, 0, TL_IGNORE); + + return table; +} + + +/** + SHOW CREATE TRIGGER high-level implementation. + + @param thd Thread context. + @param trg_name Trigger name. + + @return Operation status + @retval TRUE Error. + @retval FALSE Success. +*/ + +bool show_create_trigger(THD *thd, const sp_name *trg_name) +{ + TABLE_LIST *lst= get_trigger_table(thd, trg_name); + uint num_tables; /* NOTE: unused, only to pass to open_tables(). */ + Table_triggers_list *triggers; + Trigger *trigger; + bool error= TRUE; + + if (!lst) + return TRUE; + + if (check_table_access(thd, TRIGGER_ACL, lst, FALSE, 1, TRUE)) + { + my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "TRIGGER"); + return TRUE; + } + + /* + Metadata locks taken during SHOW CREATE TRIGGER should be released when + the statement completes as it is an information statement. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); + + /* + Open the table by name in order to load Table_triggers_list object. + */ + if (open_tables(thd, &lst, &num_tables, + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)) + { + my_error(ER_TRG_CANT_OPEN_TABLE, MYF(0), + (const char *) trg_name->m_db.str, + (const char *) lst->table_name.str); + + goto exit; + + /* Perform closing actions and return error status. */ + } + + triggers= lst->table->triggers; + + if (!triggers) + { + my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); + goto exit; + } + + trigger= triggers->find_trigger(&trg_name->m_name, 0); + + if (!trigger) + { + my_error(ER_TRG_CORRUPTED_FILE, MYF(0), + (const char *) trg_name->m_db.str, + (const char *) lst->table_name.str); + goto exit; + } + + error= show_create_trigger_impl(thd, trigger); + + /* + NOTE: if show_create_trigger_impl() failed, that means we could not + send data to the client. In this case we simply raise the error + status and client connection will be closed. + */ + +exit: + close_thread_tables(thd); + /* Release any metadata locks taken during SHOW CREATE TRIGGER. */ + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + return error; +} + +class IS_internal_schema_access : public ACL_internal_schema_access +{ +public: + IS_internal_schema_access() = default; + + ~IS_internal_schema_access() = default; + + ACL_internal_access_result check(privilege_t want_access, + privilege_t *save_priv) const; + + const ACL_internal_table_access *lookup(const char *name) const; +}; + +ACL_internal_access_result +IS_internal_schema_access::check(privilege_t want_access, + privilege_t *save_priv) const +{ + want_access &= ~SELECT_ACL; + + /* + We don't allow any simple privileges but SELECT_ACL on + the information_schema database. + */ + if (unlikely((want_access & DB_ACLS) != NO_ACL)) + return ACL_INTERNAL_ACCESS_DENIED; + + /* Always grant SELECT for the information schema. */ + *save_priv|= SELECT_ACL; + + return want_access ? ACL_INTERNAL_ACCESS_CHECK_GRANT : + ACL_INTERNAL_ACCESS_GRANTED; +} + +const ACL_internal_table_access * +IS_internal_schema_access::lookup(const char *name) const +{ + /* There are no per table rules for the information schema. */ + return NULL; +} + +static IS_internal_schema_access is_internal_schema_access; + +void initialize_information_schema_acl() +{ + ACL_internal_schema_registry::register_schema(&INFORMATION_SCHEMA_NAME, + &is_internal_schema_access); +} + + +/** + Dumps a text description of a thread, its security context + (user, host) and the current query. + + @param thd thread context + @param buffer pointer to preferred result buffer + @param length length of buffer + @param max_query_len how many chars of query to copy (0 for all) + + @return Pointer to string +*/ + +extern "C" +char *thd_get_error_context_description(THD *thd, char *buffer, + unsigned int length, + unsigned int max_query_len) +{ + String str(buffer, length, &my_charset_latin1); + const Security_context *sctx= &thd->main_security_ctx; + char header[256]; + size_t len; + + len= my_snprintf(header, sizeof(header), + "MariaDB thread id %u, OS thread handle %lu, query id %llu", + (uint)thd->thread_id, (ulong) thd->real_id, (ulonglong) thd->query_id); + str.length(0); + str.append(header, len); + + if (sctx->host) + { + str.append(' '); + str.append(sctx->host, strlen(sctx->host)); + } + + if (sctx->ip) + { + str.append(' '); + str.append(sctx->ip, strlen(sctx->ip)); + } + + if (sctx->user) + { + str.append(' '); + str.append(sctx->user, strlen(sctx->user)); + } + + /* Don't wait if LOCK_thd_data is used as this could cause a deadlock */ + if (!mysql_mutex_trylock(&thd->LOCK_thd_data)) + { + if (const char *info= thread_state_info(thd)) + { + str.append(' '); + str.append(info, strlen(info)); + } + + if (thd->query()) + { + if (max_query_len < 1) + len= thd->query_length(); + else + len= MY_MIN(thd->query_length(), max_query_len); + str.append('\n'); + str.append(thd->query(), len); + } + mysql_mutex_unlock(&thd->LOCK_thd_data); + } + + if (str.c_ptr_safe() == buffer) + return buffer; + + /* + We have to copy the new string to the destination buffer because the string + was reallocated to a larger buffer to be able to fit. + */ + DBUG_ASSERT(buffer != NULL); + length= MY_MIN(str.length(), length-1); + memcpy(buffer, str.ptr(), length); + /* Make sure that the new string is null terminated */ + buffer[length]= '\0'; + return buffer; +} |