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 3, 2018
1 parent d1b3588 commit ff27ca9
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 11 deletions.
84 changes: 75 additions & 9 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 @@ -967,7 +967,7 @@ 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()
Expand All @@ -976,14 +976,36 @@ public extension Model {
return
}

let query = Select(from: table)
var query = Select(from: table)
var orderByArray: [OrderBy] = []
for order in order {
guard let column = table.columns.first(where: {$0.name == order.value}) else {
onCompletion(nil, RequestError(.ormInternalError, reason: "Column \(order.value) not found"))
return
}
if case .asc(order.value) = order {
orderByArray.append(.ASC(column))
} else {
orderByArray.append(.DESC(column))
}
}
if orderByArray.count > 0 {
query = query.order(by: orderByArray)
}
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<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()
Expand All @@ -992,12 +1014,34 @@ public extension Model {
return
}

let query = Select(from: table)
var query = Select(from: table)
var orderByArray: [OrderBy] = []
for order in order {
guard let column = table.columns.first(where: {$0.name == order.value}) else {
onCompletion(nil, RequestError(.ormInternalError, reason: "Column \(order.value) not found"))
return
}
if case .asc(order.value) = order {
orderByArray.append(.ASC(column))
} else {
orderByArray.append(.DESC(column))
}
}
if orderByArray.count > 0 {
query = query.order(by: orderByArray)
}
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<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()
Expand All @@ -1006,7 +1050,29 @@ public extension Model {
return
}

let query = Select(from: table)
var query = Select(from: table)
var orderByArray: [OrderBy] = []
for order in order {
guard let column = table.columns.first(where: {$0.name == order.value}) else {
onCompletion(nil, RequestError(.ormInternalError, reason: "Column \(order.value) not found"))
return
}
if case .asc(order.value) = order {
orderByArray.append(.ASC(column))
} else {
orderByArray.append(.DESC(column))
}
}
if orderByArray.count > 0 {
query = query.order(by: orderByArray)
}
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)
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 = [[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?]? {
Expand Down
136 changes: 136 additions & 0 deletions Tests/SwiftKueryORMTests/TestFind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ class TestFind: XCTestCase {
("testFind", testFind),
("testFindAll", testFindAll),
("testFindAllMatching", testFindAllMatching),
("testFindAllLimit", testFindAllLimit),
("testFindAllLimitAndOffset", testFindAllLimitAndOffset),
("testFindAllOrderByDescending", testFindAllOrderByDescending),
("testFindAllOrderByAscending", testFindAllOrderByAscending),
("testFindAllOrderByColumnNotFound", testFindAllOrderByColumnNotFound),
]
}

Expand Down Expand Up @@ -113,4 +118,135 @@ 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()
}
})
}

/**
Testing that query should fail if column is not found in table
*/
func testFindAllOrderByColumnNotFound() {
let connection: TestConnection = createConnection(.returnThreeRowsSortedAscending)
Database.default = Database(single: connection)
performTest(asyncTasks: { expectation in
Person.findAll(order: Order.asc("ages")) { array, error in
XCTAssertNotNil(error, "Error should not be nil")
if let error = error {
XCTAssertEqual("710 : Column ages not found", String(describing: error), "Error should be: 710 : Column ages not found")
}
XCTAssertNil(connection.query, "Find Failed: Query should be nil")
expectation.fulfill()
}
})
}
}

0 comments on commit ff27ca9

Please sign in to comment.