| 1 | /* -*- c-basic-offset: 2 -*- */ |
| 2 | /* |
| 3 | Copyright(C) 2010 Tetsuro IKEDA |
| 4 | Copyright(C) 2010-2013 Kentoku SHIBA |
| 5 | Copyright(C) 2011-2015 Kouhei Sutou <kou@clear-code.com> |
| 6 | |
| 7 | This library is free software; you can redistribute it and/or |
| 8 | modify it under the terms of the GNU Lesser General Public |
| 9 | License as published by the Free Software Foundation; either |
| 10 | version 2.1 of the License, or (at your option) any later version. |
| 11 | |
| 12 | This library is distributed in the hope that it will be useful, |
| 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | Lesser General Public License for more details. |
| 16 | |
| 17 | You should have received a copy of the GNU Lesser General Public |
| 18 | License along with this library; if not, write to the Free Software |
| 19 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 20 | */ |
| 21 | |
| 22 | #include <mrn_mysql.h> |
| 23 | |
| 24 | #include "mrn_database_manager.hpp" |
| 25 | #include "mrn_encoding.hpp" |
| 26 | #include "mrn_lock.hpp" |
| 27 | #include "mrn_path_mapper.hpp" |
| 28 | |
| 29 | #include <groonga/plugin.h> |
| 30 | |
| 31 | // for debug |
| 32 | #define MRN_CLASS_NAME "mrn::DatabaseManager" |
| 33 | |
| 34 | #ifdef WIN32 |
| 35 | # include <direct.h> |
| 36 | # define MRN_MKDIR(pathname, mode) _mkdir((pathname)) |
| 37 | #else |
| 38 | # include <dirent.h> |
| 39 | # include <unistd.h> |
| 40 | # define MRN_MKDIR(pathname, mode) mkdir((pathname), (mode)) |
| 41 | #endif |
| 42 | |
| 43 | extern "C" { |
| 44 | grn_rc GRN_PLUGIN_IMPL_NAME_TAGGED(init, normalizers_mysql)(grn_ctx *ctx); |
| 45 | grn_rc GRN_PLUGIN_IMPL_NAME_TAGGED(register, normalizers_mysql)(grn_ctx *ctx); |
| 46 | } |
| 47 | |
| 48 | namespace mrn { |
| 49 | DatabaseManager::DatabaseManager(grn_ctx *ctx, mysql_mutex_t *mutex) |
| 50 | : ctx_(ctx), |
| 51 | cache_(NULL), |
| 52 | mutex_(mutex) { |
| 53 | } |
| 54 | |
| 55 | DatabaseManager::~DatabaseManager(void) { |
| 56 | if (cache_) { |
| 57 | void *db_address; |
| 58 | GRN_HASH_EACH(ctx_, cache_, id, NULL, 0, &db_address, { |
| 59 | Database *db; |
| 60 | memcpy(&db, db_address, sizeof(grn_obj *)); |
| 61 | delete db; |
| 62 | }); |
| 63 | grn_hash_close(ctx_, cache_); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | bool DatabaseManager::init(void) { |
| 68 | MRN_DBUG_ENTER_METHOD(); |
| 69 | cache_ = grn_hash_create(ctx_, |
| 70 | NULL, |
| 71 | GRN_TABLE_MAX_KEY_SIZE, |
| 72 | sizeof(grn_obj *), |
| 73 | GRN_OBJ_KEY_VAR_SIZE); |
| 74 | if (!cache_) { |
| 75 | GRN_LOG(ctx_, GRN_LOG_ERROR, |
| 76 | "failed to initialize hash table for caching opened databases" ); |
| 77 | DBUG_RETURN(false); |
| 78 | } |
| 79 | |
| 80 | DBUG_RETURN(true); |
| 81 | } |
| 82 | |
| 83 | int DatabaseManager::open(const char *path, Database **db) { |
| 84 | MRN_DBUG_ENTER_METHOD(); |
| 85 | |
| 86 | int error = 0; |
| 87 | *db = NULL; |
| 88 | |
| 89 | mrn::PathMapper mapper(path); |
| 90 | mrn::Lock lock(mutex_); |
| 91 | |
| 92 | error = mrn::encoding::set(ctx_, system_charset_info); |
| 93 | if (error) { |
| 94 | DBUG_RETURN(error); |
| 95 | } |
| 96 | |
| 97 | grn_id id; |
| 98 | void *db_address; |
| 99 | id = grn_hash_get(ctx_, cache_, |
| 100 | mapper.db_name(), strlen(mapper.db_name()), |
| 101 | &db_address); |
| 102 | if (id == GRN_ID_NIL) { |
| 103 | grn_obj *grn_db; |
| 104 | struct stat db_stat; |
| 105 | if (stat(mapper.db_path(), &db_stat)) { |
| 106 | GRN_LOG(ctx_, GRN_LOG_INFO, |
| 107 | "database not found. creating...: <%s>" , mapper.db_path()); |
| 108 | if (path[0] == FN_CURLIB && |
| 109 | mrn_is_directory_separator(path[1])) { |
| 110 | ensure_database_directory(); |
| 111 | } |
| 112 | grn_db = grn_db_create(ctx_, mapper.db_path(), NULL); |
| 113 | if (ctx_->rc) { |
| 114 | error = ER_CANT_CREATE_TABLE; |
| 115 | my_message(error, ctx_->errbuf, MYF(0)); |
| 116 | DBUG_RETURN(error); |
| 117 | } |
| 118 | } else { |
| 119 | grn_db = grn_db_open(ctx_, mapper.db_path()); |
| 120 | if (ctx_->rc) { |
| 121 | error = ER_CANT_OPEN_FILE; |
| 122 | my_message(error, ctx_->errbuf, MYF(0)); |
| 123 | DBUG_RETURN(error); |
| 124 | } |
| 125 | } |
| 126 | *db = new Database(ctx_, grn_db); |
| 127 | grn_hash_add(ctx_, cache_, |
| 128 | mapper.db_name(), strlen(mapper.db_name()), |
| 129 | &db_address, NULL); |
| 130 | memcpy(db_address, db, sizeof(Database *)); |
| 131 | error = ensure_normalizers_registered((*db)->get()); |
| 132 | if (!error) { |
| 133 | if ((*db)->is_broken()) { |
| 134 | error = ER_CANT_OPEN_FILE; |
| 135 | char error_message[MRN_MESSAGE_BUFFER_SIZE]; |
| 136 | snprintf(error_message, MRN_MESSAGE_BUFFER_SIZE, |
| 137 | "mroonga: database: open: " |
| 138 | "The database maybe broken. " |
| 139 | "We recommend you to recreate the database. " |
| 140 | "If the database isn't broken, " |
| 141 | "you can remove this error by running " |
| 142 | "'groonga %s table_remove mroonga_operations' " |
| 143 | "on server. But the latter isn't recommended." , |
| 144 | mapper.db_path()); |
| 145 | my_message(error, error_message, MYF(0)); |
| 146 | } |
| 147 | } |
| 148 | } else { |
| 149 | memcpy(db, db_address, sizeof(Database *)); |
| 150 | grn_ctx_use(ctx_, (*db)->get()); |
| 151 | } |
| 152 | |
| 153 | DBUG_RETURN(error); |
| 154 | } |
| 155 | |
| 156 | void DatabaseManager::close(const char *path) { |
| 157 | MRN_DBUG_ENTER_METHOD(); |
| 158 | |
| 159 | mrn::PathMapper mapper(path); |
| 160 | mrn::Lock lock(mutex_); |
| 161 | |
| 162 | grn_id id; |
| 163 | void *db_address; |
| 164 | id = grn_hash_get(ctx_, cache_, |
| 165 | mapper.db_name(), strlen(mapper.db_name()), |
| 166 | &db_address); |
| 167 | if (id == GRN_ID_NIL) { |
| 168 | DBUG_VOID_RETURN; |
| 169 | } |
| 170 | |
| 171 | Database *db = NULL; |
| 172 | memcpy(&db, db_address, sizeof(Database *)); |
| 173 | grn_ctx_use(ctx_, db->get()); |
| 174 | if (db) { |
| 175 | delete db; |
| 176 | } |
| 177 | |
| 178 | grn_hash_delete_by_id(ctx_, cache_, id, NULL); |
| 179 | |
| 180 | DBUG_VOID_RETURN; |
| 181 | } |
| 182 | |
| 183 | bool DatabaseManager::drop(const char *path) { |
| 184 | MRN_DBUG_ENTER_METHOD(); |
| 185 | |
| 186 | mrn::PathMapper mapper(path); |
| 187 | mrn::Lock lock(mutex_); |
| 188 | |
| 189 | grn_id id; |
| 190 | void *db_address; |
| 191 | id = grn_hash_get(ctx_, cache_, |
| 192 | mapper.db_name(), strlen(mapper.db_name()), |
| 193 | &db_address); |
| 194 | |
| 195 | Database *db = NULL; |
| 196 | if (id == GRN_ID_NIL) { |
| 197 | struct stat dummy; |
| 198 | if (stat(mapper.db_path(), &dummy) == 0) { |
| 199 | grn_obj *grn_db = grn_db_open(ctx_, mapper.db_path()); |
| 200 | db = new Database(ctx_, grn_db); |
| 201 | } |
| 202 | } else { |
| 203 | memcpy(&db, db_address, sizeof(Database *)); |
| 204 | grn_ctx_use(ctx_, db->get()); |
| 205 | } |
| 206 | |
| 207 | if (!db) { |
| 208 | DBUG_RETURN(false); |
| 209 | } |
| 210 | |
| 211 | if (db->remove() == GRN_SUCCESS) { |
| 212 | if (id != GRN_ID_NIL) { |
| 213 | grn_hash_delete_by_id(ctx_, cache_, id, NULL); |
| 214 | } |
| 215 | delete db; |
| 216 | DBUG_RETURN(true); |
| 217 | } else { |
| 218 | GRN_LOG(ctx_, GRN_LOG_ERROR, |
| 219 | "failed to drop database: <%s>: <%s>" , |
| 220 | mapper.db_path(), ctx_->errbuf); |
| 221 | if (id == GRN_ID_NIL) { |
| 222 | delete db; |
| 223 | } |
| 224 | DBUG_RETURN(false); |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | int DatabaseManager::clear(void) { |
| 229 | MRN_DBUG_ENTER_METHOD(); |
| 230 | |
| 231 | int error = 0; |
| 232 | |
| 233 | mrn::Lock lock(mutex_); |
| 234 | |
| 235 | grn_hash_cursor *cursor; |
| 236 | cursor = grn_hash_cursor_open(ctx_, cache_, |
| 237 | NULL, 0, NULL, 0, |
| 238 | 0, -1, 0); |
| 239 | if (ctx_->rc) { |
| 240 | my_message(ER_ERROR_ON_READ, ctx_->errbuf, MYF(0)); |
| 241 | DBUG_RETURN(ER_ERROR_ON_READ); |
| 242 | } |
| 243 | |
| 244 | while (grn_hash_cursor_next(ctx_, cursor) != GRN_ID_NIL) { |
| 245 | if (ctx_->rc) { |
| 246 | error = ER_ERROR_ON_READ; |
| 247 | my_message(error, ctx_->errbuf, MYF(0)); |
| 248 | break; |
| 249 | } |
| 250 | void *db_address; |
| 251 | Database *db; |
| 252 | grn_hash_cursor_get_value(ctx_, cursor, &db_address); |
| 253 | memcpy(&db, db_address, sizeof(Database *)); |
| 254 | grn_ctx_use(ctx_, db->get()); |
| 255 | grn_rc rc = grn_hash_cursor_delete(ctx_, cursor, NULL); |
| 256 | if (rc) { |
| 257 | error = ER_ERROR_ON_READ; |
| 258 | my_message(error, ctx_->errbuf, MYF(0)); |
| 259 | break; |
| 260 | } |
| 261 | delete db; |
| 262 | } |
| 263 | grn_hash_cursor_close(ctx_, cursor); |
| 264 | |
| 265 | DBUG_RETURN(error); |
| 266 | } |
| 267 | |
| 268 | const char *DatabaseManager::error_message() { |
| 269 | MRN_DBUG_ENTER_METHOD(); |
| 270 | DBUG_RETURN(ctx_->errbuf); |
| 271 | } |
| 272 | |
| 273 | void DatabaseManager::mkdir_p(const char *directory) { |
| 274 | MRN_DBUG_ENTER_METHOD(); |
| 275 | |
| 276 | int i = 0; |
| 277 | char sub_directory[MRN_MAX_PATH_SIZE]; |
| 278 | sub_directory[0] = '\0'; |
| 279 | while (true) { |
| 280 | if (mrn_is_directory_separator(directory[i]) || |
| 281 | directory[i] == '\0') { |
| 282 | sub_directory[i] = '\0'; |
| 283 | struct stat directory_status; |
| 284 | if (stat(sub_directory, &directory_status) != 0) { |
| 285 | DBUG_PRINT("info" , ("mroonga: creating directory: <%s>" , sub_directory)); |
| 286 | GRN_LOG(ctx_, GRN_LOG_INFO, "creating directory: <%s>" , sub_directory); |
| 287 | if (MRN_MKDIR(sub_directory, S_IRWXU) == 0) { |
| 288 | DBUG_PRINT("info" , |
| 289 | ("mroonga: created directory: <%s>" , sub_directory)); |
| 290 | GRN_LOG(ctx_, GRN_LOG_INFO, "created directory: <%s>" , sub_directory); |
| 291 | } else { |
| 292 | DBUG_PRINT("error" , |
| 293 | ("mroonga: failed to create directory: <%s>: <%s>" , |
| 294 | sub_directory, strerror(errno))); |
| 295 | GRN_LOG(ctx_, GRN_LOG_ERROR, |
| 296 | "failed to create directory: <%s>: <%s>" , |
| 297 | sub_directory, strerror(errno)); |
| 298 | DBUG_VOID_RETURN; |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | if (directory[i] == '\0') { |
| 304 | break; |
| 305 | } |
| 306 | |
| 307 | sub_directory[i] = directory[i]; |
| 308 | ++i; |
| 309 | } |
| 310 | |
| 311 | DBUG_VOID_RETURN; |
| 312 | } |
| 313 | |
| 314 | void DatabaseManager::ensure_database_directory(void) { |
| 315 | MRN_DBUG_ENTER_METHOD(); |
| 316 | |
| 317 | const char *path_prefix = mrn::PathMapper::default_path_prefix; |
| 318 | if (!path_prefix) |
| 319 | DBUG_VOID_RETURN; |
| 320 | |
| 321 | const char *last_path_separator; |
| 322 | last_path_separator = strrchr(path_prefix, FN_LIBCHAR); |
| 323 | #ifdef FN_LIBCHAR2 |
| 324 | if (!last_path_separator) |
| 325 | last_path_separator = strrchr(path_prefix, FN_LIBCHAR2); |
| 326 | #endif |
| 327 | if (!last_path_separator) |
| 328 | DBUG_VOID_RETURN; |
| 329 | if (path_prefix == last_path_separator) |
| 330 | DBUG_VOID_RETURN; |
| 331 | |
| 332 | char database_directory[MRN_MAX_PATH_SIZE]; |
| 333 | size_t database_directory_length = last_path_separator - path_prefix; |
| 334 | strncpy(database_directory, path_prefix, database_directory_length); |
| 335 | database_directory[database_directory_length] = '\0'; |
| 336 | mkdir_p(database_directory); |
| 337 | |
| 338 | DBUG_VOID_RETURN; |
| 339 | } |
| 340 | |
| 341 | int DatabaseManager::ensure_normalizers_registered(grn_obj *db) { |
| 342 | MRN_DBUG_ENTER_METHOD(); |
| 343 | |
| 344 | int error = 0; |
| 345 | #ifdef WITH_GROONGA_NORMALIZER_MYSQL |
| 346 | { |
| 347 | # ifdef MRN_GROONGA_NORMALIZER_MYSQL_EMBEDDED |
| 348 | GRN_PLUGIN_IMPL_NAME_TAGGED(init, normalizers_mysql)(ctx_); |
| 349 | GRN_PLUGIN_IMPL_NAME_TAGGED(register, normalizers_mysql)(ctx_); |
| 350 | # else |
| 351 | grn_obj *mysql_normalizer; |
| 352 | mysql_normalizer = grn_ctx_get(ctx_, "NormalizerMySQLGeneralCI" , -1); |
| 353 | if (mysql_normalizer) { |
| 354 | grn_obj_unlink(ctx_, mysql_normalizer); |
| 355 | } else { |
| 356 | grn_plugin_register(ctx_, GROONGA_NORMALIZER_MYSQL_PLUGIN_NAME); |
| 357 | } |
| 358 | # endif |
| 359 | } |
| 360 | #endif |
| 361 | |
| 362 | DBUG_RETURN(error); |
| 363 | } |
| 364 | } |
| 365 | |