00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00031
00032 #ifdef HAVE_CONFIG_H
00033 #include "autoconfig.h"
00034 #endif
00035
00036 #ifdef HAVE_MYSQL
00037
00038
00039
00040
00041
00042
00043 #include "mysql_storage.h"
00044 #include "config_manager.h"
00045
00046 #ifdef AUTO_CREATE_DATABASE
00047 #include "mysql_create_sql.h"
00048 #include <zlib.h>
00049 #endif
00050
00051
00052 #define MYSQL_UPDATE_1_2_1 "ALTER TABLE `mt_cds_object` CHANGE `location` `location` BLOB NULL DEFAULT NULL"
00053 #define MYSQL_UPDATE_1_2_2 "ALTER TABLE `mt_cds_object` CHANGE `metadata` `metadata` BLOB NULL DEFAULT NULL"
00054 #define MYSQL_UPDATE_1_2_3 "ALTER TABLE `mt_cds_object` CHANGE `auxdata` `auxdata` BLOB NULL DEFAULT NULL"
00055 #define MYSQL_UPDATE_1_2_4 "ALTER TABLE `mt_cds_object` CHANGE `resources` `resources` BLOB NULL DEFAULT NULL"
00056 #define MYSQL_UPDATE_1_2_5 "ALTER TABLE `mt_autoscan` CHANGE `location` `location` BLOB NULL DEFAULT NULL"
00057 #define MYSQL_UPDATE_1_2_6 "UPDATE `mt_internal_setting` SET `value`='2' WHERE `key`='db_version'"
00058
00059
00060 #define MYSQL_UPDATE_2_3_1 "ALTER TABLE `mt_autoscan` CHANGE `scan_mode` `scan_mode` ENUM( 'timed', 'inotify' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL"
00061 #define MYSQL_UPDATE_2_3_2 "ALTER TABLE `mt_autoscan` DROP INDEX `mt_autoscan_obj_id`, ADD UNIQUE `mt_autoscan_obj_id` ( `obj_id` )"
00062 #define MYSQL_UPDATE_2_3_3 "ALTER TABLE `mt_autoscan` ADD `path_ids` BLOB AFTER `location`"
00063 #define MYSQL_UPDATE_2_3_4 "UPDATE `mt_internal_setting` SET `value`='3' WHERE `key`='db_version' AND `value`='2'"
00064
00065
00066 #define MYSQL_UPDATE_3_4_1 "ALTER TABLE `mt_cds_object` ADD `service_id` varchar(255) default NULL"
00067 #define MYSQL_UPDATE_3_4_2 "ALTER TABLE `mt_cds_object` ADD KEY `cds_object_service_id` (`service_id`)"
00068 #define MYSQL_UPDATE_3_4_3 "UPDATE `mt_internal_setting` SET `value`='4' WHERE `key`='db_version' AND `value`='3'"
00069
00070
00071 using namespace zmm;
00072 using namespace mxml;
00073
00074 MysqlStorage::MysqlStorage() : SQLStorage()
00075 {
00076 mysql_init_key_initialized = false;
00077 mysql_connection = false;
00078 mysqlMutex = Ref<Mutex> (new Mutex(true));
00079 table_quote_begin = '`';
00080 table_quote_end = '`';
00081 insertBuffer = nil;
00082 }
00083 MysqlStorage::~MysqlStorage()
00084 {
00085 AUTOLOCK(mysqlMutex);
00086
00087
00088 if(mysql_connection)
00089 {
00090 mysql_close(&db);
00091 mysql_connection = false;
00092 }
00093 log_debug("calling mysql_server_end...\n");
00094 mysql_server_end();
00095 log_debug("...ok\n");
00096 }
00097
00098 void MysqlStorage::checkMysqlThreadInit()
00099 {
00100 if (! mysql_connection)
00101 throw _Exception(_("mysql connection is not open or already closed"));
00102
00103 if (pthread_getspecific(mysql_init_key) == NULL)
00104 {
00105 log_debug("running mysql_thread_init(); thread_id=%d\n", pthread_self());
00106 if (mysql_thread_init()) throw _Exception(_("error while calling mysql_thread_init()"));
00107 if (pthread_setspecific(mysql_init_key, (void *) 1)) throw _Exception(_("error while calling pthread_setspecific()"));
00108 }
00109 }
00110
00111 void MysqlStorage::threadCleanup()
00112 {
00113 log_debug("thread cleanup; thread_id=%d\n", pthread_self());
00114 if (pthread_getspecific(mysql_init_key) != NULL)
00115 {
00116 mysql_thread_end();
00117 }
00118 }
00119
00120 void MysqlStorage::init()
00121 {
00122 log_debug("start\n");
00123 SQLStorage::init();
00124
00125 AUTOLOCK(mysqlMutex);
00126 int ret;
00127
00128 if (! mysql_thread_safe())
00129 {
00130 throw _Exception(_("mysql library is not thread safe!"));
00131 }
00132
00134 ret = pthread_key_create(&mysql_init_key, NULL);
00135 if (ret)
00136 {
00137 throw _Exception(_("could not create pthread_key"));
00138 }
00139 mysql_server_init(0, NULL, NULL);
00140 pthread_setspecific(mysql_init_key, (void *) 1);
00141
00142 Ref<ConfigManager> config = ConfigManager::getInstance();
00143
00144 String dbHost = config->getOption(CFG_SERVER_STORAGE_MYSQL_HOST);
00145 String dbName = config->getOption(CFG_SERVER_STORAGE_MYSQL_DATABASE);
00146 String dbUser = config->getOption(CFG_SERVER_STORAGE_MYSQL_USERNAME);
00147 int dbPort = config->getIntOption(CFG_SERVER_STORAGE_MYSQL_PORT);
00148 String dbPass = config->getOption(CFG_SERVER_STORAGE_MYSQL_PASSWORD);
00149 String dbSock = config->getOption(CFG_SERVER_STORAGE_MYSQL_SOCKET);
00150
00151 MYSQL *res_mysql;
00152
00153 res_mysql = mysql_init(&db);
00154 if(! res_mysql)
00155 {
00156 throw _Exception(_("mysql_init failed"));
00157 }
00158
00159 mysql_init_key_initialized = true;
00160
00161 mysql_options(&db, MYSQL_SET_CHARSET_NAME, "utf8");
00162
00163 #ifdef HAVE_MYSQL_OPT_RECONNECT
00164 my_bool my_bool_var = true;
00165 mysql_options(&db, MYSQL_OPT_RECONNECT, &my_bool_var);
00166 #endif
00167
00168 res_mysql = mysql_real_connect(&db,
00169 dbHost.c_str(),
00170 dbUser.c_str(),
00171 (dbPass == nil ? NULL : dbPass.c_str()),
00172 dbName.c_str(),
00173 dbPort,
00174 (dbSock == nil ? NULL : dbSock.c_str()),
00175 0
00176 );
00177 if(! res_mysql)
00178 {
00179 throw _Exception(_("The connection to the MySQL database has failed: ") + getError(&db));
00180 }
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192 mysql_connection = true;
00193
00194 String dbVersion = nil;
00195 try
00196 {
00197 dbVersion = getInternalSetting(_("db_version"));
00198 }
00199 catch (Exception)
00200 {
00201 }
00202
00203 if (dbVersion == nil)
00204 {
00205 #ifdef AUTO_CREATE_DATABASE
00206 log_info("database doesn't seem to exist. automatically creating database...\n");
00207 unsigned char buf[MS_CREATE_SQL_INFLATED_SIZE + 1];
00208 unsigned long uncompressed_size = MS_CREATE_SQL_INFLATED_SIZE;
00209 int ret = uncompress(buf, &uncompressed_size, mysql_create_sql, MS_CREATE_SQL_DEFLATED_SIZE);
00210 if (ret != Z_OK || uncompressed_size != MS_CREATE_SQL_INFLATED_SIZE)
00211 throw _Exception(_("Error while uncompressing mysql create sql. returned: ") + ret);
00212 buf[MS_CREATE_SQL_INFLATED_SIZE] = '\0';
00213
00214 char *sql_start = (char *)buf;
00215 char *sql_end = strchr(sql_start, ';');
00216 if (sql_end == NULL)
00217 {
00218 throw _Exception(_("';' not found in mysql create sql"));
00219 }
00220 do
00221 {
00222 ret = mysql_real_query(&db, sql_start, sql_end - sql_start);
00223 if (ret)
00224 {
00225 String myError = getError(&db);
00226 throw _StorageException(myError, _("Mysql: error while creating db: ") + myError);
00227 }
00228 sql_start = sql_end + 1;
00229 if (*sql_start == '\n')
00230 sql_start++;
00231
00232 sql_end = strchr(sql_start, ';');
00233 }
00234 while(sql_end != NULL);
00235 dbVersion = getInternalSetting(_("db_version"));
00236 if (dbVersion == nil)
00237 {
00238 shutdown();
00239 throw _Exception(_("error while creating database"));
00240 }
00241 log_info("database created successfully.\n");
00242 #else
00243 shutdown();
00244 throw _Exception(_("database doesn't seem to exist yet and autocreation wasn't compiled in"));
00245 #endif
00246
00247 }
00248 log_debug("db_version: %s\n", dbVersion.c_str());
00249
00250
00251 if (dbVersion == "1")
00252 {
00253 log_info("Doing an automatic database upgrade from database version 1 to version 2...\n");
00254 _exec(MYSQL_UPDATE_1_2_1);
00255 _exec(MYSQL_UPDATE_1_2_2);
00256 _exec(MYSQL_UPDATE_1_2_3);
00257 _exec(MYSQL_UPDATE_1_2_4);
00258 _exec(MYSQL_UPDATE_1_2_5);
00259 _exec(MYSQL_UPDATE_1_2_6);
00260 log_info("database upgrade successful.\n");
00261 dbVersion = _("2");
00262 }
00263
00264 if (dbVersion == "2")
00265 {
00266 log_info("Doing an automatic database upgrade from database version 2 to version 3...\n");
00267 _exec(MYSQL_UPDATE_2_3_1);
00268 _exec(MYSQL_UPDATE_2_3_2);
00269 _exec(MYSQL_UPDATE_2_3_3);
00270 _exec(MYSQL_UPDATE_2_3_4);
00271 log_info("database upgrade successful.\n");
00272 dbVersion = _("3");
00273 }
00274
00275 if (dbVersion == "3")
00276 {
00277 log_info("Doing an automatic database upgrade from database version 3 to version 4...\n");
00278 _exec(MYSQL_UPDATE_3_4_1);
00279 _exec(MYSQL_UPDATE_3_4_2);
00280 _exec(MYSQL_UPDATE_3_4_3);
00281 log_info("database upgrade successful.\n");
00282 dbVersion = _("4");
00283 }
00284
00285
00286
00287 if (! string_ok(dbVersion) || dbVersion != "4")
00288 throw _Exception(_("The database seems to be from a newer version (database version ") + dbVersion + ")!");
00289
00290 AUTOUNLOCK();
00291
00292 log_debug("end\n");
00293
00294 dbReady();
00295 }
00296
00297 String MysqlStorage::quote(String value)
00298 {
00299
00300
00301
00302
00303
00304 char *q = (char *)MALLOC(value.length() * 2 + 2);
00305 *q = '\'';
00306 long size = mysql_real_escape_string(&db, q + 1, value.c_str(), value.length());
00307 q[size + 1] = '\'';
00308 String ret(q, size + 2);
00309 FREE(q);
00310 return ret;
00311 }
00312
00313 String MysqlStorage::getError(MYSQL *db)
00314 {
00315 Ref<StringBuffer> err_buf(new StringBuffer());
00316 *err_buf << "mysql_error (" << String::from(mysql_errno(db));
00317 *err_buf << "): \"" << mysql_error(db) << "\"";
00318 log_debug("%s\n", err_buf->c_str());
00319 return err_buf->toString();
00320 }
00321
00322 Ref<SQLResult> MysqlStorage::select(const char *query, int length)
00323 {
00324 #ifdef MYSQL_SELECT_DEBUG
00325 log_debug("%s\n", query);
00326 print_backtrace();
00327 #endif
00328
00329 int res;
00330
00331 checkMysqlThreadInit();
00332 AUTOLOCK(mysqlMutex);
00333 res = mysql_real_query(&db, query, length);
00334 if (res)
00335 {
00336 String myError = getError(&db);
00337 throw _StorageException(myError, _("Mysql: mysql_real_query() failed: ") + myError + "; query: " + query);
00338 }
00339
00340 MYSQL_RES *mysql_res;
00341 mysql_res = mysql_store_result(&db);
00342 if(! mysql_res)
00343 {
00344 String myError = getError(&db);
00345 throw _StorageException(myError, _("Mysql: mysql_store_result() failed: ") + myError + "; query: " + query);
00346 }
00347 return Ref<SQLResult> (new MysqlResult(mysql_res));
00348 }
00349
00350 int MysqlStorage::exec(const char *query, int length, bool getLastInsertId)
00351 {
00352 #ifdef MYSQL_EXEC_DEBUG
00353 log_debug("%s\n", query);
00354 print_backtrace();
00355 #endif
00356
00357 int res;
00358
00359 checkMysqlThreadInit();
00360 AUTOLOCK(mysqlMutex);
00361 res = mysql_real_query(&db, query, length);
00362 if(res)
00363 {
00364 String myError = getError(&db);
00365 throw _StorageException(myError, _("Mysql: mysql_real_query() failed: ") + myError + "; query: " + query);
00366 }
00367 int insert_id=-1;
00368 if (getLastInsertId) insert_id = mysql_insert_id(&db);
00369 return insert_id;
00370
00371 }
00372
00373 void MysqlStorage::shutdownDriver()
00374 {
00375 }
00376
00377 void MysqlStorage::storeInternalSetting(String key, String value)
00378 {
00379 String quotedValue = quote(value);
00380 Ref<StringBuffer> q(new StringBuffer());
00381 *q << "INSERT INTO " << QTB << INTERNAL_SETTINGS_TABLE << QTE << " (`key`, `value`) "
00382 "VALUES (" << quote(key) << ", "<< quotedValue << ") "
00383 "ON DUPLICATE KEY UPDATE `value` = " << quotedValue;
00384 SQLStorage::exec(q);
00385 }
00386
00387 void MysqlStorage::_exec(const char *query, int length)
00388 {
00389 if (mysql_real_query(&db, query, (length > 0 ? length : strlen(query))))
00390 {
00391 String myError = getError(&db);
00392 throw _StorageException(myError, _("Mysql: error while updating db: ") + myError);
00393 }
00394 }
00395
00396 void MysqlStorage::_addToInsertBuffer(Ref<StringBuffer> query)
00397 {
00398 if (insertBuffer == nil)
00399 {
00400 insertBuffer = Ref<Array<StringBase> >(new Array<StringBase>());
00401 insertBuffer->append(_("BEGIN"));
00402 }
00403 Ref<StringBase> sb (new StringBase(query->c_str()));
00404 insertBuffer->append(sb);
00405 }
00406
00407 void MysqlStorage::_flushInsertBuffer()
00408 {
00409 if (insertBuffer == nil)
00410 return;
00411 insertBuffer->append(_("COMMIT"));
00412
00413 checkMysqlThreadInit();
00414 AUTOLOCK(mysqlMutex);
00415 for (int i=0; i < insertBuffer->size(); i++)
00416 {
00417 _exec(insertBuffer->get(i)->data, insertBuffer->get(i)->len);
00418 }
00419 AUTOUNLOCK();
00420 insertBuffer->clear();
00421 insertBuffer->append(_("BEGIN"));
00422 }
00423
00424
00425
00426
00427 MysqlResult::MysqlResult(MYSQL_RES *mysql_res) : SQLResult()
00428 {
00429 this->mysql_res = mysql_res;
00430 nullRead = false;
00431 }
00432
00433 MysqlResult::~MysqlResult()
00434 {
00435 if(mysql_res)
00436 {
00437 if (! nullRead)
00438 {
00439 MYSQL_ROW mysql_row;
00440 while((mysql_row = mysql_fetch_row(mysql_res)) != NULL);
00441 }
00442 mysql_free_result(mysql_res);
00443 mysql_res = NULL;
00444 }
00445 }
00446
00447 Ref<SQLRow> MysqlResult::nextRow()
00448 {
00449 MYSQL_ROW mysql_row;
00450 mysql_row = mysql_fetch_row(mysql_res);
00451 if(mysql_row)
00452 {
00453 return Ref<SQLRow>(new MysqlRow(mysql_row, Ref<SQLResult>(this)));
00454 }
00455 nullRead = true;
00456 mysql_free_result(mysql_res);
00457 mysql_res = NULL;
00458 return nil;
00459 }
00460
00461
00462
00463 MysqlRow::MysqlRow(MYSQL_ROW mysql_row, Ref<SQLResult> sqlResult) : SQLRow(sqlResult)
00464 {
00465 this->mysql_row = mysql_row;
00466 }
00467
00468 #endif // HAVE_MYSQL