1 /*
2 Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License, version 2.0,
6 as published by the Free Software Foundation.
7
8 This program is also distributed with certain software (including
9 but not limited to OpenSSL) that is licensed under separate terms,
10 as designated in a particular file or component or in included license
11 documentation. The authors of MySQL hereby grant you an additional
12 permission to link the program and your derivative works with the
13 separately licensed software that they have included with MySQL.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License, version 2.0, for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
23
24 #include "sql/opt_costconstantcache.h"
25
26 #include <memory>
27
28 #include "m_ctype.h"
29 #include "m_string.h"
30 #include "my_dbug.h"
31 #include "my_loglevel.h"
32 #include "mysql/components/services/log_builtins.h"
33 #include "mysql/components/services/log_shared.h"
34 #include "mysqld_error.h"
35 #include "sql/current_thd.h" // current_thd
36 #include "sql/field.h" // Field
37 #include "sql/mysqld.h" // key_LOCK_cost_const
38 #include "sql/records.h" // unique_ptr_destroy_only<RowIterator>
39 #include "sql/row_iterator.h"
40 #include "sql/sql_base.h" // open_and_lock_tables
41 #include "sql/sql_class.h" // THD
42 #include "sql/sql_const.h"
43 #include "sql/sql_lex.h" // lex_start/lex_end
44 #include "sql/sql_tmp_table.h" // init_cache_tmp_engine_properties
45 #include "sql/table.h" // TABLE
46 #include "sql/transaction.h" // trans_commit_stmt
47 #include "sql_string.h"
48 #include "template_utils.h" // pointer_cast
49 #include "thr_lock.h"
50 #include "thr_mutex.h"
51
52 Cost_constant_cache *cost_constant_cache = nullptr;
53
54 static void read_cost_constants(Cost_model_constants *cost_constants);
55
56 /**
57 Minimal initialization of the object. The main initialization is done
58 by calling init().
59 */
60
Cost_constant_cache()61 Cost_constant_cache::Cost_constant_cache()
62 : current_cost_constants(nullptr), m_inited(false) {}
63
~Cost_constant_cache()64 Cost_constant_cache::~Cost_constant_cache() {
65 // Verify that close has been called
66 DBUG_ASSERT(current_cost_constants == nullptr);
67 DBUG_ASSERT(m_inited == false);
68 }
69
init()70 void Cost_constant_cache::init() {
71 DBUG_TRACE;
72
73 DBUG_ASSERT(m_inited == false);
74
75 // Initialize the mutex that is used for protecting the cost constants
76 mysql_mutex_init(key_LOCK_cost_const, &LOCK_cost_const, MY_MUTEX_INIT_FAST);
77
78 // Create cost constants from constants found in the source code
79 Cost_model_constants *cost_constants = create_defaults();
80
81 // Set this to be the current set of cost constants
82 update_current_cost_constants(cost_constants);
83
84 m_inited = true;
85 }
86
close()87 void Cost_constant_cache::close() {
88 DBUG_TRACE;
89
90 DBUG_ASSERT(m_inited);
91
92 if (m_inited == false) return; /* purecov: inspected */
93
94 // Release the current cost constant set
95 if (current_cost_constants) {
96 release_cost_constants(current_cost_constants);
97 current_cost_constants = nullptr;
98 }
99
100 // To ensure none is holding the mutex when deleting it, lock/unlock it.
101 mysql_mutex_lock(&LOCK_cost_const);
102 mysql_mutex_unlock(&LOCK_cost_const);
103
104 mysql_mutex_destroy(&LOCK_cost_const);
105
106 m_inited = false;
107 }
108
reload()109 void Cost_constant_cache::reload() {
110 DBUG_TRACE;
111 DBUG_ASSERT(m_inited = true);
112
113 // Create cost constants from the constants defined in the source code
114 Cost_model_constants *cost_constants = create_defaults();
115
116 // Update the cost constants from the database tables
117 read_cost_constants(cost_constants);
118
119 // Set this to be the current set of cost constants
120 update_current_cost_constants(cost_constants);
121 }
122
create_defaults() const123 Cost_model_constants *Cost_constant_cache::create_defaults() const {
124 // Create default cost constants
125 Cost_model_constants *cost_constants = new Cost_model_constants();
126
127 return cost_constants;
128 }
129
update_current_cost_constants(Cost_model_constants * new_cost_constants)130 void Cost_constant_cache::update_current_cost_constants(
131 Cost_model_constants *new_cost_constants) {
132 /*
133 Increase the ref counter to ensure that the new cost constants
134 are not deleted until next time we have a new set of cost constants.
135 */
136 new_cost_constants->inc_ref_count();
137
138 /*
139 The mutex needs to be held for the entire period for removing the
140 current cost constants and adding the new cost constants to ensure
141 that no user of this class can access the object when there is no
142 current cost constants.
143 */
144 mysql_mutex_lock(&LOCK_cost_const);
145
146 // Release the current cost constants by decrementing the ref counter
147 if (current_cost_constants) {
148 const unsigned int ref_count = current_cost_constants->dec_ref_count();
149
150 // If there is none using the current cost constants then delete them
151 if (ref_count == 0) delete current_cost_constants;
152 }
153
154 // Start to use the new cost constants
155 current_cost_constants = new_cost_constants;
156
157 mysql_mutex_unlock(&LOCK_cost_const);
158 }
159
160 /**
161 Write warnings about illegal entries in the server_cost table
162
163 The warnings are written to the MySQL error log.
164
165 @param cost_name name of the cost constant
166 @param value value it was attempted set to
167 @param error error status
168 */
169
report_server_cost_warnings(const LEX_CSTRING & cost_name,double value,cost_constant_error error)170 static void report_server_cost_warnings(const LEX_CSTRING &cost_name,
171 double value,
172 cost_constant_error error) {
173 switch (error) {
174 case UNKNOWN_COST_NAME:
175 LogErr(WARNING_LEVEL, ER_SERVER_COST_UNKNOWN_COST_CONSTANT,
176 cost_name.str);
177 break;
178 case INVALID_COST_VALUE:
179 LogErr(WARNING_LEVEL, ER_SERVER_COST_INVALID_COST_CONSTANT, cost_name.str,
180 value);
181 break;
182 default:
183 DBUG_ASSERT(false); /* purecov: inspected */
184 }
185 }
186
187 /**
188 Write warnings about illegal entries in the engine_cost table
189
190 The warnings are written to the MySQL error log.
191
192 @param se_name name of storage engine
193 @param storage_category device type
194 @param cost_name name of the cost constant
195 @param value value it was attempted set to
196 @param error error status
197 */
198
report_engine_cost_warnings(const LEX_CSTRING & se_name,int storage_category,const LEX_CSTRING & cost_name,double value,cost_constant_error error)199 static void report_engine_cost_warnings(const LEX_CSTRING &se_name,
200 int storage_category,
201 const LEX_CSTRING &cost_name,
202 double value,
203 cost_constant_error error) {
204 switch (error) {
205 case UNKNOWN_COST_NAME:
206 LogErr(WARNING_LEVEL, ER_ENGINE_COST_UNKNOWN_COST_CONSTANT,
207 cost_name.str);
208 break;
209 case UNKNOWN_ENGINE_NAME:
210 LogErr(WARNING_LEVEL, ER_ENGINE_COST_UNKNOWN_STORAGE_ENGINE, se_name.str);
211 break;
212 case INVALID_DEVICE_TYPE:
213 LogErr(WARNING_LEVEL, ER_ENGINE_COST_INVALID_DEVICE_TYPE_FOR_SE,
214 storage_category, se_name.str, cost_name.str);
215 break;
216 case INVALID_COST_VALUE:
217 LogErr(WARNING_LEVEL,
218 ER_ENGINE_COST_INVALID_CONST_CONSTANT_FOR_SE_AND_DEVICE,
219 cost_name.str, se_name.str, storage_category, value);
220 break;
221 default:
222 DBUG_ASSERT(false); /* purecov: inspected */
223 }
224 }
225
226 /**
227 Read the table that contains the cost constants for the server.
228
229 The table must already be opened. The cost constant object is updated
230 with cost constants found in the configuration table.
231
232 @param thd the THD
233 @param table the table to read from
234 @param [in,out] cost_constants cost constant object
235 */
236
read_server_cost_constants(THD * thd,TABLE * table,Cost_model_constants * cost_constants)237 static void read_server_cost_constants(THD *thd, TABLE *table,
238 Cost_model_constants *cost_constants) {
239 DBUG_TRACE;
240
241 /*
242 The server constant table has the following columns:
243
244 cost_name VARCHAR(64) NOT NULL COLLATE utf8_general_ci
245 cost_value FLOAT DEFAULT NULL
246 last_update TIMESTAMP
247 comment VARCHAR(1024) DEFAULT NULL
248 */
249
250 // Prepare to read from the table
251 unique_ptr_destroy_only<RowIterator> iterator =
252 init_table_iterator(thd, table, nullptr, false,
253 /*ignore_not_found_rows=*/false);
254 if (iterator != nullptr) {
255 table->use_all_columns();
256
257 // Read one record
258 while (!iterator->Read()) {
259 /*
260 Check if a non-default value has been configured for this cost
261 constant.
262 */
263 if (!table->field[1]->is_null()) {
264 char cost_name_buf[MAX_FIELD_WIDTH];
265 String cost_name(cost_name_buf, sizeof(cost_name_buf),
266 &my_charset_utf8_general_ci);
267
268 // Read the name of the cost constant
269 table->field[0]->val_str(&cost_name);
270 cost_name[cost_name.length()] = 0; // Null-terminate
271
272 // Read the value this cost constant should have
273 const float value = static_cast<float>(table->field[1]->val_real());
274
275 // Update the cost model with this cost constant
276 const LEX_CSTRING cost_constant = cost_name.lex_cstring();
277 const cost_constant_error err =
278 cost_constants->update_server_cost_constant(cost_constant, value);
279
280 if (err != COST_CONSTANT_OK)
281 report_server_cost_warnings(cost_constant, value, err);
282 }
283 }
284 } else {
285 LogErr(WARNING_LEVEL, ER_SERVER_COST_FAILED_TO_READ);
286 }
287 }
288
289 /**
290 Read the table that contains the cost constants for the storage engines.
291
292 The table must already be opened. The cost constant object is updated
293 with cost constants found in the configuration table.
294
295 @param thd the THD
296 @param table the table to read from
297 @param [in,out] cost_constants cost constant object
298 */
299
read_engine_cost_constants(THD * thd,TABLE * table,Cost_model_constants * cost_constants)300 static void read_engine_cost_constants(THD *thd, TABLE *table,
301 Cost_model_constants *cost_constants) {
302 DBUG_TRACE;
303
304 /*
305 The engine constant table has the following columns:
306
307 engine_name VARCHAR(64) NOT NULL COLLATE utf8_general_ci,
308 device_type INTEGER NOT NULL,
309 cost_name VARCHAR(64) NOT NULL COLLATE utf8_general_ci,
310 cost_value FLOAT DEFAULT NULL,
311 last_update TIMESTAMP
312 comment VARCHAR(1024) DEFAULT NULL,
313 */
314
315 // Prepare to read from the table
316 unique_ptr_destroy_only<RowIterator> iterator =
317 init_table_iterator(thd, table, nullptr, false,
318 /*ignore_not_found_rows=*/false);
319 if (iterator != nullptr) {
320 table->use_all_columns();
321
322 // Read one record
323 while (!iterator->Read()) {
324 /*
325 Check if a non-default value has been configured for this cost
326 constant.
327 */
328 if (!table->field[3]->is_null()) {
329 char engine_name_buf[MAX_FIELD_WIDTH];
330 String engine_name(engine_name_buf, sizeof(engine_name_buf),
331 &my_charset_utf8_general_ci);
332 char cost_name_buf[MAX_FIELD_WIDTH];
333 String cost_name(cost_name_buf, sizeof(cost_name_buf),
334 &my_charset_utf8_general_ci);
335
336 // Read the name of the storage engine
337 table->field[0]->val_str(&engine_name);
338 engine_name[engine_name.length()] = 0; // Null-terminate
339
340 // Read the device type
341 const int device_type = static_cast<int>(table->field[1]->val_int());
342
343 // Read the name of the cost constant
344 table->field[2]->val_str(&cost_name);
345 cost_name[cost_name.length()] = 0; // Null-terminate
346
347 // Read the value this cost constant should have
348 const float value = static_cast<float>(table->field[3]->val_real());
349
350 // Update the cost model with this cost constant
351 const LEX_CSTRING engine = engine_name.lex_cstring();
352 const LEX_CSTRING cost_constant = cost_name.lex_cstring();
353 const cost_constant_error err =
354 cost_constants->update_engine_cost_constant(
355 thd, engine, device_type, cost_constant, value);
356 if (err != COST_CONSTANT_OK)
357 report_engine_cost_warnings(engine, device_type, cost_constant, value,
358 err);
359 }
360 }
361 } else {
362 LogErr(WARNING_LEVEL, ER_ENGINE_COST_FAILED_TO_READ);
363 }
364 }
365
366 /**
367 Read the cost configuration tables and update the cost constant set.
368
369 The const constant set must be initialized with default values when
370 calling this function.
371
372 @param cost_constants set with cost constants
373 */
374
read_cost_constants(Cost_model_constants * cost_constants)375 static void read_cost_constants(Cost_model_constants *cost_constants) {
376 DBUG_TRACE;
377
378 /*
379 This function creates its own THD. If there exists a current THD this needs
380 to be restored at the end of this function. The reason the current THD
381 can not be used is that this might already have opened and closed tables
382 and thus opening new tables will fail.
383 */
384 THD *orig_thd = current_thd;
385
386 // Create and initialize a new THD.
387 THD *thd = new THD;
388 DBUG_ASSERT(thd);
389 thd->thread_stack = pointer_cast<char *>(&thd);
390 thd->store_globals();
391 lex_start(thd);
392
393 TABLE_LIST tables[2] = {TABLE_LIST("mysql", "server_cost", TL_READ),
394 TABLE_LIST("mysql", "engine_cost", TL_READ)};
395 tables[0].next_global = tables[0].next_local =
396 tables[0].next_name_resolution_table = &tables[1];
397
398 if (!open_and_lock_tables(thd, tables, MYSQL_LOCK_IGNORE_TIMEOUT)) {
399 DBUG_ASSERT(tables[0].table != nullptr);
400 DBUG_ASSERT(tables[1].table != nullptr);
401
402 // Read the server constants table
403 read_server_cost_constants(thd, tables[0].table, cost_constants);
404 // Read the storage engine table
405 read_engine_cost_constants(thd, tables[1].table, cost_constants);
406 } else {
407 LogErr(WARNING_LEVEL, ER_FAILED_TO_OPEN_COST_CONSTANT_TABLES);
408 }
409
410 trans_commit_stmt(thd);
411 close_thread_tables(thd);
412 lex_end(thd->lex);
413
414 // Delete the locally created THD
415 delete thd;
416
417 // If the caller already had a THD, this must be restored
418 if (orig_thd) orig_thd->store_globals();
419 }
420
init_optimizer_cost_module(bool enable_plugins)421 void init_optimizer_cost_module(bool enable_plugins) {
422 DBUG_ASSERT(cost_constant_cache == nullptr);
423 cost_constant_cache = new Cost_constant_cache();
424 cost_constant_cache->init();
425 /*
426 Initialize max_key_length and max_key_part_length for internal temporary
427 table engines.
428 */
429 if (enable_plugins) init_cache_tmp_engine_properties();
430 }
431
delete_optimizer_cost_module()432 void delete_optimizer_cost_module() {
433 if (cost_constant_cache) {
434 cost_constant_cache->close();
435 delete cost_constant_cache;
436 cost_constant_cache = nullptr;
437 }
438 }
439
reload_optimizer_cost_constants()440 void reload_optimizer_cost_constants() {
441 if (cost_constant_cache) cost_constant_cache->reload();
442 }
443