SwiftData #Predicate cannot test for nil relationship

In Xcode Version 15.0 beta (15A5160n) this predicate will not build:

let thisPredicate = #Predicate<Ledgers> {
    ($0.myStore != nil)
}

The definition of .myStore in the Ledgers class is:

var myStore: Stores?

an optional relationship. Is there anyway to test for nil optional relationships in SwiftData?

The inverse relationship in Stores is:

@Relationship(.deny, inverse: \Ledgers.myStore) var myReceipts: [Ledgers]

I have the same problem, SwiftData predicates do not appear to allow nil tests on references to other SwiftData classes - it will work for a String? for example. Trying to work out a workaround has driven me crazy (and I failed.)

I have the same problem too. After many attempts to do a more reasonable workaround, I came up with this brut force kludge just to get past it.

    @MainActor static func allRootItems() -> [Item] {        
        //TODO: What I wanted to do - Hopefully this will be fixed someday
//      if let items = try? context.fetch(FetchDescriptor(predicate: #Predicate<Item>{ $0.parent == nil } ) )
        
        //TODO: Very temporary workaround - Hopefully this will not be needed someday
        if let items = try? context.fetch(FetchDescriptor<Item>()) {
            return items.compactMap { $0.parent == nil ? $0 : nil }
        } else {
            return [Item]()
        }
    }

Still not working in Beta 5 (15A5209g)

Still an issue with the final release of iOS 17.

Given the following models (initializers omitted for brevity):

@Model
final class Item {
    var timestamp: Date
    var group: Group?
}


@Model
final class Group {
    var name: String
    var items: [Item]
}

This predicate doesn't compile:

func query(group: Group) {
    let predicate = #Predicate<Item> {
        $0.group == group
    }
}

Force-unwrapping doesn't work either: $0.group! == group.

Based on my testing if you define inverse relationship then nil comparison works on Xcode Version 15.0 (15A240d)

@newwbee Can you please show your code?

I have just tested and neither of those variations (where you put the inverse) work:

@Model
final class Item {
    var timestamp: Date
    @Relationship
    var group: Group?
}


@Model
final class Group {
    var name: String
    @Relationship(inverse: \Item.group)
    var items: [Item]
}
@Model
final class Item {
    var timestamp: Date

    @Relationship(inverse: \Group.items)
    var group: Group?
}


@Model
final class Group {
    var name: String

    @Relationship
    var items: [Item]
}

I did a bit more experimenting. The problem doesn't seem to be in the optionality of the types per se, as even the following doesn't work:

@Model
final class Item {
    var timestamp: Date
    var group: Group
}


@Model
final class Group {
    var name: String
    var items: [Item]
}

What does work is comparing the objects' IDs:

This works:

func query(group: Group) {
    let id = group.id
    let predicate = #Predicate<Item> { item in
        item.group?.id == id // Compiles
    }
}

However, if the id is not in a local variable, the Predicate macro doesn't compile:

func query(group: Group) {
    let predicate = #Predicate<Item> { item in
        item.group?.id == group.id // Doesn't compile
    }
}
func query(group: Group) {
    let id = group.id
    let predicate = #Predicate<Item> { item in
        item.group?.id == id // Compiles
    }
}

@MarcusAurelius

This method can indeed compile, but when I use similar code to run, the following error message will appear:

Thread 1: Fatal error: Couldn't find \Tag.id on Tag with fields

May I ask if your code can use the above predicate to fetch data?

So, if I'm understanding this, #Predicate can not use @Model objects, but only members? (And if a member is itself a @Model object, this recurses infinitely?) (And, on top of that, cannot use any method parameters or struct/class members, but can capture local variables?)

Apple confirmed that capturing group.id is the correct way to do it. They explained that the reason for that is because the predicate requires all captured values to be Codable and Sendable. And since Group is neither, it won't work and requires a local capture.

I haven't tested this further, as I am not actively exploring moving to iOS 17 at this point.

SwiftData #Predicate cannot test for nil relationship
 
 
Q