From e5a5f7c45158aba4cd5c19fb3dfddba20d8fd914 Mon Sep 17 00:00:00 2001 From: Pete Walters Date: Thu, 24 Oct 2024 11:54:46 -0500 Subject: [PATCH] Cache the TSInteraction insert statement for backup restore. --- Scripts/sds_codegen/sds_generate.py | 44 ++++++++- .../MessageBackupInteractionStore.swift | 21 ++++- .../Interactions/TSInteraction+SDS.swift | 89 +++++++++++++++++++ 3 files changed, 151 insertions(+), 3 deletions(-) diff --git a/Scripts/sds_codegen/sds_generate.py b/Scripts/sds_codegen/sds_generate.py index aebf8cd0c4..1b494c7636 100755 --- a/Scripts/sds_codegen/sds_generate.py +++ b/Scripts/sds_codegen/sds_generate.py @@ -1128,7 +1128,8 @@ def generate_swift_extensions_for_model(clazz): return has_sds_superclass = clazz.has_sds_superclass() - has_remove_methods = clazz.name not in ("TSThread","TSInteraction") + has_remove_methods = clazz.name not in ("TSThread", "TSInteraction") + has_grdb_serializer = clazz.name in ("TSInteraction") swift_filename = os.path.basename(clazz.filepath) swift_filename = swift_filename[: swift_filename.find(".")] + "+SDS.swift" @@ -1758,6 +1759,43 @@ extension %(class_name)s: DeepCopyable { swift_body += """ } } +""" + + if has_grdb_serializer: + swift_body += """ +// MARK: - Table Metadata + +extension %sRecord { + + // This defines all of the columns used in the table + // where this model (and any subclasses) are persisted. + internal func asArguments() -> StatementArguments { + let databaseValues: [DatabaseValueConvertible?] = [ +""" % str( + remove_prefix_from_class_name(clazz.name) + ) + + def write_grdb_column_metadata(property): + # column_name = property.swift_identifier() + column_name = property.column_name() + return """ %s, + """ % ( + str(column_name) + ) + + for property in sds_properties: + if property.name != "id": + swift_body += write_grdb_column_metadata(property) + + if len(record_properties) > 0: + for property in record_properties: + swift_body += write_grdb_column_metadata(property) + + swift_body += """ + ] + return StatementArguments(databaseValues) + } +} """ if not has_sds_superclass: @@ -1817,7 +1855,9 @@ extension %sSerializer { SDSTableMetadata( tableName: "%s", columns: [ -""" % (database_table_name,) +""" % ( + database_table_name, + ) swift_body += "\n".join( [ " %sColumn," % str(column_property_name) diff --git a/SignalServiceKit/MessageBackup/Archivers/ChatItem/MessageBackupInteractionStore.swift b/SignalServiceKit/MessageBackup/Archivers/ChatItem/MessageBackupInteractionStore.swift index 0151cbb779..5ae45f4c0d 100644 --- a/SignalServiceKit/MessageBackup/Archivers/ChatItem/MessageBackupInteractionStore.swift +++ b/SignalServiceKit/MessageBackup/Archivers/ChatItem/MessageBackupInteractionStore.swift @@ -30,6 +30,9 @@ public final class MessageBackupInteractionStore { {} } + // Even generating the sql string itself is expensive when multiplied by 200k messages. + // So we generate the string once and cache it (on top of caching the Statement) + private var cachedSQL: String? func insert( _ interaction: TSInteraction, in thread: MessageBackup.ChatThread, @@ -67,7 +70,23 @@ public final class MessageBackupInteractionStore { // and restore, we'll only send back a Null message. (Until such a day // when resends use the interactions table and not MSL at all). - try interaction.asRecord().insert(context.tx.databaseConnection) + let sql: String + if let cachedSQL { + sql = cachedSQL + } else { + let columnsSQL = InteractionRecord.CodingKeys.allCases.filter({ $0 != .id }).map(\.name).joined(separator: ", ") + let valuesSQL = InteractionRecord.CodingKeys.allCases.filter({ $0 != .id }).map({ _ in "?" }).joined(separator: ", ") + sql = """ + INSERT INTO \(InteractionRecord.databaseTableName) (\(columnsSQL)) \ + VALUES (\(valuesSQL)) + """ + cachedSQL = sql + } + + let statement = try context.tx.databaseConnection.cachedStatement(sql: sql) + try statement.setUncheckedArguments((interaction.asRecord() as! InteractionRecord).asArguments()) + try statement.execute() + interaction.updateRowId(context.tx.databaseConnection.lastInsertedRowID) guard let interactionRowId = interaction.sqliteRowId else { throw OWSAssertionError("Missing row id after insertion!") diff --git a/SignalServiceKit/Messages/Interactions/TSInteraction+SDS.swift b/SignalServiceKit/Messages/Interactions/TSInteraction+SDS.swift index ab50e1c0b2..7d7e3ff8bf 100644 --- a/SignalServiceKit/Messages/Interactions/TSInteraction+SDS.swift +++ b/SignalServiceKit/Messages/Interactions/TSInteraction+SDS.swift @@ -6212,6 +6212,95 @@ extension TSInteraction: DeepCopyable { // MARK: - Table Metadata +extension InteractionRecord { + + // This defines all of the columns used in the table + // where this model (and any subclasses) are persisted. + internal func asArguments() -> StatementArguments { + let databaseValues: [DatabaseValueConvertible?] = [ + recordType, + uniqueId, + receivedAtTimestamp, + timestamp, + threadUniqueId, + attachmentIds, + authorId, + authorPhoneNumber, + authorUUID, + body, + callType, + configurationDurationSeconds, + configurationIsEnabled, + contactShare, + createdByRemoteName, + createdInExistingGroup, + customMessage, + envelopeData, + errorType, + expireStartedAt, + expiresAt, + expiresInSeconds, + groupMetaMessage, + hasLegacyMessageState, + hasSyncedTranscript, + wasNotCreatedLocally, + isLocalChange, + isViewOnceComplete, + isViewOnceMessage, + isVoiceMessage, + legacyMessageState, + legacyWasDelivered, + linkPreview, + messageId, + messageSticker, + messageType, + mostRecentFailureText, + preKeyBundle, + protocolVersion, + quotedMessage, + read, + recipientAddress, + recipientAddressStates, + sender, + serverTimestamp, + deprecated_sourceDeviceId, + storedMessageState, + storedShouldStartExpireTimer, + unregisteredAddress, + verificationState, + wasReceivedByUD, + infoMessageUserInfo, + wasRemotelyDeleted, + bodyRanges, + offerType, + serverDeliveryTimestamp, + eraId, + hasEnded, + creatorUuid, + joinedMemberUuids, + wasIdentityVerified, + paymentCancellation, + paymentNotification, + paymentRequest, + viewed, + serverGuid, + storyAuthorUuidString, + storyTimestamp, + isGroupStoryReply, + storyReactionEmoji, + giftBadge, + editState, + archivedPaymentInfo, + expireTimerVersion, + isSmsMessageRestoredFromBackup, + + ] + return StatementArguments(databaseValues) + } +} + +// MARK: - Table Metadata + extension TSInteractionSerializer { // This defines all of the columns used in the table