Complex Predicates in SwiftData

I'm running into an issue trying to combine Predicates to be used with SwiftData.

This PR by @jmschonfeld adds the ability to combine predicates together by defining predicate A, predicate B and then predicate C can combine them together.

The issue I'm running into is that this doesn't seem to work for my case. I have a Quote model that has relationships defined for Authors and Tags - both are many to many relationships.

What I would like to do is provide a SwiftData query to see if a search term contains the quote.content, the quote.authors.name, to the quote.tags.name.

Following the example in the PR above I created the following:

@Query private var quotes: [Quote]
...
let contentContainsTerm = #Predicate<Quote> { $0.content.localizedStandardContains(searchText) }
let authorNameContainsTerm = #Predicate<Author> { $0.name.localizedStandardContains(searchText) }
let tagNameContainsTerm = #Predicate<Tag> { $0.name.localizedStandardContains(searchText) }

let filter = #Predicate<Quote, Author, Tag> {
    contentContainsTerm.evaluate($0) ||
    authorNameContainsTerm.evaluate($1)) ||
    tagNameContainsTerm.evaluate($2)

 _quotes = Query(filter: filter, sort: [sortDescriptor])

This produces the error: Cannot convert value of type 'Predicate<Quote, Author, Tag>' to expected argument type 'Predicate<Array<Quote>.Element>' (aka 'Predicate<Quote>')

I understand that the Types are not comparable but I'm not sure how to create the predicates with the Quote type and reference the relationships. Any guidance would be appreciated. This is a simple join query but I don't know how to construct it with Swift Predicates.

Hey @natebird - you're on the right track! Could you share what your Quote type looks like? Presumably, it might have some author and tag property, right? If so, you'll want to use those relationships in your filter to get the author and tag since your Query is only filtering over Quotes. For example:

let filter = #Predicate<Quote> {
    contentContainsTerm.evaluate($0) ||
    authorNameContainsTerm.evaluate($0.author) ||
    tagNameContainsTerm.evaluate($0.tag)
}

or for a to-many relationship, you could do something like:

...
$0.authors.contains { author in
    authorNameContainsTerm.evaluate(author)
}
...

Does that work for you / get you closer to your goal?

Thanks for the guidance @jmschonfeld. I think I'm getting closer but still may be missing something or structuring things incorrectly.

Here is the Quote model

@Model
public final class Quote {
    public var id: String = UUID().uuidString
    public var content: String = ""
    public var timestamp: Date = Date()
    public var notes: String = ""
    public var source: String = ""
    public var isFavorite: Bool = false
    
    @Relationship(inverse: \Author.quotes) public var authors: [Author]?
    @Relationship(inverse: \Tag.quotes) public var tags: [Tag]?
    ...
}

I updated the Query predicate to be something similar to what you demonstrated above.

let term = searchText.wrappedValue
let contentContainsTerm = #Predicate<Quote> { $0.content.localizedStandardContains(term) }
let authorNameContainsTerm = #Predicate<Author> { $0.name.localizedStandardContains(term) }

let filter = #Predicate<Quote> {
    contentContainsTerm.evaluate($0) ||
    (($0.authors?.contains { author in
          authorNameContainsTerm.evaluate(author) }) != nil)
    }

_quotes = Query(filter: filter, sort: [sortDescriptor])

This compiles – although it takes a while and I had to remove the tag relationship because it failed to compile in time - I'm on a M1 16" MBP.

However, after building and running on device the search fails on the first keystroke with the following error. It seems my model isn't set up correctly or CoreData can't handle this to-many relationship.

CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x3013e3b80> , to-many key not allowed here with userInfo of (null)

@jmschonfeld do you have any suggestions to move past this issue?

CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x3013e3b80> , to-many key not allowed here with userInfo of (null)

Hey sorry for the delay - looking into it a bit this seems like it might be a bug in how SwiftData consumes the Predicate rather than in the construction/representation of the Predicate itself in Foundation. Would you be able to file a feedback (feel free to post the feedback number here / DM it to me) and we can route that to the right team to investigate further