Manually manage full-text search table

This commit is contained in:
Scott Nonnenberg 2021-02-04 12:46:55 -08:00 committed by GitHub
parent 245f8c665d
commit 2f90d6aca9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 223 additions and 84 deletions

View file

@ -21,12 +21,15 @@ const ERASE_SQL_KEY = 'erase-sql-key';
let singleQueue = null; let singleQueue = null;
let multipleQueue = null; let multipleQueue = null;
// Note: we don't want queue timeouts, because delays here are due to in-progress sql
// operations. For example we might try to start a transaction when the prevous isn't
// done, causing that database operation to fail.
function makeNewSingleQueue() { function makeNewSingleQueue() {
singleQueue = new Queue({ concurrency: 1, timeout: 1000 * 60 * 2 }); singleQueue = new Queue({ concurrency: 1 });
return singleQueue; return singleQueue;
} }
function makeNewMultipleQueue() { function makeNewMultipleQueue() {
multipleQueue = new Queue({ concurrency: 10, timeout: 1000 * 60 * 2 }); multipleQueue = new Queue({ concurrency: 10 });
return multipleQueue; return multipleQueue;
} }

View file

@ -54,12 +54,12 @@ export const ConfirmationDialog = React.memo(
const handleAction = React.useCallback( const handleAction = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => { (e: React.MouseEvent<HTMLButtonElement>) => {
onClose();
if (e.currentTarget.dataset.action) { if (e.currentTarget.dataset.action) {
const actionIndex = parseInt(e.currentTarget.dataset.action, 10); const actionIndex = parseInt(e.currentTarget.dataset.action, 10);
const { action } = actions[actionIndex]; const { action } = actions[actionIndex];
action(); action();
} }
onClose();
}, },
[onClose, actions] [onClose, actions]
); );

View file

