qTox/src/persistence/db/upgrades/dbto11.cpp

331 lines
15 KiB
C++

/*
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/>.
*/
#include "dbto11.h"
#include "src/core/toxpk.h"
#include "src/persistence/db/upgrades/dbupgrader.h"
#include <QDebug>
bool DbTo11::dbSchema10to11(RawDatabase& db)
{
QVector<RawDatabase::Query> upgradeQueries;
if (!appendDeduplicatePeersQueries(db, upgradeQueries)) {
return false;
}
if (!db.execNow(upgradeQueries)) {
return false;
}
upgradeQueries.clear();
if (!appendSplitPeersQueries(db, upgradeQueries)) {
return false;
}
upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 11;"));
bool transactionPass = db.execNow(upgradeQueries);
if (transactionPass) {
return db.execNow("VACUUM");
}
return transactionPass;
}
bool DbTo11::appendDeduplicatePeersQueries(RawDatabase& db, QVector<RawDatabase::Query>& upgradeQueries)
{
std::vector<DbUpgrader::BadEntry> badPeers;
if (!getInvalidPeers(db, badPeers)) {
return false;
}
DbUpgrader::mergeDuplicatePeers(upgradeQueries, db, badPeers);
return true;
}
bool DbTo11::getInvalidPeers(RawDatabase& db, std::vector<DbUpgrader::BadEntry>& badPeers)
{
return db.execNow(
RawDatabase::Query("SELECT id, public_key FROM peers WHERE CAST(public_key AS BLOB) != CAST(UPPER(public_key) AS BLOB)",
[&](const QVector<QVariant>& row) {
badPeers.emplace_back(DbUpgrader::BadEntry{row[0].toInt(), row[1].toString()});
}));
}
bool DbTo11::appendSplitPeersQueries(RawDatabase& db, QVector<RawDatabase::Query>& upgradeQueries)
{
// splitting peers table into separate chat and authors table.
// peers had a dual-meaning of each friend having their own chat, but also each peer
// being authors of messages. This wasn't true for self which was in the peers table
// but had no related chat. For upcomming group history, the confusion is amplified
// since each group is a chat so is in peers, but authors no messages, and each group
// member is an author so is in peers, but has no chat.
// Splitting peers makes the relation much clearer and the tables have a single meaning.
if (!PeersToAuthors::appendPeersToAuthorsQueries(db, upgradeQueries)) {
return false;
}
if (!PeersToChats::appendPeersToChatsQueries(db, upgradeQueries)) {
return false;
}
appendDropPeersQueries(upgradeQueries);
return true;
}
bool DbTo11::PeersToAuthors::appendPeersToAuthorsQueries(RawDatabase& db, QVector<RawDatabase::Query>& upgradeQueries)
{
appendCreateNewTablesQueries(upgradeQueries);
if (!appendPopulateAuthorQueries(db, upgradeQueries)) {
return false;
}
if (!appendUpdateAliasesFkQueries(db, upgradeQueries)) {
return false;
}
appendUpdateTextMessagesFkQueries(upgradeQueries);
appendUpdateFileTransfersFkQueries(upgradeQueries);
appendReplaceOldTablesQueries(upgradeQueries);
return true;
}
void DbTo11::PeersToAuthors::appendCreateNewTablesQueries(QVector<RawDatabase::Query>& upgradeQueries)
{
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE authors (id INTEGER PRIMARY KEY, "
"public_key BLOB NOT NULL UNIQUE);"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE aliases_new ("
"id INTEGER PRIMARY KEY, "
"owner INTEGER, "
"display_name BLOB NOT NULL, "
"UNIQUE(owner, display_name), "
"FOREIGN KEY (owner) REFERENCES authors(id));"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE text_messages_new "
"(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_new(id));"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE file_transfers_new "
"(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_new(id));"));
}
bool DbTo11::PeersToAuthors::appendPopulateAuthorQueries(RawDatabase &db, QVector<RawDatabase::Query> &upgradeQueries)
{
// don't copy over directly, so that we can convert from string to blob
return db.execNow(RawDatabase::Query(QStringLiteral(
"SELECT DISTINCT peers.public_key FROM peers JOIN aliases ON peers.id=aliases.owner"),
[&](const QVector<QVariant>& row) {
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO authors (public_key) VALUES (?)"),
{ToxPk{row[0].toString()}.getByteArray()});
}
));
}
bool DbTo11::PeersToAuthors::appendUpdateAliasesFkQueries(RawDatabase &db, QVector<RawDatabase::Query>& upgradeQueries)
{
return db.execNow(RawDatabase::Query(QStringLiteral(
"SELECT id, public_key FROM peers"),
[&](const QVector<QVariant>& row) {
const auto oldPeerId = row[0].toLongLong();
const auto pk = ToxPk{row[1].toString()};
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO aliases_new (id, owner, display_name) "
"SELECT id, "
"(SELECT id FROM authors WHERE public_key = ?), "
"display_name "
"FROM aliases WHERE "
"owner = '%1';").arg(oldPeerId),
{pk.getByteArray()});
}));
}
void DbTo11::PeersToAuthors::appendReplaceOldTablesQueries(QVector<RawDatabase::Query>& upgradeQueries) {
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE text_messages;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE text_messages_new RENAME TO text_messages;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE file_transfers;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE file_transfers_new RENAME TO file_transfers;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE aliases;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE aliases_new RENAME TO aliases;"));
}
bool DbTo11::PeersToChats::appendPeersToChatsQueries(RawDatabase& db, QVector<RawDatabase::Query>& upgradeQueries)
{
appendCreateNewTablesQueries(upgradeQueries);
if (!appendPopulateChatsQueries(db, upgradeQueries)) {
return false;
}
if (!appendUpdateHistoryFkQueries(db, upgradeQueries)) {
return false;
}
appendUpdateTextMessagesFkQueries(upgradeQueries);
appendUpdateFileTransfersFkQueries(upgradeQueries);
appendUpdateSystemMessagesFkQueries(upgradeQueries);
appendUpdateFauxOfflinePendingFkQueries(upgradeQueries);
appendUpdateBrokenMessagesFkQueries(upgradeQueries);
appendReplaceOldTablesQueries(upgradeQueries);
return true;
}
void DbTo11::PeersToChats::appendCreateNewTablesQueries(QVector<RawDatabase::Query>& upgradeQueries)
{
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE chats (id INTEGER PRIMARY KEY, "
"uuid BLOB NOT NULL UNIQUE);"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE history_new "
"(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 chats(id));"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE text_messages_new "
"(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_new(id, message_type), "
"FOREIGN KEY (sender_alias) REFERENCES aliases(id));"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE file_transfers_new "
"(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_new(id, message_type), "
"FOREIGN KEY (sender_alias) REFERENCES aliases(id));"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE system_messages_new "
"(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_new(id, message_type));"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE faux_offline_pending_new (id INTEGER PRIMARY KEY, "
"required_extensions INTEGER NOT NULL DEFAULT 0, "
"FOREIGN KEY (id) REFERENCES history_new(id));"));
upgradeQueries += RawDatabase::Query(QStringLiteral(
"CREATE TABLE broken_messages_new (id INTEGER PRIMARY KEY, "
"reason INTEGER NOT NULL DEFAULT 0, "
"FOREIGN KEY (id) REFERENCES history_new(id));"));
}
bool DbTo11::PeersToChats::appendPopulateChatsQueries(RawDatabase& db, QVector<RawDatabase::Query>& upgradeQueries)
{
// don't copy over directly, so that we can convert from string to blob
return db.execNow(RawDatabase::Query(QStringLiteral(
"SELECT DISTINCT peers.public_key FROM peers JOIN history ON peers.id=history.chat_id"),
[&](const QVector<QVariant>& row) {
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO chats (uuid) VALUES (?)"),
{ToxPk{row[0].toString()}.getByteArray()});
}
));
}
bool DbTo11::PeersToChats::appendUpdateHistoryFkQueries(RawDatabase& db, QVector<RawDatabase::Query>& upgradeQueries)
{
return db.execNow(RawDatabase::Query(QStringLiteral(
"SELECT DISTINCT peers.id, peers.public_key FROM peers JOIN history ON peers.id=history.chat_id"),
[&](const QVector<QVariant>& row) {
const auto oldPeerId = row[0].toLongLong();
const auto pk = ToxPk{row[1].toString()};
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO history_new (id, message_type, timestamp, chat_id) "
"SELECT id, "
"message_type, "
"timestamp, "
"(SELECT id FROM chats WHERE uuid = ?) "
"FROM history WHERE chat_id = '%1'").arg(oldPeerId),
{pk.getByteArray()});
}));
}
void DbTo11::PeersToChats::appendReplaceOldTablesQueries(QVector<RawDatabase::Query>& upgradeQueries) {
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE text_messages;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE text_messages_new RENAME TO text_messages;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE file_transfers;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE file_transfers_new RENAME TO file_transfers;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE system_messages;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE system_messages_new RENAME TO system_messages;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE faux_offline_pending;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE faux_offline_pending_new RENAME TO faux_offline_pending;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE broken_messages;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE broken_messages_new RENAME TO broken_messages;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE history;"));
upgradeQueries += RawDatabase::Query(QStringLiteral("ALTER TABLE history_new RENAME TO history;"));
}
void DbTo11::appendUpdateTextMessagesFkQueries(QVector<RawDatabase::Query>& upgradeQueries)
{
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO text_messages_new SELECT * FROM text_messages"));
}
void DbTo11::appendUpdateFileTransfersFkQueries(QVector<RawDatabase::Query>& upgradeQueries)
{
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO file_transfers_new SELECT * FROM file_transfers"));
}
void DbTo11::PeersToChats::appendUpdateSystemMessagesFkQueries(QVector<RawDatabase::Query>& upgradeQueries)
{
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO system_messages_new SELECT * FROM system_messages"));
}
void DbTo11::PeersToChats::appendUpdateFauxOfflinePendingFkQueries(QVector<RawDatabase::Query>& upgradeQueries)
{
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO faux_offline_pending_new SELECT * FROM faux_offline_pending"));
}
void DbTo11::PeersToChats::appendUpdateBrokenMessagesFkQueries(QVector<RawDatabase::Query>& upgradeQueries)
{
upgradeQueries += RawDatabase::Query(QStringLiteral(
"INSERT INTO broken_messages_new SELECT * FROM broken_messages"));
}
void DbTo11::appendDropPeersQueries(QVector<RawDatabase::Query>& upgradeQueries)
{
upgradeQueries += RawDatabase::Query(QStringLiteral("DROP TABLE peers;"));
}