Distinguish optimize SQL completion from integrity check results

This commit is contained in:
evgeny committed 2026-10-27 17:27:04 +01:00
1 parent 6ba86bb1df
commit 65c6bbc03b
3 files changed
+45 -21

No files matched your search

+32 -11
View File
@@ -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<void*>(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<DbManager*>(this), "PRAGMA quick_check");
return runPragma(m_db, "PRAGMA quick_check", PragmaMode::IntegrityCheck);
}
IntegrityResult DbManager::integrityCheckFull() const {
return runPragma(const_cast<DbManager*>(this), "PRAGMA integrity_check");
return runPragma(m_db, "PRAGMA integrity_check", PragmaMode::IntegrityCheck);
}
IntegrityResult DbManager::integrityOptimize() const {
return runPragma(const_cast<DbManager*>(this), "PRAGMA optimize");
return runPragma(m_db, "PRAGMA optimize", PragmaMode::Command);
}
+9 -7
View File
@@ -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;
@@ -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<int>("operation"); QTest::addColumn<bool>("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<QPushButton*>(); QCOMPARE(buttons.size(), 3);
for (auto* button : buttons) QVERIFY(!button->isEnabled());
QTRY_VERIFY_WITH_TIMEOUT(!statusText(page).isEmpty(), 1000);