8#include <QCoreApplication>
9#include <QElapsedTimer>
10#include <QRegularExpression>
33#define DEBUG_RECONNECT 0
43 const QString& dbUserName,
52 QMutexLocker locker(&
sMutex);
55 if (dbHostName.isEmpty() || dbUserName.isEmpty())
63 dbparms.
m_dbName = std::move(dbName);
75 db->SetDBParams(dbparms);
77 ret = db->OpenDatabase(
true);
86 : m_name(std::move(name)), m_driver(std::move(driver))
88 if (!QSqlDatabase::isDriverAvailable(
m_driver))
90 LOG(VB_FLUSH, LOG_CRIT,
91 QString(
"FATAL: Unable to load the QT %1 driver, is it installed?")
98 LOG(VB_DATABASE, LOG_INFO,
"Database object created: " +
m_name);
100 if (!
m_db.isValid() ||
m_db.isOpenError())
103 LOG(VB_FLUSH, LOG_CRIT, QString(
"FATAL: Unable to create database object (%1), the installed QT driver may be invalid.").arg(
m_name));
115 m_db = QSqlDatabase();
118 QSqlDatabase::removeDatabase(
m_name);
119 LOG(VB_DATABASE, LOG_INFO,
"Database object deleted: " +
m_name);
139 LOG(VB_GENERAL, LOG_ERR,
140 "MSqlDatabase::OpenDatabase(), db object is not valid!");
144 bool connected =
true;
176 m_db.setHostName(
"localhost");
180 m_db.setConnectOptions(QString(
"MYSQL_OPT_READ_TIMEOUT=300"));
182 connected =
m_db.open();
191 LOG(VB_GENERAL, LOG_INFO,
192 QString(
"Using WOL to wakeup database server (Try %1 of "
198 LOG(VB_GENERAL, LOG_ERR,
199 QString(
"Failed to run WOL command '%1'")
204 connected =
m_db.open();
209 LOG(VB_GENERAL, LOG_ERR,
210 "WOL failed, unable to connect to database!");
215 LOG(VB_DATABASE, LOG_INFO,
216 QString(
"[%1] Connected to database '%2' at host: %3")
229 bool have_schema =
false;
230 QString sql =
"SELECT COUNT(TABLE_NAME) "
231 " FROM INFORMATION_SCHEMA.TABLES "
232 " WHERE TABLE_SCHEMA = DATABASE() "
233 " AND TABLE_TYPE = 'BASE TABLE';";
237 QSqlQuery query(sql,
m_db);
239 have_schema = query.value(0).toInt() > 1;
249 LOG(VB_GENERAL, LOG_ERR, QString(
"[%1] Unable to connect to database!").arg(
m_name));
263 return m_db.isOpen();
271 bool open =
m_db.isOpen();
274 LOG(VB_GENERAL, LOG_INFO,
"MySQL reconnected successfully");
283 QSqlQuery query(
m_db);
286 query.exec(
"SET @@session.time_zone='+00:00'");
288 query.exec(
"SET @@session.sql_mode=''");
301 LOG(VB_GENERAL, LOG_CRIT,
302 "MDBManager exiting with connections still open");
322 db =
m_inuse[QThread::currentThread()];
339 LOG(VB_DATABASE, LOG_INFO,
340 QString(
"New DB connection, total: %1").arg(
m_connCount));
352 m_inuse[QThread::currentThread()] = db;
368 if (db ==
m_inuse[QThread::currentThread()])
376 m_inuse[QThread::currentThread()] =
nullptr;
383 m_pool[QThread::currentThread()].push_front(db);
393 QMutexLocker locker(&
m_lock);
399 DBList::iterator it = list.begin();
401 uint purgedConnections = 0;
402 uint totalConnections = 0;
404 while (it != list.end())
407 if ((*it)->m_lastDBKick.secsTo(now) <=
kPurgeTimeout.count())
430 if (leaveOne && it == list.end() &&
431 purgedConnections > 0 &&
432 totalConnections == purgedConnections)
437 LOG(VB_GENERAL, LOG_INFO,
438 QString(
"New DB connection, total: %1").arg(
m_connCount));
442 LOG(VB_DATABASE, LOG_INFO,
"Deleting idle DB connection...");
444 LOG(VB_DATABASE, LOG_INFO,
"Done deleting idle DB connection.");
447 list.push_front(newDb);
449 if (purgedConnections)
451 LOG(VB_DATABASE, LOG_INFO,
452 QString(
"Purged %1 idle of %2 total DB connections.")
453 .arg(purgedConnections).arg(totalConnections));
465 LOG(VB_GENERAL, LOG_INFO,
"New static DB connection" + name);
468 (*dbcon)->OpenDatabase();
470 if (!
m_staticPool[QThread::currentThread()].contains(*dbcon))
471 m_staticPool[QThread::currentThread()].push_back(*dbcon);
490 m_pool[QThread::currentThread()].clear();
493 for (
auto *conn : std::as_const(list))
495 LOG(VB_DATABASE, LOG_INFO,
496 "Closing DB connection named '" + conn->m_name +
"'");
504 while (!slist.isEmpty())
507 LOG(VB_DATABASE, LOG_INFO,
508 "Closing DB connection named '" + db->
m_name +
"'");
526 qi.
qsqldb = QSqlDatabase();
532 : QSqlQuery(QString(), qi.qsqldb),
534 m_isConnected(m_db && m_db->isOpen()),
535 m_returnConnection(qi.returnConnection)
545 if (dbmanager &&
m_db)
563 if (db->
m_db.hostName().isEmpty())
569 GetMythDB()->GetDBManager()->pushConnection(db);
630 LOG(VB_GENERAL, LOG_ERR,
631 "MSqlQuery::exec(void) called without a prepared query.");
638 LOG(VB_GENERAL, LOG_INFO,
639 "MSqlQuery disconnecting DB to test reconnection logic");
648 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
655 bool result = QSqlQuery::exec();
656 qint64 elapsed = timer.elapsed();
659 result = QSqlQuery::exec();
664#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
667 QVariantList tmp = QSqlQuery::boundValues();
669 bool has_null_strings =
false;
671 for (
auto it = tmp.begin(); it != tmp.end(); ++it)
673#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
674 auto type =
static_cast<QMetaType::Type
>(it->type());
676 auto type = it->typeId();
678 if (
type != QMetaType::QString)
680 if (it->isNull() || it->toString().isNull())
682 has_null_strings =
true;
683 *it = QVariant(QString(
""));
686 if (has_null_strings)
688#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
691 for (
int i = 0; i < static_cast<int>(tmp.size()); i++)
692 QSqlQuery::bindValue(i, tmp.at(i));
695 result = QSqlQuery::exec();
696 elapsed = timer.elapsed();
700 LOG(VB_GENERAL, LOG_ERR,
701 QString(
"Original query failed, but resend with empty "
702 "strings in place of NULL strings worked. ") +
709 QString str = lastQuery();
715#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
720 str.replace(b.key(),
'\'' + b.value().toString() +
'\'');
724 static const QRegularExpression placeholders {
"(:\\w+)" };
725 auto match = placeholders.match(str);
726 while (match.hasMatch())
728 str.replace(match.capturedStart(), match.capturedLength(),
731 :
'\'' + b.takeFirst().toString() +
'\'');
732 match = placeholders.match(str);
736 LOG(VB_DATABASE, LOG_INFO,
737 QString(
"MSqlQuery::exec(%1) %2%3%4")
738 .arg(
m_db->MSqlDatabase::GetConnectionName(), str,
739 QString(
" <<<< Took %1ms").arg(QString::number(elapsed)),
741 ? QString(
", Returned %1 row(s)").arg(
size())
760 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
764 bool result = QSqlQuery::exec(query);
767 result = QSqlQuery::exec(query);
769 LOG(VB_DATABASE, LOG_INFO,
770 QString(
"MSqlQuery::exec(%1) %2%3")
771 .arg(
m_db->MSqlDatabase::GetConnectionName(), query,
773 ? QString(
" <<<< Returns %1 row(s)").arg(
size())
780 int where,
bool relative)
const
785 QSqlRecord rec =
record();
787 for (
int i = 0; i < rec.count(); i++)
792 str.append(rec.fieldName(i) +
" = " +
796 if (QString(
"seek")==
type)
798 LOG(VB_DATABASE, LOG_DEBUG,
799 QString(
"MSqlQuery::seek(%1,%2,%3) Result: \"%4\"")
800 .arg(
m_db->MSqlDatabase::GetConnectionName())
801 .arg(where).arg(relative)
806 LOG(VB_DATABASE, LOG_DEBUG,
807 QString(
"MSqlQuery::%1(%2) Result: \"%3\"")
808 .arg(
type,
m_db->MSqlDatabase::GetConnectionName(), str));
816 return seekDebug(
"next", QSqlQuery::next(), 0,
false);
821 return seekDebug(
"previous", QSqlQuery::previous(), 0,
false);
826 return seekDebug(
"first", QSqlQuery::first(), 0,
false);
831 return seekDebug(
"last", QSqlQuery::last(), 0,
false);
836 return seekDebug(
"seek", QSqlQuery::seek(where, relative), where, relative);
851 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
862 bool ok = QSqlQuery::prepare(query);
867 if (!ok && !(
GetMythDB()->SuppressDBMessages()))
869 LOG(VB_GENERAL, LOG_ERR,
870 QString(
"Error preparing query: %1").arg(query));
871 LOG(VB_GENERAL, LOG_ERR,
884 bool isOpen = db->
isOpen();
886 GetMythDB()->GetDBManager()->pushConnection(db);
892#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
893 if (
static_cast<QMetaType::Type
>(val.type()) == QMetaType::QDateTime)
895 QSqlQuery::bindValue(placeholder,
901 QSqlQuery::bindValue(placeholder, val, QSql::In);
906#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
907 auto type =
static_cast<QMetaType::Type
>(val.type());
909 auto type = val.typeId();
911 if (
type == QMetaType::QString && val.toString().isNull())
913 QSqlQuery::bindValue(placeholder, QString(
""), QSql::In);
916#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
917 if (
type == QMetaType::QDateTime)
919 QSqlQuery::bindValue(placeholder,
925 QSqlQuery::bindValue(placeholder, val, QSql::In);
930 MSqlBindings::const_iterator it;
931 for (it = bindings.begin(); it != bindings.end(); ++it)
939 return QSqlQuery::lastInsertId();
948#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
954 QVariantList tmp = QSqlQuery::boundValues();
957 for (
int i = 0; i < static_cast<int>(tmp.size()); i++)
958 QSqlQuery::bindValue(i, tmp.at(i));
971 static QStringList kLostConnectionCodes = {
"2006",
"2013",
"4031" };
973 QString error_code = QSqlQuery::lastError().nativeErrorCode();
976 LOG(VB_GENERAL, LOG_DEBUG, QString(
"SQL Native Error Code: %1")
981 return (kLostConnectionCodes.contains(error_code) &&
Reconnect());
987 MSqlBindings::Iterator it;
988 for (it = addfrom.begin(); it != addfrom.end(); ++it)
990 output.insert(it.key(), it.value());
995 explicit Holder( QString hldr = QString(),
int pos = -1 )
1010 static const QRegularExpression rx {
"('[^']+'|:\\w+)",
1011 QRegularExpression::UseUnicodePropertiesOption};
1013 QVector<Holder> holders;
1015 auto matchIter = rx.globalMatch(query);
1016 while (matchIter.hasNext())
1018 auto match = matchIter.next();
1019 if (match.capturedLength(1) > 0)
1020 holders.append(
Holder(match.captured(), match.capturedStart()));
1026 for (
int i = holders.count() - 1; i >= 0; --i)
1028 holder = holders[(
uint)i].m_holderName;
1029 val = bindings[holder];
1030#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1031 QSqlField f(
"", val.type());
1033 QSqlField f(
"", val.metaType());
1040 query = query.replace((
uint)holders[(
uint)i].m_holderPos, holder.length(),
1041 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)
Small class to handle TCP port checking and finding link-local context.
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.