diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index a83905cf0..a8954a3b9 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -635,6 +635,7 @@ FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; }; FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */; }; FD559DF52A7368CB00C7C62A /* DispatchQueue+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */; }; + FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */; }; FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */; }; FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; }; FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; }; @@ -1755,6 +1756,7 @@ FD52090828B59411006098F6 /* ScreenLockUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockUI.swift; sourceTree = ""; }; FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = ""; }; FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Utilities.swift"; sourceTree = ""; }; + FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SQLInterpolation+Utilities.swift"; sourceTree = ""; }; FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = ""; }; FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = ""; }; FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = ""; }; @@ -3693,6 +3695,7 @@ FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */, FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */, FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */, + FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */, ); path = Utilities; sourceTree = ""; @@ -5666,6 +5669,7 @@ C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */, FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */, FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */, + FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */, FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, diff --git a/Session/Conversations/Message Cells/Content Views/QuoteView.swift b/Session/Conversations/Message Cells/Content Views/QuoteView.swift index bda79ae51..ea86a406c 100644 --- a/Session/Conversations/Message Cells/Content Views/QuoteView.swift +++ b/Session/Conversations/Message Cells/Content Views/QuoteView.swift @@ -156,7 +156,7 @@ final class QuoteView: UIView { if attachment.isVisualMedia { attachment.thumbnail( size: .small, - success: { image, _ in + success: { [imageView] image, _ in guard Thread.isMainThread else { DispatchQueue.main.async { imageView.image = image @@ -234,8 +234,6 @@ final class QuoteView: UIView { } // Label stack view - let bodyLabelSize = bodyLabel.systemLayoutSizeFitting(availableSpace) - let isCurrentUser: Bool = [ currentUserPublicKey, currentUserBlinded15PublicKey, @@ -288,9 +286,8 @@ final class QuoteView: UIView { cancelButton.set(.height, to: cancelButtonSize) cancelButton.addTarget(self, action: #selector(cancel), for: UIControl.Event.touchUpInside) - addSubview(cancelButton) + mainStackView.addArrangedSubview(cancelButton) cancelButton.center(.vertical, in: self) - cancelButton.pin(.right, to: .right, of: self) } } diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index bedc9fc31..9c1733e6c 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -522,7 +522,7 @@ extension Attachment { \(interaction[.id]) = \(interactionAttachment[.interactionId]) OR ( \(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND - \(Interaction.linkPreviewFilterLiteral) + \(Interaction.linkPreviewFilterLiteral()) ) ) @@ -568,7 +568,7 @@ extension Attachment { \(interaction[.id]) = \(interactionAttachment[.interactionId]) OR ( \(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND - \(Interaction.linkPreviewFilterLiteral) + \(Interaction.linkPreviewFilterLiteral()) ) ) diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index c50645ca0..ffecf09bd 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -29,13 +29,14 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu /// Whenever using this `linkPreview` association make sure to filter the result using /// `.filter(literal: Interaction.linkPreviewFilterLiteral)` to ensure the correct LinkPreview is returned public static let linkPreview = hasOne(LinkPreview.self, using: LinkPreview.interactionForeignKey) - public static var linkPreviewFilterLiteral: SQL = { - let interaction: TypedTableAlias = TypedTableAlias() - let linkPreview: TypedTableAlias = TypedTableAlias() + public static func linkPreviewFilterLiteral( + interaction: TypedTableAlias = TypedTableAlias(), + linkPreview: TypedTableAlias = TypedTableAlias() + ) -> SQL { let halfResolution: Double = LinkPreview.timstampResolution return "(\(interaction[.timestampMs]) BETWEEN (\(linkPreview[.timestamp]) - \(halfResolution)) * 1000 AND (\(linkPreview[.timestamp]) + \(halfResolution)) * 1000)" - }() + } public static let recipientStates = hasMany(RecipientState.self, using: RecipientState.interactionForeignKey) public typealias Columns = CodingKeys diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift index 7b5989579..96c28cf99 100644 --- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift @@ -143,7 +143,7 @@ public enum GarbageCollectionJob: JobExecutor { FROM \(LinkPreview.self) LEFT JOIN \(Interaction.self) ON ( \(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND - \(Interaction.linkPreviewFilterLiteral) + \(Interaction.linkPreviewFilterLiteral()) ) WHERE \(interaction[.id]) IS NULL ) diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 2a796cbd1..c4aeef7a6 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -777,10 +777,11 @@ public extension MessageViewModel { let disappearingMessagesConfig: TypedTableAlias = TypedTableAlias() let profile: TypedTableAlias = TypedTableAlias() let quote: TypedTableAlias = TypedTableAlias() + let quoteInteraction: TypedTableAlias = TypedTableAlias(name: "quoteInteraction") + let quoteLinkPreview: TypedTableAlias = TypedTableAlias(name: "quoteLinkPreview") let linkPreview: TypedTableAlias = TypedTableAlias() let threadProfile: SQL = SQL(stringLiteral: "threadProfile") - let quoteInteraction: SQL = SQL(stringLiteral: "quoteInteraction") let quoteInteractionAttachment: SQL = SQL(stringLiteral: "quoteInteractionAttachment") let readReceipt: SQL = SQL(stringLiteral: "readReceipt") let idColumn: SQL = SQL(stringLiteral: Interaction.Columns.id.name) @@ -845,7 +846,7 @@ public extension MessageViewModel { \(quote[.interactionId]), \(quote[.authorId]), \(quote[.timestampMs]), - \(quoteInteraction).\(interactionBodyColumn) AS \(quoteBodyColumn), + \(quoteInteraction[.body]), \(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) AS \(quoteAttachmentIdColumn), \(ViewModel.quoteAttachmentKey).*, \(ViewModel.linkPreviewKey).*, @@ -873,12 +874,12 @@ public extension MessageViewModel { LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(interaction[.threadId]) LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId]) LEFT JOIN \(Quote.self) ON \(quote[.interactionId]) = \(interaction[.id]) - LEFT JOIN \(Interaction.self) AS \(quoteInteraction) ON ( - \(quoteInteraction).\(timestampMsColumn) = \(quote[.timestampMs]) AND ( - \(quoteInteraction).\(authorIdColumn) = \(quote[.authorId]) OR ( + LEFT JOIN \(quoteInteraction) ON ( + \(quoteInteraction[.timestampMs]) = \(quote[.timestampMs]) AND ( + \(quoteInteraction[.authorId]) = \(quote[.authorId]) OR ( -- A users outgoing message is stored in some cases using their standard id -- but the quote will use their blinded id so handle that case - \(quoteInteraction).\(authorIdColumn) = \(userPublicKey) AND + \(quoteInteraction[.authorId]) = \(userPublicKey) AND ( \(quote[.authorId]) = \(blinded15PublicKey ?? "''") OR \(quote[.authorId]) = \(blinded25PublicKey ?? "''") @@ -887,14 +888,25 @@ public extension MessageViewModel { ) ) LEFT JOIN \(InteractionAttachment.self) AS \(quoteInteractionAttachment) ON ( - \(quoteInteractionAttachment).\(interactionAttachmentInteractionIdColumn) = \(quoteInteraction).\(idColumn) AND + \(quoteInteractionAttachment).\(interactionAttachmentInteractionIdColumn) = \(quoteInteraction[.id]) AND \(quoteInteractionAttachment).\(interactionAttachmentAlbumIndexColumn) = 0 ) - LEFT JOIN \(Attachment.self) AS \(ViewModel.quoteAttachmentKey) ON \(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) + LEFT JOIN \(quoteLinkPreview) ON ( + \(quoteLinkPreview[.url]) = \(quoteInteraction[.linkPreviewUrl]) AND + \(Interaction.linkPreviewFilterLiteral( + interaction: quoteInteraction, + linkPreview: quoteLinkPreview + )) + ) + LEFT JOIN \(Attachment.self) AS \(ViewModel.quoteAttachmentKey) ON ( + \(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) OR + \(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quoteLinkPreview[.attachmentId]) OR + \(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quote[.attachmentId]) + ) LEFT JOIN \(LinkPreview.self) ON ( \(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND - \(Interaction.linkPreviewFilterLiteral) + \(Interaction.linkPreviewFilterLiteral()) ) LEFT JOIN \(Attachment.self) AS \(ViewModel.linkPreviewAttachmentKey) ON \(ViewModel.linkPreviewAttachmentKey).\(attachmentIdColumn) = \(linkPreview[.attachmentId]) LEFT JOIN \(RecipientState.self) ON ( diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index c9d0c43eb..de2548745 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -712,7 +712,7 @@ public extension SessionThreadViewModel { ) LEFT JOIN \(LinkPreview.self) ON ( \(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND - \(Interaction.linkPreviewFilterLiteral) AND + \(Interaction.linkPreviewFilterLiteral()) AND \(SQL("\(linkPreview[.variant]) = \(LinkPreview.Variant.openGroupInvitation)")) ) LEFT JOIN \(InteractionAttachment.self) AS \(firstInteractionAttachmentLiteral) ON ( diff --git a/SessionUtilitiesKit/Database/Types/TypedTableAlias.swift b/SessionUtilitiesKit/Database/Types/TypedTableAlias.swift index 14dd0aacd..fa91388c4 100644 --- a/SessionUtilitiesKit/Database/Types/TypedTableAlias.swift +++ b/SessionUtilitiesKit/Database/Types/TypedTableAlias.swift @@ -4,9 +4,15 @@ import Foundation import GRDB public class TypedTableAlias where T: TableRecord, T: ColumnExpressible { - public let alias: TableAlias = TableAlias(name: T.databaseTableName) + internal let name: String + internal let tableName: String + public let alias: TableAlias - public init() {} + public init(name: String = T.databaseTableName) { + self.name = name + self.tableName = T.databaseTableName + self.alias = TableAlias(name: name) + } public subscript(_ column: T.Columns) -> SQLExpression { return alias[column.name] diff --git a/SessionUtilitiesKit/Database/Utilities/SQLInterpolation+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/SQLInterpolation+Utilities.swift new file mode 100644 index 000000000..d8918ef17 --- /dev/null +++ b/SessionUtilitiesKit/Database/Utilities/SQLInterpolation+Utilities.swift @@ -0,0 +1,21 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB + +public extension SQLInterpolation { + /// Appends the table name of the record type. + /// + /// // SELECT * FROM player + /// let player: TypedTableAlias = TypedTableAlias() + /// let request: SQLRequest = "SELECT * FROM \(player)" + @_disfavoredOverload + mutating func appendInterpolation(_ typedTableAlias: TypedTableAlias) { + let name: String = typedTableAlias.name + let tableName: String = typedTableAlias.tableName + + guard name != tableName else { return appendLiteral(tableName.quotedDatabaseIdentifier) } + + appendLiteral("\(tableName.quotedDatabaseIdentifier) AS \(name.quotedDatabaseIdentifier)") + } +}