Cleaned up the GRDB interface for complex queries
This commit is contained in:
parent
42853a08c9
commit
e6c26e7ff4
|
@ -636,6 +636,7 @@
|
||||||
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.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 */; };
|
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 */; };
|
FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */; };
|
||||||
|
FD5931AB2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5931AA2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift */; };
|
||||||
FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.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 */; };
|
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 */; };
|
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; };
|
||||||
|
@ -1757,6 +1758,7 @@
|
||||||
FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = "<group>"; };
|
FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = "<group>"; };
|
||||||
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Utilities.swift"; sourceTree = "<group>"; };
|
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SQLInterpolation+Utilities.swift"; sourceTree = "<group>"; };
|
FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SQLInterpolation+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
|
FD5931AA2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScopeAdapter+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = "<group>"; };
|
FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = "<group>"; };
|
||||||
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = "<group>"; };
|
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = "<group>"; };
|
||||||
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
|
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -3695,6 +3697,7 @@
|
||||||
FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */,
|
FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */,
|
||||||
FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */,
|
FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */,
|
||||||
FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */,
|
FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */,
|
||||||
|
FD5931AA2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift */,
|
||||||
FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */,
|
FD5931A62A8DA5DA0040147D /* SQLInterpolation+Utilities.swift */,
|
||||||
);
|
);
|
||||||
path = Utilities;
|
path = Utilities;
|
||||||
|
@ -5681,6 +5684,7 @@
|
||||||
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
|
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
|
||||||
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
|
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
|
||||||
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */,
|
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */,
|
||||||
|
FD5931AB2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift in Sources */,
|
||||||
FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */,
|
FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */,
|
||||||
FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */,
|
FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */,
|
||||||
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
|
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
|
||||||
|
|
|
@ -203,6 +203,11 @@ class GlobalSearchViewController: BaseVC, SessionUtilRespondingViewController, U
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
// Don't log the 'interrupt' error as that's just the user typing too fast
|
||||||
|
if (error as? DatabaseError)?.resultCode != DatabaseError.SQLITE_INTERRUPT {
|
||||||
|
SNLog("[GlobalSearch] Failed to find results due to error: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
return .failure(error)
|
return .failure(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,16 +199,18 @@ public class MediaGalleryViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Item: FetchableRecordWithRowId, Decodable, Identifiable, Differentiable, Equatable, Hashable {
|
public struct Item: FetchableRecordWithRowId, Decodable, Identifiable, Differentiable, Equatable, Hashable, ColumnExpressible {
|
||||||
fileprivate static let interactionIdKey: SQL = SQL(stringLiteral: CodingKeys.interactionId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
fileprivate static let interactionVariantKey: SQL = SQL(stringLiteral: CodingKeys.interactionVariant.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
fileprivate static let interactionAuthorIdKey: SQL = SQL(stringLiteral: CodingKeys.interactionAuthorId.stringValue)
|
case interactionId
|
||||||
fileprivate static let interactionTimestampMsKey: SQL = SQL(stringLiteral: CodingKeys.interactionTimestampMs.stringValue)
|
case interactionVariant
|
||||||
fileprivate static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
case interactionAuthorId
|
||||||
fileprivate static let attachmentKey: SQL = SQL(stringLiteral: CodingKeys.attachment.stringValue)
|
case interactionTimestampMs
|
||||||
fileprivate static let attachmentAlbumIndexKey: SQL = SQL(stringLiteral: CodingKeys.attachmentAlbumIndex.stringValue)
|
|
||||||
|
case rowId
|
||||||
fileprivate static let attachmentString: String = CodingKeys.attachment.stringValue
|
case attachmentAlbumIndex
|
||||||
|
case attachment
|
||||||
|
}
|
||||||
|
|
||||||
public var id: String { attachment.id }
|
public var id: String { attachment.id }
|
||||||
public var differenceIdentifier: String { attachment.id }
|
public var differenceIdentifier: String { attachment.id }
|
||||||
|
@ -306,7 +308,7 @@ public class MediaGalleryViewModel {
|
||||||
let finalFilterSQL: SQL = {
|
let finalFilterSQL: SQL = {
|
||||||
guard let customFilters: SQL = customFilters else {
|
guard let customFilters: SQL = customFilters else {
|
||||||
return """
|
return """
|
||||||
WHERE \(attachment.alias[Column.rowID]) IN \(rowIds)
|
WHERE \(attachment[.rowId]) IN \(rowIds)
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,14 +320,14 @@ public class MediaGalleryViewModel {
|
||||||
}()
|
}()
|
||||||
let request: SQLRequest<Item> = """
|
let request: SQLRequest<Item> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(interaction[.id]) AS \(Item.interactionIdKey),
|
\(interaction[.id]) AS \(Item.Columns.interactionId),
|
||||||
\(interaction[.variant]) AS \(Item.interactionVariantKey),
|
\(interaction[.variant]) AS \(Item.Columns.interactionVariant),
|
||||||
\(interaction[.authorId]) AS \(Item.interactionAuthorIdKey),
|
\(interaction[.authorId]) AS \(Item.Columns.interactionAuthorId),
|
||||||
\(interaction[.timestampMs]) AS \(Item.interactionTimestampMsKey),
|
\(interaction[.timestampMs]) AS \(Item.Columns.interactionTimestampMs),
|
||||||
|
|
||||||
\(attachment.alias[Column.rowID]) AS \(Item.rowIdKey),
|
\(attachment[.rowId]) AS \(Item.Columns.rowId),
|
||||||
\(interactionAttachment[.albumIndex]) AS \(Item.attachmentAlbumIndexKey),
|
\(interactionAttachment[.albumIndex]) AS \(Item.Columns.attachmentAlbumIndex),
|
||||||
\(Item.attachmentKey).*
|
\(attachment.allColumns)
|
||||||
FROM \(Attachment.self)
|
FROM \(Attachment.self)
|
||||||
\(joinSQL)
|
\(joinSQL)
|
||||||
\(finalFilterSQL)
|
\(finalFilterSQL)
|
||||||
|
@ -338,8 +340,8 @@ public class MediaGalleryViewModel {
|
||||||
Attachment.numberOfSelectedColumns(db)
|
Attachment.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(Item.self, [
|
||||||
Item.attachmentString: adapters[1]
|
.attachment: adapters[1]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,11 +258,12 @@ class BlockedContactsViewModel: SessionTableViewModel<NoNav, BlockedContactsView
|
||||||
|
|
||||||
// MARK: - DataModel
|
// MARK: - DataModel
|
||||||
|
|
||||||
public struct DataModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable {
|
public struct DataModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable, ColumnExpressible {
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let profileKey: SQL = SQL(stringLiteral: CodingKeys.profile.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case rowId
|
||||||
public static let profileString: String = CodingKeys.profile.stringValue
|
case profile
|
||||||
|
}
|
||||||
|
|
||||||
public var differenceIdentifier: String { profile.id }
|
public var differenceIdentifier: String { profile.id }
|
||||||
public var id: String { profile.id }
|
public var id: String { profile.id }
|
||||||
|
@ -286,11 +287,11 @@ class BlockedContactsViewModel: SessionTableViewModel<NoNav, BlockedContactsView
|
||||||
|
|
||||||
let request: SQLRequest<DataModel> = """
|
let request: SQLRequest<DataModel> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(profile.alias[Column.rowID]) AS \(DataModel.rowIdKey),
|
\(profile[.rowId]) AS \(DataModel.Columns.rowId),
|
||||||
\(DataModel.profileKey).*
|
\(profile.allColumns)
|
||||||
|
|
||||||
FROM \(Profile.self)
|
FROM \(Profile.self)
|
||||||
WHERE \(profile.alias[Column.rowID]) IN \(rowIds)
|
WHERE \(profile[.rowId]) IN \(rowIds)
|
||||||
ORDER BY \(orderSQL)
|
ORDER BY \(orderSQL)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -300,8 +301,8 @@ class BlockedContactsViewModel: SessionTableViewModel<NoNav, BlockedContactsView
|
||||||
Profile.numberOfSelectedColumns(db)
|
Profile.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(DataModel.self, [
|
||||||
DataModel.profileString: adapters[1]
|
.profile: adapters[1]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,19 @@ public extension ClosedGroup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Search Queries
|
||||||
|
|
||||||
|
public extension ClosedGroup {
|
||||||
|
struct FullTextSearch: Decodable, ColumnExpressible {
|
||||||
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case name
|
||||||
|
}
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
public extension ClosedGroup {
|
public extension ClosedGroup {
|
||||||
|
|
|
@ -696,6 +696,17 @@ public extension Interaction {
|
||||||
// MARK: - Search Queries
|
// MARK: - Search Queries
|
||||||
|
|
||||||
public extension Interaction {
|
public extension Interaction {
|
||||||
|
struct FullTextSearch: Decodable, ColumnExpressible {
|
||||||
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case threadId
|
||||||
|
case body
|
||||||
|
}
|
||||||
|
|
||||||
|
let threadId: String
|
||||||
|
let body: String
|
||||||
|
}
|
||||||
|
|
||||||
struct TimestampInfo: FetchableRecord, Codable {
|
struct TimestampInfo: FetchableRecord, Codable {
|
||||||
public let id: Int64
|
public let id: Int64
|
||||||
public let timestampMs: Int64
|
public let timestampMs: Int64
|
||||||
|
@ -711,8 +722,7 @@ public extension Interaction {
|
||||||
|
|
||||||
static func idsForTermWithin(threadId: String, pattern: FTS5Pattern) -> SQLRequest<TimestampInfo> {
|
static func idsForTermWithin(threadId: String, pattern: FTS5Pattern) -> SQLRequest<TimestampInfo> {
|
||||||
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
|
let interaction: TypedTableAlias<Interaction> = TypedTableAlias()
|
||||||
let interactionFullTextSearch: SQL = SQL(stringLiteral: Interaction.fullTextSearchTableName)
|
let interactionFullTextSearch: TypedTableAlias<FullTextSearch> = TypedTableAlias(name: Interaction.fullTextSearchTableName)
|
||||||
let threadIdLiteral: SQL = SQL(stringLiteral: Interaction.Columns.threadId.name)
|
|
||||||
|
|
||||||
let request: SQLRequest<TimestampInfo> = """
|
let request: SQLRequest<TimestampInfo> = """
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -720,9 +730,9 @@ public extension Interaction {
|
||||||
\(interaction[.timestampMs])
|
\(interaction[.timestampMs])
|
||||||
FROM \(Interaction.self)
|
FROM \(Interaction.self)
|
||||||
JOIN \(interactionFullTextSearch) ON (
|
JOIN \(interactionFullTextSearch) ON (
|
||||||
\(interactionFullTextSearch).rowid = \(interaction.alias[Column.rowID]) AND
|
\(interactionFullTextSearch[.rowId]) = \(interaction[.rowId]) AND
|
||||||
\(SQL("\(interactionFullTextSearch).\(threadIdLiteral) = \(threadId)")) AND
|
\(SQL("\(interactionFullTextSearch[.threadId]) = \(threadId)")) AND
|
||||||
\(interactionFullTextSearch).\(SQL(stringLiteral: Interaction.Columns.body.name)) MATCH \(pattern)
|
\(interactionFullTextSearch[.body]) MATCH \(pattern)
|
||||||
)
|
)
|
||||||
|
|
||||||
ORDER BY \(interaction[.timestampMs].desc)
|
ORDER BY \(interaction[.timestampMs].desc)
|
||||||
|
|
|
@ -215,6 +215,19 @@ public extension OpenGroup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Search Queries
|
||||||
|
|
||||||
|
public extension OpenGroup {
|
||||||
|
struct FullTextSearch: Decodable, ColumnExpressible {
|
||||||
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case name
|
||||||
|
}
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
public extension OpenGroup {
|
public extension OpenGroup {
|
||||||
|
|
|
@ -298,6 +298,21 @@ public extension Profile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Search Queries
|
||||||
|
|
||||||
|
public extension Profile {
|
||||||
|
struct FullTextSearch: Decodable, ColumnExpressible {
|
||||||
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case nickname
|
||||||
|
case name
|
||||||
|
}
|
||||||
|
|
||||||
|
let nickname: String?
|
||||||
|
let name: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
public extension Profile {
|
public extension Profile {
|
||||||
|
|
|
@ -365,7 +365,7 @@ public extension SessionThread {
|
||||||
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
||||||
|
|
||||||
return """
|
return """
|
||||||
SELECT \(thread.allColumns())
|
SELECT \(thread.allColumns)
|
||||||
FROM \(SessionThread.self)
|
FROM \(SessionThread.self)
|
||||||
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
|
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
|
||||||
WHERE (
|
WHERE (
|
||||||
|
|
|
@ -82,7 +82,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Interaction.self)
|
DELETE FROM \(Interaction.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(interaction.alias[Column.rowID])
|
SELECT \(interaction[.rowId])
|
||||||
FROM \(Interaction.self)
|
FROM \(Interaction.self)
|
||||||
JOIN \(SessionThread.self) ON (
|
JOIN \(SessionThread.self) ON (
|
||||||
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND
|
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND
|
||||||
|
@ -90,7 +90,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
)
|
)
|
||||||
JOIN (
|
JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(\(interaction.alias[Column.rowID])) AS interactionCount,
|
COUNT(\(interaction[.rowId])) AS interactionCount,
|
||||||
\(interaction[.threadId])
|
\(interaction[.threadId])
|
||||||
FROM \(Interaction.self)
|
FROM \(Interaction.self)
|
||||||
GROUP BY \(interaction[.threadId])
|
GROUP BY \(interaction[.threadId])
|
||||||
|
@ -112,7 +112,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Job.self)
|
DELETE FROM \(Job.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(job.alias[Column.rowID])
|
SELECT \(job[.rowId])
|
||||||
FROM \(Job.self)
|
FROM \(Job.self)
|
||||||
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(job[.threadId])
|
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(job[.threadId])
|
||||||
LEFT JOIN \(Interaction.self) ON \(interaction[.id]) = \(job[.interactionId])
|
LEFT JOIN \(Interaction.self) ON \(interaction[.id]) = \(job[.interactionId])
|
||||||
|
@ -139,7 +139,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(LinkPreview.self)
|
DELETE FROM \(LinkPreview.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(linkPreview.alias[Column.rowID])
|
SELECT \(linkPreview[.rowId])
|
||||||
FROM \(LinkPreview.self)
|
FROM \(LinkPreview.self)
|
||||||
LEFT JOIN \(Interaction.self) ON (
|
LEFT JOIN \(Interaction.self) ON (
|
||||||
\(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND
|
\(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND
|
||||||
|
@ -159,7 +159,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(OpenGroup.self)
|
DELETE FROM \(OpenGroup.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(openGroup.alias[Column.rowID])
|
SELECT \(openGroup[.rowId])
|
||||||
FROM \(OpenGroup.self)
|
FROM \(OpenGroup.self)
|
||||||
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(openGroup[.threadId])
|
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(openGroup[.threadId])
|
||||||
WHERE (
|
WHERE (
|
||||||
|
@ -178,7 +178,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Capability.self)
|
DELETE FROM \(Capability.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(capability.alias[Column.rowID])
|
SELECT \(capability[.rowId])
|
||||||
FROM \(Capability.self)
|
FROM \(Capability.self)
|
||||||
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.server]) = \(capability[.openGroupServer])
|
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.server]) = \(capability[.openGroupServer])
|
||||||
WHERE \(openGroup[.threadId]) IS NULL
|
WHERE \(openGroup[.threadId]) IS NULL
|
||||||
|
@ -195,7 +195,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(BlindedIdLookup.self)
|
DELETE FROM \(BlindedIdLookup.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(blindedIdLookup.alias[Column.rowID])
|
SELECT \(blindedIdLookup[.rowId])
|
||||||
FROM \(BlindedIdLookup.self)
|
FROM \(BlindedIdLookup.self)
|
||||||
LEFT JOIN \(SessionThread.self) ON (
|
LEFT JOIN \(SessionThread.self) ON (
|
||||||
\(thread[.id]) = \(blindedIdLookup[.blindedId]) OR
|
\(thread[.id]) = \(blindedIdLookup[.blindedId]) OR
|
||||||
|
@ -222,7 +222,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Contact.self)
|
DELETE FROM \(Contact.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(contact.alias[Column.rowID])
|
SELECT \(contact[.rowId])
|
||||||
FROM \(Contact.self)
|
FROM \(Contact.self)
|
||||||
LEFT JOIN \(BlindedIdLookup.self) ON (
|
LEFT JOIN \(BlindedIdLookup.self) ON (
|
||||||
\(blindedIdLookup[.blindedId]) = \(contact[.id]) AND
|
\(blindedIdLookup[.blindedId]) = \(contact[.id]) AND
|
||||||
|
@ -243,7 +243,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Attachment.self)
|
DELETE FROM \(Attachment.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(attachment.alias[Column.rowID])
|
SELECT \(attachment[.rowId])
|
||||||
FROM \(Attachment.self)
|
FROM \(Attachment.self)
|
||||||
LEFT JOIN \(Quote.self) ON \(quote[.attachmentId]) = \(attachment[.id])
|
LEFT JOIN \(Quote.self) ON \(quote[.attachmentId]) = \(attachment[.id])
|
||||||
LEFT JOIN \(LinkPreview.self) ON \(linkPreview[.attachmentId]) = \(attachment[.id])
|
LEFT JOIN \(LinkPreview.self) ON \(linkPreview[.attachmentId]) = \(attachment[.id])
|
||||||
|
@ -269,7 +269,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(Profile.self)
|
DELETE FROM \(Profile.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(profile.alias[Column.rowID])
|
SELECT \(profile[.rowId])
|
||||||
FROM \(Profile.self)
|
FROM \(Profile.self)
|
||||||
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(profile[.id])
|
LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(profile[.id])
|
||||||
LEFT JOIN \(Interaction.self) ON \(interaction[.authorId]) = \(profile[.id])
|
LEFT JOIN \(Interaction.self) ON \(interaction[.authorId]) = \(profile[.id])
|
||||||
|
@ -310,7 +310,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
try db.execute(literal: """
|
try db.execute(literal: """
|
||||||
DELETE FROM \(SessionThread.self)
|
DELETE FROM \(SessionThread.self)
|
||||||
WHERE \(Column.rowID) IN (
|
WHERE \(Column.rowID) IN (
|
||||||
SELECT \(thread.alias[Column.rowID])
|
SELECT \(thread[.rowId])
|
||||||
FROM \(SessionThread.self)
|
FROM \(SessionThread.self)
|
||||||
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
|
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])
|
||||||
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id])
|
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id])
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
import GRDB
|
import GRDB
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
public struct MentionInfo: FetchableRecord, Decodable {
|
public struct MentionInfo: FetchableRecord, Decodable, ColumnExpressible {
|
||||||
fileprivate static let threadVariantKey: SQL = SQL(stringLiteral: CodingKeys.threadVariant.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
fileprivate static let openGroupServerKey: SQL = SQL(stringLiteral: CodingKeys.openGroupServer.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
fileprivate static let openGroupRoomTokenKey: SQL = SQL(stringLiteral: CodingKeys.openGroupRoomToken.stringValue)
|
case profile
|
||||||
|
case threadVariant
|
||||||
fileprivate static let profileString: String = CodingKeys.profile.stringValue
|
case openGroupServer
|
||||||
|
case openGroupRoomToken
|
||||||
|
}
|
||||||
|
|
||||||
public let profile: Profile
|
public let profile: Profile
|
||||||
public let threadVariant: SessionThread.Variant
|
public let threadVariant: SessionThread.Variant
|
||||||
|
@ -79,7 +81,7 @@ public extension MentionInfo {
|
||||||
return SQLRequest("""
|
return SQLRequest("""
|
||||||
SELECT
|
SELECT
|
||||||
\(Profile.self).*,
|
\(Profile.self).*,
|
||||||
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)"))
|
\(SQL("\(threadVariant) AS \(MentionInfo.Columns.threadVariant)"))
|
||||||
|
|
||||||
\(targetJoin)
|
\(targetJoin)
|
||||||
\(targetWhere) AND \(SQL("\(profile[.id]) = \(threadId)"))
|
\(targetWhere) AND \(SQL("\(profile[.id]) = \(threadId)"))
|
||||||
|
@ -89,7 +91,7 @@ public extension MentionInfo {
|
||||||
return SQLRequest("""
|
return SQLRequest("""
|
||||||
SELECT
|
SELECT
|
||||||
\(Profile.self).*,
|
\(Profile.self).*,
|
||||||
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)"))
|
\(SQL("\(threadVariant) AS \(MentionInfo.Columns.threadVariant)"))
|
||||||
|
|
||||||
\(targetJoin)
|
\(targetJoin)
|
||||||
JOIN \(GroupMember.self) ON (
|
JOIN \(GroupMember.self) ON (
|
||||||
|
@ -107,9 +109,9 @@ public extension MentionInfo {
|
||||||
SELECT
|
SELECT
|
||||||
\(Profile.self).*,
|
\(Profile.self).*,
|
||||||
MAX(\(interaction[.timestampMs])), -- Want the newest interaction (for sorting)
|
MAX(\(interaction[.timestampMs])), -- Want the newest interaction (for sorting)
|
||||||
\(SQL("\(threadVariant) AS \(MentionInfo.threadVariantKey)")),
|
\(SQL("\(threadVariant) AS \(MentionInfo.Columns.threadVariant)")),
|
||||||
\(openGroup[.server]) AS \(MentionInfo.openGroupServerKey),
|
\(openGroup[.server]) AS \(MentionInfo.Columns.openGroupServer),
|
||||||
\(openGroup[.roomToken]) AS \(MentionInfo.openGroupRoomTokenKey)
|
\(openGroup[.roomToken]) AS \(MentionInfo.Columns.openGroupRoomToken)
|
||||||
|
|
||||||
\(targetJoin)
|
\(targetJoin)
|
||||||
JOIN \(Interaction.self) ON (
|
JOIN \(Interaction.self) ON (
|
||||||
|
@ -130,8 +132,8 @@ public extension MentionInfo {
|
||||||
Profile.numberOfSelectedColumns(db)
|
Profile.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(MentionInfo.self, [
|
||||||
MentionInfo.profileString: adapters[0]
|
.profile: adapters[0]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,42 +11,66 @@ fileprivate typealias AttachmentInteractionInfo = MessageViewModel.AttachmentInt
|
||||||
fileprivate typealias ReactionInfo = MessageViewModel.ReactionInfo
|
fileprivate typealias ReactionInfo = MessageViewModel.ReactionInfo
|
||||||
fileprivate typealias TypingIndicatorInfo = MessageViewModel.TypingIndicatorInfo
|
fileprivate typealias TypingIndicatorInfo = MessageViewModel.TypingIndicatorInfo
|
||||||
|
|
||||||
public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable {
|
public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, Hashable, Identifiable, Differentiable, ColumnExpressible {
|
||||||
public static let threadIdKey: SQL = SQL(stringLiteral: CodingKeys.threadId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let threadVariantKey: SQL = SQL(stringLiteral: CodingKeys.threadVariant.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
public static let threadIsTrustedKey: SQL = SQL(stringLiteral: CodingKeys.threadIsTrusted.stringValue)
|
case threadId
|
||||||
public static let threadHasDisappearingMessagesEnabledKey: SQL = SQL(stringLiteral: CodingKeys.threadHasDisappearingMessagesEnabled.stringValue)
|
case threadVariant
|
||||||
public static let threadOpenGroupServerKey: SQL = SQL(stringLiteral: CodingKeys.threadOpenGroupServer.stringValue)
|
case threadIsTrusted
|
||||||
public static let threadOpenGroupPublicKeyKey: SQL = SQL(stringLiteral: CodingKeys.threadOpenGroupPublicKey.stringValue)
|
case threadHasDisappearingMessagesEnabled
|
||||||
public static let threadContactNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.threadContactNameInternal.stringValue)
|
case threadOpenGroupServer
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
case threadOpenGroupPublicKey
|
||||||
public static let authorNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.authorNameInternal.stringValue)
|
case threadContactNameInternal
|
||||||
public static let stateKey: SQL = SQL(stringLiteral: CodingKeys.state.stringValue)
|
|
||||||
public static let hasAtLeastOneReadReceiptKey: SQL = SQL(stringLiteral: CodingKeys.hasAtLeastOneReadReceipt.stringValue)
|
// Interaction Info
|
||||||
public static let mostRecentFailureTextKey: SQL = SQL(stringLiteral: CodingKeys.mostRecentFailureText.stringValue)
|
|
||||||
public static let isTypingIndicatorKey: SQL = SQL(stringLiteral: CodingKeys.isTypingIndicator.stringValue)
|
case rowId
|
||||||
public static let isSenderOpenGroupModeratorKey: SQL = SQL(stringLiteral: CodingKeys.isSenderOpenGroupModerator.stringValue)
|
case id
|
||||||
public static let profileKey: SQL = SQL(stringLiteral: CodingKeys.profile.stringValue)
|
case openGroupServerMessageId
|
||||||
public static let quoteKey: SQL = SQL(stringLiteral: CodingKeys.quote.stringValue)
|
case variant
|
||||||
public static let quoteAttachmentKey: SQL = SQL(stringLiteral: CodingKeys.quoteAttachment.stringValue)
|
case timestampMs
|
||||||
public static let linkPreviewKey: SQL = SQL(stringLiteral: CodingKeys.linkPreview.stringValue)
|
case receivedAtTimestampMs
|
||||||
public static let linkPreviewAttachmentKey: SQL = SQL(stringLiteral: CodingKeys.linkPreviewAttachment.stringValue)
|
case authorId
|
||||||
public static let currentUserPublicKeyKey: SQL = SQL(stringLiteral: CodingKeys.currentUserPublicKey.stringValue)
|
case authorNameInternal
|
||||||
public static let cellTypeKey: SQL = SQL(stringLiteral: CodingKeys.cellType.stringValue)
|
case body
|
||||||
public static let authorNameKey: SQL = SQL(stringLiteral: CodingKeys.authorName.stringValue)
|
case rawBody
|
||||||
public static let canHaveProfileKey: SQL = SQL(stringLiteral: CodingKeys.canHaveProfile.stringValue)
|
case expiresStartedAtMs
|
||||||
public static let shouldShowProfileKey: SQL = SQL(stringLiteral: CodingKeys.shouldShowProfile.stringValue)
|
case expiresInSeconds
|
||||||
public static let shouldShowDateHeaderKey: SQL = SQL(stringLiteral: CodingKeys.shouldShowDateHeader.stringValue)
|
|
||||||
public static let positionInClusterKey: SQL = SQL(stringLiteral: CodingKeys.positionInCluster.stringValue)
|
case state
|
||||||
public static let isOnlyMessageInClusterKey: SQL = SQL(stringLiteral: CodingKeys.isOnlyMessageInCluster.stringValue)
|
case hasAtLeastOneReadReceipt
|
||||||
public static let isLastKey: SQL = SQL(stringLiteral: CodingKeys.isLast.stringValue)
|
case mostRecentFailureText
|
||||||
public static let isLastOutgoingKey: SQL = SQL(stringLiteral: CodingKeys.isLastOutgoing.stringValue)
|
case isSenderOpenGroupModerator
|
||||||
|
case isTypingIndicator
|
||||||
public static let profileString: String = CodingKeys.profile.stringValue
|
case profile
|
||||||
public static let quoteString: String = CodingKeys.quote.stringValue
|
case quote
|
||||||
public static let quoteAttachmentString: String = CodingKeys.quoteAttachment.stringValue
|
case quoteAttachment
|
||||||
public static let linkPreviewString: String = CodingKeys.linkPreview.stringValue
|
case linkPreview
|
||||||
public static let linkPreviewAttachmentString: String = CodingKeys.linkPreviewAttachment.stringValue
|
case linkPreviewAttachment
|
||||||
|
|
||||||
|
case currentUserPublicKey
|
||||||
|
|
||||||
|
// Post-Query Processing Data
|
||||||
|
|
||||||
|
case attachments
|
||||||
|
case reactionInfo
|
||||||
|
case cellType
|
||||||
|
case authorName
|
||||||
|
case senderName
|
||||||
|
case canHaveProfile
|
||||||
|
case shouldShowProfile
|
||||||
|
case shouldShowDateHeader
|
||||||
|
case containsOnlyEmoji
|
||||||
|
case glyphCount
|
||||||
|
case previousVariant
|
||||||
|
case positionInCluster
|
||||||
|
case isOnlyMessageInCluster
|
||||||
|
case isLast
|
||||||
|
case isLastOutgoing
|
||||||
|
case currentUserBlinded15PublicKey
|
||||||
|
case currentUserBlinded25PublicKey
|
||||||
|
case optimisticMessageId
|
||||||
|
}
|
||||||
|
|
||||||
public enum CellType: Int, Decodable, Equatable, Hashable, DatabaseValueConvertible {
|
public enum CellType: Int, Decodable, Equatable, Hashable, DatabaseValueConvertible {
|
||||||
case textOnlyMessage
|
case textOnlyMessage
|
||||||
|
@ -462,13 +486,13 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable,
|
||||||
// MARK: - AttachmentInteractionInfo
|
// MARK: - AttachmentInteractionInfo
|
||||||
|
|
||||||
public extension MessageViewModel {
|
public extension MessageViewModel {
|
||||||
struct AttachmentInteractionInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, Comparable {
|
struct AttachmentInteractionInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, Comparable, ColumnExpressible {
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let attachmentKey: SQL = SQL(stringLiteral: CodingKeys.attachment.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
public static let interactionAttachmentKey: SQL = SQL(stringLiteral: CodingKeys.interactionAttachment.stringValue)
|
case rowId
|
||||||
|
case attachment
|
||||||
public static let attachmentString: String = CodingKeys.attachment.stringValue
|
case interactionAttachment
|
||||||
public static let interactionAttachmentString: String = CodingKeys.interactionAttachment.stringValue
|
}
|
||||||
|
|
||||||
public let rowId: Int64
|
public let rowId: Int64
|
||||||
public let attachment: Attachment
|
public let attachment: Attachment
|
||||||
|
@ -491,13 +515,13 @@ public extension MessageViewModel {
|
||||||
// MARK: - ReactionInfo
|
// MARK: - ReactionInfo
|
||||||
|
|
||||||
public extension MessageViewModel {
|
public extension MessageViewModel {
|
||||||
struct ReactionInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, Comparable, Hashable, Differentiable {
|
struct ReactionInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, Comparable, Hashable, Differentiable, ColumnExpressible {
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let reactionKey: SQL = SQL(stringLiteral: CodingKeys.reaction.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
public static let profileKey: SQL = SQL(stringLiteral: CodingKeys.profile.stringValue)
|
case rowId
|
||||||
|
case reaction
|
||||||
public static let reactionString: String = CodingKeys.reaction.stringValue
|
case profile
|
||||||
public static let profileString: String = CodingKeys.profile.stringValue
|
}
|
||||||
|
|
||||||
public let rowId: Int64
|
public let rowId: Int64
|
||||||
public let reaction: Reaction
|
public let reaction: Reaction
|
||||||
|
@ -522,9 +546,12 @@ public extension MessageViewModel {
|
||||||
// MARK: - TypingIndicatorInfo
|
// MARK: - TypingIndicatorInfo
|
||||||
|
|
||||||
public extension MessageViewModel {
|
public extension MessageViewModel {
|
||||||
struct TypingIndicatorInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable {
|
struct TypingIndicatorInfo: FetchableRecordWithRowId, Decodable, Identifiable, Equatable, ColumnExpressible {
|
||||||
public static let rowIdKey: SQL = SQL(stringLiteral: CodingKeys.rowId.stringValue)
|
public typealias Columns = CodingKeys
|
||||||
public static let threadIdKey: SQL = SQL(stringLiteral: CodingKeys.threadId.stringValue)
|
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||||||
|
case rowId
|
||||||
|
case threadId
|
||||||
|
}
|
||||||
|
|
||||||
public let rowId: Int64
|
public let rowId: Int64
|
||||||
public let threadId: String
|
public let threadId: String
|
||||||
|
@ -776,60 +803,48 @@ public extension MessageViewModel {
|
||||||
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
||||||
let disappearingMessagesConfig: TypedTableAlias<DisappearingMessagesConfiguration> = TypedTableAlias()
|
let disappearingMessagesConfig: TypedTableAlias<DisappearingMessagesConfiguration> = TypedTableAlias()
|
||||||
let profile: TypedTableAlias<Profile> = TypedTableAlias()
|
let profile: TypedTableAlias<Profile> = TypedTableAlias()
|
||||||
|
let threadProfile: TypedTableAlias<Profile> = TypedTableAlias(name: "threadProfile")
|
||||||
let quote: TypedTableAlias<Quote> = TypedTableAlias()
|
let quote: TypedTableAlias<Quote> = TypedTableAlias()
|
||||||
let quoteInteraction: TypedTableAlias<Interaction> = TypedTableAlias(name: "quoteInteraction")
|
let quoteInteraction: TypedTableAlias<Interaction> = TypedTableAlias(name: "quoteInteraction")
|
||||||
|
let quoteInteractionAttachment: TypedTableAlias<InteractionAttachment> = TypedTableAlias(
|
||||||
|
name: "quoteInteractionAttachment"
|
||||||
|
)
|
||||||
let quoteLinkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias(name: "quoteLinkPreview")
|
let quoteLinkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias(name: "quoteLinkPreview")
|
||||||
|
let quoteAttachment: TypedTableAlias<Attachment> = TypedTableAlias(name: ViewModel.CodingKeys.quoteAttachment.stringValue)
|
||||||
let linkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias()
|
let linkPreview: TypedTableAlias<LinkPreview> = TypedTableAlias()
|
||||||
|
let linkPreviewAttachment: TypedTableAlias<Attachment> = TypedTableAlias(ViewModel.self, column: .linkPreviewAttachment)
|
||||||
let threadProfile: SQL = SQL(stringLiteral: "threadProfile")
|
let readReceipt: TypedTableAlias<RecipientState> = TypedTableAlias(name: "readReceipt")
|
||||||
let quoteInteractionAttachment: SQL = SQL(stringLiteral: "quoteInteractionAttachment")
|
|
||||||
let readReceipt: SQL = SQL(stringLiteral: "readReceipt")
|
|
||||||
let idColumn: SQL = SQL(stringLiteral: Interaction.Columns.id.name)
|
|
||||||
let interactionBodyColumn: SQL = SQL(stringLiteral: Interaction.Columns.body.name)
|
|
||||||
let profileIdColumn: SQL = SQL(stringLiteral: Profile.Columns.id.name)
|
|
||||||
let nicknameColumn: SQL = SQL(stringLiteral: Profile.Columns.nickname.name)
|
|
||||||
let nameColumn: SQL = SQL(stringLiteral: Profile.Columns.name.name)
|
|
||||||
let quoteBodyColumn: SQL = SQL(stringLiteral: Quote.Columns.body.name)
|
|
||||||
let quoteAttachmentIdColumn: SQL = SQL(stringLiteral: Quote.Columns.attachmentId.name)
|
|
||||||
let readReceiptInteractionIdColumn: SQL = SQL(stringLiteral: RecipientState.Columns.interactionId.name)
|
|
||||||
let readTimestampMsColumn: SQL = SQL(stringLiteral: RecipientState.Columns.readTimestampMs.name)
|
|
||||||
let timestampMsColumn: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name)
|
|
||||||
let authorIdColumn: SQL = SQL(stringLiteral: Interaction.Columns.authorId.name)
|
|
||||||
let attachmentIdColumn: SQL = SQL(stringLiteral: Attachment.Columns.id.name)
|
|
||||||
let interactionAttachmentInteractionIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.interactionId.name)
|
|
||||||
let interactionAttachmentAttachmentIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.attachmentId.name)
|
|
||||||
let interactionAttachmentAlbumIndexColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.albumIndex.name)
|
|
||||||
|
|
||||||
let numColumnsBeforeLinkedRecords: Int = 22
|
let numColumnsBeforeLinkedRecords: Int = 22
|
||||||
let finalGroupSQL: SQL = (groupSQL ?? "")
|
let finalGroupSQL: SQL = (groupSQL ?? "")
|
||||||
let request: SQLRequest<ViewModel> = """
|
let request: SQLRequest<ViewModel> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(thread[.id]) AS \(ViewModel.threadIdKey),
|
\(thread[.id]) AS \(ViewModel.Columns.threadId),
|
||||||
\(thread[.variant]) AS \(ViewModel.threadVariantKey),
|
\(thread[.variant]) AS \(ViewModel.Columns.threadVariant),
|
||||||
-- Default to 'true' for non-contact threads
|
-- Default to 'true' for non-contact threads
|
||||||
IFNULL(\(contact[.isTrusted]), true) AS \(ViewModel.threadIsTrustedKey),
|
IFNULL(\(contact[.isTrusted]), true) AS \(ViewModel.Columns.threadIsTrusted),
|
||||||
-- Default to 'false' when no contact exists
|
-- Default to 'false' when no contact exists
|
||||||
IFNULL(\(disappearingMessagesConfig[.isEnabled]), false) AS \(ViewModel.threadHasDisappearingMessagesEnabledKey),
|
IFNULL(\(disappearingMessagesConfig[.isEnabled]), false) AS \(ViewModel.Columns.threadHasDisappearingMessagesEnabled),
|
||||||
\(openGroup[.server]) AS \(ViewModel.threadOpenGroupServerKey),
|
\(openGroup[.server]) AS \(ViewModel.Columns.threadOpenGroupServer),
|
||||||
\(openGroup[.publicKey]) AS \(ViewModel.threadOpenGroupPublicKeyKey),
|
\(openGroup[.publicKey]) AS \(ViewModel.Columns.threadOpenGroupPublicKey),
|
||||||
IFNULL(\(threadProfile).\(nicknameColumn), \(threadProfile).\(nameColumn)) AS \(ViewModel.threadContactNameInternalKey),
|
IFNULL(\(threadProfile[.nickname]), \(threadProfile[.name])) AS \(ViewModel.Columns.threadContactNameInternal),
|
||||||
|
|
||||||
\(interaction.alias[Column.rowID]) AS \(ViewModel.rowIdKey),
|
\(interaction[.rowId]) AS \(ViewModel.Columns.rowId),
|
||||||
\(interaction[.id]),
|
\(interaction[.id]),
|
||||||
\(interaction[.openGroupServerMessageId]),
|
\(interaction[.openGroupServerMessageId]),
|
||||||
\(interaction[.variant]),
|
\(interaction[.variant]),
|
||||||
\(interaction[.timestampMs]),
|
\(interaction[.timestampMs]),
|
||||||
\(interaction[.receivedAtTimestampMs]),
|
\(interaction[.receivedAtTimestampMs]),
|
||||||
\(interaction[.authorId]),
|
\(interaction[.authorId]),
|
||||||
IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.authorNameInternalKey),
|
IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.Columns.authorNameInternal),
|
||||||
\(interaction[.body]),
|
\(interaction[.body]),
|
||||||
\(interaction[.expiresStartedAtMs]),
|
\(interaction[.expiresStartedAtMs]),
|
||||||
\(interaction[.expiresInSeconds]),
|
\(interaction[.expiresInSeconds]),
|
||||||
|
|
||||||
-- Default to 'sending' assuming non-processed interaction when null
|
-- Default to 'sending' assuming non-processed interaction when null
|
||||||
IFNULL(MIN(\(recipientState[.state])), \(SQL("\(RecipientState.State.sending)"))) AS \(ViewModel.stateKey),
|
IFNULL(MIN(\(recipientState[.state])), \(SQL("\(RecipientState.State.sending)"))) AS \(ViewModel.Columns.state),
|
||||||
(\(readReceipt).\(readTimestampMsColumn) IS NOT NULL) AS \(ViewModel.hasAtLeastOneReadReceiptKey),
|
(\(readReceipt[.readTimestampMs]) IS NOT NULL) AS \(ViewModel.Columns.hasAtLeastOneReadReceipt),
|
||||||
\(recipientState[.mostRecentFailureText]) AS \(ViewModel.mostRecentFailureTextKey),
|
\(recipientState[.mostRecentFailureText]) AS \(ViewModel.Columns.mostRecentFailureText),
|
||||||
|
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
|
@ -840,36 +855,36 @@ public extension MessageViewModel {
|
||||||
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND
|
\(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND
|
||||||
\(SQL("\(groupMember[.role]) IN \([GroupMember.Role.moderator, GroupMember.Role.admin])"))
|
\(SQL("\(groupMember[.role]) IN \([GroupMember.Role.moderator, GroupMember.Role.admin])"))
|
||||||
)
|
)
|
||||||
) AS \(ViewModel.isSenderOpenGroupModeratorKey),
|
) AS \(ViewModel.Columns.isSenderOpenGroupModerator),
|
||||||
|
|
||||||
\(ViewModel.profileKey).*,
|
\(profile.allColumns),
|
||||||
\(quote[.interactionId]),
|
\(quote[.interactionId]),
|
||||||
\(quote[.authorId]),
|
\(quote[.authorId]),
|
||||||
\(quote[.timestampMs]),
|
\(quote[.timestampMs]),
|
||||||
\(quoteInteraction[.body]),
|
\(quoteInteraction[.body]),
|
||||||
\(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) AS \(quoteAttachmentIdColumn),
|
\(quoteInteractionAttachment[.attachmentId]),
|
||||||
\(ViewModel.quoteAttachmentKey).*,
|
\(quoteAttachment.allColumns),
|
||||||
\(ViewModel.linkPreviewKey).*,
|
\(linkPreview.allColumns),
|
||||||
\(ViewModel.linkPreviewAttachmentKey).*,
|
\(linkPreviewAttachment.allColumns),
|
||||||
|
|
||||||
\(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey),
|
\(SQL("\(userPublicKey)")) AS \(ViewModel.Columns.currentUserPublicKey),
|
||||||
|
|
||||||
-- All of the below properties are set in post-query processing but to prevent the
|
-- All of the below properties are set in post-query processing but to prevent the
|
||||||
-- query from crashing when decoding we need to provide default values
|
-- query from crashing when decoding we need to provide default values
|
||||||
\(CellType.textOnlyMessage) AS \(ViewModel.cellTypeKey),
|
\(CellType.textOnlyMessage) AS \(ViewModel.Columns.cellType),
|
||||||
'' AS \(ViewModel.authorNameKey),
|
'' AS \(ViewModel.Columns.authorName),
|
||||||
false AS \(ViewModel.canHaveProfileKey),
|
false AS \(ViewModel.Columns.canHaveProfile),
|
||||||
false AS \(ViewModel.shouldShowProfileKey),
|
false AS \(ViewModel.Columns.shouldShowProfile),
|
||||||
false AS \(ViewModel.shouldShowDateHeaderKey),
|
false AS \(ViewModel.Columns.shouldShowDateHeader),
|
||||||
\(Position.middle) AS \(ViewModel.positionInClusterKey),
|
\(Position.middle) AS \(ViewModel.Columns.positionInCluster),
|
||||||
false AS \(ViewModel.isOnlyMessageInClusterKey),
|
false AS \(ViewModel.Columns.isOnlyMessageInCluster),
|
||||||
false AS \(ViewModel.isLastKey),
|
false AS \(ViewModel.Columns.isLast),
|
||||||
false AS \(ViewModel.isLastOutgoingKey)
|
false AS \(ViewModel.Columns.isLastOutgoing)
|
||||||
|
|
||||||
FROM \(Interaction.self)
|
FROM \(Interaction.self)
|
||||||
JOIN \(SessionThread.self) ON \(thread[.id]) = \(interaction[.threadId])
|
JOIN \(SessionThread.self) ON \(thread[.id]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId])
|
LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(Profile.self) AS \(threadProfile) ON \(threadProfile).\(profileIdColumn) = \(interaction[.threadId])
|
LEFT JOIN \(threadProfile) ON \(threadProfile[.id]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(DisappearingMessagesConfiguration.self) ON \(disappearingMessagesConfig[.threadId]) = \(interaction[.threadId])
|
LEFT JOIN \(DisappearingMessagesConfiguration.self) ON \(disappearingMessagesConfig[.threadId]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(interaction[.threadId])
|
LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(interaction[.threadId])
|
||||||
LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])
|
LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])
|
||||||
|
@ -887,9 +902,9 @@ public extension MessageViewModel {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
LEFT JOIN \(InteractionAttachment.self) AS \(quoteInteractionAttachment) ON (
|
LEFT JOIN \(quoteInteractionAttachment) ON (
|
||||||
\(quoteInteractionAttachment).\(interactionAttachmentInteractionIdColumn) = \(quoteInteraction[.id]) AND
|
\(quoteInteractionAttachment[.interactionId]) = \(quoteInteraction[.id]) AND
|
||||||
\(quoteInteractionAttachment).\(interactionAttachmentAlbumIndexColumn) = 0
|
\(quoteInteractionAttachment[.albumIndex]) = 0
|
||||||
)
|
)
|
||||||
LEFT JOIN \(quoteLinkPreview) ON (
|
LEFT JOIN \(quoteLinkPreview) ON (
|
||||||
\(quoteLinkPreview[.url]) = \(quoteInteraction[.linkPreviewUrl]) AND
|
\(quoteLinkPreview[.url]) = \(quoteInteraction[.linkPreviewUrl]) AND
|
||||||
|
@ -898,27 +913,27 @@ public extension MessageViewModel {
|
||||||
linkPreview: quoteLinkPreview
|
linkPreview: quoteLinkPreview
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
LEFT JOIN \(Attachment.self) AS \(ViewModel.quoteAttachmentKey) ON (
|
LEFT JOIN \(quoteAttachment) ON (
|
||||||
\(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) OR
|
\(quoteAttachment[.id]) = \(quoteInteractionAttachment[.attachmentId]) OR
|
||||||
\(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quoteLinkPreview[.attachmentId]) OR
|
\(quoteAttachment[.id]) = \(quoteLinkPreview[.attachmentId]) OR
|
||||||
\(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quote[.attachmentId])
|
\(quoteAttachment[.id]) = \(quote[.attachmentId])
|
||||||
)
|
)
|
||||||
|
|
||||||
LEFT JOIN \(LinkPreview.self) ON (
|
LEFT JOIN \(LinkPreview.self) ON (
|
||||||
\(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND
|
\(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND
|
||||||
\(Interaction.linkPreviewFilterLiteral())
|
\(Interaction.linkPreviewFilterLiteral())
|
||||||
)
|
)
|
||||||
LEFT JOIN \(Attachment.self) AS \(ViewModel.linkPreviewAttachmentKey) ON \(ViewModel.linkPreviewAttachmentKey).\(attachmentIdColumn) = \(linkPreview[.attachmentId])
|
LEFT JOIN \(linkPreviewAttachment) ON \(linkPreviewAttachment[.id]) = \(linkPreview[.attachmentId])
|
||||||
LEFT JOIN \(RecipientState.self) ON (
|
LEFT JOIN \(RecipientState.self) ON (
|
||||||
-- Ignore 'skipped' states
|
-- Ignore 'skipped' states
|
||||||
\(SQL("\(recipientState[.state]) != \(RecipientState.State.skipped)")) AND
|
\(SQL("\(recipientState[.state]) != \(RecipientState.State.skipped)")) AND
|
||||||
\(recipientState[.interactionId]) = \(interaction[.id])
|
\(recipientState[.interactionId]) = \(interaction[.id])
|
||||||
)
|
)
|
||||||
LEFT JOIN \(RecipientState.self) AS \(readReceipt) ON (
|
LEFT JOIN \(readReceipt) ON (
|
||||||
\(readReceipt).\(readTimestampMsColumn) IS NOT NULL AND
|
\(readReceipt[.readTimestampMs]) IS NOT NULL AND
|
||||||
\(readReceipt).\(readReceiptInteractionIdColumn) = \(interaction[.id])
|
\(readReceipt[.interactionId]) = \(interaction[.id])
|
||||||
)
|
)
|
||||||
WHERE \(interaction.alias[Column.rowID]) IN \(rowIds)
|
WHERE \(interaction[.rowId]) IN \(rowIds)
|
||||||
\(finalGroupSQL)
|
\(finalGroupSQL)
|
||||||
ORDER BY \(orderSQL)
|
ORDER BY \(orderSQL)
|
||||||
"""
|
"""
|
||||||
|
@ -933,12 +948,12 @@ public extension MessageViewModel {
|
||||||
Attachment.numberOfSelectedColumns(db)
|
Attachment.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(ViewModel.self, [
|
||||||
ViewModel.profileString: adapters[1],
|
.profile: adapters[1],
|
||||||
ViewModel.quoteString: adapters[2],
|
.quote: adapters[2],
|
||||||
ViewModel.quoteAttachmentString: adapters[3],
|
.quoteAttachment: adapters[3],
|
||||||
ViewModel.linkPreviewString: adapters[4],
|
.linkPreview: adapters[4],
|
||||||
ViewModel.linkPreviewAttachmentString: adapters[5]
|
.linkPreviewAttachment: adapters[5]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -965,9 +980,9 @@ public extension MessageViewModel.AttachmentInteractionInfo {
|
||||||
let numColumnsBeforeLinkedRecords: Int = 1
|
let numColumnsBeforeLinkedRecords: Int = 1
|
||||||
let request: SQLRequest<AttachmentInteractionInfo> = """
|
let request: SQLRequest<AttachmentInteractionInfo> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(attachment.alias[Column.rowID]) AS \(AttachmentInteractionInfo.rowIdKey),
|
\(attachment[.rowId]) AS \(AttachmentInteractionInfo.Columns.rowId),
|
||||||
\(AttachmentInteractionInfo.attachmentKey).*,
|
\(attachment.allColumns),
|
||||||
\(AttachmentInteractionInfo.interactionAttachmentKey).*
|
\(interactionAttachment.allColumns)
|
||||||
FROM \(Attachment.self)
|
FROM \(Attachment.self)
|
||||||
JOIN \(InteractionAttachment.self) ON \(interactionAttachment[.attachmentId]) = \(attachment[.id])
|
JOIN \(InteractionAttachment.self) ON \(interactionAttachment[.attachmentId]) = \(attachment[.id])
|
||||||
\(finalFilterSQL)
|
\(finalFilterSQL)
|
||||||
|
@ -980,9 +995,9 @@ public extension MessageViewModel.AttachmentInteractionInfo {
|
||||||
InteractionAttachment.numberOfSelectedColumns(db)
|
InteractionAttachment.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(AttachmentInteractionInfo.self, [
|
||||||
AttachmentInteractionInfo.attachmentString: adapters[1],
|
.attachment: adapters[1],
|
||||||
AttachmentInteractionInfo.interactionAttachmentString: adapters[2]
|
.interactionAttachment: adapters[2]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1046,9 +1061,9 @@ public extension MessageViewModel.ReactionInfo {
|
||||||
let numColumnsBeforeLinkedRecords: Int = 1
|
let numColumnsBeforeLinkedRecords: Int = 1
|
||||||
let request: SQLRequest<ReactionInfo> = """
|
let request: SQLRequest<ReactionInfo> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(reaction.alias[Column.rowID]) AS \(ReactionInfo.rowIdKey),
|
\(reaction[.rowId]) AS \(ReactionInfo.Columns.rowId),
|
||||||
\(ReactionInfo.reactionKey).*,
|
\(reaction.allColumns),
|
||||||
\(ReactionInfo.profileKey).*
|
\(profile.allColumns)
|
||||||
FROM \(Reaction.self)
|
FROM \(Reaction.self)
|
||||||
LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(reaction[.authorId])
|
LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(reaction[.authorId])
|
||||||
\(finalFilterSQL)
|
\(finalFilterSQL)
|
||||||
|
@ -1061,9 +1076,9 @@ public extension MessageViewModel.ReactionInfo {
|
||||||
Profile.numberOfSelectedColumns(db)
|
Profile.numberOfSelectedColumns(db)
|
||||||
])
|
])
|
||||||
|
|
||||||
return ScopeAdapter([
|
return ScopeAdapter.with(ReactionInfo.self, [
|
||||||
ReactionInfo.reactionString: adapters[1],
|
.reaction: adapters[1],
|
||||||
ReactionInfo.profileString: adapters[2]
|
.profile: adapters[2]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1129,8 +1144,8 @@ public extension MessageViewModel.TypingIndicatorInfo {
|
||||||
}()
|
}()
|
||||||
let request: SQLRequest<MessageViewModel.TypingIndicatorInfo> = """
|
let request: SQLRequest<MessageViewModel.TypingIndicatorInfo> = """
|
||||||
SELECT
|
SELECT
|
||||||
\(threadTypingIndicator.alias[Column.rowID]) AS \(MessageViewModel.TypingIndicatorInfo.rowIdKey),
|
\(threadTypingIndicator[.rowId]),
|
||||||
\(threadTypingIndicator[.threadId]) AS \(MessageViewModel.TypingIndicatorInfo.threadIdKey)
|
\(threadTypingIndicator[.threadId])
|
||||||
FROM \(ThreadTypingIndicator.self)
|
FROM \(ThreadTypingIndicator.self)
|
||||||
\(finalFilterSQL)
|
\(finalFilterSQL)
|
||||||
"""
|
"""
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1351,18 +1351,24 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
|
||||||
|
|
||||||
// Fetch the inserted/updated rows
|
// Fetch the inserted/updated rows
|
||||||
let additionalFilters: SQL = SQL(rowIds.contains(Column.rowID))
|
let additionalFilters: SQL = SQL(rowIds.contains(Column.rowID))
|
||||||
let updatedItems: [T] = (try? dataQuery(additionalFilters)
|
|
||||||
.fetchAll(db))
|
|
||||||
.defaulting(to: [])
|
|
||||||
|
|
||||||
// If the inserted/updated rows we irrelevant (eg. associated to another thread, a quote or a link
|
do {
|
||||||
// preview) then trigger the update callback (if there were deletions) and stop here
|
let updatedItems: [T] = try dataQuery(additionalFilters)
|
||||||
guard !updatedItems.isEmpty else { return hasOtherChanges }
|
.fetchAll(db)
|
||||||
|
|
||||||
// Process the upserted data (assume at least one value changed)
|
// If the inserted/updated rows we irrelevant (eg. associated to another thread, a quote or a link
|
||||||
dataCache.mutate { $0 = $0.upserting(items: updatedItems) }
|
// preview) then trigger the update callback (if there were deletions) and stop here
|
||||||
|
guard !updatedItems.isEmpty else { return hasOtherChanges }
|
||||||
return true
|
|
||||||
|
// Process the upserted data (assume at least one value changed)
|
||||||
|
dataCache.mutate { $0 = $0.upserting(items: updatedItems) }
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
SNLog("[PagedDatabaseObserver] Error loading associated data: \(error)")
|
||||||
|
return hasOtherChanges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearCache(_ db: Database) {
|
public func clearCache(_ db: Database) {
|
||||||
|
|
|
@ -3,28 +3,65 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
|
||||||
public class TypedTableAlias<T> where T: TableRecord, T: ColumnExpressible {
|
public struct TypedTableAlias<T: ColumnExpressible> {
|
||||||
internal let name: String
|
public enum RowIdColumn {
|
||||||
internal let tableName: String
|
case rowId
|
||||||
public let alias: TableAlias
|
}
|
||||||
|
|
||||||
public init(name: String = T.databaseTableName) {
|
internal let name: String
|
||||||
|
internal let tableName: String?
|
||||||
|
internal let alias: TableAlias
|
||||||
|
|
||||||
|
public var allColumns: SQLSelection { alias[AllColumns().sqlSelection] }
|
||||||
|
public var never: NeverJoiningTypedTableAlias<T> { NeverJoiningTypedTableAlias<T>(alias: self) }
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
public init(name: String, tableName: String? = nil) {
|
||||||
|
self.name = name
|
||||||
|
self.tableName = tableName
|
||||||
|
self.alias = TableAlias(name: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(name: String) where T: TableRecord {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.tableName = T.databaseTableName
|
self.tableName = T.databaseTableName
|
||||||
self.alias = TableAlias(name: name)
|
self.alias = TableAlias(name: name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init() where T: TableRecord {
|
||||||
|
self = TypedTableAlias(name: T.databaseTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<VM: ColumnExpressible>(_ viewModel: VM.Type, column: VM.Columns, tableName: String?) {
|
||||||
|
self.name = column.name
|
||||||
|
self.tableName = tableName
|
||||||
|
self.alias = TableAlias(name: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<VM: ColumnExpressible>(_ viewModel: VM.Type, column: VM.Columns) where T: TableRecord {
|
||||||
|
self = TypedTableAlias(viewModel, column: column, tableName: T.databaseTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Functions
|
||||||
|
|
||||||
public subscript(_ column: T.Columns) -> SQLExpression {
|
public subscript(_ column: T.Columns) -> SQLExpression {
|
||||||
return alias[column.name]
|
return alias[column.name]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// **Warning:** For this to work you **MUST** call the '.aliased()' method when joining or it will
|
public subscript(_ column: RowIdColumn) -> SQLSelection {
|
||||||
/// throw when trying to decode
|
return alias[Column.rowID]
|
||||||
public func allColumns() -> SQLSelection {
|
|
||||||
return alias[AllColumns().sqlSelection]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - NeverJoiningTypedTableAlias
|
||||||
|
|
||||||
|
public struct NeverJoiningTypedTableAlias<T: ColumnExpressible> {
|
||||||
|
internal let alias: TypedTableAlias<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Extensions
|
||||||
|
|
||||||
extension QueryInterfaceRequest {
|
extension QueryInterfaceRequest {
|
||||||
public func aliased<T>(_ typedAlias: TypedTableAlias<T>) -> Self {
|
public func aliased<T>(_ typedAlias: TypedTableAlias<T>) -> Self {
|
||||||
return aliased(typedAlias.alias)
|
return aliased(typedAlias.alias)
|
||||||
|
@ -38,7 +75,5 @@ extension Association {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TableAlias {
|
extension TableAlias {
|
||||||
public func allColumns() -> SQLSelection {
|
public var allColumns: SQLSelection { self[AllColumns().sqlSelection] }
|
||||||
return self[AllColumns().sqlSelection]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,58 @@ public extension SQLInterpolation {
|
||||||
@_disfavoredOverload
|
@_disfavoredOverload
|
||||||
mutating func appendInterpolation<T>(_ typedTableAlias: TypedTableAlias<T>) {
|
mutating func appendInterpolation<T>(_ typedTableAlias: TypedTableAlias<T>) {
|
||||||
let name: String = typedTableAlias.name
|
let name: String = typedTableAlias.name
|
||||||
let tableName: String = typedTableAlias.tableName
|
|
||||||
|
|
||||||
|
guard let tableName: String = typedTableAlias.tableName else { return appendLiteral(name.quotedDatabaseIdentifier) }
|
||||||
guard name != tableName else { return appendLiteral(tableName.quotedDatabaseIdentifier) }
|
guard name != tableName else { return appendLiteral(tableName.quotedDatabaseIdentifier) }
|
||||||
|
|
||||||
appendLiteral("\(tableName.quotedDatabaseIdentifier) AS \(name.quotedDatabaseIdentifier)")
|
appendLiteral("\(tableName.quotedDatabaseIdentifier) AS \(name.quotedDatabaseIdentifier)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends a simple SQL query for use when we want a `LEFT JOIN` that will always fail
|
||||||
|
///
|
||||||
|
/// // SELECT * FROM player LEFT JOIN team AS testTeam ON false
|
||||||
|
/// let player: TypedTableAlias<Player> = TypedTableAlias()
|
||||||
|
/// let testTeam: TypedTableAlias<Team> = TypedTableAlias(name: "testTeam")
|
||||||
|
/// let request: SQLRequest<Player> = "SELECT * FROM \(player) LEFT JOIN \(testTeam.never)
|
||||||
|
@_disfavoredOverload
|
||||||
|
mutating func appendInterpolation<T: ColumnExpressible>(_ neverJoiningAlias: NeverJoiningTypedTableAlias<T>) where T: TableRecord {
|
||||||
|
guard let tableName: String = neverJoiningAlias.alias.tableName else {
|
||||||
|
appendLiteral("(SELECT \(generateSelection(for: T.self))) AS \(neverJoiningAlias.alias.name.quotedDatabaseIdentifier) ON false")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLiteral("\(tableName.quotedDatabaseIdentifier) AS \(neverJoiningAlias.alias.name.quotedDatabaseIdentifier) ON false")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a simple SQL query for use when we want a `LEFT JOIN` that will always fail
|
||||||
|
///
|
||||||
|
/// // SELECT * FROM player LEFT JOIN (SELECT 0 AS teamInfo.Column.A, 0 AS teamInfo.Column.B) AS teamInfo ON false
|
||||||
|
/// let player: TypedTableAlias<Player> = TypedTableAlias()
|
||||||
|
/// let teamInfo: TypedTableAlias<TeamInfo> = TypedTableAlias(name: "teamInfo")
|
||||||
|
/// let request: SQLRequest<Player> = "SELECT * FROM \(player) LEFT JOIN \(teamInfo.never)
|
||||||
|
@_disfavoredOverload
|
||||||
|
mutating func appendInterpolation<T: ColumnExpressible>(_ neverJoiningAlias: NeverJoiningTypedTableAlias<T>) where T.Columns: CaseIterable {
|
||||||
|
appendLiteral("(SELECT \(generateSelection(for: T.self))) AS \(neverJoiningAlias.alias.name.quotedDatabaseIdentifier) ON false")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a simple SQL query for use when we want a `LEFT JOIN` that will always fail
|
||||||
|
///
|
||||||
|
/// // SELECT * FROM player LEFT JOIN (SELECT 0 AS teamInfo.Column.A, 0 AS teamInfo.Column.B) AS teamInfo ON false
|
||||||
|
/// let player: TypedTableAlias<Player> = TypedTableAlias()
|
||||||
|
/// let teamInfo: TypedTableAlias<TeamInfo> = TypedTableAlias(name: "teamInfo")
|
||||||
|
/// let request: SQLRequest<Player> = "SELECT * FROM \(player) LEFT JOIN \(teamInfo.never)
|
||||||
|
@_disfavoredOverload
|
||||||
|
mutating func appendInterpolation<T: ColumnExpressible>(_ neverJoiningAlias: NeverJoiningTypedTableAlias<T>) {
|
||||||
|
appendLiteral("(SELECT \(generateSelection(for: T.self))) AS \(neverJoiningAlias.alias.name.quotedDatabaseIdentifier) ON false")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateSelection<T: ColumnExpressible>(for type: T.Type) -> String where T.Columns: CaseIterable {
|
||||||
|
return T.Columns.allCases
|
||||||
|
.map { "NULL AS \($0.name)" }
|
||||||
|
.joined(separator: ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateSelection<T: ColumnExpressible>(for type: T.Type) -> String {
|
||||||
|
return "SELECT 1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
public extension ScopeAdapter {
|
||||||
|
static func with<VM: ColumnExpressible>(
|
||||||
|
_ viewModel: VM.Type,
|
||||||
|
_ scopes: [VM.Columns: RowAdapter]
|
||||||
|
) -> ScopeAdapter {
|
||||||
|
return ScopeAdapter(scopes.reduce(into: [:]) { result, next in result[next.key.name] = next.value })
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue