I can create all kinds of predicates fine, but I'm not sure what can I use for implement "ENDSWITH"?
extension Filter {
var predicate: Predicate<WordModel>? {
switch self {
case .all:
.none
case .lettersContaining(let string):
#Predicate<WordModel> {
$0.letters.localizedStandardContains(string)
}
case .lettersStartingWith(let string):
#Predicate<WordModel> {
$0.letters.starts(with: string)
}
case .lettersEndingWith(let string):
#Predicate<WordModel> {
$0.letters.hasSuffix(string) //
}
}
}
}
This code says "The hasSuffix(_:) function is not supported in this predicate".
I can see in the docs (at PredicateExpressions) that the predicate closure is a builder expression that can contain a fixed set of expressions, I'm just not sure what to use for my use-case.
What do you use to create "ENDSWITH" predicates for a SwiftData @Query?
At the time of writing, this definitely doesn't exist, and it can't be implemented manually, either.
The rest of my answer below shows my attempt at that, which ultimately hits a show-stopping limitation, right at the end.
It's possible to implement a predicate that supports matching with
ends(with:)behaviour, but with pretty severe limitations.Firstly, you can see the list of all the functions the
#Predicatemacro supports, here.Even if you hand-roll your own custom predicate type that matches items using
ends(with:)behaviour, you won't be able register it in this_knownSupportedFunctions, so you won't be able to use it with the#Predicatemacro. So usages of the predicate have to be hand-rolled, and won't be pretty.Also, the resulting hand-rolled predicate can only be used with pure Swift code, but not with e.g. SwiftData or CoreData, because it's not possible to define how this custom predicate should lower to
NSPredicate(which is what is used to ultimately lower it to SQL).Step 1 - Implementing an
ends(with:)functionTo my surprise, there's no
ends(with:)counterpart tostarts(with:).We can implement one:
Step 2 - Creating a new
PredicateExpressionNext, we'll need to add the various predicate types needed to wrap it. I wrote all these by copying the
SequenceStartsWith-related code and adapting it as needed.Step 3 - Using our new expression
Finally, we'll need to actually put this new predicate to use. Unfortunately, there's no way for us to modify/extend the
#Predicatemacro syntax, short of forking it.What we can do instead, is to use the
#Predicatewith astarts(with:)call to start, e.g.We can then use
swift -Xfrontend -dump-macro-expansions predicate_demo.swiftto dump the expansion of that macro:We can then modify the
build_startstobuild_ends, to get our final example:Step 4 - Lowering to
NSPredicateTo make this new
CollectionEndsWithpredicate work with SwiftData, CoreData, etc., we'll need to implement the ability to lower it toNSPredicate. It could leverage the existingNSComparisonPredicate.Operator.endsWith.Here's what would look like:
Unfortunately, this isn't possible, because it relies on a bunch of private members from
swift-foundation/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift, likeConvertibleExpression.