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");
179 m_db.setConnectOptions(QString(
"MYSQL_OPT_READ_TIMEOUT=300"));
181 connected =
m_db.open();
190 LOG(VB_GENERAL, LOG_INFO,
191 QString(
"Using WOL to wakeup database server (Try %1 of "
197 LOG(VB_GENERAL, LOG_ERR,
198 QString(
"Failed to run WOL command '%1'")
203 connected =
m_db.open();
208 LOG(VB_GENERAL, LOG_ERR,
209 "WOL failed, unable to connect to database!");
214 LOG(VB_DATABASE, LOG_INFO,
215 QString(
"[%1] Connected to database '%2' at host: %3")
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';";
236 QSqlQuery query(sql,
m_db);
238 have_schema = query.value(0).toInt() > 1;
248 LOG(VB_GENERAL, LOG_ERR, QString(
"[%1] Unable to connect to database!").arg(
m_name));
262 return m_db.isOpen();
270 bool open =
m_db.isOpen();
273 LOG(VB_GENERAL, LOG_INFO,
"MySQL reconnected successfully");
282 QSqlQuery query(
m_db);
285 query.exec(
"SET @@session.time_zone='+00:00'");
287 query.exec(
"SET @@session.sql_mode=''");
300 LOG(VB_GENERAL, LOG_CRIT,
301 "MDBManager exiting with connections still open");
321 db =
m_inuse[QThread::currentThread()];
338 LOG(VB_DATABASE, LOG_INFO,
339 QString(
"New DB connection, total: %1").arg(
m_connCount));
351 m_inuse[QThread::currentThread()] = db;
367 if (db ==
m_inuse[QThread::currentThread()])
375 m_inuse[QThread::currentThread()] =
nullptr;
382 m_pool[QThread::currentThread()].push_front(db);
392 QMutexLocker locker(&
m_lock);
398 DBList::iterator it = list.begin();
400 uint purgedConnections = 0;
401 uint totalConnections = 0;
403 while (it != list.end())
406 if ((*it)->m_lastDBKick.secsTo(now) <=
kPurgeTimeout.count())
429 if (leaveOne && it == list.end() &&
430 purgedConnections > 0 &&
431 totalConnections == purgedConnections)
436 LOG(VB_GENERAL, LOG_INFO,
437 QString(
"New DB connection, total: %1").arg(
m_connCount));
441 LOG(VB_DATABASE, LOG_INFO,
"Deleting idle DB connection...");
443 LOG(VB_DATABASE, LOG_INFO,
"Done deleting idle DB connection.");
446 list.push_front(newDb);
448 if (purgedConnections)
450 LOG(VB_DATABASE, LOG_INFO,
451 QString(
"Purged %1 idle of %2 total DB connections.")
452 .arg(purgedConnections).arg(totalConnections));
464 LOG(VB_GENERAL, LOG_INFO,
"New static DB connection" + name);
467 (*dbcon)->OpenDatabase();
469 if (!
m_staticPool[QThread::currentThread()].contains(*dbcon))
470 m_staticPool[QThread::currentThread()].push_back(*dbcon);
489 m_pool[QThread::currentThread()].clear();
492 for (
auto *conn : std::as_const(list))
494 LOG(VB_DATABASE, LOG_INFO,
495 "Closing DB connection named '" + conn->m_name +
"'");
503 while (!slist.isEmpty())
506 LOG(VB_DATABASE, LOG_INFO,
507 "Closing DB connection named '" + db->
m_name +
"'");
525 qi.
qsqldb = QSqlDatabase();
531 : QSqlQuery(QString(), qi.qsqldb),
533 m_isConnected(m_db && m_db->isOpen()),
534 m_returnConnection(qi.returnConnection)
544 if (dbmanager &&
m_db)
562 if (db->
m_db.hostName().isEmpty())
568 GetMythDB()->GetDBManager()->pushConnection(db);
629 LOG(VB_GENERAL, LOG_ERR,
630 "MSqlQuery::exec(void) called without a prepared query.");
637 LOG(VB_GENERAL, LOG_INFO,
638 "MSqlQuery disconnecting DB to test reconnection logic");
647 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
654 bool result = QSqlQuery::exec();
655 qint64 elapsed = timer.elapsed();
658 result = QSqlQuery::exec();
663#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
666 QVariantList tmp = QSqlQuery::boundValues();
668 bool has_null_strings =
false;
670 for (
auto it = tmp.begin(); it != tmp.end(); ++it)
672#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
673 auto type =
static_cast<QMetaType::Type
>(it->type());
675 auto type = it->typeId();
677 if (
type != QMetaType::QString)
679 if (it->isNull() || it->toString().isNull())
681 has_null_strings =
true;
682 *it = QVariant(QString(
""));
685 if (has_null_strings)
687#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
690 for (
int i = 0; i < static_cast<int>(tmp.size()); i++)
691 QSqlQuery::bindValue(i, tmp.at(i));
694 result = QSqlQuery::exec();
695 elapsed = timer.elapsed();
699 LOG(VB_GENERAL, LOG_ERR,
700 QString(
"Original query failed, but resend with empty "
701 "strings in place of NULL strings worked. ") +
708 QString str = lastQuery();
714#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
719 str.replace(b.key(),
'\'' + b.value().toString() +
'\'');
723 static const QRegularExpression placeholders {
"(:\\w+)" };
724 auto match = placeholders.match(str);
725 while (match.hasMatch())
727 str.replace(match.capturedStart(), match.capturedLength(),
730 :
'\'' + b.takeFirst().toString() +
'\'');
731 match = placeholders.match(str);
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)),
740 ? QString(
", Returned %1 row(s)").arg(
size())
759 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
763 bool result = QSqlQuery::exec(query);
766 result = QSqlQuery::exec(query);
768 LOG(VB_DATABASE, LOG_INFO,
769 QString(
"MSqlQuery::exec(%1) %2%3")
770 .arg(
m_db->MSqlDatabase::GetConnectionName(), query,
772 ? QString(
" <<<< Returns %1 row(s)").arg(
size())
779 int where,
bool relative)
const
784 QSqlRecord rec =
record();
786 for (
int i = 0; i < rec.count(); i++)
791 str.append(rec.fieldName(i) +
" = " +
795 if (QString(
"seek")==
type)
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)
805 LOG(VB_DATABASE, LOG_DEBUG,
806 QString(
"MSqlQuery::%1(%2) Result: \"%3\"")
807 .arg(
type,
m_db->MSqlDatabase::GetConnectionName(), str));
815 return seekDebug(
"next", QSqlQuery::next(), 0,
false);
820 return seekDebug(
"previous", QSqlQuery::previous(), 0,
false);
825 return seekDebug(
"first", QSqlQuery::first(), 0,
false);
830 return seekDebug(
"last", QSqlQuery::last(), 0,
false);
835 return seekDebug(
"seek", QSqlQuery::seek(where, relative), where, relative);
850 LOG(VB_GENERAL, LOG_INFO,
"MySQL server disconnected");
861 bool ok = QSqlQuery::prepare(query);
866 if (!ok && !(
GetMythDB()->SuppressDBMessages()))
868 LOG(VB_GENERAL, LOG_ERR,
869 QString(
"Error preparing query: %1").arg(query));
870 LOG(VB_GENERAL, LOG_ERR,
883 bool isOpen = db->
isOpen();
885 GetMythDB()->GetDBManager()->pushConnection(db);
891#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
892 if (
static_cast<QMetaType::Type
>(val.type()) == QMetaType::QDateTime)
894 QSqlQuery::bindValue(placeholder,
900 QSqlQuery::bindValue(placeholder, val, QSql::In);
905#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
906 auto type =
static_cast<QMetaType::Type
>(val.type());
908 auto type = val.typeId();
910 if (
type == QMetaType::QString && val.toString().isNull())
912 QSqlQuery::bindValue(placeholder, QString(
""), QSql::In);
915#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
916 if (
type == QMetaType::QDateTime)
918 QSqlQuery::bindValue(placeholder,
924 QSqlQuery::bindValue(placeholder, val, QSql::In);
929 MSqlBindings::const_iterator it;
930 for (it = bindings.begin(); it != bindings.end(); ++it)
938 return QSqlQuery::lastInsertId();
947#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
953 QVariantList tmp = QSqlQuery::boundValues();
956 for (
int i = 0; i < static_cast<int>(tmp.size()); i++)
957 QSqlQuery::bindValue(i, tmp.at(i));
970 static QStringList kLostConnectionCodes = {
"2006",
"2013",
"4031" };
972 QString error_code = QSqlQuery::lastError().nativeErrorCode();
975 LOG(VB_GENERAL, LOG_DEBUG, QString(
"SQL Native Error Code: %1")
980 return (kLostConnectionCodes.contains(error_code) &&
Reconnect());
986 MSqlBindings::Iterator it;
987 for (it = addfrom.begin(); it != addfrom.end(); ++it)
989 output.insert(it.key(), it.value());
994 explicit Holder( QString hldr = QString(),
int pos = -1 )
1009 static const QRegularExpression rx {
"('[^']+'|:\\w+)",
1010 QRegularExpression::UseUnicodePropertiesOption};
1012 QVector<Holder> holders;
1014 auto matchIter = rx.globalMatch(query);
1015 while (matchIter.hasNext())
1017 auto match = matchIter.next();
1018 if (match.capturedLength(1) > 0)
1019 holders.append(
Holder(match.captured(), match.capturedStart()));
1025 for (
int i = holders.count() - 1; i >= 0; --i)
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());
1032 QSqlField f(
"", val.metaType());
1039 query = query.replace((
uint)holders[(
uint)i].m_holderPos, holder.length(),
1040 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.