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
40namespace myrocks {
41
42Rdb_pk_comparator Rdb_cf_options::s_pk_comparator;
43Rdb_rev_comparator Rdb_cf_options::s_rev_pk_comparator;
44
45bool 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
73void 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
88void 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
99bool 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.
116void 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.
126bool 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.
155bool 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
212bool 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
262bool 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
306bool 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
319const rocksdb::Comparator *
320Rdb_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
328std::shared_ptr<rocksdb::MergeOperator>
329Rdb_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
335void 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