Kitura / Swift-Kuery-ORM

An ORM for Swift, built on Codable
Apache License 2.0
212 stars 30 forks source link

Consider adding support for filtering against arrays #96

Closed fwgreen closed 5 years ago

fwgreen commented 5 years ago

This feature is in Swift-Kuery, as it supports WHERE field IN (?, ?, ...) clauses, but the ORM does not have access to it. As I use this for multiple entities in my domain, my current workaround is to extend Model –Which clearly was never meant for that. Being able to code a filter like this seems quite natural:

let jobs = Filter(occupation: ["Baker", "Butcher"])
kilnerm commented 5 years ago

@fwgreen If I understand this request correctly I believe you can already achieve this using QueryParameters....

struct User: Model {
    let name: String
    let age: Int32
}

struct Query: QueryParams {
    let age: [Int32]
}
let query = Query(age: [11,22,44])

let user1 = User(name: "user1", age: 11)
let user2 = User(name: "user2", age: 22)
let user3 = User(name: "user3", age: 33)
let user4 = User(name: "user4", age: 44)

User.findAll(matching: query)

The call to findAll above would return me an array containing user1, user2 and user4.

Have I understood your requirement correctly?

fwgreen commented 5 years ago

I've tried that. With the current release package (0.5.2) the result is a crash:

Fatal error: Unexpectedly found nil while unwrapping an Optional value
Illegal instruction: 4

My workaround has been to extend model with my own findAll that substitutes a WHERE IN clause in the query:

extension Model {
    static func findAll<I: Identifier>(
        column: String, 
        identifiers: [I], 
        using db: Database? = nil, 
        _ onCompletion: @escaping ([Self]?, RequestError?) -> Void) 
    {
        var table: Table 
        do {
            table = try Self.getTable()
        } catch {
            return
        }
        var list = identifiers.map { "'\($0.value)'" }.joined(separator: ",")
        if list.isEmpty { list = "0" }
        let query = Select(from: table).where(column + " IN (\(list))")
        Self.executeQuery(query: query, parameters: nil, onCompletion)
    }
}
kilnerm commented 5 years ago

@fwgreen The good news is that I have a fix for the crash in master. I will tag a new release on Monday so you can pick it up more easily.

kilnerm commented 5 years ago

@fwgreen I have tagged release 0.6.0. Can you take a look and let me know if it now works for you please?

fwgreen commented 5 years ago

@kilnerm Thanks for your work on this. I was able to try it and it does work, but with two small issues: It can't handle an empty array:

Fatal error: Can't remove first element from an empty collection

and the name of the field in your filter has to match your CodingKey, not your Model:

struct User: Model {
    let name: String
    let age: Int32

    enum CodingKeys : String, CodingKey {
       case age
       case name = "full_name"
    }
}

struct Query: QueryParams {
    //let name: [String] Fatal error: Unexpectedly found nil while unwrapping an Optional value
    let full_name: [String]
}
kilnerm commented 5 years ago

@fwgreen I have pushed a fix to master for the Fatel Error, the ORM will now return an error stating empty arrays cannot be used a filters.

The second issue is looking likely to be a case of some additional documentation being needed. If you add the same CodingKeys definition to the QueryParams structure then the filter will work.

fwgreen commented 5 years ago

Thanks! I'm closing the issue as the core problem is now resolved.