├── .circleci
└── config.yml
├── .gitignore
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── MySQLProvider
│ ├── Driver+Config.swift
│ ├── Droplet+MySQL.swift
│ ├── Error.swift
│ ├── Exports.swift
│ └── Provider.swift
└── Tests
├── LinuxMain.swift
└── MySQLProviderTests
└── VaporMySQL.swift
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | jobs:
4 | macos:
5 | macos:
6 | xcode: "9.0"
7 | steps:
8 | - run: brew install vapor/tap/vapor vapor/tap/cmysql
9 | - run: brew services start mysql
10 | - run: sleep 3 && mysqladmin -uroot create test
11 | - checkout
12 | - run: swift build
13 | - run: swift test
14 |
15 | linux:
16 | docker:
17 | - image: swift:4.0.3
18 | - image: mysql:5.7
19 | environment:
20 | MYSQL_ALLOW_EMPTY_PASSWORD: true
21 | MYSQL_DATABASE: test
22 | steps:
23 | - run: apt-get update
24 | - run: apt-get install -y libmysqlclient-dev libssl-dev
25 | - checkout
26 | - run: swift build
27 | - run: swift test
28 |
29 | # 3.1 backward compat checks
30 |
31 | linux-3:
32 | docker:
33 | - image: swift:3.1.1
34 | - image: mysql:5.7
35 | environment:
36 | MYSQL_ALLOW_EMPTY_PASSWORD: true
37 | MYSQL_DATABASE: test
38 | steps:
39 | - run: apt-get update
40 | - run: apt-get install -y libmysqlclient-dev libssl-dev
41 | - checkout
42 | - run: swift build
43 | - run: swift test
44 |
45 | workflows:
46 | version: 2
47 | tests:
48 | jobs:
49 | - linux
50 | - linux-3
51 | - macos
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .build
2 | Packages
3 | *.xcodeproj
4 | Package.pins
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Qutheory, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | import PackageDescription
2 |
3 | let package = Package(
4 | name: "MySQLProvider",
5 | dependencies: [
6 | // MySQL driver for Fluent
7 | .Package(url: "https://github.com/vapor/mysql-driver.git", majorVersion: 2),
8 | // A provider for including Fluent in Vapor applications
9 | .Package(url: "https://github.com/vapor/fluent-provider.git", majorVersion: 1),
10 | // A web framework and server for Swift that works on macOS and Ubuntu.
11 | .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2),
12 | ]
13 | )
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Sources/MySQLProvider/Driver+Config.swift:
--------------------------------------------------------------------------------
1 | import URI
2 | import Vapor
3 | import Fluent
4 | import MySQLDriver
5 |
6 | extension MySQLDriver.Driver: ConfigInitializable {
7 | /// Creates a MySQLDriver from a `mysql.json`
8 | /// config file.
9 | ///
10 | /// The file should contain similar JSON:
11 | ///
12 | /// {
13 | /// "hostname": "127.0.0.1",
14 | /// "user": "root",
15 | /// "password": "",
16 | /// "database": "test",
17 | /// "port": 3306, // optional
18 | /// "flag": 0, // optional
19 | /// "encoding": "utf8" // optional
20 | /// }
21 | ///
22 | /// Optionally include a url instead:
23 | ///
24 | /// {
25 | /// "url": "mysql://user:pass@host:3306/database"
26 | /// }
27 | public convenience init(config: Config) throws {
28 | guard let mysql = config["mysql"]?.object else {
29 | throw ConfigError.missingFile("mysql")
30 | }
31 |
32 | let flag = mysql["flag"]?.uint
33 | let encoding = mysql["encoding"]?.string
34 |
35 | if let url = mysql["url"]?.string {
36 | try self.init(url: url, flag: flag, encoding: encoding)
37 | } else {
38 | let masterHostname: String
39 | if let master = mysql["hostname"]?.string {
40 | masterHostname = master
41 | } else if let master = mysql["master"]?.string {
42 | masterHostname = master
43 | } else {
44 | throw ConfigError.missing(key: ["hostname"], file: "mysql", desiredType: String.self)
45 | }
46 |
47 | let readReplicaHostnames: [String]
48 | if let array = mysql["readReplicas"]?.array?.flatMap({ $0.string }) {
49 | readReplicaHostnames = array
50 | } else if let string = mysql["readReplicas"]?.string {
51 | readReplicaHostnames = string.commaSeparatedArray()
52 | } else {
53 | readReplicaHostnames = []
54 | }
55 |
56 | guard let user = mysql["user"]?.string else {
57 | throw ConfigError.missing(key: ["user"], file: "mysql", desiredType: String.self)
58 | }
59 |
60 | guard let password = mysql["password"]?.string else {
61 | throw ConfigError.missing(key: ["password"], file: "mysql", desiredType: String.self)
62 | }
63 |
64 | guard let database = mysql["database"]?.string else {
65 | throw ConfigError.missing(key: ["database"], file: "mysql", desiredType: String.self)
66 | }
67 |
68 | let port = mysql["port"]?.uint
69 |
70 | try self.init(
71 | masterHostname: masterHostname,
72 | readReplicaHostnames: readReplicaHostnames,
73 | user: user,
74 | password: password,
75 | database: database,
76 | port: port ?? 3306,
77 | flag: flag ?? 0,
78 | encoding: encoding ?? "utf8"
79 | )
80 | }
81 | }
82 |
83 | /// See MySQLDriver.init(host: String, ...)
84 | public convenience init(url: String, flag: UInt?, encoding: String?) throws {
85 | let uri = try URI(url)
86 | guard
87 | let user = uri.userInfo?.username,
88 | let pass = uri.userInfo?.info
89 | else {
90 | throw ConfigError.missing(key: ["url(userInfo)"], file: "mysql", desiredType: URI.self)
91 | }
92 |
93 | let db = uri.path
94 | .characters
95 | .split(separator: "/")
96 | .map { String($0) }
97 | .joined(separator: "")
98 |
99 | try self.init(
100 | masterHostname: uri.hostname,
101 | readReplicaHostnames: [],
102 | user: user,
103 | password: pass,
104 | database: db,
105 | port: uri.port.flatMap { UInt($0) } ?? 3306,
106 | flag: flag ?? 0,
107 | encoding: encoding ?? "utf8"
108 | )
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Sources/MySQLProvider/Droplet+MySQL.swift:
--------------------------------------------------------------------------------
1 | import Vapor
2 |
3 | extension Droplet {
4 | /// Returns the current MySQL driver or throws
5 | /// an error.
6 | /// This is a convenience for casting the
7 | /// drop.database.driver as a MySQLDriver type.
8 | public func mysql() throws -> MySQLDriver.Driver {
9 | let database = try assertDatabase()
10 |
11 | guard let driver = database.driver as? MySQLDriver.Driver else {
12 | throw MySQLProviderError.invalidFluentDriver(database.driver)
13 | }
14 |
15 | return driver
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/MySQLProvider/Error.swift:
--------------------------------------------------------------------------------
1 | public enum MySQLProviderError: Error {
2 | case invalidFluentDriver(Fluent.Driver)
3 | case unspecified(Error)
4 | }
5 |
6 | extension MySQLProviderError: Debuggable {
7 | public var reason: String {
8 | switch self {
9 | case .invalidFluentDriver(let driver):
10 | return "Invalid Fluent driver: \(type(of: driver)). MySQL driver required."
11 | case .unspecified(let error):
12 | return "Unknown: \(error)"
13 | }
14 | }
15 |
16 | public var identifier: String {
17 | switch self {
18 | case .invalidFluentDriver:
19 | return "invalidFluentDriver"
20 | case .unspecified:
21 | return "unknown"
22 | }
23 | }
24 |
25 | public var possibleCauses: [String] {
26 | switch self {
27 | case .invalidFluentDriver:
28 | return [
29 | "You have not added the `MySQLProvider.Provider` to your Droplet.",
30 | "You have not specified `mysql` in the `fluent.json` file.",
31 | "`drop.database` is getting set programatically to a non-MySQL database"
32 | ]
33 | case .unspecified:
34 | return []
35 | }
36 | }
37 |
38 | public var suggestedFixes: [String] {
39 | return [
40 | "Ensure you have properly configured the MySQLProvider package according to the documentation"
41 | ]
42 | }
43 |
44 | public var documentationLinks: [String] {
45 | return [
46 | "https://docs.vapor.codes/2.0/mysql/package/"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/MySQLProvider/Exports.swift:
--------------------------------------------------------------------------------
1 | @_exported import MySQLDriver
2 | @_exported import FluentProvider
3 |
--------------------------------------------------------------------------------
/Sources/MySQLProvider/Provider.swift:
--------------------------------------------------------------------------------
1 | import Vapor
2 |
3 | public final class Provider: Vapor.Provider {
4 | public static let repositoryName = "mysql-provider"
5 |
6 | public init(config: Config) throws { }
7 |
8 |
9 | public func boot(_ config: Config) throws {
10 | // add the fluent provider so the end user doesn't have to
11 | try config.addProvider(FluentProvider.Provider.self)
12 | // add the mysql driver
13 | config.addConfigurable(driver: MySQLDriver.Driver.init, name: "mysql")
14 | }
15 |
16 | public func boot(_ drop: Droplet) throws { }
17 | public func beforeRun(_ drop: Droplet) {}
18 | }
19 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | #if os(Linux)
2 |
3 | import XCTest
4 | @testable import MySQLProviderTests
5 |
6 | XCTMain([
7 | testCase(VaporMySQL.allTests),
8 | ])
9 |
10 | #endif
--------------------------------------------------------------------------------
/Tests/MySQLProviderTests/VaporMySQL.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import Vapor
3 | @testable import MySQLProvider
4 |
5 | class VaporMySQL: XCTestCase {
6 | func testHappyPath() throws {
7 | let config = try Config(node: [
8 | "fluent": [
9 | "driver": "mysql",
10 | "maxConnections": 1337
11 | ],
12 | "mysql": [
13 | "hostname": "127.0.0.1",
14 | "user": "root",
15 | "password": "",
16 | "database": "test"
17 | ]
18 | ])
19 |
20 | try config.addProvider(Provider.self)
21 | let drop = try Droplet(config)
22 | let database = try drop.assertDatabase()
23 |
24 | XCTAssertEqual(database.threadConnectionPool.maxConnections, 1337)
25 | let result = try database.raw("SELECT @@version")
26 |
27 | XCTAssert(result[0, "@@version"]?.string?.contains("5.") == true)
28 | }
29 |
30 | func testDifferentDriver() throws {
31 | var config = Config([:])
32 | try config.set("fluent.driver", "memory")
33 | try config.addProvider(Provider.self)
34 | let drop = try Droplet(config: config)
35 |
36 | // we're still adding the VaporMySQL provider,
37 | // but nothing should fail since we are specifying "memory"
38 | _ = try drop.assertDatabase()
39 | }
40 |
41 | func testMissingConfigFails() throws {
42 | let config = try Config(node: [
43 | "fluent": [
44 | "driver": "mysql"
45 | ]
46 | ])
47 | try config.addProvider(Provider.self)
48 |
49 | do {
50 | _ = try Droplet(config)
51 | XCTFail("Should have failed.")
52 | } catch ConfigError.missingFile(let file) {
53 | XCTAssert(file == "mysql")
54 | } catch {
55 | XCTFail("Wrong error: \(error)")
56 | }
57 | }
58 |
59 | static let allTests = [
60 | ("testHappyPath", testHappyPath),
61 | ("testDifferentDriver", testDifferentDriver),
62 | ("testMissingConfigFails", testMissingConfigFails)
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------