Added a quick integrity check during startup, fixes #679
This commit is contained in:
@@ -19,6 +19,9 @@
|
||||
|
||||
#include "database/Db.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <Wt/Dbo/FixedSqlConnectionPool.h>
|
||||
#include <Wt/Dbo/backend/Sqlite3.h>
|
||||
|
||||
@@ -70,21 +73,98 @@ namespace lms::db
|
||||
|
||||
std::filesystem::path _dbPath;
|
||||
};
|
||||
|
||||
enum class IntegrityCheckType
|
||||
{
|
||||
Quick,
|
||||
Full
|
||||
};
|
||||
bool checkDbIntegrity(Wt::Dbo::SqlConnection& connection, IntegrityCheckType checkType, std::function<void(std::string_view error)> errorCallback)
|
||||
{
|
||||
bool integrityCheckPassed{};
|
||||
|
||||
auto statement = connection.prepareStatement(checkType == IntegrityCheckType::Full ? "PRAGMA integrity_check" : "PRAGMA quick_check");
|
||||
statement->execute();
|
||||
|
||||
std::string result;
|
||||
result.reserve(32);
|
||||
while (statement->nextRow())
|
||||
{
|
||||
result.clear();
|
||||
statement->getResult(0, &result, result.capacity());
|
||||
|
||||
if (result == "ok")
|
||||
{
|
||||
integrityCheckPassed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
errorCallback(result);
|
||||
}
|
||||
|
||||
return integrityCheckPassed;
|
||||
}
|
||||
|
||||
bool checkDbForeignKeyConstraints(Wt::Dbo::SqlConnection& connection, std::function<void(std::string_view table, long long rowId, std::string_view referredTable)> errorCallback)
|
||||
{
|
||||
bool foreignKeyConstraintsPassed{ true };
|
||||
|
||||
auto statement = connection.prepareStatement("PRAGMA foreign_key_check");
|
||||
statement->execute();
|
||||
|
||||
std::string table;
|
||||
std::string foreignTable;
|
||||
// see https://www.sqlite.org/pragma.html#pragma_foreign_key_check for exepcted result
|
||||
while (statement->nextRow())
|
||||
{
|
||||
foreignKeyConstraintsPassed = false;
|
||||
|
||||
table.clear();
|
||||
foreignTable.clear();
|
||||
long long rowId{};
|
||||
|
||||
statement->getResult(0, &table, static_cast<int>(table.capacity()));
|
||||
statement->getResult(1, &rowId);
|
||||
statement->getResult(2, &foreignTable, static_cast<int>(foreignTable.capacity()));
|
||||
|
||||
errorCallback(table, rowId, foreignTable);
|
||||
}
|
||||
|
||||
return foreignKeyConstraintsPassed;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Session living class handling the database and the login
|
||||
Db::Db(const std::filesystem::path& dbPath, std::size_t connectionCount)
|
||||
{
|
||||
std::string checkType{ "quick" };
|
||||
LMS_LOG(DB, INFO, "Creating connection pool on file " << dbPath);
|
||||
|
||||
auto connection{ std::make_unique<Connection>(dbPath) };
|
||||
if (core::IConfig * config{ core::Service<core::IConfig>::get() }) // may not be here on testU
|
||||
{
|
||||
connection->setProperty("show-queries", config->getBool("db-show-queries", false) ? "true" : "false");
|
||||
checkType = config->getString("db-integrity-check", "quick");
|
||||
}
|
||||
|
||||
auto connectionPool{ std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), connectionCount) };
|
||||
connectionPool->setTimeout(std::chrono::seconds{ 10 });
|
||||
|
||||
_connectionPool = std::move(connectionPool);
|
||||
|
||||
if (checkType == "quick")
|
||||
{
|
||||
performQuickCheck();
|
||||
}
|
||||
else if (checkType == "full")
|
||||
{
|
||||
performIntegrityCheck();
|
||||
performForeignKeyConstraintsCheck();
|
||||
}
|
||||
else if (checkType != "none")
|
||||
{
|
||||
throw Exception("Invalid 'db-integrity-check' value: '" + checkType + "'. Expected 'quick', 'full' or 'none'.");
|
||||
}
|
||||
}
|
||||
|
||||
void Db::executeSql(const std::string& sql)
|
||||
@@ -114,6 +194,55 @@ namespace lms::db
|
||||
return *tlsSession;
|
||||
}
|
||||
|
||||
void Db::performQuickCheck()
|
||||
{
|
||||
ScopedConnection connection{ *_connectionPool };
|
||||
|
||||
LMS_LOG(DB, INFO, "Performing quick database check...");
|
||||
|
||||
// Quick check is just a simple integrity check
|
||||
bool quickCheckPassed{ checkDbIntegrity(*connection, IntegrityCheckType::Quick, [&](std::string_view error) {
|
||||
LMS_LOG(DB, ERROR, "Quick check error: " << error);
|
||||
}) };
|
||||
|
||||
if (quickCheckPassed)
|
||||
LMS_LOG(DB, INFO, "Quick database check passed!");
|
||||
else
|
||||
LMS_LOG(DB, ERROR, "Quick database check done with errors!");
|
||||
}
|
||||
|
||||
void Db::performIntegrityCheck()
|
||||
{
|
||||
ScopedConnection connection{ *_connectionPool };
|
||||
|
||||
LMS_LOG(DB, INFO, "Checking database integrity...");
|
||||
|
||||
bool integrityCheckPassed{ checkDbIntegrity(*connection, IntegrityCheckType::Full, [&](std::string_view error) {
|
||||
LMS_LOG(DB, ERROR, "Integrity check error: " << error);
|
||||
}) };
|
||||
|
||||
if (integrityCheckPassed)
|
||||
LMS_LOG(DB, INFO, "Database integrity check passed!");
|
||||
else
|
||||
LMS_LOG(DB, ERROR, "Database integrity check done with errors!");
|
||||
}
|
||||
|
||||
void Db::performForeignKeyConstraintsCheck()
|
||||
{
|
||||
ScopedConnection connection{ *_connectionPool };
|
||||
|
||||
LMS_LOG(DB, INFO, "Checking foreign key constraints...");
|
||||
|
||||
bool foreignKeyConstraintsPassed{ checkDbForeignKeyConstraints(*connection, [&](std::string_view table, long long rowId, std::string_view referredTable) {
|
||||
LMS_LOG(DB, ERROR, "Foreign key constraint failed in table '" << table << "', rowid = " << rowId << ", referred table = '" << referredTable << "'");
|
||||
}) };
|
||||
|
||||
if (!foreignKeyConstraintsPassed)
|
||||
throw Exception("Foreign key constraints check failed! Please restore from a backup or recreate the database.");
|
||||
|
||||
LMS_LOG(DB, INFO, "Foreign key constraints check passed!");
|
||||
}
|
||||
|
||||
Db::ScopedConnection::ScopedConnection(Wt::Dbo::SqlConnectionPool& pool)
|
||||
: _connectionPool{ pool }
|
||||
, _connection{ _connectionPool.getConnection() }
|
||||
|
||||
@@ -46,6 +46,10 @@ namespace lms::db
|
||||
core::RecursiveSharedMutex& getMutex() { return _sharedMutex; }
|
||||
Wt::Dbo::SqlConnectionPool& getConnectionPool() { return *_connectionPool; }
|
||||
|
||||
void performQuickCheck();
|
||||
void performIntegrityCheck();
|
||||
void performForeignKeyConstraintsCheck();
|
||||
|
||||
class ScopedConnection
|
||||
{
|
||||
public:
|
||||
@@ -53,6 +57,7 @@ namespace lms::db
|
||||
~ScopedConnection();
|
||||
|
||||
Wt::Dbo::SqlConnection* operator->() const;
|
||||
Wt::Dbo::SqlConnection& operator*() const { return *_connection; }
|
||||
|
||||
private:
|
||||
ScopedConnection(const ScopedConnection&) = delete;
|
||||
|
||||
Reference in New Issue
Block a user