Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Vapor 3 MySQL #3721

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions frameworks/Swift/vapor/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@
"version": "1.0.1"
}
},
{
"package": "Fluent",
"repositoryURL": "https://github.com/vapor/fluent.git",
"state": {
"branch": null,
"revision": "8f22ef2dfeb278179a2d6e91769a5c1ceccf31ef",
"version": "3.0.0-rc.2.4.1"
}
},
{
"package": "FluentMySQL",
"repositoryURL": "https://github.com/vapor/fluent-mysql.git",
"state": {
"branch": null,
"revision": "19956a04b1983f20879d9c770a49d7948c42b270",
"version": "3.0.0-rc.2.4"
}
},
{
"package": "HTTP",
"repositoryURL": "https://github.com/vapor/http.git",
Expand All @@ -46,6 +64,15 @@
"version": "3.0.5"
}
},
{
"package": "Leaf",
"repositoryURL": "https://github.com/vapor/leaf.git",
"state": {
"branch": null,
"revision": "a71cb0cd29ec88ee72baa00668c05e327114f27c",
"version": "3.0.0-rc.2.2"
}
},
{
"package": "Multipart",
"repositoryURL": "https://github.com/vapor/multipart.git",
Expand All @@ -55,6 +82,15 @@
"version": "3.0.1"
}
},
{
"package": "MySQL",
"repositoryURL": "https://github.com/vapor/mysql.git",
"state": {
"branch": null,
"revision": "cda473257572c8e02706d0c65fd145765c5a414d",
"version": "3.0.0-rc.2.2"
}
},
{
"package": "Routing",
"repositoryURL": "https://github.com/vapor/routing.git",
Expand All @@ -73,6 +109,15 @@
"version": "1.0.0"
}
},
{
"package": "SQL",
"repositoryURL": "https://github.com/vapor/sql.git",
"state": {
"branch": null,
"revision": "1bb94271d8645b6d6983721080ad642aa35d749c",
"version": "1.0.0"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",
Expand Down
3 changes: 3 additions & 0 deletions frameworks/Swift/vapor/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ let package = Package(
name: "tfb",
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
.package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0-rc"),
.package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc.1")
],
targets: [
.target(name: "vapor-tfb", dependencies: ["Vapor"], exclude: ["Resources"]),
.target(name: "vapor-tfb-mysql", dependencies: ["FluentMySQL", "Leaf", "Vapor"]),
]
)
10 changes: 10 additions & 0 deletions frameworks/Swift/vapor/Resources/Views/fortune.leaf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head><title>Fortunes</title></head>
<body>
<table>
<tr><th>id</th><th>message</th></tr>
#for(fortune in fortunes) { <tr><td>#(fortune.id)</td><td>#(fortune.message)</td></tr> }
</table>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Vapor
import FluentMySQL

final class Fortune: MySQLModel, Migration, Content {
static let entity = "fortune"

var id: Int?
var message: String

init(id: Int?, message: String) {
self.id = id
self.message = message
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Vapor

struct Message: Content {
let message: String?

init(message: String? = nil) {
self.message = message
}
}
15 changes: 15 additions & 0 deletions frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/World.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Vapor
import FluentMySQL

final class World: MySQLModel, Migration, Content {
static let entity = "world"

var id: Int?
var randomNumber: Int

/// Creates a new World
init(id: Int?, randomNumber: Int) {
self.id = id
self.randomNumber = randomNumber
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Vapor

public let validQueriesRange: ClosedRange<Int> = 1...500

/// Tests 3 and 5 are parameterized with `queries`.
///
/// The queries parameter must be bounded to between 1 and 500.
/// If the parameter is missing, is not an integer, or is an integer less than 1,
/// the value should be interpreted as 1; if greater than 500, the value should be interpreted as 500.
///
/// - Parameter request: HTTP request
/// - Returns: queries
public func queriesParam(for request: Request) throws -> Int {
// TODO: throw instead of using !
let rangeMax = try request.parameters.next(String.self)
.removingPercentEncoding? // convert url-encoded chars
.components(separatedBy: "...")
.last? // ignore lower bound, only retain last part which contains upper bound
.dropLast() // remove ]
let paramNormalized = rangeMax.flatMap(String.init).flatMap(Int.init) ?? validQueriesRange.upperBound
return clamp(paramNormalized, to: validQueriesRange)
}

func clamp(_ value: Int, to: ClosedRange<Int>) -> Int {
return max(to.lowerBound, min(to.upperBound, value))
}
29 changes: 29 additions & 0 deletions frameworks/Swift/vapor/Sources/vapor-tfb-mysql/WorldMeta.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Vapor
#if os(Linux)
import Glibc
#endif

private extension Int {
static func random<T>(_ max: T) -> Int where T: BinaryInteger {
#if os(Linux)
return Int(SwiftGlibc.random() % (Int(max) + 1))
#else
return Int(arc4random_uniform(UInt32(max)))
#endif
}
}

public struct WorldMeta {
private init() { }

public static let maxId: UInt32 = 10_000

public static func randomId() -> Int {
return Int.random(maxId)
}

public static let maxRandomNumber: Int32 = 10_000
public static func randomRandomNumber() -> Int {
return Int.random(maxRandomNumber)
}
}
122 changes: 122 additions & 0 deletions frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import FluentMySQL
import Leaf
import Vapor

// Services
var services = Services.default()

// Router
let router = EngineRouter.default()

// Config
var config = Config.default()

// Routes

var jsonRes = HTTPResponse(status: .ok, headers: ["Server": "Vapor"])
let jsonEncoder = JSONEncoder()

// Test type 2: Single database query
router.get("db") { req -> Future<Response> in
let worldId = WorldMeta.randomId()
return try World.find(worldId, on: req)
.map(to: Response.self) { world in
let res = Response(http: jsonRes, using: req.sharedContainer)

if let world = world {
try res.content.encode(json: world, using: jsonEncoder)
} else {
try res.content.encode(json: Message(), using: jsonEncoder)
}

return res
}
}

// Test type 3: Multiple database queries
router.get("queries", String.parameter) { req -> Future<Response> in
let queries = try queriesParam(for: req)
let ids = (1...queries).lazy.map({ _ in WorldMeta.randomId() })

return try ids.map { id in
return try World.find(id, on: req)
}
.flatten(on: req)
.map { result in
return result.compactMap({ $0 })
}.map { worlds in
let res = Response(http: jsonRes, using: req.sharedContainer)
try res.content.encode(json: worlds, using: jsonEncoder)
return res
}
}

// Test type 4: Fortunes
router.get("fortunes") { req -> Future<View> in
let posixLocale = Locale(identifier: "en_US_POSIX")

return Fortune.query(on: req).all()
.map { fortunes -> [Fortune] in
var newFortunes = fortunes
let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
newFortunes.insert(additional, at: 0)
newFortunes.sort(by: { lhs, rhs -> Bool in
return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
})
return newFortunes
}
.flatMap { fortunes in
// FIXME: Add headers
return try req.view().render("fortune", ["fortunes": fortunes])
}
}

// Test type 5: Database updates
router.get("updates", String.parameter) { req -> Future<Response> in
let queries = try queriesParam(for: req)
let ids = (1...queries).lazy.map({ _ in WorldMeta.randomId() })

return try ids.map { id in
return try World.find(id, on: req)
}
.flatten(on: req)
.map { result -> [World] in
let worlds = result.compactMap({ $0 })
worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
return worlds
}
.flatMap { worlds -> Future<[World]> in
return worlds
.map { $0.save(on: req) }
.flatten(on: req)
}.flatMap { _ in
return World.query(on: req).all()
}.map { worlds in
let res = Response(http: jsonRes, using: req.sharedContainer)
try res.content.encode(json: worlds, using: jsonEncoder)
return res
}
}

services.register(router, as: Router.self)

try services.register(LeafProvider())
config.prefer(LeafRenderer.self, for: ViewRenderer.self)
try services.register(FluentMySQLProvider())
/// Register custom MySQL Config
let mySQLUrl = URL(string: "mysql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world")!
let mysqlConfig = MySQLDatabaseConfig(hostname: mySQLUrl.host!, port: mySQLUrl.port!, username: mySQLUrl.user!, password: mySQLUrl.password, database: "hello_world")
services.register(mysqlConfig)

/// Configure migrations
services.register(MigrationConfig())
/// Allow requests as DatabaseConnectible
Fortune.defaultDatabase = .mysql
World.defaultDatabase = .mysql

// Middlewares (remove unused ErrorMiddleware)
var middlewares = MiddlewareConfig()
services.register(middlewares)

let app = try Application(config: config, environment: .detect(), services: services)
try app.run()
21 changes: 21 additions & 0 deletions frameworks/Swift/vapor/benchmark_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@
"display_name": "Vapor",
"notes": "",
"versus": ""
},
"mysql": {
"db_url": "/db",
"query_url": "/queries/",
"fortune_url": "/fortunes",
"update_url": "/updates/",
"port": 8080,
"approach": "Realistic",
"classification": "Fullstack",
"database": "MySQL",
"framework": "Vapor",
"language": "Swift",
"flavor": "None",
"orm": "Full",
"platform": "None",
"webserver": "None",
"os": "Linux",
"database_os": "Linux",
"display_name": "Vapor",
"notes": "",
"versus": ""
}
}]
}
8 changes: 8 additions & 0 deletions frameworks/Swift/vapor/vapor-mysql.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM swift:4.1

ADD ./ /vapor
WORKDIR /vapor
RUN apt update -yqq && apt install -yqq cmysql libmysqlclient-dev
RUN swift build -c release

CMD .build/release/vapor-tfb-mysql -e production -b 0.0.0.0:8080