This commit is contained in:
Martin Rotter 2021-03-05 13:32:16 +01:00
parent be07b2d514
commit 654997227b
7 changed files with 370 additions and 55 deletions

View File

@ -12,7 +12,9 @@
DatabaseDriver::DatabaseDriver(QObject* parent) : QObject(parent) DatabaseDriver::DatabaseDriver(QObject* parent) : QObject(parent)
{} {}
QStringList DatabaseDriver::prepareScript(const QString& base_sql_folder, const QString& sql_file, const QString& database_name) { QStringList DatabaseDriver::prepareScript(const QString& base_sql_folder,
const QString& sql_file,
const QString& database_name) {
QStringList statements; QStringList statements;
auto next_file = base_sql_folder + QDir::separator() + sql_file; auto next_file = base_sql_folder + QDir::separator() + sql_file;
QString sql_script = QString::fromUtf8(IOFactory::readFile(next_file)); QString sql_script = QString::fromUtf8(IOFactory::readFile(next_file));
@ -30,11 +32,11 @@ QStringList DatabaseDriver::prepareScript(const QString& base_sql_folder, const
auto included_file = base_sql_folder + QDir::separator() + included_file_name; auto included_file = base_sql_folder + QDir::separator() + included_file_name;
QString included_sql_script = QString::fromUtf8(IOFactory::readFile(included_file)); QString included_sql_script = QString::fromUtf8(IOFactory::readFile(included_file));
auto included_statements = sql_script.split(APP_DB_COMMENT_SPLIT, auto included_statements = included_sql_script.split(APP_DB_COMMENT_SPLIT,
#if QT_VERSION >= 0x050F00 // Qt >= 5.15.0 #if QT_VERSION >= 0x050F00 // Qt >= 5.15.0
Qt::SplitBehaviorFlags::SkipEmptyParts); Qt::SplitBehaviorFlags::SkipEmptyParts);
#else #else
QString::SplitBehavior::SkipEmptyParts); QString::SplitBehavior::SkipEmptyParts);
#endif #endif
statements << included_statements; statements << included_statements;

View File

@ -3,6 +3,7 @@
#include "database/databasefactory.h" #include "database/databasefactory.h"
#include "3rd-party/boolinq/boolinq.h" #include "3rd-party/boolinq/boolinq.h"
#include "database/mariadbdriver.h"
#include "database/sqlitedriver.h" #include "database/sqlitedriver.h"
#include "gui/messagebox.h" #include "gui/messagebox.h"
#include "miscellaneous/application.h" #include "miscellaneous/application.h"
@ -19,28 +20,6 @@ DatabaseFactory::DatabaseFactory(QObject* parent)
determineDriver(); determineDriver();
} }
/*else if (m_activeDatabaseDriver == DatabaseDriver::UsedDriver::MYSQL) {
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className(),
DatabaseDriver::DesiredType::FromSettings);
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;
}
}
else {
return 0;
}*/
void DatabaseFactory::removeConnection(const QString& connection_name) { void DatabaseFactory::removeConnection(const QString& connection_name) {
qDebugNN << LOGSEC_DB << "Removing database connection '" << connection_name << "'."; qDebugNN << LOGSEC_DB << "Removing database connection '" << connection_name << "'.";
QSqlDatabase::removeDatabase(connection_name); QSqlDatabase::removeDatabase(connection_name);
@ -52,7 +31,7 @@ void DatabaseFactory::determineDriver() {
}; };
if (QSqlDatabase::isDriverAvailable(APP_DB_MYSQL_DRIVER)) { if (QSqlDatabase::isDriverAvailable(APP_DB_MYSQL_DRIVER)) {
//m_allDbDrivers.append(new MariaDbDriver(this)); m_allDbDrivers.append(new MariaDbDriver(this));
} }
const QString db_driver = qApp->settings()->value(GROUP(Database), SETTING(Database::ActiveDriver)).toString(); const QString db_driver = qApp->settings()->value(GROUP(Database), SETTING(Database::ActiveDriver)).toString();

View File

@ -13,19 +13,6 @@ class DatabaseFactory : public QObject {
Q_OBJECT Q_OBJECT
public: public:
// Describes possible MySQL-specific errors.
enum class MySQLError {
Ok = 0,
UnknownError = 1,
AccessDenied = 1045,
UnknownDatabase = 1049,
ConnectionError = 2002,
CantConnect = 2003,
UnknownHost = 2005
};
// Constructor.
explicit DatabaseFactory(QObject* parent = nullptr); explicit DatabaseFactory(QObject* parent = nullptr);
// Removes connection. // Removes connection.

View File

@ -0,0 +1,298 @@
// 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) {}
MariaDbDriver::MariaDbError MariaDbDriver::testConnection(const QString& hostname, int port,
const QString& w_database, const QString& username,
const QString& password) {
QSqlDatabase database = QSqlDatabase::addDatabase(APP_DB_MYSQL_DRIVER, 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::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 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)
}
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(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()) {
qFatal("Cannot open MySQL database: %s.", qPrintable(database.lastError().text()));
}
else {
QSqlQuery query_db(database);
query_db.setForwardOnly(true);
if (!query_db.exec(QString("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, 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());
}
}
}
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 QString installed_db_schema = query_db.value(0).toString();
if (installed_db_schema.toInt() < QString(APP_DB_SCHEMA_VERSION).toInt()) {
if (updateDatabaseSchema(database, installed_db_schema, database_name)) {
qDebugNN << LOGSEC_DB
<< "Database schema was updated from '"
<< installed_db_schema
<< "' to '"
<< APP_DB_SCHEMA_VERSION
<< "' successully or it is already up to date.";
}
else {
qFatal("Database schema was not updated from '%s' to '%s' successully.",
qPrintable(installed_db_schema),
APP_DB_SCHEMA_VERSION);
}
}
}
query_db.finish();
}
// Everything is initialized now.
m_databaseInitialized = true;
return database;
}
bool MariaDbDriver::updateDatabaseSchema(const QSqlDatabase& database,
const QString& source_db_schema_version,
const QString& database_name) {
int working_version = QString(source_db_schema_version).remove('.').toInt();
const int current_version = QString(APP_DB_SCHEMA_VERSION).remove('.').toInt();
while (working_version != current_version) {
try {
const QStringList statements = prepareScript(APP_SQL_PATH,
QString(APP_DB_UPDATE_FILE_PATTERN).arg(QSL("mysql"),
QString::number(working_version),
QString::number(working_version + 1)),
database_name);
for (const QString& statement : statements) {
QSqlQuery query = database.exec(statement);
if (!query.exec(statement) && query.lastError().isValid()) {
throw ApplicationException(query.lastError().text());
}
}
}
catch (const ApplicationException& ex) {
qFatal("Error when running SQL scripts: %s.", qPrintable(ex.message()));
}
// Increment the version.
qDebugNN << LOGSEC_DB
<< "Updating database schema: '"
<< working_version
<< "' -> '"
<< working_version + 1
<< "'.";
working_version++;
}
return true;
}
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(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()) {
qFatal("MySQL database was NOT opened. Delivered error message: '%s'.",
qPrintable(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.";
}
return database;
}
}

