/* 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-1301 USA */ #include "mysql/components/my_service.h" #include "mysql/components/services/log_builtins.h" #include "mysql/plugin.h" #include "sql/mysqld.h" #include "sql/sql_base.h" #include "sql/sql_class.h" // THD #include "sql/sql_table.h" // filename_to_tablename #include "sql/strfunc.h" // casedn #include "sql/sql_parse.h" // sql_command_flags #include "sql/sql_lex.h" // LEX #include "sql/item_func.h" // Item_Func #include "sql/item_cmpfunc.h" // Item_cond #include "sql/dd/cache/dictionary_client.h" // dd::cache::Dictionary_client #include "sql/dd/dd_schema.h" // dd::Schema_MDL_locker #include "sql/dd/string_type.h" // dd::String_type #include "sql/dd/sdi_file.h" // dd::sdi_file #include "sql/auth/auth_acls.h" // *_ACLS #include "sql/auth/auth_common.h" // check_access + check_grant_db #include "mf_wcomp.h" // wild_compare,wild_one,wild_many #include "my_dir.h" // MY_DIR // MySQL 8.0 logger service interface static SERVICE_TYPE(registry) *reg_srv = nullptr; SERVICE_TYPE(log_builtins) *log_bi = nullptr; SERVICE_TYPE(log_builtins_string) *log_bs = nullptr; namespace table_sizes { static const char *IS_TABLE_NAME = "TABLE_SIZES"; // InnoDB FTS /** For storing table info when checking for orphaned tables. */ struct fts_aux_table_t { /** Parent table id */ uint64_t parent_id; /** Table FT index id */ uint64_t index_id; }; /** Check if a table is an FTS auxiliary table name. @param[out] table FTS table info @param[in] name Table name @param[in] len Length of table name @return true if the name matches an auxiliary table name pattern */ bool fts_is_aux_table_name(fts_aux_table_t *table, const char *name, int len) { /** FTS auxiliary table prefix that are common to all FT indexes.*/ const char *FTS_PREFIX = "fts_"; const char *FTS_PREFIX_5_7 = "FTS_"; /** FTS auxiliary table suffixes that are common to all FT indexes. */ const char *fts_common_tables[] = {"being_deleted", "being_deleted_cache", "config", "deleted", "deleted_cache", nullptr}; const char *fts_common_tables_5_7[] = {"BEING_DELETED", "BEING_DELETED_CACHE", "CONFIG", "DELETED", "DELETED_CACHE", nullptr}; /** FTS auxiliary INDEX split intervals. */ const char *fts_index_selector[] = {"index_1", "index_2", "index_3", "index_4", "index_5", "index_6", nullptr}; const char *fts_index_selector_5_7[] = {"INDEX_1", "INDEX_2", "INDEX_3", "INDEX_4", "INDEX_5", "INDEX_6", nullptr}; const char *ptr = name; const char *end = name + len; /* All auxiliary tables are prefixed with "FTS_" and the name length will be at the very least greater than 20 bytes. */ if (ptr != nullptr && len > 20 && (strncmp(ptr, FTS_PREFIX, 4) == 0 || strncmp(ptr, FTS_PREFIX_5_7, 4) == 0)) { /* Skip the prefix. */ ptr += 4; len -= 4; /* Try and read the table id. */ if (sscanf(ptr, "%016" PRIx64, &table->parent_id) != 1) return false; /* Skip the table id. */ ptr = static_cast(memchr(ptr, '_', len)); if (ptr == nullptr) return false; /* Skip the underscore. */ ++ptr; len = end - ptr; /* It's not enough to be a FTS auxiliary table name */ if (len == 0) return false; /* First search the common table suffix array. */ for (int i = 0; fts_common_tables[i] != nullptr; ++i) { if (strncmp(ptr, fts_common_tables[i], len) == 0 || strncmp(ptr, fts_common_tables_5_7[i], len) == 0) { return true; } } /* Could be obsolete common tables. */ if (native_strncasecmp(ptr, "ADDED", len) == 0 || native_strncasecmp(ptr, "STOPWORDS", len) == 0) { return true; } /* Try and read the index id. */ if (sscanf(ptr, "%016" PRIx64, &table->index_id) != 1) return false; /* Skip the index id. */ ptr = static_cast(memchr(ptr, '_', len)); if (ptr == nullptr) return false; /* Skip the underscore. */ ++ptr; len = end - ptr; /* It's not enough to be a FTS auxiliary table name */ if (len == 0) return false; /* Search the FT index specific array. */ for (int i = 0; fts_index_selector[i] != nullptr; ++i) { if (strncmp(ptr, fts_index_selector[i], len) == 0 || strncmp(ptr, fts_index_selector_5_7[i], len) == 0) { return true; } } /* Other FT index specific table(s). */ if (native_strncasecmp(ptr, "DOC_ID", len) == 0) return true; } return false; } /*----------------------------------------------------------------------------*/ struct LOOKUP_FIELD_VALUES { std::map db_values; std::map table_values; }; static bool get_lookup_value(THD *thd, Item_func *item_func, Table_ref *table, LOOKUP_FIELD_VALUES *lookup_field_vals) { ST_SCHEMA_TABLE *schema_table = table->schema_table; ST_FIELD_INFO *field_info = schema_table->fields_info; String foo; item_func->print(thd, &foo, QT_ORDINARY); if (item_func->functype() == Item_func::EQ_FUNC || item_func->functype() == Item_func::EQUAL_FUNC || item_func->functype() == Item_func::LIKE_FUNC) { int idx_field, idx_val; char tmp[MAX_FIELD_WIDTH]; String *tmp_str, str_buf(tmp, sizeof(tmp), system_charset_info); bool is_wildcard = (item_func->functype() == Item_func::LIKE_FUNC); if (item_func->arguments()[0]->type() == Item::FIELD_ITEM && item_func->arguments()[1]->const_item()) { idx_field = 0; idx_val = 1; } else if (item_func->arguments()[1]->type() == Item::FIELD_ITEM && item_func->arguments()[0]->const_item()) { idx_field = 1; idx_val = 0; } else return 0; Item_field *item_field = (Item_field *)item_func->arguments()[idx_field]; if (table->table != item_field->field->table) return 0; tmp_str = item_func->arguments()[idx_val]->val_str(&str_buf); if (!tmp_str || !tmp_str->length()) return 0; if (lower_case_table_names && files_charset_info->casedn_multiply == 1) my_casedn_str(files_charset_info, tmp_str->c_ptr_quick()); if (my_strcasecmp(system_charset_info, item_field->field_name, field_info[0].field_name) == 0) { /* Lookup value is database name */ lookup_field_vals->db_values[dd::String_type(tmp_str->ptr(), tmp_str->length())] = is_wildcard; } else if (my_strcasecmp(system_charset_info, item_field->field_name, field_info[1].field_name) == 0) { /* Lookup value is table name */ lookup_field_vals->table_values[dd::String_type(tmp_str->ptr(), tmp_str->length())] = is_wildcard; } } else if (item_func->functype() == Item_func::IN_FUNC) { Item_func_in *in_func = static_cast(item_func); char tmp[MAX_FIELD_WIDTH]; String *tmp_str, str_buf(tmp, sizeof(tmp), system_charset_info); if (in_func->arguments()[0]->type() != Item::FIELD_ITEM) return 0; Item_field *item_field = (Item_field *)in_func->arguments()[0]; if (table->table != item_field->field->table) return 0; for (uint i = 1; i < in_func->arg_count; ++i) { if (in_func->arguments()[i] == nullptr) continue; tmp_str = in_func->arguments()[i]->val_str(&str_buf); if (!tmp_str || !tmp_str->length()) continue; if (lower_case_table_names && files_charset_info->casedn_multiply == 1) my_casedn_str(files_charset_info, tmp_str->c_ptr_quick()); /* Lookup value is database name */ if (my_strcasecmp(system_charset_info, item_field->field_name, field_info[0].field_name) == 0) { lookup_field_vals->db_values[dd::String_type(tmp_str->ptr(), tmp_str->length())] = false; } else if (my_strcasecmp(system_charset_info, item_field->field_name, field_info[1].field_name) == 0) { lookup_field_vals->table_values[dd::String_type(tmp_str->ptr(), tmp_str->length())] = false; } } } return 0; } static bool calc_lookup_values_from_cond(THD *thd, Item *cond, Table_ref *table, LOOKUP_FIELD_VALUES *lookup_field_vals) { if (!cond) return 0; if (cond->type() == Item::COND_ITEM) { auto func_type = ((Item_cond *)cond)->functype(); if (func_type == Item_func::COND_AND_FUNC) { List_iterator 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; } /*----------------------------------------------------------------------------*/ static bool is_special_db(const dd::String_type &db_name) { /* I_S does not exist and we hide P_S */ return is_infoschema_db(db_name.c_str(), db_name.size()) || is_perfschema_db(db_name.c_str(), db_name.size()); } static int make_db_list(THD *thd, std::vector *db_names, LOOKUP_FIELD_VALUES *lookup_field_vals) { dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client()); std::vector schemas; if (thd->dd_client()->fetch_global_components(&schemas)) return 1; for (const dd::Schema *schema_obj : schemas) { const dd::String_type &db_name = schema_obj->name(); if (db_name.length() > NAME_LEN || is_special_db(db_name)) continue; bool match = true; for (auto &db_value : lookup_field_vals->db_values) { dd::String_type wildx = db_value.first; match = (db_value.second) ? (lower_case_table_names) ? !my_wildcmp(files_charset_info, db_name.c_str(), db_name.c_str() + db_name.length(), wildx.c_str(), wildx.c_str() + wildx.length(), wild_prefix, wild_one, wild_many) : !wild_compare(db_name.c_str(), db_name.length(), wildx.c_str(), wildx.length(), 0) : (my_strcasecmp(files_charset_info, db_name.c_str(), wildx.c_str()) == 0); if (match) break; } if (match) db_names->push_back(db_name); } return 0; } /*----------------------------------------------------------------------------*/ typedef std::map Table_sizes_map; static bool find_tables(THD *thd, Table_sizes_map &tables, Table_ref &db_data, std::map &wild) { MY_DIR *dirp; DBUG_ENTER("find_files"); char path[FN_REFLEN + 1]; build_table_filename(path, sizeof(path) - 1, db_data.db, "", "", 0); if (!(dirp = my_dir(path, MYF(MY_WANT_STAT)))) { if (my_errno() == ENOENT) my_error(ER_BAD_DB_ERROR, MYF(0), db_data.db); else { char errbuf[MYSYS_STRERROR_SIZE]; my_error(ER_CANT_READ_DIR, MYF(0), path, my_errno(), my_strerror(errbuf, sizeof(errbuf), my_errno())); } return true; } /* check global privilege */ bool has_global_show = (thd->security_context() ->master_access(db_data.db) & (DB_OP_ACLS | SHOW_DB_ACL)); char uname[NAME_LEN + 1]; /* Unencoded name */ dd::String_type fts_schema_name, fts_table_name; /* FTS lookup */ std::map fts_cache; tables.clear(); for (uint i = 0; i < dirp->number_off_files; i++) { FILEINFO *file = dirp->dir_entry + i; /* skip '.', '..' and temp files. */ if ((file->name[0] == '.' && (!file->name[1] || (file->name[1] == '.' && !file->name[2]))) || is_prefix(file->name, tmp_file_prefix)) continue; char *ext; if ((ext = strrchr(file->name, '.'))) { // sdi files are small and their name is truncated // lookup by id is possible but not worth the effort if (!strcmp(ext, dd::sdi_file::EXT.c_str())) continue; *ext = 0; ++ext; } size_t file_name_len; fts_aux_table_t fts_table; if (ext && !strcmp(ext, "ibd") && fts_is_aux_table_name(&fts_table, file->name, strlen(file->name))) { auto entry = fts_cache.find(fts_table.parent_id); if (entry != fts_cache.end()) fts_table_name = entry->second; else if (thd->dd_client()->get_table_name_by_se_private_id("InnoDB", fts_table.parent_id, &fts_schema_name, &fts_table_name)) continue; file_name_len = (my_stpnmov(uname, fts_table_name.c_str(), sizeof(uname)) - uname); } else file_name_len = filename_to_tablename(file->name, uname, sizeof(uname)); auto table_data = tables.find(uname); if (table_data != tables.end()) { table_data->second += file->mystat->st_size; continue; } bool match = true; for (auto &wild_entry : wild) { dd::String_type wildx = wild_entry.first; match = (wild_entry.second) ? (lower_case_table_names) ? !my_wildcmp(files_charset_info, uname, uname + file_name_len, wildx.c_str(), wildx.c_str() + wildx.length(), wild_prefix, wild_one, wild_many) : !wild_compare(uname, file_name_len, wildx.c_str(), wildx.length(), 0) : (my_strcasecmp(files_charset_info, uname, wildx.c_str()) == 0); if (match) break; } if (!match) continue; /* Check_grant will grant access if there is any column privileges on all of the tables thanks to the fourth parameter (bool show_table). */ db_data.table_name = uname; db_data.table_name_length = file_name_len; if (!has_global_show && check_grant(thd, SELECT_ACL, &db_data, true, 1, true)) continue; tables.emplace(uname, file->mystat->st_size); } DBUG_PRINT("info", ("found: %ld files", tables.size())); my_dirend(dirp); return false; } static bool make_table_list(THD *thd, Table_sizes_map &table_names, LOOKUP_FIELD_VALUES *lookup_field_vals, Table_ref &db_data) { const dd::Schema *sch_obj = nullptr; if (thd->dd_client()->acquire(db_data.db, &sch_obj) || !sch_obj) return 1; return find_tables(thd, table_names, db_data, lookup_field_vals->table_values); } /*----------------------------------------------------------------------------*/ static ST_FIELD_INFO fields_info[] = { {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, 0}, {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Name", 0}, {"TABLE_SIZE", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, MY_I_S_UNSIGNED, "Table_size", 0}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0} }; static bool can_access_database(THD *thd, Table_ref &db_data) { Security_context *sctx = thd->security_context(); /* check global privileges */ if (sctx->master_access(db_data.db) & (DB_OP_ACLS | SHOW_DB_ACL)) return true; /* check db privileges */ Access_bitmask db_access = sctx->check_db_level_access(thd, db_data.db, db_data.db_length); //if (check_access(thd, SELECT_ACL, db_data.db, &db_access, nullptr, false, true)) // return false; if (db_access) { db_data.grant.privilege = db_access; return true; } /* check specific table privileges */ if (!check_grant_db(thd, db_data.db)) return true; return false; } static int fill_table(THD *thd, Table_ref *tables, Item *cond) { TABLE *table = tables->table; LOOKUP_FIELD_VALUES lookup_field_vals = {}; std::vector db_names; if (calc_lookup_values_from_cond(thd, cond, tables, &lookup_field_vals)) return 0; /* EXPLAIN SELECT */ if (thd->lex->is_explain()) return 0; if (make_db_list(thd, &db_names, &lookup_field_vals)) return 1; for (auto &db_name : db_names) { Table_ref db_data; memset(reinterpret_cast(&db_data), 0, sizeof(db_data)); db_data.db = db_name.c_str(); db_data.db_length = db_name.length(); db_data.grant.privilege = 0; if (!can_access_database(thd, db_data)) continue; // We must make sure the schema is released and unlocked in the right // order. Fail if we are unable to get a meta data lock on the schema // name. dd::Schema_MDL_locker mdl_handler(thd); if (mdl_handler.ensure_locked(db_data.db)) return 1; dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client()); Table_sizes_map tables_data; int res = make_table_list(thd, tables_data, &lookup_field_vals, db_data); if (res) continue; for (auto &table_data : tables_data) { table->field[0]->store(db_data.db, db_data.db_length, system_charset_info); table->field[1]->store(table_data.first.c_str(), table_data.first.size(), system_charset_info); table->field[2]->store(table_data.second, true); if (schema_table_store_record(thd, table)) return 1; } } return 0; } static int view_init(void *ptr) { if (init_logging_service_for_plugin(®_srv, &log_bi, &log_bs)) return 1; LogErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG, "Plugin table_sizes initializing..."); if (reg_srv == nullptr) { LogPluginErrMsg(ERROR_LEVEL, ER_LOG_PRINTF_MSG, "reg_srv is NULL in init"); return 1; } ST_SCHEMA_TABLE *schema_table = (ST_SCHEMA_TABLE*)ptr; schema_table->table_name = IS_TABLE_NAME; schema_table->fields_info = fields_info; schema_table->fill_table = fill_table; schema_table->old_format = nullptr; schema_table->process_table = nullptr; return 0; } static int view_deinit(void *) { LogPluginErrMsg(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG, "Plugin table_sizes de-initializing..."); if (reg_srv == nullptr) LogPluginErrMsg(ERROR_LEVEL, ER_LOG_PRINTF_MSG, "reg_srv is NULL in deinit"); else deinit_logging_service_for_plugin(®_srv, &log_bi, &log_bs); return 0; } static struct st_mysql_information_schema info = { MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION }; } // namespace table_sizes mysql_declare_plugin(table_sizes) { MYSQL_INFORMATION_SCHEMA_PLUGIN, /* type */ &table_sizes::info, /* descriptor */ table_sizes::IS_TABLE_NAME, /* name */ "Manuel Mausz", /* author */ "Fast INFORMATION_SCHEMA table sizes", /* description */ PLUGIN_LICENSE_GPL, /* plugin license */ table_sizes::view_init, /* init function (when loaded) */ nullptr, /* check uninstall function */ table_sizes::view_deinit, /* deinit function (when unloaded) */ 0x0840, /* version = 8.4 */ nullptr, /* status variables */ nullptr, /* system variables */ nullptr, /* reserved information */ 0 /* flags */ } mysql_declare_plugin_end;