apple / swift-foundation

The Foundation project
Apache License 2.0
2.35k stars 150 forks source link

Support testing against "now" in predicates #499

Open JetForMe opened 5 months ago

JetForMe commented 5 months ago

Today I ran into an issue while converting CoreData code to SwiftData: I couldn't create a #Predicate that compared against “now”:

#Predicate<PlugReminder>
{ inReminder in
    inReminder.fireDate >= Date()
}

This seems to me to be a fairly common use case. Is there a way for #Predicate to understand an expression like this?

jmschonfeld commented 5 months ago

This depends a bit on your definition of "now". If "now" refers to the Date at which the predicate is constructed/initialized, you can express it like the following:

let currentDate = Date()
let predicate = #Predicate<PlugReminder> { inReminder in
    inReminder.fireDate >= currentDate
}

But that will capture the current date/time when the predicate is constructed - if the predicate is evaluated at a later date then currentDate will not "update". We don't currently have a mechanism to calculate the current date at the time of predicate evaluation for two reasons:

  1. There's no way to represent "the current date" at a syntactic level inside of a Predicate. We don't support arbitrary function calls so the Date() initializer isn't available (and I don't feel we'd want to support this either since we don't support initializers in the general case), and we can't support static properties like Date.now because Swift does not support keypaths to static properties (this is a language limitation that would need to be implemented/lifted) which is outlined a bit in https://github.com/apple/swift-foundation/issues/318.
  2. I'm not certain if we have a great way to represent the "current" date/time in the converted representations of Predicate, for example in cases where SwiftData or CoreData converts this Predicate to a SQL query while it may be possible, I'm not certain if we have a great way to represent this in the SQL query.
JetForMe commented 5 months ago

Yeah, fortunately in my case I was able to solve it as you suggest, at the expense of some brevity in SwiftUI’s @Query macro. Unfortunately, to continually update the result set with a new "now" will probably require restructuring the views to pass in the desired date…actually i don't even think that would work with @Query.

In any case, my thought was to be able to specify "now" in a way that gets embedded into the evaluation of the predicate, rather than the creation of it. Most flavors of SQL support this, but iiuc Predicates can be used anywhere, so they probably need their own encoding of "now."

hassila commented 5 months ago

Just had to mentioned this with regard to static keypaths then:

https://forums.swift.org/t/pitch-metatype-keypaths/70767

jmschonfeld commented 5 months ago

Yeah we can keep this issue to track investigating supporting the concept of "now" within the evaluation of the predicate. I think we'll likely want to represent this as Date.now so we'll likely need static keypaths (thanks @hassila for the reference to the pitch!), but if we were to in some language version get support for that we can think through how Date.now might be represented in external formats such as NSPredicate, SQL (for SwiftData/CoreData), etc. Predicate doesn't need to support every format (since developers can write their own Predicate types using our APIs to selectively include/exclude operators based on support for another format), but we'll have to consider the main NSPredicate/SQL uses in particular for Predicate