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