Pause, cancel & resume backup media download
This commit is contained in:
parent
65539b1419
commit
028a3f3ef0
28 changed files with 958 additions and 141 deletions
213
ts/test-node/sql/migration_1200_test.ts
Normal file
213
ts/test-node/sql/migration_1200_test.ts
Normal file
|
@ -0,0 +1,213 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { AttachmentDownloadSource, type WritableDB } from '../../sql/Interface';
|
||||
import { objectToJSON, sql } from '../../sql/util';
|
||||
import { createDB, updateToVersion } from './helpers';
|
||||
import type { AttachmentDownloadJobType } from '../../types/AttachmentDownload';
|
||||
import { IMAGE_JPEG } from '../../types/MIME';
|
||||
|
||||
type UnflattenedAttachmentDownloadJobType = Omit<
|
||||
AttachmentDownloadJobType,
|
||||
'digest' | 'contentType' | 'size' | 'ciphertextSize'
|
||||
>;
|
||||
|
||||
function createJob(
|
||||
index: number,
|
||||
overrides?: Partial<UnflattenedAttachmentDownloadJobType>
|
||||
): UnflattenedAttachmentDownloadJobType {
|
||||
return {
|
||||
messageId: `message${index}`,
|
||||
attachmentType: 'attachment',
|
||||
attachment: {
|
||||
digest: `digest${index}`,
|
||||
contentType: IMAGE_JPEG,
|
||||
size: 128,
|
||||
},
|
||||
receivedAt: 100 + index,
|
||||
sentAt: 100 + index,
|
||||
attempts: 0,
|
||||
active: false,
|
||||
retryAfter: null,
|
||||
lastAttemptTimestamp: null,
|
||||
source: AttachmentDownloadSource.STANDARD,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
function insertJob(
|
||||
db: WritableDB,
|
||||
index: number,
|
||||
overrides?: Partial<UnflattenedAttachmentDownloadJobType>
|
||||
): void {
|
||||
const job = createJob(index, overrides);
|
||||
try {
|
||||
db.prepare('INSERT INTO messages (id) VALUES ($id)').run({
|
||||
id: job.messageId,
|
||||
});
|
||||
} catch (e) {
|
||||
// pass; message has already been inserted
|
||||
}
|
||||
const [query, params] = sql`
|
||||
INSERT INTO attachment_downloads
|
||||
(
|
||||
messageId,
|
||||
attachmentType,
|
||||
attachmentJson,
|
||||
digest,
|
||||
contentType,
|
||||
size,
|
||||
receivedAt,
|
||||
sentAt,
|
||||
active,
|
||||
attempts,
|
||||
retryAfter,
|
||||
lastAttemptTimestamp,
|
||||
source
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
${job.messageId},
|
||||
${job.attachmentType},
|
||||
${objectToJSON(job.attachment)},
|
||||
${job.attachment.digest},
|
||||
${job.attachment.contentType},
|
||||
${job.attachment.size},
|
||||
${job.receivedAt},
|
||||
${job.sentAt},
|
||||
${job.active ? 1 : 0},
|
||||
${job.attempts},
|
||||
${job.retryAfter},
|
||||
${job.lastAttemptTimestamp},
|
||||
${job.source}
|
||||
);
|
||||
`;
|
||||
|
||||
db.prepare(query).run(params);
|
||||
}
|
||||
|
||||
const NUM_STANDARD_JOBS = 100;
|
||||
|
||||
describe('SQL/updateToSchemaVersion1200', () => {
|
||||
let db: WritableDB;
|
||||
|
||||
after(() => {
|
||||
db.close();
|
||||
});
|
||||
|
||||
before(() => {
|
||||
db = createDB();
|
||||
updateToVersion(db, 1200);
|
||||
db.transaction(() => {
|
||||
for (let i = 0; i < 10_000; i += 1) {
|
||||
insertJob(db, i, {
|
||||
source:
|
||||
i < NUM_STANDARD_JOBS
|
||||
? AttachmentDownloadSource.STANDARD
|
||||
: AttachmentDownloadSource.BACKUP_IMPORT,
|
||||
});
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
it('uses correct index for standard query', () => {
|
||||
const now = Date.now();
|
||||
const [query, params] = sql`
|
||||
SELECT * FROM attachment_downloads
|
||||
WHERE
|
||||
active = 0
|
||||
AND
|
||||
(retryAfter is NULL OR retryAfter <= ${now})
|
||||
ORDER BY receivedAt DESC
|
||||
LIMIT 3
|
||||
`;
|
||||
const details = db
|
||||
.prepare(`EXPLAIN QUERY PLAN ${query}`)
|
||||
.all(params)
|
||||
.map(step => step.detail)
|
||||
.join(', ');
|
||||
assert.equal(
|
||||
details,
|
||||
'SEARCH attachment_downloads USING INDEX attachment_downloads_active_receivedAt (active=?)'
|
||||
);
|
||||
});
|
||||
|
||||
it('uses correct index for standard query with sources', () => {
|
||||
const now = Date.now();
|
||||
// query with sources (e.g. when backup-import is paused)
|
||||
const [query, params] = sql`
|
||||
SELECT * FROM attachment_downloads
|
||||
WHERE
|
||||
active IS 0
|
||||
AND
|
||||
source IN ('standard')
|
||||
AND
|
||||
(retryAfter is NULL OR retryAfter <= ${now})
|
||||
ORDER BY receivedAt DESC
|
||||
LIMIT 3
|
||||
`;
|
||||
const details = db
|
||||
.prepare(`EXPLAIN QUERY PLAN ${query}`)
|
||||
.all(params)
|
||||
.map(step => step.detail)
|
||||
.join(', ');
|
||||
assert.equal(
|
||||
details,
|
||||
'SEARCH attachment_downloads USING INDEX attachment_downloads_active_source_receivedAt (active=? AND source=?)'
|
||||
);
|
||||
});
|
||||
|
||||
it('uses provided index for prioritized query with sources', () => {
|
||||
// prioritize visible messages with sources (e.g. when backup-import is paused)
|
||||
const [query, params] = sql`
|
||||
SELECT * FROM attachment_downloads
|
||||
INDEXED BY attachment_downloads_active_messageId
|
||||
WHERE
|
||||
active IS 0
|
||||
AND
|
||||
messageId IN ('message12', 'message101')
|
||||
AND
|
||||
(lastAttemptTimestamp is NULL OR lastAttemptTimestamp <= ${Date.now()})
|
||||
AND
|
||||
source IN ('standard')
|
||||
ORDER BY receivedAt ASC
|
||||
LIMIT 3
|
||||
`;
|
||||
const result = db.prepare(query).all(params);
|
||||
assert.strictEqual(result.length, 1);
|
||||
assert.deepStrictEqual(result[0].messageId, 'message12');
|
||||
const details = db
|
||||
.prepare(`EXPLAIN QUERY PLAN ${query}`)
|
||||
.all(params)
|
||||
.map(step => step.detail)
|
||||
.join(', ');
|
||||
assert.equal(
|
||||
details,
|
||||
'SEARCH attachment_downloads USING INDEX attachment_downloads_active_messageId (active=? AND messageId=?), USE TEMP B-TREE FOR ORDER BY'
|
||||
);
|
||||
});
|
||||
|
||||
it('uses existing index to remove all backup jobs ', () => {
|
||||
// prioritize visible messages with sources (e.g. when backup-import is paused)
|
||||
const [query, params] = sql`
|
||||
DELETE FROM attachment_downloads
|
||||
WHERE source = 'backup_import';
|
||||
`;
|
||||
|
||||
const details = db
|
||||
.prepare(`EXPLAIN QUERY PLAN ${query}`)
|
||||
.all(params)
|
||||
.map(step => step.detail)
|
||||
.join(', ');
|
||||
assert.equal(
|
||||
details,
|
||||
'SEARCH attachment_downloads USING COVERING INDEX attachment_downloads_source_ciphertextSize (source=?)'
|
||||
);
|
||||
db.prepare(query).run(params);
|
||||
assert.equal(
|
||||
db.prepare('SELECT * FROM attachment_downloads').all().length,
|
||||
NUM_STANDARD_JOBS
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue