KeyPath -> NSPredicate, securely

So basically, you dream language is C# linq.

1 Like

So I figured out how to implement this DSL with the currently available tools.
I've published a beta of the project here.

The documentation isn't written yet, but the DSL works likes this:

let orderings: [NSSortDescription] = [\Person.age|asc, \Person.name|desc]
let predicate: NSPredicate  = Person.age > 30 && \friends.count < 2 

The same syntax also yields function forms:

let orderings: [(Person,Person)->Bool] = [\Person.age|asc, \Person.name|desc]
let predicate: (Person)->Bool  = Person.age > 30 && \friends.count < 2 

Type inference usually makes the explicit types unnecessary. Thus some NSManagedContext library could implement the such a shape:

let people = Person.select(
    where: \.age > 30 && \friends.count < 2 , 
    orderBy:  [\Person.age|asc, \Person.name|desc])

I've tested the library against CoreData and everything is woking well for me. The only caveat is that the KeyPath has to be reachable via @objc, but that's implicit with @NSManaged anyway.

Interested parties are encouraged to test against their code bases. Please provide and responses/feedback/feature requests via the project site so we don't clutter this forum. Thank you!

4 Likes

Something like C# linq would be nice but I would decouple the data source from it though. I'm interested in creating Query objects that could be used to fetch into custom classes such as Core Data's managed objects, structs or dictionaries. Examples:


// Person is a managed object subclass or maybe a struct
// Query<Person>
let query =
    from Person
    where age > 30

// This query could be used to fetch data as managed objects as
// specified by the Foo entity in the object model.
// Query<ManagedObject>
let query = 
    from "Foo"
    where someAttribute == "something"

// Just the first and last name... We are doing something simple, no need for
// fetching as managed objects here.
// Query<[String:Any]>
let query =
    from Person
    select firstName, lastName
    where age > 30

It would be nice to query across to-many relationships. Let's imagine Family has a pets to-many relationship.

// Query<Family>
let familiesWithoutPetsQuery =
    from Family
    where pets.isEmpty
    orderBy lastName asc

// Query<Family>
let familiesWithPetsQuery =
    from Family
    where pets.isEmpty == false
    orderBy lastName asc

And compose Query objects like this:

/ / Query<Pet>
let isPuppyOrKitty =
    from Pet
    where age < 1.0 && type in (.dog, .cat)

// Query<Family>
let familiesWithPuppiesOrKitties =
    from Family
    where pets containsAnySatisfying isPuppyOrKittyQuery
    orderBy pets.count

Fetching:

// Assume query is Query<Family>
// Fetch all rows as an array of Family objects 
// [Family]
let families = managedObjectContext.fetch(query)

// Fetch one at a time and do something with it
// QueryIterator<Family>
var iterator = QueryIterator(query)
while let family = iterator.next(managedObjectContext) {
    // do something with family here
    family.doSomething()

    // now save changes
    managedObjectContext.saveChanges()
}