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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 | |
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 | |
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 | |
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 | |
99 | bool Rdb_cf_options::set_default(const std::string &default_config) { |
100 | rocksdb::ColumnFamilyOptions options; |
101 | |
102 | if (!default_config.empty() && |
103 | !rocksdb::GetColumnFamilyOptionsFromString(options, default_config, |
104 | &options) |
105 | .ok()) { |
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. |
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])) |
120 | ++(*pos); |
121 | } |
122 | |
123 | // Find a valid column family name. Note that all characters except a |
124 | // semicolon are valid (should this change?) and all spaces are trimmed from |
125 | // the beginning and end but are not removed between other characters. |
126 | bool Rdb_cf_options::find_column_family(const std::string &input, |
127 | size_t *const pos, |
128 | std::string *const key) { |
129 | DBUG_ASSERT(pos != nullptr); |
130 | DBUG_ASSERT(key != nullptr); |
131 | |
132 | const size_t beg_pos = *pos; |
133 | size_t end_pos = *pos - 1; |
134 | |
135 | // Loop through the characters in the string until we see a '='. |
136 | for (; *pos < input.size() && input[*pos] != '='; ++(*pos)) { |
137 | // If this is not a space, move the end position to the current position. |
138 | if (input[*pos] != ' ') |
139 | end_pos = *pos; |
140 | } |
141 | |
142 | if (end_pos == beg_pos - 1) { |
143 | // NO_LINT_DEBUG |
144 | sql_print_warning("No column family found (options: %s)" , input.c_str()); |
145 | return false; |
146 | } |
147 | |
148 | *key = input.substr(beg_pos, end_pos - beg_pos + 1); |
149 | return true; |
150 | } |
151 | |
152 | // Find a valid options portion. Everything is deemed valid within the options |
153 | // portion until we hit as many close curly braces as we have seen open curly |
154 | // braces. |
155 | bool Rdb_cf_options::find_options(const std::string &input, size_t *const pos, |
156 | std::string *const options) { |
157 | DBUG_ASSERT(pos != nullptr); |
158 | DBUG_ASSERT(options != nullptr); |
159 | |
160 | // Make sure we have an open curly brace at the current position. |
161 | if (*pos < input.size() && input[*pos] != '{') { |
162 | // NO_LINT_DEBUG |
163 | sql_print_warning("Invalid cf options, '{' expected (options: %s)" , |
164 | input.c_str()); |
165 | return false; |
166 | } |
167 | |
168 | // Skip the open curly brace and any spaces. |
169 | ++(*pos); |
170 | skip_spaces(input, pos); |
171 | |
172 | // Set up our brace_count, the begin position and current end position. |
173 | size_t brace_count = 1; |
174 | const size_t beg_pos = *pos; |
175 | |
176 | // Loop through the characters in the string until we find the appropriate |
177 | // number of closing curly braces. |
178 | while (*pos < input.size()) { |
179 | switch (input[*pos]) { |
180 | case '}': |
181 | // If this is a closing curly brace and we bring the count down to zero |
182 | // we can exit the loop with a valid options string. |
183 | if (--brace_count == 0) { |
184 | *options = input.substr(beg_pos, *pos - beg_pos); |
185 | ++(*pos); // Move past the last closing curly brace |
186 | return true; |
187 | } |
188 | |
189 | break; |
190 | |
191 | case '{': |
192 | // If this is an open curly brace increment the count. |
193 | ++brace_count; |
194 | break; |
195 | |
196 | default: |
197 | break; |
198 | } |
199 | |
200 | // Move to the next character. |
201 | ++(*pos); |
202 | } |
203 | |
204 | // We never found the correct number of closing curly braces. |
205 | // Generate an error. |
206 | // NO_LINT_DEBUG |
207 | sql_print_warning("Mismatched cf options, '}' expected (options: %s)" , |
208 | input.c_str()); |
209 | return false; |
210 | } |
211 | |
212 | bool Rdb_cf_options::find_cf_options_pair(const std::string &input, |
213 | size_t *const pos, |
214 | std::string *const cf, |
215 | std::string *const opt_str) { |
216 | DBUG_ASSERT(pos != nullptr); |
217 | DBUG_ASSERT(cf != nullptr); |
218 | DBUG_ASSERT(opt_str != nullptr); |
219 | |
220 | // Skip any spaces. |
221 | skip_spaces(input, pos); |
222 | |
223 | // We should now have a column family name. |
224 | if (!find_column_family(input, pos, cf)) |
225 | return false; |
226 | |
227 | // If we are at the end of the input then we generate an error. |
228 | if (*pos == input.size()) { |
229 | // NO_LINT_DEBUG |
230 | sql_print_warning("Invalid cf options, '=' expected (options: %s)" , |
231 | input.c_str()); |
232 | return false; |
233 | } |
234 | |
235 | // Skip equal sign and any spaces after it |
236 | ++(*pos); |
237 | skip_spaces(input, pos); |
238 | |
239 | // Find the options for this column family. This should be in the format |
240 | // {<options>} where <options> may contain embedded pairs of curly braces. |
241 | if (!find_options(input, pos, opt_str)) |
242 | return false; |
243 | |
244 | // Skip any trailing spaces after the option string. |
245 | skip_spaces(input, pos); |
246 | |
247 | // We should either be at the end of the input string or at a semicolon. |
248 | if (*pos < input.size()) { |
249 | if (input[*pos] != ';') { |
250 | // NO_LINT_DEBUG |
251 | sql_print_warning("Invalid cf options, ';' expected (options: %s)" , |
252 | input.c_str()); |
253 | return false; |
254 | } |
255 | |
256 | ++(*pos); |
257 | } |
258 | |
259 | return true; |
260 | } |
261 | |
262 | bool Rdb_cf_options::parse_cf_options(const std::string &cf_options, |
263 | Name_to_config_t *option_map) { |
264 | std::string cf; |
265 | std::string opt_str; |
266 | rocksdb::ColumnFamilyOptions options; |
267 | |
268 | DBUG_ASSERT(option_map != nullptr); |
269 | DBUG_ASSERT(option_map->empty()); |
270 | |
271 | // Loop through the characters of the string until we reach the end. |
272 | size_t pos = 0; |
273 | |
274 | while (pos < cf_options.size()) { |
275 | // Attempt to find <cf>={<opt_str>}. |
276 | if (!find_cf_options_pair(cf_options, &pos, &cf, &opt_str)) { |
277 | return false; |
278 | } |
279 | |
280 | // Generate an error if we have already seen this column family. |
281 | if (option_map->find(cf) != option_map->end()) { |
282 | // NO_LINT_DEBUG |
283 | sql_print_warning( |
284 | "Duplicate entry for %s in override options (options: %s)" , |
285 | cf.c_str(), cf_options.c_str()); |
286 | return false; |
287 | } |
288 | |
289 | // Generate an error if the <opt_str> is not valid according to RocksDB. |
290 | if (!rocksdb::GetColumnFamilyOptionsFromString(options, opt_str, &options) |
291 | .ok()) { |
292 | // NO_LINT_DEBUG |
293 | sql_print_warning( |
294 | "Invalid cf config for %s in override options (options: %s)" , |
295 | cf.c_str(), cf_options.c_str()); |
296 | return false; |
297 | } |
298 | |
299 | // If everything is good, add this cf/opt_str pair to the map. |
300 | (*option_map)[cf] = opt_str; |
301 | } |
302 | |
303 | return true; |
304 | } |
305 | |
306 | bool Rdb_cf_options::set_override(const std::string &override_config) { |
307 | Name_to_config_t configs; |
308 | |
309 | if (!parse_cf_options(override_config, &configs)) { |
310 | return false; |
311 | } |
312 | |
313 | // Everything checked out - make the map live |
314 | m_name_map = configs; |
315 | |
316 | return true; |
317 | } |
318 | |
319 | const rocksdb::Comparator * |
320 | Rdb_cf_options::get_cf_comparator(const std::string &cf_name) { |
321 | if (Rdb_cf_manager::is_cf_name_reverse(cf_name.c_str())) { |
322 | return &s_rev_pk_comparator; |
323 | } else { |
324 | return &s_pk_comparator; |
325 | } |
326 | } |
327 | |
328 | std::shared_ptr<rocksdb::MergeOperator> |
329 | Rdb_cf_options::get_cf_merge_operator(const std::string &cf_name) { |
330 | return (cf_name == DEFAULT_SYSTEM_CF_NAME) |
331 | ? std::make_shared<Rdb_system_merge_op>() |
332 | : nullptr; |
333 | } |
334 | |
335 | void Rdb_cf_options::get_cf_options(const std::string &cf_name, |
336 | rocksdb::ColumnFamilyOptions *const opts) { |
337 | DBUG_ASSERT(opts != nullptr); |
338 | |
339 | *opts = m_default_cf_opts; |
340 | get(cf_name, opts); |
341 | |
342 | // Set the comparator according to 'rev:' |
343 | opts->comparator = get_cf_comparator(cf_name); |
344 | opts->merge_operator = get_cf_merge_operator(cf_name); |
345 | } |
346 | |
347 | } // namespace myrocks |
348 | |