├── .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 | MySQL Provider 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Slack Team 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 3.1 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 | --------------------------------------------------------------------------------