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