From 3c72ff7195831e5825795b3db4a6490dd750a115 Mon Sep 17 00:00:00 2001 From: AnthonyAmanse Date: Fri, 22 Jun 2018 11:42:36 -0700 Subject: [PATCH] Add more options in findAll 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 --- Sources/SwiftKueryORM/Model.swift | 48 +++++-- Tests/SwiftKueryORMTests/CommonUtils.swift | 17 ++- Tests/SwiftKueryORMTests/TestFind.swift | 140 +++++++++++++++++++++ 3 files changed, 194 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftKueryORM/Model.swift b/Sources/SwiftKueryORM/Model.swift index a8684c6..c7ed117 100644 --- a/Sources/SwiftKueryORM/Model.swift +++ b/Sources/SwiftKueryORM/Model.swift @@ -82,15 +82,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?, orderBy: OrderBy..., 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(using db: Database?, _ onCompletion: @escaping ([(I, Self)]?, RequestError?) -> Void) + static func findAll(using db: Database?, orderBy: OrderBy..., 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(using db: Database?, _ onCompletion: @escaping ([I: Self]?, RequestError?) -> Void) + static func findAll(using db: Database?, orderBy: OrderBy..., 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 @@ -978,7 +978,7 @@ public extension Model { /// - static func findAll(using db: Database? = nil, _ onCompletion: @escaping ([Self]?, RequestError?) -> Void) { + static func findAll(using db: Database? = nil, orderBy: OrderBy..., offset: Int? = nil, limit: Int? = nil, _ onCompletion: @escaping ([Self]?, RequestError?) -> Void) { var table: Table do { table = try Self.getTable() @@ -987,14 +987,24 @@ public extension Model { return } - let query = Select(from: 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) + } + 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(using db: Database? = nil, _ onCompletion: @escaping ([(I, Self)]?, RequestError?) -> Void) { + static func findAll(using db: Database? = nil, orderBy: OrderBy..., offset: Int? = nil, limit: Int? = nil, _ onCompletion: @escaping ([(I, Self)]?, RequestError?) -> Void) { var table: Table do { table = try Self.getTable() @@ -1003,12 +1013,22 @@ public extension Model { return } - let query = Select(from: 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) + } + Self.executeQuery(query: query, using: db, onCompletion) } /// :nodoc: - static func findAll(using db: Database? = nil, _ onCompletion: @escaping ([I: Self]?, RequestError?) -> Void) { + static func findAll(using db: Database? = nil, orderBy: OrderBy..., offset: Int? = nil, limit: Int? = nil, _ onCompletion: @escaping ([I: Self]?, RequestError?) -> Void) { var table: Table do { table = try Self.getTable() @@ -1017,7 +1037,17 @@ public extension Model { return } - let query = Select(from: 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) + } + Self.executeQuery(query: query, using: db) { (tuples: [(I, Self)]?, error: RequestError?) in if let error = error { onCompletion(nil, error) diff --git a/Tests/SwiftKueryORMTests/CommonUtils.swift b/Tests/SwiftKueryORMTests/CommonUtils.swift index 2a3c3ab..2a4eeae 100644 --- a/Tests/SwiftKueryORMTests/CommonUtils.swift +++ b/Tests/SwiftKueryORMTests/CommonUtils.swift @@ -35,6 +35,8 @@ class TestConnection: Connection { case returnEmpty case returnOneRow case returnThreeRows + case returnThreeRowsSortedAscending + case returnThreeRowsSortedDescending case returnError case returnValue } @@ -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: @@ -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 = [[1, "Joe", Int32(38)], [3, "Chris", Int32(36)], [2, "Adam", Int32(28)]] + } else if sortedByAge == "ascending" { + rows = [[2, "Adam", Int32(28)], [3, "Chris", Int32(36)], [1, "Joe", Int32(38)]] + } + } } func fetchNext() -> [Any?]? { diff --git a/Tests/SwiftKueryORMTests/TestFind.swift b/Tests/SwiftKueryORMTests/TestFind.swift index 6fc4539..2469db4 100644 --- a/Tests/SwiftKueryORMTests/TestFind.swift +++ b/Tests/SwiftKueryORMTests/TestFind.swift @@ -2,6 +2,7 @@ import XCTest @testable import SwiftKueryORM import Foundation +import SwiftKuery import KituraContracts class TestFind: XCTestCase { @@ -10,6 +11,10 @@ class TestFind: XCTestCase { ("testFind", testFind), ("testFindAll", testFindAll), ("testFindAllMatching", testFindAllMatching), + ("testFindAllLimit", testFindAllLimit), + ("testFindAllLimitAndOffset", testFindAllLimitAndOffset), + ("testFindAllOrderByDescending", testFindAllOrderByDescending), + ("testFindAllOrderByAscending", testFindAllOrderByAscending), ] } @@ -113,4 +118,139 @@ 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) + var column: Column? + do { + let table = try Person.getTable() + column = table.columns.first(where: {$0.name == "age"}) + XCTAssertNotNil(column, "Find Failed: column not found for ordering") + guard column != nil else { + return + } + } catch let error { + XCTAssertNil(error, "Find Failed: \(String(describing: error))") + } + performTest(asyncTasks: { expectation in + Person.findAll(orderBy: OrderBy.DESC(column!)) { 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) + var column: Column? + do { + let table = try Person.getTable() + column = table.columns.first(where: {$0.name == "age"}) + XCTAssertNotNil(column, "Find Failed: column not found for ordering") + guard column != nil else { + return + } + } catch let error { + XCTAssertNil(error, "Find Failed: \(String(describing: error))") + } + performTest(asyncTasks: { expectation in + Person.findAll(orderBy: OrderBy.DESC(column!)) { 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 { + 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() + } + }) + } }