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),
532 m_isConnected(m_db && m_db->isOpen()),
533 m_returnConnection(qi.returnConnection)
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));
Structure containing the basic Database parameters.
QString m_dbName
database name
QString m_dbPassword
DB password.
std::chrono::seconds m_wolReconnect
seconds to wait for reconnect
QString m_dbUserName
DB user name.
QString m_dbType
database type (MySQL, Postgres, etc.)
QString m_wolCommand
command to use for wake-on-lan
bool m_wolEnabled
true if wake-on-lan params are used
int m_dbPort
database port
int m_wolRetry
times to retry to reconnect
QString m_dbHostName
database server
DB connection pool, used by MSqlQuery. Do not use directly.
void PurgeIdleConnections(bool leaveOne=false)
MSqlDatabase * m_channelCon
void CloseDatabases(void)
void pushConnection(MSqlDatabase *db)
QHash< QThread *, int > m_inuseCount
QList< MSqlDatabase * > DBList
MSqlDatabase * getChannelCon(void)
QHash< QThread *, DBList > m_staticPool
MSqlDatabase * getStaticCon(MSqlDatabase **dbcon, const QString &name)
QHash< QThread *, DBList > m_pool
MSqlDatabase * popConnection(bool reuse)
MSqlDatabase * m_schedCon
MSqlDatabase * getSchedCon(void)
QHash< QThread *, MSqlDatabase * > m_inuse
QSqlDatabase wrapper, used by MSqlQuery. Do not use directly.
bool OpenDatabase(bool skipdb=false)
void InitSessionVars(void)
QSqlDatabase db(void) const
MSqlDatabase(QString name, QString driver="QMYSQL")
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QSqlRecord record(void) const
bool Reconnect(void)
Reconnects server and re-prepares and re-binds the last prepared query.
QString m_lastPreparedQuery
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
bool lostConnectionCheck(void)
lostConnectionCheck tests for SQL error codes that indicate the connection to the server has been los...
QVariant value(int i) const
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
static MSqlQueryInfo SchedCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
void bindValues(const MSqlBindings &bindings)
Add all the bindings in the passed in bindings.
~MSqlQuery()
Returns connection to pool.
void setForwardOnly(bool f)
void bindValueNoNull(const QString &placeholder, const QVariant &val)
Add a single binding, taking care not to set a NULL value.
QVariantList boundValues(void) const
bool previous(void)
Wrap QSqlQuery::previous() so we can display the query results.
bool last(void)
Wrap QSqlQuery::last() so we can display the query results.
bool seek(int where, bool relative=false)
Wrap QSqlQuery::seek(int,bool)
MSqlQuery(const MSqlQueryInfo &qi)
Get DB connection from pool.
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
bool seekDebug(const char *type, bool result, int where, bool relative) const
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
static MSqlQueryInfo ChannelCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
QVariant lastInsertId()
Return the id of the last inserted row.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
const QSqlDriver * driver(void) const
bool IsWOLAllowed() const
static QString DBErrorMessage(const QSqlError &err)
static QString GetError(const QString &where, const MSqlQuery &query)
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.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static void InitMSqlQueryInfo(MSqlQueryInfo &qi)
void MSqlEscapeAsAQuery(QString &query, const MSqlBindings &bindings)
Given a partial query string and a bindings object, escape the string.
void MSqlAddMoreBindings(MSqlBindings &output, MSqlBindings &addfrom)
Add the entries in addfrom to the map in output.
bool TestDatabase(const QString &dbHostName, const QString &dbUserName, QString dbPassword, QString dbName, int dbPort)
static constexpr std::chrono::seconds kPurgeTimeout
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
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.
@ kDatabase
Default UTC, database format.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
bool rand_bool(uint32_t chance=2)
return a random bool with P(true) = 1/chance
bool operator==(const Holder &h) const
bool operator!=(const Holder &h) const
Holder(QString hldr=QString(), int pos=-1)
MSqlDatabase Info, used by MSqlQuery. Do not use directly.