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