View File

@ -0,0 +1,49 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef MARIADBDRIVER_H
#define MARIADBDRIVER_H
#include "database/databasedriver.h"
class MariaDbDriver : public DatabaseDriver {
public:
enum class MariaDbError {
Ok = 0,
UnknownError = 1,
AccessDenied = 1045,
UnknownDatabase = 1049,
ConnectionError = 2002,
CantConnect = 2003,
UnknownHost = 2005
};
explicit MariaDbDriver(QObject* parent = nullptr);
MariaDbError testConnection(const QString& hostname, int port, const QString& w_database,
const QString& username, const QString& password);
virtual QString humanDriverType() const;
virtual QString qtDriverCode() const;
virtual DriverType driverType() const;
virtual bool vacuumDatabase();
virtual bool saveDatabase();
virtual void backupDatabase(const QString& backup_folder, const QString& backup_name);
virtual bool initiateRestoration(const QString& database_package_file);
virtual bool finishRestoration();
virtual qint64 databaseDataSize();
virtual QSqlDatabase connection(const QString& connection_name,
DatabaseDriver::DesiredStorageType desired_type = DatabaseDriver::DesiredStorageType::FromSettings);
QString interpretErrorCode(MariaDbError error_code) const;
private:
bool updateDatabaseSchema(const QSqlDatabase& database,
const QString& source_db_schema_version,
const QString& database_name);
QSqlDatabase initializeDatabase(const QString& connection_name);
private:
bool m_databaseInitialized;
};
#endif // MARIADBDRIVER_H

