Skip to content

Instantly share code, notes, and snippets.

@fatbobman
Last active March 4, 2024 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fatbobman/8e10c7a811c6cd384b943d1a25206b7b to your computer and use it in GitHub Desktop.
Save fatbobman/8e10c7a811c6cd384b943d1a25206b7b to your computer and use it in GitHub Desktop.
how to combine predicate for SwiftData
import Foundation
import FoundationKit
import SwiftData
extension Memo {
public static func predicateFor(_ filter: MemoPredicate) -> Predicate<Memo>? {
var result: Predicate<Memo>?
switch filter {
case .filterAllMemo:
result = Predicate<Memo>(Self.Expressions.allMemos)
case .filterAllGlobalMemo:
result = Predicate<Memo>(Self.Expressions.allGlobalMemos)
case let .filterAllMemoOfRootNote(noteID):
result = Predicate<Memo>(Self.Expressions.memosOfRootNote(noteID))
case .filterMemoWithImage:
result = Predicate<Memo>(Self.Expressions.memoWithImage)
case .filterMemoWithStar:
result = Predicate<Memo>(Self.Expressions.memosWithStar)
case let .filterMemoContainsKeyword(keyword):
result = Predicate<Memo>(Self.Expressions.memosContainersKeyword(keyword))
}
return result
}
}
public enum MemoPredicate: Sendable {
/// 所有的 Memo
case filterAllMemo
/// 所有的没有关联 ItemData 的 Memo
case filterAllGlobalMemo
/// 指定根笔记下的全部 Memo,这种类型的 Memo 会与 ItemData 相关联,且不包含图片
case filterAllMemoOfRootNote(noteID: PersistentIdentifier)
/// 包含图片的 Memo
case filterMemoWithImage
/// 设置了星标的 memo
case filterMemoWithStar
/// 内容包含特定关键词的 Meo
case filterMemoContainsKeyword(keyword: String)
}
extension Memo {
public static func sortDescriptorFor(_ option: MemoSortingOption) -> [SortDescriptor<Memo>] {
switch option {
case .orderByCreateTimestamp:
return [SortDescriptor(\Memo.createTimestamp, order: .reverse)]
case .empty:
return []
}
}
}
public enum MemoSortingOption {
/// 根据创建时间排序( 降序 )
case orderByCreateTimestamp
/// 不设定排序
case empty
}
extension Memo {
public static func fetchDescriptorForQuery(_ filters: [MemoPredicate]) -> FetchDescriptor<Memo> {
/// 处理 predicate
let predicate = combinePredicate(filters)
return FetchDescriptor<Memo>(predicate: predicate, sortBy: Memo.sortDescriptorFor(.orderByCreateTimestamp))
}
}
extension Memo {
/// 生成合成后的 Predicate
static func combinePredicate(_ filters: [MemoPredicate]) -> Predicate<Memo> {
func buildConjunction(lhs: some StandardPredicateExpression<Bool>, rhs: some StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> {
PredicateExpressions.Conjunction(lhs: lhs, rhs: rhs)
}
return Predicate<Memo>({ memo in
var conditions: [any StandardPredicateExpression<Bool>] = []
for filter in filters {
switch filter {
case .filterAllMemo:
conditions.append(Self.Expressions.allMemos(memo))
case .filterAllGlobalMemo:
conditions.append(Self.Expressions.allGlobalMemos(memo))
case let .filterAllMemoOfRootNote(noteID):
conditions.append(Self.Expressions.memosOfRootNote(noteID)(memo))
case .filterMemoWithImage:
conditions.append(Self.Expressions.memoWithImage(memo))
case .filterMemoWithStar:
conditions.append(Self.Expressions.memosWithStar(memo))
case let .filterMemoContainsKeyword(keyword):
conditions.append(Self.Expressions.memosContainersKeyword(keyword)(memo))
}
}
guard let first = conditions.first else {
return PredicateExpressions.Value(true)
}
let closure: (any StandardPredicateExpression<Bool>, any StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> = {
buildConjunction(lhs: $0, rhs: $1)
}
return conditions.dropFirst().reduce(first, closure)
})
}
}
extension Memo {
enum Expressions {
/// 获取全部的 Memo
static let allMemos = { (_: PredicateExpressions.Variable<Memo>) in
PredicateExpressions.Value(true)
}
static let allGlobalMemos = { (memo: PredicateExpressions.Variable<Memo>) in
PredicateExpressions.build_NilCoalesce(
lhs: PredicateExpressions.build_flatMap(
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(memo),
keyPath: \.itemData
)
) { _ in
PredicateExpressions.build_Arg(
false
)
},
rhs: PredicateExpressions.build_Arg(
true
)
)
}
static let memosContainersKeyword = { (keyword: String) in { (memo: PredicateExpressions.Variable<Memo>) in
PredicateExpressions.build_NilCoalesce(
lhs: PredicateExpressions.build_flatMap(
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(memo),
keyPath: \.content
)
) { content in
PredicateExpressions.build_localizedStandardContains(
PredicateExpressions.build_Arg(content),
PredicateExpressions.build_Arg(keyword)
)
},
rhs: PredicateExpressions.build_Arg(
false
)
)
}
}
static let memosWithStar = { (memo: PredicateExpressions.Variable<Memo>) in
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(memo),
keyPath: \.star
)
}
// swiftlint:disable line_length
static let memosOfRootNote: (PersistentIdentifier) -> (PredicateExpressions.Variable<Memo>) -> PredicateExpressions.NilCoalesce<PredicateExpressions.OptionalFlatMap<PredicateExpressions.KeyPath<PredicateExpressions.Variable<Memo>, SchemaV1.ItemData?>, SchemaV1.ItemData, PredicateExpressions.OptionalFlatMap<PredicateExpressions.KeyPath<PredicateExpressions.Variable<SchemaV1.ItemData>, SchemaV1.Item?>, SchemaV1.Item, PredicateExpressions.OptionalFlatMap<PredicateExpressions.KeyPath<PredicateExpressions.Variable<SchemaV1.Item>, SchemaV1.Note?>, SchemaV1.Note, PredicateExpressions.Disjunction<PredicateExpressions.Equal<PredicateExpressions.KeyPath<PredicateExpressions.Variable<SchemaV1.Note>, PersistentIdentifier>, PredicateExpressions.Value<PersistentIdentifier>>, PredicateExpressions.Equal<PredicateExpressions.OptionalFlatMap<PredicateExpressions.KeyPath<PredicateExpressions.Variable<SchemaV1.Note>, SchemaV1.Note?>, SchemaV1.Note, PredicateExpressions.KeyPath<PredicateExpressions.Variable<SchemaV1.Note>, PersistentIdentifier>, PersistentIdentifier>, PredicateExpressions.Value<PersistentIdentifier?>>>, Bool>, Bool>, Bool>, PredicateExpressions.Value<Bool>> = { (noteID: PersistentIdentifier) in { (memo: PredicateExpressions.Variable<Memo>) in
PredicateExpressions.build_NilCoalesce(
lhs: PredicateExpressions.build_flatMap(
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(memo),
keyPath: \.itemData
)
) { itemData in
PredicateExpressions.build_flatMap(
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(itemData),
keyPath: \.item
)
) { item in
PredicateExpressions.build_flatMap(
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(item),
keyPath: \.note
)
) { note in
PredicateExpressions.build_Disjunction(
lhs: PredicateExpressions.build_Equal(
lhs: PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(note),
keyPath: \.persistentModelID
),
rhs: PredicateExpressions.build_Arg(noteID)
),
rhs: PredicateExpressions.build_Equal(
lhs: PredicateExpressions.build_flatMap(
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(note),
keyPath: \.parent
)
) {
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg($0),
keyPath: \.persistentModelID
)
},
rhs: PredicateExpressions.build_Arg(noteID)
)
)
}
}
},
rhs: PredicateExpressions.build_Arg(
false
)
)
}
}
// swiftlint:enable line_length
static let memoWithImage = { (memo: PredicateExpressions.Variable<Memo>) in
PredicateExpressions.build_KeyPath(
root: PredicateExpressions.build_Arg(memo),
keyPath: \.hasImages
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment