10#include <QRegularExpression>
26#define LOC QString("DBUtil: ")
61 std::array<int,3> compareto {major,
minor, point};
62 for (
int i = 0; i < 3 && !result; i++)
64 if ((
version[i] > -1) || (compareto[i] != 0))
65 result =
version[i] - compareto[i];
77 const int size = tables.size();
80 return (((size == 1) && (tables.at(0).endsWith(
".`schemalock`"))) ||
90 QString backupStartTimeStr =
94 if (backupStartTimeStr.isEmpty())
96 LOG(VB_DATABASE, LOG_ERR,
"DBUtil::BackupInProgress(): No start time "
97 "found, database backup is not in progress.");
101 backupStartTimeStr.replace(
" ",
"T");
107 if (backupEndTimeStr.isEmpty())
110 if (backupElapsed < 10min)
112 LOG(VB_DATABASE, LOG_INFO,
113 QString(
"DBUtil::BackupInProgress(): Found "
114 "database backup start time of %1 which was %2 seconds "
115 "ago, therefore it appears the backup is still running.")
116 .arg(backupStartTimeStr)
117 .arg(backupElapsed.count()));
120 LOG(VB_DATABASE, LOG_ERR, QString(
"DBUtil::BackupInProgress(): "
121 "Database backup started at %1, but no end time was found. "
122 "The backup started %2 seconds ago and should have "
123 "finished by now therefore it appears it is not running .")
124 .arg(backupStartTimeStr)
125 .arg(backupElapsed.count()));
129 backupEndTimeStr.replace(
" ",
"T");
133 if (backupEndTime >= backupStartTime)
135 LOG(VB_DATABASE, LOG_ERR,
136 QString(
"DBUtil::BackupInProgress(): Found "
137 "database backup end time of %1 later than start time "
138 "of %2, therefore backup is not running.")
139 .arg(backupEndTimeStr, backupStartTimeStr));
142 if (backupElapsed > 10min)
144 LOG(VB_DATABASE, LOG_ERR,
145 QString(
"DBUtil::BackupInProgress(): "
146 "Database backup started at %1, but has not ended yet. "
147 "The backup started %2 seconds ago and should have "
148 "finished by now therefore it appears it is not running")
149 .arg(backupStartTimeStr)
150 .arg(backupElapsed.count()));
155 LOG(VB_DATABASE, LOG_INFO, QString(
"DBUtil::BackupInProgress(): "
156 "Database backup started at %1, and is still running.")
157 .arg(backupStartTimeStr));
187 [[maybe_unused]]
bool disableRotation)
192 LOG(VB_GENERAL, LOG_CRIT,
"Database backups disabled on Windows.");
198 LOG(VB_GENERAL, LOG_CRIT,
199 "Database backups disabled. Skipping backup.");
205 LOG(VB_GENERAL, LOG_CRIT,
"New database detected. Skipping backup.");
209 QString backupScript =
GetShareDir() +
"mythconverg_backup.pl";
215 LOG(VB_GENERAL, LOG_CRIT, QString(
"Database backup script does "
216 "not exist: %1").arg(backupScript));
217 backupScript.clear();
224 "BackupDBLastRunStart",
227 if (!backupScript.isEmpty())
231 LOG(VB_GENERAL, LOG_CRIT,
"Script-based database backup failed. "
232 "Retrying with internal backup.");
239 "BackupDBLastRunEnd",
244 QString dbTag(
"BackupDB");
245 query.
prepare(
"DELETE FROM housekeeping WHERE tag = :TAG ;");
250 query.
prepare(
"INSERT INTO housekeeping(tag,lastrun) "
251 "values(:TAG ,now()) ;");
281 const QStringList all_tables =
GetTables(QStringList(
"MyISAM"));
283 if (all_tables.empty())
286 QString sql = QString(
"CHECK TABLE %1 %2;")
287 .arg(all_tables.join(
", "),
options);
289 LOG(VB_GENERAL, LOG_CRIT,
"Checking database tables.");
290 if (!query.
exec(sql))
300 LOG(VB_GENERAL, LOG_CRIT, QString(
"Found crashed database table(s): %1")
301 .arg(tables.join(
", ")));
339 QString all_tables = tables.join(
", ");
340 LOG(VB_GENERAL, LOG_CRIT, QString(
"Repairing database tables: %1")
343 QString sql = QString(
"REPAIR TABLE %1;").arg(all_tables);
344 if (!query.
exec(sql))
352 if (!bad_tables.empty())
354 LOG(VB_GENERAL, LOG_CRIT,
355 QString(
"Unable to repair crashed table(s): %1")
356 .arg(bad_tables.join(
", ")));
383 QSqlRecord record = query.
record();
384 int table_index = record.indexOf(
"Table");
385 int type_index = record.indexOf(
"Msg_type");
386 int text_index = record.indexOf(
"Msg_text");
390 QString previous_table;
394 table = query.
value(table_index).toString();
395 type = query.
value(type_index).toString();
396 text = query.
value(text_index).toString();
397 if (table != previous_table)
401 tables.append(previous_table);
404 previous_table = table;
407 if (
"status" ==
type.toLower() &&
"ok" == text.toLower())
409 else if (
"error" ==
type.toLower() ||
410 (
"status" ==
type.toLower() &&
"ok" != text.toLower()))
415 tables.append(table);
432 QString sql =
"SELECT CONCAT('`', TABLE_SCHEMA, "
433 " '`.`', TABLE_NAME, "
434 " '`') AS `TABLE_NAME` "
435 " FROM INFORMATION_SCHEMA.TABLES "
436 " WHERE TABLE_SCHEMA = DATABASE() "
437 " AND TABLE_TYPE = 'BASE TABLE'";
438 if (!engines.empty())
439 sql.append(QString(
" AND ENGINE IN ('%1')")
440 .arg(engines.join(
"', '")));
441 if (!query.
exec(sql))
449 result.append(query.
value(0).toString());
470 return QString(
"%1-%2%3").arg(
prefix, time, extension);
487 if (!dirList.empty())
491 if (!QDir(directory).
exists())
493 LOG(VB_FILE, LOG_INFO,
"GetBackupDirectory() - ignoring " +
494 directory +
", using /tmp");
499 if (directory.isNull())
520 const QString &privateinfo, QString &
filename)
524 const QByteArray tmpfile =
filename.toLocal8Bit();
526 FILE *fp = fopen(tmpfile.constData(),
"w");
529 LOG(VB_GENERAL, LOG_ERR,
LOC +
530 QString(
"Unable to create temporary "
531 "configuration file for creating DB backup: %1")
532 .arg(tmpfile.constData()));
538 if (chmod(tmpfile.constData(), S_IRUSR))
540 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Error changing permissions '%1'")
541 .arg(tmpfile.constData()) +
ENO);
544 QByteArray outarr = privateinfo.toLocal8Bit();
545 fprintf(fp,
"%s", outarr.constData());
549 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Error closing '%1'")
550 .arg(tmpfile.constData()) +
ENO);
564 bool disableRotation)
570 dbSchemaVer,
".sql");
575 if (!(scriptArgs.contains(
"rotate", Qt::CaseInsensitive)))
576 rotate =
"rotate=-1";
580 QString privateinfo =
581 QString(
"DBHostName=%1\nDBPort=%2\n"
582 "DBUserName=%3\nDBPassword=%4\n"
583 "DBName=%5\nDBSchemaVer=%6\n"
584 "DBBackupDirectory=%7\nDBBackupFilename=%8\n%9\n")
588 backupDirectory, backupFilename,
590 QString tempDatabaseConfFile;
593 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Attempting backup, anyway.");
595 LOG(VB_GENERAL, LOG_ERR, QString(
"Backing up database with script: '%1'")
598 QString command = backupScript +
" " + scriptArgs +
" " +
599 tempDatabaseConfFile;
604 QByteArray tmpfile = tempDatabaseConfFile.toLocal8Bit();
605 unlink(tmpfile.constData());
610 LOG(VB_GENERAL, LOG_ERR,
LOC +
611 QString(
"Error backing up database: %1 (%2)")
612 .arg(command).arg(status));
617 LOG(VB_GENERAL, LOG_CRIT,
"Database Backup complete.");
619 QDir dir(backupDirectory, backupFilename +
"*");
620 uint numfiles = dir.count();
627 LOG(VB_FILE, LOG_ERR,
LOC +
628 QString(
"No files beginning with the suggested database backup "
629 "filename '%1' were found in '%2'.")
630 .arg(backupFilename, backupDirectory));
634 filename = dir.path() +
"/" + dir[0];;
637 LOG(VB_FILE, LOG_ERR,
LOC +
638 QString(
"Multiple files beginning with the suggested database "
639 "backup filename '%1' were found in '%2'. "
640 "Assuming the first is the backup.")
641 .arg(backupFilename, backupDirectory));
647 LOG(VB_GENERAL, LOG_CRIT, QString(
"Backed up database to file: '%1'")
667 QString compressCommand(
"");
668 QString extension =
".sql";
670 compressCommand =
"/bin/gzip";
672 compressCommand =
"/usr/bin/gzip";
674 LOG(VB_GENERAL, LOG_CRIT,
"Neither /bin/gzip nor /usr/bin/gzip exist. "
675 "The database backup will be uncompressed.");
678 dbParams.
m_dbName +
"-" + dbSchemaVer, extension);
679 QString backupPathname = backupDirectory +
"/" + backupFilename;
681 QString privateinfo = QString(
682 "[client]\npassword=%1\n[mysqldump]\npassword=%2\n")
684 QString tempExtraConfFile;
688 QString portArg =
"";
690 portArg = QString(
" --port='%1'").arg(dbParams.
m_dbPort);
691 command = QString(
"mysqldump --defaults-extra-file='%1' --host='%2'%3"
692 " --user='%4' --add-drop-table --add-locks"
693 " --allow-keywords --complete-insert"
694 " --extended-insert --lock-tables --no-create-db --quick"
695 " '%5' > '%6' 2>/dev/null")
700 LOG(VB_FILE, LOG_INFO, QString(
"Backing up database with command: '%1'")
702 LOG(VB_GENERAL, LOG_CRIT, QString(
"Backing up database to file: '%1'")
703 .arg(backupPathname));
707 QByteArray tmpfile = tempExtraConfFile.toLocal8Bit();
708 unlink(tmpfile.constData());
712 LOG(VB_GENERAL, LOG_ERR,
LOC +
713 QString(
"Error backing up database: '%1' (%2)")
714 .arg(command).arg(status));
719 if (compressCommand !=
"")
721 LOG(VB_GENERAL, LOG_CRIT,
"Compressing database backup file.");
722 compressCommand +=
" " + backupPathname;
727 LOG(VB_GENERAL, LOG_CRIT,
728 "Compression failed, backup file will remain uncompressed.");
732 backupPathname +=
".gz";
734 LOG(VB_GENERAL, LOG_CRIT, QString(
"Database Backup filename: '%1'")
735 .arg(backupPathname));
739 LOG(VB_GENERAL, LOG_CRIT,
"Database Backup complete.");
756 if (dbmsVersion.isEmpty())
759 query.
prepare(
"SELECT VERSION();");
762 LOG(VB_GENERAL, LOG_ERR,
LOC +
763 "Unable to determine MySQL version.");
769 dbmsVersion = query.
value(0).toString();
786 static const QRegularExpression parseVersion
787 { R
"(^(\d+)(?:\.(\d+)(?:\.(\d+))?)?)" };
789 if (!match.hasMatch())
811 LOG(VB_GENERAL, LOG_DEBUG,
"Not connected to DB");
815 if (!query.
exec(
"SHOW PROCESSLIST;"))
821 QSqlRecord record = query.
record();
822 int db_index = record.indexOf(
"db");
823 QString dbName =
GetMythDB()->GetDatabaseName();
828 inUseDB = query.
value(db_index).toString();
829 if (inUseDB == dbName)
835 count = (count + 3)/4;
837 LOG(VB_GENERAL, LOG_DEBUG,
838 QString(
"DBUtil::CountClients() found %1").arg(count));
848 query.
prepare(
"SELECT GET_LOCK('schemaLock', :TIMEOUT)");
849 query.
bindValue(
":TIMEOUT", timeout_secs);
855 query.
prepare(
"SELECT RELEASE_LOCK('schemaLock')");
868 query.
prepare(
"SELECT CONVERT_TZ(NOW(), 'SYSTEM', 'Etc/UTC')");
871 LOG(VB_GENERAL, LOG_ERR,
"MySQL time zone support check failed");
875 return !query.
value(0).isNull();
895 QString sql = QString(
"SELECT COUNT(*) FROM information_schema.columns "
896 "WHERE table_schema = DATABASE() AND "
897 "table_name = '%1' AND column_name = '%2';")
898 .arg(tableName, columnName);
899 LOG(VB_GENERAL, LOG_DEBUG,
900 QString(
"DBUtil::CheckTableColumnExists() SQL: %1").arg(sql));
902 if (!query.
exec(sql))
911 result = (query.
value(0).toInt() > 0);
915 LOG(VB_GENERAL, LOG_ERR,
916 QString(
"DBUtil::CheckTableColumnExists() - Empty result set"));
919 LOG(VB_GENERAL, LOG_DEBUG,
920 QString(
"DBUtil::CheckTableColumnExists('%1', '%2') result: %3").arg(tableName,
921 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.