/* * Copyright IBM Corporation 2018 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation import LoggerAPI import SwiftKuery import KueryPostgres import TechEmpowerCommon let dbRows = 10000 let maxValue = 10000 // Kuery table definition for World class World: Table { let tableName = "world" let id = Column("id") let randomNumber = Column("randomnumber") } // Kuery table definition for Fortune class Fortunes: Table { let tableName = "fortune" let id = Column("id") let message = Column("message") } let world = World() let fortunes = Fortunes() // Kuery update statement for Updates var update = Update(world, set: [(world.randomNumber, RandomRow.randomValue)]) .where(world.id == RandomRow.randomId) /// Get a list of Fortunes from the database. /// /// - Parameter callback: The callback that will be invoked once the DB query /// has completed and results are available, passing an /// optional [Fortune] (on success) or AppError on /// failure. /// public func getFortunes(callback: @escaping ([Fortune]?, AppError?) -> Void) -> Void { // Get a dedicated connection object for this transaction from the pool dbConnPool.getConnection { (dbConn, dbConnErr) in guard let dbConn = dbConn else { guard let err = dbConnErr else { return callback(nil, AppError.OtherError("Unknown error getting connection from pool")) } return callback(nil, AppError.OtherError("Error getting connection from pool: \(err)")) } // Initiate database query let query = Select(from: fortunes) dbConn.execute(query: query) { result in var resultFortunes: [Fortune] = [] // Retrieve all rows from the query result result.asRows { results, err in guard let results = results else { guard let err = err else { return callback(nil, AppError.DBKueryError("Query failed, and no error was returned")) } return callback(nil, AppError.DBKueryError("Query failed: \(err)")) } do { // Transform the result rows into an array of Fortune objects resultFortunes = try results.map { try Fortune.init(row: $0) } } catch { return callback(nil, AppError.DataFormatError("\(error)")) } // Invoke callback with results callback(resultFortunes, nil) } } } } /// Alternate implementation of getFortunes that uses ResultSet.forEach to fetch each /// database row sequentially, rather than QueryResult.asRows (which produces an array /// of rows). The benefit of forEach is that we do not need to hold two copies of the /// entire result set in memory. /// /// - Parameter callback: The callback that will be invoked once the DB query /// has completed and results are available, passing an /// optional [Fortune] (on success) or AppError on /// failure. /// public func getFortunes_forEach(callback: @escaping ([Fortune]?, AppError?) -> Void) -> Void { // Get a dedicated connection object for this transaction from the pool dbConnPool.getConnection { (dbConn, dbConnErr) in guard let dbConn = dbConn else { guard let err = dbConnErr else { return callback(nil, AppError.OtherError("Unknown error getting connection from pool")) } return callback(nil, AppError.OtherError("Error getting connection from pool: \(err)")) } // Initiate database query let query = Select(from: fortunes) dbConn.execute(query: query) { result in var resultFortunes: [Fortune] = [] guard let results = result.asResultSet else { guard let queryErr = result.asError else { return callback(nil, AppError.DBKueryError("Expected a result set, but result was \(result)")) } return callback(nil, AppError.DBKueryError("Query failed: \(queryErr)")) } // Build an array of Fortune objects results.forEach { (values, rowErr, next) in guard let values = values else { // Reached the final row - call back with the results return callback(resultFortunes, nil) } // Append this Fortune to the list do { resultFortunes.append(try Fortune(values: values)) // Process the next column next() } catch { return callback(nil, AppError.DataFormatError("\(error)")) } } } } } /// Get a random row (range 1 to 10,000) from the database. /// /// - Parameter callback: The callback that will be invoked once the DB query /// has completed and results are available, passing an /// optional RandomRow (on success) or AppError on /// failure. /// public func getRandomRow_Raw(callback: @escaping (RandomRow?, AppError?) -> Void) -> Void { // Get a dedicated connection object for this transaction from the pool dbConnPool.getConnection { (dbConn, dbConnErr) in guard let dbConn = dbConn else { guard let dbConnErr = dbConnErr else { return callback(nil, AppError.OtherError("Unknown error getting connection from pool")) } return callback(nil, AppError.OtherError("Error getting connection from pool: \(dbConnErr)")) } // Select random row from database range let rnd = RandomRow.randomId let query = Select(world.randomNumber, from: world) .where(world.id == rnd) // Initiate database query dbConn.execute(query: query) { result in guard let resultSet = result.asResultSet else { guard let queryErr = result.asError else { return callback(nil, AppError.DBKueryError("Expected a result set, but result was \(result)")) } return callback(nil, AppError.DBKueryError("Query failed: \(queryErr)")) } resultSet.nextRow { values, nextErr in guard let values = values else { guard let nextErr = nextErr else { return callback(nil, AppError.DBKueryError("Query failed, and no error was returned")) } return callback(nil, AppError.DBKueryError("Query failed: \(nextErr)")) } // There should be exactly one value guard values.count == 1 else { return callback(nil, AppError.DBKueryError("\(values.count) values returned, expected 1, for query '\(query)'")) } // The value should be an Int32 guard let randomNumber = values[0] as? Int32 else { return callback(nil, AppError.DBKueryError("Could not convert \(String(describing: values[0])) to Int32")) } let resultRow = RandomRow(id: rnd, randomNumber: Int(randomNumber)) // Invoke callback with results callback(resultRow, nil) } } } } /// Updates a row of World to a new value. /// /// - Parameter callback: The callback that will be invoked once the DB update /// has completed, passing an optional AppError if the /// update failed. /// public func updateRow_Raw(id: Int, callback: @escaping (AppError?) -> Void) -> Void { // Get a dedicated connection object for this transaction from the pool dbConnPool.getConnection { (dbConn, err) in guard let dbConn = dbConn else { guard let err = err else { return callback(AppError.OtherError("Unknown error getting connection from pool")) } return callback(AppError.OtherError("Error getting connection from pool: \(err)")) } // Generate a random number for this row let rndValue = RandomRow.randomValue let query = Update(world, set: [(world.randomNumber, rndValue)]) .where(world.id == id) // Initiate database query dbConn.execute(query: query) { result in guard result.success else { return callback(AppError.DBKueryError("Update failed: \(String(describing: result.asError))")) } // Invoke callback once done callback(nil) } } } /// Get `count` random rows from the database, and pass the resulting array /// to a completion handler (or an AppError, in the event that a row could /// not be retrieved). /// /// - Parameter count: The number of rows to retrieve /// - Parameter result: The intermediate result array being built /// - Parameter completion: The closure to invoke with the result array, or error /// public func getRandomRows_Raw(count: Int, result: [RandomRow] = [], completion: @escaping ([RandomRow]?, AppError?) -> Void) { if count > 0 { // Select random row from database range getRandomRow_Raw { (resultRow, err) in if let resultRow = resultRow { var result = result result.append(resultRow) // Call recursively to get remaining rows getRandomRows_Raw(count: count-1, result: result, completion: completion) } else { if let err = err { completion(nil, err) } else { fatalError("Unexpected: result and error both nil") } } } } else { completion(result, nil) } } /// Update and retrieve `count` random rows from the database, and pass the /// resulting array to a completion handler (or an AppError, in the event /// that a row could not be retrieved or updated). /// /// - Parameter count: The number of rows to retrieve /// - Parameter result: The intermediate result array being built /// - Parameter completion: The closure to invoke with the result array, or error /// public func updateRandomRows_Raw(count: Int, result: [RandomRow] = [], completion: @escaping ([RandomRow]?, AppError?) -> Void) { if count > 0 { // Select random row from database range getRandomRow_Raw { (resultRow, err) in if let resultRow = resultRow { var result = result // Execute inner callback for updating the row updateRow_Raw(id: resultRow.id) { (err) in if let err = err { return completion(nil, err) } result.append(resultRow) // Call recursively to update remaining rows updateRandomRows_Raw(count: count-1, result: result, completion: completion) } } else { if let err = err { completion(nil, err) } else { fatalError("Unexpected: result and error both nil") } } } } else { completion(result, nil) } }