1 /*
2    Copyright (c) 2014, 2021, Oracle and/or its affiliates.
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 Foundation,
22    51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
23 
24 // First include (the generated) my_config.h, to get correct platform defines.
25 #include "my_config.h"
26 #include <stddef.h>
27 #include "sql_const.h"                          // MAX_FIELD_WIDTH
28 #include "field.h"                              // Field
29 #include "log.h"                                // sql_print_warning
30 #include "m_string.h"                           // LEX_CSTRING
31 #include "my_dbug.h"                            // assert
32 #include "opt_costconstants.h"
33 #include "opt_costconstantcache.h"
34 #include "template_utils.h"                     // pointer_cast
35 #include "records.h"                            // READ_RECORD
36 #include "sql_base.h"                           // open_and_lock_tables
37 #include "sql_class.h"                          // THD
38 #include "sql_lex.h"                      // lex_start/lex_end
39 #include "sql_string.h"                         // String
40 #include "table.h"                              // TABLE
41 #include "thr_lock.h"                           // TL_READ
42 #include "transaction.h"
43 #include "sql_tmp_table.h"                // init_cache_tmp_engine_properties
44 
45 Cost_constant_cache *cost_constant_cache= NULL;
46 
47 
48 static void read_cost_constants(Cost_model_constants* cost_constants);
49 
50 
51 /**
52   Minimal initialization of the object. The main initialization is done
53   by calling init().
54 */
55 
Cost_constant_cache()56 Cost_constant_cache::Cost_constant_cache()
57   : current_cost_constants(NULL), m_inited(false)
58 {
59 }
60 
61 
~Cost_constant_cache()62 Cost_constant_cache::~Cost_constant_cache()
63 {
64   // Verify that close has been called
65   assert(current_cost_constants == NULL);
66   assert(m_inited == false);
67 }
68 
69 
init()70 void Cost_constant_cache::init()
71 {
72   DBUG_ENTER("Cost_constant_cache::init");
73 
74   assert(m_inited == false);
75 
76   // Initialize the mutex that is used for protecting the cost constants
77   mysql_mutex_init(key_LOCK_cost_const, &LOCK_cost_const,
78                    MY_MUTEX_INIT_FAST);
79 
80   // Create cost constants from constants found in the source code
81   Cost_model_constants *cost_constants= create_defaults();
82 
83   // Set this to be the current set of cost constants
84   update_current_cost_constants(cost_constants);
85 
86   m_inited= true;
87 
88   DBUG_VOID_RETURN;
89 }
90 
91 
close()92 void Cost_constant_cache::close()
93 {
94   DBUG_ENTER("Cost_constant_cache::close");
95 
96   assert(m_inited);
97 
98   if (m_inited == false)
99     DBUG_VOID_RETURN;                           /* purecov: inspected */
100 
101   // Release the current cost constant set
102   if (current_cost_constants)
103   {
104     release_cost_constants(current_cost_constants);
105     current_cost_constants= NULL;
106   }
107 
108   // To ensure none is holding the mutex when deleting it, lock/unlock it.
109   mysql_mutex_lock(&LOCK_cost_const);
110   mysql_mutex_unlock(&LOCK_cost_const);
111 
112   mysql_mutex_destroy(&LOCK_cost_const);
113 
114   m_inited= false;
115 
116   DBUG_VOID_RETURN;
117 }
118 
119 
reload()120 void Cost_constant_cache::reload()
121 {
122   DBUG_ENTER("Cost_constant_cache::reload");
123   assert(m_inited= true);
124 
125   // Create cost constants from the constants defined in the source code
126   Cost_model_constants *cost_constants= create_defaults();
127 
128   // Update the cost constants from the database tables
129   read_cost_constants(cost_constants);
130 
131   // Set this to be the current set of cost constants
132   update_current_cost_constants(cost_constants);
133 
134   DBUG_VOID_RETURN;
135 }
136 
137 
138 
create_defaults() const139 Cost_model_constants *Cost_constant_cache::create_defaults() const
140 {
141   // Create default cost constants
142   Cost_model_constants *cost_constants= new Cost_model_constants();
143 
144   return cost_constants;
145 }
146 
147 
148 void
update_current_cost_constants(Cost_model_constants * new_cost_constants)149 Cost_constant_cache::update_current_cost_constants(Cost_model_constants *new_cost_constants)
150 {
151   /*
152     Increase the ref counter to ensure that the new cost constants
153     are not deleted until next time we have a new set of cost constants.
154   */
155   new_cost_constants->inc_ref_count();
156 
157   /*
158     The mutex needs to be held for the entire period for removing the
159     current cost constants and adding the new cost constants to ensure
160     that no user of this class can access the object when there is no
161     current cost constants.
162   */
163   mysql_mutex_lock(&LOCK_cost_const);
164 
165   // Release the current cost constants by decrementing the ref counter
166   if (current_cost_constants)
167   {
168     const unsigned int ref_count= current_cost_constants->dec_ref_count();
169 
170     // If there is none using the current cost constants then delete them
171     if (ref_count == 0)
172       delete current_cost_constants;
173   }
174 
175   // Start to use the new cost constants
176   current_cost_constants= new_cost_constants;
177 
178   mysql_mutex_unlock(&LOCK_cost_const);
179 }
180 
181 
182 /**
183   Write warnings about illegal entries in the server_cost table
184 
185   The warnings are written to the MySQL error log.
186 
187   @param cost_name  name of the cost constant
188   @param value      value it was attempted set to
189   @param err        error status
190 */
191 
report_server_cost_warnings(const LEX_CSTRING & cost_name,double value,cost_constant_error error)192 static void report_server_cost_warnings(const LEX_CSTRING &cost_name,
193                                         double value,
194                                         cost_constant_error error)
195 {
196   switch(error)
197   {
198   case UNKNOWN_COST_NAME:
199     sql_print_warning("Unknown cost constant \"%s\" in mysql.server_cost table\n",
200                       cost_name.str);
201     break;
202   case INVALID_COST_VALUE:
203     sql_print_warning("Invalid value for cost constant \"%s\" in mysql.server_cost table: %.1f\n",
204                       cost_name.str, value);
205     break;
206   default:
207     assert(false);                         /* purecov: inspected */
208   }
209 }
210 
211 
212 /**
213   Write warnings about illegal entries in the engine_cost table
214 
215   The warnings are written to the MySQL error log.
216 
217   @param se_name          name of storage engine
218   @param storage_category device type
219   @param cost_name        name of the cost constant
220   @param value            value it was attempted set to
221   @param err              error status
222 */
223 
report_engine_cost_warnings(const LEX_CSTRING & se_name,int storage_category,const LEX_CSTRING & cost_name,double value,cost_constant_error error)224 static void report_engine_cost_warnings(const LEX_CSTRING &se_name,
225                                         int storage_category,
226                                         const LEX_CSTRING &cost_name,
227                                         double value,
228                                         cost_constant_error error)
229 {
230   switch(error)
231   {
232   case UNKNOWN_COST_NAME:
233     sql_print_warning("Unknown cost constant \"%s\" in mysql.engine_cost table\n",
234                       cost_name.str);
235     break;
236   case UNKNOWN_ENGINE_NAME:
237     sql_print_warning("Unknown storage engine \"%s\" in mysql.engine_cost table\n",
238                       se_name.str);
239     break;
240   case INVALID_DEVICE_TYPE:
241     sql_print_warning("Invalid device type %d for \"%s\" storage engine for cost constant \"%s\" in mysql.engine_cost table\n",
242                       storage_category, se_name.str, cost_name.str);
243     break;
244   case INVALID_COST_VALUE:
245     sql_print_warning("Invalid value for cost constant \"%s\" for \"%s\" storage engine and device type %d in mysql.engine_cost table: %.1f\n",
246                       cost_name.str, se_name.str, storage_category, value);
247     break;
248   default:
249     assert(false);                         /* purecov: inspected */
250   }
251 }
252 
253 
254 /**
255   Read the table that contains the cost constants for the server.
256 
257   The table must already be opened. The cost constant object is updated
258   with cost constants found in the configuration table.
259 
260   @param thd                    the THD
261   @param table                  the table to read from
262   @param cost_constants[in,out] cost constant object
263 */
264 
read_server_cost_constants(THD * thd,TABLE * table,Cost_model_constants * cost_constants)265 static void read_server_cost_constants(THD *thd, TABLE *table,
266                                        Cost_model_constants* cost_constants)
267 {
268   DBUG_ENTER("read_server_cost_constants");
269 
270   /*
271     The server constant table has the following columns:
272 
273     cost_name   VARCHAR(64) NOT NULL COLLATE utf8_general_ci
274     cost_value  FLOAT DEFAULT NULL
275     last_update TIMESTAMP
276     comment     VARCHAR(1024) DEFAULT NULL
277   */
278 
279   READ_RECORD read_record_info;
280 
281   // Prepare to read from the table
282   const bool ret= init_read_record(&read_record_info, thd, table, NULL, true,
283                                    true, false);
284   if (!ret)
285   {
286     table->use_all_columns();
287 
288     // Read one record
289     while (!read_record_info.read_record(&read_record_info))
290     {
291       /*
292         Check if a non-default value has been configured for this cost
293         constant.
294       */
295       if (!table->field[1]->is_null())
296       {
297         char cost_name_buf[MAX_FIELD_WIDTH];
298         String cost_name(cost_name_buf, sizeof(cost_name_buf),
299                          &my_charset_utf8_general_ci);
300 
301         // Read the name of the cost constant
302         table->field[0]->val_str(&cost_name);
303         cost_name[cost_name.length()]= 0; // Null-terminate
304 
305         // Read the value this cost constant should have
306         const float value= static_cast<float>(table->field[1]->val_real());
307 
308         // Update the cost model with this cost constant
309         const LEX_CSTRING cost_constant= cost_name.lex_cstring();
310         const cost_constant_error err=
311           cost_constants->update_server_cost_constant(cost_constant, value);
312 
313         if (err != COST_CONSTANT_OK)
314           report_server_cost_warnings(cost_constant, value, err);
315       }
316     }
317 
318     end_read_record(&read_record_info);
319   }
320   else
321   {
322     sql_print_warning("init_read_record returned error when reading from mysql.server_cost table.\n");
323   }
324 
325   DBUG_VOID_RETURN;
326 }
327 
328 
329 /**
330   Read the table that contains the cost constants for the storage engines.
331 
332   The table must already be opened. The cost constant object is updated
333   with cost constants found in the configuration table.
334 
335   @param thd                    the THD
336   @param table                  the table to read from
337   @param cost_constants[in,out] cost constant object
338 */
339 
read_engine_cost_constants(THD * thd,TABLE * table,Cost_model_constants * cost_constants)340 static void read_engine_cost_constants(THD *thd, TABLE *table,
341                                        Cost_model_constants* cost_constants)
342 {
343   DBUG_ENTER("read_engine_cost_constants");
344 
345   /*
346     The engine constant table has the following columns:
347 
348     engine_name VARCHAR(64) NOT NULL COLLATE utf8_general_ci,
349     device_type INTEGER NOT NULL,
350     cost_name   VARCHAR(64) NOT NULL COLLATE utf8_general_ci,
351     cost_value  FLOAT DEFAULT NULL,
352     last_update TIMESTAMP
353     comment     VARCHAR(1024) DEFAULT NULL,
354   */
355 
356   READ_RECORD read_record_info;
357 
358   // Prepare to read from the table
359   const bool ret= init_read_record(&read_record_info, thd, table, NULL, true,
360                                    true, false);
361   if (!ret)
362   {
363     table->use_all_columns();
364 
365     // Read one record
366     while (!read_record_info.read_record(&read_record_info))
367     {
368       /*
369         Check if a non-default value has been configured for this cost
370         constant.
371       */
372       if (!table->field[3]->is_null())
373       {
374         char engine_name_buf[MAX_FIELD_WIDTH];
375         String engine_name(engine_name_buf, sizeof(engine_name_buf),
376                            &my_charset_utf8_general_ci);
377         char cost_name_buf[MAX_FIELD_WIDTH];
378         String cost_name(cost_name_buf, sizeof(cost_name_buf),
379                          &my_charset_utf8_general_ci);
380 
381         // Read the name of the storage engine
382         table->field[0]->val_str(&engine_name);
383         engine_name[engine_name.length()]= 0; // Null-terminate
384 
385         // Read the device type
386         const int device_type= static_cast<int>(table->field[1]->val_int());
387 
388         // Read the name of the cost constant
389         table->field[2]->val_str(&cost_name);
390         cost_name[cost_name.length()]= 0; // Null-terminate
391 
392         // Read the value this cost constant should have
393         const float value= static_cast<float>(table->field[3]->val_real());
394 
395         // Update the cost model with this cost constant
396         const LEX_CSTRING engine= engine_name.lex_cstring();
397         const LEX_CSTRING cost_constant= cost_name.lex_cstring();
398         const cost_constant_error err=
399           cost_constants->update_engine_cost_constant(thd, engine,
400                                                       device_type,
401                                                       cost_constant, value);
402         if (err != COST_CONSTANT_OK)
403           report_engine_cost_warnings(engine, device_type, cost_constant,
404                                       value, err);
405       }
406     }
407 
408     end_read_record(&read_record_info);
409   }
410   else
411   {
412     sql_print_warning("init_read_record returned error when reading from mysql.engine_cost table.\n");
413   }
414 
415   DBUG_VOID_RETURN;
416 }
417 
418 
419 /**
420   Read the cost configuration tables and update the cost constant set.
421 
422   The const constant set must be initialized with default values when
423   calling this function.
424 
425   @param cost_constants set with cost constants
426 */
427 
read_cost_constants(Cost_model_constants * cost_constants)428 static void read_cost_constants(Cost_model_constants* cost_constants)
429 {
430   DBUG_ENTER("read_cost_constants");
431 
432   /*
433     This function creates its own THD. If there exists a current THD this needs
434     to be restored at the end of this function. The reason the current THD
435     can not be used is that this might already have opened and closed tables
436     and thus opening new tables will fail.
437   */
438   THD *orig_thd= current_thd;
439 
440   // Create and initialize a new THD.
441   THD *thd= new THD;
442   assert(thd);
443   thd->thread_stack= pointer_cast<char*>(&thd);
444   thd->store_globals();
445   lex_start(thd);
446 
447   TABLE_LIST tables[2];
448   tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
449                            C_STRING_WITH_LEN("server_cost"),
450                            "server_cost", TL_READ);
451   tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
452                            C_STRING_WITH_LEN("engine_cost"),
453                            "engine_cost", TL_READ);
454   tables[0].next_global= tables[0].next_local=
455     tables[0].next_name_resolution_table= &tables[1];
456 
457   if (!open_and_lock_tables(thd, tables, MYSQL_LOCK_IGNORE_TIMEOUT))
458   {
459     assert(tables[0].table != NULL);
460     assert(tables[1].table != NULL);
461 
462     // Read the server constants table
463     read_server_cost_constants(thd, tables[0].table, cost_constants);
464     // Read the storage engine table
465     read_engine_cost_constants(thd, tables[1].table, cost_constants);
466   }
467   else
468   {
469     sql_print_warning("Failed to open optimizer cost constant tables\n");
470   }
471 
472   trans_commit_stmt(thd);
473   close_thread_tables(thd);
474   lex_end(thd->lex);
475 
476   // Delete the locally created THD
477   delete thd;
478 
479   // If the caller already had a THD, this must be restored
480   if(orig_thd)
481     orig_thd->store_globals();
482 
483   DBUG_VOID_RETURN;
484 }
485 
486 
init_optimizer_cost_module(bool enable_plugins)487 void init_optimizer_cost_module(bool enable_plugins)
488 {
489   assert(cost_constant_cache == NULL);
490   cost_constant_cache= new Cost_constant_cache();
491   cost_constant_cache->init();
492   /*
493     Initialize max_key_length and max_key_part_length for internal temporary
494     table engines.
495   */
496   if (enable_plugins)
497     init_cache_tmp_engine_properties();
498 }
499 
500 
delete_optimizer_cost_module()501 void delete_optimizer_cost_module()
502 {
503   if (cost_constant_cache)
504   {
505     cost_constant_cache->close();
506     delete cost_constant_cache;
507     cost_constant_cache= NULL;
508   }
509 }
510 
511 
reload_optimizer_cost_constants()512 void reload_optimizer_cost_constants()
513 {
514   if (cost_constant_cache)
515     cost_constant_cache->reload();
516 }
517