Realm Swift Query API - True & False as initial values for dynamically constructed queries

Given a model…

final class O: Object {
    @Persisted(primaryKey: true) var id: Int
    @Persisted var name: String
}

and a dynamic list of strings

let strings: [String]

I want to query for matching objects with names that end in any of the strings.

WIth the existing API, I can do that like this:

let unfilteredObjects = realm.objects(O.self)

let objects = strings.isEmpty ? unfilteredObjects : unfilteredObjects.where { query in
    let queryComponents = strings.map { string in query.name.ends(with: string) }
    return queryComponents[1...].reduce(queryComponents[0]) { $0 || $1 }
}

This is a little ugly since I have to special-case the situation where strings is empty.

What I’d prefer is to have a way to seed reduce with a “false” query:

let objects = realm.objects(O.self).where { query in
    strings.map { string in query.name.ends(with: string) }.reduce(Query.false) { $0 || $1 }
}

Questions:

  1. Is there a way to get a “false” Query value with the current API that I’m missing?
  2. Is it even advisable to construct queries dynamically like this?
  3. Is there some completely different approach that is better overall?

I vote for #3

Suppose we have some PersonClass objects stored in Realm with the following names

Jay
Cindy
Larry
Carl
Linda

and we want to retrieve only Jay, Cindy, Larry and Carl (names ending in ‘y’ or ‘l’). Here ya go using predicates and a compound predicate

 //the names we want ending with 'y' or l'
let endStringArray = ["y", "l"]

//array to store the predicates for each letter
var predicateArray = [NSPredicate]() 

//iterate through the endStringArray getting each ending letter and creating a predicate
//  to retrieve matches for each letter
for endString in endStringArray { 
    let p = NSPredicate(format: "name ENDSWITH %@", endString)
    predicateArray.append(p) //store each predicate in an array
}

//create a compound predicate of all of the above predicates
let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicateArray)

//do the query
let results = realm.objects(PersonClass.self).filter(compoundPredicate)

//output the result
for person in results {
    print(person.name)
}

and the output

Jay
Cindy
Carl
Larry

Best to avoid high-level Swift function calls (map, reduce etc) when possible to avoid overloading the memory will large datasets. The above code avoids those entirely and is lazy-loading safe(r)

I am sure I can come up of a more type-safe version using modern API calls if needed.

In my example, would map & reduce have this effect? From what I can tell, they’re just used to build up a query, not to actually transform the result objects themselves.

Yes, we’re very interested in a type-safe version.

Yes. One of the best things about using Realm to back your apps is that Realm Results are lazily-loaded. Meaning that HUGE datasets can be comfortably navigated without having to worry about overloading the devices memory.

In a nutshell, if your app stores information about wine, when the user selects to retrieve every Cabernet Sauvignon (which is a LOT), Realm will just breeze through it and run that query and the results will easily contain those Cabernets.

However. if you use Swift High Order functions, like map, reduce, filter, compactMap etc. to work with those results, that laziness goes out the window and they are ALL loaded in, blowing up the device because there would be too many results to store in memory at one time.

So - your results are use case dependent. We use Swift High Order functions to massage our Realm data all the time - BUT, you have to go into it knowing the potential size of your data.

I think a good general rule of thumb is if you know the rough size of your data, it’s safe to use Swift functions. Otherwise, stick with Realm functions to massage the data.

let strings: [String] = ...
realm.objects(O.self).where { query in
    let queryComponents = strings.map { string in query.name.ends(with: string) }
    return queryComponents[1...].reduce(queryComponents[0]) { $0 || $1 }
}

My understanding is that the closure passed to where is only used to construct a predicate. From that perspective, it doesn’t seem like using map or reduce within that closure would be the same as using map or reduce on the Results.

You are absolutely correct!

I didn’t look closely enought at the code and saw the map and reduce but those are being used on the strings array and queryComponents so those will not affect Realm.

So just curious - what’s the use care for the code? Are you filtering realm for strings that match strings in the array?

That’s correct! I want to query realm for objects with names that end in any of the strings in the array.