| 1 | /* |
| 2 | Copyright (c) 2008, Roland Bouman |
| 3 | http://rpbouman.blogspot.com/ |
| 4 | roland.bouman@gmail.com |
| 5 | All rights reserved. |
| 6 | |
| 7 | Redistribution and use in source and binary forms, with or without |
| 8 | modification, are permitted provided that the following conditions are met: |
| 9 | * Redistributions of source code must retain the above copyright |
| 10 | notice, this list of conditions and the following disclaimer. |
| 11 | * Redistributions in binary form must reproduce the above copyright |
| 12 | notice, this list of conditions and the following disclaimer in the |
| 13 | documentation and/or other materials provided with the distribution. |
| 14 | * Neither the name of the Roland Bouman nor the |
| 15 | names of the contributors may be used to endorse or promote products |
| 16 | derived from this software without specific prior written permission. |
| 17 | |
| 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 21 | DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
| 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 | */ |
| 29 | |
| 30 | |
| 31 | #ifndef MYSQL_SERVER |
| 32 | #define MYSQL_SERVER |
| 33 | #endif |
| 34 | |
| 35 | #include <my_global.h> |
| 36 | #include <sql_parse.h> // check_global_access |
| 37 | #include <sql_acl.h> // PROCESS_ACL |
| 38 | #include <sql_class.h> // THD |
| 39 | #include <sql_cache.h> |
| 40 | #include <table.h> // ST_SCHEMA_TABLE |
| 41 | #include <set_var.h> // sql_mode_string_representation |
| 42 | #include <tztime.h> |
| 43 | #include <mysql/plugin.h> |
| 44 | |
| 45 | class Accessible_Query_Cache : public Query_cache { |
| 46 | public: |
| 47 | HASH *get_queries() |
| 48 | { |
| 49 | return &this->queries; |
| 50 | } |
| 51 | } *qc; |
| 52 | |
| 53 | bool schema_table_store_record(THD *thd, TABLE *table); |
| 54 | |
| 55 | #define MAX_STATEMENT_TEXT_LENGTH 32767 |
| 56 | #define COLUMN_STATEMENT_SCHEMA 0 |
| 57 | #define COLUMN_STATEMENT_TEXT 1 |
| 58 | #define COLUMN_RESULT_BLOCKS_COUNT 2 |
| 59 | #define COLUMN_RESULT_BLOCKS_SIZE 3 |
| 60 | #define COLUMN_RESULT_BLOCKS_SIZE_USED 4 |
| 61 | #define COLUMN_LIMIT 5 |
| 62 | #define COLUMN_MAX_SORT_LENGTH 6 |
| 63 | #define COLUMN_GROUP_CONCAT_MAX_LENGTH 7 |
| 64 | #define COLUMN_CHARACTER_SET_CLIENT 8 |
| 65 | #define COLUMN_CHARACTER_SET_RESULT 9 |
| 66 | #define COLUMN_COLLATION 10 |
| 67 | #define COLUMN_TIMEZONE 11 |
| 68 | #define COLUMN_DEFAULT_WEEK_FORMAT 12 |
| 69 | #define COLUMN_DIV_PRECISION_INCREMENT 13 |
| 70 | #define COLUMN_SQL_MODE 14 |
| 71 | #define COLUMN_LC_TIME_NAMES 15 |
| 72 | |
| 73 | #define COLUMN_CLIENT_LONG_FLAG 16 |
| 74 | #define COLUMN_CLIENT_PROTOCOL_41 17 |
| 75 | #define COLUMN_PROTOCOL_TYPE 18 |
| 76 | #define COLUMN_MORE_RESULTS_EXISTS 19 |
| 77 | #define COLUMN_IN_TRANS 20 |
| 78 | #define COLUMN_AUTOCOMMIT 21 |
| 79 | #define COLUMN_PKT_NR 22 |
| 80 | #define COLUMN_HITS 23 |
| 81 | |
| 82 | /* ST_FIELD_INFO is defined in table.h */ |
| 83 | static ST_FIELD_INFO qc_info_fields[]= |
| 84 | { |
| 85 | {"STATEMENT_SCHEMA" , NAME_LEN, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
| 86 | {"STATEMENT_TEXT" , MAX_STATEMENT_TEXT_LENGTH, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
| 87 | {"RESULT_BLOCKS_COUNT" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, 0, 0}, |
| 88 | {"RESULT_BLOCKS_SIZE" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, 0, 0}, |
| 89 | {"RESULT_BLOCKS_SIZE_USED" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, 0, 0}, |
| 90 | {"LIMIT" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, 0, 0}, |
| 91 | {"MAX_SORT_LENGTH" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, 0, 0}, |
| 92 | {"GROUP_CONCAT_MAX_LENGTH" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0, 0, 0}, |
| 93 | {"CHARACTER_SET_CLIENT" , MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
| 94 | {"CHARACTER_SET_RESULT" , MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
| 95 | {"COLLATION" , MY_CS_NAME_SIZE, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
| 96 | {"TIMEZONE" , 50, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
| 97 | {"DEFAULT_WEEK_FORMAT" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, 0, 0}, |
| 98 | {"DIV_PRECISION_INCREMENT" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0, 0, 0}, |
| 99 | {"SQL_MODE" , 250, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
| 100 | {"LC_TIME_NAMES" , 100, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
| 101 | {"CLIENT_LONG_FLAG" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_TINY, 0, 0, 0, 0}, |
| 102 | {"CLIENT_PROTOCOL_41" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_TINY, 0, 0, 0, 0}, |
| 103 | {"PROTOCOL_TYPE" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_TINY, 0, 0, 0, 0}, |
| 104 | {"MORE_RESULTS_EXISTS" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_TINY, 0, 0, 0, 0}, |
| 105 | {"IN_TRANS" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_TINY, 0, 0, 0, 0}, |
| 106 | {"AUTOCOMMIT" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_TINY, 0, 0, 0, 0}, |
| 107 | {"PACKET_NUMBER" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_TINY, 0, 0, 0, 0}, |
| 108 | {"HITS" , MY_INT32_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, MY_I_S_UNSIGNED, 0, 0}, |
| 109 | {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0} |
| 110 | }; |
| 111 | |
| 112 | |
| 113 | static const char unknown[]= "#UNKNOWN#" ; |
| 114 | |
| 115 | static int qc_info_fill_table(THD *thd, TABLE_LIST *tables, |
| 116 | COND *cond) |
| 117 | { |
| 118 | int status= 1; |
| 119 | CHARSET_INFO *scs= system_charset_info; |
| 120 | TABLE *table= tables->table; |
| 121 | HASH *queries = qc->get_queries(); |
| 122 | |
| 123 | /* one must have PROCESS privilege to see others' queries */ |
| 124 | if (check_global_access(thd, PROCESS_ACL, true)) |
| 125 | return 0; |
| 126 | |
| 127 | if (qc->try_lock(thd)) |
| 128 | return 0; // QC is or is being disabled |
| 129 | |
| 130 | /* loop through all queries in the query cache */ |
| 131 | for (uint i= 0; i < queries->records; i++) |
| 132 | { |
| 133 | const uchar *query_cache_block_raw; |
| 134 | Query_cache_block* query_cache_block; |
| 135 | Query_cache_query* query_cache_query; |
| 136 | Query_cache_query_flags flags; |
| 137 | uint result_blocks_count; |
| 138 | ulonglong result_blocks_size; |
| 139 | ulonglong result_blocks_size_used; |
| 140 | Query_cache_block *first_result_block; |
| 141 | Query_cache_block *result_block; |
| 142 | const char *statement_text; |
| 143 | size_t statement_text_length; |
| 144 | size_t flags_length; |
| 145 | const char *key, *db; |
| 146 | size_t key_length, db_length; |
| 147 | LEX_CSTRING sql_mode_str; |
| 148 | const String *tz; |
| 149 | CHARSET_INFO *cs_client; |
| 150 | CHARSET_INFO *cs_result; |
| 151 | CHARSET_INFO *collation; |
| 152 | |
| 153 | query_cache_block_raw = my_hash_element(queries, i); |
| 154 | query_cache_block = (Query_cache_block*)query_cache_block_raw; |
| 155 | if (unlikely(!query_cache_block || |
| 156 | query_cache_block->type != Query_cache_block::QUERY)) |
| 157 | continue; |
| 158 | |
| 159 | query_cache_query = query_cache_block->query(); |
| 160 | |
| 161 | /* Get the actual SQL statement for this query cache query */ |
| 162 | statement_text = (const char*)query_cache_query->query(); |
| 163 | statement_text_length = strlen(statement_text); |
| 164 | /* We truncate SQL statements up to MAX_STATEMENT_TEXT_LENGTH in our I_S table */ |
| 165 | table->field[COLUMN_STATEMENT_TEXT]->store((char*)statement_text, |
| 166 | MY_MIN(statement_text_length, MAX_STATEMENT_TEXT_LENGTH), scs); |
| 167 | |
| 168 | /* get the entire key that identifies this query cache query */ |
| 169 | key = (const char*)query_cache_query_get_key(query_cache_block_raw, |
| 170 | &key_length, 0); |
| 171 | /* get and store the flags */ |
| 172 | flags_length= key_length - QUERY_CACHE_FLAGS_SIZE; |
| 173 | memcpy(&flags, key+flags_length, QUERY_CACHE_FLAGS_SIZE); |
| 174 | table->field[COLUMN_LIMIT]->store(flags.limit, 0); |
| 175 | table->field[COLUMN_MAX_SORT_LENGTH]->store(flags.max_sort_length, 0); |
| 176 | table->field[COLUMN_GROUP_CONCAT_MAX_LENGTH]->store(flags.group_concat_max_len, 0); |
| 177 | |
| 178 | cs_client= get_charset(flags.character_set_client_num, MYF(MY_WME)); |
| 179 | if (likely(cs_client)) |
| 180 | table->field[COLUMN_CHARACTER_SET_CLIENT]-> |
| 181 | store(cs_client->csname, strlen(cs_client->csname), scs); |
| 182 | else |
| 183 | table->field[COLUMN_CHARACTER_SET_CLIENT]-> |
| 184 | store(STRING_WITH_LEN(unknown), scs); |
| 185 | |
| 186 | cs_result= get_charset(flags.character_set_results_num, MYF(MY_WME)); |
| 187 | if (likely(cs_result)) |
| 188 | table->field[COLUMN_CHARACTER_SET_RESULT]-> |
| 189 | store(cs_result->csname, strlen(cs_result->csname), scs); |
| 190 | else |
| 191 | table->field[COLUMN_CHARACTER_SET_RESULT]-> |
| 192 | store(STRING_WITH_LEN(unknown), scs); |
| 193 | |
| 194 | collation= get_charset(flags.collation_connection_num, MYF(MY_WME)); |
| 195 | if (likely(collation)) |
| 196 | table->field[COLUMN_COLLATION]-> |
| 197 | store(collation->name, strlen(collation->name), scs); |
| 198 | else |
| 199 | table->field[COLUMN_COLLATION]-> store(STRING_WITH_LEN(unknown), scs); |
| 200 | |
| 201 | tz= flags.time_zone->get_name(); |
| 202 | if (likely(tz)) |
| 203 | table->field[COLUMN_TIMEZONE]->store(tz->ptr(), tz->length(), scs); |
| 204 | else |
| 205 | table->field[COLUMN_TIMEZONE]-> store(STRING_WITH_LEN(unknown), scs); |
| 206 | table->field[COLUMN_DEFAULT_WEEK_FORMAT]->store(flags.default_week_format, 0); |
| 207 | table->field[COLUMN_DIV_PRECISION_INCREMENT]->store(flags.div_precision_increment, 0); |
| 208 | |
| 209 | sql_mode_string_representation(thd, flags.sql_mode, &sql_mode_str); |
| 210 | table->field[COLUMN_SQL_MODE]->store(sql_mode_str.str, sql_mode_str.length, scs); |
| 211 | |
| 212 | table->field[COLUMN_LC_TIME_NAMES]->store(flags.lc_time_names->name,strlen(flags.lc_time_names->name), scs); |
| 213 | |
| 214 | table->field[COLUMN_CLIENT_LONG_FLAG]->store(flags.client_long_flag, 0); |
| 215 | table->field[COLUMN_CLIENT_PROTOCOL_41]->store(flags.client_protocol_41, 0); |
| 216 | table->field[COLUMN_PROTOCOL_TYPE]->store(flags.protocol_type, 0); |
| 217 | table->field[COLUMN_MORE_RESULTS_EXISTS]->store(flags.more_results_exists, 0); |
| 218 | table->field[COLUMN_IN_TRANS]->store(flags.in_trans, 0); |
| 219 | table->field[COLUMN_AUTOCOMMIT]->store(flags.autocommit, 0); |
| 220 | table->field[COLUMN_PKT_NR]->store(flags.pkt_nr, 0); |
| 221 | table->field[COLUMN_HITS]->store(query_cache_query->hits(), 0); |
| 222 | |
| 223 | /* The database against which the statement is executed is part of the |
| 224 | query cache query key |
| 225 | */ |
| 226 | compile_time_assert(QUERY_CACHE_DB_LENGTH_SIZE == 2); |
| 227 | db= key + statement_text_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE; |
| 228 | db_length= uint2korr(db - QUERY_CACHE_DB_LENGTH_SIZE); |
| 229 | |
| 230 | table->field[COLUMN_STATEMENT_SCHEMA]->store(db, db_length, scs); |
| 231 | |
| 232 | /* If we have result blocks, process them */ |
| 233 | first_result_block= query_cache_query->result(); |
| 234 | if(query_cache_query->is_results_ready() && |
| 235 | first_result_block) |
| 236 | { |
| 237 | /* initialize so we can loop over the result blocks*/ |
| 238 | result_block= first_result_block; |
| 239 | result_blocks_count = 1; |
| 240 | result_blocks_size = result_block->length; |
| 241 | result_blocks_size_used = result_block->used; |
| 242 | |
| 243 | /* loop over the result blocks*/ |
| 244 | while((result_block= result_block->next)!=first_result_block) |
| 245 | { |
| 246 | /* calculate total number of result blocks */ |
| 247 | result_blocks_count++; |
| 248 | /* calculate total size of result blocks */ |
| 249 | result_blocks_size += result_block->length; |
| 250 | /* calculate total of used size of result blocks */ |
| 251 | result_blocks_size_used += result_block->used; |
| 252 | } |
| 253 | } |
| 254 | else |
| 255 | { |
| 256 | result_blocks_count = 0; |
| 257 | result_blocks_size = 0; |
| 258 | result_blocks_size_used = 0; |
| 259 | } |
| 260 | table->field[COLUMN_RESULT_BLOCKS_COUNT]->store(result_blocks_count, 0); |
| 261 | table->field[COLUMN_RESULT_BLOCKS_SIZE]->store(result_blocks_size, 0); |
| 262 | table->field[COLUMN_RESULT_BLOCKS_SIZE_USED]-> |
| 263 | store(result_blocks_size_used, 0); |
| 264 | |
| 265 | if (schema_table_store_record(thd, table)) |
| 266 | goto cleanup; |
| 267 | } |
| 268 | status = 0; |
| 269 | |
| 270 | cleanup: |
| 271 | qc->unlock(); |
| 272 | return status; |
| 273 | } |
| 274 | |
| 275 | static int qc_info_plugin_init(void *p) |
| 276 | { |
| 277 | ST_SCHEMA_TABLE *schema= (ST_SCHEMA_TABLE *)p; |
| 278 | |
| 279 | schema->fields_info= qc_info_fields; |
| 280 | schema->fill_table= qc_info_fill_table; |
| 281 | |
| 282 | #ifdef _WIN32 |
| 283 | qc = (Accessible_Query_Cache *) |
| 284 | GetProcAddress(GetModuleHandle(NULL), "?query_cache@@3VQuery_cache@@A" ); |
| 285 | #else |
| 286 | qc = (Accessible_Query_Cache *)&query_cache; |
| 287 | #endif |
| 288 | |
| 289 | return qc == 0; |
| 290 | } |
| 291 | |
| 292 | |
| 293 | static struct st_mysql_information_schema qc_info_plugin= |
| 294 | { MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION }; |
| 295 | |
| 296 | /* |
| 297 | Plugin library descriptor |
| 298 | */ |
| 299 | |
| 300 | maria_declare_plugin(query_cache_info) |
| 301 | { |
| 302 | MYSQL_INFORMATION_SCHEMA_PLUGIN, |
| 303 | &qc_info_plugin, |
| 304 | "QUERY_CACHE_INFO" , |
| 305 | "Roland Bouman, Daniel Black" , |
| 306 | "Lists all queries in the query cache." , |
| 307 | PLUGIN_LICENSE_BSD, |
| 308 | qc_info_plugin_init, /* Plugin Init */ |
| 309 | 0, /* Plugin Deinit */ |
| 310 | 0x0101, /* version, hex */ |
| 311 | NULL, /* status variables */ |
| 312 | NULL, /* system variables */ |
| 313 | "1.1" , /* version as a string */ |
| 314 | MariaDB_PLUGIN_MATURITY_STABLE |
| 315 | } |
| 316 | maria_declare_plugin_end; |
| 317 | |
| 318 | |