Browse Source

Update Swift/kitura to Kitura 2.5 and use ORM (#4173)

* Refactor to reduce code duplication
Fix use of Kuery API to be properly asynchronous
Update to Swift 4.1.2

* Add ORM implementation using Swift-Kuery-ORM
Rename executable targets
Update benchmark_config

* Add Codable Routing implementation
Add recursive ORM getters
Update dockerfiles

* Update to Kitura 2.5.x
Remove Package.resolved
Use a parallel approach for ORMCodable until Kuery is fully asynchronous

* Update README.md

* Refactor into parallel and sequential versions

Remove unnecessary semaphore

Use parallel version until Kuery is asynchronous
David Jones 6 years ago
parent
commit
3ad275cb7f
36 changed files with 1455 additions and 1308 deletions
  1. 1 1
      frameworks/Swift/kitura/.swift-version
  2. 0 277
      frameworks/Swift/kitura/Package.resolved
  3. 30 7
      frameworks/Swift/kitura/Package.swift
  4. 17 13
      frameworks/Swift/kitura/README.md
  5. 42 0
      frameworks/Swift/kitura/Sources/KueryPostgres/Postgres.swift
  6. 223 0
      frameworks/Swift/kitura/Sources/KueryPostgresORM/PostgresORM.swift
  7. 207 0
      frameworks/Swift/kitura/Sources/KueryPostgresRaw/PostgresRaw.swift
  8. 44 0
      frameworks/Swift/kitura/Sources/TechEmpower/main.swift
  9. 13 0
      frameworks/Swift/kitura/Sources/TechEmpowerCommon/Error.swift
  10. 67 0
      frameworks/Swift/kitura/Sources/TechEmpowerCommon/Fortune.swift
  11. 70 0
      frameworks/Swift/kitura/Sources/TechEmpowerCommon/RandomRow.swift
  12. 0 55
      frameworks/Swift/kitura/Sources/TechEmpowerKuery/Fortune.swift
  13. 0 203
      frameworks/Swift/kitura/Sources/TechEmpowerKuery/Postgres.swift
  14. 0 22
      frameworks/Swift/kitura/Sources/TechEmpowerKuery/error.swift
  15. 0 182
      frameworks/Swift/kitura/Sources/TechEmpowerKuery/main.swift
  16. 0 55
      frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Fortune.swift
  17. 0 203
      frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Postgres.swift
  18. 0 163
      frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/main.swift
  19. 0 55
      frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/Fortune.swift
  20. 13 17
      frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/Mongo.swift
  21. 0 22
      frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/error.swift
  22. 1 20
      frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/main.swift
  23. 210 0
      frameworks/Swift/kitura/Sources/TechEmpowerPostgres/main.swift
  24. 2 1
      frameworks/Swift/kitura/Sources/TechEmpowerPostgresMustache/Fortune+Mustache.swift
  25. 57 0
      frameworks/Swift/kitura/Sources/TechEmpowerPostgresMustache/main.swift
  26. 166 0
      frameworks/Swift/kitura/Sources/TechEmpowerPostgresORM/main.swift
  27. 136 0
      frameworks/Swift/kitura/Sources/TechEmpowerPostgresORMCodable/main.swift
  28. 112 10
      frameworks/Swift/kitura/benchmark_config.json
  29. 7 0
      frameworks/Swift/kitura/kitura-gcd-postgres-orm-codable.dockerfile
  30. 7 0
      frameworks/Swift/kitura/kitura-gcd-postgres-orm.dockerfile
  31. 7 0
      frameworks/Swift/kitura/kitura-gcd-postgres.dockerfile
  32. 1 1
      frameworks/Swift/kitura/kitura-gcd.dockerfile
  33. 7 0
      frameworks/Swift/kitura/kitura-postgres-orm-codable.dockerfile
  34. 7 0
      frameworks/Swift/kitura/kitura-postgres-orm.dockerfile
  35. 7 0
      frameworks/Swift/kitura/kitura-postgres.dockerfile
  36. 1 1
      frameworks/Swift/kitura/kitura.dockerfile

+ 1 - 1
frameworks/Swift/kitura/.swift-version

@@ -1 +1 @@
-4.1
+4.1.2

+ 0 - 277
frameworks/Swift/kitura/Package.resolved

@@ -1,277 +0,0 @@
-{
-  "object": {
-    "pins": [
-      {
-        "package": "Signals",
-        "repositoryURL": "https://github.com/IBM-Swift/BlueSignals.git",
-        "state": {
-          "branch": null,
-          "revision": "acd6304348ebeb181c0952ee91aa2c72a7826a12",
-          "version": "1.0.1"
-        }
-      },
-      {
-        "package": "Socket",
-        "repositoryURL": "https://github.com/IBM-Swift/BlueSocket.git",
-        "state": {
-          "branch": null,
-          "revision": "36561e7722f26151b8a6ba4b4bb96fd9b3f36980",
-          "version": "1.0.4"
-        }
-      },
-      {
-        "package": "SSLService",
-        "repositoryURL": "https://github.com/IBM-Swift/BlueSSLService.git",
-        "state": {
-          "branch": null,
-          "revision": "4759df40007b50d087c6513f844c36b243415296",
-          "version": "1.0.4"
-        }
-      },
-      {
-        "package": "BSON",
-        "repositoryURL": "https://github.com/OpenKitten/BSON.git",
-        "state": {
-          "branch": null,
-          "revision": "3edad325da31095c9d54d04c6125f1fec1f3c5bf",
-          "version": "5.2.3"
-        }
-      },
-      {
-        "package": "CCurl",
-        "repositoryURL": "https://github.com/IBM-Swift/CCurl.git",
-        "state": {
-          "branch": null,
-          "revision": "2a431e3a5fb8d004aa7d4d1bebb5d511f568c43e",
-          "version": "1.0.0"
-        }
-      },
-      {
-        "package": "CEpoll",
-        "repositoryURL": "https://github.com/IBM-Swift/CEpoll.git",
-        "state": {
-          "branch": null,
-          "revision": "4e56acbea4d452110236dcbfcee0045a697c0e9f",
-          "version": "1.0.0"
-        }
-      },
-      {
-        "package": "Cheetah",
-        "repositoryURL": "https://github.com/OpenKitten/Cheetah.git",
-        "state": {
-          "branch": null,
-          "revision": "801f4fa278670719bec074b708241878ad5fb25e",
-          "version": "2.0.2"
-        }
-      },
-      {
-        "package": "CLibpq",
-        "repositoryURL": "https://github.com/IBM-Swift/CLibpq.git",
-        "state": {
-          "branch": null,
-          "revision": "20421e0c7890a2a9b1515eb1321ebcd76ad7231f",
-          "version": "0.1.2"
-        }
-      },
-      {
-        "package": "Configuration",
-        "repositoryURL": "https://github.com/IBM-Swift/Configuration.git",
-        "state": {
-          "branch": null,
-          "revision": "a5e3ce4619089069697bbef5abfe3687f6830b9d",
-          "version": "3.0.1"
-        }
-      },
-      {
-        "package": "CryptoKitten",
-        "repositoryURL": "https://github.com/OpenKitten/CryptoKitten.git",
-        "state": {
-          "branch": null,
-          "revision": "bf7a4d6dbd6f4bbd96ae620101239446d2cdd3f9",
-          "version": "0.2.3"
-        }
-      },
-      {
-        "package": "FileKit",
-        "repositoryURL": "https://github.com/IBM-Swift/FileKit.git",
-        "state": {
-          "branch": null,
-          "revision": "319016dbf4b3ce04589265c23c9cac0016fe8096",
-          "version": "0.0.1"
-        }
-      },
-      {
-        "package": "Mustache",
-        "repositoryURL": "https://github.com/IBM-Swift/GRMustache.swift.git",
-        "state": {
-          "branch": null,
-          "revision": "f27856d0e2182d41a56c8c6be115baadf87857d0",
-          "version": "1.7.4"
-        }
-      },
-      {
-        "package": "HeliumLogger",
-        "repositoryURL": "https://github.com/IBM-Swift/HeliumLogger.git",
-        "state": {
-          "branch": null,
-          "revision": "2afeb4c02f5a48a7fbd081fe8607b91eeebd7ecb",
-          "version": "1.7.1"
-        }
-      },
-      {
-        "package": "KittenCTLS",
-        "repositoryURL": "https://github.com/OpenKitten/KittenCTLS.git",
-        "state": {
-          "branch": null,
-          "revision": "be8c42f22975e8033c6f342f42b8999407672bb3",
-          "version": "1.0.1"
-        }
-      },
-      {
-        "package": "Kitura",
-        "repositoryURL": "https://github.com/IBM-Swift/Kitura.git",
-        "state": {
-          "branch": null,
-          "revision": "55c2937de7d2d766082a5af63f28325dea6473fa",
-          "version": "2.3.0"
-        }
-      },
-      {
-        "package": "KituraMustache",
-        "repositoryURL": "https://github.com/IBM-Swift/Kitura-MustacheTemplateEngine.git",
-        "state": {
-          "branch": null,
-          "revision": "54e4929c4bab6b5c44a11ef68bb04894245a8370",
-          "version": "1.7.3"
-        }
-      },
-      {
-        "package": "Kitura-net",
-        "repositoryURL": "https://github.com/IBM-Swift/Kitura-net.git",
-        "state": {
-          "branch": null,
-          "revision": "755c39febb661d5da85e1ec9fcf98721c420af0a",
-          "version": "2.1.0"
-        }
-      },
-      {
-        "package": "KituraStencil",
-        "repositoryURL": "https://github.com/IBM-Swift/Kitura-StencilTemplateEngine.git",
-        "state": {
-          "branch": null,
-          "revision": "535d9e1a3def131386cca1e26cfb5ef0477d4c65",
-          "version": "1.9.1"
-        }
-      },
-      {
-        "package": "Kitura-TemplateEngine",
-        "repositoryURL": "https://github.com/IBM-Swift/Kitura-TemplateEngine.git",
-        "state": {
-          "branch": null,
-          "revision": "d43155da4dadd91c1d34d76e317571dc54655594",
-          "version": "1.7.4"
-        }
-      },
-      {
-        "package": "KituraContracts",
-        "repositoryURL": "https://github.com/IBM-Swift/KituraContracts.git",
-        "state": {
-          "branch": null,
-          "revision": "de85c1a28bb379cd423f5a17eb651222112d2c3e",
-          "version": "0.0.22"
-        }
-      },
-      {
-        "package": "LoggerAPI",
-        "repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git",
-        "state": {
-          "branch": null,
-          "revision": "5041f2673aa75d6e973d9b6bd3956bc5068387c8",
-          "version": "1.7.3"
-        }
-      },
-      {
-        "package": "MongoKitten",
-        "repositoryURL": "https://github.com/OpenKitten/MongoKitten.git",
-        "state": {
-          "branch": null,
-          "revision": "f564cc39c6088c23bbafd7ea8910dc270517b77e",
-          "version": "4.1.3"
-        }
-      },
-      {
-        "package": "OpenSSL",
-        "repositoryURL": "https://github.com/IBM-Swift/OpenSSL.git",
-        "state": {
-          "branch": null,
-          "revision": "95c405c18a071a1c13616e984ba1b3c6091ca6ca",
-          "version": "1.0.0"
-        }
-      },
-      {
-        "package": "PathKit",
-        "repositoryURL": "https://github.com/kylef/PathKit.git",
-        "state": {
-          "branch": null,
-          "revision": "fa81fa9e3a9f59645159c4ea45c0c46ee6558f71",
-          "version": "0.9.1"
-        }
-      },
-      {
-        "package": "Schrodinger",
-        "repositoryURL": "https://github.com/OpenKitten/Schrodinger.git",
-        "state": {
-          "branch": null,
-          "revision": "d7030fe0a4a6eeddc02044650a8b409a4f57365a",
-          "version": "1.0.1"
-        }
-      },
-      {
-        "package": "Spectre",
-        "repositoryURL": "https://github.com/kylef/Spectre.git",
-        "state": {
-          "branch": null,
-          "revision": "e34d5687e1e9d865e3527dd58bc2f7464ef6d936",
-          "version": "0.8.0"
-        }
-      },
-      {
-        "package": "Stencil",
-        "repositoryURL": "https://github.com/kylef/Stencil.git",
-        "state": {
-          "branch": null,
-          "revision": "c2e25f25acfbe24442809c055141d8323af8f6cc",
-          "version": "0.11.0"
-        }
-      },
-      {
-        "package": "SwiftKuery",
-        "repositoryURL": "https://github.com/IBM-Swift/Swift-Kuery.git",
-        "state": {
-          "branch": null,
-          "revision": "a95ae0131c6742e8ff1575db8ed7fda1bf4b93ee",
-          "version": "1.3.0"
-        }
-      },
-      {
-        "package": "SwiftKueryPostgreSQL",
-        "repositoryURL": "https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL",
-        "state": {
-          "branch": null,
-          "revision": "4177014a8e85da24afbcdbbb7baaac070d549fac",
-          "version": "1.1.4"
-        }
-      },
-      {
-        "package": "SwiftyJSON",
-        "repositoryURL": "https://github.com/IBM-Swift/SwiftyJSON.git",
-        "state": {
-          "branch": null,
-          "revision": "7eb6c83f91c519c19e6a507d604c9176d5ae5b15",
-          "version": "17.0.1"
-        }
-      }
-    ]
-  },
-  "version": 1
-}

+ 30 - 7
frameworks/Swift/kitura/Package.swift

@@ -6,23 +6,46 @@ import PackageDescription
 let package = Package(
     name: "Kitura-TechEmpower",
     dependencies: [
-        .package(url: "https://github.com/IBM-Swift/Kitura.git", from: "2.3.0"),
+        .package(url: "https://github.com/IBM-Swift/Kitura.git", .upToNextMinor(from: "2.5.0")),
+        .package(url: "https://github.com/IBM-Swift/LoggerAPI.git", from: "1.7.0"),
         .package(url: "https://github.com/IBM-Swift/HeliumLogger.git", from: "1.7.0"),
         .package(url: "https://github.com/IBM-Swift/Configuration.git", from: "3.0.0"),
-        .package(url: "https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL", from: "1.0.0"),
+        .package(url: "https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL.git", from: "1.0.0"),
+        .package(url: "https://github.com/IBM-Swift/Swift-Kuery-ORM.git", from: "0.3.0"),
         .package(url: "https://github.com/IBM-Swift/Kitura-StencilTemplateEngine.git", from: "1.9.0"),
         .package(url: "https://github.com/IBM-Swift/Kitura-MustacheTemplateEngine.git", from: "1.7.2"),
         .package(url: "https://github.com/OpenKitten/MongoKitten.git", from: "4.1.3"),
     ],
     targets: [
         .target(
-            name: "TechEmpowerKuery",
-            dependencies: ["Kitura", "HeliumLogger", "Configuration", "SwiftKueryPostgreSQL", "KituraStencil"]),
+            name: "TechEmpowerCommon",
+            dependencies: []),
         .target(
-            name: "TechEmpowerKueryMustache",
-            dependencies: ["Kitura", "HeliumLogger", "Configuration", "SwiftKueryPostgreSQL", "KituraMustache"]),
+            name: "KueryPostgres",
+            dependencies: [.target(name: "TechEmpowerCommon"), "Configuration", "SwiftKueryPostgreSQL"]),
+        .target(
+            name: "KueryPostgresRaw",
+            dependencies: [.target(name: "KueryPostgres"), "LoggerAPI"]),
+        .target(
+            name: "KueryPostgresORM",
+            dependencies: [.target(name: "KueryPostgres"), "LoggerAPI", "SwiftKueryORM"]),
+        .target(
+            name: "TechEmpower",
+            dependencies: ["Kitura"]),
+        .target(
+            name: "TechEmpowerPostgres",
+            dependencies: [.target(name: "KueryPostgresRaw"), "Kitura", "HeliumLogger", "KituraStencil"]),
+        .target(
+            name: "TechEmpowerPostgresORM",
+            dependencies: [.target(name: "KueryPostgresORM"), "Kitura", "HeliumLogger", "KituraStencil"]),
+        .target(
+            name: "TechEmpowerPostgresORMCodable",
+            dependencies: [.target(name: "KueryPostgresORM"), "Kitura", "HeliumLogger", "KituraStencil"]),
+        .target(
+            name: "TechEmpowerPostgresMustache",
+            dependencies: [.target(name: "KueryPostgresRaw"), "Kitura", "HeliumLogger", "KituraMustache"]),
         .target(
             name: "TechEmpowerMongoKitten",
-            dependencies: ["Kitura", "HeliumLogger", "Configuration", "MongoKitten", "KituraStencil"]),
+            dependencies: [.target(name: "TechEmpowerCommon"), "Kitura", "HeliumLogger", "Configuration", "MongoKitten", "KituraStencil"]),
     ]
 )