View File

@ -3,6 +3,7 @@
#include "gui/settings/settingsdatabase.h" #include "gui/settings/settingsdatabase.h"
#include "database/databasefactory.h" #include "database/databasefactory.h"
#include "database/mariadbdriver.h"
#include "definitions/definitions.h" #include "definitions/definitions.h"
#include "gui/guiutilities.h" #include "gui/guiutilities.h"
#include "miscellaneous/application.h" #include "miscellaneous/application.h"
@ -44,27 +45,24 @@ SettingsDatabase::~SettingsDatabase() {
} }
void SettingsDatabase::mysqlTestConnection() { void SettingsDatabase::mysqlTestConnection() {
// TODO: TODO MariaDbDriver* driv = static_cast<MariaDbDriver*>(qApp->database()->driver());
const MariaDbDriver::MariaDbError error_code = driv->testConnection(m_ui->m_txtMysqlHostname->lineEdit()->text(),
m_ui->m_spinMysqlPort->value(),
m_ui->m_txtMysqlDatabase->lineEdit()->text(),
m_ui->m_txtMysqlUsername->lineEdit()->text(),
m_ui->m_txtMysqlPassword->lineEdit()->text());
const QString interpretation = driv->interpretErrorCode(error_code);
/* switch (error_code) {
const DatabaseFactory::MySQLError error_code = qApp->database()->driver()->mysqlTestConnection(m_ui->m_txtMysqlHostname->lineEdit()->text(), case MariaDbDriver::MariaDbError::Ok:
m_ui->m_spinMysqlPort->value(), case MariaDbDriver::MariaDbError::UnknownDatabase:
m_ui->m_txtMysqlDatabase->lineEdit()->text(),
m_ui->m_txtMysqlUsername->lineEdit()->text(),
m_ui->m_txtMysqlPassword->lineEdit()->text());
const QString interpretation = qApp->database()->driver()->mysqlInterpretErrorCode(error_code);
switch (error_code) {
case DatabaseFactory::MySQLError::Ok:
case DatabaseFactory::MySQLError::UnknownDatabase:
m_ui->m_lblMysqlTestResult->setStatus(WidgetWithStatus::StatusType::Ok, interpretation, interpretation); m_ui->m_lblMysqlTestResult->setStatus(WidgetWithStatus::StatusType::Ok, interpretation, interpretation);
break; break;
default: default:
m_ui->m_lblMysqlTestResult->setStatus(WidgetWithStatus::StatusType::Error, interpretation, interpretation); m_ui->m_lblMysqlTestResult->setStatus(WidgetWithStatus::StatusType::Error, interpretation, interpretation);
break; break;
} }
*/
} }
void SettingsDatabase::onMysqlHostnameChanged(const QString& new_hostname) { void SettingsDatabase::onMysqlHostnameChanged(const QString& new_hostname) {

View File

@ -47,6 +47,7 @@ HEADERS += core/feeddownloader.h \
database/databasedriver.h \ database/databasedriver.h \
database/databasefactory.h \ database/databasefactory.h \
database/databasequeries.h \ database/databasequeries.h \
database/mariadbdriver.h \
database/sqlitedriver.h \ database/sqlitedriver.h \
definitions/definitions.h \ definitions/definitions.h \
definitions/typedefs.h \ definitions/typedefs.h \
@ -228,6 +229,7 @@ SOURCES += core/feeddownloader.cpp \
database/databasedriver.cpp \ database/databasedriver.cpp \
database/databasefactory.cpp \ database/databasefactory.cpp \
database/databasequeries.cpp \ database/databasequeries.cpp \
database/mariadbdriver.cpp \
database/sqlitedriver.cpp \ database/sqlitedriver.cpp \
dynamic-shortcuts/dynamicshortcuts.cpp \ dynamic-shortcuts/dynamicshortcuts.cpp \
dynamic-shortcuts/dynamicshortcutswidget.cpp \ dynamic-shortcuts/dynamicshortcutswidget.cpp \