|
MythTV
0.26-pre
|
00001 #include <unistd.h> 00002 00003 // ANSI C 00004 #include <cstdlib> 00005 00006 // Qt 00007 #include <QVector> 00008 #include <QSqlDriver> 00009 #include <QSemaphore> 00010 #include <QSqlError> 00011 #include <QSqlField> 00012 #include <QSqlRecord> 00013 00014 // MythTV 00015 #include "compat.h" 00016 #include "mythdbcon.h" 00017 #include "mythdb.h" 00018 #include "mythcorecontext.h" 00019 #include "mythlogging.h" 00020 #include "mythsystem.h" 00021 #include "exitcodes.h" 00022 #include "mthread.h" 00023 00024 #define DEBUG_RECONNECT 0 00025 #if DEBUG_RECONNECT 00026 #include <stdlib.h> 00027 #endif 00028 00029 static const uint kPurgeTimeout = 60 * 60; 00030 00031 bool TestDatabase(QString dbHostName, 00032 QString dbUserName, 00033 QString dbPassword, 00034 QString dbName, 00035 int dbPort) 00036 { 00037 bool ret = false; 00038 00039 if (dbHostName.isEmpty() || dbUserName.isEmpty()) 00040 return ret; 00041 00042 MSqlDatabase *db = new MSqlDatabase("dbtest"); 00043 00044 if (!db) 00045 return ret; 00046 00047 DatabaseParams dbparms; 00048 dbparms.dbName = dbName; 00049 dbparms.dbUserName = dbUserName; 00050 dbparms.dbPassword = dbPassword; 00051 dbparms.dbHostName = dbHostName; 00052 dbparms.dbPort = dbPort; 00053 00054 // Just use some sane defaults for these values 00055 dbparms.wolEnabled = false; 00056 dbparms.wolReconnect = 1; 00057 dbparms.wolRetry = 3; 00058 dbparms.wolCommand = QString(); 00059 00060 db->SetDBParams(dbparms); 00061 00062 ret = db->OpenDatabase(true); 00063 00064 delete db; 00065 db = NULL; 00066 00067 return ret; 00068 } 00069 00070 MSqlDatabase::MSqlDatabase(const QString &name) 00071 { 00072 m_name = name; 00073 m_name.detach(); 00074 m_db = QSqlDatabase::addDatabase("QMYSQL", m_name); 00075 LOG(VB_DATABASE, LOG_INFO, "Database connection created: " + m_name); 00076 00077 if (!m_db.isValid()) 00078 { 00079 LOG(VB_GENERAL, LOG_ERR, "Unable to init db connection."); 00080 return; 00081 } 00082 m_lastDBKick = QDateTime::currentDateTime().addSecs(-60); 00083 } 00084 00085 MSqlDatabase::~MSqlDatabase() 00086 { 00087 if (m_db.isOpen()) 00088 { 00089 m_db.close(); 00090 m_db = QSqlDatabase(); // forces a destroy and must be done before 00091 // removeDatabase() so that connections 00092 // and queries are cleaned up correctly 00093 QSqlDatabase::removeDatabase(m_name); 00094 LOG(VB_DATABASE, LOG_INFO, "Database connection deleted: " + m_name); 00095 } 00096 } 00097 00098 bool MSqlDatabase::isOpen() 00099 { 00100 if (m_db.isValid()) 00101 { 00102 if (m_db.isOpen()) 00103 return true; 00104 } 00105 return false; 00106 } 00107 00108 bool MSqlDatabase::OpenDatabase(bool skipdb) 00109 { 00110 if (!m_db.isValid()) 00111 { 00112 LOG(VB_GENERAL, LOG_ERR, 00113 "MSqlDatabase::OpenDatabase(), db object is not valid!"); 00114 return false; 00115 } 00116 00117 bool connected = true; 00118 00119 if (!m_db.isOpen()) 00120 { 00121 if (!skipdb) 00122 m_dbparms = GetMythDB()->GetDatabaseParams(); 00123 m_db.setDatabaseName(m_dbparms.dbName); 00124 m_db.setUserName(m_dbparms.dbUserName); 00125 m_db.setPassword(m_dbparms.dbPassword); 00126 m_db.setHostName(m_dbparms.dbHostName); 00127 00128 if (m_dbparms.dbHostName.isEmpty()) // Bootstrapping without a database? 00129 { 00130 connected = true; // Pretend to be connected 00131 return true; // to reduce errors 00132 } 00133 00134 if (m_dbparms.dbPort) 00135 m_db.setPort(m_dbparms.dbPort); 00136 00137 // Prefer using the faster localhost connection if using standard 00138 // ports, even if the user specified a DBHostName of 127.0.0.1. This 00139 // will cause MySQL to use a Unix socket (on *nix) or shared memory (on 00140 // Windows) connection. 00141 if ((m_dbparms.dbPort == 0 || m_dbparms.dbPort == 3306) && 00142 m_dbparms.dbHostName == "127.0.0.1") 00143 m_db.setHostName("localhost"); 00144 00145 connected = m_db.open(); 00146 00147 if (!connected && m_dbparms.wolEnabled) 00148 { 00149 int trycount = 0; 00150 00151 while (!connected && trycount++ < m_dbparms.wolRetry) 00152 { 00153 LOG(VB_GENERAL, LOG_INFO, 00154 QString("Using WOL to wakeup database server (Try %1 of " 00155 "%2)") 00156 .arg(trycount).arg(m_dbparms.wolRetry)); 00157 00158 if (myth_system(m_dbparms.wolCommand) != GENERIC_EXIT_OK) 00159 { 00160 LOG(VB_GENERAL, LOG_ERR, 00161 QString("Failed to run WOL command '%1'") 00162 .arg(m_dbparms.wolCommand)); 00163 } 00164 00165 sleep(m_dbparms.wolReconnect); 00166 connected = m_db.open(); 00167 } 00168 00169 if (!connected) 00170 { 00171 LOG(VB_GENERAL, LOG_ERR, 00172 "WOL failed, unable to connect to database!"); 00173 } 00174 } 00175 if (connected) 00176 { 00177 LOG(VB_DATABASE, LOG_INFO, 00178 QString("Connected to database '%1' at host: %2") 00179 .arg(m_db.databaseName()).arg(m_db.hostName())); 00180 00181 // WriteDelayed depends on SetHaveDBConnection() and SetHaveSchema() 00182 // both being called with true, so order is important here. 00183 GetMythDB()->SetHaveDBConnection(true); 00184 if (!GetMythDB()->HaveSchema()) 00185 { 00186 // We can't just check the count of QSqlDatabase::tables() 00187 // because it returns all tables visible to the user in *all* 00188 // databases (not just the current DB). 00189 bool have_schema = false; 00190 QString sql = "SELECT COUNT( " 00191 " INFORMATION_SCHEMA.TABLES.TABLE_NAME " 00192 " ) " 00193 " FROM INFORMATION_SCHEMA.TABLES " 00194 " WHERE INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA " 00195 " = DATABASE() " 00196 " AND INFORMATION_SCHEMA.TABLES.TABLE_TYPE = " 00197 " 'BASE TABLE';"; 00198 // We can't use MSqlQuery to determine if we have a schema, 00199 // since it will open a new connection, which will try to check 00200 // if we have a schema 00201 QSqlQuery query = m_db.exec(sql); // don't convert to MSqlQuery 00202 if (query.next()) 00203 have_schema = query.value(0).toInt() > 1; 00204 GetMythDB()->SetHaveSchema(have_schema); 00205 } 00206 GetMythDB()->WriteDelayedSettings(); 00207 } 00208 } 00209 00210 if (!connected) 00211 { 00212 GetMythDB()->SetHaveDBConnection(false); 00213 LOG(VB_GENERAL, LOG_ERR, "Unable to connect to database!"); 00214 LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(m_db.lastError())); 00215 } 00216 00217 return connected; 00218 } 00219 00220 bool MSqlDatabase::KickDatabase(void) 00221 { 00222 m_lastDBKick = QDateTime::currentDateTime().addSecs(-60); 00223 00224 if (!m_db.isOpen()) 00225 m_db.open(); 00226 00227 return m_db.isOpen(); 00228 } 00229 00230 bool MSqlDatabase::Reconnect() 00231 { 00232 m_db.close(); 00233 m_db.open(); 00234 00235 bool open = m_db.isOpen(); 00236 if (open) 00237 LOG(VB_GENERAL, LOG_INFO, "MySQL reconnected successfully"); 00238 00239 return open; 00240 } 00241 00242 // ----------------------------------------------------------------------- 00243 00244 00245 00246 MDBManager::MDBManager() 00247 { 00248 m_nextConnID = 0; 00249 m_connCount = 0; 00250 00251 m_schedCon = NULL; 00252 m_DDCon = NULL; 00253 } 00254 00255 MDBManager::~MDBManager() 00256 { 00257 CloseDatabases(); 00258 00259 if (m_connCount != 0 || m_schedCon || m_DDCon) 00260 { 00261 LOG(VB_GENERAL, LOG_CRIT, 00262 "MDBManager exiting with connections still open"); 00263 } 00264 #if 0 /* some post logStop() debugging... */ 00265 cout<<"m_connCount: "<<m_connCount<<endl; 00266 cout<<"m_schedCon: "<<m_schedCon<<endl; 00267 cout<<"m_DDCon: "<<m_DDCon<<endl; 00268 #endif 00269 } 00270 00271 MSqlDatabase *MDBManager::popConnection(bool reuse) 00272 { 00273 PurgeIdleConnections(true); 00274 00275 m_lock.lock(); 00276 00277 MSqlDatabase *db; 00278 00279 #if REUSE_CONNECTION 00280 if (reuse) 00281 { 00282 db = m_inuse[QThread::currentThread()]; 00283 if (db != NULL) 00284 { 00285 m_inuse_count[QThread::currentThread()]++; 00286 m_lock.unlock(); 00287 return db; 00288 } 00289 } 00290 #endif 00291 00292 DBList &list = m_pool[QThread::currentThread()]; 00293 if (list.isEmpty()) 00294 { 00295 db = new MSqlDatabase("DBManager" + QString::number(m_nextConnID++)); 00296 ++m_connCount; 00297 LOG(VB_DATABASE, LOG_INFO, 00298 QString("New DB connection, total: %1").arg(m_connCount)); 00299 } 00300 else 00301 { 00302 db = list.back(); 00303 list.pop_back(); 00304 } 00305 00306 #if REUSE_CONNECTION 00307 if (reuse) 00308 { 00309 m_inuse_count[QThread::currentThread()]=1; 00310 m_inuse[QThread::currentThread()] = db; 00311 } 00312 #endif 00313 00314 m_lock.unlock(); 00315 00316 db->OpenDatabase(); 00317 00318 return db; 00319 } 00320 00321 void MDBManager::pushConnection(MSqlDatabase *db) 00322 { 00323 m_lock.lock(); 00324 00325 #if REUSE_CONNECTION 00326 if (db == m_inuse[QThread::currentThread()]) 00327 { 00328 int cnt = --m_inuse_count[QThread::currentThread()]; 00329 if (cnt > 0) 00330 { 00331 m_lock.unlock(); 00332 return; 00333 } 00334 m_inuse[QThread::currentThread()] = NULL; 00335 } 00336 #endif 00337 00338 if (db) 00339 { 00340 db->m_lastDBKick = QDateTime::currentDateTime(); 00341 m_pool[QThread::currentThread()].push_front(db); 00342 } 00343 00344 m_lock.unlock(); 00345 00346 PurgeIdleConnections(true); 00347 } 00348 00349 void MDBManager::PurgeIdleConnections(bool leaveOne) 00350 { 00351 QMutexLocker locker(&m_lock); 00352 00353 leaveOne = leaveOne || (gCoreContext && gCoreContext->IsUIThread()); 00354 00355 QDateTime now = QDateTime::currentDateTime(); 00356 DBList &list = m_pool[QThread::currentThread()]; 00357 DBList::iterator it = list.begin(); 00358 00359 uint purgedConnections = 0, totalConnections = 0; 00360 MSqlDatabase *newDb = NULL; 00361 while (it != list.end()) 00362 { 00363 totalConnections++; 00364 if ((*it)->m_lastDBKick.secsTo(now) <= (int)kPurgeTimeout) 00365 { 00366 ++it; 00367 continue; 00368 } 00369 00370 // This connection has not been used in the kPurgeTimeout 00371 // seconds close it. 00372 MSqlDatabase *entry = *it; 00373 it = list.erase(it); 00374 --m_connCount; 00375 purgedConnections++; 00376 00377 // Qt's MySQL driver apparently keeps track of the number of 00378 // open DB connections, and when it hits 0, calls 00379 // my_thread_global_end(). The mysql library then assumes the 00380 // application is ending and that all threads that created DB 00381 // connections have already exited. This is rarely true, and 00382 // may result in the mysql library pausing 5 seconds and 00383 // printing a message like "Error in my_thread_global_end(): 1 00384 // threads didn't exit". This workaround simply creates an 00385 // extra DB connection before all pooled connections are 00386 // purged so that my_thread_global_end() won't be called. 00387 if (leaveOne && it == list.end() && 00388 purgedConnections > 0 && 00389 totalConnections == purgedConnections) 00390 { 00391 newDb = new MSqlDatabase("DBManager" + 00392 QString::number(m_nextConnID++)); 00393 ++m_connCount; 00394 LOG(VB_GENERAL, LOG_INFO, 00395 QString("New DB connection, total: %1").arg(m_connCount)); 00396 newDb->m_lastDBKick = QDateTime::currentDateTime(); 00397 } 00398 00399 LOG(VB_DATABASE, LOG_INFO, "Deleting idle DB connection..."); 00400 delete entry; 00401 LOG(VB_DATABASE, LOG_INFO, "Done deleting idle DB connection."); 00402 } 00403 if (newDb) 00404 list.push_front(newDb); 00405 00406 if (purgedConnections) 00407 { 00408 LOG(VB_DATABASE, LOG_INFO, 00409 QString("Purged %1 idle of %2 total DB connections.") 00410 .arg(purgedConnections).arg(totalConnections)); 00411 } 00412 } 00413 00414 MSqlDatabase *MDBManager::getStaticCon(MSqlDatabase **dbcon, QString name) 00415 { 00416 if (!dbcon) 00417 return NULL; 00418 00419 if (!*dbcon) 00420 { 00421 *dbcon = new MSqlDatabase(name); 00422 LOG(VB_GENERAL, LOG_INFO, "New static DB connection" + name); 00423 } 00424 00425 (*dbcon)->OpenDatabase(); 00426 00427 if (!m_static_pool[QThread::currentThread()].contains(*dbcon)) 00428 m_static_pool[QThread::currentThread()].push_back(*dbcon); 00429 00430 return *dbcon; 00431 } 00432 00433 MSqlDatabase *MDBManager::getSchedCon() 00434 { 00435 return getStaticCon(&m_schedCon, "SchedCon"); 00436 } 00437 00438 MSqlDatabase *MDBManager::getDDCon() 00439 { 00440 return getStaticCon(&m_DDCon, "DataDirectCon"); 00441 } 00442 00443 void MDBManager::CloseDatabases() 00444 { 00445 m_lock.lock(); 00446 DBList list = m_pool[QThread::currentThread()]; 00447 m_pool[QThread::currentThread()].clear(); 00448 m_lock.unlock(); 00449 00450 for (DBList::iterator it = list.begin(); it != list.end(); ++it) 00451 { 00452 LOG(VB_DATABASE, LOG_INFO, 00453 "Closing DB connection named '" + (*it)->m_name + "'"); 00454 (*it)->m_db.close(); 00455 delete (*it); 00456 m_connCount--; 00457 } 00458 00459 m_lock.lock(); 00460 DBList &slist = m_static_pool[QThread::currentThread()]; 00461 while (!slist.isEmpty()) 00462 { 00463 MSqlDatabase *db = slist.takeFirst(); 00464 LOG(VB_DATABASE, LOG_INFO, 00465 "Closing DB connection named '" + db->m_name + "'"); 00466 db->m_db.close(); 00467 delete db; 00468 00469 if (db == m_schedCon) 00470 m_schedCon = NULL; 00471 if (db == m_DDCon) 00472 m_DDCon = NULL; 00473 } 00474 m_lock.unlock(); 00475 } 00476 00477 00478 // ----------------------------------------------------------------------- 00479 00480 static void InitMSqlQueryInfo(MSqlQueryInfo &qi) 00481 { 00482 qi.db = NULL; 00483 qi.qsqldb = QSqlDatabase(); 00484 qi.returnConnection = true; 00485 } 00486 00487 00488 MSqlQuery::MSqlQuery(const MSqlQueryInfo &qi) 00489 : QSqlQuery(QString::null, qi.qsqldb) 00490 { 00491 m_isConnected = false; 00492 m_db = qi.db; 00493 m_returnConnection = qi.returnConnection; 00494 00495 m_isConnected = m_db && m_db->isOpen(); 00496 00497 #ifdef DEBUG_QT4_PORT 00498 m_testbindings = QRegExp("(:\\w+)\\W.*\\1\\b"); 00499 #endif 00500 } 00501 00502 MSqlQuery::~MSqlQuery() 00503 { 00504 if (m_returnConnection) 00505 { 00506 MDBManager *dbmanager = GetMythDB()->GetDBManager(); 00507 00508 if (dbmanager && m_db) 00509 { 00510 dbmanager->pushConnection(m_db); 00511 } 00512 } 00513 } 00514 00515 MSqlQueryInfo MSqlQuery::InitCon(ConnectionReuse _reuse) 00516 { 00517 bool reuse = kNormalConnection == _reuse; 00518 MSqlDatabase *db = GetMythDB()->GetDBManager()->popConnection(reuse); 00519 MSqlQueryInfo qi; 00520 00521 InitMSqlQueryInfo(qi); 00522 00523 00524 // Bootstrapping without a database? 00525 //if (db->pretendHaveDB) 00526 if (db->m_db.hostName().isEmpty()) 00527 { 00528 // Return an invalid database so that QSqlQuery does nothing. 00529 // Also works around a Qt4 bug where QSqlQuery::~QSqlQuery 00530 // calls QMYSQLResult::cleanup() which uses mysql_next_result() 00531 00532 GetMythDB()->GetDBManager()->pushConnection(db); 00533 qi.returnConnection = false; 00534 return qi; 00535 } 00536 00537 qi.db = db; 00538 qi.qsqldb = db->db(); 00539 00540 db->KickDatabase(); 00541 00542 return qi; 00543 } 00544 00545 MSqlQueryInfo MSqlQuery::SchedCon() 00546 { 00547 MSqlDatabase *db = GetMythDB()->GetDBManager()->getSchedCon(); 00548 MSqlQueryInfo qi; 00549 00550 InitMSqlQueryInfo(qi); 00551 qi.returnConnection = false; 00552 00553 if (db) 00554 { 00555 qi.db = db; 00556 qi.qsqldb = db->db(); 00557 00558 db->KickDatabase(); 00559 } 00560 00561 return qi; 00562 } 00563 00564 MSqlQueryInfo MSqlQuery::DDCon() 00565 { 00566 MSqlDatabase *db = GetMythDB()->GetDBManager()->getDDCon(); 00567 MSqlQueryInfo qi; 00568 00569 InitMSqlQueryInfo(qi); 00570 qi.returnConnection = false; 00571 00572 if (db) 00573 { 00574 qi.db = db; 00575 qi.qsqldb = db->db(); 00576 00577 db->KickDatabase(); 00578 } 00579 00580 return qi; 00581 } 00582 00583 bool MSqlQuery::exec() 00584 { 00585 if (!m_db) 00586 { 00587 // Database structure's been deleted 00588 return false; 00589 } 00590 00591 if (m_last_prepared_query.isEmpty()) 00592 { 00593 LOG(VB_GENERAL, LOG_ERR, 00594 "MSqlQuery::exec(void) called without a prepared query."); 00595 return false; 00596 } 00597 00598 #if DEBUG_RECONNECT 00599 if (random() < RAND_MAX / 50) 00600 { 00601 LOG(VB_GENERAL, LOG_INFO, 00602 "MSqlQuery disconnecting DB to test reconnection logic"); 00603 m_db->m_db.close(); 00604 } 00605 #endif 00606 00607 // Database connection down. Try to restart it, give up if it's still 00608 // down 00609 if (!m_db->isOpen() && !Reconnect()) 00610 { 00611 LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected"); 00612 return false; 00613 } 00614 00615 bool result = QSqlQuery::exec(); 00616 00617 // if the query failed with "MySQL server has gone away" 00618 // Close and reopen the database connection and retry the query if it 00619 // connects again 00620 if (!result && QSqlQuery::lastError().number() == 2006 && Reconnect()) 00621 result = QSqlQuery::exec(); 00622 00623 if (!result) 00624 { 00625 QString err = MythDB::GetError("MSqlQuery", *this); 00626 MSqlBindings tmp = QSqlQuery::boundValues(); 00627 bool has_null_strings = false; 00628 for (MSqlBindings::iterator it = tmp.begin(); it != tmp.end(); ++it) 00629 { 00630 if (it->type() != QVariant::String) 00631 continue; 00632 if (it->isNull() || it->toString().isNull()) 00633 { 00634 has_null_strings = true; 00635 *it = QVariant(QString("")); 00636 } 00637 } 00638 if (has_null_strings) 00639 { 00640 bindValues(tmp); 00641 result = QSqlQuery::exec(); 00642 } 00643 if (result) 00644 { 00645 LOG(VB_GENERAL, LOG_ERR, 00646 QString("Original query failed, but resend with empty " 00647 "strings in place of NULL strings worked. ") + 00648 "\n" + err); 00649 } 00650 } 00651 00652 if (VERBOSE_LEVEL_CHECK(VB_DATABASE, LOG_DEBUG)) 00653 { 00654 QString str = lastQuery(); 00655 00656 // Database logging will cause an infinite loop here if not filtered 00657 // out 00658 if (!str.startsWith("INSERT INTO logging ")) 00659 { 00660 // Sadly, neither executedQuery() nor lastQuery() display 00661 // the values in bound queries against a MySQL5 database. 00662 // So, replace the named placeholders with their values. 00663 00664 QMapIterator<QString, QVariant> b = boundValues(); 00665 while (b.hasNext()) 00666 { 00667 b.next(); 00668 str.replace(b.key(), '\'' + b.value().toString() + '\''); 00669 } 00670 00671 LOG(VB_DATABASE, LOG_DEBUG, 00672 QString("MSqlQuery::exec(%1) %2%3") 00673 .arg(m_db->MSqlDatabase::GetConnectionName()).arg(str) 00674 .arg(isSelect() ? QString(" <<<< Returns %1 row(s)") 00675 .arg(size()) : QString())); 00676 } 00677 } 00678 00679 return result; 00680 } 00681 00682 bool MSqlQuery::exec(const QString &query) 00683 { 00684 if (!m_db) 00685 { 00686 // Database structure's been deleted 00687 return false; 00688 } 00689 00690 // Database connection down. Try to restart it, give up if it's still 00691 // down 00692 if (!m_db->isOpen() && !Reconnect()) 00693 { 00694 LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected"); 00695 return false; 00696 } 00697 00698 bool result = QSqlQuery::exec(query); 00699 00700 // if the query failed with "MySQL server has gone away" 00701 // Close and reopen the database connection and retry the query if it 00702 // connects again 00703 if (!result && QSqlQuery::lastError().number() == 2006 && Reconnect()) 00704 result = QSqlQuery::exec(query); 00705 00706 LOG(VB_DATABASE, LOG_DEBUG, 00707 QString("MSqlQuery::exec(%1) %2%3") 00708 .arg(m_db->MSqlDatabase::GetConnectionName()).arg(query) 00709 .arg(isSelect() ? QString(" <<<< Returns %1 row(s)") 00710 .arg(size()) : QString())); 00711 00712 return result; 00713 } 00714 00715 bool MSqlQuery::seekDebug(const char *type, bool result, 00716 int where, bool relative) const 00717 { 00718 if (result && VERBOSE_LEVEL_CHECK(VB_DATABASE, LOG_DEBUG)) 00719 { 00720 QString str; 00721 QSqlRecord rec = record(); 00722 00723 for (long int i = 0; i < rec.count(); i++) 00724 { 00725 if (!str.isEmpty()) 00726 str.append(", "); 00727 00728 str.append(rec.fieldName(i) + " = " + 00729 value(i).toString()); 00730 } 00731 00732 if (QString("seek")==type) 00733 { 00734 LOG(VB_DATABASE, LOG_DEBUG, 00735 QString("MSqlQuery::seek(%1,%2,%3) Result: \"%4\"") 00736 .arg(m_db->MSqlDatabase::GetConnectionName()) 00737 .arg(where).arg(relative) 00738 .arg(str)); 00739 } 00740 else 00741 { 00742 LOG(VB_DATABASE, LOG_DEBUG, 00743 QString("MSqlQuery::%1(%2) Result: \"%3\"") 00744 .arg(type).arg(m_db->MSqlDatabase::GetConnectionName()) 00745 .arg(str)); 00746 } 00747 } 00748 return result; 00749 } 00750 00751 bool MSqlQuery::next(void) 00752 { 00753 return seekDebug("next", QSqlQuery::next(), 0, false); 00754 } 00755 00756 bool MSqlQuery::previous(void) 00757 { 00758 return seekDebug("previous", QSqlQuery::previous(), 0, false); 00759 } 00760 00761 bool MSqlQuery::first(void) 00762 { 00763 return seekDebug("first", QSqlQuery::first(), 0, false); 00764 } 00765 00766 bool MSqlQuery::last(void) 00767 { 00768 return seekDebug("last", QSqlQuery::last(), 0, false); 00769 } 00770 00771 bool MSqlQuery::seek(int where, bool relative) 00772 { 00773 return seekDebug("seek", QSqlQuery::seek(where, relative), where, relative); 00774 } 00775 00776 bool MSqlQuery::prepare(const QString& query) 00777 { 00778 if (!m_db) 00779 { 00780 // Database structure's been deleted 00781 return false; 00782 } 00783 00784 m_last_prepared_query = query; 00785 00786 #ifdef DEBUG_QT4_PORT 00787 if (query.contains(m_testbindings)) 00788 { 00789 LOG(VB_GENERAL, LOG_DEBUG, 00790 QString("\n\nQuery contains bind value \"%1\" twice:\n\n\n") 00791 .arg(m_testbindings.cap(1)) + query); 00792 #if 0 00793 exit(1); 00794 #endif 00795 } 00796 #endif 00797 00798 // Database connection down. Try to restart it, give up if it's still 00799 // down 00800 if (!m_db) 00801 { 00802 // Database structure has been deleted... 00803 return false; 00804 } 00805 00806 if (!m_db->isOpen() && !Reconnect()) 00807 { 00808 LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected"); 00809 return false; 00810 } 00811 00812 bool ok = QSqlQuery::prepare(query); 00813 00814 // if the prepare failed with "MySQL server has gone away" 00815 // Close and reopen the database connection and retry the query if it 00816 // connects again 00817 if (!ok && QSqlQuery::lastError().number() == 2006 && Reconnect()) 00818 ok = true; 00819 00820 if (!ok && !(GetMythDB()->SuppressDBMessages())) 00821 { 00822 LOG(VB_GENERAL, LOG_ERR, 00823 QString("Error preparing query: %1").arg(query)); 00824 LOG(VB_GENERAL, LOG_ERR, 00825 MythDB::DBErrorMessage(QSqlQuery::lastError())); 00826 } 00827 00828 return ok; 00829 } 00830 00831 bool MSqlQuery::testDBConnection() 00832 { 00833 MSqlDatabase *db = GetMythDB()->GetDBManager()->popConnection(true); 00834 00835 // popConnection() has already called OpenDatabase(), 00836 // so we only have to check if it was successful: 00837 bool isOpen = db->isOpen(); 00838 00839 GetMythDB()->GetDBManager()->pushConnection(db); 00840 return isOpen; 00841 } 00842 00843 void MSqlQuery::bindValue(const QString &placeholder, const QVariant &val) 00844 { 00845 #ifdef DEBUG_QT4_PORT 00846 // XXX - HACK BEGIN 00847 // qt4 doesn't like to bind values without occurrence in the prepared query 00848 if (!m_last_prepared_query.contains(placeholder)) 00849 { 00850 LOG(VB_GENERAL, LOG_ERR, "Trying to bind a value to placeholder " + 00851 placeholder + " without occurrence in the prepared query." 00852 " Ignoring it.\nQuery was: \"" + m_last_prepared_query + "\""); 00853 return; 00854 } 00855 // XXX - HACK END 00856 #endif 00857 00858 QSqlQuery::bindValue(placeholder, val, QSql::In); 00859 } 00860 00861 void MSqlQuery::bindValues(const MSqlBindings &bindings) 00862 { 00863 MSqlBindings::const_iterator it; 00864 for (it = bindings.begin(); it != bindings.end(); ++it) 00865 { 00866 bindValue(it.key(), it.value()); 00867 } 00868 } 00869 00870 QVariant MSqlQuery::lastInsertId() 00871 { 00872 return QSqlQuery::lastInsertId(); 00873 } 00874 00875 bool MSqlQuery::Reconnect(void) 00876 { 00877 if (!m_db->Reconnect()) 00878 return false; 00879 if (!m_last_prepared_query.isEmpty()) 00880 { 00881 MSqlBindings tmp = QSqlQuery::boundValues(); 00882 if (!QSqlQuery::prepare(m_last_prepared_query)) 00883 return false; 00884 bindValues(tmp); 00885 } 00886 return true; 00887 } 00888 00889 void MSqlAddMoreBindings(MSqlBindings &output, MSqlBindings &addfrom) 00890 { 00891 MSqlBindings::Iterator it; 00892 for (it = addfrom.begin(); it != addfrom.end(); ++it) 00893 { 00894 output.insert(it.key(), it.value()); 00895 } 00896 } 00897 00898 struct Holder { 00899 Holder( const QString& hldr = QString::null, int pos = -1 ) 00900 : holderName( hldr ), holderPos( pos ) {} 00901 00902 bool operator==( const Holder& h ) const 00903 { return h.holderPos == holderPos && h.holderName == holderName; } 00904 bool operator!=( const Holder& h ) const 00905 { return h.holderPos != holderPos || h.holderName != holderName; } 00906 QString holderName; 00907 int holderPos; 00908 }; 00909 00910 void MSqlEscapeAsAQuery(QString &query, MSqlBindings &bindings) 00911 { 00912 MSqlQuery result(MSqlQuery::InitCon()); 00913 00914 QString q = query; 00915 QRegExp rx(QString::fromLatin1("'[^']*'|:([a-zA-Z0-9_]+)")); 00916 00917 QVector<Holder> holders; 00918 00919 int i = 0; 00920 while ((i = rx.indexIn(q, i)) != -1) 00921 { 00922 if (!rx.cap(1).isEmpty()) 00923 holders.append(Holder(rx.cap(0), i)); 00924 i += rx.matchedLength(); 00925 } 00926 00927 q = query; 00928 QVariant val; 00929 QString holder; 00930 00931 for (i = (int)holders.count() - 1; i >= 0; --i) 00932 { 00933 holder = holders[(uint)i].holderName; 00934 val = bindings[holder]; 00935 QSqlField f("", val.type()); 00936 if (val.isNull()) 00937 f.clear(); 00938 else 00939 f.setValue(val); 00940 00941 query = query.replace((uint)holders[(uint)i].holderPos, holder.length(), 00942 result.driver()->formatValue(f)); 00943 } 00944 } 00945
1.7.6.1