Skip to content

Commit

Permalink
Use Core Data prefetching when StreamRuntimeCheck._isDatabasePrefetch…
Browse files Browse the repository at this point in the history
…ingEnabled is set (#3495)
  • Loading branch information
laevandus authored Nov 20, 2024
1 parent 2b1c0c5 commit b5fa385
Show file tree
Hide file tree
Showing 20 changed files with 212 additions and 14 deletions.
5 changes: 5 additions & 0 deletions Sources/StreamChat/Config/StreamRuntimeCheck.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ public enum StreamRuntimeCheck {
///
/// Uses version 2 for offline state sync.
public static var _isSyncV2Enabled = true

/// For *internal use* only
///
/// Core Data prefetches data used for creating immutable model objects (faulting is disabled).
public static var _isDatabasePrefetchingEnabled = false
}
17 changes: 17 additions & 0 deletions Sources/StreamChat/Database/DTOs/ChannelDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class ChannelDTO: NSManagedObject {

static func fetchRequest(for cid: ChannelId) -> NSFetchRequest<ChannelDTO> {
let request = NSFetchRequest<ChannelDTO>(entityName: ChannelDTO.entityName)
ChannelDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \ChannelDTO.updatedAt, ascending: false)]
request.predicate = NSPredicate(format: "cid == %@", cid.rawValue)
return request
Expand All @@ -139,6 +140,7 @@ class ChannelDTO: NSManagedObject {
static func load(cids: [ChannelId], context: NSManagedObjectContext) -> [ChannelDTO] {
guard !cids.isEmpty else { return [] }
let request = NSFetchRequest<ChannelDTO>(entityName: ChannelDTO.entityName)
ChannelDTO.applyPrefetchingState(to: request)
request.predicate = NSPredicate(format: "cid IN %@", cids)
return load(by: request, context: context)
}
Expand All @@ -159,6 +161,19 @@ class ChannelDTO: NSManagedObject {
}
}

extension ChannelDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[
KeyPath.string(\ChannelDTO.currentlyTypingUsers),
KeyPath.string(\ChannelDTO.pinnedMessages),
KeyPath.string(\ChannelDTO.messages),
KeyPath.string(\ChannelDTO.members),
KeyPath.string(\ChannelDTO.reads),
KeyPath.string(\ChannelDTO.watchers)
]
}
}

// MARK: - Reset Ephemeral Values

extension ChannelDTO: EphemeralValuesContainer {
Expand Down Expand Up @@ -382,6 +397,7 @@ extension ChannelDTO {
chatClientConfig: ChatClientConfig
) -> NSFetchRequest<ChannelDTO> {
let request = NSFetchRequest<ChannelDTO>(entityName: ChannelDTO.entityName)
ChannelDTO.applyPrefetchingState(to: request)

// Fetch results controller requires at least one sorting descriptor.
var sortDescriptors = query.sort.compactMap { $0.key.sortDescriptor(isAscending: $0.isAscending) }
Expand Down Expand Up @@ -420,6 +436,7 @@ extension ChannelDTO {

static func directMessageChannel(participantId: UserId, context: NSManagedObjectContext) -> ChannelDTO? {
let request = NSFetchRequest<ChannelDTO>(entityName: ChannelDTO.entityName)
ChannelDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \ChannelDTO.updatedAt, ascending: false)]
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "cid CONTAINS ':!members'"),
Expand Down
10 changes: 10 additions & 0 deletions Sources/StreamChat/Database/DTOs/ChannelMuteDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ final class ChannelMuteDTO: NSManagedObject {

static func fetchRequest(for cid: ChannelId) -> NSFetchRequest<ChannelMuteDTO> {
let request = NSFetchRequest<ChannelMuteDTO>(entityName: ChannelMuteDTO.entityName)
ChannelMuteDTO.applyPrefetchingState(to: request)
request.predicate = NSPredicate(format: "channel.cid == %@", cid.rawValue)
return request
}
Expand All @@ -37,6 +38,15 @@ final class ChannelMuteDTO: NSManagedObject {
}
}

extension ChannelMuteDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[
KeyPath.string(\ChannelMuteDTO.channel),
KeyPath.string(\ChannelMuteDTO.currentUser)
]
}
}

