1 /* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 #include "sql/dd/impl/tables/dd_properties.h"
24 
25 #include <string>
26 
27 #include "m_ctype.h"
28 #include "my_base.h"
29 #include "my_bitmap.h"
30 #include "my_dbug.h"
31 #include "my_inttypes.h"
32 #include "my_sys.h"
33 #include "mysql/udf_registration_types.h"
34 #include "mysqld_error.h"
35 #include "sql/auth/sql_security_ctx.h"
36 #include "sql/dd/dd_version.h"  // dd::DD_VERSION
37 #include "sql/dd/impl/raw/raw_table.h"
38 #include "sql/dd/impl/transaction_impl.h"
39 #include "sql/dd/impl/types/object_table_definition_impl.h"
40 #include "sql/dd/properties.h"
41 #include "sql/dd/string_type.h"  // dd::String_type, dd::Stringstream_type
42 #include "sql/field.h"
43 #include "sql/handler.h"
44 #include "sql/sql_const.h"
45 #include "sql/stateless_allocator.h"
46 #include "sql/table.h"
47 #include "sql_string.h"
48 
49 namespace dd {
50 namespace tables {
51 
instance()52 DD_properties &DD_properties::instance() {
53   static DD_properties *s_instance = new (std::nothrow) DD_properties();
54   return *s_instance;
55 }
56 
57 // Setup the initial definition of mysql.dd_properties table.
DD_properties()58 DD_properties::DD_properties() : m_properties() {
59   m_target_def.set_table_name("dd_properties");
60 
61   m_target_def.add_field(FIELD_PROPERTIES, "FIELD_PROPERTIES",
62                          "properties MEDIUMBLOB");
63 
64   m_target_def.add_populate_statement(
65       "INSERT INTO dd_properties (properties)"
66       "VALUES ('DD_VERSION=0')");
67 
68   /*
69     Initialize the descriptors of the valid keys. The keys are used for
70     the following purposes:
71 
72       DD_VERSION                Actual DD version.
73       IS_VERSION                Actual I_S version.
74       PS_VERSION                Actual P_S version.
75       NDBINFO_VERSION           Actual ndbinfo version.
76       SDI_VERSION               Actual SDI version.
77       LCTN                      L_C_T_N setting used during
78                                 --initialize.
79       MYSQLD_VERSION_LO         Lowest server version which has
80                                 been using the data directory.
81       MYSQLD_VERSION_HI         Highest server version which has
82                                 been using the data directory.
83       MYSQLD_VERSION            Current server version.
84       MINOR_DOWNGRADE_THRESHOLD The current DD can be used by
85                                 previous MRUs, unless their
86                                 target DD version is less than
87                                 the downgrade threshold.
88       SYSTEM_TABLES             List of system tables with
89                                 definitions.
90       UPGRADE_TARGET_SCHEMA     Temporary schema used during
91                                 upgrade.
92       UPGRADE_ACTUAL_SCHEMA     Temporary schema used during
93                                 upgrade.
94       MYSQLD_VERSION_UPGRADED   The server version of the last
95                                 completed successful upgrade.
96   */
97   m_property_desc = {
98       {"DD_VERSION", Property_type::UNSIGNED_INT_32},
99       {"IS_VERSION", Property_type::UNSIGNED_INT_32},
100       {"PS_VERSION", Property_type::UNSIGNED_INT_32},
101       {"NDBINFO_VERSION", Property_type::UNSIGNED_INT_32},
102       {"SDI_VERSION", Property_type::UNSIGNED_INT_32},
103       {"LCTN", Property_type::UNSIGNED_INT_32},
104       {"MYSQLD_VERSION_LO", Property_type::UNSIGNED_INT_32},
105       {"MYSQLD_VERSION_HI", Property_type::UNSIGNED_INT_32},
106       {"MYSQLD_VERSION", Property_type::UNSIGNED_INT_32},
107       {"MINOR_DOWNGRADE_THRESHOLD", Property_type::UNSIGNED_INT_32},
108       {"SYSTEM_TABLES", Property_type::PROPERTIES},
109       {"UPGRADE_TARGET_SCHEMA", Property_type::CHARACTER_STRING},
110       {"UPGRADE_ACTUAL_SCHEMA", Property_type::CHARACTER_STRING},
111       {"MYSQLD_VERSION_UPGRADED", Property_type::UNSIGNED_INT_32}};
112 }
113 
114 // Read all properties from disk and populate the cache.
init_cached_properties(THD * thd)115 bool DD_properties::init_cached_properties(THD *thd) {
116   // Early exit in case the properties are already initialized.
117   if (!m_properties.empty()) return false;
118 
119   /*
120     Start a DD transaction to get the properties. Please note that we
121     must do this read using isolation level ISO_READ_UNCOMMITTED
122     because the SE undo logs may not yet be available.
123   */
124   Transaction_ro trx(thd, ISO_READ_UNCOMMITTED);
125   trx.otx.add_table<DD_properties>();
126 
127   if (trx.otx.open_tables()) {
128     DBUG_ASSERT(false);
129     return true;
130   }
131 
132   Raw_table *raw_t = trx.otx.get_table(name());
133   DBUG_ASSERT(raw_t);
134   TABLE *t = raw_t->get_table();
135   DBUG_ASSERT(t);
136   t->use_all_columns();
137 
138   /*
139     We should not read from this table until after it has been populated,
140     so there should always be a row stored. We read the row, and populate
141     the property cache based on its contents.
142   */
143   if (t->file->ha_rnd_init(true) || t->file->ha_rnd_next(t->record[0])) {
144     DBUG_ASSERT(false);
145     t->file->ha_rnd_end();
146     return true;
147   }
148 
149   String val;
150   t->field[FIELD_PROPERTIES]->val_str(&val);
151   m_properties.insert_values(val.c_ptr_safe());
152 
153   t->file->ha_rnd_end();
154   return (m_properties.empty());
155 }
156 
157 // Flush all properties from the cache to disk.
flush_cached_properties(THD * thd)158 bool DD_properties::flush_cached_properties(THD *thd) {
159   DBUG_ASSERT(!m_properties.empty());
160 
161   Update_dictionary_tables_ctx ctx(thd);
162   ctx.otx.add_table<DD_properties>();
163 
164   if (ctx.otx.open_tables()) return true;
165 
166   Raw_table *raw_t = ctx.otx.get_table(name());
167   DBUG_ASSERT(raw_t);
168   TABLE *t = raw_t->get_table();
169   DBUG_ASSERT(t);
170   t->use_all_columns();
171   bitmap_set_all(t->write_set);
172   bitmap_set_all(t->read_set);
173 
174   int rc = 0;
175   if ((rc = t->file->ha_rnd_init(true))) {
176     t->file->print_error(rc, MYF(0));
177     t->file->ha_rnd_end();
178     return true;
179   }
180 
181   /*
182     If a row is already stored, then we update it. If no row is stored,
183     it means we an error situation, since we should not write to the table
184     until after its populate SQL statement has been executed.
185   */
186   if ((rc = t->file->ha_rnd_next(t->record[0]))) {
187     DBUG_ASSERT(false);
188     t->file->ha_rnd_end();
189     return true;
190   }
191 
192   store_record(t, record[1]);
193   const String_type &prop_str = m_properties.raw_string();
194   t->field[FIELD_PROPERTIES]->store(prop_str.c_str(), prop_str.length(),
195                                     system_charset_info);
196   rc = t->file->ha_update_row(t->record[1], t->record[0]);
197   t->file->ha_rnd_end();
198 
199   if (rc && rc != HA_ERR_RECORD_IS_THE_SAME) {
200     t->file->print_error(rc, MYF(0));
201     return true;
202   }
203 
204   return false;
205 }
206 
207 /*
208   Initialize the cache, and read the property value for the given key
209   without checking or validating the key.
210 */
unchecked_get(THD * thd,const String_type & key,String_type * value,bool * exists)211 bool DD_properties::unchecked_get(THD *thd, const String_type &key,
212                                   String_type *value, bool *exists) {
213   if (init_cached_properties(thd)) return true;
214 
215   *exists = m_properties.exists(key);
216   if (*exists) m_properties.get(key, value);
217 
218   if (*exists == false && key == "DD_VERSION") {
219     *exists = m_properties.exists("DD_version");
220     if (*exists) m_properties.get("DD_version", value);
221   }
222 
223   return false;
224 }
225 
226 /*
227   Initialize the cache, and set the property value for the given key
228   without checking or validating the key. Flush the cache to disk in
229   order to make the value persistent.
230 */
unchecked_set(THD * thd,const String_type & key,const String_type & value)231 bool DD_properties::unchecked_set(THD *thd, const String_type &key,
232                                   const String_type &value) {
233   // Read cached properties from disk, if not existing.
234   if (init_cached_properties(thd)) return true;
235 
236   // Update the cached properties and the table.
237   m_properties.set(key, value);
238   return flush_cached_properties(thd);
239 }
240 
241 // Read the integer property for the given key.
get(THD * thd,const String_type & key,uint * value,bool * exists)242 bool DD_properties::get(THD *thd, const String_type &key, uint *value,
243                         bool *exists) {
244   DBUG_ASSERT(m_property_desc.find(key) != m_property_desc.end() &&
245               m_property_desc[key] == Property_type::UNSIGNED_INT_32);
246   String_type val_str;
247   return unchecked_get(thd, key, &val_str, exists) ||
248          dd::Properties::from_str(val_str, value);
249 }
250 
251 // Set the integer property for the given key.
set(THD * thd,const String_type & key,uint value)252 bool DD_properties::set(THD *thd, const String_type &key, uint value) {
253   DBUG_ASSERT(m_property_desc.find(key) != m_property_desc.end() &&
254               m_property_desc[key] == Property_type::UNSIGNED_INT_32);
255   return unchecked_set(thd, key, dd::Properties::to_str(value));
256 }
257 
258 // Read the character string property for the given key.
get(THD * thd,const String_type & key,String_type * value,bool * exists)259 bool DD_properties::get(THD *thd, const String_type &key, String_type *value,
260                         bool *exists) {
261   DBUG_ASSERT(m_property_desc.find(key) != m_property_desc.end() &&
262               m_property_desc[key] == Property_type::CHARACTER_STRING);
263   return unchecked_get(thd, key, value, exists);
264 }
265 
266 // Set the character string property for the given key.
set(THD * thd,const String_type & key,const String_type & value)267 bool DD_properties::set(THD *thd, const String_type &key,
268                         const String_type &value) {
269   DBUG_ASSERT(m_property_desc.find(key) != m_property_desc.end() &&
270               m_property_desc[key] == Property_type::CHARACTER_STRING);
271   return unchecked_set(thd, key, value);
272 }
273 
274 // Read the properties object for the given key.
get(THD * thd,const String_type & key,std::unique_ptr<Properties> * properties,bool * exists)275 bool DD_properties::get(THD *thd, const String_type &key,
276                         std::unique_ptr<Properties> *properties, bool *exists) {
277   DBUG_ASSERT(m_property_desc.find(key) != m_property_desc.end() &&
278               m_property_desc[key] == Property_type::PROPERTIES);
279 
280   String_type property_string;
281   if (unchecked_get(thd, key, &property_string, exists)) return true;
282 
283   properties->reset(Properties::parse_properties(property_string.c_str()));
284   return false;
285 }
286 
287 // Set the property object for the given key.
set(THD * thd,const String_type & key,const dd::Properties & properties)288 bool DD_properties::set(THD *thd, const String_type &key,
289                         const dd::Properties &properties) {
290   DBUG_ASSERT(m_property_desc.find(key) != m_property_desc.end() &&
291               m_property_desc[key] == Property_type::PROPERTIES);
292   return unchecked_set(thd, key, properties.raw_string());
293 }
294 
295 // Initialize the cache, and remove the submitted key if it exists.
remove(THD * thd,const String_type & key)296 bool DD_properties::remove(THD *thd, const String_type &key) {
297   // Read cached properties from disk, if not existing.
298   if (init_cached_properties(thd)) return true;
299 
300   // Update the cached properties and the table.
301   if (m_properties.exists(key)) (void)m_properties.remove(key);
302   return flush_cached_properties(thd);
303 }
304 
305 }  // namespace tables
306 }  // namespace dd
307