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