123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- /*
- * 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)
- }
- }
|