summaryrefslogtreecommitdiffstats
path: root/table_sizes.cc
diff options
context:
space:
mode:
Diffstat (limited to 'table_sizes.cc')
-rw-r--r--table_sizes.cc597
1 files changed, 597 insertions, 0 deletions
diff --git a/table_sizes.cc b/table_sizes.cc
new file mode 100644
index 0000000..ec8efc0
--- /dev/null
+++ b/table_sizes.cc
@@ -0,0 +1,597 @@
1/*
2 This program is free software; you can redistribute it and/or modify
3 it under the terms of the GNU General Public License as published by
4 the Free Software Foundation; version 2 of the License.
5
6 This program is distributed in the hope that it will be useful,
7 but WITHOUT ANY WARRANTY; without even the implied warranty of
8 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 GNU General Public License for more details.
10
11 You should have received a copy of the GNU General Public License
12 along with this program; if not, write to the Free Software
13 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
14*/
15
16#include <mysql/plugin.h>
17#include <boost/optional/optional.hpp>
18
19#include "sql/mysqld.h"
20#include "sql/sql_base.h"
21#include "sql/sql_class.h" // THD
22#include "sql/sql_table.h" // filename_to_tablename
23#include "sql/strfunc.h" // casedn
24#include "sql/sql_parse.h" // sql_command_flags
25#include "sql/sql_lex.h" // LEX
26#include "sql/item_func.h" // Item_Func
27#include "sql/item_cmpfunc.h" // Item_cond
28#include "sql/dd/cache/dictionary_client.h" // dd::cache::Dictionary_client
29#include "sql/dd/dd_schema.h" // dd::Schema_MDL_locker
30#include "sql/dd/string_type.h" // dd::String_type
31#include "sql/dd/sdi_file.h" // dd::sdi_file
32#include "sql/auth/auth_acls.h" // *_ACLS
33#include "sql/auth/auth_common.h" // check_grant_db
34#include "mf_wcomp.h" // wild_compare,wild_one,wild_many
35#include "my_dir.h" // MY_DIR
36
37// InnoDB FTS
38/** For storing table info when checking for orphaned tables. */
39struct fts_aux_table_t
40{
41 /** Parent table id */
42 uint64_t parent_id;
43
44 /** Table FT index id */
45 uint64_t index_id;
46};
47
48/** Check if a table is an FTS auxiliary table name.
49@param[out] table FTS table info
50@param[in] name Table name
51@param[in] len Length of table name
52@return true if the name matches an auxiliary table name pattern */
53bool fts_is_aux_table_name(fts_aux_table_t *table, const char *name,
54 int len)
55{
56 /** FTS auxiliary table prefix that are common to all FT indexes.*/
57 const char *FTS_PREFIX = "fts_";
58 const char *FTS_PREFIX_5_7 = "FTS_";
59
60 /** FTS auxiliary table suffixes that are common to all FT indexes. */
61 const char *fts_common_tables[] = {"being_deleted", "being_deleted_cache",
62 "config", "deleted",
63 "deleted_cache", NULL};
64 const char *fts_common_tables_5_7[] = {"BEING_DELETED", "BEING_DELETED_CACHE",
65 "CONFIG", "DELETED",
66 "DELETED_CACHE", NULL};
67
68 /** FTS auxiliary INDEX split intervals. */
69 const char *fts_index_selector[] = {"index_1", "index_2", "index_3", "index_4",
70 "index_5", "index_6", NULL};
71 const char *fts_index_selector_5_7[] = {"INDEX_1", "INDEX_2", "INDEX_3", "INDEX_4",
72 "INDEX_5", "INDEX_6", NULL};
73
74 const char *ptr = name;
75 const char *end = name + len;
76
77 /* All auxiliary tables are prefixed with "FTS_" and the name
78 length will be at the very least greater than 20 bytes. */
79 if (ptr != NULL && len > 20 &&
80 (strncmp(ptr, FTS_PREFIX, 4) == 0 ||
81 strncmp(ptr, FTS_PREFIX_5_7, 4) == 0))
82 {
83 /* Skip the prefix. */
84 ptr += 4;
85 len -= 4;
86
87 /* Try and read the table id. */
88 if (sscanf(ptr, "%016" PRIx64, &table->parent_id) != 1)
89 return false;
90
91 /* Skip the table id. */
92 ptr = static_cast<const char *>(memchr(ptr, '_', len));
93 if (ptr == NULL)
94 return false;
95
96 /* Skip the underscore. */
97 ++ptr;
98 len = end - ptr;
99
100 /* First search the common table suffix array. */
101 for (int i = 0; fts_common_tables[i] != NULL; ++i) {
102 if (strncmp(ptr, fts_common_tables[i], len) == 0 ||
103 strncmp(ptr, fts_common_tables_5_7[i], len) == 0) {
104 return true;
105 }
106 }
107
108 /* Could be obsolete common tables. */
109 if (native_strncasecmp(ptr, "ADDED", len) == 0 ||
110 native_strncasecmp(ptr, "STOPWORDS", len) == 0) {
111 return true;
112 }
113
114 /* Try and read the index id. */
115 if (sscanf(ptr, "%016" PRIx64, &table->index_id) != 1)
116 return false;
117
118 /* Skip the index id. */
119 ptr = static_cast<const char *>(memchr(ptr, '_', len));
120
121 if (ptr == NULL)
122 return false;
123
124 /* Skip the underscore. */
125 ++ptr;
126 len = end - ptr;
127
128 /* Search the FT index specific array. */
129 for (int i = 0; fts_index_selector[i] != NULL; ++i) {
130 if (strncmp(ptr, fts_index_selector[i], len) == 0 ||
131 strncmp(ptr, fts_index_selector_5_7[i], len) == 0) {
132 return true;
133 }
134 }
135
136 /* Other FT index specific table(s). */
137 if (native_strncasecmp(ptr, "DOC_ID", len) == 0)
138 return true;
139 }
140
141 return false;
142}
143
144/*----------------------------------------------------------------------------*/
145
146/**
147 Condition pushdown used for INFORMATION_SCHEMA / SHOW queries.
148 This structure is to implement an optimization when
149 accessing data dictionary data in the INFORMATION_SCHEMA
150 or SHOW commands.
151 When the query contain a TABLE_SCHEMA or TABLE_NAME clause,
152 narrow the search for data based on the constraints given.
153*/
154struct LOOKUP_FIELD_VALUES {
155 /**
156 Value of a TABLE_SCHEMA clause.
157 Note that this value length may exceed @c NAME_LEN.
158 */
159 boost::optional<dd::String_type> db_value;
160 /**
161 Value of a TABLE_NAME clause.
162 Note that this value length may exceed @c NAME_LEN.
163 */
164 boost::optional<dd::String_type> table_value;
165};
166
167/**
168 @brief Get lookup value from the part of 'WHERE' condition
169
170 @details This function gets lookup value from
171 the part of 'WHERE' condition if it's possible and
172 fill appropriate lookup_field_vals struct field
173 with this value.
174
175 @param[in] thd thread handler
176 @param[in] item_func part of WHERE condition
177 @param[in] table I_S table
178 @param[in, out] lookup_field_vals Struct which holds lookup values
179
180 @return
181 0 success
182 1 error, there can be no matching records for the condition
183*/
184
185static bool get_lookup_value(Item_func *item_func, Table_ref *table,
186 LOOKUP_FIELD_VALUES *lookup_field_vals)
187{
188 ST_SCHEMA_TABLE *schema_table = table->schema_table;
189 ST_FIELD_INFO *field_info = schema_table->fields_info;
190
191 if (item_func->functype() == Item_func::EQ_FUNC ||
192 item_func->functype() == Item_func::EQUAL_FUNC) {
193 int idx_field, idx_val;
194 char tmp[MAX_FIELD_WIDTH];
195 String *tmp_str, str_buff(tmp, sizeof(tmp), system_charset_info);
196 Item_field *item_field;
197 CHARSET_INFO *cs = system_charset_info;
198
199 if (item_func->arguments()[0]->type() == Item::FIELD_ITEM &&
200 item_func->arguments()[1]->const_item()) {
201 idx_field = 0;
202 idx_val = 1;
203 } else if (item_func->arguments()[1]->type() == Item::FIELD_ITEM &&
204 item_func->arguments()[0]->const_item()) {
205 idx_field = 1;
206 idx_val = 0;
207 } else
208 return 0;
209
210 item_field = (Item_field *)item_func->arguments()[idx_field];
211 if (table->table != item_field->field->table) return 0;
212 tmp_str = item_func->arguments()[idx_val]->val_str(&str_buff);
213
214 /* impossible value */
215 if (!tmp_str) return 1;
216
217 /* Lookup value is database name */
218 if (!cs->coll->strnncollsp(cs, (const uchar *)field_info[0].field_name,
219 strlen(field_info[0].field_name),
220 (const uchar *)item_field->field_name,
221 strlen(item_field->field_name))) {
222 lookup_field_vals->db_value.emplace(tmp_str->ptr(), tmp_str->length());
223 }
224 /* Lookup value is table name */
225 else if (!cs->coll->strnncollsp(cs, (const uchar *)field_info[1].field_name,
226 strlen(field_info[1].field_name),
227 (const uchar *)item_field->field_name,
228 strlen(item_field->field_name))) {
229 lookup_field_vals->table_value.emplace(tmp_str->ptr(), tmp_str->length());
230 }
231 }
232 return 0;
233}
234
235/**
236 @brief Calculates lookup values from 'WHERE' condition
237
238 @details This function calculates lookup value(database name, table name)
239 from 'WHERE' condition if it's possible and
240 fill lookup_field_vals struct fields with these values.
241
242 @param[in] thd thread handler
243 @param[in] cond WHERE condition
244 @param[in] table I_S table
245 @param[in, out] lookup_field_vals Struct which holds lookup values
246
247 @return
248 0 success
249 1 error, there can be no matching records for the condition
250*/
251
252static bool calc_lookup_values_from_cond(Item *cond,
253 Table_ref *table, LOOKUP_FIELD_VALUES *lookup_field_vals)
254{
255 if (!cond) return 0;
256
257 if (cond->type() == Item::COND_ITEM) {
258 if (((Item_cond *)cond)->functype() == Item_func::COND_AND_FUNC) {
259 List_iterator<Item> li(*((Item_cond *)cond)->argument_list());
260 Item *item;
261 while ((item = li++)) {
262 if (item->type() == Item::FUNC_ITEM) {
263 if (get_lookup_value((Item_func *)item, table, lookup_field_vals))
264 return 1;
265 } else {
266 if (calc_lookup_values_from_cond(item, table, lookup_field_vals))
267 return 1;
268 }
269 }
270 }
271 return 0;
272 } else if (cond->type() == Item::FUNC_ITEM &&
273 get_lookup_value((Item_func *)cond, table, lookup_field_vals))
274 return 1;
275 return 0;
276}
277
278/**
279 @brief Calculate lookup values(database name, table name)
280
281 @details This function calculates lookup values(database name, table name)
282 from 'WHERE' condition and fill lookup_field_vals struct field
283 with these values.
284
285 @param[in] cond WHERE condition
286 @param[in] tables I_S table
287 @param[in, out] lookup_field_vals Struct which holds lookup values
288
289 @return
290 0 success
291 1 error, there can be no matching records for the condition
292*/
293
294static bool get_lookup_field_values(Item *cond, Table_ref *tables,
295 LOOKUP_FIELD_VALUES *lookup_field_vals)
296{
297 bool rc = calc_lookup_values_from_cond(cond, tables, lookup_field_vals);
298
299 if (lower_case_table_names && !rc) {
300 /*
301 We can safely do in-place upgrades here since all of the above cases
302 are allocating a new memory buffer for these strings.
303 */
304 if (lookup_field_vals->db_value && !lookup_field_vals->db_value->empty())
305 casedn(system_charset_info, *lookup_field_vals->db_value);
306 if (lookup_field_vals->table_value && !lookup_field_vals->table_value->empty())
307 casedn(system_charset_info, *lookup_field_vals->table_value);
308 }
309
310 return rc;
311}
312
313/*----------------------------------------------------------------------------*/
314
315static bool is_special_db(const dd::String_type &db_name)
316{
317 /* I_S does not exist and we hide P_S */
318 return is_infoschema_db(db_name.c_str(), db_name.size())
319 || is_perfschema_db(db_name.c_str(), db_name.size());
320}
321
322/**
323 Return either single or all schemas
324
325 @param[in] thd thread handler
326 @param[out] db_names list of schemas
327 @param[in] lookup_field_vals Struct which holds lookup values
328
329 @return true if error, false otherwise
330*/
331static int make_db_list(THD *thd, std::vector<dd::String_type> *db_names,
332 LOOKUP_FIELD_VALUES *lookup_field_vals)
333{
334 /*
335 If we have db lookup value we just add it to list and
336 exit from the function.
337 We don't do this for database names longer than the maximum
338 path length.
339 */
340 if (lookup_field_vals->db_value
341 && lookup_field_vals->db_value->length() <= NAME_LEN)
342 {
343 const dd::String_type &db_name = lookup_field_vals->db_value.get();
344 if (!is_special_db(db_name))
345 db_names->push_back(db_name);
346 return 0;
347 }
348
349 dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
350 std::vector<const dd::Schema *> schemas;
351 if (thd->dd_client()->fetch_global_components(&schemas))
352 return 1;
353
354 for (const dd::Schema *schema_obj : schemas)
355 {
356 const dd::String_type &db_name = schema_obj->name();
357 if (is_special_db(db_name))
358 continue;
359 db_names->push_back(db_name);
360 }
361 return 0;
362}
363
364/*----------------------------------------------------------------------------*/
365
366/**
367 Find files in a given directory.
368
369 @param thd thread handler
370 @param tables put found files in this list
371 @param db_name filter for found files
372 @param wild filter for found files
373
374 @return true if error, false otherwise
375*/
376typedef std::map<dd::String_type, size_t> Table_sizes_map;
377bool find_tables(THD *thd, Table_sizes_map &tables,
378 Table_ref &db_data, boost::optional<dd::String_type> &wild)
379{
380 MY_DIR *dirp;
381 DBUG_ENTER("find_files");
382
383 char path[FN_REFLEN + 1];
384 build_table_filename(path, sizeof(path) - 1, db_data.db, "", "", 0);
385
386 if (!(dirp = my_dir(path, MYF(MY_WANT_STAT))))
387 {
388 if (my_errno() == ENOENT)
389 my_error(ER_BAD_DB_ERROR, MYF(0), db_data.db);
390 else
391 {
392 char errbuf[MYSYS_STRERROR_SIZE];
393 my_error(ER_CANT_READ_DIR, MYF(0), path,
394 my_errno(), my_strerror(errbuf, sizeof(errbuf), my_errno()));
395 }
396 return true;
397 }
398
399 char uname[NAME_LEN + 1]; /* Unencoded name */
400 dd::String_type fts_schema_name, fts_table_name; /* FTS lookup */
401 std::map<dd::Object_id, dd::String_type> fts_cache;
402 tables.clear();
403 for (uint i = 0; i < dirp->number_off_files; i++)
404 {
405 FILEINFO *file = dirp->dir_entry + i;
406 /* skip '.', '..' and temp files. */
407 if ((file->name[0] == '.' &&
408 (!file->name[1] || (file->name[1] == '.' && !file->name[2])))
409 || is_prefix(file->name, tmp_file_prefix))
410 continue;
411
412 char *ext;
413 if ((ext = strrchr(file->name, '.')))
414 {
415 // sdi files are small and their name is truncated
416 // lookup by id is possible but not worth the effort
417 if (!strcmp(ext, dd::sdi_file::EXT.c_str()))
418 continue;
419 *ext = 0;
420 ++ext;
421 }
422
423 size_t file_name_len;
424 fts_aux_table_t fts_table;
425 if (ext && !strcmp(ext, "ibd")
426 && fts_is_aux_table_name(&fts_table, file->name, strlen(file->name)))
427 {
428 auto entry = fts_cache.find(fts_table.parent_id);
429 if (entry != fts_cache.end())
430 fts_table_name = entry->second;
431 else if (thd->dd_client()->get_table_name_by_se_private_id("InnoDB",
432 fts_table.parent_id, &fts_schema_name, &fts_table_name))
433 continue;
434
435 file_name_len = (my_stpnmov(uname, fts_table_name.c_str(), sizeof(uname)) - uname);
436 }
437 else
438 file_name_len = filename_to_tablename(file->name, uname, sizeof(uname));
439
440 auto table_data = tables.find(uname);
441 if (table_data != tables.end())
442 {
443 table_data->second += file->mystat->st_size;
444 continue;
445 }
446
447 if (wild)
448 {
449 if (lower_case_table_names)
450 {
451 if (my_wildcmp(files_charset_info,
452 uname, uname + file_name_len,
453 wild->c_str(), wild->c_str() + wild->length(),
454 wild_prefix, wild_one, wild_many))
455 continue;
456 }
457 else if (wild_compare(uname, file_name_len, wild->c_str(), wild->length(), 0))
458 continue;
459 }
460
461 /*
462 Check_grant will grant access if there is any column privileges on
463 all of the tables thanks to the fourth parameter (bool show_table).
464 */
465 db_data.table_name = uname;
466 db_data.table_name_length = file_name_len;
467 if (check_grant(thd, SELECT_ACL, &db_data, true, 1, true))
468 continue;
469
470 tables.emplace(uname, file->mystat->st_size);
471 }
472
473 DBUG_PRINT("info", ("found: %ld files", tables.size()));
474 my_dirend(dirp);
475
476 return false;
477}
478
479static bool make_table_list(THD *thd, Table_sizes_map &table_names,
480 LOOKUP_FIELD_VALUES *lookup_field_vals, Table_ref &db_data)
481{
482 const dd::Schema *sch_obj = NULL;
483 if (thd->dd_client()->acquire(db_data.db, &sch_obj) || !sch_obj)
484 return 1;
485
486 return find_tables(thd, table_names, db_data, lookup_field_vals->table_value);
487}
488
489/*----------------------------------------------------------------------------*/
490
491static ST_FIELD_INFO fields_info[] =
492{
493 {"TABLE_SCHEMA", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, 0},
494 {"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, "Name", 0},
495 {"TABLE_SIZE", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
496 MY_I_S_UNSIGNED, "Table_size", 0},
497 {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0}
498};
499
500static int fill_table(THD *thd, Table_ref *tables, Item *cond)
501{
502 TABLE *table = tables->table;
503 LOOKUP_FIELD_VALUES lookup_field_vals;
504 std::vector<dd::String_type> db_names;
505
506 DBUG_ENTER("fill_table");
507
508 if (get_lookup_field_values(cond, tables, &lookup_field_vals))
509 return 0;
510
511#define STR_OR_NIL(S) ((S) ? (S->c_str()) : "<nil>")
512 DBUG_PRINT("INDEX VALUES", ("db_name='%s', table_name='%s'",
513 STR_OR_NIL(lookup_field_vals.db_value),
514 STR_OR_NIL(lookup_field_vals.table_value)));
515
516 /* EXPLAIN SELECT */
517 if (thd->lex->is_explain())
518 return 0;
519
520 if (make_db_list(thd, &db_names, &lookup_field_vals))
521 return 1;
522
523 for (auto &db_name : db_names)
524 {
525 Table_ref db_data;
526 memset(reinterpret_cast<char *>(&db_data), 0, sizeof(db_data));
527 db_data.db = db_name.c_str();
528 db_data.db_length = db_name.length();
529 db_data.grant.privilege = 0;
530
531 // Get user's global and db-level privileges.
532 if (check_access(thd, SELECT_ACL, db_data.db, &db_data.grant.privilege, NULL, false, true))
533 continue;
534
535 // Now check, if user has access on global level or to any of database/
536 // table/column/routine.
537 if (!(db_data.grant.privilege & DB_OP_ACLS) && check_grant_db(thd, db_data.db))
538 continue;
539
540 // We must make sure the schema is released and unlocked in the right
541 // order. Fail if we are unable to get a meta data lock on the schema
542 // name.
543 dd::Schema_MDL_locker mdl_handler(thd);
544 if (mdl_handler.ensure_locked(db_data.db))
545 return 1;
546
547 dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
548 Table_sizes_map tables_data;
549 int res = make_table_list(thd, tables_data, &lookup_field_vals, db_data);
550 if (res)
551 continue;
552
553 for (auto &table_data : tables_data)
554 {
555 table->field[0]->store(db_data.db, db_data.db_length,
556 system_charset_info);
557 table->field[1]->store(table_data.first.c_str(), table_data.first.size(),
558 system_charset_info);
559 table->field[2]->store(table_data.second, true);
560 if (schema_table_store_record(thd, table))
561 return 1;
562 }
563 }
564
565 return 0;
566}
567
568static int init(void *ptr)
569{
570 ST_SCHEMA_TABLE *schema_table = (ST_SCHEMA_TABLE*)ptr;
571
572 schema_table->fields_info = fields_info;
573 schema_table->fill_table = fill_table;
574 return 0;
575}
576
577static struct st_mysql_information_schema info =
578{ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION };
579
580mysql_declare_plugin(table_sizes)
581{
582 MYSQL_INFORMATION_SCHEMA_PLUGIN,
583 &info, /* type-specific descriptor */
584 "TABLE_SIZES", /* table name */
585 "Manuel Mausz", /* author */
586 "Fast INFORMATION_SCHEMA table sizes", /* description */
587 PLUGIN_LICENSE_GPL, /* license type */
588 init, /* init function */
589 NULL,
590 NULL,
591 0x0800, /* version = 8.0 */
592 NULL, /* no status variables */
593 NULL, /* no system variables */
594 NULL, /* no reserved information */
595 0 /* no flags */
596}
597mysql_declare_plugin_end;