extension NSManagedObjectContext {
@discardableResult
func saveChannelMute(payload: MutedChannelPayload) throws -> ChannelMuteDTO {
Expand Down
8 changes: 8 additions & 0 deletions Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ class ChannelReadDTO: NSManagedObject {

static func fetchRequest(userId: String) -> NSFetchRequest<ChannelReadDTO> {
let request = NSFetchRequest<ChannelReadDTO>(entityName: ChannelReadDTO.entityName)
ChannelReadDTO.applyPrefetchingState(to: request)
request.predicate = NSPredicate(format: "user.id == %@", userId)
return request
}

static func fetchRequest(for cid: ChannelId, userId: String) -> NSFetchRequest<ChannelReadDTO> {
let request = NSFetchRequest<ChannelReadDTO>(entityName: ChannelReadDTO.entityName)
ChannelReadDTO.applyPrefetchingState(to: request)
request.predicate = NSPredicate(format: "channel.cid == %@ && user.id == %@", cid.rawValue, userId)
return request
}
Expand Down Expand Up @@ -191,6 +193,12 @@ extension NSManagedObjectContext {
}
}

extension ChannelReadDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[KeyPath.string(\ChannelReadDTO.user)]
}
}

extension ChatChannelRead {
fileprivate static func create(fromDTO dto: ChannelReadDTO) throws -> ChatChannelRead {
try .init(
Expand Down
17 changes: 17 additions & 0 deletions Sources/StreamChat/Database/DTOs/CurrentUserDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class CurrentUserDTO: NSManagedObject {
/// Returns a default fetch request for the current user.
static var defaultFetchRequest: NSFetchRequest<CurrentUserDTO> {
let request = NSFetchRequest<CurrentUserDTO>(entityName: CurrentUserDTO.entityName)
CurrentUserDTO.applyPrefetchingState(to: request)
// Sorting doesn't matter here as soon as we have a single current-user in a database.
// It's here to make the request safe for FRC
request.sortDescriptors = [.init(keyPath: \CurrentUserDTO.unreadMessagesCount, ascending: true)]
Expand All @@ -46,6 +47,7 @@ extension CurrentUserDTO {
/// - Parameter context: The context used to fetch `CurrentUserDTO`
fileprivate static func load(context: NSManagedObjectContext) -> CurrentUserDTO? {
let request = NSFetchRequest<CurrentUserDTO>(entityName: CurrentUserDTO.entityName)
CurrentUserDTO.applyPrefetchingState(to: request)
let result = load(by: request, context: context)

log.assert(
Expand All @@ -61,6 +63,7 @@ extension CurrentUserDTO {
/// - Parameter context: The context used to fetch/create `CurrentUserDTO`
fileprivate static func loadOrCreate(context: NSManagedObjectContext) -> CurrentUserDTO {
let request = NSFetchRequest<CurrentUserDTO>(entityName: CurrentUserDTO.entityName)
CurrentUserDTO.applyPrefetchingState(to: request)
let result = load(by: request, context: context)
log.assert(
result.count <= 1,
Expand Down Expand Up @@ -194,6 +197,20 @@ extension NSManagedObjectContext: CurrentUserDatabaseSession {
}
}

extension CurrentUserDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[
KeyPath.string(\CurrentUserDTO.channelMutes),
KeyPath.string(\CurrentUserDTO.currentDevice),
KeyPath.string(\CurrentUserDTO.devices),
KeyPath.string(\CurrentUserDTO.flaggedMessages),
KeyPath.string(\CurrentUserDTO.flaggedUsers),
KeyPath.string(\CurrentUserDTO.mutedUsers),
KeyPath.string(\CurrentUserDTO.user)
]
}
}

extension CurrentUserDTO {
/// Snapshots the current state of `CurrentUserDTO` and returns an immutable model object from it.
func asModel() throws -> CurrentChatUser { try .create(fromDTO: self) }
Expand Down
8 changes: 8 additions & 0 deletions Sources/StreamChat/Database/DTOs/MemberModelDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ extension MemberDTO {
/// Returns a fetch request for the dto with the provided `userId`.
static func member(_ userId: UserId, in cid: ChannelId) -> NSFetchRequest<MemberDTO> {
let request = NSFetchRequest<MemberDTO>(entityName: MemberDTO.entityName)
MemberDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \MemberDTO.memberCreatedAt, ascending: false)]
request.predicate = NSPredicate(format: "id == %@", Self.createId(userId: userId, channeldId: cid))
return request
Expand All @@ -51,6 +52,7 @@ extension MemberDTO {
/// Returns a fetch request for the DTOs matching the provided `query`.
static func members(matching query: ChannelMemberListQuery) -> NSFetchRequest<MemberDTO> {
let request = NSFetchRequest<MemberDTO>(entityName: MemberDTO.entityName)
MemberDTO.applyPrefetchingState(to: request)
request.predicate = NSPredicate(format: "ANY queries.queryHash == %@", query.queryHash)
var sortDescriptors = query.sortDescriptors
// For consistent order we need to have a sort descriptor which breaks ties
Expand Down Expand Up @@ -166,6 +168,12 @@ extension NSManagedObjectContext {
}
}

extension MemberDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[KeyPath.string(\MemberDTO.user)]
}
}

extension MemberDTO {
func asModel() throws -> ChatChannelMember { try .create(fromDTO: self) }
}
Expand Down
31 changes: 31 additions & 0 deletions Sources/StreamChat/Database/DTOs/MessageDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ class MessageDTO: NSManagedObject {
shouldShowShadowedMessages: Bool
) -> NSFetchRequest<MessageDTO> {
let request = NSFetchRequest<MessageDTO>(entityName: MessageDTO.entityName)
MessageDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \MessageDTO.defaultSortingKey, ascending: sortAscending)]
request.predicate = channelMessagesPredicate(
for: cid.rawValue,
Expand All @@ -374,6 +375,7 @@ class MessageDTO: NSManagedObject {
shouldShowShadowedMessages: Bool
) -> NSFetchRequest<MessageDTO> {
let request = NSFetchRequest<MessageDTO>(entityName: MessageDTO.entityName)
MessageDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \MessageDTO.defaultSortingKey, ascending: sortAscending)]
request.predicate = threadRepliesPredicate(
for: messageId,
Expand All @@ -387,6 +389,7 @@ class MessageDTO: NSManagedObject {

static func messagesFetchRequest(for query: MessageSearchQuery) -> NSFetchRequest<MessageDTO> {
let request = NSFetchRequest<MessageDTO>(entityName: MessageDTO.entityName)
MessageDTO.applyPrefetchingState(to: request)
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "ANY searches.filterHash == %@", query.filterHash),
NSPredicate(format: "isHardDeleted == NO")
Expand All @@ -399,6 +402,7 @@ class MessageDTO: NSManagedObject {
/// Returns a fetch request for the dto with a specific `messageId`.
static func message(withID messageId: MessageId) -> NSFetchRequest<MessageDTO> {
let request = NSFetchRequest<MessageDTO>(entityName: MessageDTO.entityName)
MessageDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \MessageDTO.defaultSortingKey, ascending: false)]
request.predicate = NSPredicate(format: "id == %@", messageId)
return request
Expand All @@ -413,6 +417,7 @@ class MessageDTO: NSManagedObject {
context: NSManagedObjectContext
) -> [MessageDTO] {
let request = NSFetchRequest<MessageDTO>(entityName: entityName)
MessageDTO.applyPrefetchingState(to: request)
request.predicate = channelMessagesPredicate(
for: cid,
deletedMessagesVisibility: deletedMessagesVisibility,
Expand All @@ -426,6 +431,7 @@ class MessageDTO: NSManagedObject {

static func preview(for cid: String, context: NSManagedObjectContext) -> MessageDTO? {
let request = NSFetchRequest<MessageDTO>(entityName: entityName)
MessageDTO.applyPrefetchingState(to: request)
request.predicate = previewMessagePredicate(
cid: cid,
includeShadowedMessages: context.shouldShowShadowedMessages ?? false
Expand Down Expand Up @@ -466,6 +472,7 @@ class MessageDTO: NSManagedObject {
context: NSManagedObjectContext
) -> [MessageDTO] {
let request = NSFetchRequest<MessageDTO>(entityName: entityName)
MessageDTO.applyPrefetchingState(to: request)
request.predicate = NSPredicate(format: "parentMessageId == %@", messageId)
request.sortDescriptors = [NSSortDescriptor(keyPath: \MessageDTO.createdAt, ascending: false)]
request.fetchLimit = limit
Expand All @@ -487,6 +494,7 @@ class MessageDTO: NSManagedObject {
]

let request = NSFetchRequest<MessageDTO>(entityName: MessageDTO.entityName)
MessageDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \MessageDTO.defaultSortingKey, ascending: false)]
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: subpredicates)

Expand Down Expand Up @@ -534,6 +542,7 @@ class MessageDTO: NSManagedObject {
guard let message = load(id: id, context: context) else { return nil }

let request = NSFetchRequest<MessageDTO>(entityName: entityName)
MessageDTO.applyPrefetchingState(to: request)
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
channelMessagesPredicate(for: cid, deletedMessagesVisibility: deletedMessagesVisibility, shouldShowShadowedMessages: shouldShowShadowedMessages),
.init(format: "id != %@", id),
Expand All @@ -554,6 +563,7 @@ class MessageDTO: NSManagedObject {
context: NSManagedObjectContext
) throws -> [MessageDTO] {
let request = NSFetchRequest<MessageDTO>(entityName: MessageDTO.entityName)
MessageDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \MessageDTO.defaultSortingKey, ascending: sortAscending)]
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
channelMessagesPredicate(
Expand All @@ -577,6 +587,7 @@ class MessageDTO: NSManagedObject {
context: NSManagedObjectContext
) throws -> [MessageDTO] {
let request = NSFetchRequest<MessageDTO>(entityName: MessageDTO.entityName)
MessageDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \MessageDTO.defaultSortingKey, ascending: sortAscending)]
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
threadRepliesPredicate(
Expand Down Expand Up @@ -1227,6 +1238,26 @@ extension NSManagedObjectContext: MessageDatabaseSession {
}
}

extension MessageDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[
KeyPath.string(\MessageDTO.attachments),
KeyPath.string(\MessageDTO.flaggedBy),
KeyPath.string(\MessageDTO.mentionedUsers),
KeyPath.string(\MessageDTO.moderationDetails),
KeyPath.string(\MessageDTO.pinnedBy),
KeyPath.string(\MessageDTO.poll),
KeyPath.string(\MessageDTO.quotedBy),
KeyPath.string(\MessageDTO.quotedMessage),
KeyPath.string(\MessageDTO.reactionGroups),
KeyPath.string(\MessageDTO.reads),
KeyPath.string(\MessageDTO.replies),
KeyPath.string(\MessageDTO.threadParticipants),
KeyPath.string(\MessageDTO.user)
]
}
}

extension MessageDTO {
/// Snapshots the current state of `MessageDTO` and returns an immutable model object from it.
func asModel() throws -> ChatMessage { try .init(fromDTO: self, depth: 0) }
Expand Down
9 changes: 9 additions & 0 deletions Sources/StreamChat/Database/DTOs/MessageReactionDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ final class MessageReactionDTO: NSManagedObject {
extension MessageReactionDTO {
static func reactionListFetchRequest(query: ReactionListQuery) -> NSFetchRequest<MessageReactionDTO> {
let request = NSFetchRequest<MessageReactionDTO>(entityName: MessageReactionDTO.entityName)
MessageReactionDTO.applyPrefetchingState(to: request)

// Fetch results controller requires at least one sorting descriptor.
// At the moment, we do not allow changing the query sorting.
Expand Down Expand Up @@ -83,6 +84,7 @@ extension MessageReactionDTO {
}

let request = NSFetchRequest<MessageReactionDTO>(entityName: MessageReactionDTO.entityName)
MessageReactionDTO.applyPrefetchingState(to: request)
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "id IN %@", ids),
Self.notLocallyDeletedPredicates
Expand Down Expand Up @@ -120,6 +122,7 @@ extension MessageReactionDTO {
sort: [NSSortDescriptor]
) -> NSFetchRequest<MessageReactionDTO> {
let request = NSFetchRequest<MessageReactionDTO>(entityName: MessageReactionDTO.entityName)
MessageReactionDTO.applyPrefetchingState(to: request)
request.sortDescriptors = sort
request.predicate = NSPredicate(format: "message.id == %@", messageId)
request.fetchBatchSize = 30
Expand Down Expand Up @@ -184,6 +187,12 @@ extension NSManagedObjectContext {
}
}

extension MessageReactionDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[KeyPath.string(\MessageReactionDTO.user)]
}
}

extension MessageReactionDTO {
var localState: LocalReactionState? {
get {
Expand Down
11 changes: 11 additions & 0 deletions Sources/StreamChat/Database/DTOs/PollDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,23 @@ class PollDTO: NSManagedObject {

static func fetchRequest(for pollId: String) -> NSFetchRequest<PollDTO> {
let request = NSFetchRequest<PollDTO>(entityName: PollDTO.entityName)
PollDTO.applyPrefetchingState(to: request)
request.sortDescriptors = [NSSortDescriptor(keyPath: \PollDTO.updatedAt, ascending: false)]
request.predicate = NSPredicate(format: "id == %@", pollId)
return request
}
}

extension PollDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[
KeyPath.string(\PollDTO.createdBy),
KeyPath.string(\PollDTO.latestVotes),
KeyPath.string(\PollDTO.latestVotesByOption)
]
}
}

extension PollDTO {
func asModel() throws -> Poll {
var extraData: [String: RawJSON] = [:]
Expand Down
7 changes: 7 additions & 0 deletions Sources/StreamChat/Database/DTOs/PollOptionDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,18 @@ class PollOptionDTO: NSManagedObject {

static func fetchRequest(for optionId: String) -> NSFetchRequest<PollOptionDTO> {
let request = NSFetchRequest<PollOptionDTO>(entityName: PollOptionDTO.entityName)
PollOptionDTO.applyPrefetchingState(to: request)
request.predicate = NSPredicate(format: "id == %@", optionId)
return request
}
}

extension PollOptionDTO {
override class func prefetchedRelationshipKeyPaths() -> [String] {
[KeyPath.string(\PollOptionDTO.latestVotes)]
}
}

extension PollOptionDTO {
func asModel() throws -> PollOption {
var extraData: [String: RawJSON] = [:]
Expand Down
Loading

0 comments on commit b5fa385

Please sign in to comment.