276 lines
9.8 KiB
C++
276 lines
9.8 KiB
C++
// For license of this file, see <project-root-folder>/LICENSE.md.
|
|
|
|
#include "database/mariadbdriver.h"
|
|
|
|
#include "definitions/definitions.h"
|
|
#include "exceptions/applicationexception.h"
|
|
#include "miscellaneous/application.h"
|
|
#include "miscellaneous/settings.h"
|
|
|
|
#include <QDir>
|
|
#include <QSqlError>
|
|
#include <QSqlQuery>
|
|
|
|
MariaDbDriver::MariaDbDriver(QObject* parent) : DatabaseDriver(parent), m_databaseInitialized(false) {}
|
|
|
|
QString MariaDbDriver::ddlFilePrefix() const {
|
|
return QSL("mysql");
|
|
}
|
|
|
|
MariaDbDriver::MariaDbError MariaDbDriver::testConnection(const QString& hostname,
|
|
int port,
|
|
const QString& w_database,
|
|
const QString& username,
|
|
const QString& password) {
|
|
QSqlDatabase database = QSqlDatabase::addDatabase(QSL(APP_DB_MYSQL_DRIVER), QSL(APP_DB_MYSQL_TEST));
|
|
|
|
database.setHostName(hostname);
|
|
database.setPort(port);
|
|
database.setUserName(username);
|
|
database.setPassword(password);
|
|
database.setDatabaseName(w_database);
|
|
|
|
if (database.open() && !database.lastError().isValid()) {
|
|
QSqlQuery query(QSL("SELECT version();"), database);
|
|
|
|
if (!query.lastError().isValid() && query.next()) {
|
|
qDebugNN << LOGSEC_DB << "Checked MySQL database, version is" << QUOTE_W_SPACE_DOT(query.value(0).toString());
|
|
|
|
// Connection succeeded, clean up the mess and return OK status.
|
|
database.close();
|
|
return MariaDbError::Ok;
|
|
}
|
|
else {
|
|
database.close();
|
|
return MariaDbError::UnknownError;
|
|
}
|
|
}
|
|
else if (database.lastError().isValid()) {
|
|
auto nat = database.lastError().nativeErrorCode();
|
|
bool nat_converted = false;
|
|
auto nat_int = nat.toInt(&nat_converted);
|
|
|
|
if (nat_converted) {
|
|
return static_cast<MariaDbError>(nat_int);
|
|
}
|
|
else {
|
|
qWarningNN << LOGSEC_DB << "Failed to recognize MySQL error code:" << QUOTE_W_SPACE_DOT(nat);
|
|
|
|
return MariaDbError::UnknownError;
|
|
}
|
|
}
|
|
else {
|
|
return MariaDbError::UnknownError;
|
|
}
|
|
}
|
|
|
|
QString MariaDbDriver::location() const {
|
|
return QSL("%1/%2").arg(qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLHostname)).toString(),
|
|
qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLDatabase)).toString());
|
|
}
|
|
|
|
QString MariaDbDriver::interpretErrorCode(MariaDbDriver::MariaDbError error_code) const {
|
|
switch (error_code) {
|
|
case MariaDbError::Ok:
|
|
return tr("MySQL server works as expected.");
|
|
|
|
case MariaDbError::UnknownDatabase:
|
|
return tr("Selected database does not exist (yet). It will be created. It's okay.");
|
|
|
|
case MariaDbError::CantConnect:
|
|
case MariaDbError::ConnectionError:
|
|
case MariaDbError::UnknownHost:
|
|
return tr("No MySQL server is running in the target destination.");
|
|
|
|
case MariaDbError::AccessDenied:
|
|
return tr("Access denied. Invalid username or password used.");
|
|
|
|
default:
|
|
return tr("Unknown error: '%1'.").arg(int(error_code));
|
|
}
|
|
}
|
|
|
|
QString MariaDbDriver::humanDriverType() const {
|
|
return tr("MariaDB");
|
|
}
|
|
|
|
QString MariaDbDriver::qtDriverCode() const {
|
|
return QSL(APP_DB_MYSQL_DRIVER);
|
|
}
|
|
|
|
DatabaseDriver::DriverType MariaDbDriver::driverType() const {
|
|
return DatabaseDriver::DriverType::MySQL;
|
|
}
|
|
|
|
bool MariaDbDriver::vacuumDatabase() {
|
|
QSqlDatabase database = connection(objectName());
|
|
QSqlQuery query_vacuum(database);
|
|
|
|
return query_vacuum.exec(QSL("OPTIMIZE TABLE Feeds;")) && query_vacuum.exec(QSL("OPTIMIZE TABLE Messages;"));
|
|
}
|
|
|
|
bool MariaDbDriver::saveDatabase() {
|
|
return true;
|
|
}
|
|
|
|
void MariaDbDriver::backupDatabase(const QString& backup_folder, const QString& backup_name) {
|
|
Q_UNUSED(backup_folder)
|
|
Q_UNUSED(backup_name)
|
|
|
|
saveDatabase();
|
|
}
|
|
|
|
bool MariaDbDriver::initiateRestoration(const QString& database_package_file) {
|
|
Q_UNUSED(database_package_file)
|
|
return true;
|
|
}
|
|
|
|
bool MariaDbDriver::finishRestoration() {
|
|
return true;
|
|
}
|
|
|
|
qint64 MariaDbDriver::databaseDataSize() {
|
|
QSqlDatabase database = connection(metaObject()->className());
|
|
QSqlQuery query(database);
|
|
|
|
query.prepare("SELECT Round(Sum(data_length + index_length), 1) "
|
|
"FROM information_schema.tables "
|
|
"WHERE table_schema = :db "
|
|
"GROUP BY table_schema;");
|
|
query.bindValue(QSL(":db"), database.databaseName());
|
|
|
|
if (query.exec() && query.next()) {
|
|
return query.value(0).value<qint64>();
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
QSqlDatabase MariaDbDriver::initializeDatabase(const QString& connection_name) {
|
|
// Folders are created. Create new QSqlDatabase object.
|
|
QSqlDatabase database = QSqlDatabase::addDatabase(QSL(APP_DB_MYSQL_DRIVER), connection_name);
|
|
const QString database_name = qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLDatabase)).toString();
|
|
|
|
database.setHostName(qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLHostname)).toString());
|
|
database.setPort(qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLPort)).toInt());
|
|
database.setUserName(qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLUsername)).toString());
|
|
database.setPassword(qApp->settings()->password(GROUP(Database), SETTING(Database::MySQLPassword)).toString());
|
|
|
|
if (!database.open()) {
|
|
// NOTE: In this case throw exception and fallback SQL backend will be used.
|
|
throw ApplicationException(database.lastError().text());
|
|
}
|
|
else {
|
|
QSqlQuery query_db(database);
|
|
|
|
query_db.setForwardOnly(true);
|
|
setPragmas(query_db);
|
|
|
|
if (!query_db.exec(QSL("USE %1").arg(database_name)) ||
|
|
!query_db.exec(QSL("SELECT inf_value FROM Information WHERE inf_key = 'schema_version'"))) {
|
|
// If no "rssguard" database exists or schema version is wrong, then initialize it.
|
|
qWarningNN << LOGSEC_DB << "Error occurred. MySQL database is not initialized. Initializing now.";
|
|
|
|
try {
|
|
const QStringList statements = prepareScript(APP_SQL_PATH, QSL(APP_DB_MYSQL_INIT), database_name);
|
|
|
|
for (const QString& statement : statements) {
|
|
query_db.exec(statement);
|
|
|
|
if (query_db.lastError().isValid()) {
|
|
throw ApplicationException(query_db.lastError().text());
|
|
}
|
|
}
|
|
|
|
setSchemaVersion(query_db, QSL(APP_DB_SCHEMA_VERSION).toInt(), true);
|
|
}
|
|
catch (const ApplicationException& ex) {
|
|
qFatal("Error when running SQL scripts: %s.", qPrintable(ex.message()));
|
|
}
|
|
|
|
qDebugNN << LOGSEC_DB << "MySQL database backend should be ready now.";
|
|
}
|
|
else {
|
|
// Database was previously initialized. Now just check the schema version.
|
|
query_db.next();
|
|
const int installed_db_schema = query_db.value(0).toString().toInt();
|
|
|
|
if (installed_db_schema < QSL(APP_DB_SCHEMA_VERSION).toInt()) {
|
|
try {
|
|
updateDatabaseSchema(query_db, installed_db_schema, database_name);
|
|
qDebugNN << LOGSEC_DB << "Database schema was updated from" << QUOTE_W_SPACE(installed_db_schema) << "to"
|
|
<< QUOTE_W_SPACE(APP_DB_SCHEMA_VERSION) << "successully.";
|
|
}
|
|
catch (const ApplicationException& ex) {
|
|
qFatal("Error when updating DB schema from %d: %s.", installed_db_schema, qPrintable(ex.message()));
|
|
}
|
|
}
|
|
}
|
|
|
|
query_db.finish();
|
|
}
|
|
|
|
m_databaseInitialized = true;
|
|
return database;
|
|
}
|
|
|
|
void MariaDbDriver::setPragmas(QSqlQuery& query) {
|
|
query.exec(QSL("SET NAMES 'utf8mb4';"));
|
|
query.exec(QSL("SET CHARACTER SET utf8mb4;"));
|
|
}
|
|
|
|
QSqlDatabase MariaDbDriver::connection(const QString& connection_name,
|
|
DatabaseDriver::DesiredStorageType desired_type) {
|
|
Q_UNUSED(desired_type)
|
|
|
|
if (!m_databaseInitialized) {
|
|
// Return initialized database.
|
|
return initializeDatabase(connection_name);
|
|
}
|
|
else {
|
|
QSqlDatabase database;
|
|
|
|
if (QSqlDatabase::contains(connection_name)) {
|
|
qDebugNN << LOGSEC_DB << "MySQL connection '" << connection_name << "' is already active.";
|
|
|
|
// This database connection was added previously, no need to
|
|
// setup its properties.
|
|
database = QSqlDatabase::database(connection_name);
|
|
}
|
|
else {
|
|
// Database connection with this name does not exist
|
|
// yet, add it and set it up.
|
|
database = QSqlDatabase::addDatabase(QSL(APP_DB_MYSQL_DRIVER), connection_name);
|
|
database.setHostName(qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLHostname)).toString());
|
|
database.setPort(qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLPort)).toInt());
|
|
database.setUserName(qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLUsername)).toString());
|
|
database.setPassword(qApp->settings()->password(GROUP(Database), SETTING(Database::MySQLPassword)).toString());
|
|
database.setDatabaseName(qApp->settings()->value(GROUP(Database), SETTING(Database::MySQLDatabase)).toString());
|
|
}
|
|
|
|
if (!database.isOpen() && !database.open()) {
|
|
// NOTE: In this case throw exception and fallback SQL backend will be used.
|
|
throw ApplicationException(database.lastError().text());
|
|
}
|
|
else {
|
|
qDebugNN << LOGSEC_DB << "MySQL database connection" << QUOTE_W_SPACE(connection_name) << "to file"
|
|
<< QUOTE_W_SPACE(QDir::toNativeSeparators(database.databaseName())) << "seems to be established.";
|
|
}
|
|
|
|
QSqlQuery query_db(database);
|
|
|
|
query_db.setForwardOnly(true);
|
|
setPragmas(query_db);
|
|
|
|
return database;
|
|
}
|
|
}
|
|
|
|
QString MariaDbDriver::autoIncrementPrimaryKey() const {
|
|
return QSL("INTEGER AUTO_INCREMENT PRIMARY KEY");
|
|
}
|
|
|
|
QString MariaDbDriver::blob() const {
|
|
return QSL("MEDIUMBLOB");
|
|
}
|