| 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 | |