Postgres.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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 SwiftKueryPostgreSQL
  20. import Configuration
  21. #if os(Linux)
  22. import Glibc
  23. #else
  24. import Darwin
  25. #endif
  26. // We will load our database configuration from config.json, but this can be
  27. // overridden with the TFB_DB_CONFIG environment variable.
  28. let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
  29. let manager = ConfigurationManager().load(file: configurationFilename, relativeFrom: .pwd).load(.environmentVariables)
  30. let dbHost = manager["DB_HOST"] as? String ?? manager["db:host"] as? String ?? "localhost"
  31. let dbPort = Int32(manager["DB_PORT"] as? String != nil ? Int(manager["DB_PORT"] as! String) ?? 5432 : manager["db:port"] as? Int ?? 5432)
  32. let dbName = manager["db:name"] as? String ?? "hello_world"
  33. let dbUser = manager["db:user"] as? String ?? "benchmarkdbuser"
  34. let dbPass = manager["db:password"] as? String ?? "benchmarkdbpass"
  35. let dbRows = 10000
  36. let maxValue = 10000
  37. class World: Table {
  38. let tableName = "World"
  39. let id = Column("id")
  40. let randomNumber = Column("randomNumber")
  41. }
  42. class Fortunes: Table {
  43. let tableName = "Fortune"
  44. let id = Column("id")
  45. let message = Column("message")
  46. }
  47. let world = World()
  48. let fortunes = Fortunes()
  49. var update = Update(world, set: [(world.randomNumber, randomNumberGenerator(maxValue))])
  50. .where(world.id == randomNumberGenerator(dbRows))
  51. let dbConnPoolOpts = ConnectionPoolOptions(initialCapacity: 20, maxCapacity: 50, timeout:10000)
  52. func releaseConnection(connection: Connection) {
  53. connection.closeConnection()
  54. }
  55. func generateConnection() -> Connection? {
  56. var dbConn: Connection
  57. dbConn = PostgreSQLConnection(host: dbHost, port: dbPort,
  58. options: [.databaseName(dbName),
  59. .userName(dbUser), .password(dbPass) ])
  60. dbConn.connect() { error in
  61. if let error = error {
  62. print(error)
  63. return
  64. }
  65. }
  66. return dbConn
  67. }
  68. let dbConnPool = ConnectionPool(options: dbConnPoolOpts, connectionGenerator: generateConnection, connectionReleaser:releaseConnection)
  69. // Return a random number within the range of rows in the database
  70. func randomNumberGenerator(_ maxVal: Int) -> Int {
  71. #if os(Linux)
  72. return Int(random() % maxVal) + 1
  73. #else
  74. return Int(arc4random_uniform(UInt32(maxVal))) + 1
  75. #endif
  76. }
  77. func getFortunes() -> ([Fortune]?, AppError?) {
  78. var resultFortunes: [Fortune]? = nil
  79. var errRes: AppError? = nil
  80. // Get a dedicated connection object for this transaction from the pool
  81. guard let dbConn = dbConnPool.getConnection() else {
  82. errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
  83. return (nil, errRes)
  84. }
  85. // Ensure that when we complete, the connection is returned to the pool
  86. defer {
  87. releaseConnection(connection: dbConn)
  88. }
  89. let query = Select(from: fortunes)
  90. dbConn.execute(query: query) { result in
  91. guard let rows = result.asRows, result.success else {
  92. errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
  93. return
  94. }
  95. do {
  96. resultFortunes = try rows.map { try Fortune.init(row: $0) }
  97. } catch {
  98. errRes = AppError.DataFormatError("\(error)")
  99. }
  100. }
  101. return (resultFortunes, errRes)
  102. }
  103. // Get a random row (range 1 to 10,000) from DB: id(int),randomNumber(int)
  104. // Convert to object using object-relational mapping (ORM) tool
  105. // Serialize object to JSON - example: {"id":3217,"randomNumber":2149}
  106. func getRandomRow() -> ([String:Int]?, AppError?) {
  107. var resultDict: [String:Int]? = nil
  108. var errRes: AppError? = nil
  109. // Get a dedicated connection object for this transaction from the pool
  110. guard let dbConn = dbConnPool.getConnection() else {
  111. errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
  112. return (resultDict, errRes)
  113. }
  114. // Ensure that when we complete, the connection is returned to the pool
  115. defer {
  116. releaseConnection(connection: dbConn)
  117. }
  118. let rnd = randomNumberGenerator(dbRows)
  119. let query = Select(world.randomNumber, from: world)
  120. .where(world.id == rnd)
  121. dbConn.execute(query: query) { result in
  122. if let resultSet = result.asResultSet {
  123. guard result.success else {
  124. errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
  125. return
  126. }
  127. for row in resultSet.rows {
  128. for value in row {
  129. if let unwrapped = value {
  130. guard let randomNumber = unwrapped as? Int32 else {
  131. errRes = AppError.DBKueryError("Error: could not get field as an Int")
  132. return
  133. }
  134. resultDict = ["id":rnd, "randomNumber":Int(randomNumber)]
  135. } else {
  136. errRes = AppError.DBKueryError("Error: randomNumber value is nil")
  137. }
  138. }
  139. }
  140. }
  141. }
  142. return (resultDict, errRes)
  143. }
  144. // Updates a row of World to a new value.
  145. func updateRow(id: Int) throws -> AppError? {
  146. // Get a dedicated connection object for this transaction from the pool
  147. guard let dbConn = dbConnPool.getConnection() else {
  148. throw AppError.OtherError("Timed out waiting for a DB connection from the pool")
  149. }
  150. // Ensure that when we complete, the connection is returned to the pool
  151. defer {
  152. releaseConnection(connection: dbConn)
  153. }
  154. let rndValue = randomNumberGenerator(maxValue)
  155. let query = Update(world, set: [(world.randomNumber, rndValue)])
  156. .where(world.id == id)
  157. var errRes: AppError? = nil
  158. dbConn.execute(query: query) { result in
  159. if result.asResultSet != nil {
  160. guard result.success else {
  161. errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
  162. return
  163. }
  164. }
  165. }
  166. return errRes
  167. }