diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp index 74095b19..90d0ec7a 100644 --- a/tools/chatgui/db/db_manager.cpp +++ b/tools/chatgui/db/db_manager.cpp @@ -700,6 +700,9 @@ QByteArray DbManager::syncListPeers(const QString& chId) const { /* ── integrity checks ── */ +enum class PragmaMode { IntegrityCheck, Command }; + +/* SQLite сообщает нарушения целостности строками даже при SQLITE_OK; сохраняем первое и дополнительные сообщения. */ static int integrityCallback(void* data, int nCols, char** values, char** /*names*/) { auto* result = (IntegrityResult*)data; for (int i = 0; i < nCols; i++) { @@ -712,37 +715,55 @@ static int integrityCallback(void* data, int nCols, char** values, char** /*name return 0; } -static IntegrityResult runPragma(DbManager* self, const char* pragma) { +/* Проверки требуют ответа ok; у команды обслуживания успех определяется завершением SQL, без обязательных строк. */ +static IntegrityResult runPragma(sqlite3* db, const char* pragma, PragmaMode mode) { IntegrityResult result; - if (!self->m_db) { - result.ok = false; + DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "DB maintenance begin operation=%s db=%p integrity=%d", + pragma, static_cast(db), mode == PragmaMode::IntegrityCheck); + if (!db) { result.message = "Database not open"; + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "DB maintenance failed operation=%s: database not open", pragma); return result; } QElapsedTimer timer; timer.start(); char* errMsg = nullptr; - int rc = sqlite3_exec(self->m_db, pragma, integrityCallback, &result, &errMsg); + const int rc = sqlite3_exec(db, pragma, mode == PragmaMode::IntegrityCheck ? integrityCallback : nullptr, &result, &errMsg); result.elapsedMs = (int)timer.elapsed(); if (rc != SQLITE_OK) { - result.ok = false; - result.message = QString::fromUtf8(errMsg ? errMsg : "unknown error"); + result.message = QString::fromUtf8(errMsg ? errMsg : sqlite3_errmsg(db)); + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "DB maintenance failed operation=%s rc=%d elapsed=%dms: %s", + pragma, rc, result.elapsedMs, qPrintable(result.message)); sqlite3_free(errMsg); return result; } - if (errMsg) sqlite3_free(errMsg); - result.ok = (result.message == "ok" && result.errors.isEmpty()); + sqlite3_free(errMsg); + if (mode == PragmaMode::Command) { + result.ok = true; + result.message = "ok"; + } else { + result.ok = result.message == "ok" && result.errors.isEmpty(); + if (result.message.isEmpty()) result.message = "Integrity check returned no result"; + } + if (result.ok) { + DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "DB maintenance complete operation=%s rc=%d elapsed=%dms result=ok", pragma, rc, result.elapsedMs); + } else { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "DB maintenance failed operation=%s rc=%d elapsed=%dms extra_errors=%d: %s", + pragma, rc, result.elapsedMs, int(result.errors.size()), qPrintable(result.message)); + for (const auto& error : result.errors) + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "DB maintenance detail operation=%s: %s", pragma, qPrintable(error)); + } return result; } IntegrityResult DbManager::integrityCheckQuick() const { - return runPragma(const_cast(this), "PRAGMA quick_check"); + return runPragma(m_db, "PRAGMA quick_check", PragmaMode::IntegrityCheck); } IntegrityResult DbManager::integrityCheckFull() const { - return runPragma(const_cast(this), "PRAGMA integrity_check"); + return runPragma(m_db, "PRAGMA integrity_check", PragmaMode::IntegrityCheck); } IntegrityResult DbManager::integrityOptimize() const { - return runPragma(const_cast(this), "PRAGMA optimize"); + return runPragma(m_db, "PRAGMA optimize", PragmaMode::Command); } diff --git a/tools/chatgui/db/db_manager.h b/tools/chatgui/db/db_manager.h index cac53f08..dd895a8f 100644 --- a/tools/chatgui/db/db_manager.h +++ b/tools/chatgui/db/db_manager.h @@ -69,11 +69,12 @@ struct NodeAddr { int socketId = 0; // socket_id из ETCP }; +/* Результат синхронной проверки/обслуживания SQLite; успех optimize не подтверждает целостность данных. */ struct IntegrityResult { - bool ok = false; - QString message; - QStringList errors; - int elapsedMs = 0; + bool ok = false; // Проверка: SQL успешен и получен ok без нарушений; optimize: SQL завершён успешно. + QString message; // ok при успехе, иначе первая диагностика проверки либо ошибка SQL/отсутствия БД. + QStringList errors; // Дополнительные сообщения integrity-проверки после первого. + int elapsedMs = 0; // Время SQL-вызова, мс; 0 также при отсутствии handle. }; struct DmConversation { @@ -160,9 +161,10 @@ public: static QString sanitizeTableName(const QString& chId); static uint64_t computeDatahash(const QByteArray& data); - IntegrityResult integrityCheckQuick() const; - IntegrityResult integrityCheckFull() const; - IntegrityResult integrityOptimize() const; + /* Синхронные PRAGMA на заимствованном handle; вызывающий сохраняет его живым до возврата, ошибки логируются в CONFIG. */ + IntegrityResult integrityCheckQuick() const; // quick_check: требует ответ ok, нарушения в строках SQLite означают отказ. + IntegrityResult integrityCheckFull() const; // integrity_check: тот же контракт, полная проверка со сверкой индексов. + IntegrityResult integrityOptimize() const; // optimize: SQL-успех допустим без строк результата; не является integrity-проверкой. sqlite3* m_db = nullptr; QString m_dbPath; diff --git a/tools/chatgui/tests/test_database_maintenance.cpp b/tools/chatgui/tests/test_database_maintenance.cpp index 5f8d8dde..fe2c599e 100644 --- a/tools/chatgui/tests/test_database_maintenance.cpp +++ b/tools/chatgui/tests/test_database_maintenance.cpp @@ -10,7 +10,7 @@ extern "C" { using Operation = IntegrityResult (DbManager::*)() const; static const Operation operations[] = {&DbManager::integrityCheckQuick, &DbManager::integrityCheckFull, &DbManager::integrityOptimize}; static const char* titles[] = {"Quick Check", "Full Check", "Optimize"}; -static const char* slots[] = {"onQuickCheck", "onFullCheck", "onOptimize"}; +static const char* actionSlots[] = {"onQuickCheck", "onFullCheck", "onOptimize"}; struct Database { QTemporaryDir directory; @@ -112,14 +112,15 @@ private slots: QTest::addColumn("operation"); QTest::addColumn("denied"); for (int operation = 0; operation < 3; ++operation) for (bool denied : {false, true}) - QTest::newRow(qPrintable(QString("%1-%2").arg(titles[operation]).arg(denied ? "denied" : "success"))) << operation << denied; + QTest::newRow(qPrintable(QString("%1-%2").arg(titles[operation]).arg(denied ? "denied" : "success"))) + << operation << denied; } void pageShowsResult() { QFETCH(int, operation); QFETCH(bool, denied); Database db; QVERIFY(db.open()); DatabaseSettingsPage page(&db.manager); if (denied) QCOMPARE(sqlite3_set_authorizer(db.handle, denyPragma, nullptr), SQLITE_OK); - QVERIFY(QMetaObject::invokeMethod(&page, slots[operation], Qt::DirectConnection)); + QVERIFY(QMetaObject::invokeMethod(&page, actionSlots[operation], Qt::DirectConnection)); const auto buttons = page.findChildren(); QCOMPARE(buttons.size(), 3); for (auto* button : buttons) QVERIFY(!button->isEnabled()); QTRY_VERIFY_WITH_TIMEOUT(!statusText(page).isEmpty(), 1000);