MythTV  master
mythdbcon.cpp
Go to the documentation of this file.
1 #include <unistd.h>
2 
3 // ANSI C
4 #include <cstdlib>
5 
6 // Qt
7 #include <QCoreApplication>
8 #include <QElapsedTimer>
9 #include <QSemaphore>
10 #include <QSqlDriver>
11 #include <QSqlError>
12 #include <QSqlField>
13 #include <QSqlRecord>
14 #include <QVector>
15 #include <utility>
16 
17 // MythTV
18 #include "compat.h"
19 #include "mythdbcon.h"
20 #include "mythdb.h"
21 #include "mythcorecontext.h"
22 #include "mythlogging.h"
23 #include "mythsystemlegacy.h"
24 #include "exitcodes.h"
25 #include "mthread.h"
26 #include "mythdate.h"
27 #include "portchecker.h"
28 #include "mythmiscutil.h"
29 
30 #define DEBUG_RECONNECT 0
31 #if DEBUG_RECONNECT
32 #include <cstdlib>
33 #endif
34 
35 static const uint kPurgeTimeout = 60 * 60;
36 
37 bool TestDatabase(const QString& dbHostName,
38  const QString& dbUserName,
39  QString dbPassword,
40  QString dbName,
41  int dbPort)
42 {
43  bool ret = false;
44 
45  if (dbHostName.isEmpty() || dbUserName.isEmpty())
46  return ret;
47 
48  auto *db = new MSqlDatabase("dbtest");
49  if (!db)
50  return ret;
51 
52  DatabaseParams dbparms;
53  dbparms.m_dbName = std::move(dbName);
54  dbparms.m_dbUserName = dbUserName;
55  dbparms.m_dbPassword = std::move(dbPassword);
56  dbparms.m_dbHostName = dbHostName;
57  dbparms.m_dbPort = dbPort;
58 
59  // Just use some sane defaults for these values
60  dbparms.m_wolEnabled = false;
61  dbparms.m_wolReconnect = 1;
62  dbparms.m_wolRetry = 3;
63  dbparms.m_wolCommand = QString();
64 
65  db->SetDBParams(dbparms);
66 
67  ret = db->OpenDatabase(true);
68 
69  delete db;
70  db = nullptr;
71 
72  return ret;
73 }
74 
76  : m_name(std::move(name))
77 {
78  if (!QSqlDatabase::isDriverAvailable("QMYSQL"))
79  {
80  LOG(VB_FLUSH, LOG_CRIT, "FATAL: Unable to load the QT mysql driver, is it installed?");
81  exit(GENERIC_EXIT_DB_ERROR); // Exits before we can process the log queue
82  //return;
83  }
84 
85  m_db = QSqlDatabase::addDatabase("QMYSQL", m_name);
86  LOG(VB_DATABASE, LOG_INFO, "Database object created: " + m_name);
87 
88  if (!m_db.isValid() || m_db.isOpenError())
89  {
90  LOG(VB_FLUSH, LOG_CRIT, MythDB::DBErrorMessage(m_db.lastError()));
91  LOG(VB_FLUSH, LOG_CRIT, QString("FATAL: Unable to create database object (%1), the installed QT driver may be invalid.").arg(m_name));
92  exit(GENERIC_EXIT_DB_ERROR); // Exits before we can process the log queue
93  //return;
94  }
95  m_lastDBKick = MythDate::current().addSecs(-60);
96 }
97 
99 {
100  if (m_db.isOpen())
101  {
102  m_db.close();
103  m_db = QSqlDatabase(); // forces a destroy and must be done before
104  // removeDatabase() so that connections
105  // and queries are cleaned up correctly
106  QSqlDatabase::removeDatabase(m_name);
107  LOG(VB_DATABASE, LOG_INFO, "Database object deleted: " + m_name);
108  }
109 }
110 
112 {
113  if (m_db.isValid())
114  {
115  if (m_db.isOpen())
116  return true;
117  }
118  return false;
119 }
120 
121 bool MSqlDatabase::OpenDatabase(bool skipdb)
122 {
123  if (!m_db.isValid())
124  {
125  LOG(VB_GENERAL, LOG_ERR,
126  "MSqlDatabase::OpenDatabase(), db object is not valid!");
127  return false;
128  }
129 
130  bool connected = true;
131 
132  if (!m_db.isOpen())
133  {
134  if (!skipdb)
136  m_db.setDatabaseName(m_dbparms.m_dbName);
137  m_db.setUserName(m_dbparms.m_dbUserName);
138  m_db.setPassword(m_dbparms.m_dbPassword);
139 
140  if (m_dbparms.m_dbHostName.isEmpty()) // Bootstrapping without a database?
141  {
142  // Pretend to be connected to reduce errors
143  return true;
144  }
145 
146  // code to ensure that a link-local ip address has the scope
147  int port = 3306;
148  if (m_dbparms.m_dbPort)
149  port = m_dbparms.m_dbPort;
151  m_db.setHostName(m_dbparms.m_dbHostName);
152 
153  if (m_dbparms.m_dbPort)
154  m_db.setPort(m_dbparms.m_dbPort);
155 
156  // Prefer using the faster localhost connection if using standard
157  // ports, even if the user specified a DBHostName of 127.0.0.1. This
158  // will cause MySQL to use a Unix socket (on *nix) or shared memory (on
159  // Windows) connection.
160  if ((m_dbparms.m_dbPort == 0 || m_dbparms.m_dbPort == 3306) &&
161  m_dbparms.m_dbHostName == "127.0.0.1")
162  m_db.setHostName("localhost");
163 
164  // Default read timeout is 10 mins - set a better value 300 seconds
165  m_db.setConnectOptions(QString("MYSQL_OPT_READ_TIMEOUT=300"));
166 
167  connected = m_db.open();
168 
169  if (!connected && m_dbparms.m_wolEnabled
171  {
172  int trycount = 0;
173 
174  while (!connected && trycount++ < m_dbparms.m_wolRetry)
175  {
176  LOG(VB_GENERAL, LOG_INFO,
177  QString("Using WOL to wakeup database server (Try %1 of "
178  "%2)")
179  .arg(trycount).arg(m_dbparms.m_wolRetry));
180 
182  {
183  LOG(VB_GENERAL, LOG_ERR,
184  QString("Failed to run WOL command '%1'")
185  .arg(m_dbparms.m_wolCommand));
186  }
187 
189  connected = m_db.open();
190  }
191 
192  if (!connected)
193  {
194  LOG(VB_GENERAL, LOG_ERR,
195  "WOL failed, unable to connect to database!");
196  }
197  }
198  if (connected)
199  {
200  LOG(VB_DATABASE, LOG_INFO,
201  QString("[%1] Connected to database '%2' at host: %3")
202  .arg(m_name)
203  .arg(m_db.databaseName()).arg(m_db.hostName()));
204 
205  InitSessionVars();
206 
207  // WriteDelayed depends on SetHaveDBConnection() and SetHaveSchema()
208  // both being called with true, so order is important here.
210  if (!GetMythDB()->HaveSchema())
211  {
212  // We can't just check the count of QSqlDatabase::tables()
213  // because it returns all tables visible to the user in *all*
214  // databases (not just the current DB).
215  bool have_schema = false;
216  QString sql = "SELECT COUNT(TABLE_NAME) "
217  " FROM INFORMATION_SCHEMA.TABLES "
218  " WHERE TABLE_SCHEMA = DATABASE() "
219  " AND TABLE_TYPE = 'BASE TABLE';";
220  // We can't use MSqlQuery to determine if we have a schema,
221  // since it will open a new connection, which will try to check
222  // if we have a schema
223  QSqlQuery query = m_db.exec(sql); // don't convert to MSqlQuery
224  if (query.next())
225  have_schema = query.value(0).toInt() > 1;
226  GetMythDB()->SetHaveSchema(have_schema);
227  }
229  }
230  }
231 
232  if (!connected)
233  {
234  GetMythDB()->SetHaveDBConnection(false);
235  LOG(VB_GENERAL, LOG_ERR, QString("[%1] Unable to connect to database!").arg(m_name));
236  LOG(VB_GENERAL, LOG_ERR, MythDB::DBErrorMessage(m_db.lastError()));
237  }
238 
239  return connected;
240 }
241 
243 {
244  m_lastDBKick = MythDate::current().addSecs(-60);
245 
246  if (!m_db.isOpen())
247  m_db.open();
248 
249  return m_db.isOpen();
250 }
251 
253 {
254  m_db.close();
255  m_db.open();
256 
257  bool open = m_db.isOpen();
258  if (open)
259  {
260  LOG(VB_GENERAL, LOG_INFO, "MySQL reconnected successfully");
261  InitSessionVars();
262  }
263 
264  return open;
265 }
266 
268 {
269  // Make sure NOW() returns time in UTC...
270  m_db.exec("SET @@session.time_zone='+00:00'");
271  // Disable strict mode
272  m_db.exec("SET @@session.sql_mode=''");
273 }
274 
275 // -----------------------------------------------------------------------
276 
277 
278 
280 {
281  CloseDatabases();
282 
283  if (m_connCount != 0 || m_schedCon || m_channelCon)
284  {
285  LOG(VB_GENERAL, LOG_CRIT,
286  "MDBManager exiting with connections still open");
287  }
288 #if 0 /* some post logStop() debugging... */
289  cout<<"m_connCount: "<<m_connCount<<endl;
290  cout<<"m_schedCon: "<<m_schedCon<<endl;
291  cout<<"m_channelCon: "<<m_channelCon<<endl;
292 #endif
293 }
294 
296 {
297  PurgeIdleConnections(true);
298 
299  m_lock.lock();
300 
301  MSqlDatabase *db = nullptr;
302 
303 #if REUSE_CONNECTION
304  if (reuse)
305  {
306  db = m_inuse[QThread::currentThread()];
307  if (db != nullptr)
308  {
309  m_inuseCount[QThread::currentThread()]++;
310  m_lock.unlock();
311  return db;
312  }
313  }
314 #endif
315 
316  DBList &list = m_pool[QThread::currentThread()];
317  if (list.isEmpty())
318  {
319  db = new MSqlDatabase("DBManager" + QString::number(m_nextConnID++));
320  ++m_connCount;
321  LOG(VB_DATABASE, LOG_INFO,
322  QString("New DB connection, total: %1").arg(m_connCount));
323  }
324  else
325  {
326  db = list.back();
327  list.pop_back();
328  }
329 
330 #if REUSE_CONNECTION
331  if (reuse)
332  {
333  m_inuseCount[QThread::currentThread()]=1;
334  m_inuse[QThread::currentThread()] = db;
335  }
336 #endif
337 
338  m_lock.unlock();
339 
340  db->OpenDatabase();
341 
342  return db;
343 }
344 
346 {
347  m_lock.lock();
348 
349 #if REUSE_CONNECTION
350  if (db == m_inuse[QThread::currentThread()])
351  {
352  int cnt = --m_inuseCount[QThread::currentThread()];
353  if (cnt > 0)
354  {
355  m_lock.unlock();
356  return;
357  }
358  m_inuse[QThread::currentThread()] = nullptr;
359  }
360 #endif
361 
362  if (db)
363  {
365  m_pool[QThread::currentThread()].push_front(db);
366  }
367 
368  m_lock.unlock();
369 
370  PurgeIdleConnections(true);
371 }
372 
374 {
375  QMutexLocker locker(&m_lock);
376 
377  leaveOne = leaveOne || (gCoreContext && gCoreContext->IsUIThread());
378 
379  QDateTime now = MythDate::current();
380  DBList &list = m_pool[QThread::currentThread()];
381  DBList::iterator it = list.begin();
382 
383  uint purgedConnections = 0;
384  uint totalConnections = 0;
385  MSqlDatabase *newDb = nullptr;
386  while (it != list.end())
387  {
388  totalConnections++;
389  if ((*it)->m_lastDBKick.secsTo(now) <= (int)kPurgeTimeout)
390  {
391  ++it;
392  continue;
393  }
394 
395  // This connection has not been used in the kPurgeTimeout
396  // seconds close it.
397  MSqlDatabase *entry = *it;
398  it = list.erase(it);
399  --m_connCount;
400  purgedConnections++;
401 
402  // Qt's MySQL driver apparently keeps track of the number of
403  // open DB connections, and when it hits 0, calls
404  // my_thread_global_end(). The mysql library then assumes the
405  // application is ending and that all threads that created DB
406  // connections have already exited. This is rarely true, and
407  // may result in the mysql library pausing 5 seconds and
408  // printing a message like "Error in my_thread_global_end(): 1
409  // threads didn't exit". This workaround simply creates an
410  // extra DB connection before all pooled connections are
411  // purged so that my_thread_global_end() won't be called.
412  if (leaveOne && it == list.end() &&
413  purgedConnections > 0 &&
414  totalConnections == purgedConnections)
415  {
416  newDb = new MSqlDatabase("DBManager" +
417  QString::number(m_nextConnID++));
418  ++m_connCount;
419  LOG(VB_GENERAL, LOG_INFO,
420  QString("New DB connection, total: %1").arg(m_connCount));
421  newDb->m_lastDBKick = MythDate::current();
422  }
423 
424  LOG(VB_DATABASE, LOG_INFO, "Deleting idle DB connection...");
425  delete entry;
426  LOG(VB_DATABASE, LOG_INFO, "Done deleting idle DB connection.");
427  }
428  if (newDb)
429  list.push_front(newDb);
430 
431  if (purgedConnections)
432  {
433  LOG(VB_DATABASE, LOG_INFO,
434  QString("Purged %1 idle of %2 total DB connections.")
435  .arg(purgedConnections).arg(totalConnections));
436  }
437 }
438 
439 MSqlDatabase *MDBManager::getStaticCon(MSqlDatabase **dbcon, const QString& name)
440 {
441  if (!dbcon)
442  return nullptr;
443 
444  if (!*dbcon)
445  {
446  *dbcon = new MSqlDatabase(name);
447  LOG(VB_GENERAL, LOG_INFO, "New static DB connection" + name);
448  }
449 
450  (*dbcon)->OpenDatabase();
451 
452  if (!m_staticPool[QThread::currentThread()].contains(*dbcon))
453  m_staticPool[QThread::currentThread()].push_back(*dbcon);
454 
455  return *dbcon;
456 }
457 
459 {
460  return getStaticCon(&m_schedCon, "SchedCon");
461 }
462 
464 {
465  return getStaticCon(&m_channelCon, "ChannelCon");
466 }
467 
469 {
470  m_lock.lock();
471  DBList list = m_pool[QThread::currentThread()];
472  m_pool[QThread::currentThread()].clear();
473  m_lock.unlock();
474 
475  for (auto *conn : qAsConst(list))
476  {
477  LOG(VB_DATABASE, LOG_INFO,
478  "Closing DB connection named '" + conn->m_name + "'");
479  conn->m_db.close();
480  delete conn;
481  m_connCount--;
482  }
483 
484  m_lock.lock();
485  DBList &slist = m_staticPool[QThread::currentThread()];
486  while (!slist.isEmpty())
487  {
488  MSqlDatabase *db = slist.takeFirst();
489  LOG(VB_DATABASE, LOG_INFO,
490  "Closing DB connection named '" + db->m_name + "'");
491  db->m_db.close();
492  delete db;
493 
494  if (db == m_schedCon)
495  m_schedCon = nullptr;
496  if (db == m_channelCon)
497  m_channelCon = nullptr;
498  }
499  m_lock.unlock();
500 }
501 
502 
503 // -----------------------------------------------------------------------
504 
506 {
507  qi.db = nullptr;
508  qi.qsqldb = QSqlDatabase();
509  qi.returnConnection = true;
510 }
511 
512 
514  : QSqlQuery(QString(), qi.qsqldb)
515 {
516  m_db = qi.db;
518 
519  m_isConnected = m_db && m_db->isOpen();
520 }
521 
523 {
524  if (m_returnConnection)
525  {
526  MDBManager *dbmanager = GetMythDB()->GetDBManager();
527 
528  if (dbmanager && m_db)
529  {
530  dbmanager->pushConnection(m_db);
531  }
532  }
533 }
534 
536 {
537  bool reuse = kNormalConnection == _reuse;
539  MSqlQueryInfo qi;
540 
541  InitMSqlQueryInfo(qi);
542 
543 
544  // Bootstrapping without a database?
545  //if (db->pretendHaveDB)
546  if (db->m_db.hostName().isEmpty())
547  {
548  // Return an invalid database so that QSqlQuery does nothing.
549  // Also works around a Qt4 bug where QSqlQuery::~QSqlQuery
550  // calls QMYSQLResult::cleanup() which uses mysql_next_result()
551 
553  qi.returnConnection = false;
554  return qi;
555  }
556 
557  qi.db = db;
558  qi.qsqldb = db->db();
559 
560  db->KickDatabase();
561 
562  return qi;
563 }
564 
566 {
568  MSqlQueryInfo qi;
569 
570  InitMSqlQueryInfo(qi);
571  qi.returnConnection = false;
572 
573  if (db)
574  {
575  qi.db = db;
576  qi.qsqldb = db->db();
577 
578  db->KickDatabase();
579  }
580 
581  return qi;
582 }
583 
585 {
587  MSqlQueryInfo qi;
588 
589  InitMSqlQueryInfo(qi);
590  qi.returnConnection = false;
591 
592  if (db)
593  {
594  qi.db = db;
595  qi.qsqldb = db->db();
596 
597  db->KickDatabase();
598  }
599 
600  return qi;
601 }
602 
604 {
605  if (!m_db)
606  {
607  // Database structure's been deleted
608  return false;
609  }
610 
611  if (m_lastPreparedQuery.isEmpty())
612  {
613  LOG(VB_GENERAL, LOG_ERR,
614  "MSqlQuery::exec(void) called without a prepared query.");
615  return false;
616  }
617 
618 #if DEBUG_RECONNECT
619  if (random() < RAND_MAX / 50)
620  {
621  LOG(VB_GENERAL, LOG_INFO,
622  "MSqlQuery disconnecting DB to test reconnection logic");
623  m_db->m_db.close();
624  }
625 #endif
626 
627  // Database connection down. Try to restart it, give up if it's still
628  // down
629  if (!m_db->isOpen() && !Reconnect())
630  {
631  LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected");
632  return false;
633  }
634 
635  QElapsedTimer timer;
636  timer.start();
637 
638  bool result = QSqlQuery::exec();
639  qint64 elapsed = timer.elapsed();
640 
641  // if the query failed with "MySQL server has gone away"
642  // Close and reopen the database connection and retry the query if it
643  // connects again
644  if (!result
645  && QSqlQuery::lastError().nativeErrorCode() == "2006"
646  && Reconnect())
647  result = QSqlQuery::exec();
648 
649  if (!result)
650  {
651  QString err = MythDB::GetError("MSqlQuery", *this);
652  MSqlBindings tmp = QSqlQuery::boundValues();
653  bool has_null_strings = false;
654  // NOLINTNEXTLINE(modernize-loop-convert)
655  for (MSqlBindings::iterator it = tmp.begin(); it != tmp.end(); ++it)
656  {
657  if (it->type() != QVariant::String)
658  continue;
659  if (it->isNull() || it->toString().isNull())
660  {
661  has_null_strings = true;
662  *it = QVariant(QString(""));
663  }
664  }
665  if (has_null_strings)
666  {
667  bindValues(tmp);
668  timer.restart();
669  result = QSqlQuery::exec();
670  elapsed = timer.elapsed();
671  }
672  if (result)
673  {
674  LOG(VB_GENERAL, LOG_ERR,
675  QString("Original query failed, but resend with empty "
676  "strings in place of NULL strings worked. ") +
677  "\n" + err);
678  }
679  }
680 
681  if (VERBOSE_LEVEL_CHECK(VB_DATABASE, LOG_INFO))
682  {
683  QString str = lastQuery();
684 
685  // Database logging will cause an infinite loop here if not filtered
686  // out
687  if (!str.startsWith("INSERT INTO logging "))
688  {
689  // Sadly, neither executedQuery() nor lastQuery() display
690  // the values in bound queries against a MySQL5 database.
691  // So, replace the named placeholders with their values.
692 
693  QMapIterator<QString, QVariant> b = boundValues();
694  while (b.hasNext())
695  {
696  b.next();
697  str.replace(b.key(), '\'' + b.value().toString() + '\'');
698  }
699 
700  LOG(VB_DATABASE, LOG_INFO,
701  QString("MSqlQuery::exec(%1) %2%3%4")
702  .arg(m_db->MSqlDatabase::GetConnectionName()).arg(str)
703  .arg(QString(" <<<< Took %1ms").arg(QString::number(elapsed)))
704  .arg(isSelect() ? QString(", Returned %1 row(s)")
705  .arg(size()) : QString()));
706  }
707  }
708 
709  return result;
710 }
711 
712 bool MSqlQuery::exec(const QString &query)
713 {
714  if (!m_db)
715  {
716  // Database structure's been deleted
717  return false;
718  }
719 
720  // Database connection down. Try to restart it, give up if it's still
721  // down
722  if (!m_db->isOpen() && !Reconnect())
723  {
724  LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected");
725  return false;
726  }
727 
728  bool result = QSqlQuery::exec(query);
729 
730  // if the query failed with "MySQL server has gone away"
731  // Close and reopen the database connection and retry the query if it
732  // connects again
733  if (!result
734  && QSqlQuery::lastError().nativeErrorCode() == "2006"
735  && Reconnect())
736  result = QSqlQuery::exec(query);
737 
738  LOG(VB_DATABASE, LOG_INFO,
739  QString("MSqlQuery::exec(%1) %2%3")
740  .arg(m_db->MSqlDatabase::GetConnectionName()).arg(query)
741  .arg(isSelect() ? QString(" <<<< Returns %1 row(s)")
742  .arg(size()) : QString()));
743 
744  return result;
745 }
746 
747 bool MSqlQuery::seekDebug(const char *type, bool result,
748  int where, bool relative) const
749 {
750  if (result && VERBOSE_LEVEL_CHECK(VB_DATABASE, LOG_DEBUG))
751  {
752  QString str;
753  QSqlRecord rec = record();
754 
755  for (int i = 0; i < rec.count(); i++)
756  {
757  if (!str.isEmpty())
758  str.append(", ");
759 
760  str.append(rec.fieldName(i) + " = " +
761  value(i).toString());
762  }
763 
764  if (QString("seek")==type)
765  {
766  LOG(VB_DATABASE, LOG_DEBUG,
767  QString("MSqlQuery::seek(%1,%2,%3) Result: \"%4\"")
768  .arg(m_db->MSqlDatabase::GetConnectionName())
769  .arg(where).arg(relative)
770  .arg(str));
771  }
772  else
773  {
774  LOG(VB_DATABASE, LOG_DEBUG,
775  QString("MSqlQuery::%1(%2) Result: \"%3\"")
776  .arg(type).arg(m_db->MSqlDatabase::GetConnectionName())
777  .arg(str));
778  }
779  }
780  return result;
781 }
782 
783 bool MSqlQuery::next(void)
784 {
785  return seekDebug("next", QSqlQuery::next(), 0, false);
786 }
787 
789 {
790  return seekDebug("previous", QSqlQuery::previous(), 0, false);
791 }
792 
794 {
795  return seekDebug("first", QSqlQuery::first(), 0, false);
796 }
797 
798 bool MSqlQuery::last(void)
799 {
800  return seekDebug("last", QSqlQuery::last(), 0, false);
801 }
802 
803 bool MSqlQuery::seek(int where, bool relative)
804 {
805  return seekDebug("seek", QSqlQuery::seek(where, relative), where, relative);
806 }
807 
808 bool MSqlQuery::prepare(const QString& query)
809 {
810  if (!m_db)
811  {
812  // Database structure's been deleted
813  return false;
814  }
815 
817 
818  if (!m_db->isOpen() && !Reconnect())
819  {
820  LOG(VB_GENERAL, LOG_INFO, "MySQL server disconnected");
821  return false;
822  }
823 
824  // QT docs indicate that there are significant speed ups and a reduction
825  // in memory usage by enabling forward-only cursors
826  //
827  // Unconditionally enable this since all existing uses of the database
828  // iterate forward over the result set.
829  setForwardOnly(true);
830 
831  bool ok = QSqlQuery::prepare(query);
832 
833  // if the prepare failed with "MySQL server has gone away"
834  // Close and reopen the database connection and retry the query if it
835  // connects again
836  if (!ok
837  && QSqlQuery::lastError().nativeErrorCode() == "2006"
838  && Reconnect())
839  ok = true;
840 
841  if (!ok && !(GetMythDB()->SuppressDBMessages()))
842  {
843  LOG(VB_GENERAL, LOG_ERR,
844  QString("Error preparing query: %1").arg(query));
845  LOG(VB_GENERAL, LOG_ERR,
846  MythDB::DBErrorMessage(QSqlQuery::lastError()));
847  }
848 
849  return ok;
850 }
851 
853 {
855 
856  // popConnection() has already called OpenDatabase(),
857  // so we only have to check if it was successful:
858  bool isOpen = db->isOpen();
859 
861  return isOpen;
862 }
863 
864 void MSqlQuery::bindValue(const QString &placeholder, const QVariant &val)
865 {
866  QSqlQuery::bindValue(placeholder, val, QSql::In);
867 }
868 
869 void MSqlQuery::bindValueNoNull(const QString &placeholder, const QVariant &val)
870 {
871  if ((val.type() == QVariant::String) && val.isNull())
872  {
873  QSqlQuery::bindValue(placeholder, QString(""), QSql::In);
874  return;
875  }
876  QSqlQuery::bindValue(placeholder, val, QSql::In);
877 }
878 
879 void MSqlQuery::bindValues(const MSqlBindings &bindings)
880 {
881  MSqlBindings::const_iterator it;
882  for (it = bindings.begin(); it != bindings.end(); ++it)
883  {
884  bindValue(it.key(), it.value());
885  }
886 }
887 
889 {
890  return QSqlQuery::lastInsertId();
891 }
892 
894 {
895  if (!m_db->Reconnect())
896  return false;
897  if (!m_lastPreparedQuery.isEmpty())
898  {
899  MSqlBindings tmp = QSqlQuery::boundValues();
901  return false;
902  bindValues(tmp);
903  }
904  return true;
905 }
906 
908 {
909  MSqlBindings::Iterator it;
910  for (it = addfrom.begin(); it != addfrom.end(); ++it)
911  {
912  output.insert(it.key(), it.value());
913  }
914 }
915 
916 struct Holder {
917  explicit Holder( QString hldr = QString(), int pos = -1 )
918  : m_holderName(std::move( hldr )), m_holderPos( pos ) {}
919 
920  bool operator==( const Holder& h ) const
921  { return h.m_holderPos == m_holderPos && h.m_holderName == m_holderName; }
922  bool operator!=( const Holder& h ) const
923  { return h.m_holderPos != m_holderPos || h.m_holderName != m_holderName; }
924  QString m_holderName;
926 };
927 
928 void MSqlEscapeAsAQuery(QString &query, MSqlBindings &bindings)
929 {
930  MSqlQuery result(MSqlQuery::InitCon());
931 
932  QString q = query;
933  QRegExp rx(QString::fromLatin1("'[^']*'|:([a-zA-Z0-9_]+)"));
934 
935  QVector<Holder> holders;
936 
937  int i = 0;
938  while ((i = rx.indexIn(q, i)) != -1)
939  {
940  if (!rx.cap(1).isEmpty())
941  holders.append(Holder(rx.cap(0), i));
942  i += rx.matchedLength();
943  }
944 
945  QVariant val;
946  QString holder;
947 
948  for (i = holders.count() - 1; i >= 0; --i)
949  {
950  holder = holders[(uint)i].m_holderName;
951  val = bindings[holder];
952  QSqlField f("", val.type());
953  if (val.isNull())
954  f.clear();
955  else
956  f.setValue(val);
957 
958  query = query.replace((uint)holders[(uint)i].m_holderPos, holder.length(),
959  result.driver()->formatValue(f));
960  }
961 }
void InitSessionVars(void)
Definition: mythdbcon.cpp:267
#define GENERIC_EXIT_DB_ERROR
Database error.
Definition: exitcodes.h:17
QSqlDatabase wrapper, used by MSqlQuery. Do not use directly.
Definition: mythdbcon.h:26
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
static MSqlQueryInfo ChannelCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
Definition: mythdbcon.cpp:584
DatabaseParams GetDatabaseParams(void) const
Definition: mythdb.cpp:199
QString m_dbHostName
database server
Definition: mythdbparams.h:21
MSqlDatabase * getChannelCon(void)
Definition: mythdbcon.cpp:463
QSqlDatabase m_db
Definition: mythdbcon.h:47
bool operator==(const Holder &h) const
Definition: mythdbcon.cpp:920
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
DatabaseParams m_dbparms
Definition: mythdbcon.h:49
bool OpenDatabase(bool skipdb=false)
Definition: mythdbcon.cpp:121
bool TestDatabase(const QString &dbHostName, const QString &dbUserName, QString dbPassword, QString dbName, int dbPort)
Definition: mythdbcon.cpp:37
bool last(void)
Wrap QSqlQuery::last() so we can display the query results.
Definition: mythdbcon.cpp:798
QHash< QThread *, MSqlDatabase * > m_inuse
Definition: mythdbcon.h:77
int m_wolReconnect
seconds to wait for reconnect
Definition: mythdbparams.h:34
query bindValue(":CHANID", chanID)
void WriteDelayedSettings(void)
Definition: mythdb.cpp:914
MSqlDatabase * m_schedCon
Definition: mythdbcon.h:84
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
query prepare("SELECT chanid, channum, callsign, name " "FROM channel WHERE chanid = :CHANID;")
int size(void) const
Definition: mythdbcon.h:203
MSqlDatabase * popConnection(bool reuse)
Definition: mythdbcon.cpp:295
MSqlDatabase * getStaticCon(MSqlDatabase **dbcon, const QString &name)
Definition: mythdbcon.cpp:439
MSqlDatabase(QString name)
Definition: mythdbcon.cpp:75
bool MythWakeup(const QString &wakeUpCommand, uint flags, uint timeout)
static const uint kPurgeTimeout
Definition: mythdbcon.cpp:35
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QSqlDatabase db(void) const
Definition: mythdbcon.h:41
int m_holderPos
Definition: mythdbcon.cpp:925
static guint32 * tmp
Definition: goom_core.cpp:30
unsigned sleep(unsigned int x)
Definition: compat.h:159
Holder(QString hldr=QString(), int pos=-1)
Definition: mythdbcon.cpp:917
static bool resolveLinkLocal(QString &host, int port, int timeLimit=30000)
Convenience method to resolve link-local address.
QVariant value(int i) const
Definition: mythdbcon.h:198
QMutex m_lock
Definition: mythdbcon.h:73
int m_wolRetry
times to retry to reconnect
Definition: mythdbparams.h:35
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
Definition: mythdbcon.h:98
QString m_name
Definition: mythdbcon.h:46
bool Reconnect(void)
Reconnects server and re-prepares and re-binds the last prepared query.
Definition: mythdbcon.cpp:893
~MSqlDatabase(void)
Definition: mythdbcon.cpp:98
void MSqlEscapeAsAQuery(QString &query, MSqlBindings &bindings)
Given a partial query string and a bindings object, escape the string.
Definition: mythdbcon.cpp:928
MSqlQuery query(MSqlQuery::InitCon())
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:888
bool m_returnConnection
Definition: mythdbcon.h:240
QString m_lastPreparedQuery
Definition: mythdbcon.h:241
~MSqlQuery()
Returns connection to pool.
Definition: mythdbcon.cpp:522
static void InitMSqlQueryInfo(MSqlQueryInfo &qi)
Definition: mythdbcon.cpp:505
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
bool operator!=(const Holder &h) const
Definition: mythdbcon.cpp:922
QSqlRecord record(void) const
Definition: mythdbcon.h:205
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
void SetHaveDBConnection(bool connected)
Set a flag indicating we have successfully connected to the database.
Definition: mythdb.cpp:932
void pushConnection(MSqlDatabase *db)
Definition: mythdbcon.cpp:345
QString m_dbName
database name
Definition: mythdbparams.h:26
QString m_dbUserName
DB user name.
Definition: mythdbparams.h:24
QList< MSqlDatabase * > DBList
Definition: mythdbcon.h:74
QString m_dbPassword
DB password.
Definition: mythdbparams.h:25
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
Definition: mythdbcon.cpp:852
static MSqlQueryInfo SchedCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
Definition: mythdbcon.cpp:565
unsigned int uint
Definition: compat.h:140
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
bool Reconnect(void)
Definition: mythdbcon.cpp:252
QString m_holderName
Definition: mythdbcon.cpp:924
const char * m_name
Definition: ParseText.cpp:329
bool previous(void)
Wrap QSqlQuery::previous() so we can display the query results.
Definition: mythdbcon.cpp:788
MSqlDatabase * db
Definition: mythdbcon.h:92
bool seekDebug(const char *type, bool result, int where, bool relative) const
Definition: mythdbcon.cpp:747
int m_dbPort
database port
Definition: mythdbparams.h:23
QMap< QString, QVariant > boundValues(void) const
Definition: mythdbcon.h:200
int m_connCount
Definition: mythdbcon.h:82
int m_nextConnID
Definition: mythdbcon.h:81
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:793
MSqlQuery(const MSqlQueryInfo &qi)
Get DB connection from pool.
Definition: mythdbcon.cpp:513
bool m_wolEnabled
true if wake-on-lan params are used
Definition: mythdbparams.h:33
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
MSqlDatabase * getSchedCon(void)
Definition: mythdbcon.cpp:458
Structure containing the basic Database parameters.
Definition: mythdbparams.h:9
DB connection pool, used by MSqlQuery. Do not use directly.
Definition: mythdbcon.h:53
void SetHaveSchema(bool schema)
Set a flag indicating that we have discovered tables and that this therefore not a new empty database...
Definition: mythdb.cpp:941
PictureAttribute next(PictureAttributeSupported Supported, PictureAttribute Attribute)
~MDBManager(void)
Definition: mythdbcon.cpp:279
bool returnConnection
Definition: mythdbcon.h:94
void bindValueNoNull(const QString &placeholder, const QVariant &val)
Add a single binding, taking care not to set a NULL value.
Definition: mythdbcon.cpp:869
MSqlDatabase * m_channelCon
Definition: mythdbcon.h:85
bool KickDatabase(void)
Definition: mythdbcon.cpp:242
bool seek(int where, bool relative=false)
Wrap QSqlQuery::seek(int,bool)
Definition: mythdbcon.cpp:803
void bindValues(const MSqlBindings &bindings)
Add all the bindings in the passed in bindings.
Definition: mythdbcon.cpp:879
MDBManager * GetDBManager(void)
Definition: mythdb.cpp:115
QDateTime m_lastDBKick
Definition: mythdbcon.h:48
QHash< QThread *, int > m_inuseCount
Definition: mythdbcon.h:78
MSqlDatabase * m_db
Definition: mythdbcon.h:238
bool IsWOLAllowed() const
void MSqlAddMoreBindings(MSqlBindings &output, MSqlBindings &addfrom)
Add the entries in addfrom to the map in output.
Definition: mythdbcon.cpp:907
const QSqlDriver * driver(void) const
Definition: mythdbcon.h:209
QString m_wolCommand
command to use for wake-on-lan
Definition: mythdbparams.h:36
static QString DBErrorMessage(const QSqlError &err)
Definition: mythdb.cpp:184
QHash< QThread *, DBList > m_staticPool
Definition: mythdbcon.h:86
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
static long int random(void)
Definition: compat.h:149
MythDB * GetMythDB(void)
Definition: mythdb.cpp:46
QHash< QThread *, DBList > m_pool
Definition: mythdbcon.h:75
bool isOpen(void)
Definition: mythdbcon.cpp:111
static QString GetError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:163
void CloseDatabases(void)
Definition: mythdbcon.cpp:468
bool m_isConnected
Definition: mythdbcon.h:239
QSqlDatabase qsqldb
Definition: mythdbcon.h:93
void PurgeIdleConnections(bool leaveOne=false)
Definition: mythdbcon.cpp:373
MSqlDatabase Info, used by MSqlQuery. Do not use directly.
Definition: mythdbcon.h:90
#define output
void setForwardOnly(bool f)
Definition: mythdbcon.h:207
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23