Use UUIDs in group database schema
This commit is contained in:
parent
74fde10ff5
commit
63fcdbe787
79 changed files with 4530 additions and 3664 deletions
417
ts/sql/migrations/43-gv2-uuid.ts
Normal file
417
ts/sql/migrations/43-gv2-uuid.ts
Normal file
|
@ -0,0 +1,417 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from 'better-sqlite3';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
import { assert } from '../../util/assert';
|
||||
import {
|
||||
TableIterator,
|
||||
getCountFromTable,
|
||||
jsonToObject,
|
||||
objectToJSON,
|
||||
} from '../util';
|
||||
import type { EmptyQuery, Query } from '../util';
|
||||
import type { MessageType, ConversationType } from '../Interface';
|
||||
|
||||
export default function updateToSchemaVersion43(
|
||||
currentVersion: number,
|
||||
db: Database,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
if (currentVersion >= 43) {
|
||||
return;
|
||||
}
|
||||
|
||||
type LegacyPendingMemberType = {
|
||||
addedByUserId?: string;
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
type LegacyAdminApprovalType = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
type LegacyConversationType = {
|
||||
id: string;
|
||||
membersV2?: Array<{
|
||||
conversationId: string;
|
||||
}>;
|
||||
pendingMembersV2?: Array<LegacyPendingMemberType>;
|
||||
pendingAdminApprovalV2?: Array<LegacyAdminApprovalType>;
|
||||
};
|
||||
|
||||
const getConversationUuid = db
|
||||
.prepare<Query>(
|
||||
`
|
||||
SELECT uuid
|
||||
FROM
|
||||
conversations
|
||||
WHERE
|
||||
id = $conversationId
|
||||
`
|
||||
)
|
||||
.pluck();
|
||||
|
||||
const updateConversationStmt = db.prepare(
|
||||
`
|
||||
UPDATE conversations SET
|
||||
json = $json,
|
||||
members = $members
|
||||
WHERE id = $id;
|
||||
`
|
||||
);
|
||||
|
||||
const updateMessageStmt = db.prepare(
|
||||
`
|
||||
UPDATE messages SET
|
||||
json = $json,
|
||||
sourceUuid = $sourceUuid
|
||||
WHERE id = $id;
|
||||
`
|
||||
);
|
||||
|
||||
const upgradeConversation = (convo: ConversationType) => {
|
||||
const legacy = (convo as unknown) as LegacyConversationType;
|
||||
let result = convo;
|
||||
|
||||
const memberKeys: Array<keyof LegacyConversationType> = [
|
||||
'membersV2',
|
||||
'pendingMembersV2',
|
||||
'pendingAdminApprovalV2',
|
||||
];
|
||||
for (const key of memberKeys) {
|
||||
const oldValue = legacy[key];
|
||||
if (!Array.isArray(oldValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let addedByCount = 0;
|
||||
|
||||
const newValue = oldValue
|
||||
.map(member => {
|
||||
const uuid: UUIDStringType = getConversationUuid.get({
|
||||
conversationId: member.conversationId,
|
||||
});
|
||||
if (!uuid) {
|
||||
logger.warn(
|
||||
`updateToSchemaVersion43: ${legacy.id}.${key} UUID not found ` +
|
||||
`for ${member.conversationId}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const updated = {
|
||||
...omit(member, 'conversationId'),
|
||||
uuid,
|
||||
};
|
||||
|
||||
// We previously stored our conversation
|
||||
if (!('addedByUserId' in member) || !member.addedByUserId) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
const addedByUserId:
|
||||
| UUIDStringType
|
||||
| undefined = getConversationUuid.get({
|
||||
conversationId: member.addedByUserId,
|
||||
});
|
||||
|
||||
if (!addedByUserId) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
addedByCount += 1;
|
||||
|
||||
return {
|
||||
...updated,
|
||||
addedByUserId,
|
||||
};
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
result = {
|
||||
...result,
|
||||
[key]: newValue,
|
||||
};
|
||||
|
||||
if (oldValue.length !== 0) {
|
||||
logger.info(
|
||||
`updateToSchemaVersion43: migrated ${oldValue.length} ${key} ` +
|
||||
`entries to ${newValue.length} for ${legacy.id}`
|
||||
);
|
||||
}
|
||||
|
||||
if (addedByCount > 0) {
|
||||
logger.info(
|
||||
`updateToSchemaVersion43: migrated ${addedByCount} addedByUserId ` +
|
||||
`in ${key} for ${legacy.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (result === convo) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dbMembers: string | null;
|
||||
if (result.membersV2) {
|
||||
dbMembers = result.membersV2.map(item => item.uuid).join(' ');
|
||||
} else if (result.members) {
|
||||
dbMembers = result.members.join(' ');
|
||||
} else {
|
||||
dbMembers = null;
|
||||
}
|
||||
|
||||
updateConversationStmt.run({
|
||||
id: result.id,
|
||||
json: objectToJSON(result),
|
||||
members: dbMembers,
|
||||
});
|
||||
};
|
||||
|
||||
type LegacyMessageType = {
|
||||
id: string;
|
||||
groupV2Change?: {
|
||||
from: string;
|
||||
details: Array<
|
||||
(
|
||||
| {
|
||||
type:
|
||||
| 'member-add'
|
||||
| 'member-add-from-invite'
|
||||
| 'member-add-from-link'
|
||||
| 'member-add-from-admin-approval'
|
||||
| 'member-privilege'
|
||||
| 'member-remove'
|
||||
| 'pending-add-one'
|
||||
| 'pending-remove-one'
|
||||
| 'admin-approval-add-one'
|
||||
| 'admin-approval-remove-one';
|
||||
conversationId: string;
|
||||
}
|
||||
| {
|
||||
type: unknown;
|
||||
conversationId?: undefined;
|
||||
}
|
||||
) &
|
||||
(
|
||||
| {
|
||||
type:
|
||||
| 'member-add-from-invite'
|
||||
| 'pending-remove-one'
|
||||
| 'pending-remove-many'
|
||||
| 'admin-approval-remove-one';
|
||||
inviter: string;
|
||||
}
|
||||
| {
|
||||
inviter?: undefined;
|
||||
}
|
||||
)
|
||||
>;
|
||||
};
|
||||
sourceUuid: string;
|
||||
invitedGV2Members?: Array<LegacyPendingMemberType>;
|
||||
};
|
||||
|
||||
const upgradeMessage = (message: MessageType): boolean => {
|
||||
const {
|
||||
id,
|
||||
groupV2Change,
|
||||
sourceUuid,
|
||||
invitedGV2Members,
|
||||
} = (message as unknown) as LegacyMessageType;
|
||||
let result = message;
|
||||
|
||||
if (groupV2Change) {
|
||||
assert(result.groupV2Change, 'Pacify typescript');
|
||||
|
||||
const from: UUIDStringType | undefined = getConversationUuid.get({
|
||||
conversationId: groupV2Change.from,
|
||||
});
|
||||
|
||||
if (from) {
|
||||
result = {
|
||||
...result,
|
||||
groupV2Change: {
|
||||
...result.groupV2Change,
|
||||
from,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
...result,
|
||||
groupV2Change: omit(result.groupV2Change, ['from']),
|
||||
};
|
||||
}
|
||||
|
||||
let changedDetails = false;
|
||||
const details = groupV2Change.details
|
||||
.map((legacyDetail, i) => {
|
||||
const oldDetail = result.groupV2Change?.details[i];
|
||||
assert(oldDetail, 'Pacify typescript');
|
||||
let newDetail = oldDetail;
|
||||
|
||||
for (const key of ['conversationId' as const, 'inviter' as const]) {
|
||||
const oldValue = legacyDetail[key];
|
||||
const newKey = key === 'conversationId' ? 'uuid' : key;
|
||||
|
||||
if (oldValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
changedDetails = true;
|
||||
|
||||
let newValue: UUIDStringType | null = getConversationUuid.get({
|
||||
conversationId: oldValue,
|
||||
});
|
||||
if (key === 'inviter') {
|
||||
newValue = newValue ?? UUID.cast(oldValue);
|
||||
}
|
||||
if (!newValue) {
|
||||
logger.warn(
|
||||
`updateToSchemaVersion43: ${id}.groupV2Change.details.${key} ` +
|
||||
`UUID not found for ${oldValue}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
assert(newDetail.type === legacyDetail.type, 'Pacify typescript');
|
||||
newDetail = {
|
||||
...omit(newDetail, key),
|
||||
[newKey]: newValue,
|
||||
};
|
||||
}
|
||||
|
||||
return newDetail;
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
if (changedDetails) {
|
||||
result = {
|
||||
...result,
|
||||
groupV2Change: {
|
||||
...result.groupV2Change,
|
||||
details,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceUuid) {
|
||||
const newValue: UUIDStringType =
|
||||
getConversationUuid.get({
|
||||
conversationId: sourceUuid,
|
||||
}) ?? UUID.cast(sourceUuid);
|
||||
|
||||
if (newValue !== sourceUuid) {
|
||||
result = {
|
||||
...result,
|
||||
sourceUuid: newValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (invitedGV2Members) {
|
||||
const newMembers = invitedGV2Members
|
||||
.map(({ addedByUserId, conversationId }, i) => {
|
||||
const uuid: UUIDStringType | null = getConversationUuid.get({
|
||||
conversationId,
|
||||
});
|
||||
const oldMember =
|
||||
result.invitedGV2Members && result.invitedGV2Members[i];
|
||||
assert(oldMember !== undefined, 'Pacify typescript');
|
||||
|
||||
if (!uuid) {
|
||||
logger.warn(
|
||||
`updateToSchemaVersion43: ${id}.invitedGV2Members UUID ` +
|
||||
`not found for ${conversationId}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const newMember = {
|
||||
...omit(oldMember, ['conversationId']),
|
||||
uuid,
|
||||
};
|
||||
|
||||
if (!addedByUserId) {
|
||||
return newMember;
|
||||
}
|
||||
|
||||
const newAddedBy: UUIDStringType | null = getConversationUuid.get({
|
||||
conversationId: addedByUserId,
|
||||
});
|
||||
if (!newAddedBy) {
|
||||
return newMember;
|
||||
}
|
||||
|
||||
return {
|
||||
...newMember,
|
||||
addedByUserId: newAddedBy,
|
||||
};
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
result = {
|
||||
...result,
|
||||
invitedGV2Members: newMembers,
|
||||
};
|
||||
}
|
||||
|
||||
if (result === message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
updateMessageStmt.run({
|
||||
id: result.id,
|
||||
json: JSON.stringify(result),
|
||||
sourceUuid: result.sourceUuid ?? null,
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
db.transaction(() => {
|
||||
const allConversations = db
|
||||
.prepare<EmptyQuery>(
|
||||
`
|
||||
SELECT json, profileLastFetchedAt
|
||||
FROM conversations
|
||||
ORDER BY id ASC;
|
||||
`
|
||||
)
|
||||
.all()
|
||||
.map(({ json }) => jsonToObject<ConversationType>(json));
|
||||
|
||||
logger.info(
|
||||
'updateToSchemaVersion43: About to iterate through ' +
|
||||
`${allConversations.length} conversations`
|
||||
);
|
||||
|
||||
for (const convo of allConversations) {
|
||||
upgradeConversation(convo);
|
||||
}
|
||||
|
||||
const messageCount = getCountFromTable(db, 'messages');
|
||||
logger.info(
|
||||
'updateToSchemaVersion43: About to iterate through ' +
|
||||
`${messageCount} messages`
|
||||
);
|
||||
|
||||
let updatedCount = 0;
|
||||
for (const message of new TableIterator<MessageType>(db, 'messages')) {
|
||||
if (upgradeMessage(message)) {
|
||||
updatedCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`updateToSchemaVersion43: Updated ${updatedCount} messages`);
|
||||
|
||||
db.pragma('user_version = 43');
|
||||
})();
|
||||
logger.info('updateToSchemaVersion43: success!');
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue