PostgresRaw.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /*
  2. * Copyright IBM Corporation 2018
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import Foundation
  17. import LoggerAPI
  18. import SwiftKuery
  19. import KueryPostgres
  20. import TechEmpowerCommon
  21. let dbRows = 10000
  22. let maxValue = 10000
  23. // Kuery table definition for World
  24. class World: Table {
  25. let tableName = "world"
  26. let id = Column("id")
  27. let randomNumber = Column("randomnumber")
  28. }
  29. // Kuery table definition for Fortune
  30. class Fortunes: Table {
  31. let tableName = "fortune"
  32. let id = Column("id")
  33. let message = Column("message")
  34. }
  35. let world = World()
  36. let fortunes = Fortunes()
  37. // Kuery update statement for Updates
  38. var update = Update(world, set: [(world.randomNumber, RandomRow.randomValue)])
  39. .where(world.id == RandomRow.randomId)
  40. /// Get a list of Fortunes from the database.
  41. ///
  42. /// - Parameter callback: The callback that will be invoked once the DB query
  43. /// has completed and results are available, passing an
  44. /// optional [Fortune] (on success) or AppError on
  45. /// failure.
  46. ///
  47. public func getFortunes(callback: @escaping ([Fortune]?, AppError?) -> Void) -> Void {
  48. // Get a dedicated connection object for this transaction from the pool
  49. dbConnPool.getConnection { (dbConn, dbConnErr) in
  50. guard let dbConn = dbConn else {
  51. guard let err = dbConnErr else {
  52. return callback(nil, AppError.OtherError("Unknown error getting connection from pool"))
  53. }
  54. return callback(nil, AppError.OtherError("Error getting connection from pool: \(err)"))
  55. }
  56. // Initiate database query
  57. let query = Select(from: fortunes)
  58. dbConn.execute(query: query) { result in
  59. var resultFortunes: [Fortune] = []
  60. // Retrieve all rows from the query result
  61. result.asRows {
  62. results, err in
  63. guard let results = results else {
  64. guard let err = err else {
  65. return callback(nil, AppError.DBKueryError("Query failed, and no error was returned"))
  66. }
  67. return callback(nil, AppError.DBKueryError("Query failed: \(err)"))
  68. }
  69. do {
  70. // Transform the result rows into an array of Fortune objects
  71. resultFortunes = try results.map { try Fortune.init(row: $0) }
  72. } catch {
  73. return callback(nil, AppError.DataFormatError("\(error)"))
  74. }
  75. // Invoke callback with results
  76. callback(resultFortunes, nil)
  77. }
  78. }
  79. }
  80. }
  81. /// Alternate implementation of getFortunes that uses ResultSet.forEach to fetch each
  82. /// database row sequentially, rather than QueryResult.asRows (which produces an array
  83. /// of rows). The benefit of forEach is that we do not need to hold two copies of the
  84. /// entire result set in memory.
  85. ///
  86. /// - Parameter callback: The callback that will be invoked once the DB query
  87. /// has completed and results are available, passing an
  88. /// optional [Fortune] (on success) or AppError on
  89. /// failure.
  90. ///
  91. public func getFortunes_forEach(callback: @escaping ([Fortune]?, AppError?) -> Void) -> Void {
  92. // Get a dedicated connection object for this transaction from the pool
  93. dbConnPool.getConnection { (dbConn, dbConnErr) in
  94. guard let dbConn = dbConn else {
  95. guard let err = dbConnErr else {
  96. return callback(nil, AppError.OtherError("Unknown error getting connection from pool"))
  97. }
  98. return callback(nil, AppError.OtherError("Error getting connection from pool: \(err)"))
  99. }
  100. // Initiate database query
  101. let query = Select(from: fortunes)
  102. dbConn.execute(query: query) { result in
  103. var resultFortunes: [Fortune] = []
  104. guard let results = result.asResultSet else {
  105. guard let queryErr = result.asError else {
  106. return callback(nil, AppError.DBKueryError("Expected a result set, but result was \(result)"))
  107. }
  108. return callback(nil, AppError.DBKueryError("Query failed: \(queryErr)"))
  109. }
  110. // Build an array of Fortune objects
  111. results.forEach { (values, rowErr, next) in
  112. guard let values = values else {
  113. // Reached the final row - call back with the results
  114. return callback(resultFortunes, nil)
  115. }
  116. // Append this Fortune to the list
  117. do {
  118. resultFortunes.append(try Fortune(values: values))
  119. // Process the next column
  120. next()
  121. } catch {
  122. return callback(nil, AppError.DataFormatError("\(error)"))
  123. }
  124. }
  125. }
  126. }
  127. }
  128. /// Get a random row (range 1 to 10,000) from the database.
  129. ///
  130. /// - Parameter callback: The callback that will be invoked once the DB query
  131. /// has completed and results are available, passing an
  132. /// optional RandomRow (on success) or AppError on
  133. /// failure.
  134. ///
  135. public func getRandomRow_Raw(callback: @escaping (RandomRow?, AppError?) -> Void) -> Void {
  136. // Get a dedicated connection object for this transaction from the pool
  137. dbConnPool.getConnection { (dbConn, dbConnErr) in
  138. guard let dbConn = dbConn else {
  139. guard let dbConnErr = dbConnErr else {
  140. return callback(nil, AppError.OtherError("Unknown error getting connection from pool"))
  141. }
  142. return callback(nil, AppError.OtherError("Error getting connection from pool: \(dbConnErr)"))
  143. }
  144. // Select random row from database range
  145. let rnd = RandomRow.randomId
  146. let query = Select(world.randomNumber, from: world)
  147. .where(world.id == rnd)
  148. // Initiate database query
  149. dbConn.execute(query: query) { result in
  150. guard let resultSet = result.asResultSet else {
  151. guard let queryErr = result.asError else {
  152. return callback(nil, AppError.DBKueryError("Expected a result set, but result was \(result)"))
  153. }
  154. return callback(nil, AppError.DBKueryError("Query failed: \(queryErr)"))
  155. }
  156. resultSet.nextRow {
  157. values, nextErr in
  158. guard let values = values else {
  159. guard let nextErr = nextErr else {
  160. return callback(nil, AppError.DBKueryError("Query failed, and no error was returned"))
  161. }
  162. return callback(nil, AppError.DBKueryError("Query failed: \(nextErr)"))
  163. }
  164. // There should be exactly one value
  165. guard values.count == 1 else {
  166. return callback(nil, AppError.DBKueryError("\(values.count) values returned, expected 1, for query '\(query)'"))
  167. }
  168. // The value should be an Int32
  169. guard let randomNumber = values[0] as? Int32 else {
  170. return callback(nil, AppError.DBKueryError("Could not convert \(String(describing: values[0])) to Int32"))
  171. }
  172. let resultRow = RandomRow(id: rnd, randomNumber: Int(randomNumber))
  173. // Invoke callback with results
  174. callback(resultRow, nil)
  175. }
  176. }
  177. }
  178. }
  179. /// Updates a row of World to a new value.
  180. ///
  181. /// - Parameter callback: The callback that will be invoked once the DB update
  182. /// has completed, passing an optional AppError if the
  183. /// update failed.
  184. ///
  185. public func updateRow_Raw(id: Int, callback: @escaping (AppError?) -> Void) -> Void {
  186. // Get a dedicated connection object for this transaction from the pool
  187. dbConnPool.getConnection { (dbConn, err) in
  188. guard let dbConn = dbConn else {
  189. guard let err = err else {
  190. return callback(AppError.OtherError("Unknown error getting connection from pool"))
  191. }
  192. return callback(AppError.OtherError("Error getting connection from pool: \(err)"))
  193. }
  194. // Generate a random number for this row
  195. let rndValue = RandomRow.randomValue
  196. let query = Update(world, set: [(world.randomNumber, rndValue)])
  197. .where(world.id == id)
  198. // Initiate database query
  199. dbConn.execute(query: query) { result in
  200. guard result.success else {
  201. return callback(AppError.DBKueryError("Update failed: \(String(describing: result.asError))"))
  202. }
  203. // Invoke callback once done
  204. callback(nil)
  205. }
  206. }
  207. }
  208. /// Get `count` random rows from the database, and pass the resulting array
  209. /// to a completion handler (or an AppError, in the event that a row could
  210. /// not be retrieved).
  211. ///
  212. /// - Parameter count: The number of rows to retrieve
  213. /// - Parameter result: The intermediate result array being built
  214. /// - Parameter completion: The closure to invoke with the result array, or error
  215. ///
  216. public func getRandomRows_Raw(count: Int, result: [RandomRow] = [], completion: @escaping ([RandomRow]?, AppError?) -> Void) {
  217. if count > 0 {
  218. // Select random row from database range
  219. getRandomRow_Raw { (resultRow, err) in
  220. if let resultRow = resultRow {
  221. var result = result
  222. result.append(resultRow)
  223. // Call recursively to get remaining rows
  224. getRandomRows_Raw(count: count-1, result: result, completion: completion)
  225. } else {
  226. if let err = err {
  227. completion(nil, err)
  228. } else {
  229. fatalError("Unexpected: result and error both nil")
  230. }
  231. }
  232. }
  233. } else {
  234. completion(result, nil)
  235. }
  236. }
  237. /// Update and retrieve `count` random rows from the database, and pass the
  238. /// resulting array to a completion handler (or an AppError, in the event
  239. /// that a row could not be retrieved or updated).
  240. ///
  241. /// - Parameter count: The number of rows to retrieve
  242. /// - Parameter result: The intermediate result array being built
  243. /// - Parameter completion: The closure to invoke with the result array, or error
  244. ///
  245. public func updateRandomRows_Raw(count: Int, result: [RandomRow] = [], completion: @escaping ([RandomRow]?, AppError?) -> Void) {
  246. if count > 0 {
  247. // Select random row from database range
  248. getRandomRow_Raw { (resultRow, err) in
  249. if let resultRow = resultRow {
  250. var result = result
  251. // Execute inner callback for updating the row
  252. updateRow_Raw(id: resultRow.id) { (err) in
  253. if let err = err {
  254. return completion(nil, err)
  255. }
  256. result.append(resultRow)
  257. // Call recursively to update remaining rows
  258. updateRandomRows_Raw(count: count-1, result: result, completion: completion)
  259. }
  260. } else {
  261. if let err = err {
  262. completion(nil, err)
  263. } else {
  264. fatalError("Unexpected: result and error both nil")
  265. }
  266. }
  267. }
  268. } else {
  269. completion(result, nil)
  270. }
  271. }