Skip to content

Commit

Permalink
Add more options in findAll
Browse files Browse the repository at this point in the history
This commit adds in options:
* ORDER BY clause for sorting
* OFFSET and LIMIT for retrieving a portion of the rows

This builds the appropriate SQL queries for the options listed above.
This commit also adds in initial tests for the new options.

Signed-off-by: AnthonyAmanse <[email protected]>
  • Loading branch information
AnthonyAmanse committed Jul 13, 2018
1 parent 6d57603 commit a03ea46
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 28 deletions.
92 changes: 66 additions & 26 deletions Sources/SwiftKueryORM/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ public protocol Model: Codable {

/// Call to find all the models in the database that accepts a completion
/// handler. The callback is passed an array of models or an error
static func findAll(using db: Database?, _ onCompletion: @escaping ([Self]?, RequestError?) -> Void)
static func findAll(using db: Database?, order: Order..., offset: Int?, limit: Int?, _ onCompletion: @escaping ([Self]?, RequestError?) -> Void)

/// Call to find all the models in the database that accepts a completion
/// handler. The callback is passed an array of tuples (id, model) or an error
static func findAll<I: Identifier>(using db: Database?, _ onCompletion: @escaping ([(I, Self)]?, RequestError?) -> Void)
static func findAll<I: Identifier>(using db: Database?, order: Order..., offset: Int?, limit: Int?, _ onCompletion: @escaping ([(I, Self)]?, RequestError?) -> Void)

/// Call to find all the models in the database that accepts a completion
/// handler. The callback is passed a dictionary [id: model] or an error
static func findAll<I: Identifier>(using db: Database?, _ onCompletion: @escaping ([I: Self]?, RequestError?) -> Void)
static func findAll<I: Identifier>(using db: Database?, order: Order..., offset: Int?, limit: Int?, _ onCompletion: @escaping ([I: Self]?, RequestError?) -> Void)

/// Call to find all the models in the database matching the QueryParams that accepts a completion
/// handler. The callback is passed an array of models or an error
Expand Down Expand Up @@ -895,25 +895,32 @@ public extension Model {
}

/**
This function constructs an array of OrderBy from the QueryParameters values
This function constructs an array of OrderBy from the QueryParameters or the Order values
*/
private static func getOrderBy(values: [String: Any], table: Table) -> [OrderBy] {
private static func getOrderBy(values: [String: Any]? = nil, order: [Order]? = nil, table: Table) -> [OrderBy] {
var orderByArray: [OrderBy] = []
for (_, value) in values {
if let orderValue = value as? Ordering {
let columnDictionary = table.columns.reduce(into: [String: Column]()) { dict, value in
dict[value.name] = value
}
let orders = orderValue.getValues()
for order in orders where columnDictionary[order.value] != nil {
let column = columnDictionary[order.value]!
if case .asc(_) = order {
orderByArray.append(.ASC(column))
} else {
orderByArray.append(.DESC(column))
}
}
}
var orders: [Order] = []
let columnDictionary = table.columns.reduce(into: [String: Column]()) { dict, value in
dict[value.name] = value
}

if let order = order {
orders = order
} else if let values = values {
for (_, value) in values {
if let orderValue = value as? Ordering {
orders = orderValue.getValues()
}
}
}

for order in orders where columnDictionary[order.value] != nil {
let column = columnDictionary[order.value]!
if case .asc(_) = order {
orderByArray.append(.ASC(column))
} else {
orderByArray.append(.DESC(column))
}
}

return orderByArray
Expand Down Expand Up @@ -967,46 +974,79 @@ public extension Model {


///
static func findAll(using db: Database? = nil, _ onCompletion: @escaping ([Self]?, RequestError?) -> Void) {
static func findAll(using db: Database? = nil, order: Order..., offset: Int? = nil, limit: Int? = nil, _ onCompletion: @escaping ([Self]?, RequestError?) -> Void) {
var table: Table
do {
table = try Self.getTable()
} catch let error {
onCompletion(nil, Self.convertError(error))
return
}
let orderBy: [OrderBy] = Self.getOrderBy(order: order, table: table)

var query = Select(from: table)
if orderBy.count > 0 {
query = query.order(by: orderBy)
}
if let offset = offset {
query = query.offset(offset)
}
if let limit = limit {
query = query.limit(to: limit)
}

let query = Select(from: table)
Self.executeQuery(query: query, using: db, onCompletion)
}

/// Find all the models
/// - Parameter using: Optional Database to use
/// - Returns: An array of tuples (id, model)
static func findAll<I: Identifier>(using db: Database? = nil, _ onCompletion: @escaping ([(I, Self)]?, RequestError?) -> Void) {
static func findAll<I: Identifier>(using db: Database? = nil, order: Order..., offset: Int? = nil, limit: Int? = nil, _ onCompletion: @escaping ([(I, Self)]?, RequestError?) -> Void) {
var table: Table
do {
table = try Self.getTable()
} catch let error {
onCompletion(nil, Self.convertError(error))
return
}
let orderBy: [OrderBy] = Self.getOrderBy(order: order, table: table)

var query = Select(from: table)
if orderBy.count > 0 {
query = query.order(by: orderBy)
}
if let offset = offset {
query = query.offset(offset)
}
if let limit = limit {
query = query.limit(to: limit)
}

let query = Select(from: table)
Self.executeQuery(query: query, using: db, onCompletion)
}

/// :nodoc:
static func findAll<I: Identifier>(using db: Database? = nil, _ onCompletion: @escaping ([I: Self]?, RequestError?) -> Void) {
static func findAll<I: Identifier>(using db: Database? = nil, order: Order..., offset: Int? = nil, limit: Int? = nil, _ onCompletion: @escaping ([I: Self]?, RequestError?) -> Void) {
var table: Table
do {
table = try Self.getTable()
} catch let error {
onCompletion(nil, Self.convertError(error))
return
}
let orderBy: [OrderBy] = Self.getOrderBy(order: order, table: table)

var query = Select(from: table)
if orderBy.count > 0 {
query = query.order(by: orderBy)
}
if let offset = offset {
query = query.offset(offset)
}
if let limit = limit {
query = query.limit(to: limit)
}

let query = Select(from: table)
Self.executeQuery(query: query, using: db) { (tuples: [(I, Self)]?, error: RequestError?) in
if let error = error {
onCompletion(nil, error)
Expand Down
17 changes: 15 additions & 2 deletions Tests/SwiftKueryORMTests/CommonUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class TestConnection: Connection {
case returnEmpty
case returnOneRow
case returnThreeRows
case returnThreeRowsSortedAscending
case returnThreeRowsSortedDescending
case returnError
case returnValue
}
Expand Down Expand Up @@ -100,6 +102,10 @@ class TestConnection: Connection {
onCompletion(.resultSet(ResultSet(TestResultFetcher(numberOfRows: 1))))
case .returnThreeRows:
onCompletion(.resultSet(ResultSet(TestResultFetcher(numberOfRows: 3))))
case .returnThreeRowsSortedAscending:
onCompletion(.resultSet(ResultSet(TestResultFetcher(numberOfRows: 3, sortedByAge: "ascending"))))
case .returnThreeRowsSortedDescending:
onCompletion(.resultSet(ResultSet(TestResultFetcher(numberOfRows: 3, sortedByAge: "descending"))))
case .returnError:
onCompletion(.error(QueryError.noResult("Error in query execution.")))
case .returnValue:
Expand Down Expand Up @@ -136,12 +142,19 @@ class TestConnection: Connection {

class TestResultFetcher: ResultFetcher {
let numberOfRows: Int
let rows = [[1, "Joe", Int32(38)], [2, "Adam", Int32(28)], [3, "Chris", Int32(36)]]
var rows = [[1, "Joe", Int32(38)], [2, "Adam", Int32(28)], [3, "Chris", Int32(36)]]
let titles = ["id", "name", "age"]
var fetched = 0

init(numberOfRows: Int) {
init(numberOfRows: Int, sortedByAge: String? = nil) {
self.numberOfRows = numberOfRows
if let sortedByAge = sortedByAge {
if sortedByAge == "descending" {
rows.sort {($0[2] as! Int32) > ($1[2] as! Int32)}
} else if sortedByAge == "ascending" {
rows.sort {($0[2] as! Int32) < ($1[2] as! Int32)}
}
}
}

func fetchNext() -> [Any?]? {
Expand Down
117 changes: 117 additions & 0 deletions Tests/SwiftKueryORMTests/TestFind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class TestFind: XCTestCase {
("testFind", testFind),
("testFindAll", testFindAll),
("testFindAllMatching", testFindAllMatching),
("testFindAllLimit", testFindAllLimit),
("testFindAllLimitAndOffset", testFindAllLimitAndOffset),
("testFindAllOrderByDescending", testFindAllOrderByDescending),
("testFindAllOrderByAscending", testFindAllOrderByAscending),
]
}

Expand Down Expand Up @@ -113,4 +117,117 @@ class TestFind: XCTestCase {
}
})
}


/**
Testing that the correct SQL Query is created to retrieve a specific model.
Testing that the model can be retrieved
*/
func testFindAllLimit() {
let connection: TestConnection = createConnection(.returnOneRow)
Database.default = Database(single: connection)
performTest(asyncTasks: { expectation in
Person.findAll(limit: 1) { array, error in
XCTAssertNil(error, "Find Failed: \(String(describing: error))")
XCTAssertNotNil(connection.query, "Find Failed: Query is nil")
if let query = connection.query {
let expectedQuery = "SELECT * FROM \"People\" LIMIT 1"
let resultQuery = connection.descriptionOf(query: query)
XCTAssertEqual(resultQuery, expectedQuery, "Find Failed: Invalid query")
}
XCTAssertNotNil(array, "Find Failed: No array of models returned")
if let array = array {
XCTAssertEqual(array.count, 1, "Find Failed: \(String(describing: array.count)) is not equal to 1")
}
expectation.fulfill()
}
})
}

/**
Testing that the correct SQL Query is created to retrieve a specific model.
Testing that the model can be retrieved
*/
func testFindAllLimitAndOffset() {
let connection: TestConnection = createConnection(.returnOneRow)
Database.default = Database(single: connection)
performTest(asyncTasks: { expectation in
Person.findAll(offset: 2, limit: 1) { array, error in
XCTAssertNil(error, "Find Failed: \(String(describing: error))")
XCTAssertNotNil(connection.query, "Find Failed: Query is nil")
if let query = connection.query {
let expectedQuery = "SELECT * FROM \"People\" LIMIT 1 OFFSET 2"
let resultQuery = connection.descriptionOf(query: query)
XCTAssertEqual(resultQuery, expectedQuery, "Find Failed: Invalid query")
}
XCTAssertNotNil(array, "Find Failed: No array of models returned")
if let array = array {
XCTAssertEqual(array.count, 1, "Find Failed: \(String(describing: array.count)) is not equal to 1")
}
expectation.fulfill()
}
})
}

/**
Testing that the correct SQL Query is created to retrieve a specific model.
Testing that correct amount of models are retrieved
Testing that models are sorted by age in descending order
*/
func testFindAllOrderByDescending() {
let connection: TestConnection = createConnection(.returnThreeRowsSortedDescending)
Database.default = Database(single: connection)
performTest(asyncTasks: { expectation in
Person.findAll(order: Order.desc("age")) { array, error in
XCTAssertNil(error, "Find Failed: \(String(describing: error))")
XCTAssertNotNil(connection.query, "Find Failed: Query is nil")
if let query = connection.query {
let expectedQuery = "SELECT * FROM \"People\" ORDER BY \"People\".\"age\" DESC"
let resultQuery = connection.descriptionOf(query: query)
XCTAssertEqual(resultQuery, expectedQuery, "Find Failed: Invalid query")
}
XCTAssertNotNil(array, "Find Failed: No array of models returned")
if let array = array {
for (index, person) in array.enumerated() {
if index + 1 < array.count {
XCTAssertGreaterThanOrEqual(person.age, array[index + 1].age, "Find Failed: Age of person: \(String(describing: person.age)) is not greater than or equal to age of next person: \(String(describing: array[index + 1].age))")
}
}
XCTAssertEqual(array.count, 3, "Find Failed: \(String(describing: array.count)) is not equal to 3")
}
expectation.fulfill()
}
})
}

/**
Testing that the correct SQL Query is created to retrieve a specific model.
Testing that correct amount of models are retrieved
Testing that models are sorted by age in ascending order
*/
func testFindAllOrderByAscending() {
let connection: TestConnection = createConnection(.returnThreeRowsSortedAscending)
Database.default = Database(single: connection)
performTest(asyncTasks: { expectation in
Person.findAll(order: Order.asc("age")) { array, error in
XCTAssertNil(error, "Find Failed: \(String(describing: error))")
XCTAssertNotNil(connection.query, "Find Failed: Query is nil")
if let query = connection.query {
let expectedQuery = "SELECT * FROM \"People\" ORDER BY \"People\".\"age\" ASC"
let resultQuery = connection.descriptionOf(query: query)
XCTAssertEqual(resultQuery, expectedQuery, "Find Failed: Invalid query")
}
XCTAssertNotNil(array, "Find Failed: No array of models returned")
if let array = array {
for (index, person) in array.enumerated() {
if index + 1 < array.count {
XCTAssertLessThanOrEqual(person.age, array[index + 1].age, "Find Failed: Age of person: \(String(describing: person.age)) is not less than or equal to age of next person: \(String(describing: array[index + 1].age))")
}
}
XCTAssertEqual(array.count, 3, "Find Failed: \(String(describing: array.count)) is not equal to 3")
}
expectation.fulfill()
}
})
}
}

0 comments on commit a03ea46

Please sign in to comment.