SwiftData Predicate for optional to-many (as required by CloudKit) relationships crashes

Fails with "to-many key not allowed here"

//        parent.children?.contains(where: {
//            $0.name == "Abbiejean"
//        }) != nil
        parent.children.flatMap { children in
            children.contains(where: { $0.name == "Abbijean" })
        } == true

How are we supposed to query on relationships? This is a huge problem. This is a major limitation blocking migration of CoreData to SwiftData. We can do this with NSPredicate:

         let moodAnalysis = NSPredicate(format: "ANY moodAnalysis.labels.title == %@", label.description)

        let stateOfMinds = NSPredicate(format: "SUBQUERY(stateOfMinds, $x, SUBQUERY($x.labels, $y, $y.title == %@).@count > 0).@count > 0", label.description)

The accepted answer on stack overflow is: you can't

Document says that optionals are allowed in predicates The SwiftData team has made a big show of saying that we can use idiomatic swift for our predicates. But we cannot even filter on relationships when the container is backed by CloudKit...

That should be a HUGE warning in the documentation. "For those of you who are considering a costly refactor from CoreData to SwiftData, and are currently using CloudKit, all relationships are mandatory optional arrays, and you can't write predicates on them"

That's worth a feedback report with a test / sample app that reproduces this using your model

Submitted and here is a project that reproduces https://github.com/bryan1anderson/SwiftDataOptionalRelationships/tree/main

This has been an issue for a while. https://fatbobman.com/en/posts/how-to-handle-optional-values-in-swiftdata-predicates/#unprocessable-optional-values wrote about it in 2024. There are several unanswered questions here. Feedback tickets. Requests on swift.org.

I’ve been looking for a solution to this for a while. I finally found this method at the bottom of another thread:

let searchPredicate = #Predicate<SectionsSD> {
   $0.toArticles?.contains(filter) ?? false
}

Which appears to be working for me as an empty/nil check here:

let descriptor = FetchDescriptor<Flashcard>(
                predicate: #Predicate {
                    $0.annotations?.contains { _ in true } ?? false
                }
             )

I was able to combine it with an expression to actually look inside the to-many side:

let hasAnnotation = #Expression<Flashcard, Bool> { card in
            if let annotations = card.annotations {
                return annotations.contains { annotation in
                    annotation.reference == "hola"
                }
            } else {
                return false
            }
        }
let descriptor = FetchDescriptor<Flashcard>(
    predicate: #Predicate {
        hasAnnotation.evaluate($0)
    }
)

Seems to be working so far.

SwiftData Predicate for optional to-many (as required by CloudKit) relationships crashes
 
 
Q