Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSPredicate Inside a SUBQUERY

I'm trying to write a SUBQUERY for an NSPredicate. My problem is I don't have the query on hand for the predicate portion of the SUBQUERY. Is there a way to nest an NSPredicate inside the SUBQUERY?

For example this is what I have tried:

let ordersPredicate = NSPredicate()//some predicate passed in
//how do I use the ordersPredicate inside the subquery??
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x,'%@').@count > 0", ordersPredicate.predicateFormat)

Update: I had to do a dirty workaround for now by executing the first predicate and doing an ANY IN query. Its lame :( because I'm executing the ordersPredicate elsewhere as well later in the pipeline..

let fetchRequest = NSFetchRequest()//blah
fetchRequest.predicate = orderPredicate
let orders = //execute fetch
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x, ANY $x in %@).@count > 0", orders)
like image 773
Nathan Hart Avatar asked Apr 19 '16 13:04

Nathan Hart


2 Answers

Thanks to some pointers from @Willeke I was able to come up with a solution.

public extension NSPredicate{
    public func stringForSubQuery(prefix:String) -> String{
        var predicateString = ""
        if let predicate = self as? NSCompoundPredicate{
            for (index, subPredicate) in predicate.subpredicates.enumerate(){

                if let subPredicate = subPredicate as? NSComparisonPredicate{
                    predicateString = "\(predicateString) \(prefix)\(subPredicate.predicateFormat)"
                }
                else if let subPredicate = subPredicate as? NSCompoundPredicate{
                    predicateString = "\(predicateString) (\(subPredicate.stringForSubQuery(prefix)))"
                }
                //if its not the last predicate then append the operator string
                if index < predicate.subpredicates.count - 1 {
                    predicateString = "\(predicateString) \(getPredicateOperatorString(predicate.compoundPredicateType))"
                }
            }
        }
        return predicateString
    }
    private func getPredicateOperatorString(predicateType: NSCompoundPredicateType) -> String{
        switch(predicateType){
        case .NotPredicateType: return "!"
        case .AndPredicateType: return "&&"
        case .OrPredicateType: return "||"
        }
    }
}

And here is the usage

let ordersPredicate = NSPredicate()//some predicate passed in
let ordersPredicateFormat = orderPredicate.stringForSubQuery("$x.")
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x, \(ordersPredicateFormat)).@count > 0")
like image 150
Nathan Hart Avatar answered Oct 14 '22 00:10

Nathan Hart


For anyone coming across this in the future, you don't need to build the predicate string yourself, just use predicateFormat directly in your subquery string. It is already properly escaped, so use string interpolation.

let ordersPredicate = NSPredicate()
// Add it to the query with string interpolation
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x, \(ordersPredicate.predicateFormat)).@count > 0")

If you try to pass it as an argument, it will try to escape the string again, which will probably not work.

// Don't do this!
let subQueryPredicate = NSPredicate(format: "SUBQUERY(orders, $x, %@).@count > 0", ordersPredicate.predicateFormat)
like image 3
Brojowski Avatar answered Oct 14 '22 00:10

Brojowski