+ 17 - 13
frameworks/Swift/kitura/README.md

@@ -4,25 +4,29 @@ This is the [Kitura](https://kitura.io) portion of a [benchmarking test suite](h
 
 ## Variants
 
-There are two versions of the benchmark, using different database backends:
-- `kitura`: Kitura with Postgres
-- `kitura-mongokitten`: Kitura with MongoDB
+The benchmark is split up into multiple executable targets, demonstrating different database backends and uses of the Kitura routing APIs:
+- `kitura`: Implementations of Plaintext and JSON using 'raw' (Express-style) routing
+- `kitura-postgres`: Implementation of database tests, using Postgres with Swift-Kuery (no ORM)
+- `kitura-postgres-orm`: Equivalent implementation with Postgres and Swift-Kuery-ORM
+- `kitura-postgres-orm-codable`: Equivalent implementation with Postgres, using [Codable Routing together with Swift-Kuery-ORM](https://developer.ibm.com/swift/2018/03/01/introducing-swift-kuery-orm/)
+- `kitura-mongodb`: Implementation of database tests, using MongoDB with MongoKitten (no ORM)
 
-There are two additional variants: `kitura-gcd` and `kitura-gcd-mongokitten`. These are compiled from the same source, but use the Grand Central Dispatch threading model (used by default on macOS) instead of the default epoll implementation on Linux.
+There are additional variants for each of the above implementations, with the '-gcd' suffix: These are compiled from the same source, but use the Grand Central Dispatch threading model (used by default on macOS) instead of a direct epoll implementation (the default on Linux).
 
-Each listens on port 8080, and use common URLs described below.
+Each listens on port 8080, and uses the common URLs described below.
 
 ## Versions and Dependencies
 
-This version of the benchmark requires Swift 4.0.3 or Swift 4.1, and uses the following versions of Kitura and dependencies:
+This version of the benchmark requires Swift 4.0.3 or higher, and uses the following versions of Kitura and dependencies:
 
-- [Kitura 2.3](https://github.com/IBM-Swift/Kitura)
-- [HeliumLogger 1.7](https://github.com/IBM-Swift/HeliumLogger)
-- [Configuration 3.0](https://github.com/IBM-Swift/Configuration)
-- [SwiftKueryPostgreSQL 1.1](https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL)
-- [KituraStencil 1.9](https://github.com/IBM-Swift/Kitura-StencilTemplateEngine)
-- [KituraMustache 1.7](https://github.com/IBM-Swift/Kitura-MustacheTemplateEngine)
-- [MongoKitten 4.1.3](https://github.com/OpenKitten/MongoKitten)
+- [Kitura 2.5](https://github.com/IBM-Swift/Kitura)
+- [HeliumLogger 1.x](https://github.com/IBM-Swift/HeliumLogger)
+- [Configuration 3.x](https://github.com/IBM-Swift/Configuration)
+- [SwiftKueryPostgreSQL 1.x](https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL)
+- [SwiftKueryORM 0.x](https://github.com/IBM-Swift/Swift-Kuery-ORM)
+- [KituraStencil 1.x](https://github.com/IBM-Swift/Kitura-StencilTemplateEngine)
+- [KituraMustache 1.x](https://github.com/IBM-Swift/Kitura-MustacheTemplateEngine)
+- [MongoKitten 4.x](https://github.com/OpenKitten/MongoKitten)
 
 ## Test URLs
 ### JSON serialization

+ 42 - 0
frameworks/Swift/kitura/Sources/KueryPostgres/Postgres.swift

@@ -0,0 +1,42 @@
+/*
+ * 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 SwiftKuery
+import SwiftKueryPostgreSQL
+import Configuration
+
+#if os(Linux)
+    import Glibc
+#else
+    import Darwin
+#endif
+
+// We will load our database configuration from config.json, but this can be
+// overridden with the TFB_DB_CONFIG environment variable.
+let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
+let manager = ConfigurationManager().load(file: configurationFilename, relativeFrom: .pwd).load(.environmentVariables)
+
+let dbHost = manager["DB_HOST"] as? String ?? manager["db:host"] as? String ?? "localhost"
+let dbPort = Int32(manager["DB_PORT"] as? String != nil ? Int(manager["DB_PORT"] as! String) ?? 5432 : manager["db:port"] as? Int ?? 5432)
+let dbName = manager["db:name"] as? String ?? "hello_world"
+let dbUser = manager["db:user"] as? String ?? "benchmarkdbuser"
+let dbPass = manager["db:password"] as? String ?? "benchmarkdbpass"
+
+let dbConnPoolOpts = ConnectionPoolOptions(initialCapacity: 20, maxCapacity: 50, timeout:10000)
+
+public let dbConnPool = PostgreSQLConnection.createPool(host: dbHost, port: dbPort, options: [.databaseName(dbName), .userName(dbUser), .password(dbPass)], poolOptions: dbConnPoolOpts)
+

+ 223 - 0
frameworks/Swift/kitura/Sources/KueryPostgresORM/PostgresORM.swift

@@ -0,0 +1,223 @@
+/*
+ * 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 Dispatch
+import LoggerAPI
+import SwiftKuery
+import SwiftKueryORM
+import KueryPostgres
+import TechEmpowerCommon
+
+// ORM conformance
+extension RandomRow: Model {
+    public static var tableName: String { return "world" }
+}
+
+// ORM conformance
+extension Fortune: Model {
+    public static var tableName: String { return "fortune" }
+}
+
+// Configure our ORM Database connection pool as dbConnPool created by KueryPostgres
+public func setupORM() {
+    Database.default = Database(dbConnPool)
+}
+
+/// 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 RequestError on
+///                       failure.
+///
+public func getFortunes(callback: @escaping ([Fortune]?, RequestError?) -> Void) -> Void {
+    Fortune.findAll { (fortunes, err) in
+        if let err = err {
+            return callback(nil, err)
+        } else {
+            callback(fortunes, nil)
+        }
+    }
+}
+
+/// 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 RequestError on
+///                       failure.
+///
+public func getRandomRow(callback: @escaping (RandomRow?, RequestError?) -> Void) -> Void {
+    // Select random row from database range
+    let rnd = RandomRow.randomId
+    RandomRow.find(id: rnd, callback)
+}
+
+/// 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 RequestError if the
+///                       update failed.
+///
+public func updateRow(id: Int, callback: @escaping (RequestError?) -> Void) -> Void {
+    // Generate a random number for this row
+    let row = RandomRow(id: id, randomNumber: RandomRow.randomValue)
+    row.update(id: id) { (resultRow, err) in
+        if let err = err {
+            return callback(err)
+        } else {
+            callback(nil)
+        }
+    }
+}
+
+/// Get `count` random rows from the database, and pass the resulting array
+/// to a completion handler (or a RequestError, 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(count: Int, result: [RandomRow] = [], completion: @escaping ([RandomRow]?, RequestError?) -> Void) {
+    if count > 0 {
+        // Select random row from database range
+        RandomRow.find(id: RandomRow.randomId) { (resultRow, err) in
+            if let resultRow = resultRow {
+                var result = result
+                result.append(resultRow)
+                getRandomRows(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)
+    }
+}
+
+/// A parallel version of `getRandomRows` that invokes each get in parallel, builds an
+/// array of results and waits for each get to complete before returning.
+///
+/// - Parameter count: The number of rows to retrieve
+/// - Parameter completion: The closure to invoke with the result array, or error
+///
+public func getRandomRowsParallel(count: Int, completion: @escaping ([RandomRow]?, RequestError?) -> Void) {
+    var results: [RandomRow] = []
+    guard count > 0 else {
+        return completion(results, nil)
+    }
+    // Used to protect result array from concurrent modification
+    let updateLock = DispatchSemaphore(value: 1)
+    // Execute each query. Each callback will append its result to `results`
+    for _ in 1...count {
+        RandomRow.find(id: RandomRow.randomId) { (resultRow, err) in
+            guard let resultRow = resultRow else {
+                Log.error("\(err ?? .internalServerError)")
+                completion(nil, err ?? .internalServerError)
+                return
+            }
+            updateLock.wait()
+            results.append(resultRow)
+            if results.count == count {
+                completion(results, nil)
+            }
+            updateLock.signal()
+        }
+    }
+}
+
+/// Update and retrieve `count` random rows from the database, and pass the
+/// resulting array to a completion handler (or a RequestError, 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(count: Int, result: [RandomRow] = [], completion: @escaping ([RandomRow]?, RequestError?) -> Void) {
+    if count > 0 {
+        // Select random row from database range
+        RandomRow.find(id: RandomRow.randomId) { (resultRow, err) in
+            if let resultRow = resultRow {
+                var result = result
+                let row = RandomRow(id: resultRow.id, randomNumber: RandomRow.randomValue)
+                row.update(id: row.id) { (resultRow, err) in
+                    if let resultRow = resultRow {
+                        result.append(resultRow)
+                        updateRandomRows(count: count-1, result: result, completion: completion)
+                    } else {
+                        completion(nil, err)
+                    }
+                }
+            } else {
+                if let err = err {
+                    completion(nil, err)
+                } else {
+                    fatalError("Unexpected: result and error both nil")
+                }
+            }
+        }
+    } else {
+        completion(result, nil)
+    }
+}
+
+/// A parallel version of `updateRandomRows` that invokes each get/update operation
+/// in parallel, builds an array of results and waits for each get to complete before
+/// returning.
+///
+/// - Parameter count: The number of rows to retrieve
+/// - Parameter completion: The closure to invoke with the result array, or error
+///
+public func updateRandomRowsParallel(count: Int, completion: @escaping ([RandomRow]?, RequestError?) -> Void) {
+    var results: [RandomRow] = []
+    guard count > 0 else {
+        return completion(results, nil)
+    }
+    // Used to protect result array from concurrent modification
+    let updateLock = DispatchSemaphore(value: 1)
+    // Execute each query. Each callback will append its result to `results`
+    for _ in 1...count {
+        RandomRow.find(id: RandomRow.randomId) { (resultRow, err) in
+            guard let resultRow = resultRow else {
+                Log.error("\(err ?? .internalServerError)")
+                completion(nil, err ?? .internalServerError)
+                return
+            }
+            let row = RandomRow(id: resultRow.id, randomNumber: RandomRow.randomValue)
+            row.update(id: row.id) { (resultRow, err) in
+                if let resultRow = resultRow {
+                    updateLock.wait()
+                    results.append(resultRow)
+                    if results.count == count {
+                        completion(results, nil)
+                    }
+                    updateLock.signal()
+                } else {
+                    Log.error("\(err ?? .internalServerError)")
+                    completion(nil, err ?? .internalServerError)
+                    return
+                }
+            }
+        }
+    }
+}

+ 207 - 0
frameworks/Swift/kitura/Sources/KueryPostgresRaw/PostgresRaw.swift

@@ -0,0 +1,207 @@
+/*
+ * 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
+    guard let dbConn = dbConnPool.getConnection() else {
+        return callback(nil, AppError.OtherError("Timed out waiting for a DB connection from the pool"))
+    }
+    // Initiate database query
+    let query = Select(from: fortunes)
+    dbConn.execute(query: query) { result in
+        var resultFortunes: [Fortune]? = nil
+        guard let rows = result.asRows, result.success else {
+            return callback(nil, AppError.DBKueryError("Query failed: \(String(describing: result.asError))"))
+        }
+        do {
+            resultFortunes = try rows.map { try Fortune.init(row: $0) }
+        } catch {
+            return callback(nil, AppError.DataFormatError("\(error)"))
+        }
+        // Invoke callback with results
+        callback(resultFortunes, nil)
+    }
+
+}
+
+/// 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
+    guard let dbConn = dbConnPool.getConnection() else {
+        return callback(nil, AppError.OtherError("Timed out waiting for a DB connection from the pool"))
+    }
+    // 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
+        var resultRow: RandomRow? = nil
+        guard let resultSet = result.asResultSet, result.success else {
+            return callback(nil, AppError.DBKueryError("Query failed: \(String(describing: result.asError))"))
+        }
+            
+        for row in resultSet.rows {
+            for value in row {
+                guard let value = value else {
+                    return callback(nil, AppError.DBKueryError("Error: randomNumber value is nil"))
+                }
+                guard let randomNumber = value as? Int32 else {
+                    return callback(nil, AppError.DBKueryError("Error: could not convert \(value) to Int32"))
+                }
+                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
+    guard let dbConn = dbConnPool.getConnection() else {
+        return callback(AppError.OtherError("Timed out waiting for a DB connection from the pool"))
+    }
+    // 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)
+    }
+}

+ 44 - 0
frameworks/Swift/kitura/Sources/TechEmpower/main.swift

@@ -0,0 +1,44 @@
+/*
+ * 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 Kitura
+import TechEmpowerCommon
+
+let router = Router()
+
+//
+// TechEmpower test 6: plaintext
+//
+router.get("/plaintext") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/plain"
+    try response.status(.OK).send("Hello, world!").end()
+}
+
+//
+// TechEmpower test 1: JSON serialization
+//
+router.get("/json") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let result = ["message":"Hello, World!"]
+    try response.status(.OK).send(json: result).end()
+}
+
+Kitura.addHTTPServer(onPort: 8080, with: router)
+Kitura.run()

+ 13 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/error.swift → frameworks/Swift/kitura/Sources/TechEmpowerCommon/Error.swift

@@ -14,9 +14,22 @@
  * limitations under the License.
  */
 
+/// Represents various errors that can occur with the Kitura TechEmpower benchmark.
 public enum AppError: Error {
+
+    /// An error occurring when executing a raw SQL query against a database.
     case DBError(String, query: String)
+
+    /// An error occurring when executing a Kuery operation against a database.
     case DBKueryError(String)
+
+    /// An error occurring when the format of the data retrieved by a database
+    /// operation was not as expected.
     case DataFormatError(String)
+
+    /// An error occurring when a connection to the database cannot be established.
+    case ConnectionError(String)
+
+    /// Any other type of error
     case OtherError(String)
 }

+ 67 - 0
frameworks/Swift/kitura/Sources/TechEmpowerCommon/Fortune.swift

@@ -0,0 +1,67 @@
+/*
+ * 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
+
+public struct Fortune: Codable {
+
+    /// The id of this Fortune
+    public let id: Int
+
+    /// The message contained within this Fortune
+    public let message: String
+
+    public init(id: Int, message: String) {
+        self.id = id
+        self.message = message
+    }
+
+    /// Create a Fortune instance from a [String: Any?] dictionary,
+    /// such as that retrieved by Kuery.
+    ///
+    /// - Parameter row: A dictionary representing the fields of a
+    ///                  Fortune database row.
+    /// - throws: if the fields and types contained in the dictionary
+    ///           do not match those expected.
+    public init(row: [String:Any?]) throws {
+        guard let idField = row["id"] else {
+            throw AppError.DataFormatError("Missing 'id' field")
+        }
+        guard let msgField = row["message"] else {
+            throw AppError.DataFormatError("Missing 'message' field")
+        }
+        guard let message = msgField as? String else {
+            throw AppError.DataFormatError("'message' field not a String")
+        }
+        guard let id = idField as? Int32 else {
+            throw AppError.DataFormatError("'id' field not an Int32")
+        }
+        self.init(id: Int(id), message: message)
+    }
+
+}
+
+extension Fortune: Comparable {
+
+    public static func == (lhs: Fortune, rhs: Fortune) -> Bool {
+        return lhs.id == rhs.id && lhs.message == rhs.message
+    }
+
+    public static func < (lhs: Fortune, rhs: Fortune) -> Bool {
+        return lhs.message < rhs.message || (lhs.message == rhs.message && lhs.id < rhs.id)
+    }
+
+}

+ 70 - 0
frameworks/Swift/kitura/Sources/TechEmpowerCommon/RandomRow.swift

@@ -0,0 +1,70 @@
+/*
+ * 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
+
+// Return a random number within the range of rows in the database
+private func randomNumberGenerator(_ maxVal: Int) -> Int {
+    #if os(Linux)
+    return Int(random() % maxVal) + 1
+    #else
+    return Int(arc4random_uniform(UInt32(maxVal))) + 1
+    #endif
+}
+
+public struct RandomRow: Codable {
+
+    /// The number of rows in the World table
+    public static let dbRows = 10000
+
+    /// The maximum value for randomNumber
+    public static let maxValue = 10000
+
+    /// A generated random row id suitable for retrieving
+    /// or creating a RandomRow instance.
+    public static var randomId: Int {
+        return randomNumberGenerator(dbRows)
+    }
+
+    /// A generated random value suitable for assigning as the
+    /// `randomNumber` for a RandomRow instance.
+    public static var randomValue: Int {
+        return randomNumberGenerator(maxValue)
+    }
+
+    /// The id for this RandomRow, ranging from 1 to dbRows
+    public let id: Int
+
+    /// A random number ranging from 1 to maxValue
+    public let randomNumber: Int
+
+    public init(id: Int, randomNumber: Int) {
+        self.id = id
+        self.randomNumber = randomNumber
+    }
+
+    /// Map the properties of this type to their corresponding database
+    /// column names (required by the ORM).
+    enum CodingKeys: String, CodingKey {
+        case id
+        case randomNumber = "randomnumber"
+    }
+
+    /// Returns a JSON-convertible dictionary representation of this RandomRow.
+    public func asDictionary() -> [String: Int] {
+        return ["id": self.id, "randomNumber": self.randomNumber]
+    }
+}

+ 0 - 55
frameworks/Swift/kitura/Sources/TechEmpowerKuery/Fortune.swift

@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-struct Fortune {
-
-  let id: Int
-  let message: String
-
-  public init(id: Int, message: String) {
-    self.id = id
-    self.message = message
-  }
-
-  init(row: [String:Any?]) throws {
-    guard let idField = row["id"] else {
-      throw AppError.DataFormatError("Missing 'id' field")
-    }
-    guard let msgField = row["message"] else {
-      throw AppError.DataFormatError("Missing 'message' field")
-    }
-    guard let message = msgField as? String else {
-      throw AppError.DataFormatError("'message' field not a String")
-    }
-    guard let id = idField as? Int32 else {
-      throw AppError.DataFormatError("'id' field not an Int32")
-    }
-    self.init(id: Int(id), message: message)
-  }
-
-}
-
-extension Fortune: Comparable {
-
-  static func == (lhs: Fortune, rhs: Fortune) -> Bool {
-    return lhs.id == rhs.id && lhs.message == rhs.message
-  }
-
-  static func < (lhs: Fortune, rhs: Fortune) -> Bool {
-    return lhs.message < rhs.message || (lhs.message == rhs.message && lhs.id < rhs.id)
-  }
-
-}

+ 0 - 203
frameworks/Swift/kitura/Sources/TechEmpowerKuery/Postgres.swift

@@ -1,203 +0,0 @@
-/*
- * 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 SwiftKueryPostgreSQL
-import Configuration
-
-#if os(Linux)
-    import Glibc
-#else
-    import Darwin
-#endif
-
-// We will load our database configuration from config.json, but this can be
-// overridden with the TFB_DB_CONFIG environment variable.
-let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
-let manager = ConfigurationManager().load(file: configurationFilename, relativeFrom: .pwd).load(.environmentVariables)
-
-let dbHost = manager["DB_HOST"] as? String ?? manager["db:host"] as? String ?? "localhost"
-let dbPort = Int32(manager["DB_PORT"] as? String != nil ? Int(manager["DB_PORT"] as! String) ?? 5432 : manager["db:port"] as? Int ?? 5432)
-let dbName = manager["db:name"] as? String ?? "hello_world"
-let dbUser = manager["db:user"] as? String ?? "benchmarkdbuser"
-let dbPass = manager["db:password"] as? String ?? "benchmarkdbpass"
-
-let dbRows = 10000
-let maxValue = 10000
-
-class World: Table {
-    let tableName = "World"
-    
-    let id = Column("id")
-    let randomNumber = Column("randomNumber")
-}
-
-class Fortunes: Table {
-    let tableName = "Fortune"
-
-    let id = Column("id")
-    let message = Column("message")
-}
-
-let world = World()
-let fortunes = Fortunes()
-
-var update = Update(world, set: [(world.randomNumber, randomNumberGenerator(maxValue))])
-    .where(world.id == randomNumberGenerator(dbRows))
-
-
-let dbConnPoolOpts = ConnectionPoolOptions(initialCapacity: 20, maxCapacity: 50, timeout:10000)
-
-func releaseConnection(connection: Connection) {
-    connection.closeConnection()
-}
-
-func generateConnection() -> Connection? {
-    var dbConn: Connection
-    dbConn = PostgreSQLConnection(host: dbHost, port: dbPort,
-                                  options: [.databaseName(dbName),
-                                            .userName(dbUser), .password(dbPass) ])
-
-    
-    dbConn.connect() { error in
-        if let error = error {
-            print(error)
-            return
-        }
-    }
-    return dbConn
-}
-
-let dbConnPool = ConnectionPool(options: dbConnPoolOpts, connectionGenerator: generateConnection, connectionReleaser:releaseConnection)
-
-// Return a random number within the range of rows in the database
-func randomNumberGenerator(_ maxVal: Int) -> Int {
-    #if os(Linux)
-        return Int(random() % maxVal) + 1
-    #else
-        return Int(arc4random_uniform(UInt32(maxVal))) + 1
-    #endif
-}
-
-func getFortunes() -> ([Fortune]?, AppError?) {
-    var resultFortunes: [Fortune]? = nil
-    var errRes: AppError? = nil
-    
-    // Get a dedicated connection object for this transaction from the pool
-    guard let dbConn = dbConnPool.getConnection() else {
-        errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
-        return (nil, errRes)
-    }
-    // Ensure that when we complete, the connection is returned to the pool
-    defer {
-        releaseConnection(connection: dbConn)
-    }
-    
-    let query = Select(from: fortunes)
-
-    dbConn.execute(query: query) { result in
-        guard let rows = result.asRows, result.success else {
-            errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
-            return
-        }
-        do {
-            resultFortunes = try rows.map { try Fortune.init(row: $0) }
-        } catch {
-            errRes = AppError.DataFormatError("\(error)")
-        }
-    }
-
-    return (resultFortunes, errRes)
-}
-
-// Get a random row (range 1 to 10,000) from DB: id(int),randomNumber(int)
-// Convert to object using object-relational mapping (ORM) tool
-// Serialize object to JSON - example: {"id":3217,"randomNumber":2149}
-func getRandomRow() -> ([String:Int]?, AppError?) {
-    var resultDict: [String:Int]? = nil
-    var errRes: AppError? = nil
-    // Get a dedicated connection object for this transaction from the pool
-    guard let dbConn = dbConnPool.getConnection() else {
-        errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
-        return (resultDict, errRes)
-    }
-    // Ensure that when we complete, the connection is returned to the pool
-    defer {
-        releaseConnection(connection: dbConn)
-    }
-    let rnd = randomNumberGenerator(dbRows)
-    
-    let query = Select(world.randomNumber, from: world)
-        .where(world.id == rnd)
-    
-    dbConn.execute(query: query) { result in
-        if let resultSet = result.asResultSet {
-            guard result.success else {
-                errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
-                return
-            }
-            
-            for row in resultSet.rows {
-                for value in row {
-                    if let unwrapped = value {
-                        guard let randomNumber = unwrapped as? Int32 else {
-                        errRes = AppError.DBKueryError("Error: could not get field as an Int")
-                        return
-                    }
-                        resultDict = ["id":rnd, "randomNumber":Int(randomNumber)]
-                    } else {
-                        errRes = AppError.DBKueryError("Error: randomNumber value is nil")
-                    }
-
-                }
-                
-            }
-        }
-    }
-    return (resultDict, errRes)
-}
-
-// Updates a row of World to a new value.
-func updateRow(id: Int) throws  -> AppError? {
-    // Get a dedicated connection object for this transaction from the pool
-    guard let dbConn = dbConnPool.getConnection() else {
-        throw AppError.OtherError("Timed out waiting for a DB connection from the pool")
-    }
-    // Ensure that when we complete, the connection is returned to the pool
-    defer {
-        releaseConnection(connection: dbConn)
-    }
-    let rndValue = randomNumberGenerator(maxValue)
-    let query = Update(world, set: [(world.randomNumber, rndValue)])
-        .where(world.id == id)
-    var errRes: AppError? = nil
-    dbConn.execute(query: query) { result in
-        if result.asResultSet != nil {
-            guard result.success else {
-                errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
-                return
-            }
-            
-        }
-        
-    }
-    return errRes
-}
-
-
-

+ 0 - 22
frameworks/Swift/kitura/Sources/TechEmpowerKuery/error.swift

@@ -1,22 +0,0 @@
-/*
- * 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.
- */
-
-public enum AppError: Error {
-    case DBError(String, query: String)
-    case DBKueryError(String)
-    case DataFormatError(String)
-    case OtherError(String)
-}

+ 0 - 182
frameworks/Swift/kitura/Sources/TechEmpowerKuery/main.swift

@@ -1,182 +0,0 @@
-/*
- * 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 Kitura
-import LoggerAPI
-import HeliumLogger
-import SwiftKuery
-import SwiftKueryPostgreSQL
-import KituraStencil
-import Stencil
-
-Log.logger = HeliumLogger(.info)
-
-// Stencil stuff
-let ext = Extension()
-
-// Stencil does not yet support automatic HTML escaping:
-// https://github.com/kylef/Stencil/pull/80
-//
-ext.registerFilter("htmlencode") { (value: Any?) in
-    if let value = value as? String {
-        return value
-            .replacingOccurrences(of: "&", with: "&amp;")
-            .replacingOccurrences(of: "<", with: "&lt;")
-            .replacingOccurrences(of: ">", with: "&gt;")
-            .replacingOccurrences(of: "'", with: "&apos;")
-            .replacingOccurrences(of: "\"", with: "&quot;")
-    }
-    return value
-}
-
-let router = Router()
-router.add(templateEngine: StencilTemplateEngine(extension: ext))
-
-//
-// TechEmpower test 6: plaintext
-//
-router.get("/plaintext") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    response.headers["Content-Type"] = "text/plain"
-    try response.status(.OK).send("Hello, world!").end()
-}
-
-//
-// TechEmpower test 1: JSON serialization
-//
-router.get("/json") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let result = ["message":"Hello, World!"]
-    try response.status(.OK).send(json: result).end()
-}
-
-//
-// TechEmpower test 2: Single database query (raw, no ORM)
-//
-router.get("/db") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let result = getRandomRow()
-    guard let dict = result.0 else {
-        guard let err = result.1 else {
-            Log.error("Unknown Error")
-            try response.status(.badRequest).send("Unknown error").end()
-            return
-        }
-        Log.error("\(err)")
-        try response.status(.badRequest).send("Error: \(err)").end()
-        return
-    }
-    try response.status(.OK).send(json: dict).end()
-}
-
-//
-// TechEmpower test 3: Multiple database queries (raw, no ORM)
-// Get param provides number of queries: /queries?queries=N
-//
-router.get("/queries") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let queriesParam = request.queryParameters["queries"] ?? "1"
-    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
-    var results: [[String:Int]] = []
-    for _ in 1...numQueries {
-        let result = getRandomRow()
-        guard let dict = result.0 else {
-            guard let err = result.1 else {
-                Log.error("Unknown Error")
-                try response.status(.badRequest).send("Unknown error").end()
-                return
-            }
-            Log.error("\(err)")
-            try response.status(.badRequest).send("Error: \(err)").end()
-            return
-        }
-        results.append(dict)
-    }
-    // Return JSON representation of array of results
-    try response.status(.OK).send(json: results).end()
-}
-
-//
-// TechEmpower test 4: fortunes (raw, no ORM)
-//
-router.get("/fortunes") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    response.headers["Content-Type"] = "text/html; charset=UTF-8"
-    let result = getFortunes()
-    guard var fortunes = result.0 else {
-        guard let err = result.1 else {
-            Log.error("Unknown Error")
-            try response.status(.badRequest).send("Unknown error").end()
-            return
-        }
-        Log.error("\(err)")
-        try response.status(.badRequest).send("Error: \(err)").end()
-        return
-    }
-    fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
-    do {
-      try response.render("fortunes.stencil", context: ["fortunes": fortunes.sorted()]).end()
-    } catch {
-      print("Error: \(error)")
-    }
-}
-
-//
-// TechEmpower test 5: updates (raw, no ORM)
-//
-router.get("/updates") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let queriesParam = request.queryParameters["queries"] ?? "1"
-    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
-    var results: [[String:Int]] = []
-    for _ in 1...numQueries {
-        let result = getRandomRow()
-        guard let dict = result.0 else {
-            guard let err = result.1 else {
-                Log.error("Unknown Error")
-                try response.status(.badRequest).send("Unknown error").end()
-                return
-            }
-            Log.error("\(err)")
-            try response.status(.badRequest).send("Error: \(err)").end()
-            return
-        }
-        do {
-            var error: AppError?
-            try error = updateRow(id: dict["id"]!)
-            if let appError = error {
-                throw appError
-            }
-        } catch let err as AppError {
-            try response.status(.badRequest).send("Error: \(err)").end()
-            return
-        }
-        results.append(dict)
-    }
-    
-    // Return JSON representation of array of results
-    try response.status(.OK).send(json: results).end()
-}
-
-
-Kitura.addHTTPServer(onPort: 8080, with: router)
-Kitura.run()

+ 0 - 55
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Fortune.swift

@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-struct Fortune {
-
-  let id: Int
-  let message: String
-
-  public init(id: Int, message: String) {
-    self.id = id
-    self.message = message
-  }
-
-  init(row: [String:Any?]) throws {
-    guard let idField = row["id"] else {
-      throw AppError.DataFormatError("Missing 'id' field")
-    }
-    guard let msgField = row["message"] else {
-      throw AppError.DataFormatError("Missing 'message' field")
-    }
-    guard let message = msgField as? String else {
-      throw AppError.DataFormatError("'message' field not a String")
-    }
-    guard let id = idField as? Int32 else {
-      throw AppError.DataFormatError("'id' field not an Int32")
-    }
-    self.init(id: Int(id), message: message)
-  }
-
-}
-
-extension Fortune: Comparable {
-
-  static func == (lhs: Fortune, rhs: Fortune) -> Bool {
-    return lhs.id == rhs.id && lhs.message == rhs.message
-  }
-
-  static func < (lhs: Fortune, rhs: Fortune) -> Bool {
-    return lhs.message < rhs.message || (lhs.message == rhs.message && lhs.id < rhs.id)
-  }
-
-}

+ 0 - 203
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Postgres.swift

@@ -1,203 +0,0 @@
-/*
- * 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 SwiftKueryPostgreSQL
-import Configuration
-
-#if os(Linux)
-    import Glibc
-#else
-    import Darwin
-#endif
-
-// We will load our database configuration from config.json, but this can be
-// overridden with the TFB_DB_CONFIG environment variable.
-let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
-let manager = ConfigurationManager().load(file: configurationFilename, relativeFrom: .pwd).load(.environmentVariables)
-
-let dbHost = manager["DB_HOST"] as? String ?? manager["db:host"] as? String ?? "localhost"
-let dbPort = Int32(manager["DB_PORT"] as? String != nil ? Int(manager["DB_PORT"] as! String) ?? 5432 : manager["db:port"] as? Int ?? 5432)
-let dbName = manager["db:name"] as? String ?? "hello_world"
-let dbUser = manager["db:user"] as? String ?? "benchmarkdbuser"
-let dbPass = manager["db:password"] as? String ?? "benchmarkdbpass"
-
-let dbRows = 10000
-let maxValue = 10000
-
-class World: Table {
-    let tableName = "World"
-    
-    let id = Column("id")
-    let randomNumber = Column("randomNumber")
-}
-
-class Fortunes: Table {
-    let tableName = "Fortune"
-
-    let id = Column("id")
-    let message = Column("message")
-}
-
-let world = World()
-let fortunes = Fortunes()
-
-var update = Update(world, set: [(world.randomNumber, randomNumberGenerator(maxValue))])
-    .where(world.id == randomNumberGenerator(dbRows))
-
-
-let dbConnPoolOpts = ConnectionPoolOptions(initialCapacity: 20, maxCapacity: 50, timeout:10000)
-
-func releaseConnection(connection: Connection) {
-    connection.closeConnection()
-}
-
-func generateConnection() -> Connection? {
-    var dbConn: Connection
-    dbConn = PostgreSQLConnection(host: dbHost, port: dbPort,
-                                  options: [.databaseName(dbName),
-                                            .userName(dbUser), .password(dbPass) ])
-
-    
-    dbConn.connect() { error in
-        if let error = error {
-            print(error)
-            return
-        }
-    }
-    return dbConn
-}
-
-let dbConnPool = ConnectionPool(options: dbConnPoolOpts, connectionGenerator: generateConnection, connectionReleaser:releaseConnection)
-
-// Return a random number within the range of rows in the database
-func randomNumberGenerator(_ maxVal: Int) -> Int {
-    #if os(Linux)
-        return Int(random() % maxVal) + 1
-    #else
-        return Int(arc4random_uniform(UInt32(maxVal))) + 1
-    #endif
-}
-
-func getFortunes() -> ([Fortune]?, AppError?) {
-    var resultFortunes: [Fortune]? = nil
-    var errRes: AppError? = nil
-    
-    // Get a dedicated connection object for this transaction from the pool
-    guard let dbConn = dbConnPool.getConnection() else {
-        errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
-        return (nil, errRes)
-    }
-    // Ensure that when we complete, the connection is returned to the pool
-    defer {
-        releaseConnection(connection: dbConn)
-    }
-    
-    let query = Select(from: fortunes)
-
-    dbConn.execute(query: query) { result in
-        guard let rows = result.asRows, result.success else {
-            errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
-            return
-        }
-        do {
-            resultFortunes = try rows.map { try Fortune.init(row: $0) }
-        } catch {
-            errRes = AppError.DataFormatError("\(error)")
-        }
-    }
-
-    return (resultFortunes, errRes)
-}
-
-// Get a random row (range 1 to 10,000) from DB: id(int),randomNumber(int)
-// Convert to object using object-relational mapping (ORM) tool
-// Serialize object to JSON - example: {"id":3217,"randomNumber":2149}
-func getRandomRow() -> ([String:Int]?, AppError?) {
-    var resultDict: [String:Int]? = nil
-    var errRes: AppError? = nil
-    // Get a dedicated connection object for this transaction from the pool
-    guard let dbConn = dbConnPool.getConnection() else {
-        errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
-        return (resultDict, errRes)
-    }
-    // Ensure that when we complete, the connection is returned to the pool
-    defer {
-        releaseConnection(connection: dbConn)
-    }
-    let rnd = randomNumberGenerator(dbRows)
-    
-    let query = Select(world.randomNumber, from: world)
-        .where(world.id == rnd)
-    
-    dbConn.execute(query: query) { result in
-        if let resultSet = result.asResultSet {
-            guard result.success else {
-                errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
-                return
-            }
-            
-            for row in resultSet.rows {
-                for value in row {
-                    if let unwrapped = value {
-                        guard let randomNumber = unwrapped as? Int32 else {
-                        errRes = AppError.DBKueryError("Error: could not get field as an Int")
-                        return
-                    }
-                        resultDict = ["id":rnd, "randomNumber":Int(randomNumber)]
-                    } else {
-                        errRes = AppError.DBKueryError("Error: randomNumber value is nil")
-                    }
-
-                }
-                
-            }
-        }
-    }
-    return (resultDict, errRes)
-}
-
-// Updates a row of World to a new value.
-func updateRow(id: Int) throws  -> AppError? {
-    // Get a dedicated connection object for this transaction from the pool
-    guard let dbConn = dbConnPool.getConnection() else {
-        throw AppError.OtherError("Timed out waiting for a DB connection from the pool")
-    }
-    // Ensure that when we complete, the connection is returned to the pool
-    defer {
-        releaseConnection(connection: dbConn)
-    }
-    let rndValue = randomNumberGenerator(maxValue)
-    let query = Update(world, set: [(world.randomNumber, rndValue)])
-        .where(world.id == id)
-    var errRes: AppError? = nil
-    dbConn.execute(query: query) { result in
-        if result.asResultSet != nil {
-            guard result.success else {
-                errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
-                return
-            }
-            
-        }
-        
-    }
-    return errRes
-}
-
-
-

+ 0 - 163
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/main.swift

@@ -1,163 +0,0 @@
-/*
- * 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 Kitura
-import LoggerAPI
-import HeliumLogger
-import SwiftKuery
-import SwiftKueryPostgreSQL
-import KituraMustache
-
-Log.logger = HeliumLogger(.info)
-
-let router = Router()
-router.add(templateEngine: MustacheTemplateEngine())
-
-//
-// TechEmpower test 6: plaintext
-//
-router.get("/plaintext") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    response.headers["Content-Type"] = "text/plain"
-    try response.status(.OK).send("Hello, world!").end()
-}
-
-//
-// TechEmpower test 1: JSON serialization
-//
-router.get("/json") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let result = ["message":"Hello, World!"]
-    try response.status(.OK).send(json: result).end()
-}
-
-//
-// TechEmpower test 2: Single database query (raw, no ORM)
-//
-router.get("/db") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let result = getRandomRow()
-    guard let dict = result.0 else {
-        guard let err = result.1 else {
-            Log.error("Unknown Error")
-            try response.status(.badRequest).send("Unknown error").end()
-            return
-        }
-        Log.error("\(err)")
-        try response.status(.badRequest).send("Error: \(err)").end()
-        return
-    }
-    try response.status(.OK).send(json: dict).end()
-}
-
-//
-// TechEmpower test 3: Multiple database queries (raw, no ORM)
-// Get param provides number of queries: /queries?queries=N
-//
-router.get("/queries") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let queriesParam = request.queryParameters["queries"] ?? "1"
-    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
-    var results: [[String:Int]] = []
-    for _ in 1...numQueries {
-        let result = getRandomRow()
-        guard let dict = result.0 else {
-            guard let err = result.1 else {
-                Log.error("Unknown Error")
-                try response.status(.badRequest).send("Unknown error").end()
-                return
-            }
-            Log.error("\(err)")
-            try response.status(.badRequest).send("Error: \(err)").end()
-            return
-        }
-        results.append(dict)
-    }
-    // Return JSON representation of array of results
-    try response.status(.OK).send(json: results).end()
-}
-
-//
-// TechEmpower test 4: fortunes (raw, no ORM)
-//
-router.get("/fortunes") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    response.headers["Content-Type"] = "text/html; charset=UTF-8"
-    let result = getFortunes()
-    guard var fortunes = result.0 else {
-        guard let err = result.1 else {
-            Log.error("Unknown Error")
-            try response.status(.badRequest).send("Unknown error").end()
-            return
-        }
-        Log.error("\(err)")
-        try response.status(.badRequest).send("Error: \(err)").end()
-        return
-    }
-    fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
-    do {
-      try response.render("fortunes.mustache", context: ["fortunes": fortunes.sorted()]).end()
-    } catch {
-      print("Error: \(error)")
-    }
-}
-
-//
-// TechEmpower test 5: updates (raw, no ORM)
-//
-router.get("/updates") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let queriesParam = request.queryParameters["queries"] ?? "1"
-    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
-    var results: [[String:Int]] = []
-    for _ in 1...numQueries {
-        let result = getRandomRow()
-        guard let dict = result.0 else {
-            guard let err = result.1 else {
-                Log.error("Unknown Error")
-                try response.status(.badRequest).send("Unknown error").end()
-                return
-            }
-            Log.error("\(err)")
-            try response.status(.badRequest).send("Error: \(err)").end()
-            return
-        }
-        do {
-            var error: AppError?
-            try error = updateRow(id: dict["id"]!)
-            if let appError = error {
-                throw appError
-            }
-        } catch let err as AppError {
-            try response.status(.badRequest).send("Error: \(err)").end()
-            return
-        }
-        results.append(dict)
-    }
-    
-    // Return JSON representation of array of results
-    try response.status(.OK).send(json: results).end()
-}
-
-
-Kitura.addHTTPServer(onPort: 8080, with: router)
-Kitura.run()

+ 0 - 55
frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/Fortune.swift

@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-struct Fortune {
-
-  let id: Int
-  let message: String
-
-  public init(id: Int, message: String) {
-    self.id = id
-    self.message = message
-  }
-
-  init(row: [String:Any?]) throws {
-    guard let idField = row["id"] else {
-      throw AppError.DataFormatError("Missing 'id' field")
-    }
-    guard let msgField = row["message"] else {
-      throw AppError.DataFormatError("Missing 'message' field")
-    }
-    guard let message = msgField as? String else {
-      throw AppError.DataFormatError("'message' field not a String")
-    }
-    guard let id = idField as? Int32 else {
-      throw AppError.DataFormatError("'id' field not an Int32")
-    }
-    self.init(id: Int(id), message: message)
-  }
-
-}
-
-extension Fortune: Comparable {
-
-  static func == (lhs: Fortune, rhs: Fortune) -> Bool {
-    return lhs.id == rhs.id && lhs.message == rhs.message
-  }
-
-  static func < (lhs: Fortune, rhs: Fortune) -> Bool {
-    return lhs.message < rhs.message || (lhs.message == rhs.message && lhs.id < rhs.id)
-  }
-
-}

+ 13 - 17
frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/Mongo.swift

@@ -18,6 +18,7 @@ import Foundation
 import LoggerAPI
 import Configuration
 import MongoKitten
+import TechEmpowerCommon
 
 #if os(Linux)
     import Glibc
@@ -25,6 +26,10 @@ import MongoKitten
     import Darwin
 #endif
 
+public enum MongoAppError: Error {
+    case MongoError(String)
+}
+
 // We will load our database configuration from config.json, but this can be
 // overridden with the TFB_DB_CONFIG environment variable.
 let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
@@ -49,24 +54,15 @@ func connectToDB() throws {
     //myDatabase = try MongoKitten.Database("mongodb://\(dbUser):\(dbPass)@\(dbHost):\(dbPort)/\(dbName)")
     myDatabase = try MongoKitten.Database(connectString)
     guard let myDatabase = myDatabase else {
-        throw AppError.MongoError("Nil MongoDB connection to \(connectString)")
+        throw AppError.ConnectionError("Nil MongoDB connection to \(connectString)")
     }
     guard myDatabase.server.isConnected else {
-        throw AppError.MongoError("Not connected to \(connectString)")
+        throw AppError.ConnectionError("Not connected to \(connectString)")
     }
     world = myDatabase["world"]
     fortune = myDatabase["fortune"]
 }
 
-// Return a random number within the range of rows in the database
-func randomNumberGenerator(_ maxVal: Int) -> Int {
-    #if os(Linux)
-        return Int(random() % maxVal) + 1
-    #else
-        return Int(arc4random_uniform(UInt32(maxVal))) + 1
-    #endif
-}
-
 // Allow construction of a Fortune from a MongoKitten Document
 extension Fortune {
     init(document: Document) throws {
@@ -80,7 +76,7 @@ extension Fortune {
 
 func getFortunes() throws -> [Fortune] {
     guard let fortune = fortune else {
-        throw AppError.MongoError("Fortune collection not initialized")
+        throw MongoAppError.MongoError("Fortune collection not initialized")
     }
     
 //    let allFortunes: [Document] = Array(try fortune.find())
@@ -94,10 +90,10 @@ func getFortunes() throws -> [Fortune] {
 // Serialize object to JSON - example: {"id":3217,"randomNumber":2149}
 func getRandomRow() throws -> [String:Int] {
     guard let world = world else {
-        throw AppError.MongoError("World collection not initialized")
+        throw MongoAppError.MongoError("World collection not initialized")
     }
 
-    let rnd = randomNumberGenerator(dbRows)
+    let rnd = RandomRow.randomId
     let result = try world.findOne("_id" == rnd)
     guard let document = result else {
         throw AppError.DataFormatError("World entry id=\(rnd) not found")
@@ -111,11 +107,11 @@ func getRandomRow() throws -> [String:Int] {
 // Updates a row of World to a new value.
 func updateRandomRow() throws -> [String:Int] {
     guard let world = world else {
-        throw AppError.MongoError("World collection not initialized")
+        throw MongoAppError.MongoError("World collection not initialized")
     }
     
-    let rnd = randomNumberGenerator(dbRows)
-    let rndValue = randomNumberGenerator(maxValue)
+    let rnd = RandomRow.randomId
+    let rndValue = RandomRow.randomValue
     let document = try world.findAndUpdate("_id" == rnd, with: ["randomNumber": rndValue])
     guard let id = Int(document["_id"]), let randomNumber = Int(document["randomNumber"]) else {
         throw AppError.DataFormatError("Expected fields of World document could not be retreived")

+ 0 - 22
frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/error.swift

@@ -1,22 +0,0 @@
-/*
- * 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.
- */
-
-public enum AppError: Error {
-    case DBError(String, query: String)
-    case MongoError(String)
-    case DataFormatError(String)
-    case OtherError(String)
-}

+ 1 - 20
frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/main.swift

@@ -21,6 +21,7 @@ import MongoKitten
 import KituraStencil
 import Stencil
 //import KituraMustache
+import TechEmpowerCommon
 
 Log.logger = HeliumLogger(.info)
 
@@ -46,26 +47,6 @@ let router = Router()
 router.add(templateEngine: StencilTemplateEngine(extension: ext))
 //router.add(templateEngine: MustacheTemplateEngine())
 
-//
-// TechEmpower test 6: plaintext
-//
-router.get("/plaintext") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    response.headers["Content-Type"] = "text/plain"
-    try response.status(.OK).send("Hello, world!").end()
-}
-
-//
-// TechEmpower test 1: JSON serialization
-//
-router.get("/json") {
-    request, response, next in
-    response.headers["Server"] = "Kitura"
-    let result = ["message":"Hello, World!"]
-    try response.status(.OK).send(json: result).end()
-}
-
 //
 // TechEmpower test 2: Single database query (raw, no ORM)
 //

+ 210 - 0
frameworks/Swift/kitura/Sources/TechEmpowerPostgres/main.swift

@@ -0,0 +1,210 @@
+/*
+ * 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 Kitura
+import LoggerAPI
+import HeliumLogger
+import KituraStencil
+import Stencil
+import TechEmpowerCommon
+import KueryPostgresRaw
+
+Log.logger = HeliumLogger(.info)
+
+// Stencil stuff
+let ext = Extension()
+
+// Stencil does not yet support automatic HTML escaping:
+// https://github.com/kylef/Stencil/pull/80
+//
+ext.registerFilter("htmlencode") { (value: Any?) in
+    if let value = value as? String {
+        return value
+            .replacingOccurrences(of: "&", with: "&amp;")
+            .replacingOccurrences(of: "<", with: "&lt;")
+            .replacingOccurrences(of: ">", with: "&gt;")
+            .replacingOccurrences(of: "'", with: "&apos;")
+            .replacingOccurrences(of: "\"", with: "&quot;")
+    }
+    return value
+}
+
+let router = Router()
+router.add(templateEngine: StencilTemplateEngine(extension: ext))
+
+//
+// TechEmpower test 2: Single database query (raw, no ORM)
+//
+router.get("/db") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    getRandomRow_Raw { (row, err) in
+        guard let row = row else {
+            guard let err = err else {
+                Log.error("Unknown Error")
+                try? response.status(.badRequest).send("Unknown error").end()
+                return
+            }
+            Log.error("\(err)")
+            try? response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        try? response.status(.OK).send(json: row.asDictionary()).end()
+    }
+}
+
+//
+// TechEmpower test 3: Multiple database queries (raw, no ORM)
+// Get param provides number of queries: /queries?queries=N
+//
+router.get("/queries") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    getRandomRows_Raw(count: numQueries) { (rows, err) in
+        if let rows = rows {
+            try? response.status(.OK).send(json: rows).end()
+        } else if let err = err {
+            try? response.status(.badRequest).send("Error: \(err)").end()
+        } else {
+            fatalError("Unexpected: rows and err both nil")
+        }
+    }
+}
+
+router.get("/queriesParallel") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    var results: [[String:Int]] = []
+    // Used to protect result array from concurrent modification
+    let updateLock = DispatchSemaphore(value: 1)
+    // Execute each query. Each callback will append its result to `results`
+    for _ in 1...numQueries {
+        getRandomRow_Raw { (row, err) in
+            guard let row = row else {
+                guard let err = err else {
+                    Log.error("Unknown Error")
+                    try? response.status(.badRequest).send("Unknown error").end()
+                    return
+                }
+                Log.error("\(err)")
+                try? response.status(.badRequest).send("Error: \(err)").end()
+                return
+            }
+            updateLock.wait()
+            results.append(row.asDictionary())
+            if results.count == numQueries {
+                // Return JSON representation of array of results
+                try? response.status(.OK).send(json: results).end()
+            }
+            updateLock.signal()
+        }
+    }
+}
+
+//
+// TechEmpower test 4: fortunes (raw, no ORM)
+//
+router.get("/fortunes") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/html; charset=UTF-8"
+    getFortunes { (fortunes, err) in
+        guard var fortunes = fortunes else {
+            guard let err = err else {
+                Log.error("Unknown Error")
+                try? response.status(.badRequest).send("Unknown error").end()
+                return
+            }
+            Log.error("\(err)")
+            try? response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
+        do {
+          try response.render("fortunes.stencil", context: ["fortunes": fortunes.sorted()]).end()
+        } catch {
+          print("Error: \(error)")
+        }
+    }
+}
+
+//
+// TechEmpower test 5: updates (raw, no ORM)
+//
+router.get("/updates") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    updateRandomRows_Raw(count: numQueries) { (rows, err) in
+        if let rows = rows {
+            try? response.status(.OK).send(json: rows).end()
+        } else if let err = err {
+            try? response.status(.badRequest).send("Error: \(err)").end()
+        } else {
+            fatalError("Unexpected: rows and err both nil")
+        }
+    }
+}
+
+
+router.get("/updatesParallel") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    var results: [[String:Int]] = []
+    // Used to protect result array from concurrent modification
+    let updateLock = DispatchSemaphore(value: 1)
+    // Execute each query. Each callback will append its result to `results`
+    for _ in 1...numQueries {
+        getRandomRow_Raw { (row, err) in
+            guard let row = row else {
+                guard let err = err else {
+                    Log.error("Unknown Error")
+                    try? response.status(.badRequest).send("Unknown error").end()
+                    return
+                }
+                Log.error("\(err)")
+                try? response.status(.badRequest).send("Error: \(err)").end()
+                return
+            }
+            // Execute inner callback for updating the row
+            updateRow_Raw(id: row.id) { (err) in
+                if let err = err {
+                    try? response.status(.badRequest).send("Error: \(err)").end()
+                    return
+                }
+                updateLock.wait()
+                results.append(row.asDictionary())
+                if results.count == numQueries {
+                    // Return JSON representation of array of results
+                    try? response.status(.OK).send(json: results).end()
+                }
+                updateLock.signal()
+            }
+        }
+    }
+}
+
+
+Kitura.addHTTPServer(onPort: 8080, with: router)
+Kitura.run()

+ 2 - 1
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Fortune+Mustache.swift → frameworks/Swift/kitura/Sources/TechEmpowerPostgresMustache/Fortune+Mustache.swift

@@ -15,9 +15,10 @@
  */
 
 import Mustache
+import TechEmpowerCommon
 
 extension Fortune: MustacheBoxable {
-  var mustacheBox: MustacheBox {
+  public var mustacheBox: MustacheBox {
     return Box(["id": self.id, "message": self.message])
   }
 }

+ 57 - 0
frameworks/Swift/kitura/Sources/TechEmpowerPostgresMustache/main.swift

@@ -0,0 +1,57 @@
+/*
+ * 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 Kitura
+import LoggerAPI
+import HeliumLogger
+import KituraMustache
+import TechEmpowerCommon
+import KueryPostgresRaw
+
+Log.logger = HeliumLogger(.info)
+
+let router = Router()
+router.add(templateEngine: MustacheTemplateEngine())
+
+//
+// TechEmpower test 4: fortunes (raw, no ORM)
+//
+router.get("/fortunes") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/html; charset=UTF-8"
+    getFortunes { (fortunes, err) in
+        guard var fortunes = fortunes else {
+            guard let err = err else {
+                Log.error("Unknown Error")
+                try? response.status(.badRequest).send("Unknown error").end()
+                return
+            }
+            Log.error("\(err)")
+            try? response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
+        do {
+          try response.render("fortunes.mustache", context: ["fortunes": fortunes.sorted()]).end()
+        } catch {
+          print("Error: \(error)")
+        }
+    }
+}
+
+Kitura.addHTTPServer(onPort: 8080, with: router)
+Kitura.run()

+ 166 - 0
frameworks/Swift/kitura/Sources/TechEmpowerPostgresORM/main.swift

@@ -0,0 +1,166 @@
+/*
+ * 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 Kitura
+import LoggerAPI
+import HeliumLogger
+import KituraStencil
+import Stencil
+import TechEmpowerCommon
+import KueryPostgresORM
+
+Log.logger = HeliumLogger(.info)
+
+// Stencil stuff
+let ext = Extension()
+
+// Stencil does not yet support automatic HTML escaping:
+// https://github.com/kylef/Stencil/pull/80
+//
+ext.registerFilter("htmlencode") { (value: Any?) in
+    if let value = value as? String {
+        return value
+            .replacingOccurrences(of: "&", with: "&amp;")
+            .replacingOccurrences(of: "<", with: "&lt;")
+            .replacingOccurrences(of: ">", with: "&gt;")
+            .replacingOccurrences(of: "'", with: "&apos;")
+            .replacingOccurrences(of: "\"", with: "&quot;")
+    }
+    return value
+}
+
+let router = Router()
+router.add(templateEngine: StencilTemplateEngine(extension: ext))
+
+setupORM()
+
+//
+// TechEmpower test 2: Single database query (full ORM)
+//
+router.get("/db") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    getRandomRow { (row, err) in
+        guard let row = row else {
+            guard let err = err else {
+                Log.error("Unknown Error")
+                try? response.status(.badRequest).send("Unknown error").end()
+                return
+            }
+            Log.error("\(err)")
+            try? response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        try? response.status(.OK).send(json: row.asDictionary()).end()
+    }
+}
+
+//
+// TechEmpower test 3: Multiple database queries (full ORM)
+// Get param provides number of queries: /queries?queries=N
+//
+router.get("/queries") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    getRandomRows(count: numQueries) { (rows, err) in
+        if let rows = rows {
+            try? response.status(.OK).send(json: rows).end()
+        } else if let err = err {
+            try? response.status(.badRequest).send("Error: \(err)").end()
+        } else {
+            fatalError("Unexpected: rows and err both nil")
+        }
+    }
+}
+
+router.get("/queriesParallel") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    getRandomRowsParallel(count: numQueries) { (rows, err) in
+        if let rows = rows {
+            try? response.status(.OK).send(json: rows).end()
+        } else if let err = err {
+            try? response.status(.badRequest).send("Error: \(err)").end()
+        } else {
+            fatalError("Unexpected: rows and err both nil")
+        }
+    }
+}
+
+//
+// TechEmpower test 4: fortunes (full ORM)
+//
+router.get("/fortunes") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/html; charset=UTF-8"
+    Fortune.findAll { (fortunes, err) in
+        if var fortunes = fortunes {
+            fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
+            do {
+                try response.render("fortunes.stencil", context: ["fortunes": fortunes.sorted()]).end()
+            } catch {
+                try? response.status(.internalServerError).send("Error: \(error)").end()
+            }
+        } else {
+            try? response.status(.internalServerError).send("Error: \(err ?? .internalServerError)").end()
+        }
+    }
+}
+
+//
+// TechEmpower test 5: updates (full ORM)
+//
+router.get("/updates") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    updateRandomRows(count: numQueries) { (rows, err) in
+        if let rows = rows {
+            try? response.status(.OK).send(json: rows).end()
+        } else if let err = err {
+            try? response.status(.badRequest).send("Error: \(err)").end()
+        } else {
+            fatalError("Unexpected: rows and err both nil")
+        }
+    }
+}
+
+router.get("/updatesParallel") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    updateRandomRowsParallel(count: numQueries) { (rows, err) in
+        if let rows = rows {
+            try? response.status(.OK).send(json: rows).end()
+        } else if let err = err {
+            try? response.status(.badRequest).send("Error: \(err)").end()
+        } else {
+            fatalError("Unexpected: rows and err both nil")
+        }
+    }
+}
+
+
+Kitura.addHTTPServer(onPort: 8080, with: router)
+Kitura.run()

+ 136 - 0
frameworks/Swift/kitura/Sources/TechEmpowerPostgresORMCodable/main.swift

@@ -0,0 +1,136 @@
+/*
+ * 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 Kitura
+import LoggerAPI
+import HeliumLogger
+import KituraStencil
+import Stencil
+import TechEmpowerCommon
+import KueryPostgres
+import SwiftKueryORM
+import KueryPostgresORM
+
+Log.logger = HeliumLogger(.info)
+
+// Stencil stuff
+let ext = Extension()
+
+// Stencil does not yet support automatic HTML escaping:
+// https://github.com/kylef/Stencil/pull/80
+//
+ext.registerFilter("htmlencode") { (value: Any?) in
+    if let value = value as? String {
+        return value
+            .replacingOccurrences(of: "&", with: "&amp;")
+            .replacingOccurrences(of: "<", with: "&lt;")
+            .replacingOccurrences(of: ">", with: "&gt;")
+            .replacingOccurrences(of: "'", with: "&apos;")
+            .replacingOccurrences(of: "\"", with: "&quot;")
+    }
+    return value
+}
+
+let router = Router()
+router.add(templateEngine: StencilTemplateEngine(extension: ext))
+
+// Configure our ORM Database connection pool as dbConnPool created by KueryPostgres
+Database.default = Database(dbConnPool)
+
+// Define the query parameters we can receive
+struct TFBParams: QueryParams {
+    let queries: Int
+
+    // Override default decode to cater for the query parameter specification:
+    //   If the parameter is missing, is not an integer, or is an integer less
+    //   than 1, the value should be interpreted as 1.
+    // This means that rather than failing to decode on a non-integer value, we
+    // should fall back to a value of 1.
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        if let value = try? container.decode(Int.self, forKey: CodingKeys.queries) {
+            self.queries = value
+        } else {
+            self.queries = 1
+        }
+    }
+}
+
+// Set Server header on all responses as per TechEmpower spec
+router.all("/*") {
+    _, response, next in
+    response.headers["Server"] = "Kitura"
+    next()
+}
+
+//
+// TechEmpower test 2: Single database query (full ORM)
+//
+router.get("/db") { (respondWith: @escaping (RandomRow?, RequestError?) -> Void) in
+    // Select random row from database range
+    RandomRow.find(id: RandomRow.randomId, respondWith)
+}
+
+//
+// TechEmpower test 3: Multiple database queries (full ORM)
+// Get param provides number of queries: /queries?queries=N
+//
+router.get("/queries") { (params: TFBParams, respondWith: @escaping ([RandomRow]?, RequestError?) -> Void) in
+    let numQueries = max(1, min(params.queries, 500))  // Snap to range of 1-500 as per test spec
+    getRandomRows(count: numQueries, completion: respondWith)
+}
+router.get("/queriesParallel") { (params: TFBParams, respondWith: @escaping ([RandomRow]?, RequestError?) -> Void) in
+    let numQueries = max(1, min(params.queries, 500))  // Snap to range of 1-500 as per test spec
+    getRandomRowsParallel(count: numQueries, completion: respondWith)
+}
+
+//
+// TechEmpower test 4: fortunes (full ORM)
+// TODO: convert to Codable once templating support is available
+//
+router.get("/fortunes") {
+    request, response, next in
+    response.headers["Content-Type"] = "text/html; charset=UTF-8"
+    Fortune.findAll { (fortunes, err) in
+        if var fortunes = fortunes {
+            fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
+            do {
+                try response.render("fortunes.stencil", context: ["fortunes": fortunes.sorted()]).end()
+            } catch {
+                try? response.status(.internalServerError).send("Error: \(error)").end()
+            }
+        } else {
+            try? response.status(.internalServerError).send("Error: \(err ?? .internalServerError)").end()
+        }
+    }
+}
+
+//
+// TechEmpower test 5: updates (full ORM)
+//
+router.get("/updates") { (params: TFBParams, respondWith: @escaping ([RandomRow]?, RequestError?) -> Void) in
+    let numQueries = max(1, min(params.queries, 500))  // Snap to range of 1-500 as per test spec
+    updateRandomRows(count: numQueries, completion: respondWith)
+}
+router.get("/updatesParallel") { (params: TFBParams, respondWith: @escaping ([RandomRow]?, RequestError?) -> Void) in
+    let numQueries = max(1, min(params.queries, 500))  // Snap to range of 1-500 as per test spec
+    updateRandomRowsParallel(count: numQueries, completion: respondWith)
+}
+
+
+Kitura.addHTTPServer(onPort: 8080, with: router)
+Kitura.run()

+ 112 - 10
frameworks/Swift/kitura/benchmark_config.json

@@ -3,11 +3,26 @@
   "tests": [{
     "default": {
       "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "none",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "none",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
+    "postgres": {
       "db_url": "/db",
-      "query_url": "/queries?queries=",
+      "query_url": "/queriesParallel?queries=",
       "fortune_url": "/fortunes",
-      "update_url": "/updates?queries=",
-      "plaintext_url": "/plaintext",
+      "update_url": "/updatesParallel?queries=",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",
@@ -22,13 +37,49 @@
       "display_name": "Kitura",
       "notes": ""
     },
+    "postgres-orm": {
+      "db_url": "/db",
+      "query_url": "/queriesParallel?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updatesParallel?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "Full",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
+    "postgres-orm-codable": {
+      "db_url": "/db",
+      "query_url": "/queriesParallel?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updatesParallel?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "Full",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
     "mongodb": {
-      "json_url": "/json",
       "db_url": "/db",
       "query_url": "/queries?queries=",
       "fortune_url": "/fortunes",
       "update_url": "/updates?queries=",
-      "plaintext_url": "/plaintext",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",
@@ -45,11 +96,26 @@
     },
     "gcd": {
       "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "none",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "none",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
+    "gcd-postgres": {
       "db_url": "/db",
-      "query_url": "/queries?queries=",
+      "query_url": "/queriesParallel?queries=",
       "fortune_url": "/fortunes",
-      "update_url": "/updates?queries=",
-      "plaintext_url": "/plaintext",
+      "update_url": "/updatesParallel?queries=",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",
@@ -64,13 +130,49 @@
       "display_name": "Kitura",
       "notes": ""
     },
+    "gcd-postgres-orm": {
+      "db_url": "/db",
+      "query_url": "/queriesParallel?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updatesParallel?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "Full",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
+    "gcd-postgres-orm-codable": {
+      "db_url": "/db",
+      "query_url": "/queriesParallel?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updatesParallel?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "Full",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
     "gcd-mongodb": {
-      "json_url": "/json",
       "db_url": "/db",
       "query_url": "/queries?queries=",
       "fortune_url": "/fortunes",
       "update_url": "/updates?queries=",
-      "plaintext_url": "/plaintext",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",

+ 7 - 0
frameworks/Swift/kitura/kitura-gcd-postgres-orm-codable.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release -Xswiftc -DGCD_ASYNCH
+CMD .build/release/TechEmpowerPostgresORMCodable

+ 7 - 0
frameworks/Swift/kitura/kitura-gcd-postgres-orm.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release -Xswiftc -DGCD_ASYNCH
+CMD .build/release/TechEmpowerPostgresORM

+ 7 - 0
frameworks/Swift/kitura/kitura-gcd-postgres.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release -Xswiftc -DGCD_ASYNCH
+CMD .build/release/TechEmpowerPostgres

+ 1 - 1
frameworks/Swift/kitura/kitura-gcd.dockerfile

@@ -4,4 +4,4 @@ ADD ./ /kitura
 WORKDIR /kitura
 RUN apt update -yqq && apt install -yqq libpq-dev
 RUN swift build -c release -Xswiftc -DGCD_ASYNCH
-CMD .build/release/TechEmpowerKuery
+CMD .build/release/TechEmpower

+ 7 - 0
frameworks/Swift/kitura/kitura-postgres-orm-codable.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release
+CMD .build/release/TechEmpowerPostgresORMCodable

+ 7 - 0
frameworks/Swift/kitura/kitura-postgres-orm.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release
+CMD .build/release/TechEmpowerPostgresORM

+ 7 - 0
frameworks/Swift/kitura/kitura-postgres.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release
+CMD .build/release/TechEmpowerPostgres

+ 1 - 1
frameworks/Swift/kitura/kitura.dockerfile

@@ -4,4 +4,4 @@ ADD ./ /kitura
 WORKDIR /kitura
 RUN apt update -yqq && apt install -yqq libpq-dev
 RUN swift build -c release
-CMD .build/release/TechEmpowerKuery
+CMD .build/release/TechEmpower