feat(service): Write (untested) migration to the new format
This commit is contained in:
parent
293908c40c
commit
071ea6db5d
@ -8,6 +8,7 @@ Future<void> configureDatabase(Database db) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createDatabase(Database db, int version) async {
|
Future<void> createDatabase(Database db, int version) async {
|
||||||
|
// TODO: Adjust to migrations
|
||||||
// XMPP state
|
// XMPP state
|
||||||
await db.execute(
|
await db.execute(
|
||||||
'''
|
'''
|
||||||
|
@ -42,6 +42,7 @@ import 'package:moxxyv2/service/database/migrations/0002_reactions_2.dart';
|
|||||||
import 'package:moxxyv2/service/database/migrations/0002_shared_media.dart';
|
import 'package:moxxyv2/service/database/migrations/0002_shared_media.dart';
|
||||||
import 'package:moxxyv2/service/database/migrations/0002_sticker_metadata.dart';
|
import 'package:moxxyv2/service/database/migrations/0002_sticker_metadata.dart';
|
||||||
import 'package:moxxyv2/service/database/migrations/0003_avatar_hashes.dart';
|
import 'package:moxxyv2/service/database/migrations/0003_avatar_hashes.dart';
|
||||||
|
import 'package:moxxyv2/service/database/migrations/0003_jid_attribute.dart';
|
||||||
import 'package:moxxyv2/service/database/migrations/0003_new_omemo.dart';
|
import 'package:moxxyv2/service/database/migrations/0003_new_omemo.dart';
|
||||||
import 'package:moxxyv2/service/database/migrations/0003_new_omemo_pseudo_messages.dart';
|
import 'package:moxxyv2/service/database/migrations/0003_new_omemo_pseudo_messages.dart';
|
||||||
import 'package:moxxyv2/service/database/migrations/0003_remove_subscriptions.dart';
|
import 'package:moxxyv2/service/database/migrations/0003_remove_subscriptions.dart';
|
||||||
@ -154,6 +155,8 @@ const List<DatabaseMigration<Database>> migrations = [
|
|||||||
DatabaseMigration(40, upgradeFromV39ToV40),
|
DatabaseMigration(40, upgradeFromV39ToV40),
|
||||||
DatabaseMigration(41, upgradeFromV40ToV41),
|
DatabaseMigration(41, upgradeFromV40ToV41),
|
||||||
DatabaseMigration(42, upgradeFromV41ToV42),
|
DatabaseMigration(42, upgradeFromV41ToV42),
|
||||||
|
// TODO: Merge after #300, #312, and #314
|
||||||
|
DatabaseMigration(42, upgradeFromV45ToV46),
|
||||||
];
|
];
|
||||||
|
|
||||||
class DatabaseService {
|
class DatabaseService {
|
||||||
|
328
lib/service/database/migrations/0003_jid_attribute.dart
Normal file
328
lib/service/database/migrations/0003_jid_attribute.dart
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
import 'package:moxxyv2/service/database/constants.dart';
|
||||||
|
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||||
|
|
||||||
|
extension MaybeGet<K, V> on Map<K, V> {
|
||||||
|
V? maybeGet(K? key) {
|
||||||
|
if (key == null) return null;
|
||||||
|
|
||||||
|
return this[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> upgradeFromV45ToV46(Database db) async {
|
||||||
|
// Migrate everything to the tuple of (account JID, <old pk>)
|
||||||
|
// Things we do not migrate to this scheme:
|
||||||
|
// - Stickers: Technically, makes no sense
|
||||||
|
// - File metadata: We want to aggresively cache, so we keep it
|
||||||
|
|
||||||
|
// Get the account JID
|
||||||
|
final rawJid = await db.query(
|
||||||
|
xmppStateTable,
|
||||||
|
where: 'key = ?',
|
||||||
|
whereArgs: ['jid'],
|
||||||
|
limit: 1,
|
||||||
|
);
|
||||||
|
if (rawJid.isEmpty) {
|
||||||
|
// TODO: Remove all messages?
|
||||||
|
}
|
||||||
|
final accountJid = rawJid.first['value']! as String;
|
||||||
|
|
||||||
|
// Migrate the XMPP state
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${xmppStateTable}_new (
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
accountJid TEXT NOT NULL,
|
||||||
|
value TEXT,
|
||||||
|
PRIMARY KEY (key, accountJid)
|
||||||
|
)''',
|
||||||
|
);
|
||||||
|
for (final statePair in await db.query(xmppStateTable)) {
|
||||||
|
await db.insert(
|
||||||
|
'${xmppStateTable}_new',
|
||||||
|
{
|
||||||
|
...statePair,
|
||||||
|
'accountJid': accountJid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await db.execute('DROP TABLE $xmppStateTable');
|
||||||
|
await db
|
||||||
|
.execute('ALTER TABLE ${xmppStateTable}_new RENAME TO $xmppStateTable');
|
||||||
|
|
||||||
|
// Migrate messages
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${messagesTable}_new (
|
||||||
|
accountJid TEXT NOT NULL,
|
||||||
|
sender TEXT NOT NULL,
|
||||||
|
body TEXT,
|
||||||
|
timestamp INTEGER NOT NULL,
|
||||||
|
sid TEXT NOT NULL,
|
||||||
|
conversationJid TEXT NOT NULL,
|
||||||
|
isFileUploadNotification INTEGER NOT NULL,
|
||||||
|
encrypted INTEGER NOT NULL,
|
||||||
|
errorType INTEGER,
|
||||||
|
warningType INTEGER,
|
||||||
|
received INTEGER,
|
||||||
|
displayed INTEGER,
|
||||||
|
acked INTEGER,
|
||||||
|
originId TEXT,
|
||||||
|
quote_id TEXT,
|
||||||
|
file_metadata_id TEXT,
|
||||||
|
isDownloading INTEGER NOT NULL,
|
||||||
|
isUploading INTEGER NOT NULL,
|
||||||
|
isRetracted INTEGER,
|
||||||
|
isEdited INTEGER NOT NULL,
|
||||||
|
containsNoStore INTEGER NOT NULL,
|
||||||
|
stickerPackId TEXT,
|
||||||
|
pseudoMessageType INTEGER,
|
||||||
|
pseudoMessageData TEXT,
|
||||||
|
PRIMARY KEY (accountJid, senderJid, sid),
|
||||||
|
CONSTRAINT fk_quote
|
||||||
|
FOREIGN KEY (accountJid, quote_id)
|
||||||
|
REFERENCES $messagesTable (accountJid, sid)
|
||||||
|
CONSTRAINT fk_file_metadata
|
||||||
|
FOREIGN KEY (file_metadata_id)
|
||||||
|
REFERENCES $fileMetadataTable (id)
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
final messages = await db.query(messagesTable);
|
||||||
|
// Build up the message map
|
||||||
|
/// Message's old id attribute -> Message's sid attribute.
|
||||||
|
final messageMap = <int, String>{};
|
||||||
|
for (var message in messages) {
|
||||||
|
messageMap[message['id']! as int] = message['sid']! as String;
|
||||||
|
}
|
||||||
|
// Then migrate messages
|
||||||
|
for (final message in messages) {
|
||||||
|
await db.insert('${messagesTable}_new', {
|
||||||
|
...message
|
||||||
|
..remove('id')
|
||||||
|
..remove('quote_id'),
|
||||||
|
'accountJid': accountJid,
|
||||||
|
'quote_id': message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await db.execute('DROP TABLE $messagesTable');
|
||||||
|
await db.execute('ALTER TABLE ${messagesTable}_new RENAME TO $messagesTable');
|
||||||
|
await db.execute('DROP INDEX idx_messages_id');
|
||||||
|
await db.execute(
|
||||||
|
'CREATE INDEX idx_messages_sid ON $messagesTable (accountJid, sid)');
|
||||||
|
await db.execute(
|
||||||
|
'CREATE INDEX idx_messages_origin_sid ON $messagesTable (accountJid, originId, sid)');
|
||||||
|
|
||||||
|
// Migrate conversations
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${conversationsTable}_new (
|
||||||
|
jid TEXT NOT NULL,
|
||||||
|
accountJid TEXT NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
avatarPath TEXT NOT NULL,
|
||||||
|
avatarHash TEXT,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
lastChangeTimestamp INTEGER NOT NULL,
|
||||||
|
unreadCounter INTEGER NOT NULL,
|
||||||
|
open INTEGER NOT NULL,
|
||||||
|
muted INTEGER NOT NULL,
|
||||||
|
encrypted INTEGER NOT NULL,
|
||||||
|
lastMessageId TEXT,
|
||||||
|
contactId TEXT,
|
||||||
|
contactAvatarPath TEXT,
|
||||||
|
contactDisplayName TEXT,
|
||||||
|
PRIMARY KEY (jid, accountJid),
|
||||||
|
CONSTRAINT fk_last_message
|
||||||
|
FOREIGN KEY (accountJid, lastMessageId)
|
||||||
|
REFERENCES $messagesTable (accountJid, sid),
|
||||||
|
CONSTRAINT fk_contact_id
|
||||||
|
FOREIGN KEY (contactId)
|
||||||
|
REFERENCES $contactsTable (id)
|
||||||
|
ON DELETE SET NULL
|
||||||
|
)''',
|
||||||
|
);
|
||||||
|
for (final conversation in await db.query(conversationsTable)) {
|
||||||
|
await db.insert(
|
||||||
|
'${conversationsTable}_new',
|
||||||
|
{
|
||||||
|
...conversation..remove('lastMessageId'),
|
||||||
|
'lastMessageId':
|
||||||
|
messageMap.maybeGet(conversation['lastMessageId'] as int?),
|
||||||
|
'accountJid': accountJid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await db.execute('DROP TABLE $conversationsTable');
|
||||||
|
await db.execute(
|
||||||
|
'ALTER TABLE ${conversationsTable}_new RENAME TO $conversationsTable');
|
||||||
|
await db.execute('DROP INDEX idx_conversation_id');
|
||||||
|
await db.execute(
|
||||||
|
'CREATE INDEX idx_conversation_id ON $conversationsTable (accountJid, jid)');
|
||||||
|
|
||||||
|
// Migrate reactions
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${reactionsTable}_new (
|
||||||
|
accountJid TEXT NOT NULL,
|
||||||
|
senderJid TEXT NOT NULL,
|
||||||
|
emoji TEXT NOT NULL,
|
||||||
|
message_id TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (accountJid, senderJid, emoji, message_id),
|
||||||
|
CONSTRAINT fk_message
|
||||||
|
FOREIGN KEY (accountJid, message_id)
|
||||||
|
REFERENCES $messagesTable (accountJid, sid)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
)''',
|
||||||
|
);
|
||||||
|
for (final reaction in await db.query(reactionsTable)) {
|
||||||
|
await db.insert(
|
||||||
|
'${reactionsTable}_new',
|
||||||
|
{
|
||||||
|
...reaction..remove('message_id'),
|
||||||
|
'message_id': messageMap.maybeGet(reaction['message_id'] as int?),
|
||||||
|
'accountJid': accountJid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate the roster
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${rosterTable}_new (
|
||||||
|
jid TEXT NOT NULL,
|
||||||
|
accountJid TEXT NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
avatarPath TEXT NOT NULL,
|
||||||
|
avatarHash TEXT NOT NULL,
|
||||||
|
subscription TEXT NOT NULL,
|
||||||
|
ask TEXT NOT NULL,
|
||||||
|
contactId TEXT,
|
||||||
|
contactAvatarPath TEXT,
|
||||||
|
contactDisplayName TEXT,
|
||||||
|
pseudoRosterItem INTEGER NOT NULL,
|
||||||
|
CONSTRAINT fk_contact_id
|
||||||
|
FOREIGN KEY (contactId)
|
||||||
|
REFERENCES $contactsTable (id)
|
||||||
|
ON DELETE SET NULL
|
||||||
|
)''',
|
||||||
|
);
|
||||||
|
for (final rosterItem in await db.query(rosterTable)) {
|
||||||
|
await db.insert(
|
||||||
|
'${rosterTable}_new',
|
||||||
|
{
|
||||||
|
...rosterItem..remove('id'),
|
||||||
|
'accountJid': accountJid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await db.execute('DROP TABLE $rosterTable');
|
||||||
|
await db.execute('ALTER TABLE ${rosterTable}_new RENAME TO $rosterTable');
|
||||||
|
|
||||||
|
// Migrate the blocklist
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${blocklistTable}_new (
|
||||||
|
jid TEXT NOT NULL,
|
||||||
|
accountJid TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (accountJid, jid)
|
||||||
|
);
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
for (final blocklistItem in await db.query(blocklistTable)) {
|
||||||
|
await db.insert(
|
||||||
|
'${blocklistTable}_new',
|
||||||
|
{
|
||||||
|
...blocklistItem,
|
||||||
|
'accountJid': accountJid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await db.execute('DROP TABLE $blocklistTable');
|
||||||
|
await db
|
||||||
|
.execute('ALTER TABLE ${blocklistTable}_new RENAME TO $blocklistTable');
|
||||||
|
|
||||||
|
// Migrate OMEMO device list
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${omemoDeviceListTable}_new (
|
||||||
|
jid TEXT NOT NULL,
|
||||||
|
accountJid TEXT NOT NULL,
|
||||||
|
devices TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (accountJid, jid)
|
||||||
|
)''',
|
||||||
|
);
|
||||||
|
for (final deviceListEntry in await db.query(omemoDeviceListTable)) {
|
||||||
|
await db.insert(
|
||||||
|
'${omemoDeviceListTable}_new',
|
||||||
|
{
|
||||||
|
...deviceListEntry,
|
||||||
|
'accountJid': accountJid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await db.execute('DROP TABLE $omemoDeviceListTable');
|
||||||
|
await db.execute(
|
||||||
|
'ALTER TABLE ${omemoDeviceListTable}_new RENAME TO $omemoDeviceListTable');
|
||||||
|
|
||||||
|
// Migrate OMEMO trust
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${omemoTrustTable}_new (
|
||||||
|
jid TEXT NOT NULL,
|
||||||
|
accountJid TEXT NOT NULL
|
||||||
|
device INTEGER NOT NULL,
|
||||||
|
trust INTEGER NOT NULL,
|
||||||
|
enabled INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (accountJid, jid, device)
|
||||||
|
)''',
|
||||||
|
);
|
||||||
|
for (final trustItem in await db.query(omemoTrustTable)) {
|
||||||
|
await db.insert(
|
||||||
|
'${omemoTrustTable}_new',
|
||||||
|
{
|
||||||
|
...trustItem,
|
||||||
|
'accoutJid': accountJid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await db.execute('DROP TABLE $omemoTrustTable');
|
||||||
|
await db
|
||||||
|
.execute('ALTER TABLE ${omemoTrustTable}_new RENAME TO $omemoTrustTable');
|
||||||
|
|
||||||
|
// Migrate OMEMO ratchets
|
||||||
|
await db.execute(
|
||||||
|
'''
|
||||||
|
CREATE TABLE ${omemoRatchetsTable}_new (
|
||||||
|
jid TEXT NOT NULL,
|
||||||
|
accountJid TEXT NOT NULL,
|
||||||
|
device INTEGER NOT NULL,
|
||||||
|
dhsPub TEXT NOT NULL,
|
||||||
|
dhs TEXT NOT NULL,
|
||||||
|
dhrPub TEXT,
|
||||||
|
rk TEXT NOT NULL,
|
||||||
|
cks TEXT,
|
||||||
|
ckr TEXT,
|
||||||
|
ns INTEGER NOT NULL,
|
||||||
|
nr INTEGER NOT NULL,
|
||||||
|
pn INTEGER NOT NULL,
|
||||||
|
ik TEXT NOT NULL,
|
||||||
|
ad TEXT NOT NULL,
|
||||||
|
skipped TEXT NOT NULL,
|
||||||
|
kex TEXT NOT NULL,
|
||||||
|
acked INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (accountJid, jid, device)
|
||||||
|
)''',
|
||||||
|
);
|
||||||
|
for (final ratchet in await db.query(omemoRatchetsTable)) {
|
||||||
|
await db.insert(
|
||||||
|
'${omemoRatchetsTable}_new',
|
||||||
|
{
|
||||||
|
...ratchet,
|
||||||
|
'accountJid': accountJid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await db.execute('DROP TABLE $omemoRatchetsTable');
|
||||||
|
await db.execute(
|
||||||
|
'ALTER TABLE ${omemoRatchetsTable}_new RENAME TO $omemoRatchetsTable');
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user