1 /*
2    Copyright (c) 2014, SkySQL Ab
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 as published by
6    the Free Software Foundation; version 2 of the License.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
16 
17 #ifdef USE_PRAGMA_IMPLEMENTATION
18 #pragma implementation  // gcc: Class implementation
19 #endif
20 
21 #include <my_global.h>
22 
23 /* This C++ files header file */
24 #include "./rdb_cf_options.h"
25 
26 /* C++ system header files */
27 #include <string>
28 
29 /* MySQL header files */
30 #include "./log.h"
31 
32 /* RocksDB header files */
33 #include "rocksdb/utilities/convenience.h"
34 
35 /* MyRocks header files */
36 #include "./ha_rocksdb.h"
37 #include "./rdb_cf_manager.h"
38 #include "./rdb_compact_filter.h"
39 
40 namespace myrocks {
41 
42 Rdb_pk_comparator Rdb_cf_options::s_pk_comparator;
43 Rdb_rev_comparator Rdb_cf_options::s_rev_pk_comparator;
44 
init(const rocksdb::BlockBasedTableOptions & table_options,std::shared_ptr<rocksdb::TablePropertiesCollectorFactory> prop_coll_factory,const char * const default_cf_options,const char * const override_cf_options)45 bool Rdb_cf_options::init(
46     const rocksdb::BlockBasedTableOptions &table_options,
47     std::shared_ptr<rocksdb::TablePropertiesCollectorFactory> prop_coll_factory,
48     const char *const default_cf_options,
49     const char *const override_cf_options) {
50   DBUG_ASSERT(default_cf_options != nullptr);
51   DBUG_ASSERT(override_cf_options != nullptr);
52 
53   m_default_cf_opts.comparator = &s_pk_comparator;
54   m_default_cf_opts.compaction_filter_factory.reset(
55       new Rdb_compact_filter_factory);
56 
57   m_default_cf_opts.table_factory.reset(
58       rocksdb::NewBlockBasedTableFactory(table_options));
59 
60   if (prop_coll_factory) {
61     m_default_cf_opts.table_properties_collector_factories.push_back(
62         prop_coll_factory);
63   }
64 
65   if (!set_default(std::string(default_cf_options)) ||
66       !set_override(std::string(override_cf_options))) {
67     return false;
68   }
69 
70   return true;
71 }
72 
get(const std::string & cf_name,rocksdb::ColumnFamilyOptions * const opts)73 void Rdb_cf_options::get(const std::string &cf_name,
74                          rocksdb::ColumnFamilyOptions *const opts) {
75   DBUG_ASSERT(opts != nullptr);
76 
77   // Get defaults.
78   rocksdb::GetColumnFamilyOptionsFromString(*opts, m_default_config, opts);
79 
80   // Get a custom confguration if we have one.
81   Name_to_config_t::iterator it = m_name_map.find(cf_name);
82 
83   if (it != m_name_map.end()) {
84     rocksdb::GetColumnFamilyOptionsFromString(*opts, it->second, opts);
85   }
86 }
87 
update(const std::string & cf_name,const std::string & cf_options)88 void Rdb_cf_options::update(const std::string &cf_name,
89                             const std::string &cf_options) {
90   DBUG_ASSERT(!cf_name.empty());
91   DBUG_ASSERT(!cf_options.empty());
92 
93   // Always update. If we didn't have an entry before then add it.
94   m_name_map[cf_name] = cf_options;
95 
96   DBUG_ASSERT(!m_name_map.empty());
97 }
98 
set_default(const std::string & default_config)99 bool Rdb_cf_options::set_default(const std::string &default_config) {
100   rocksdb::ColumnFamilyOptions options;
101 
102   if (!default_config.empty() && !rocksdb::GetColumnFamilyOptionsFromString(
103                                       options, default_config, &options)
104                                       .ok()) {
105     // NO_LINT_DEBUG
106     fprintf(stderr, "Invalid default column family config: %s\n",
107             default_config.c_str());
108     return false;
109   }
110 
111   m_default_config = default_config;
112   return true;
113 }
114 
115 // Skip over any spaces in the input string.
skip_spaces(const std::string & input,size_t * const pos)116 void Rdb_cf_options::skip_spaces(const std::string &input, size_t *const pos) {
117   DBUG_ASSERT(pos != nullptr);
118 
119   while (*pos < input.size() && isspace(input[*pos])) ++(*pos);
120 }
121 
122 // Find a valid column family name.  Note that all characters except a
123 // semicolon are valid (should this change?) and all spaces are trimmed from
124 // the beginning and end but are not removed between other characters.
find_column_family(const std::string & input,size_t * const pos,std::string * const key)125 bool Rdb_cf_options::find_column_family(const std::string &input,
126                                         size_t *const pos,
127                                         std::string *const key) {
128   DBUG_ASSERT(pos != nullptr);
129   DBUG_ASSERT(key != nullptr);
130 
131   const size_t beg_pos = *pos;
132   size_t end_pos = *pos - 1;
133 
134   // Loop through the characters in the string until we see a '='.
135   for (; *pos < input.size() && input[*pos] != '='; ++(*pos)) {
136     // If this is not a space, move the end position to the current position.
137     if (input[*pos] != ' ') end_pos = *pos;
138   }
139 
140   if (end_pos == beg_pos - 1) {
141     // NO_LINT_DEBUG
142     sql_print_warning("No column family found (options: %s)", input.c_str());
143     return false;
144   }
145 
146   *key = input.substr(beg_pos, end_pos - beg_pos + 1);
147   return true;
148 }
149 
150 // Find a valid options portion.  Everything is deemed valid within the options
151 // portion until we hit as many close curly braces as we have seen open curly
152 // braces.
find_options(const std::string & input,size_t * const pos,std::string * const options)153 bool Rdb_cf_options::find_options(const std::string &input, size_t *const pos,
154                                   std::string *const options) {
155   DBUG_ASSERT(pos != nullptr);
156   DBUG_ASSERT(options != nullptr);
157 
158   // Make sure we have an open curly brace at the current position.
159   if (*pos < input.size() && input[*pos] != '{') {
160     // NO_LINT_DEBUG
161     sql_print_warning("Invalid cf options, '{' expected (options: %s)",
162                       input.c_str());
163     return false;
164   }
165 
166   // Skip the open curly brace and any spaces.
167   ++(*pos);
168   skip_spaces(input, pos);
169 
170   // Set up our brace_count, the begin position and current end position.
171   size_t brace_count = 1;
172   const size_t beg_pos = *pos;
173 
174   // Loop through the characters in the string until we find the appropriate
175   // number of closing curly braces.
176   while (*pos < input.size()) {
177     switch (input[*pos]) {
178       case '}':
179         // If this is a closing curly brace and we bring the count down to zero
180         // we can exit the loop with a valid options string.
181         if (--brace_count == 0) {
182           *options = input.substr(beg_pos, *pos - beg_pos);
183           ++(*pos);  // Move past the last closing curly brace
184           return true;
185         }
186 
187         break;
188 
189       case '{':
190         // If this is an open curly brace increment the count.
191         ++brace_count;
192         break;
193 
194       default:
195         break;
196     }
197 
198     // Move to the next character.
199     ++(*pos);
200   }
201 
202   // We never found the correct number of closing curly braces.
203   // Generate an error.
204   // NO_LINT_DEBUG
205   sql_print_warning("Mismatched cf options, '}' expected (options: %s)",
206                     input.c_str());
207   return false;
208 }
209 
find_cf_options_pair(const std::string & input,size_t * const pos,std::string * const cf,std::string * const opt_str)210 bool Rdb_cf_options::find_cf_options_pair(const std::string &input,
211                                           size_t *const pos,
212                                           std::string *const cf,
213                                           std::string *const opt_str) {
214   DBUG_ASSERT(pos != nullptr);
215   DBUG_ASSERT(cf != nullptr);
216   DBUG_ASSERT(opt_str != nullptr);
217 
218   // Skip any spaces.
219   skip_spaces(input, pos);
220 
221   // We should now have a column family name.
222   if (!find_column_family(input, pos, cf)) return false;
223 
224   // If we are at the end of the input then we generate an error.
225   if (*pos == input.size()) {
226     // NO_LINT_DEBUG
227     sql_print_warning("Invalid cf options, '=' expected (options: %s)",
228                       input.c_str());
229     return false;
230   }
231 
232   // Skip equal sign and any spaces after it
233   ++(*pos);
234   skip_spaces(input, pos);
235 
236   // Find the options for this column family.  This should be in the format
237   // {<options>} where <options> may contain embedded pairs of curly braces.
238   if (!find_options(input, pos, opt_str)) return false;
239 
240   // Skip any trailing spaces after the option string.
241   skip_spaces(input, pos);
242 
243   // We should either be at the end of the input string or at a semicolon.
244   if (*pos < input.size()) {
245     if (input[*pos] != ';') {
246       // NO_LINT_DEBUG
247       sql_print_warning("Invalid cf options, ';' expected (options: %s)",
248                         input.c_str());
249       return false;
250     }
251 
252     ++(*pos);
253   }
254 
255   return true;
256 }
257 
parse_cf_options(const std::string & cf_options,Name_to_config_t * option_map)258 bool Rdb_cf_options::parse_cf_options(const std::string &cf_options,
259                                       Name_to_config_t *option_map) {
260   std::string cf;
261   std::string opt_str;
262   rocksdb::ColumnFamilyOptions options;
263 
264   DBUG_ASSERT(option_map != nullptr);
265   DBUG_ASSERT(option_map->empty());
266 
267   // Loop through the characters of the string until we reach the end.
268   size_t pos = 0;
269 
270   while (pos < cf_options.size()) {
271     // Attempt to find <cf>={<opt_str>}.
272     if (!find_cf_options_pair(cf_options, &pos, &cf, &opt_str)) {
273       return false;
274     }
275 
276     // Generate an error if we have already seen this column family.
277     if (option_map->find(cf) != option_map->end()) {
278       // NO_LINT_DEBUG
279       sql_print_warning(
280           "Duplicate entry for %s in override options (options: %s)",
281           cf.c_str(), cf_options.c_str());
282       return false;
283     }
284 
285     // Generate an error if the <opt_str> is not valid according to RocksDB.
286     if (!rocksdb::GetColumnFamilyOptionsFromString(options, opt_str, &options)
287              .ok()) {
288       // NO_LINT_DEBUG
289       sql_print_warning(
290           "Invalid cf config for %s in override options (options: %s)",
291           cf.c_str(), cf_options.c_str());
292       return false;
293     }
294 
295     // If everything is good, add this cf/opt_str pair to the map.
296     (*option_map)[cf] = opt_str;
297   }
298 
299   return true;
300 }
301 
set_override(const std::string & override_config)302 bool Rdb_cf_options::set_override(const std::string &override_config) {
303   Name_to_config_t configs;
304 
305   if (!parse_cf_options(override_config, &configs)) {
306     return false;
307   }
308 
309   // Everything checked out - make the map live
310   m_name_map = configs;
311 
312   return true;
313 }
314 
get_cf_comparator(const std::string & cf_name)315 const rocksdb::Comparator *Rdb_cf_options::get_cf_comparator(
316     const std::string &cf_name) {
317   if (Rdb_cf_manager::is_cf_name_reverse(cf_name.c_str())) {
318     return &s_rev_pk_comparator;
319   } else {
320     return &s_pk_comparator;
321   }
322 }
323 
get_cf_merge_operator(const std::string & cf_name)324 std::shared_ptr<rocksdb::MergeOperator> Rdb_cf_options::get_cf_merge_operator(
325     const std::string &cf_name) {
326   return (cf_name == DEFAULT_SYSTEM_CF_NAME)
327              ? std::make_shared<Rdb_system_merge_op>()
328              : nullptr;
329 }
330 
get_cf_options(const std::string & cf_name,rocksdb::ColumnFamilyOptions * const opts)331 void Rdb_cf_options::get_cf_options(const std::string &cf_name,
332                                     rocksdb::ColumnFamilyOptions *const opts) {
333   *opts = m_default_cf_opts;
334   get(cf_name, opts);
335 
336   // Set the comparator according to 'rev:'
337   opts->comparator = get_cf_comparator(cf_name);
338   opts->merge_operator = get_cf_merge_operator(cf_name);
339 }
340 
341 }  // namespace myrocks
342