Distinguish optimize SQL completion from integrity check results
This commit is contained in:
1 parent
6ba86bb1df
commit
65c6bbc03b
3 files changed
+45
-21
No files matched your search
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user