Skip to content

Commit

Permalink
runfix: import backup from migrated db version [WPB-5906] (#16400)
Browse files Browse the repository at this point in the history
* runfix: run db schema updates after importing a backup

* runfix: run only upgrades above archive version number

* test: update tests
  • Loading branch information
PatrykBuniX authored Dec 19, 2023
1 parent 6648de6 commit 75f012d
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 33 deletions.
8 changes: 1 addition & 7 deletions src/script/backup/BackupRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {WebWorker} from 'Util/worker';
import {BackUpHeader, DecodedHeader, ENCRYPTED_BACKUP_FORMAT, ENCRYPTED_BACKUP_VERSION} from './BackUpHeader';
import {BackupRepository, Filename} from './BackupRepository';
import {BackupService} from './BackupService';
import {CancelError, DifferentAccountError, IncompatibleBackupError, IncompatiblePlatformError} from './Error';
import {CancelError, DifferentAccountError, IncompatiblePlatformError} from './Error';
import {handleZipEvent} from './zipWorker';

import {ConversationRepository} from '../conversation/ConversationRepository';
Expand Down Expand Up @@ -172,12 +172,6 @@ describe('BackupRepository', () => {
metaChanges: {user_id: 'fail'},
},
],
[
{
expectedError: IncompatibleBackupError,
metaChanges: {version: 13}, // version 14 contains a migration script, thus will generate an error
},
],
[
{
expectedError: IncompatiblePlatformError,
Expand Down
28 changes: 11 additions & 17 deletions src/script/backup/BackupRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ export class BackupRepository {
throw new InvalidMetaDataError();
}

await this.verifyMetadata(user, files);
const archiveVersion = await this.verifyMetadata(user, files);
const fileDescriptors = Object.entries(files)
.filter(([filename]) => filename !== Filename.METADATA)
.map(([filename, content]) => {
Expand All @@ -326,7 +326,7 @@ export class BackupRepository {
const nbEntities = fileDescriptors.reduce((acc, {entities}) => acc + entities.length, 0);
initCallback(nbEntities);

await this.importHistoryData(fileDescriptors, progressCallback);
await this.importHistoryData(archiveVersion, fileDescriptors, progressCallback);
}

private async createDecryptedBackup(
Expand Down Expand Up @@ -357,6 +357,7 @@ export class BackupRepository {
}

private async importHistoryData(
archiveVersion: number,
fileDescriptors: FileDescriptor[],
progressCallback: ProgressCallback,
): Promise<void> {
Expand All @@ -377,6 +378,9 @@ export class BackupRepository {
}
}

// Run all the database migrations on the imported data
await this.backupService.runDbSchemaUpdates(archiveVersion);

await this.conversationRepository.updateConversations(importedConversations);
await this.conversationRepository.initAllLocal1To1Conversations();
// doesn't need to be awaited
Expand Down Expand Up @@ -474,15 +478,16 @@ export class BackupRepository {
return omit(entity, 'primary_key');
}

private async verifyMetadata(user: User, files: Record<string, Uint8Array>): Promise<void> {
private async verifyMetadata(user: User, files: Record<string, Uint8Array>): Promise<number> {
const rawData = files[Filename.METADATA];
const metaData = new TextDecoder().decode(rawData);
const parsedMetaData = JSON.parse(metaData);
this._verifyMetadata(user, parsedMetaData);
const archiveVersion = this._verifyMetadata(user, parsedMetaData);
this.logger.log('Validated metadata during history import', files);
return archiveVersion;
}

private _verifyMetadata(user: User, archiveMetadata: Metadata): void {
private _verifyMetadata(user: User, archiveMetadata: Metadata): number {
const localMetadata = this.createMetaData(user, '');
const isExpectedUserId = archiveMetadata.user_id === localMetadata.user_id;
if (!isExpectedUserId) {
Expand All @@ -498,18 +503,7 @@ export class BackupRepository {
throw new IncompatiblePlatformError(message);
}

const lowestDbVersion = Math.min(archiveMetadata.version, localMetadata.version);
const involvesDatabaseMigration = StorageSchemata.SCHEMATA.reduce((involvesMigration, schemaData) => {
if (schemaData.version > lowestDbVersion) {
return involvesMigration || !!schemaData.upgrade;
}
return involvesMigration;
}, false);

if (involvesDatabaseMigration) {
const message = 'History cannot be restored: Database version mismatch';
throw new IncompatibleBackupError(message);
}
return archiveMetadata.version;
}

private mapDecodingError(decodingError: string) {
Expand Down
9 changes: 9 additions & 0 deletions src/script/backup/BackupService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ export class BackupService {
] as const;
}

async runDbSchemaUpdates(archiveVersion: number): Promise<void> {
const {db} = this.storageService;
if (!db) {
this.logger.warn('Database schema will not run because the database is not initialized');
return;
}
return db.runDbSchemaUpdates(archiveVersion);
}

/**
* Will import all entities in the Database.
* If a primaryKey generator is given, it will only import the entities that are not already in the DB
Expand Down
34 changes: 25 additions & 9 deletions src/script/storage/DexieDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,7 @@ export class DexieDatabase extends Dexie {
super(dbName);
this.logger = getLogger(`Dexie (${dbName})`);

StorageSchemata.SCHEMATA.forEach(({schema, upgrade, version}) => {
const versionInstance = this.version(version).stores(schema);
if (upgrade) {
versionInstance.upgrade((transaction: Transaction) => {
this.logger.warn(`Database upgrade to version '${version}'`);
upgrade(transaction, this);
});
}
});
void this.initDbSchema();

this.amplify = this.table(StorageSchemata.OBJECT_STORE.AMPLIFY);
this.clients = this.table(StorageSchemata.OBJECT_STORE.CLIENTS);
Expand All @@ -70,4 +62,28 @@ export class DexieDatabase extends Dexie {
this.users = this.table(StorageSchemata.OBJECT_STORE.USERS);
this.groupIds = this.table(StorageSchemata.OBJECT_STORE.GROUP_IDS);
}

private readonly initDbSchema = async (): Promise<void> => {
StorageSchemata.SCHEMATA.forEach(({schema, upgrade, version}) => {
const versionInstance = this.version(version).stores(schema);
if (upgrade) {
versionInstance.upgrade((transaction: Transaction) => {
this.logger.warn(`Database upgrade to version '${version}'`);
upgrade(transaction, this);
});
}
});
};

public readonly runDbSchemaUpdates = async (archiveVersion: number): Promise<void> => {
for (const {upgrade, version} of StorageSchemata.SCHEMATA) {
// If the archive version is greater than the current version, run the upgrade
if (upgrade && version > archiveVersion) {
await this.transaction('rw', this.tables, transaction => {
this.logger.info(`Running DB schema update for version '${version}'`);
upgrade(transaction, this);
});
}
}
};
}

0 comments on commit 75f012d

Please sign in to comment.