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
43extern "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
48namespace 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