summaryrefslogtreecommitdiffstats
path: root/storage/mroonga/lib/mrn_database_manager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'storage/mroonga/lib/mrn_database_manager.cpp')
-rw-r--r--storage/mroonga/lib/mrn_database_manager.cpp364
1 files changed, 364 insertions, 0 deletions
diff --git a/storage/mroonga/lib/mrn_database_manager.cpp b/storage/mroonga/lib/mrn_database_manager.cpp
new file mode 100644
index 00000000..149c556f
--- /dev/null
+++ b/storage/mroonga/lib/mrn_database_manager.cpp
@@ -0,0 +1,364 @@
+/* -*- c-basic-offset: 2 -*- */
+/*
+ Copyright(C) 2010 Tetsuro IKEDA
+ Copyright(C) 2010-2013 Kentoku SHIBA
+ Copyright(C) 2011-2015 Kouhei Sutou <kou@clear-code.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
+*/
+
+#include <mrn_mysql.h>
+
+#include "mrn_database_manager.hpp"
+#include "mrn_encoding.hpp"
+#include "mrn_lock.hpp"
+#include "mrn_path_mapper.hpp"
+
+#include <groonga/plugin.h>
+
+// for debug
+#define MRN_CLASS_NAME "mrn::DatabaseManager"
+
+#ifdef WIN32
+# include <direct.h>
+# define MRN_MKDIR(pathname, mode) _mkdir((pathname))
+#else
+# include <dirent.h>
+# include <unistd.h>
+# define MRN_MKDIR(pathname, mode) mkdir((pathname), (mode))
+#endif
+
+extern "C" {
+ grn_rc GRN_PLUGIN_IMPL_NAME_TAGGED(init, normalizers_mysql)(grn_ctx *ctx);
+ grn_rc GRN_PLUGIN_IMPL_NAME_TAGGED(register, normalizers_mysql)(grn_ctx *ctx);
+}
+
+namespace mrn {
+ DatabaseManager::DatabaseManager(grn_ctx *ctx, mysql_mutex_t *mutex)
+ : ctx_(ctx),
+ cache_(NULL),
+ mutex_(mutex) {
+ }
+
+ DatabaseManager::~DatabaseManager(void) {
+ if (cache_) {
+ void *db_address;
+ GRN_HASH_EACH(ctx_, cache_, id, NULL, 0, &db_address, {
+ Database *db;
+ memcpy(&db, db_address, sizeof(grn_obj *));
+ delete db;
+ });
+ grn_hash_close(ctx_, cache_);
+ }
+ }
+
+ bool DatabaseManager::init(void) {
+ MRN_DBUG_ENTER_METHOD();
+ cache_ = grn_hash_create(ctx_,
+ NULL,
+ GRN_TABLE_MAX_KEY_SIZE,
+ sizeof(grn_obj *),
+ GRN_OBJ_KEY_VAR_SIZE);
+ if (!cache_) {
+ GRN_LOG(ctx_, GRN_LOG_ERROR,
+ "failed to initialize hash table for caching opened databases");
+ DBUG_RETURN(false);
+ }
+
+ DBUG_RETURN(true);
+ }
+
+ int DatabaseManager::open(const char *path, Database **db) {
+ MRN_DBUG_ENTER_METHOD();
+
+ int error = 0;
+ *db = NULL;
+
+ mrn::PathMapper mapper(path);
+ mrn::Lock lock(mutex_);
+
+ error = mrn::encoding::set(ctx_, system_charset_info);
+ if (error) {
+ DBUG_RETURN(error);
+ }
+
+ grn_id id;
+ void *db_address;
+ id = grn_hash_get(ctx_, cache_,
+ mapper.db_name(), strlen(mapper.db_name()),
+ &db_address);
+ if (id == GRN_ID_NIL) {
+ grn_obj *grn_db;
+ struct stat db_stat;
+ if (stat(mapper.db_path(), &db_stat)) {
+ GRN_LOG(ctx_, GRN_LOG_INFO,
+ "database not found. creating...: <%s>", mapper.db_path());
+ if (path[0] == FN_CURLIB &&
+ mrn_is_directory_separator(path[1])) {
+ ensure_database_directory();
+ }
+ grn_db = grn_db_create(ctx_, mapper.db_path(), NULL);
+ if (ctx_->rc) {
+ error = ER_CANT_CREATE_TABLE;
+ my_message(error, ctx_->errbuf, MYF(0));
+ DBUG_RETURN(error);
+ }
+ } else {
+ grn_db = grn_db_open(ctx_, mapper.db_path());
+ if (ctx_->rc) {
+ error = ER_CANT_OPEN_FILE;
+ my_message(error, ctx_->errbuf, MYF(0));
+ DBUG_RETURN(error);
+ }
+ }
+ *db = new Database(ctx_, grn_db);
+ grn_hash_add(ctx_, cache_,
+ mapper.db_name(), strlen(mapper.db_name()),
+ &db_address, NULL);
+ memcpy(db_address, db, sizeof(Database *));
+ error = ensure_normalizers_registered((*db)->get());
+ if (!error) {
+ if ((*db)->is_broken()) {
+ error = ER_CANT_OPEN_FILE;
+ char error_message[MRN_MESSAGE_BUFFER_SIZE];
+ snprintf(error_message, MRN_MESSAGE_BUFFER_SIZE,
+ "mroonga: database: open: "
+ "The database maybe broken. "
+ "We recommend you to recreate the database. "
+ "If the database isn't broken, "
+ "you can remove this error by running "
+ "'groonga %s table_remove mroonga_operations' "
+ "on server. But the latter isn't recommended.",
+ mapper.db_path());
+ my_message(error, error_message, MYF(0));
+ }
+ }
+ } else {
+ memcpy(db, db_address, sizeof(Database *));
+ grn_ctx_use(ctx_, (*db)->get());
+ }
+
+ DBUG_RETURN(error);
+ }
+
+ void DatabaseManager::close(const char *path) {
+ MRN_DBUG_ENTER_METHOD();
+
+ mrn::PathMapper mapper(path);
+ mrn::Lock lock(mutex_);
+
+ grn_id id;
+ void *db_address;
+ id = grn_hash_get(ctx_, cache_,
+ mapper.db_name(), strlen(mapper.db_name()),
+ &db_address);
+ if (id == GRN_ID_NIL) {
+ DBUG_VOID_RETURN;
+ }
+
+ Database *db = NULL;
+ memcpy(&db, db_address, sizeof(Database *));
+ grn_ctx_use(ctx_, db->get());
+ if (db) {
+ delete db;
+ }
+
+ grn_hash_delete_by_id(ctx_, cache_, id, NULL);
+
+ DBUG_VOID_RETURN;
+ }
+
+ bool DatabaseManager::drop(const char *path) {
+ MRN_DBUG_ENTER_METHOD();
+
+ mrn::PathMapper mapper(path);
+ mrn::Lock lock(mutex_);
+
+ grn_id id;
+ void *db_address;
+ id = grn_hash_get(ctx_, cache_,
+ mapper.db_name(), strlen(mapper.db_name()),
+ &db_address);
+
+ Database *db = NULL;
+ if (id == GRN_ID_NIL) {
+ struct stat dummy;
+ if (stat(mapper.db_path(), &dummy) == 0) {
+ grn_obj *grn_db = grn_db_open(ctx_, mapper.db_path());
+ db = new Database(ctx_, grn_db);
+ }
+ } else {
+ memcpy(&db, db_address, sizeof(Database *));
+ grn_ctx_use(ctx_, db->get());
+ }
+
+ if (!db) {
+ DBUG_RETURN(false);
+ }
+
+ if (db->remove() == GRN_SUCCESS) {
+ if (id != GRN_ID_NIL) {
+ grn_hash_delete_by_id(ctx_, cache_, id, NULL);
+ }
+ delete db;
+ DBUG_RETURN(true);
+ } else {
+ GRN_LOG(ctx_, GRN_LOG_ERROR,
+ "failed to drop database: <%s>: <%s>",
+ mapper.db_path(), ctx_->errbuf);
+ if (id == GRN_ID_NIL) {
+ delete db;
+ }
+ DBUG_RETURN(false);
+ }
+ }
+
+ int DatabaseManager::clear(void) {
+ MRN_DBUG_ENTER_METHOD();
+
+ int error = 0;
+
+ mrn::Lock lock(mutex_);
+
+ grn_hash_cursor *cursor;
+ cursor = grn_hash_cursor_open(ctx_, cache_,
+ NULL, 0, NULL, 0,
+ 0, -1, 0);
+ if (ctx_->rc) {
+ my_message(ER_ERROR_ON_READ, ctx_->errbuf, MYF(0));
+ DBUG_RETURN(ER_ERROR_ON_READ);
+ }
+
+ while (grn_hash_cursor_next(ctx_, cursor) != GRN_ID_NIL) {
+ if (ctx_->rc) {
+ error = ER_ERROR_ON_READ;
+ my_message(error, ctx_->errbuf, MYF(0));
+ break;
+ }
+ void *db_address;
+ Database *db;
+ grn_hash_cursor_get_value(ctx_, cursor, &db_address);
+ memcpy(&db, db_address, sizeof(Database *));
+ grn_ctx_use(ctx_, db->get());
+ grn_rc rc = grn_hash_cursor_delete(ctx_, cursor, NULL);
+ if (rc) {
+ error = ER_ERROR_ON_READ;
+ my_message(error, ctx_->errbuf, MYF(0));
+ break;
+ }
+ delete db;
+ }
+ grn_hash_cursor_close(ctx_, cursor);
+
+ DBUG_RETURN(error);
+ }
+
+ const char *DatabaseManager::error_message() {
+ MRN_DBUG_ENTER_METHOD();
+ DBUG_RETURN(ctx_->errbuf);
+ }
+
+ void DatabaseManager::mkdir_p(const char *directory) {
+ MRN_DBUG_ENTER_METHOD();
+
+ int i = 0;
+ char sub_directory[MRN_MAX_PATH_SIZE];
+ sub_directory[0] = '\0';
+ while (true) {
+ if (mrn_is_directory_separator(directory[i]) ||
+ directory[i] == '\0') {
+ sub_directory[i] = '\0';
+ struct stat directory_status;
+ if (stat(sub_directory, &directory_status) != 0) {
+ DBUG_PRINT("info", ("mroonga: creating directory: <%s>", sub_directory));
+ GRN_LOG(ctx_, GRN_LOG_INFO, "creating directory: <%s>", sub_directory);
+ if (MRN_MKDIR(sub_directory, S_IRWXU) == 0) {
+ DBUG_PRINT("info",
+ ("mroonga: created directory: <%s>", sub_directory));
+ GRN_LOG(ctx_, GRN_LOG_INFO, "created directory: <%s>", sub_directory);
+ } else {
+ DBUG_PRINT("error",
+ ("mroonga: failed to create directory: <%s>: <%s>",
+ sub_directory, strerror(errno)));
+ GRN_LOG(ctx_, GRN_LOG_ERROR,
+ "failed to create directory: <%s>: <%s>",
+ sub_directory, strerror(errno));
+ DBUG_VOID_RETURN;
+ }
+ }
+ }
+
+ if (directory[i] == '\0') {
+ break;
+ }
+
+ sub_directory[i] = directory[i];
+ ++i;
+ }
+
+ DBUG_VOID_RETURN;
+ }
+
+ void DatabaseManager::ensure_database_directory(void) {
+ MRN_DBUG_ENTER_METHOD();
+
+ const char *path_prefix = mrn::PathMapper::default_path_prefix;
+ if (!path_prefix)
+ DBUG_VOID_RETURN;
+
+ const char *last_path_separator;
+ last_path_separator = strrchr(path_prefix, FN_LIBCHAR);
+#ifdef FN_LIBCHAR2
+ if (!last_path_separator)
+ last_path_separator = strrchr(path_prefix, FN_LIBCHAR2);
+#endif
+ if (!last_path_separator)
+ DBUG_VOID_RETURN;
+ if (path_prefix == last_path_separator)
+ DBUG_VOID_RETURN;
+
+ char database_directory[MRN_MAX_PATH_SIZE];
+ size_t database_directory_length = last_path_separator - path_prefix;
+ strncpy(database_directory, path_prefix, database_directory_length);
+ database_directory[database_directory_length] = '\0';
+ mkdir_p(database_directory);
+
+ DBUG_VOID_RETURN;
+ }
+
+ int DatabaseManager::ensure_normalizers_registered(grn_obj *db) {
+ MRN_DBUG_ENTER_METHOD();
+
+ int error = 0;
+#ifdef WITH_GROONGA_NORMALIZER_MYSQL
+ {
+# ifdef MRN_GROONGA_NORMALIZER_MYSQL_EMBEDDED
+ GRN_PLUGIN_IMPL_NAME_TAGGED(init, normalizers_mysql)(ctx_);
+ GRN_PLUGIN_IMPL_NAME_TAGGED(register, normalizers_mysql)(ctx_);
+# else
+ grn_obj *mysql_normalizer;
+ mysql_normalizer = grn_ctx_get(ctx_, "NormalizerMySQLGeneralCI", -1);
+ if (mysql_normalizer) {
+ grn_obj_unlink(ctx_, mysql_normalizer);
+ } else {
+ grn_plugin_register(ctx_, GROONGA_NORMALIZER_MYSQL_PLUGIN_NAME);
+ }
+# endif
+ }
+#endif
+
+ DBUG_RETURN(error);
+ }
+}