@ -1621,6 +1621,30 @@ async function updateToSchemaVersion22(
} }
} }
async function updateToSchemaVersion23(
currentVersion: number,
instance: PromisifiedSQLDatabase
) {
if (currentVersion >= 23) {
return;
}
try {
await instance.run('BEGIN TRANSACTION;');
// Remove triggers which keep full-text search up to date
await instance.run('DROP TRIGGER messages_on_insert;');
await instance.run('DROP TRIGGER messages_on_update;');
await instance.run('DROP TRIGGER messages_on_delete;');
await instance.run('PRAGMA user_version = 23;');
await instance.run('COMMIT TRANSACTION;');
console.log('updateToSchemaVersion23: success!');
} catch (error) {
await instance.run('ROLLBACK');
throw error;
}
}
const SCHEMA_VERSIONS = [ const SCHEMA_VERSIONS = [
updateToSchemaVersion1, updateToSchemaVersion1,
updateToSchemaVersion2, updateToSchemaVersion2,
@ -1644,6 +1668,7 @@ const SCHEMA_VERSIONS = [
updateToSchemaVersion20, updateToSchemaVersion20,
updateToSchemaVersion21, updateToSchemaVersion21,
updateToSchemaVersion22, updateToSchemaVersion22,
updateToSchemaVersion23,
]; ];
async function updateSchema(instance: PromisifiedSQLDatabase) { async function updateSchema(instance: PromisifiedSQLDatabase) {
@ -1849,6 +1874,7 @@ async function getIdentityKeyById(id: string) {
async function bulkAddIdentityKeys(array: Array<IdentityKeyType>) { async function bulkAddIdentityKeys(array: Array<IdentityKeyType>) {
return bulkAdd(IDENTITY_KEYS_TABLE, array); return bulkAdd(IDENTITY_KEYS_TABLE, array);
} }
bulkAddIdentityKeys.needsSerial = true;
async function removeIdentityKeyById(id: string) { async function removeIdentityKeyById(id: string) {
return removeById(IDENTITY_KEYS_TABLE, id); return removeById(IDENTITY_KEYS_TABLE, id);
} }
@ -1869,6 +1895,7 @@ async function getPreKeyById(id: number) {
async function bulkAddPreKeys(array: Array<PreKeyType>) { async function bulkAddPreKeys(array: Array<PreKeyType>) {
return bulkAdd(PRE_KEYS_TABLE, array); return bulkAdd(PRE_KEYS_TABLE, array);
} }
bulkAddPreKeys.needsSerial = true;
async function removePreKeyById(id: number) { async function removePreKeyById(id: number) {
return removeById(PRE_KEYS_TABLE, id); return removeById(PRE_KEYS_TABLE, id);
} }
@ -1889,6 +1916,7 @@ async function getSignedPreKeyById(id: number) {
async function bulkAddSignedPreKeys(array: Array<SignedPreKeyType>) { async function bulkAddSignedPreKeys(array: Array<SignedPreKeyType>) {
return bulkAdd(SIGNED_PRE_KEYS_TABLE, array); return bulkAdd(SIGNED_PRE_KEYS_TABLE, array);
} }
bulkAddSignedPreKeys.needsSerial = true;
async function removeSignedPreKeyById(id: number) { async function removeSignedPreKeyById(id: number) {
return removeById(SIGNED_PRE_KEYS_TABLE, id); return removeById(SIGNED_PRE_KEYS_TABLE, id);
} }
@ -1918,6 +1946,7 @@ async function getAllItems() {
async function bulkAddItems(array: Array<ItemType>) { async function bulkAddItems(array: Array<ItemType>) {
return bulkAdd(ITEMS_TABLE, array); return bulkAdd(ITEMS_TABLE, array);
} }
bulkAddItems.needsSerial = true;
async function removeItemById(id: string) { async function removeItemById(id: string) {
return removeById(ITEMS_TABLE, id); return removeById(ITEMS_TABLE, id);
} }
@ -1990,6 +2019,7 @@ async function getSessionsById(conversationId: string) {
async function bulkAddSessions(array: Array<SessionType>) { async function bulkAddSessions(array: Array<SessionType>) {
return bulkAdd(SESSIONS_TABLE, array); return bulkAdd(SESSIONS_TABLE, array);
} }
bulkAddSessions.needsSerial = true;
async function removeSessionById(id: string) { async function removeSessionById(id: string) {
return removeById(SESSIONS_TABLE, id); return removeById(SESSIONS_TABLE, id);
} }
@ -2043,6 +2073,7 @@ async function bulkAdd(table: string, array: Array<any>) {
throw error; throw error;
} }
} }
bulkAdd.needsSerial = true;
async function getById(table: string, id: string | number) { async function getById(table: string, id: string | number) {
const db = getInstance(); const db = getInstance();
@ -2452,7 +2483,10 @@ async function getMessageCount(conversationId?: string) {
async function saveMessage( async function saveMessage(
data: MessageType, data: MessageType,
{ forceSave }: { forceSave?: boolean } = {} {
forceSave,
alreadyInTransaction,
}: { forceSave?: boolean; alreadyInTransaction?: boolean } = {}
) { ) {
const db = getInstance(); const db = getInstance();
const { const {
@ -2502,32 +2536,69 @@ async function saveMessage(
}; };
if (id && !forceSave) { if (id && !forceSave) {
await db.run( if (!alreadyInTransaction) {
`UPDATE messages SET await db.run('BEGIN TRANSACTION;');
id = $id, }
json = $json,
body = $body, try {
conversationId = $conversationId, await Promise.all([
expirationStartTimestamp = $expirationStartTimestamp, db.run(
expires_at = $expires_at, `UPDATE messages SET
expireTimer = $expireTimer, id = $id,
hasAttachments = $hasAttachments, json = $json,
hasFileAttachments = $hasFileAttachments,
hasVisualMediaAttachments = $hasVisualMediaAttachments, body = $body,
isErased = $isErased, conversationId = $conversationId,
isViewOnce = $isViewOnce, expirationStartTimestamp = $expirationStartTimestamp,
received_at = $received_at, expires_at = $expires_at,
schemaVersion = $schemaVersion, expireTimer = $expireTimer,
sent_at = $sent_at, hasAttachments = $hasAttachments,
source = $source, hasFileAttachments = $hasFileAttachments,
sourceUuid = $sourceUuid, hasVisualMediaAttachments = $hasVisualMediaAttachments,
sourceDevice = $sourceDevice, isErased = $isErased,
type = $type, isViewOnce = $isViewOnce,
unread = $unread received_at = $received_at,
WHERE id = $id;`, schemaVersion = $schemaVersion,
payload sent_at = $sent_at,
); source = $source,
sourceUuid = $sourceUuid,
sourceDevice = $sourceDevice,
type = $type,
unread = $unread
WHERE id = $id;`,
payload
),
db.run('DELETE FROM messages_fts WHERE id = $id;', {
$id: id,
}),
]);
if (body) {
await db.run(
`INSERT INTO messages_fts(
id,
body
) VALUES (
$id,
$body
);
`,
{
$id: id,
$body: body,
}
);
}
if (!alreadyInTransaction) {
await db.run('COMMIT TRANSACTION;');
}
} catch (error) {
if (!alreadyInTransaction) {
await db.run('ROLLBACK;');
}
throw error;
}
return id; return id;
} }
@ -2537,61 +2608,93 @@ async function saveMessage(
id: id || generateUUID(), id: id || generateUUID(),
}; };
await db.run( if (!alreadyInTransaction) {
`INSERT INTO messages ( await db.run('BEGIN TRANSACTION;');
id, }
json,
body, try {
conversationId, await Promise.all([
expirationStartTimestamp, db.run(
expires_at, `INSERT INTO messages (
expireTimer, id,
hasAttachments, json,
hasFileAttachments,
hasVisualMediaAttachments, body,
isErased, conversationId,
isViewOnce, expirationStartTimestamp,
received_at, expires_at,
schemaVersion, expireTimer,
sent_at, hasAttachments,
source, hasFileAttachments,
sourceUuid, hasVisualMediaAttachments,
sourceDevice, isErased,
type, isViewOnce,
unread received_at,
) values ( schemaVersion,
$id, sent_at,
$json, source,
sourceUuid,
sourceDevice,
type,
unread
) values (
$id,
$json,
$body,
$conversationId,
$expirationStartTimestamp,
$expires_at,
$expireTimer,
$hasAttachments,
$hasFileAttachments,
$hasVisualMediaAttachments,
$isErased,
$isViewOnce,
$received_at,
$schemaVersion,
$sent_at,
$source,
$sourceUuid,
$sourceDevice,
$type,
$unread
);`,
{
...payload,
$id: toCreate.id,
$json: objectToJSON(toCreate),
}
),
db.run(
`INSERT INTO messages_fts(
id,
body
) VALUES (
$id,
$body
);
`,
{
$id: id,
$body: body,
}
),
]);
$body, if (!alreadyInTransaction) {
$conversationId, await db.run('COMMIT TRANSACTION;');
$expirationStartTimestamp,
$expires_at,
$expireTimer,
$hasAttachments,
$hasFileAttachments,
$hasVisualMediaAttachments,
$isErased,
$isViewOnce,
$received_at,
$schemaVersion,
$sent_at,
$source,
$sourceUuid,
$sourceDevice,
$type,
$unread
);`,
{
...payload,
$id: toCreate.id,
$json: objectToJSON(toCreate),
} }
); } catch (error) {
if (!alreadyInTransaction) {
await db.run('ROLLBACK;');
}
throw error;
}
return toCreate.id; return toCreate.id;
} }
saveMessage.needsSerial = true;
async function saveMessages( async function saveMessages(
arrayOfMessages: Array<MessageType>, arrayOfMessages: Array<MessageType>,
@ -2603,7 +2706,7 @@ async function saveMessages(
try { try {
await Promise.all([ await Promise.all([
...map(arrayOfMessages, async message => ...map(arrayOfMessages, async message =>
saveMessage(message, { forceSave }) saveMessage(message, { forceSave, alreadyInTransaction: true })
), ),
]); ]);
@ -2617,16 +2720,49 @@ saveMessages.needsSerial = true;
async function removeMessage(id: string) { async function removeMessage(id: string) {
const db = getInstance(); const db = getInstance();
await db.run('DELETE FROM messages WHERE id = $id;', { $id: id }); await db.run('BEGIN TRANSACTION;');
try {
await Promise.all([
db.run('DELETE FROM messages WHERE id = $id;', { $id: id }),
db.run('DELETE FROM messages_fts WHERE id = $id;', { $id: id }),
]);
await db.run('COMMIT TRANSACTION;');
} catch (error) {
await db.run('ROLLBACK;');
throw error;
}
} }
removeMessage.needsSerial = true;
async function removeMessages(ids: Array<string>) { async function removeMessages(ids: Array<string>) {
const db = getInstance(); const db = getInstance();
await db.run( await db.run('BEGIN TRANSACTION;');
`DELETE FROM messages WHERE id IN ( ${ids.map(() => '?').join(', ')} );`,
ids try {
); await Promise.all([
db.run(
`DELETE FROM messages WHERE id IN ( ${ids
.map(() => '?')
.join(', ')} );`,
ids
),
db.run(
`DELETE FROM messages_fts WHERE id IN ( ${ids
.map(() => '?')
.join(', ')} );`,
ids
),
]);
await db.run('COMMIT TRANSACTION;');
} catch (error) {
await db.run('ROLLBACK;');
throw error;
}
} }
removeMessages.needsSerial = true;
async function getMessageById(id: string) { async function getMessageById(id: string) {
const db = getInstance(); const db = getInstance();