7 #include <QCoreApplication>
8 #include <QElapsedTimer>
9 #include <QRegularExpression>
32 #define DEBUG_RECONNECT 0
42 const QString& dbUserName,
51 QMutexLocker locker(&
sMutex);
54 if (dbHostName.isEmpty() || dbUserName.isEmpty())
62 dbparms.
m_dbName = std::move(dbName);
74 db->SetDBParams(dbparms);
76 ret = db->OpenDatabase(
true);
85 : m_name(std::move(name)), m_driver(std::move(
driver))
87 if (!QSqlDatabase::isDriverAvailable(
m_driver))
89 LOG(VB_FLUSH, LOG_CRIT,
90 QString(
"FATAL: Unable to load the QT %1 driver, is it installed?")
97 LOG(VB_DATABASE, LOG_INFO,
"Database object created: " +
m_name);
99 if (!
m_db.isValid() ||
m_db.isOpenError())
102 LOG(VB_FLUSH, LOG_CRIT, QString(
"FATAL: Unable to create database object (%1), the installed QT driver may be invalid.").arg(
m_name));
114 m_db = QSqlDatabase();
117 QSqlDatabase::removeDatabase(
m_name);
118 LOG(VB_DATABASE, LOG_INFO,
"Database object deleted: " +
m_name);
138 LOG(VB_GENERAL, LOG_ERR,
139 "MSqlDatabase::OpenDatabase(), db object is not valid!");
143 bool connected =
true;
175 m_db.setHostName(
"localhost");
178 m_db.setConnectOptions(QString(
"MYSQL_OPT_READ_TIMEOUT=300"));
180 connected =
m_db.open();
189 LOG(VB_GENERAL, LOG_INFO,
190 QString(
"Using WOL to wakeup database server (Try %1 of "
196 LOG(VB_GENERAL, LOG_ERR,
197 QString(
"Failed to run WOL command '%1'")
202 connected =
m_db.open();
207 LOG(VB_GENERAL, LOG_ERR,
208 "WOL failed, unable to connect to database!");
213 LOG(VB_DATABASE, LOG_INFO,
214 QString(
"[%1] Connected to database '%2' at host: %3")
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';";
235 QSqlQuery query(sql,
m_db);
237 have_schema = query.value(0).toInt() > 1;
247 LOG(VB_GENERAL, LOG_ERR, QString(
"[%1] Unable to connect to database!").arg(
m_name));
261 return m_db.isOpen();
269 bool open =
m_db.isOpen();
272 LOG(VB_GENERAL, LOG_INFO,
"MySQL reconnected successfully");
281 QSqlQuery query(
m_db);
284 query.exec(
"SET @@session.time_zone='+00:00'");
286 query.exec(
"SET @@session.sql_mode=''");
299 LOG(VB_GENERAL, LOG_CRIT,
300 "MDBManager exiting with connections still open");
320 db =
m_inuse[QThread::currentThread()];
337 LOG(VB_DATABASE, LOG_INFO,
338 QString(
"New DB connection, total: %1").arg(
m_connCount));
350 m_inuse[QThread::currentThread()] = db;
366 if (db ==
m_inuse[QThread::currentThread()])
374 m_inuse[QThread::currentThread()] =
nullptr;
381 m_pool[QThread::currentThread()].push_front(db);
391 QMutexLocker locker(&
m_lock);
397 DBList::iterator it = list.begin();
399 uint purgedConnections = 0;
400 uint totalConnections = 0;
402 while (it != list.end())
405 if ((*it)->m_lastDBKick.secsTo(now) <=
kPurgeTimeout.count())
428 if (leaveOne && it == list.end() &&
429 purgedConnections > 0 &&
430 totalConnections == purgedConnections)
435 LOG(VB_GENERAL, LOG_INFO,
436 QString(
"New DB connection, total: %1").arg(
m_connCount));
440 LOG(VB_DATABASE, LOG_INFO,
"Deleting idle DB connection...");
442 LOG(VB_DATABASE, LOG_INFO,
"Done deleting idle DB connection.");
445 list.push_front(newDb);
447 if (purgedConnections)
449 LOG(VB_DATABASE, LOG_INFO,
450 QString(
"Purged %1 idle of %2 total DB connections.")
451 .arg(purgedConnections).arg(totalConnections));
463 LOG(VB_GENERAL, LOG_INFO,
"New static DB connection" + name);
466 (*dbcon)->OpenDatabase();
468 if (!
m_staticPool[QThread::currentThread()].contains(*dbcon))
469 m_staticPool[QThread::currentThread()].push_back(*dbcon);
488 m_pool[QThread::currentThread()].clear();
491 for (
auto *conn : std::as_const(list))
493 LOG(VB_DATABASE, LOG_INFO,
494 "Closing DB connection named '" + conn->m_name +
"'");
502 while (!slist.isEmpty())
505 LOG(VB_DATABASE, LOG_INFO,
506 "Closing DB connection named '" + db->
m_name +
"'");
524 qi.
qsqldb = QSqlDatabase();
530 : QSqlQuery(QString(), qi.qsqldb),
543 if (dbmanager &&
m_db)
561 if (db->
m_db.hostName().isEmpty())
567 GetMythDB()->GetDBManager()->pushConnection(db);
628 LOG(VB_GENERAL, LOG_ERR,
629 "MSqlQuery::exec(void) called without a prepared query.");
636 LOG(VB_GENERAL, LOG_INFO,
637 "MSqlQuery disconnecting DB to test reconnection logic");
646 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
653 bool result = QSqlQuery::exec();
654 qint64 elapsed = timer.elapsed();
657 result = QSqlQuery::exec();
662 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
665 QVariantList
tmp = QSqlQuery::boundValues();
667 bool has_null_strings =
false;
669 for (
auto it =
tmp.begin(); it !=
tmp.end(); ++it)
671 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
672 auto type =
static_cast<QMetaType::Type
>(it->type());
674 auto type = it->typeId();
676 if (
type != QMetaType::QString)
678 if (it->isNull() || it->toString().isNull())
680 has_null_strings =
true;
681 *it = QVariant(QString(
""));
684 if (has_null_strings)
686 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
689 for (
int i = 0; i < static_cast<int>(
tmp.size()); i++)
690 QSqlQuery::bindValue(i,
tmp.at(i));
693 result = QSqlQuery::exec();
694 elapsed = timer.elapsed();
698 LOG(VB_GENERAL, LOG_ERR,
699 QString(
"Original query failed, but resend with empty "
700 "strings in place of NULL strings worked. ") +
707 QString str = lastQuery();
713 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
718 str.replace(b.key(),
'\'' + b.value().toString() +
'\'');
722 static const QRegularExpression placeholders {
"(:\\w+)" };
723 auto match = placeholders.match(str);
724 while (match.hasMatch())
726 str.replace(match.capturedStart(), match.capturedLength(),
729 :
'\'' + b.takeFirst().toString() +
'\'');
730 match = placeholders.match(str);
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)),
739 ? QString(
", Returned %1 row(s)").arg(
size())
758 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
762 bool result = QSqlQuery::exec(query);
765 result = QSqlQuery::exec(query);
767 LOG(VB_DATABASE, LOG_INFO,
768 QString(
"MSqlQuery::exec(%1) %2%3")
769 .arg(
m_db->MSqlDatabase::GetConnectionName(), query,
771 ? QString(
" <<<< Returns %1 row(s)").arg(
size())
778 int where,
bool relative)
const
783 QSqlRecord rec =
record();
785 for (
int i = 0; i < rec.count(); i++)
790 str.append(rec.fieldName(i) +
" = " +
794 if (QString(
"seek")==
type)
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)
804 LOG(VB_DATABASE, LOG_DEBUG,
805 QString(
"MSqlQuery::%1(%2) Result: \"%3\"")
806 .arg(
type,
m_db->MSqlDatabase::GetConnectionName(), str));
814 return seekDebug(
"next", QSqlQuery::next(), 0,
false);
819 return seekDebug(
"previous", QSqlQuery::previous(), 0,
false);
824 return seekDebug(
"first", QSqlQuery::first(), 0,
false);
829 return seekDebug(
"last", QSqlQuery::last(), 0,
false);
834 return seekDebug(
"seek", QSqlQuery::seek(where, relative), where, relative);
849 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
860 bool ok = QSqlQuery::prepare(query);
865 if (!ok && !(
GetMythDB()->SuppressDBMessages()))
867 LOG(VB_GENERAL, LOG_ERR,
868 QString(
"Error preparing query: %1").arg(query));
869 LOG(VB_GENERAL, LOG_ERR,
882 bool isOpen = db->
isOpen();
884 GetMythDB()->GetDBManager()->pushConnection(db);
890 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
891 if (
static_cast<QMetaType::Type
>(val.type()) == QMetaType::QDateTime)
893 QSqlQuery::bindValue(placeholder,
899 QSqlQuery::bindValue(placeholder, val, QSql::In);
904 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
905 auto type =
static_cast<QMetaType::Type
>(val.type());
907 auto type = val.typeId();
909 if (
type == QMetaType::QString && val.toString().isNull())
911 QSqlQuery::bindValue(placeholder, QString(
""), QSql::In);
914 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
915 if (
type == QMetaType::QDateTime)
917 QSqlQuery::bindValue(placeholder,
923 QSqlQuery::bindValue(placeholder, val, QSql::In);
928 MSqlBindings::const_iterator it;
929 for (it = bindings.begin(); it != bindings.end(); ++it)
937 return QSqlQuery::lastInsertId();
946 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
952 QVariantList
tmp = QSqlQuery::boundValues();
955 for (
int i = 0; i < static_cast<int>(
tmp.size()); i++)
956 QSqlQuery::bindValue(i,
tmp.at(i));
969 static QStringList kLostConnectionCodes = {
"2006",
"2013",
"4031" };
971 QString error_code = QSqlQuery::lastError().nativeErrorCode();
974 LOG(VB_GENERAL, LOG_DEBUG, QString(
"SQL Native Error Code: %1")
979 return (kLostConnectionCodes.contains(error_code) &&
Reconnect());
985 MSqlBindings::Iterator it;
986 for (it = addfrom.begin(); it != addfrom.end(); ++it)
988 output.insert(it.key(), it.value());
993 explicit Holder( QString hldr = QString(),
int pos = -1 )
1008 static const QRegularExpression rx {
"('[^']+'|:\\w+)",
1009 QRegularExpression::UseUnicodePropertiesOption};
1011 QVector<Holder> holders;
1013 auto matchIter = rx.globalMatch(query);
1014 while (matchIter.hasNext())
1016 auto match = matchIter.next();
1017 if (match.capturedLength(1) > 0)
1018 holders.append(
Holder(match.captured(), match.capturedStart()));
1024 for (
int i = holders.count() - 1; i >= 0; --i)
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());
1031 QSqlField f(
"", val.metaType());
1038 query = query.replace((
uint)holders[(
uint)i].m_holderPos, holder.length(),
1039 result.
driver()->formatValue(f));