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