MythTV  0.26-pre
mythdbcon.cpp
Go to the documentation of this file.
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends