mirror of https://github.com/qTox/qTox.git
Browse Source
So that we can have multiple db upgrade tests that can run in parallel, instead of one monster test that runs independent test cases sequentially.reviewable/pr6583/r13
5 changed files with 267 additions and 165 deletions
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
# Copyright © 2022 by The qTox Project Contributors |
||||
# |
||||
# This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
# qTox is libre software: you can redistribute it and/or modify |
||||
# it under the terms of the GNU General Public License as published by |
||||
# the Free Software Foundation, either version 3 of the License, or |
||||
# (at your option) any later version. |
||||
# |
||||
# qTox is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with qTox. If not, see <http://www.gnu.org/licenses/> |
||||
|
||||
add_library(dbutility_library STATIC |
||||
include/dbutility/dbutility.h |
||||
src/dbutility.cpp) |
||||
|
||||
target_include_directories(dbutility_library PUBLIC include/) |
||||
target_link_libraries(dbutility_library |
||||
Qt5::Core |
||||
Qt5::Test |
||||
util_library |
||||
qtox::warnings) |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright © 2022 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
qTox is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <QString> |
||||
|
||||
#include <array> |
||||
#include <memory> |
||||
#include <vector> |
||||
|
||||
class RawDatabase; |
||||
|
||||
namespace DbUtility |
||||
{ |
||||
struct SqliteMasterEntry { |
||||
QString name; |
||||
QString sql; |
||||
bool operator==(const DbUtility::SqliteMasterEntry& rhs) const; |
||||
}; |
||||
|
||||
extern const std::array<QString, 11> testFileList; |
||||
extern const std::vector<SqliteMasterEntry> schema0; |
||||
extern const std::vector<SqliteMasterEntry> schema1; |
||||
extern const std::vector<SqliteMasterEntry> schema2; |
||||
extern const std::vector<SqliteMasterEntry> schema3; |
||||
extern const std::vector<SqliteMasterEntry> schema4; |
||||
extern const std::vector<SqliteMasterEntry> schema5; |
||||
extern const std::vector<SqliteMasterEntry> schema6; |
||||
extern const std::vector<SqliteMasterEntry> schema7; |
||||
extern const std::vector<SqliteMasterEntry> schema9; |
||||
extern const std::vector<SqliteMasterEntry> schema10; |
||||
|
||||
void createSchemaAtVersion(std::shared_ptr<RawDatabase> db, const std::vector<DbUtility::SqliteMasterEntry>& schema); |
||||
void verifyDb(std::shared_ptr<RawDatabase> db, const std::vector<DbUtility::SqliteMasterEntry>& expectedSql); |
||||
} |
@ -0,0 +1,165 @@
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright © 2019 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
qTox is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include "dbutility/dbutility.h" |
||||
#include "src/persistence/db/rawdatabase.h" |
||||
|
||||
#include <QTest> |
||||
|
||||
#include <array> |
||||
#include <algorithm> |
||||
#include <vector> |
||||
|
||||
const std::array<QString,11> DbUtility::testFileList = |
||||
{"testCreation.db", "testIsNewDbTrue.db", "testIsNewDbFalse.db", |
||||
"test0to1.db", "test1to2.db", "test2to3.db", |
||||
"test3to4.db", "test4to5.db", "test5to6.db", |
||||
"test6to7.db", "test9to10.db"}; |
||||
|
||||
// db schemas can be select with "SELECT name, sql FROM sqlite_master;" on the database.
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema0 { |
||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"}, |
||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"}, |
||||
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL)"}, |
||||
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"} |
||||
}; |
||||
|
||||
// added file transfer history
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema1 { |
||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"}, |
||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"}, |
||||
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"}, |
||||
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER)"}, |
||||
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"} |
||||
}; |
||||
|
||||
// move stuck faux offline messages do a table of "broken" messages
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema2 { |
||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"}, |
||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"}, |
||||
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"}, |
||||
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER)"}, |
||||
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"}, |
||||
{"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY)"} |
||||
}; |
||||
|
||||
// move stuck 0-length action messages to the existing "broken_messages" table. Not a real schema upgrade.
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema3 = DbUtility::schema2; |
||||
|
||||
// create index in history table on chat_id to improve query speed. Not a real schema upgrade.
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema4 { |
||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name))"}, |
||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY)"}, |
||||
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"}, |
||||
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER)"}, |
||||
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"}, |
||||
{"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY)"}, |
||||
{"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"} |
||||
}; |
||||
|
||||
// added foreign keys
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema5 { |
||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name), FOREIGN KEY (owner) REFERENCES peers(id))"}, |
||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY, FOREIGN KEY (id) REFERENCES history(id))"}, |
||||
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"}, |
||||
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER, FOREIGN KEY (file_id) REFERENCES file_transfers(id), FOREIGN KEY (chat_id) REFERENCES peers(id), FOREIGN KEY (sender_alias) REFERENCES aliases(id))"}, |
||||
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"}, |
||||
{"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY, FOREIGN KEY (id) REFERENCES history(id))"}, |
||||
{"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"} |
||||
}; |
||||
|
||||
// added toxext extensions
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema6 { |
||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name), FOREIGN KEY (owner) REFERENCES peers(id))"}, |
||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY, required_extensions INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (id) REFERENCES history(id))"}, |
||||
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"}, |
||||
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER, FOREIGN KEY (file_id) REFERENCES file_transfers(id), FOREIGN KEY (chat_id) REFERENCES peers(id), FOREIGN KEY (sender_alias) REFERENCES aliases(id))"}, |
||||
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"}, |
||||
{"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY, reason INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (id) REFERENCES history(id))"}, |
||||
{"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"} |
||||
}; |
||||
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema7{ |
||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB " |
||||
"NOT NULL, UNIQUE(owner, display_name), FOREIGN KEY (owner) REFERENCES peers(id))"}, |
||||
{"faux_offline_pending", |
||||
"CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY, required_extensions INTEGER NOT " |
||||
"NULL DEFAULT 0, FOREIGN KEY (id) REFERENCES history(id))"}, |
||||
{"file_transfers", |
||||
"CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, message_type CHAR(1) NOT NULL CHECK " |
||||
"(message_type = 'F'), sender_alias INTEGER NOT NULL, file_restart_id BLOB NOT NULL, " |
||||
"file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER " |
||||
"NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL, FOREIGN KEY (id, " |
||||
"message_type) REFERENCES history(id, message_type), FOREIGN KEY (sender_alias) REFERENCES " |
||||
"aliases(id))"}, |
||||
{"history", |
||||
"CREATE TABLE history (id INTEGER PRIMARY KEY, message_type CHAR(1) NOT NULL DEFAULT 'T' " |
||||
"CHECK (message_type in ('T','F','S')), timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, " |
||||
"UNIQUE (id, message_type), FOREIGN KEY (chat_id) REFERENCES peers(id))"}, |
||||
{"text_messages", "CREATE TABLE text_messages (id INTEGER PRIMARY KEY, message_type CHAR(1) " |
||||
"NOT NULL CHECK (message_type = 'T'), sender_alias INTEGER NOT NULL, message " |
||||
"BLOB NOT NULL, FOREIGN KEY (id, message_type) REFERENCES history(id, " |
||||
"message_type), FOREIGN KEY (sender_alias) REFERENCES aliases(id))"}, |
||||
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"}, |
||||
{"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY, reason INTEGER NOT " |
||||
"NULL DEFAULT 0, FOREIGN KEY (id) REFERENCES history(id))"}, |
||||
{"system_messages", |
||||
"CREATE TABLE system_messages (id INTEGER PRIMARY KEY, message_type CHAR(1) NOT NULL CHECK " |
||||
"(message_type = 'S'), system_message_type INTEGER NOT NULL, arg1 BLOB, arg2 BLOB, arg3 BLOB, arg4 BLOB, " |
||||
"FOREIGN KEY (id, message_type) REFERENCES history(id, message_type))"}, |
||||
{"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"}}; |
||||
|
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema9 = DbUtility::schema7; |
||||
const std::vector<DbUtility::SqliteMasterEntry> DbUtility::schema10 = DbUtility::schema9; |
||||
|
||||
void DbUtility::createSchemaAtVersion(std::shared_ptr<RawDatabase> db, const std::vector<DbUtility::SqliteMasterEntry>& schema) |
||||
{ |
||||
QVector<RawDatabase::Query> queries; |
||||
for (auto const& entry : schema) { |
||||
queries += entry.sql; |
||||
} |
||||
QVERIFY(db->execNow(queries)); |
||||
} |
||||
|
||||
bool DbUtility::SqliteMasterEntry::operator==(const DbUtility::SqliteMasterEntry& rhs) const |
||||
{ |
||||
return name == rhs.name && |
||||
sql == rhs.sql; |
||||
} |
||||
|
||||
void DbUtility::verifyDb(std::shared_ptr<RawDatabase> db, const std::vector<DbUtility::SqliteMasterEntry>& expectedSql) |
||||
{ |
||||
QVERIFY(db->execNow(RawDatabase::Query(QStringLiteral( |
||||
"SELECT name, sql FROM sqlite_master;"), |
||||
[&](const QVector<QVariant>& row) { |
||||
const QString tableName = row[0].toString(); |
||||
if (row[1].isNull()) { |
||||
// implicit indexes are automatically created for primary key constraints and unique constraints
|
||||
// so their existence is already covered by the table creation SQL
|
||||
return; |
||||
} |
||||
QString tableSql = row[1].toString(); |
||||
// table and column names can be quoted. UPDATE TEABLE automatically quotes the new names, but this
|
||||
// has no functional impact on the schema. Strip quotes for comparison so that our created schema
|
||||
// matches schema made from UPDATE TABLEs.
|
||||
const QString unquotedTableSql = tableSql.remove("\""); |
||||
SqliteMasterEntry entry{tableName, unquotedTableSql}; |
||||
QVERIFY(std::find(expectedSql.begin(), expectedSql.end(), entry) != expectedSql.end()); |
||||
}))); |
||||
} |
Loading…
Reference in new issue