9#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
10#include <QtSystemDetection>
14#include <QRegularExpression>
30#define LOC QString("DBUtil: ")
65 std::array<int,3> compareto {major,
minor, point};
66 for (
int i = 0; i < 3 && !result; i++)
68 if ((
version[i] > -1) || (compareto[i] != 0))
69 result =
version[i] - compareto[i];
81 const int size = tables.size();
84 return (((size == 1) && (tables.at(0).endsWith(
".`schemalock`"))) ||
94 QString backupStartTimeStr =
98 if (backupStartTimeStr.isEmpty())
100 LOG(VB_DATABASE, LOG_ERR,
"DBUtil::BackupInProgress(): No start time "
101 "found, database backup is not in progress.");
105 backupStartTimeStr.replace(
" ",
"T");
111 if (backupEndTimeStr.isEmpty())
114 if (backupElapsed < 10min)
116 LOG(VB_DATABASE, LOG_INFO,
117 QString(
"DBUtil::BackupInProgress(): Found "
118 "database backup start time of %1 which was %2 seconds "
119 "ago, therefore it appears the backup is still running.")
120 .arg(backupStartTimeStr)
121 .arg(backupElapsed.count()));
124 LOG(VB_DATABASE, LOG_ERR, QString(
"DBUtil::BackupInProgress(): "
125 "Database backup started at %1, but no end time was found. "
126 "The backup started %2 seconds ago and should have "
127 "finished by now therefore it appears it is not running .")
128 .arg(backupStartTimeStr)
129 .arg(backupElapsed.count()));
133 backupEndTimeStr.replace(
" ",
"T");
137 if (backupEndTime >= backupStartTime)
139 LOG(VB_DATABASE, LOG_ERR,
140 QString(
"DBUtil::BackupInProgress(): Found "
141 "database backup end time of %1 later than start time "
142 "of %2, therefore backup is not running.")
143 .arg(backupEndTimeStr, backupStartTimeStr));
146 if (backupElapsed > 10min)
148 LOG(VB_DATABASE, LOG_ERR,
149 QString(
"DBUtil::BackupInProgress(): "
150 "Database backup started at %1, but has not ended yet. "
151 "The backup started %2 seconds ago and should have "
152 "finished by now therefore it appears it is not running")
153 .arg(backupStartTimeStr)
154 .arg(backupElapsed.count()));
159 LOG(VB_DATABASE, LOG_INFO, QString(
"DBUtil::BackupInProgress(): "
160 "Database backup started at %1, and is still running.")
161 .arg(backupStartTimeStr));
191 [[maybe_unused]]
bool disableRotation)
196 LOG(VB_GENERAL, LOG_CRIT,
"Database backups disabled on Windows.");
202 LOG(VB_GENERAL, LOG_CRIT,
203 "Database backups disabled. Skipping backup.");
209 LOG(VB_GENERAL, LOG_CRIT,
"New database detected. Skipping backup.");
213 QString backupScript =
GetShareDir() +
"mythconverg_backup.pl";
219 LOG(VB_GENERAL, LOG_CRIT, QString(
"Database backup script does "
220 "not exist: %1").arg(backupScript));
221 backupScript.clear();
228 "BackupDBLastRunStart",
231 if (!backupScript.isEmpty())
235 LOG(VB_GENERAL, LOG_CRIT,
"Script-based database backup failed. "
236 "Retrying with internal backup.");
243 "BackupDBLastRunEnd",
248 QString dbTag(
"BackupDB");
249 query.
prepare(
"DELETE FROM housekeeping WHERE tag = :TAG ;");
254 query.
prepare(
"INSERT INTO housekeeping(tag,lastrun) "
255 "values(:TAG ,now()) ;");
285 const QStringList all_tables =
GetTables(QStringList(
"MyISAM"));
287 if (all_tables.empty())
290 QString sql = QString(
"CHECK TABLE %1 %2;")
291 .arg(all_tables.join(
", "),
options);
293 LOG(VB_GENERAL, LOG_CRIT,
"Checking database tables.");
294 if (!query.
exec(sql))
304 LOG(VB_GENERAL, LOG_CRIT, QString(
"Found crashed database table(s): %1")
305 .arg(tables.join(
", ")));
343 QString all_tables = tables.join(
", ");
344 LOG(VB_GENERAL, LOG_CRIT, QString(
"Repairing database tables: %1")
347 QString sql = QString(
"REPAIR TABLE %1;").arg(all_tables);
348 if (!query.
exec(sql))
356 if (!bad_tables.empty())
358 LOG(VB_GENERAL, LOG_CRIT,
359 QString(
"Unable to repair crashed table(s): %1")
360 .arg(bad_tables.join(
", ")));
387 QSqlRecord record = query.
record();
388 int table_index = record.indexOf(
"Table");
389 int type_index = record.indexOf(
"Msg_type");
390 int text_index = record.indexOf(
"Msg_text");
394 QString previous_table;
398 table = query.
value(table_index).toString();
399 type = query.
value(type_index).toString();
400 text = query.
value(text_index).toString();
401 if (table != previous_table)
405 tables.append(previous_table);
408 previous_table = table;
411 if (
"status" ==
type.toLower() &&
"ok" == text.toLower())
413 else if (
"error" ==
type.toLower() ||
414 (
"status" ==
type.toLower() &&
"ok" != text.toLower()))
419 tables.append(table);
436 QString sql =
"SELECT CONCAT('`', TABLE_SCHEMA, "
437 " '`.`', TABLE_NAME, "
438 " '`') AS `TABLE_NAME` "
439 " FROM INFORMATION_SCHEMA.TABLES "
440 " WHERE TABLE_SCHEMA = DATABASE() "
441 " AND TABLE_TYPE = 'BASE TABLE'";
442 if (!engines.empty())
443 sql.append(QString(
" AND ENGINE IN ('%1')")
444 .arg(engines.join(
"', '")));
445 if (!query.
exec(sql))
453 result.append(query.
value(0).toString());
474 return QString(
"%1-%2%3").arg(
prefix, time, extension);
491 if (!dirList.empty())
495 if (!QDir(directory).
exists())
497 LOG(VB_FILE, LOG_INFO,
"GetBackupDirectory() - ignoring " +
498 directory +
", using /tmp");
503 if (directory.isNull())
524 const QString &privateinfo, QString &
filename)
528 const QByteArray tmpfile =
filename.toLocal8Bit();
530 FILE *fp = fopen(tmpfile.constData(),
"w");
533 LOG(VB_GENERAL, LOG_ERR,
LOC +
534 QString(
"Unable to create temporary "
535 "configuration file for creating DB backup: %1")
536 .arg(tmpfile.constData()));
542 if (chmod(tmpfile.constData(), S_IRUSR))
544 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Error changing permissions '%1'")
545 .arg(tmpfile.constData()) +
ENO);
548 QByteArray outarr = privateinfo.toLocal8Bit();
549 fprintf(fp,
"%s", outarr.constData());
553 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Error closing '%1'")
554 .arg(tmpfile.constData()) +
ENO);
568 bool disableRotation)
574 dbSchemaVer,
".sql");
579 if (!(scriptArgs.contains(
"rotate", Qt::CaseInsensitive)))
580 rotate =
"rotate=-1";
584 QString privateinfo =
585 QString(
"DBHostName=%1\nDBPort=%2\n"
586 "DBUserName=%3\nDBPassword=%4\n"
587 "DBName=%5\nDBSchemaVer=%6\n"
588 "DBBackupDirectory=%7\nDBBackupFilename=%8\n%9\n")
592 backupDirectory, backupFilename,
594 QString tempDatabaseConfFile;
597 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Attempting backup, anyway.");
599 LOG(VB_GENERAL, LOG_ERR, QString(
"Backing up database with script: '%1'")
602 QString command = backupScript +
" " + scriptArgs +
" " +
603 tempDatabaseConfFile;
608 QByteArray tmpfile = tempDatabaseConfFile.toLocal8Bit();
609 unlink(tmpfile.constData());
614 LOG(VB_GENERAL, LOG_ERR,
LOC +
615 QString(
"Error backing up database: %1 (%2)")
616 .arg(command).arg(status));
621 LOG(VB_GENERAL, LOG_CRIT,
"Database Backup complete.");
623 QDir dir(backupDirectory, backupFilename +
"*");
624 uint numfiles = dir.count();
631 LOG(VB_FILE, LOG_ERR,
LOC +
632 QString(
"No files beginning with the suggested database backup "
633 "filename '%1' were found in '%2'.")
634 .arg(backupFilename, backupDirectory));
638 filename = dir.path() +
"/" + dir[0];;
641 LOG(VB_FILE, LOG_ERR,
LOC +
642 QString(
"Multiple files beginning with the suggested database "
643 "backup filename '%1' were found in '%2'. "
644 "Assuming the first is the backup.")
645 .arg(backupFilename, backupDirectory));
651 LOG(VB_GENERAL, LOG_CRIT, QString(
"Backed up database to file: '%1'")
671 QString compressCommand(
"");
672 QString extension =
".sql";
674 compressCommand =
"/bin/gzip";
676 compressCommand =
"/usr/bin/gzip";
678 LOG(VB_GENERAL, LOG_CRIT,
"Neither /bin/gzip nor /usr/bin/gzip exist. "
679 "The database backup will be uncompressed.");
682 dbParams.
m_dbName +
"-" + dbSchemaVer, extension);
683 QString backupPathname = backupDirectory +
"/" + backupFilename;
685 QString privateinfo = QString(
686 "[client]\npassword=%1\n[mysqldump]\npassword=%2\n")
688 QString tempExtraConfFile;
692 QString portArg =
"";
694 portArg = QString(
" --port='%1'").arg(dbParams.
m_dbPort);
695 command = QString(
"mysqldump --defaults-extra-file='%1' --host='%2'%3"
696 " --user='%4' --add-drop-table --add-locks"
697 " --allow-keywords --complete-insert"
698 " --extended-insert --lock-tables --no-create-db --quick"
699 " '%5' > '%6' 2>/dev/null")
704 LOG(VB_FILE, LOG_INFO, QString(
"Backing up database with command: '%1'")
706 LOG(VB_GENERAL, LOG_CRIT, QString(
"Backing up database to file: '%1'")
707 .arg(backupPathname));
711 QByteArray tmpfile = tempExtraConfFile.toLocal8Bit();
712 unlink(tmpfile.constData());
716 LOG(VB_GENERAL, LOG_ERR,
LOC +
717 QString(
"Error backing up database: '%1' (%2)")
718 .arg(command).arg(status));
723 if (compressCommand !=
"")
725 LOG(VB_GENERAL, LOG_CRIT,
"Compressing database backup file.");
726 compressCommand +=
" " + backupPathname;
731 LOG(VB_GENERAL, LOG_CRIT,
732 "Compression failed, backup file will remain uncompressed.");
736 backupPathname +=
".gz";
738 LOG(VB_GENERAL, LOG_CRIT, QString(
"Database Backup filename: '%1'")
739 .arg(backupPathname));
743 LOG(VB_GENERAL, LOG_CRIT,
"Database Backup complete.");
760 if (dbmsVersion.isEmpty())
763 query.
prepare(
"SELECT VERSION();");
766 LOG(VB_GENERAL, LOG_ERR,
LOC +
767 "Unable to determine MySQL version.");
773 dbmsVersion = query.
value(0).toString();
790 static const QRegularExpression parseVersion
791 { R
"(^(\d+)(?:\.(\d+)(?:\.(\d+))?)?)" };
793 if (!match.hasMatch())
815 LOG(VB_GENERAL, LOG_DEBUG,
"Not connected to DB");
819 if (!query.
exec(
"SHOW PROCESSLIST;"))
825 QSqlRecord record = query.
record();
826 int db_index = record.indexOf(
"db");
827 QString dbName =
GetMythDB()->GetDatabaseName();
832 inUseDB = query.
value(db_index).toString();
833 if (inUseDB == dbName)
839 count = (count + 3)/4;
841 LOG(VB_GENERAL, LOG_DEBUG,
842 QString(
"DBUtil::CountClients() found %1").arg(count));
852 query.
prepare(
"SELECT GET_LOCK('schemaLock', :TIMEOUT)");
853 query.
bindValue(
":TIMEOUT", timeout_secs);
859 query.
prepare(
"SELECT RELEASE_LOCK('schemaLock')");
872 query.
prepare(
"SELECT CONVERT_TZ(NOW(), 'SYSTEM', 'Etc/UTC')");
875 LOG(VB_GENERAL, LOG_ERR,
"MySQL time zone support check failed");
879 return !query.
value(0).isNull();
899 QString sql = QString(
"SELECT COUNT(*) FROM information_schema.columns "
900 "WHERE table_schema = DATABASE() AND "
901 "table_name = '%1' AND column_name = '%2';")
902 .arg(tableName, columnName);
903 LOG(VB_GENERAL, LOG_DEBUG,
904 QString(
"DBUtil::CheckTableColumnExists() SQL: %1").arg(sql));
906 if (!query.
exec(sql))
915 result = (query.
value(0).toInt() > 0);
919 LOG(VB_GENERAL, LOG_ERR,
920 QString(
"DBUtil::CheckTableColumnExists() - Empty result set"));
923 LOG(VB_GENERAL, LOG_DEBUG,
924 QString(
"DBUtil::CheckTableColumnExists('%1', '%2') result: %3").arg(tableName,
925 columnName, QVariant(result).
toString()));
static MythDBBackupStatus BackupDB(QString &filename, bool disableRotation=false)
Requests a backup of the database.
static bool CheckTables(bool repair=false, const QString &options="QUICK")
Checks database tables.
bool ParseDBMSVersion(void)
Parses m_versionString to find the major, minor, and point version.
bool QueryDBMSVersion(void)
Reads and returns the QString version name from the DBMS or returns QString() in the event of an erro...
static bool RepairTables(const QStringList &tables)
Repairs database tables.
int CompareDBMSVersion(int major, int minor=0, int point=0)
Compares the version of the active DBMS with the provided version.
static QString CreateBackupFilename(const QString &prefix="mythconverg", const QString &extension=".sql")
Creates a filename to use for the filename.
static void UnlockSchema(MSqlQuery &query)
static bool IsBackupInProgress(void)
Test to see if a DB backup is in progress.
static int CountClients(void)
Estimate the number of MythTV programs using the database.
static QString GetBackupDirectory()
Determines the appropriate path for the database backup.
static bool TryLockSchema(MSqlQuery &query, uint timeout_secs)
Try to get a lock on the table schemalock.
static const int kUnknownVersionNumber
static bool CheckTableColumnExists(const QString &tableName, const QString &columnName)
Checks for the presence of a column in a table in the current database.
static bool CreateTemporaryDBConf(const QString &privateinfo, QString &filename)
Creates temporary file containing sensitive DB info.
QString GetDBMSVersion(void)
Returns the QString version name of the DBMS or QString() in the event of an error.
static bool CheckTimeZoneSupport(void)
Check if MySQL has working timz zone support.
static QStringList GetTables(const QStringList &engines=QStringList())
Retrieves a list of tables from the database.
static QStringList CheckRepairStatus(MSqlQuery &query)
Parse the results of a CHECK TABLE or REPAIR TABLE run.
static bool IsNewDatabase(void)
Returns true for a new (empty) database.
static bool DoBackup(const QString &backupScript, QString &filename, bool disableRotation=false)
Creates a backup of the database by executing the backupScript.
Structure containing the basic Database parameters.
QString m_dbName
database name
QString m_dbPassword
DB password.
QString m_dbUserName
DB user name.
int m_dbPort
database port
QString m_dbHostName
database server
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 first(void)
Wrap QSqlQuery::first() so we can display the query results.
QVariant value(int i) const
bool isConnected(void) const
Only updated once during object creation.
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
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.
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
QStringList GetDirList(void) const
QString FindNextDirMostFree(void)
@ GENERIC_EXIT_OK
Exited with no error.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetShareDir(void)
#define ENO
This can be appended to the LOG args with "+".
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString createTempFile(QString name_template, bool dir)
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
@ kMSAnonLog
anonymize the logs
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
std::chrono::seconds secsInPast(const QDateTime &past)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
@ kFilename
Default UTC, "yyyyMMddhhmmss".
@ kDatabase
Default UTC, database format.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.