├── Sources ├── MySQL │ ├── MySQLResultProtocol.swift │ ├── MySQLRow.swift │ ├── MySQLError.swift │ ├── MySQLConnectionPoolMessage.swift │ ├── MySQLField.swift │ ├── MySQLFieldType.swift │ ├── MySQLClientProtocol.swift │ ├── mocks │ │ ├── MockMySQLResult.swift │ │ ├── MockMySQLClient.swift │ │ ├── MocksMySQLConnectionPool.swift │ │ └── MockMySQLConnection.swift │ ├── MySQLRowParser.swift │ ├── MySQLConnectionProtocol.swift │ ├── MySQLResult.swift │ ├── MySQLFieldParser.swift │ ├── MySQLClient.swift │ ├── MySQLConnectionPoolProtocol.swift │ ├── MySQLQueryBuilder.swift │ ├── MySQLConnection.swift │ └── MySQLConnectionPool.swift └── Example │ └── main.swift ├── Tests ├── LinuxMain.swift └── MySQL │ ├── Utility.swift │ ├── MySQLResultTests.swift │ ├── MySQLClientTests.swift │ ├── MySQLRowParserTests.swift │ ├── MySQLQueryBuilderTests.swift │ ├── MySQLFieldParserTests.swift │ └── MySQLConnectionPoolTests.swift ├── Package.swift ├── Makefile ├── LICENSE ├── .gitignore └── README.md /Sources/MySQL/MySQLResultProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol MySQLResultProtocol { 4 | func nextResult() -> MySQLRow? 5 | } 6 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLRow.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(OSX) 4 | public typealias MySQLRow = [String: AnyObject] 5 | #else 6 | public typealias MySQLRow = [String: Any] 7 | #endif 8 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLError.swift: -------------------------------------------------------------------------------- 1 | public enum MySQLError: ErrorProtocol { 2 | case UnableToCreateConnection 3 | case UnableToExecuteQuery(message: String) 4 | case ConnectionPoolTimeout 5 | case NoMoreResults 6 | } 7 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLConnectionPoolMessage.swift: -------------------------------------------------------------------------------- 1 | public enum MySQLConnectionPoolMessage { 2 | case CreatedNewConnection 3 | case FailedToCreateConnection 4 | case ConnectionDisconnected 5 | case RetrievedConnectionFromPool 6 | } 7 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLField.swift: -------------------------------------------------------------------------------- 1 | // MySQLField encapsulates the metadata associated with a field in the database. 2 | //TODO: complete implementation of header corresponding to the C library. 3 | public struct MySQLField { 4 | 5 | /** 6 | Name of the field. 7 | */ 8 | var name: String = "" 9 | var type: MySQLFieldType 10 | } 11 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import MySQLTestSuite 4 | 5 | XCTMain([ 6 | testCase(MySQLClientTests.allTests), 7 | testCase(MySQLConnectionPoolTests.allTests), 8 | testCase(MySQLFieldParserTests.allTests), 9 | testCase(MySQLRowParserTests.allTests), 10 | testCase(MySQLResultTests.allTests), 11 | testCase(MySQLQueryBuilderTests.allTests) 12 | ]) 13 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "MySQL", 5 | targets: [ 6 | Target( 7 | name: "Example", 8 | dependencies: [.Target(name: "MySQL")]), 9 | Target(name: "Example"), 10 | ], 11 | //exclude: ["Tests"], 12 | dependencies: [ 13 | .Package(url: "https://github.com/notonthehighstreet/swift-libmysql", majorVersion: 0, minor: 0) 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLFieldType.swift: -------------------------------------------------------------------------------- 1 | public enum MySQLFieldType: Int { 2 | case Tiny 3 | case Short 4 | case Long 5 | case Int24 6 | case LongLong 7 | case Decimal 8 | case NewDecimal 9 | case Float 10 | case Double 11 | case Bit 12 | case Timestamp 13 | case Date 14 | case Time 15 | case DateTime 16 | case Year 17 | case String 18 | case VarString 19 | case Blob 20 | case Set 21 | case Enum 22 | case Geometry 23 | case Null 24 | } 25 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLClientProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Protocol containing the public methods available to the MySQLClient. 4 | public protocol MySQLClientProtocol { 5 | init(connection: MySQLConnectionProtocol) 6 | func info() -> String? 7 | func version() -> UInt 8 | func execute(query: String) -> (MySQLResultProtocol?, MySQLError?) 9 | func execute(builder: MySQLQueryBuilder) -> (MySQLResultProtocol?, MySQLError?) 10 | func nextResultSet() -> (MySQLResultProtocol?, MySQLError?) 11 | } 12 | -------------------------------------------------------------------------------- /Tests/MySQL/Utility.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | public func getUnsafeMutablePointer() -> UnsafeMutablePointer { 5 | #if os(OSX) 6 | let pointer = UnsafeMutablePointer(allocatingCapacity: self.characters.count + 1) 7 | (self as NSString).getCString(pointer, maxLength: self.characters.count + 1, encoding: NSUTF8StringEncoding) 8 | return pointer 9 | #else 10 | let otherStr = NSString(string: self) 11 | return UnsafeMutablePointer(otherStr.UTF8String!) 12 | #endif 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq "$(PLATFORM)" "" 2 | PLATFORM := $(shell uname) 3 | endif 4 | 5 | ifeq "$(PLATFORM)" "Darwin" 6 | BUILDCOMMAND := swift build -Xcc -fblocks -Xswiftc -I/usr/local/include -Xlinker -L/usr/local/lib 7 | REPLACECOMMAND := ls > /dev/null 8 | else 9 | BUILDCOMMAND := swift build -Xcc -fblocks 10 | REPLACECOMMAND := sed -i -e 's/MySQL.xctest/MySQLTest.xctest/g' .build/debug.yaml 11 | endif 12 | 13 | build: clean 14 | @echo --- Building package 15 | $(BUILDCOMMAND) 16 | test: clean build 17 | @echo --- Running tests 18 | $(REPLACECOMMAND) 19 | swift test 20 | 21 | clean: 22 | @echo --- Invoking swift build --clean 23 | swift build --clean 24 | -------------------------------------------------------------------------------- /Sources/MySQL/mocks/MockMySQLResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MockMySQLResult is a mock object which can be used in unit tests to replace the real instance in order to test behaviour 4 | public class MockMySQLResult: MySQLResultProtocol { 5 | public var nextCalled = 0 6 | public var results: [MySQLRow?]? 7 | 8 | private var resultItterator = 0 9 | 10 | public func nextResult() -> MySQLRow? { 11 | nextCalled += 1 12 | 13 | if results == nil || resultItterator >= results!.count{ 14 | resultItterator = 0 15 | return nil 16 | } else { 17 | resultItterator += 1 18 | return results![resultItterator - 1] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLRowParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal class MySQLRowParser { 4 | 5 | internal func parse(row: CMySQLRow, headers: [MySQLField]) -> MySQLRow { 6 | var result = MySQLRow() 7 | 8 | for i in 0...(headers.count-1) { 9 | if row[i] == nil { 10 | result[headers[i].name] = nil 11 | } else { 12 | switch headers[i].type { 13 | case MySQLFieldType.String, MySQLFieldType.VarString: 14 | result[headers[i].name] = String(cString: row[i]!) 15 | case MySQLFieldType.Tiny, MySQLFieldType.Short, MySQLFieldType.Long, MySQLFieldType.Int24, MySQLFieldType.LongLong: 16 | result[headers[i].name] = Int(String(cString: row[i]!)) 17 | case MySQLFieldType.Decimal, MySQLFieldType.NewDecimal, MySQLFieldType.Double: 18 | result[headers[i].name] = Double(String(cString: row[i]!)) 19 | case MySQLFieldType.Float: 20 | result[headers[i].name] = Float(String(cString: row[i]!)) 21 | default: 22 | result[headers[i].name] = nil 23 | } 24 | } 25 | } 26 | 27 | return result 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 notonthehighstreet 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 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLConnectionProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CMySQLClient 3 | 4 | // Pointer to a result object returned from executing a query. 5 | public typealias CMySQLResult = UnsafeMutablePointer 6 | 7 | // Pointer to an array of strings containing the data representing a row from a query. 8 | public typealias CMySQLRow = MYSQL_ROW 9 | 10 | // Pointer to a MYSQL_FIELD struct as defined in the native c library. 11 | public typealias CMySQLField = UnsafeMutablePointer 12 | 13 | // Represents a protocol for connections to implement. 14 | public protocol MySQLConnectionProtocol { 15 | func isConnected() -> Bool 16 | func connect(host: String, user: String, password: String) throws 17 | func connect(host: String, user: String, password: String, port: Int) throws 18 | func connect(host: String, user: String, password: String, port: Int, database: String) throws 19 | func close() 20 | func client_info() -> String? 21 | func client_version() -> UInt 22 | func execute(query: String) -> (CMySQLResult?, [CMySQLField]?, MySQLError?) 23 | func nextResult(result: CMySQLResult) -> CMySQLRow? 24 | func nextResultSet() -> (CMySQLResult?, [CMySQLField]?, MySQLError?) 25 | func equals(otherObject: MySQLConnectionProtocol) -> Bool 26 | } 27 | -------------------------------------------------------------------------------- /Sources/MySQL/mocks/MockMySQLClient.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MockMySQLClient is a mock object which can be used in unit tests to replace the real instance in order to test behaviour 4 | public class MockMySQLClient: MySQLClientProtocol { 5 | public var executeQueryCalled = false 6 | public var executeQueryParams: String? 7 | 8 | public var executeBuilderCalled = false 9 | public var executeBuilderParams: MySQLQueryBuilder? 10 | public var executeMySQLResultReturn: MySQLResultProtocol? 11 | public var executeMySQLErrorReturn: MySQLError? 12 | 13 | public var nextResultSetCalled = false 14 | public var nextResultSetReturn: MySQLResultProtocol? 15 | public var nextResultSetErrorReturn: MySQLError? 16 | 17 | public required init(connection: MySQLConnectionProtocol) { } 18 | public func info() -> String? { return "1.2"} 19 | public func version() -> UInt { return 1 } 20 | 21 | public func execute(query: String) -> (MySQLResultProtocol?, MySQLError?) { 22 | executeQueryParams = query 23 | executeQueryCalled = true 24 | 25 | return (executeMySQLResultReturn, executeMySQLErrorReturn) 26 | } 27 | 28 | public func execute(builder: MySQLQueryBuilder) -> (MySQLResultProtocol?, MySQLError?) { 29 | executeBuilderParams = builder 30 | executeBuilderCalled = true 31 | 32 | return (executeMySQLResultReturn, executeMySQLErrorReturn) 33 | } 34 | 35 | public func nextResultSet() -> (MySQLResultProtocol?, MySQLError?) { 36 | nextResultSetCalled = true 37 | 38 | return (nextResultSetReturn, nextResultSetErrorReturn) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CMySQLClient 3 | 4 | // MySQLResult encapsulates the fields and data returned from a query, this object is not ordinarily instanstiated. 5 | public class MySQLResult: MySQLResultProtocol { 6 | 7 | private var resultPointer: CMySQLResult? = nil 8 | private var getNextResult:(result:CMySQLResult) -> CMySQLRow? 9 | 10 | /** 11 | The fields property returns an array containing the fields which corresponds to the query executed. 12 | */ 13 | public var fields = [MySQLField]() 14 | 15 | /** 16 | nextResult returns the next row from the database for the executed query, when no more rows are available nextResult returns nil. 17 | 18 | Returns: an instance of MySQLRow which is a dictionary [field_name (String): Object], when no further rows are avaialble this method returns nil. 19 | */ 20 | public func nextResult() -> MySQLRow? { 21 | if resultPointer == nil { 22 | return nil 23 | } 24 | 25 | if let row = getNextResult(result: resultPointer!) { 26 | let parser = MySQLRowParser() 27 | return parser.parse(row: row, headers:fields) 28 | } else { 29 | return nil 30 | } 31 | } 32 | 33 | internal init(result:CMySQLResult, fields: [CMySQLField], nextResult: ((result:CMySQLResult) -> CMySQLRow?)) { 34 | resultPointer = result 35 | getNextResult = nextResult 36 | parseFields(fields: fields) 37 | } 38 | 39 | private func parseFields(fields: [CMySQLField]) { 40 | let parser = MySQLFieldParser() 41 | for field in fields { 42 | self.fields.append(parser.parse(field: field.pointee)) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Xcode 4 | # 5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | 22 | ## Other 23 | *.xccheckout 24 | *.moved-aside 25 | *.xcuserstate 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | 32 | ## Playgrounds 33 | timeline.xctimeline 34 | playground.xcworkspace 35 | 36 | # Swift Package Manager 37 | # 38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 39 | Packages/ 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 63 | 64 | fastlane/report.xml 65 | fastlane/screenshot 66 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLFieldParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CMySQLClient 3 | 4 | internal class MySQLFieldParser { 5 | internal func parse(field: MYSQL_FIELD) -> MySQLField { 6 | let name = String(cString: field.name) 7 | let type = field.type 8 | 9 | var fieldType = MySQLFieldType.Null 10 | 11 | switch type { 12 | case MYSQL_TYPE_TINY: 13 | fieldType = MySQLFieldType.Tiny 14 | case MYSQL_TYPE_SHORT: 15 | fieldType = MySQLFieldType.Short 16 | case MYSQL_TYPE_LONG: 17 | fieldType = MySQLFieldType.Long 18 | case MYSQL_TYPE_INT24: 19 | fieldType = MySQLFieldType.Int24 20 | case MYSQL_TYPE_LONGLONG: 21 | fieldType = MySQLFieldType.LongLong 22 | case MYSQL_TYPE_DECIMAL: 23 | fieldType = MySQLFieldType.Decimal 24 | case MYSQL_TYPE_NEWDECIMAL: 25 | fieldType = MySQLFieldType.NewDecimal 26 | case MYSQL_TYPE_FLOAT: 27 | fieldType = MySQLFieldType.Float 28 | case MYSQL_TYPE_DOUBLE: 29 | fieldType = MySQLFieldType.Double 30 | case MYSQL_TYPE_BIT: 31 | fieldType = MySQLFieldType.Bit 32 | case MYSQL_TYPE_TIMESTAMP: 33 | fieldType = MySQLFieldType.Timestamp 34 | case MYSQL_TYPE_DATE: 35 | fieldType = MySQLFieldType.Date 36 | case MYSQL_TYPE_TIME: 37 | fieldType = MySQLFieldType.Time 38 | case MYSQL_TYPE_DATETIME: 39 | fieldType = MySQLFieldType.DateTime 40 | case MYSQL_TYPE_YEAR: 41 | fieldType = MySQLFieldType.Year 42 | case MYSQL_TYPE_STRING: 43 | fieldType = MySQLFieldType.String 44 | case MYSQL_TYPE_VAR_STRING: 45 | fieldType = MySQLFieldType.VarString 46 | case MYSQL_TYPE_BLOB: 47 | fieldType = MySQLFieldType.Blob 48 | case MYSQL_TYPE_SET: 49 | fieldType = MySQLFieldType.Set 50 | case MYSQL_TYPE_ENUM: 51 | fieldType = MySQLFieldType.Enum 52 | case MYSQL_TYPE_GEOMETRY: 53 | fieldType = MySQLFieldType.Geometry 54 | default: 55 | fieldType = MySQLFieldType.Null 56 | } 57 | 58 | let header = MySQLField(name: name, type: fieldType) 59 | 60 | return header 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Example/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MySQL 3 | 4 | print("Running test client") 5 | 6 | MySQLConnectionPool.setConnectionProvider() { 7 | return MySQL.MySQLConnection() 8 | } 9 | 10 | var connection_noDB: MySQLConnectionProtocol 11 | 12 | do { 13 | // get a connection from the pool with no database 14 | connection_noDB = try MySQLConnectionPool.getConnection(host: "127.0.0.1", user: "root", password: "my-secret-pw", port: 3306, database: "")! 15 | defer { 16 | MySQLConnectionPool.releaseConnection(connection: connection_noDB) // release the connection back to the pool 17 | } 18 | } catch { 19 | print("Unable to create connection") 20 | exit(0) 21 | } 22 | 23 | // create a new client using the leased connection 24 | var client = MySQLClient(connection: connection_noDB) 25 | 26 | print("MySQL Client Info: " + client.info()!) 27 | print("MySQL Client Version: " + String(client.version())) 28 | 29 | client.execute(query: "DROP DATABASE IF EXISTS testdb") 30 | client.execute(query: "CREATE DATABASE testdb") 31 | 32 | var connection_withDB: MySQLConnectionProtocol 33 | 34 | do { 35 | // get a connection from the pool connecting to a specific database 36 | connection_withDB = try MySQLConnectionPool.getConnection(host: "127.0.0.1", user: "root", password: "my-secret-pw", port: 3306, database: "testdb")! 37 | defer { 38 | MySQLConnectionPool.releaseConnection(connection: connection_withDB) 39 | } 40 | } catch { 41 | print("Unable to create connection") 42 | exit(0) 43 | } 44 | 45 | client = MySQLClient(connection: connection_withDB) 46 | 47 | client.execute(query: "DROP TABLE IF EXISTS Cars") 48 | client.execute(query: "CREATE TABLE Cars(Id INT, Name TEXT, Price INT)") 49 | 50 | // use query builder to insert data 51 | var queryBuilder = MySQLQueryBuilder() 52 | .insert(data: ["Id": "1", "Name": "Audi", "Price": "52642"], table: "Cars") 53 | client.execute(builder: queryBuilder) 54 | 55 | queryBuilder = MySQLQueryBuilder() 56 | .insert(data: ["Id": "2", "Name": "Mercedes", "Price": "72341"], table: "Cars") 57 | client.execute(builder: queryBuilder) 58 | 59 | // create query to select data from the database 60 | queryBuilder = MySQLQueryBuilder() 61 | .select(fields: ["Id", "Name", "Price"], table: "Cars") 62 | 63 | var ret = client.execute(builder: queryBuilder) // returns a tuple (MySQLResult, MySQLError) 64 | if let result = ret.0 { 65 | var r = result.nextResult() // get the first result from the result set 66 | if r != nil { 67 | repeat { 68 | for value in r! { 69 | print(value) 70 | } 71 | r = result.nextResult() // get the next result from the result set 72 | } while(r != nil) // loop while there are results 73 | } else { 74 | print("No results") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/MySQL/mocks/MocksMySQLConnectionPool.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MockMySQLConnectionPool is a mock object which can be used in unit tests to replace the real instance in order to test behaviour 4 | public class MockMySQLConnectionPool : MySQLConnectionPoolProtocol { 5 | public static var setPoolSizeCalled = false 6 | public static var setConnectionProviderCalled = false 7 | public static var releaseConnectionCalled = false 8 | 9 | public static var getConnectionCalled = false 10 | public static var getConnectionReturn: MySQLConnectionProtocol? 11 | 12 | public static var connectionProvider:() -> MySQLConnectionProtocol? = { () -> MySQLConnectionProtocol? in 13 | return nil 14 | } 15 | 16 | public static var logger:(message: MySQLConnectionPoolMessage) -> Void = { 17 | (message: MySQLConnectionPoolMessage) -> Void in 18 | } 19 | 20 | public static func setPoolSize(size: Int) { 21 | self.setPoolSizeCalled = true 22 | } 23 | 24 | public static func setLogger(logger: (message: MySQLConnectionPoolMessage) -> Void) { 25 | self.logger = logger 26 | } 27 | 28 | public static func setConnectionProvider(provider: () -> MySQLConnectionProtocol?) { 29 | self.connectionProvider = provider 30 | self.setConnectionProviderCalled = true 31 | } 32 | 33 | public static func getConnection(host: String, user: String, password: String) throws -> MySQLConnectionProtocol? { 34 | self.getConnectionCalled = true 35 | return getConnectionReturn 36 | } 37 | 38 | public static func getConnection(host: String, user: String, password: String, port: Int, database: String) throws -> MySQLConnectionProtocol? { 39 | self.getConnectionCalled = true 40 | return getConnectionReturn 41 | } 42 | 43 | public static func getConnection(host: String, 44 | user: String, 45 | password: String, 46 | port: Int, 47 | database: String, 48 | closure: ((connection: MySQLConnectionProtocol) -> Void)) throws { 49 | self.getConnectionCalled = true 50 | closure(connection: getConnectionReturn!) 51 | } 52 | 53 | public static func releaseConnection(connection: MySQLConnectionProtocol) { 54 | self.releaseConnectionCalled = true 55 | } 56 | 57 | /** 58 | resetMock allows you to reset the state of this instance since it is a static class 59 | */ 60 | public static func resetMock() { 61 | setPoolSizeCalled = false 62 | setConnectionProviderCalled = false 63 | releaseConnectionCalled = false 64 | getConnectionCalled = false 65 | getConnectionReturn = nil 66 | connectionProvider = { () -> MySQLConnectionProtocol? in return nil } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/MySQL/mocks/MockMySQLConnection.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MockMySQLConnection is a mock object which can be used in unit tests to replace the real instance in order to test behaviour 4 | public class MockMySQLConnection : MySQLConnectionProtocol { 5 | public var isConnectedReturn = true 6 | public var isConnectedCalled = false 7 | public var connectError: MySQLError? 8 | 9 | public var connectCalled = false 10 | public var executeCalled = false 11 | public var closeCalled = false 12 | 13 | public var executeStatement = "" 14 | 15 | public var clientInfo:String? = nil 16 | public var clientVersion:UInt = 0 17 | 18 | public var executeReturnResult:CMySQLResult? = nil 19 | public var executeReturnHeaders:[CMySQLField]? = nil 20 | public var executeReturnError:MySQLError? = nil 21 | 22 | public var nextResultReturn:CMySQLRow? = nil 23 | 24 | public var nextResultReturnResult:CMySQLResult? = nil 25 | public var nextResultReturnHeaders:[CMySQLField]? = nil 26 | public var nextResultReturnError:MySQLError? = nil 27 | 28 | private var uuid: Double 29 | 30 | public func equals(otherObject: MySQLConnectionProtocol) -> Bool { 31 | return uuid == (otherObject as! MockMySQLConnection).uuid 32 | } 33 | 34 | public init() { 35 | uuid = NSDate().timeIntervalSince1970 36 | } 37 | 38 | public func isConnected() -> Bool { 39 | isConnectedCalled = true 40 | return isConnectedReturn 41 | } 42 | 43 | public func connect( 44 | host: String, 45 | user: String, 46 | password: String 47 | ) throws { 48 | if connectError != nil { 49 | throw connectError! 50 | } 51 | 52 | connectCalled = true 53 | } 54 | 55 | public func connect( 56 | host: String, 57 | user: String, 58 | password: String, 59 | port: Int 60 | ) throws { 61 | if connectError != nil { 62 | throw connectError! 63 | } 64 | 65 | connectCalled = true 66 | } 67 | 68 | public func connect( 69 | host: String, 70 | user: String, 71 | password: String, 72 | port: Int, 73 | database: String 74 | ) throws { 75 | if connectError != nil { 76 | throw connectError! 77 | } 78 | 79 | connectCalled = true 80 | } 81 | 82 | public func client_info() -> String? { return clientInfo } 83 | 84 | public func client_version() -> UInt { return clientVersion } 85 | 86 | public func execute(query: String) -> (CMySQLResult?, [CMySQLField]?, MySQLError?) { 87 | executeCalled = true 88 | executeStatement = query 89 | return (executeReturnResult, executeReturnHeaders, executeReturnError) 90 | } 91 | 92 | public func nextResult(result: CMySQLResult) -> CMySQLRow? { 93 | return nextResultReturn 94 | } 95 | 96 | public func nextResultSet() -> (CMySQLResult?, [CMySQLField]?, MySQLError?) { 97 | return (nextResultReturnResult, nextResultReturnHeaders, nextResultReturnError) 98 | } 99 | 100 | public func close() { closeCalled = true } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/MySQL/MySQLResultTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import CMySQLClient 4 | 5 | @testable import MySQL 6 | 7 | public class MySQLResultTests: XCTestCase { 8 | var field1 = MYSQL_FIELD() 9 | var field2 = MYSQL_FIELD() 10 | 11 | private func buildFields() -> [CMySQLField] { 12 | field1.name = "myname".getUnsafeMutablePointer() 13 | field1.type = MYSQL_TYPE_STRING 14 | 15 | field2.name = "second".getUnsafeMutablePointer() 16 | field2.type = MYSQL_TYPE_STRING 17 | 18 | var fields = [CMySQLField]() 19 | fields.append(&field1) 20 | fields.append(&field2) 21 | 22 | return fields 23 | } 24 | 25 | public func testParsesHeadersOnInit() { 26 | let connection = MockMySQLConnection() 27 | let cresult = CMySQLResult(bitPattern: 12) 28 | let myresult = MySQLResult( 29 | result: cresult!, 30 | fields: buildFields(), 31 | nextResult:connection.nextResult) 32 | 33 | XCTAssertEqual(2, myresult.fields.count, "Should have returned two headers") 34 | XCTAssertEqual("myname", myresult.fields[0].name, "Field not equal") 35 | XCTAssertEqual("second", myresult.fields[1].name, "Field not equal") 36 | } 37 | 38 | public func testNextResultReturnsNilWhenNoResult() { 39 | let connection = MockMySQLConnection() 40 | let cresult = CMySQLResult(bitPattern: 12) 41 | let myresult = MySQLResult( 42 | result: cresult!, 43 | fields: buildFields(), 44 | nextResult:connection.nextResult) 45 | let next = myresult.nextResult() 46 | 47 | XCTAssertNil(next, "Result should be nil") 48 | } 49 | 50 | public func testNextResultReturnsRow() { 51 | let cRow = CMySQLRow(allocatingCapacity: 2) 52 | 53 | cRow[0] = "myvalue".getUnsafeMutablePointer() 54 | cRow[1] = "myvalue2".getUnsafeMutablePointer() 55 | 56 | let connection = MockMySQLConnection() 57 | connection.nextResultReturn = cRow 58 | 59 | let cresult = CMySQLResult(bitPattern: 12) 60 | let myresult = MySQLResult(result: cresult!, fields: buildFields(), nextResult:connection.nextResult) 61 | let next = myresult.nextResult() 62 | 63 | XCTAssertNotNil(next, "Result should not be nil") 64 | } 65 | 66 | public func testNextResultParsesRow() { 67 | let cRow = CMySQLRow(allocatingCapacity: 2) 68 | 69 | cRow[0] = "myvalue".getUnsafeMutablePointer() 70 | cRow[1] = "myvalue2".getUnsafeMutablePointer() 71 | 72 | let connection = MockMySQLConnection() 73 | connection.nextResultReturn = cRow 74 | 75 | let cresult = CMySQLResult(bitPattern: 12) 76 | let myresult = MySQLResult(result: cresult!, fields: buildFields(), nextResult:connection.nextResult) 77 | let next = myresult.nextResult() 78 | 79 | XCTAssertEqual("myvalue", (next!["myname"] as! String), "Result should not be nil") 80 | } 81 | 82 | } 83 | 84 | extension MySQLResultTests { 85 | static var allTests: [(String, MySQLResultTests -> () throws -> Void)] { 86 | return [ 87 | ("testParsesHeadersOnInit", testParsesHeadersOnInit), 88 | ("testNextResultReturnsNilWhenNoResult", testNextResultReturnsNilWhenNoResult), 89 | ("testNextResultReturnsRow", testNextResultReturnsRow), 90 | ("testNextResultParsesRow", testNextResultParsesRow) 91 | ] 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLClient.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// MySQLClient allows execution of queries and the return of results from MySQL databases. 4 | public class MySQLClient: MySQLClientProtocol{ 5 | 6 | internal let connection: MySQLConnectionProtocol 7 | 8 | /** 9 | Initialises a new MySQLClient with the provided connection. 10 | 11 | - Parameters: 12 | - connection: A valid connection object which implements the MySQLConnectionProtocol 13 | 14 | - Returns: A MySQLClient which can be used to query the database. 15 | 16 | */ 17 | public required init(connection: MySQLConnectionProtocol) { 18 | self.connection = connection 19 | } 20 | 21 | } 22 | 23 | // MARK: MySQLClientProtocol implementation 24 | extension MySQLClient { 25 | /** 26 | Retrieve information for the underlying client library version. 27 | 28 | - Returns: String representing the current client version. 29 | */ 30 | public func info() -> String? { 31 | return connection.client_info() 32 | } 33 | 34 | /** 35 | Retrieve the version for the underlying client library version. 36 | 37 | - Returns: UInt representing the current client version. 38 | */ 39 | public func version() -> UInt { 40 | return connection.client_version() 41 | } 42 | 43 | /** 44 | Execute the given SQL query against the database. 45 | 46 | - Parameters: 47 | - query: valid TSQL statement to execute. 48 | 49 | - Returns: Tuple consiting of an optional MySQLResult and MySQLError. If the query fails then an error object will be returned and MySQLResult will be nil. Upon success MySQLError will be nil however it is still possible for no results to be returned as some queries do not return results. 50 | */ 51 | public func execute(query: String) -> (MySQLResultProtocol?, MySQLError?) { 52 | let result = connection.execute(query: query) 53 | 54 | if (result.0 != nil) { 55 | return (MySQLResult(result:result.0!, fields: result.1!, nextResult: connection.nextResult), result.2) 56 | } 57 | 58 | return (nil, result.2) 59 | } 60 | 61 | /** 62 | Execute the given SQL query against the database. 63 | 64 | - Parameters: 65 | - builder: MySQLQueryBuilder 66 | 67 | - Returns: Tuple consiting of an optional MySQLResult and MySQLError. If the query fails then an error object will be returned and MySQLResult will be nil. Upon success MySQLError will be nil however it is still possible for no results to be returned as some queries do not return results. 68 | 69 | ``` 70 | var builder = MySQLQueryBuilder() 71 | .select(["abc"], table: "MyTable") 72 | .wheres("WHERE abc=?", "1") 73 | 74 | var (result, error) = mySQLClient.execute(builder) 75 | ``` 76 | */ 77 | public func execute(builder: MySQLQueryBuilder) -> (MySQLResultProtocol?, MySQLError?) { 78 | let statement = builder.build() 79 | 80 | return execute(query: statement) 81 | } 82 | 83 | /** 84 | Return the next result set after executing a query, this might be used when you 85 | specify a multi statement query. 86 | 87 | - Returns: Tuple consiting of an optional CMySQLResult, array of CMySQLField and MySQLError. If the query fails then an error object will be returned and CMySQLResult and [CMySQLField] will be nil. Upon success MySQLError will be nil however it is still possible for no results to be returned as some queries do not return results. 88 | 89 | ``` 90 | var table1Builder = MySQLQueryBuilder() 91 | .select(["abc"], table: "MyTable") 92 | .wheres("WHERE abc=?", "1") 93 | 94 | var table2Builder = = MySQLQueryBuilder() 95 | .select(["abc"], table: "MyOtherTable") 96 | .wheres("WHERE abc=?", "1") 97 | 98 | table1Builder.join(table2Builder) 99 | 100 | var (result, error) = mySQLClient.execute(table1Builder) 101 | var row = result.nextResult() // use rows from table1 102 | 103 | result = mySQLClient.nextResult() 104 | row = result.nextResult() // use rows from table2 105 | ``` 106 | */ 107 | public func nextResultSet() -> (MySQLResultProtocol?, MySQLError?) { 108 | let result = connection.nextResultSet() 109 | 110 | if (result.0 != nil) { 111 | return (MySQLResult(result:result.0!, fields: result.1!, nextResult: connection.nextResult), result.2) 112 | } 113 | 114 | return (nil, result.2) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Tests/MySQL/MySQLClientTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import MySQL 5 | 6 | public class MySQLClientTests: XCTestCase { 7 | var mockConnection:MockMySQLConnection? 8 | var client: MySQLClient? 9 | 10 | public override func setUp() { 11 | mockConnection = MockMySQLConnection() 12 | client = MySQLClient(connection: mockConnection!) 13 | } 14 | 15 | public func testClientInfoReturnsClientInfo() { 16 | mockConnection!.clientInfo = "testinfo" 17 | 18 | let info = client!.info() 19 | XCTAssertEqual(mockConnection!.clientInfo, info, "Correct client info should have been returned") 20 | } 21 | 22 | public func testClientVersionReturnsClientVersion() { 23 | mockConnection!.clientVersion = 100 24 | 25 | let version = client!.version() 26 | XCTAssertEqual(mockConnection!.clientVersion, version, "Correct client version should have been returned") 27 | } 28 | 29 | //TODO: implement these tests correctly this is just a placeholder 30 | public func testClientExecutesQuery() { 31 | client!.execute(query: "something") 32 | 33 | XCTAssertEqual(mockConnection!.executeStatement, "something", "Query sent to connection not correct") 34 | XCTAssertTrue(mockConnection!.executeCalled, "Query should have been executed") 35 | } 36 | 37 | public func testClientExecutesQueryWithBuilder() { 38 | let builder = MySQLQueryBuilder() 39 | .select(statement: "SELECT * FROM") 40 | .wheres(statement: "WHERE abc=?", parameters: "bcd") 41 | 42 | client!.execute(builder: builder) 43 | 44 | XCTAssertEqual(mockConnection!.executeStatement, "SELECT * FROM WHERE abc='bcd';", "Query sent to connection not correct") 45 | XCTAssertTrue(mockConnection!.executeCalled, "Query should have been executed") 46 | } 47 | 48 | public func testClientQueryReturnsMySQLResultWhenResultPresent() { 49 | mockConnection!.executeReturnResult = CMySQLResult(bitPattern: 12) 50 | mockConnection!.executeReturnHeaders = [CMySQLField]() 51 | 52 | let result = client!.execute(query: "something") 53 | 54 | XCTAssertNotNil(result.0, "Query should have returned results") 55 | } 56 | 57 | public func testClientQueryDoesNotReturnMySQLResultWhenNoResult() { 58 | let result = client!.execute(query: "something") 59 | 60 | XCTAssertNil(result.0, "Query should have been executed") 61 | } 62 | 63 | public func testClientQueryReturnsErrorWhenError() { 64 | mockConnection!.executeReturnError = MySQLError.UnableToExecuteQuery(message: "boom") 65 | 66 | let result = client!.execute(query: "something") 67 | 68 | XCTAssertNotNil(result.1, "Query should have returned an error") 69 | } 70 | 71 | public func testClientNextResultSetReturnsMySQLResultWhenResultPresent() { 72 | mockConnection!.nextResultReturnResult = CMySQLResult(bitPattern: 12) 73 | mockConnection!.nextResultReturnHeaders = [CMySQLField]() 74 | 75 | let result = client!.nextResultSet() 76 | 77 | XCTAssertNotNil(result.0, "Query should have returned results") 78 | } 79 | 80 | public func testClientNextResultSetReturnsNilWhenNoResultPresent() { 81 | mockConnection!.nextResultReturnError = MySQLError.NoMoreResults 82 | 83 | let result = client!.nextResultSet() 84 | 85 | XCTAssertNil(result.0, "Query should have returned results") 86 | XCTAssertNotNil(result.1, "Query should have returned error") 87 | } 88 | } 89 | 90 | extension MySQLClientTests { 91 | static var allTests: [(String, MySQLClientTests -> () throws -> Void)] { 92 | return [ 93 | ("testClientInfoReturnsClientInfo", testClientInfoReturnsClientInfo), 94 | ("testClientVersionReturnsClientVersion", testClientVersionReturnsClientVersion), 95 | ("testClientExecutesQuery", testClientExecutesQuery), 96 | ("testClientExecutesQueryWithBuilder", testClientExecutesQueryWithBuilder), 97 | ("testClientQueryReturnsMySQLResultWhenResultPresent", testClientQueryReturnsMySQLResultWhenResultPresent), 98 | ("testClientQueryDoesNotReturnMySQLResultWhenNoResult", testClientQueryDoesNotReturnMySQLResultWhenNoResult), 99 | ("testClientQueryReturnsErrorWhenError", testClientQueryReturnsErrorWhenError), 100 | ("testClientNextResultSetReturnsMySQLResultWhenResultPresent", testClientNextResultSetReturnsMySQLResultWhenResultPresent), 101 | ("testClientNextResultSetReturnsNilWhenNoResultPresent", testClientNextResultSetReturnsNilWhenNoResultPresent) 102 | ] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLConnectionPoolProtocol.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol MySQLConnectionPoolProtocol { 4 | 5 | /** 6 | setConnectionProvider requires you to provide a closure which returns a new object which implements 7 | the MySQLConnectionProtocol. Everytime a new connection is required this block is called. 8 | 9 | - Parameters: 10 | - provider: closure returning an object implementing MySQLConnectionProtocol 11 | 12 | ``` 13 | MySQLConnectionPoolProtocol.setConnectionProvider() { 14 | return MySQLConnection() 15 | } 16 | ``` 17 | **/ 18 | static func setConnectionProvider(provider: () -> MySQLConnectionProtocol?) 19 | 20 | /** 21 | setPoolSize sets the size for the connection pool, default is 20 22 | 23 | - Parameters: 24 | - size: new size of the pool 25 | */ 26 | static func setPoolSize(size: Int) 27 | 28 | /** 29 | setLogger sets the function that will be called when an event occurs with the connectionPool, 30 | you can use this method can be used to setup logging to be used for debugging or metrics. 31 | 32 | ``` 33 | MySQLConnectionPool.setPoolSize { 34 | (message: MySQLConnectionPoolMessage) in 35 | print(message) 36 | } 37 | ``` 38 | */ 39 | static func setLogger(logger: (message: MySQLConnectionPoolMessage) -> Void) 40 | 41 | /** 42 | getConnection returns a connection from the pool, if a connection is unsuccessful then getConnection throws a MySQLError, 43 | if the pool has no available connections getConnection will block util either a connection is free or a timeout occurs. 44 | 45 | - Parameters: 46 | - host: The host name or ip address of the database. 47 | - user: The username to use for the connection. 48 | - password: The password to use for the connection. 49 | 50 | - Returns: An object conforming to the MySQLConnectionProtocol. 51 | */ 52 | static func getConnection(host: String, user: String, password: String) throws -> MySQLConnectionProtocol? 53 | 54 | /** 55 | getConnection returns a connection from the pool, if a connection is unsuccessful then getConnection throws a MySQLError, 56 | if the pool has no available connections getConnection will block util either a connection is free or a timeout occurs. 57 | 58 | - Parameters: 59 | - host: The host name or ip address of the database 60 | - user: The username to use for the connection 61 | - password: The password to use for the connection 62 | - port: The the port to connect to 63 | - database: The database to connect to 64 | 65 | - Returns: An object implementing the MySQLConnectionProtocol. 66 | */ 67 | static func getConnection(host: String, user: String, password: String, port: Int, database: String) throws -> MySQLConnectionProtocol? 68 | 69 | /** 70 | getConnection returns a connection from the pool, if a connection is unsuccessful then getConnection throws a MySQLError, 71 | if the pool has no available connections getConnection will block util either a connection is free or a timeout occurs. 72 | 73 | By passing the optional closure once the code has executed within the block the connection is automatically released 74 | back to the pool saving the requirement to manually call releaseConnection. 75 | 76 | - Parameters: 77 | - host: The host name or ip address of the database 78 | - user: The username to use for the connection 79 | - password: The password to use for the connection 80 | - port: The the port to connect to 81 | - database: The database to connect to 82 | - closure: Code that will be executed before connection is released back to the pool 83 | 84 | - Returns: An object implementing the MySQLConnectionProtocol. 85 | 86 | ``` 87 | MySQLConnectionPoolProtocol.getConnection(host: "127.0.0.1", 88 | user: "root", 89 | password: "mypassword", 90 | port: 3306, 91 | database: "mydatabase") { 92 | (connection: MySQLConnectionProtocol) in 93 | let result = connection.execute("SELECT * FROM TABLE") 94 | ... 95 | } 96 | ``` 97 | */ 98 | static func getConnection(host: String, 99 | user: String, 100 | password: String, 101 | port: Int, 102 | database: String, 103 | closure: ((connection: MySQLConnectionProtocol) -> Void)) throws 104 | 105 | /** 106 | releaseConnection returns a connection to the pool. 107 | 108 | - Parameters: 109 | - connection: Connection to be returned to the pool 110 | */ 111 | static func releaseConnection(connection: MySQLConnectionProtocol) 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-mysql 2 | 3 | [![Join the chat at https://gitter.im/notonthehighstreet/swift-mysql](https://badges.gitter.im/notonthehighstreet/swift-mysql.svg)](https://gitter.im/notonthehighstreet/swift-mysql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | swift-mysql is a MySQL client implementation for Swift 3, it wraps the libmysql library and provides many convenience functions such as connection pooling and result sets as native types. 5 | 6 | ## Build instructions for Mac 7 | ### Install Swiftenv 8 | https://github.com/kylef/swiftenv 9 | 10 | ### Install 3.0 Alpha 11 | ``` 12 | $ swiftenv install DEVELOPMENT-SNAPSHOT-2016-04-25-a 13 | $ swiftenv rehash 14 | ``` 15 | 16 | ### Install C dependencies 17 | ``` 18 | $ brew install mysql // for the client, needed to build the mysql module 19 | ``` 20 | 21 | ### Build and run tests 22 | ``` 23 | $ make test 24 | ``` 25 | 26 | ## Build instructions using Docker 27 | ### Run the docker container for building 28 | ``` 29 | $ docker run -i -t -v $(pwd):/src --name swiftmysql -w /src ibmcom/kitura-ubuntu:latest /bin/bash 30 | $ apt-get install libmysqlclient-dev 31 | ``` 32 | 33 | ### Build and run tests 34 | ``` 35 | $ make test 36 | ``` 37 | 38 | ### Usage 39 | Set the connection provider for the connection pool, this closure should return a new instance as internally the connection pool manages the connections. 40 | ```swift 41 | MySQLConnectionPool.setConnectionProvider() { 42 | return MySQL.MySQLConnection() 43 | } 44 | ``` 45 | 46 | To get a connection from the pool call get connection with the parameters for your connection, at present pooling is on the todo list and this call will return a new connection and attempt to connect to the database with the given details. When a connection fails a MySQLError will be thrown. 47 | ```swift 48 | do { 49 | connection = try MySQLConnectionPool.getConnection("192.168.99.100", user: "root", password: "my-secret-pw", database: "mydatabase")! 50 | 51 | // always release the connection back to the pool once you have finished with it, 52 | // not releasing connections back to the pool will cause a deadlock when all connections are in use. 53 | defer { 54 | MySQLConnectionPool.releaseConnection(connection) 55 | } 56 | } catch { 57 | print("Unable to create connection") 58 | exit(0) 59 | } 60 | ``` 61 | 62 | As an alternative approach to manually calling release connection you can use the getConnection method which takes a closure. Once the code inside the closure has executed then the connection is automatically released back to the pool. 63 | ```swift 64 | do { 65 | try MySQLConnectionPool.getConnection("192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test") { 66 | (connection: MySQLConnectionProtocol) in 67 | let client = MySQLClient(connection: connection) 68 | let result = client.execute("SELECT * FROM MYTABLE") 69 | ... 70 | } 71 | } catch { 72 | print("Unable to create connection") 73 | exit(0) 74 | } 75 | ``` 76 | 77 | To create a new client pass the reference to the connection obtained from the pool, at present you need to call connection.close once done with the connection, this will be managed automatically in a later releases. 78 | ```swift 79 | let client = MySQLClient(connection: connection) 80 | ``` 81 | 82 | To execute a query 83 | ```swift 84 | var result = client.execute("SELECT * FROM Cars") 85 | 86 | // result.1 contains an error, when present the query has failed. 87 | if result.1 != nil { 88 | print("Error executing query") 89 | } else { 90 | // if MySQLResult is nil then no rows have been returned from the query. 91 | if let result = ret.0 { 92 | var r = result.nextResult() // get the first result from the set 93 | if r != nil { 94 | repeat { 95 | for value in r! { 96 | print(value) // print the returned dictionary ("Name", "Audi"), ("Price", "52642"), ("Id", "1") 97 | } 98 | r = result.nextResult() // get the next result in the set, returns nil when no more records are available. 99 | } while(r != nil) 100 | } else { 101 | print("No results") 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | ## QueryBuilder 108 | When executing queries you can use the MySQLQueryBuilder class to generate a safe query for you. This will ensure that all parameters are escaped to avoid SQL injection attacks. 109 | 110 | ### Simple select 111 | ```swift 112 | var queryBuilder = MySQLQueryBuilder() 113 | .select(["Id", "Name"], table: "MyTable") 114 | 115 | var result = client.execute(queryBuilder) 116 | ``` 117 | 118 | ### Parametrised where clause 119 | ```swift 120 | var queryBuilder = MySQLQueryBuilder() 121 | .select(["Id", "Name"], table: "MyTable") 122 | .wheres("WHERE Id=?", 2) 123 | 124 | var result = client.execute(queryBuilder) 125 | ``` 126 | 127 | ## Example 128 | Please see the example program in /Sources/Example for further usage. 129 | 130 | 131 | ## Run MySQL in docker 132 | docker run --rm -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 mysql:latest 133 | 134 | ## Roadmap: 135 | - ~~Complete implementation of the connection pool.~~ 136 | - ~~Complete implementation for the MySQLField to give parity to C library.~~ 137 | - Implement type casting for MySQLRow to match field type. - Complete for numbers and strings, 138 | - Implement binary streaming for blob types. 139 | -------------------------------------------------------------------------------- /Tests/MySQL/MySQLRowParserTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import CMySQLClient 4 | 5 | @testable import MySQL 6 | 7 | public class MySQLRowParserTests: XCTestCase { 8 | 9 | let parser = MySQLRowParser() 10 | let cRow = CMySQLRow(allocatingCapacity: 1) 11 | var headers = [MySQLField]() 12 | 13 | public override func setUp() { 14 | var header1 = MySQLField(name: "tester", type: MySQLFieldType.String) 15 | header1.name = "header1" 16 | 17 | headers.append(header1) 18 | } 19 | 20 | public func testParsesRowWithStringValue() { 21 | cRow[0] = "myvalue".getUnsafeMutablePointer() 22 | headers[0].type = MySQLFieldType.String 23 | 24 | let row = parser.parse(row: cRow, headers: headers) 25 | 26 | XCTAssertEqual("myvalue", (row["header1"] as! String)) 27 | } 28 | 29 | public func testParsesFieldWithNilValueSetsNil() { 30 | cRow[0] = nil 31 | headers[0].type = MySQLFieldType.String 32 | 33 | let row = parser.parse(row: cRow, headers: headers) 34 | 35 | XCTAssertNil(row["header1"]) 36 | } 37 | 38 | public func testParsesFieldWithStringValueSetsTypeString() { 39 | cRow[0] = "myvalue".getUnsafeMutablePointer() 40 | headers[0].type = MySQLFieldType.String 41 | 42 | let row = parser.parse(row: cRow, headers: headers) 43 | 44 | XCTAssertTrue(row["header1"] is String, "Type should be String") 45 | } 46 | 47 | public func testParsesFieldWithVarStringValueSetsTypeString() { 48 | cRow[0] = "myvalue".getUnsafeMutablePointer() 49 | headers[0].type = MySQLFieldType.VarString 50 | 51 | let row = parser.parse(row: cRow, headers: headers) 52 | 53 | XCTAssertTrue(row["header1"] is String, "Type should be String") 54 | } 55 | 56 | public func testParsesFieldWithTinyValueSetsTypeInt() { 57 | cRow[0] = "1".getUnsafeMutablePointer() 58 | headers[0].type = MySQLFieldType.Tiny 59 | 60 | let row = parser.parse(row: cRow, headers: headers) 61 | 62 | XCTAssertEqual(1, row["header1"] as? Int) 63 | XCTAssertTrue(row["header1"] is Int, "Type should be Int") 64 | } 65 | 66 | public func testParsesFieldWithShortValueSetsTypeInt() { 67 | cRow[0] = "1".getUnsafeMutablePointer() 68 | headers[0].type = MySQLFieldType.Short 69 | 70 | let row = parser.parse(row: cRow, headers: headers) 71 | 72 | XCTAssertEqual(1, row["header1"] as? Int) 73 | XCTAssertTrue(row["header1"] is Int, "Type should be Int") 74 | } 75 | 76 | public func testParsesFieldWithLongValueSetsTypeInt() { 77 | cRow[0] = "1".getUnsafeMutablePointer() 78 | headers[0].type = MySQLFieldType.Long 79 | 80 | let row = parser.parse(row: cRow, headers: headers) 81 | 82 | XCTAssertEqual(1, row["header1"] as? Int) 83 | XCTAssertTrue(row["header1"] is Int, "Type should be Int") 84 | } 85 | 86 | public func testParsesFieldWithInt24ValueSetsTypeInt() { 87 | cRow[0] = "1".getUnsafeMutablePointer() 88 | headers[0].type = MySQLFieldType.Int24 89 | 90 | let row = parser.parse(row: cRow, headers: headers) 91 | 92 | XCTAssertEqual(1, row["header1"] as? Int) 93 | XCTAssertTrue(row["header1"] is Int, "Type should be Int") 94 | } 95 | 96 | public func testParsesFieldWithLongLongValueSetsTypeInt() { 97 | cRow[0] = "1".getUnsafeMutablePointer() 98 | headers[0].type = MySQLFieldType.LongLong 99 | 100 | let row = parser.parse(row: cRow, headers: headers) 101 | 102 | XCTAssertEqual(1, row["header1"] as? Int) 103 | XCTAssertTrue(row["header1"] is Int, "Type should be Int") 104 | } 105 | 106 | public func testParsesFieldWithDecimalValueSetsTypeDouble() { 107 | cRow[0] = "1".getUnsafeMutablePointer() 108 | headers[0].type = MySQLFieldType.Decimal 109 | 110 | let row = parser.parse(row: cRow, headers: headers) 111 | 112 | XCTAssertEqual(1, row["header1"] as? Double) 113 | XCTAssertTrue(row["header1"] is Double, "Type should be Int") 114 | } 115 | 116 | public func testParsesFieldWithNewDecimalValueSetsTypeDouble() { 117 | cRow[0] = "1".getUnsafeMutablePointer() 118 | headers[0].type = MySQLFieldType.NewDecimal 119 | 120 | let row = parser.parse(row: cRow, headers: headers) 121 | 122 | XCTAssertEqual(1, row["header1"] as? Double) 123 | XCTAssertTrue(row["header1"] is Double, "Type should be Int") 124 | } 125 | 126 | public func testParsesFieldWithFloatValueSetsTypeFloat() { 127 | cRow[0] = "1".getUnsafeMutablePointer() 128 | headers[0].type = MySQLFieldType.Float 129 | 130 | let row = parser.parse(row: cRow, headers: headers) 131 | 132 | XCTAssertEqual(1, row["header1"] as? Float) 133 | XCTAssertTrue(row["header1"] is Float, "Type should be Int") 134 | } 135 | 136 | public func testParsesFieldWithDoubleValueSetsTypeDouble() { 137 | cRow[0] = "1".getUnsafeMutablePointer() 138 | headers[0].type = MySQLFieldType.Double 139 | 140 | let row = parser.parse(row: cRow, headers: headers) 141 | 142 | XCTAssertEqual(1, row["header1"] as? Double) 143 | XCTAssertTrue(row["header1"] is Double, "Type should be Int") 144 | } 145 | } 146 | 147 | extension MySQLRowParserTests { 148 | static var allTests: [(String, MySQLRowParserTests -> () throws -> Void)] { 149 | return [ 150 | ("testParsesRowWithStringValue", testParsesRowWithStringValue), 151 | ("testParsesFieldWithNilValueSetsNil", testParsesFieldWithNilValueSetsNil), 152 | ("testParsesFieldWithStringValueSetsTypeString", testParsesFieldWithStringValueSetsTypeString), 153 | ("testParsesFieldWithVarStringValueSetsTypeString", testParsesFieldWithVarStringValueSetsTypeString), 154 | ("testParsesFieldWithTinyValueSetsTypeInt", testParsesFieldWithTinyValueSetsTypeInt), 155 | ("testParsesFieldWithShortValueSetsTypeInt", testParsesFieldWithShortValueSetsTypeInt), 156 | ("testParsesFieldWithLongValueSetsTypeInt", testParsesFieldWithLongValueSetsTypeInt), 157 | ("testParsesFieldWithInt24ValueSetsTypeInt", testParsesFieldWithInt24ValueSetsTypeInt), 158 | ("testParsesFieldWithLongLongValueSetsTypeInt", testParsesFieldWithLongLongValueSetsTypeInt), 159 | ("testParsesFieldWithDecimalValueSetsTypeDouble", testParsesFieldWithDecimalValueSetsTypeDouble), 160 | ("testParsesFieldWithNewDecimalValueSetsTypeDouble", testParsesFieldWithNewDecimalValueSetsTypeDouble), 161 | ("testParsesFieldWithFloatValueSetsTypeFloat", testParsesFieldWithFloatValueSetsTypeFloat), 162 | ("testParsesFieldWithDoubleValueSetsTypeDouble", testParsesFieldWithDoubleValueSetsTypeDouble) 163 | ] 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLQueryBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func ==(lhs: MySQLQueryBuilder, rhs: MySQLQueryBuilder) -> Bool { 4 | return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) 5 | } 6 | 7 | public class MySQLQueryBuilder: Equatable { 8 | var selectStatement: String? 9 | var insertStatement:String? 10 | var updateStatement:String? 11 | var whereStatement:String? 12 | 13 | var joinedStatements = [String]() 14 | 15 | public init() {} 16 | 17 | /** 18 | select sets the select statement part of the query 19 | 20 | - Parameters: 21 | - statement: select statement 22 | 23 | - Returns: returns self 24 | 25 | ``` 26 | var builder = MySQLQueryBuilder() 27 | .select("SELECT 'abc', 'cde' FROM myTable") 28 | ``` 29 | */ 30 | public func select(statement: String) -> MySQLQueryBuilder { 31 | selectStatement = statement 32 | return self 33 | } 34 | 35 | /** 36 | select sets the select statement part of the query 37 | 38 | - Parameters: 39 | - fields: array of fields to return 40 | - table: name of the table to select from 41 | 42 | - Returns: returns self 43 | 44 | ``` 45 | var builder = MySQLQueryBuilder() 46 | .select(["abc", "cde"], table: "myTable") 47 | ``` 48 | */ 49 | public func select(fields: [String], table: String) -> MySQLQueryBuilder { 50 | selectStatement = createSelectStatement(fields: fields, table: table) 51 | return self 52 | } 53 | 54 | /** 55 | insert sets the insert statement part of the query 56 | 57 | - Parameters: 58 | - data: dictionary containing the data to be inserted 59 | - table: table to insert data into 60 | 61 | - Returns: returns self 62 | 63 | ``` 64 | var builder = MySQLQueryBuilder() 65 | .insert(["abc": "cde"], table: "myTable") 66 | ``` 67 | */ 68 | public func insert(data: MySQLRow, table: String) -> MySQLQueryBuilder { 69 | insertStatement = createInsertStatement(data: data, table: table) 70 | return self 71 | } 72 | 73 | /** 74 | update sets the update statement part of the query 75 | 76 | - Parameters: 77 | - data: dictionary containing the data to be inserted 78 | - table: table to insert data into 79 | 80 | - Returns: returns self 81 | 82 | ``` 83 | var builder = MySQLQueryBuilder() 84 | .update(["abc": "cde"], table: "myTable") 85 | ``` 86 | */ 87 | public func update(data: MySQLRow, table: String) -> MySQLQueryBuilder { 88 | updateStatement = createUpdateStatement(data: data, table: table) 89 | 90 | return self 91 | } 92 | 93 | /** 94 | wheres sets the where statement part of the query, escapting the parameters 95 | to secure against SQL injection 96 | 97 | - Parameters: 98 | - statement: where clause to filter data by 99 | - parameters: list of parameters corresponding to the statement 100 | 101 | - Returns: returns self 102 | 103 | ``` 104 | var builder = MySQLQueryBuilder() 105 | .select("SELECT * FROM MyTABLE") 106 | .wheres("WHERE abc = ? and bcd = ?", abcValue, bcdValue) 107 | ``` 108 | */ 109 | public func wheres(statement: String, parameters: String...) -> MySQLQueryBuilder { 110 | var i = 0 111 | var w = "" 112 | 113 | for char in statement.characters { 114 | if char == "?" { 115 | w += "'\(parameters[i])'" 116 | i += 1 117 | } else { 118 | w += String(char) 119 | } 120 | } 121 | 122 | whereStatement = w 123 | return self 124 | } 125 | 126 | /** 127 | join concatenates the output of one or more MySQLQueryBuilders to create a multi statement query 128 | 129 | - Parameters: 130 | - builder: MySQLQueryBuilder whos output which will be concatenated to this one 131 | 132 | - Returns: returns self 133 | 134 | ``` 135 | var builder = MySQLQueryBuilder() 136 | .insert(["abc": "cde"], table: "myTable") 137 | 138 | var builder2 = MySQLQueryBuilder() 139 | .insert(["def": "ghi"], table: "myTable") 140 | 141 | let query = builder.join(builder2).build() 142 | // query: INSERT INTO myTable (abc) VALUES ('cde'); INSERT INTO myTable SET def='ghi'; 143 | ``` 144 | */ 145 | public func join(builder: MySQLQueryBuilder) -> MySQLQueryBuilder { 146 | joinedStatements.append(builder.build()) 147 | 148 | return self 149 | } 150 | 151 | /** 152 | build compiles all data and returns the SQL statement for execution 153 | 154 | - Returns: SQL Statement 155 | */ 156 | public func build() -> String { 157 | var query = "" 158 | 159 | if selectStatement != nil { 160 | query += "\(selectStatement!) " 161 | } 162 | 163 | if insertStatement != nil { 164 | query += insertStatement! 165 | } 166 | 167 | if updateStatement != nil { 168 | query += "\(updateStatement!) " 169 | } 170 | 171 | if whereStatement != nil { 172 | query += whereStatement! 173 | } 174 | 175 | query = query.trimChar(character: " ") 176 | query = query + ";" 177 | 178 | for statement in joinedStatements { 179 | query = query + " " + statement 180 | } 181 | 182 | return query 183 | } 184 | 185 | private func createSelectStatement(fields: [String], table: String) -> String { 186 | var statement = "SELECT " 187 | 188 | for field in fields { 189 | statement += "\(field), " 190 | } 191 | statement = statement.trimChar(character: " ") 192 | statement = statement.trimChar(character: ",") 193 | statement += " FROM \(table)" 194 | 195 | return statement 196 | } 197 | 198 | private func createInsertStatement(data: MySQLRow, table: String) -> String { 199 | var statement = "INSERT INTO \(table) (" 200 | 201 | for (key, _) in data { 202 | statement += "\(key), " 203 | } 204 | statement = statement.trimChar(character: " ") 205 | statement = statement.trimChar(character: ",") 206 | 207 | statement += ") VALUES (" 208 | 209 | for (_, value) in data { 210 | statement += "'\(value)', " 211 | } 212 | statement = statement.trimChar(character: " ") 213 | statement = statement.trimChar(character: ",") 214 | 215 | statement += ")" 216 | 217 | return statement 218 | } 219 | 220 | private func createUpdateStatement(data: MySQLRow, table: String) -> String { 221 | var statement = "UPDATE \(table) SET " 222 | 223 | for (key, value) in data { 224 | statement += "\(key)='\(value)', " 225 | } 226 | statement = statement.trimChar(character: " ") 227 | statement = statement.trimChar(character: ",") 228 | 229 | return statement 230 | } 231 | } 232 | 233 | extension String { 234 | public func trimChar(character: Character) -> String { 235 | if self[self.endIndex.predecessor()] == character { 236 | var chars = Array(self.characters) 237 | chars.removeLast() 238 | return String(chars) 239 | } else { 240 | return self 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Tests/MySQL/MySQLQueryBuilderTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | @testable import MySQL 5 | 6 | public class MySQLQueryBuilderTests : XCTestCase { 7 | public func testInsertReturnsSelf() { 8 | let builder = MySQLQueryBuilder() 9 | let ret = builder.insert(data: MySQLRow(), table: "") 10 | 11 | XCTAssertEqual(builder, ret, "Should have returned self") 12 | } 13 | 14 | public func testUpdateReturnsSelf() { 15 | let builder = MySQLQueryBuilder() 16 | let ret = builder.update(data: MySQLRow(), table: "") 17 | 18 | XCTAssertEqual(builder, ret, "Should have returned self") 19 | } 20 | 21 | public func testSelectReturnsSelf() { 22 | let builder = MySQLQueryBuilder() 23 | let ret = builder.select(statement: "") 24 | 25 | XCTAssertEqual(builder, ret, "Should have returned self") 26 | } 27 | 28 | public func testSelectWithFieldsReturnsSelf() { 29 | let builder = MySQLQueryBuilder() 30 | let ret = builder.select(fields: [""], table: "") 31 | 32 | XCTAssertEqual(builder, ret, "Should have returned self") 33 | } 34 | 35 | public func testWheresReturnsSelf() { 36 | let builder = MySQLQueryBuilder() 37 | let ret = builder.wheres(statement: "something = ?", parameters: "value") 38 | 39 | XCTAssertEqual(builder, ret, "Should have returned self") 40 | } 41 | 42 | public func testSelectReturnsValidQuery() { 43 | let builder = MySQLQueryBuilder() 44 | let statement = builder 45 | .select(statement: "SELECT * FROM TABLE") 46 | .build() 47 | 48 | XCTAssertEqual("SELECT * FROM TABLE;", statement, "Returned invalid select statement") 49 | } 50 | 51 | public func testSelectWithArrayReturnsValidQuery() { 52 | let builder = MySQLQueryBuilder() 53 | let statement = builder 54 | .select(fields: ["Field1", "Field2"], table: "MyTABLE") 55 | .build() 56 | 57 | XCTAssertEqual("SELECT Field1, Field2 FROM MyTABLE;", statement, "Returned invalid select statement") 58 | } 59 | 60 | public func testInsertGeneratesValidQuery() { 61 | var data = MySQLRow() 62 | data["abc"] = "bcd" 63 | data["bcd"] = "efg" 64 | 65 | let query = MySQLQueryBuilder() 66 | .insert(data: data, table: "MyTable") 67 | .build() 68 | 69 | XCTAssertEqual("INSERT INTO MyTable (abc, bcd) VALUES ('bcd', 'efg');", query, "Should have returned valid query") 70 | } 71 | 72 | public func testUpdateGeneratesValidQuery() { 73 | var data = MySQLRow() 74 | data["abc"] = "abc" 75 | data["bcd"] = "bcd" 76 | 77 | let query = MySQLQueryBuilder() 78 | .update(data: data, table: "MyTable") 79 | .build() 80 | 81 | XCTAssertEqual("UPDATE MyTable SET abc='abc', bcd='bcd';", query, "Should have returned valid query") 82 | } 83 | 84 | public func testWheresGeneratesValidQuery() { 85 | let query = MySQLQueryBuilder() 86 | .wheres(statement: "WHERE param1=? and param2=?", parameters: "abc", "bcd") 87 | .build() 88 | 89 | XCTAssertEqual("WHERE param1='abc' and param2='bcd';", query, "Should have returned valid query") 90 | } 91 | 92 | public func testSelectWithWheresGeneratesValidQuery() { 93 | let query = MySQLQueryBuilder() 94 | .select(statement: "SELECT * FROM TABLE") 95 | .wheres(statement: "WHERE param1=? and param2=?", parameters: "abc", "bcd") 96 | .build() 97 | 98 | XCTAssertEqual("SELECT * FROM TABLE WHERE param1='abc' and param2='bcd';", query, "Should have returned valid query") 99 | } 100 | 101 | public func testUpdateWithWheresGeneratesValidQuery() { 102 | let query = MySQLQueryBuilder() 103 | .update(data: ["abc": "bcd"], table: "MyTable") 104 | .wheres(statement: "WHERE param1=? and param2=?", parameters: "abc", "bcd") 105 | .build() 106 | 107 | XCTAssertEqual("UPDATE MyTable SET abc='bcd' WHERE param1='abc' and param2='bcd';", query, "Should have returned valid query") 108 | } 109 | 110 | public func testJoinWithOneJoinConcatenatesQuery() { 111 | let builder = MySQLQueryBuilder() 112 | .insert(data: ["Field1": "Field2"], table: "MyTABLE") 113 | 114 | let builder2 = MySQLQueryBuilder() 115 | .select(statement: "SELECT * FROM TABLE") 116 | .wheres(statement: "WHERE param1=? and param2=?", parameters: "abc", "bcd") 117 | 118 | let query = builder.join(builder: builder2).build() 119 | 120 | XCTAssertEqual("INSERT INTO MyTABLE (Field1) VALUES ('Field2'); SELECT * FROM TABLE WHERE param1='abc' and param2='bcd';", 121 | query, "Should have returned valid query") 122 | } 123 | 124 | public func testJoinWithTwoJoinsConcatenatesQuery() { 125 | let builder = MySQLQueryBuilder() 126 | .insert(data: ["Field1": "Field2"], table: "MyTABLE") 127 | 128 | let builder2 = MySQLQueryBuilder() 129 | .select(statement: "SELECT * FROM TABLE") 130 | .wheres(statement: "WHERE param1=? and param2=?", parameters: "abc", "bcd") 131 | 132 | let builder3 = MySQLQueryBuilder() 133 | .select(statement: "SELECT * FROM TABLE2") 134 | .wheres(statement: "WHERE param1=? and param2=?", parameters: "abc", "bcd") 135 | 136 | let query = builder.join(builder: builder2).join(builder: builder3).build() 137 | 138 | XCTAssertEqual("INSERT INTO MyTABLE (Field1) VALUES ('Field2'); SELECT * FROM TABLE WHERE param1='abc' and param2='bcd'; SELECT * FROM TABLE2 WHERE param1='abc' and param2='bcd';", 139 | query, "Should have returned valid query") 140 | } 141 | 142 | public func testTrimCharTrimsWhenStatementEndsInAComma() { 143 | let statement = "INSERT BLAH ('dfdf'," 144 | 145 | XCTAssertEqual("INSERT BLAH ('dfdf'", statement.trimChar(character: ","), "Should have trimmed comma from statement") 146 | } 147 | 148 | public func testTrimCharDoesNothingWhenStatementDoesNotEndInAComma() { 149 | let statement = "INSERT BLAH ('dfdf'" 150 | 151 | XCTAssertEqual("INSERT BLAH ('dfdf'", statement.trimChar(character: ","), "Should have trimmed comma from statement") 152 | } 153 | } 154 | 155 | extension MySQLQueryBuilderTests { 156 | static var allTests: [(String, MySQLQueryBuilderTests -> () throws -> Void)] { 157 | return [ 158 | ("testInsertReturnsSelf", testInsertReturnsSelf), 159 | ("testUpdateReturnsSelf", testUpdateReturnsSelf), 160 | ("testSelectReturnsSelf", testSelectReturnsSelf), 161 | ("testSelectWithFieldsReturnsSelf", testSelectWithFieldsReturnsSelf), 162 | ("testWheresReturnsSelf", testWheresReturnsSelf), 163 | ("testSelectReturnsValidQuery", testSelectReturnsValidQuery), 164 | ("testSelectWithArrayReturnsValidQuery", testSelectWithArrayReturnsValidQuery), 165 | ("testInsertGeneratesValidQuery", testInsertGeneratesValidQuery), 166 | ("testUpdateGeneratesValidQuery", testUpdateGeneratesValidQuery), 167 | ("testWheresGeneratesValidQuery", testWheresGeneratesValidQuery), 168 | ("testSelectWithWheresGeneratesValidQuery", testSelectWithWheresGeneratesValidQuery), 169 | ("testUpdateWithWheresGeneratesValidQuery", testUpdateWithWheresGeneratesValidQuery), 170 | ("testJoinWithOneJoinConcatenatesQuery", testJoinWithOneJoinConcatenatesQuery), 171 | ("testJoinWithTwoJoinsConcatenatesQuery", testJoinWithTwoJoinsConcatenatesQuery), 172 | ("testTrimCharTrimsWhenStatementEndsInAComma", testTrimCharTrimsWhenStatementEndsInAComma), 173 | ("testTrimCharDoesNothingWhenStatementDoesNotEndInAComma", testTrimCharDoesNothingWhenStatementDoesNotEndInAComma) 174 | ] 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Sources/MySQL/MySQLConnection.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CMySQLClient 3 | 4 | // Represents an active connection to a MySQL database. 5 | public class MySQLConnection : MySQLConnectionProtocol { 6 | private var uuid: Double 7 | 8 | private var connection: UnsafeMutablePointer? = nil 9 | private var result:UnsafeMutablePointer? = nil 10 | 11 | public init() { 12 | uuid = NSDate().timeIntervalSince1970 13 | } 14 | } 15 | 16 | extension MySQLConnection { 17 | 18 | public func equals(otherObject: MySQLConnectionProtocol) -> Bool { 19 | return uuid == (otherObject as! MySQLConnection).uuid 20 | } 21 | 22 | /** 23 | Does the current connection have an open connection to the database? 24 | 25 | - Returns: true if active connection, false if connection closed 26 | */ 27 | public func isConnected() -> Bool { 28 | if (mysql_ping(connection) != 0) { 29 | return false 30 | } else { 31 | return true 32 | } 33 | } 34 | 35 | /** 36 | Open a connection to the database with the given parameters, in the instance of a failed connection the connect method throws MySQLError. 37 | 38 | - Parameters: 39 | - host: The host name or ip address of the database. 40 | - user: The username to use for the connection. 41 | - password: The password to use for the connection. 42 | */ 43 | public func connect(host: String, user: String, password: String) throws { 44 | return try connect(host: host, user: user, password: password, port: 3306, database: "") 45 | } 46 | 47 | /** 48 | Open a connection to the database with the given parameters, in the instance of a failed connection the connect method throws MySQLError. 49 | 50 | - Parameters: 51 | - host: The host name or ip address of the database. 52 | - user: The username to use for the connection. 53 | - password: The password to use for the connection. 54 | - port: The port to be used for the connection 55 | */ 56 | public func connect(host: String, user: String, password: String, port: Int) throws { 57 | return try connect(host: host, user: user, password: password, port: port, database: "") 58 | } 59 | 60 | /** 61 | Open a connection to the database with the given parameters, in the instance of a failed connection the connect method throws MySQLError. 62 | 63 | - Parameters: 64 | - host: The host name or ip address of the database 65 | - user: The username to use for the connection 66 | - password: The password to use for the connection 67 | - port: The port to be used for the connection 68 | - database: The database to connect to 69 | */ 70 | public func connect(host: String, user: String, password: String, port: Int, database: String) throws { 71 | connection = CMySQLClient.mysql_init(nil); 72 | 73 | if connection == nil { 74 | print("Error: Unable to create connection") 75 | throw MySQLError.UnableToCreateConnection 76 | } 77 | 78 | if CMySQLClient.mysql_real_connect(connection, host, user, password, database, UInt32(port), nil, CMySQLClient.CLIENT_MULTI_STATEMENTS) == nil { 79 | print("Error: Unable to connect to database") 80 | close() 81 | throw MySQLError.UnableToCreateConnection 82 | } 83 | } 84 | 85 | /** 86 | Close the connection. 87 | */ 88 | public func close() { 89 | clearResult() 90 | CMySQLClient.mysql_close(connection) 91 | } 92 | 93 | /** 94 | Retrieve information for the underlying client library version. 95 | 96 | - Returns: String representing the current client version. 97 | */ 98 | public func client_info() -> String? { 99 | return String(cString: CMySQLClient.mysql_get_client_info()) 100 | } 101 | 102 | /** 103 | Retrieve the version for the underlying client library version. 104 | 105 | - Returns: UInt representing the current client version. 106 | */ 107 | public func client_version() -> UInt { 108 | return CMySQLClient.mysql_get_client_version(); 109 | } 110 | 111 | /** 112 | Execute the given SQL query against the database. 113 | 114 | - Parameters: 115 | - query: valid TSQL statement to execute. 116 | 117 | - Returns: Tuple consiting of an optional CMySQLResult, array of CMySQLField and MySQLError. If the query fails then an error object will be returned and CMySQLResult and [CMySQLField] will be nil. Upon success MySQLError will be nil however it is still possible for no results to be returned as some queries do not return results. 118 | */ 119 | public func execute(query: String) -> (CMySQLResult?, [CMySQLField]?, MySQLError?) { 120 | clearResult() // clear any memory allocated to a previous result 121 | 122 | if (CMySQLClient.mysql_query(connection, query) == 1) { 123 | let error = String(cString: CMySQLClient.mysql_error(connection)) 124 | print("Error executing query: " + query) 125 | print(error) 126 | 127 | return (nil, nil, MySQLError.UnableToExecuteQuery(message: error)) 128 | } 129 | 130 | print("total affected rows: " + String(mysql_affected_rows(connection))) 131 | 132 | return getResults() 133 | } 134 | 135 | /** 136 | Return the next result from executing a query. 137 | 138 | - Parameters: 139 | - result: CMySQLResult instance returned from execute. 140 | 141 | - Returns: A pointer to an array of strings. 142 | */ 143 | public func nextResult(result: CMySQLResult) -> CMySQLRow? { 144 | let row = CMySQLClient.mysql_fetch_row(result) 145 | if row != nil { 146 | return row 147 | } else { 148 | // no more results free any memory and return 149 | clearResult() 150 | return nil 151 | } 152 | } 153 | 154 | /** 155 | Return the next result set after executing a query, this might be used when you 156 | specify a multi statement query. 157 | 158 | - Returns: Tuple consiting of an optional CMySQLResult, array of CMySQLField and MySQLError. If the query fails then an error object will be returned and CMySQLResult and [CMySQLField] will be nil. Upon success MySQLError will be nil however it is still possible for no results to be returned as some queries do not return results. 159 | 160 | ``` 161 | var result = connection.execute("SELECT * FROM table1; SELECT * FROM table2;") 162 | var row = connection.nextResult(result) // use rows from table1 163 | 164 | result = connection.nextResultSet() 165 | row = connection.nextResult(result) // use rows from table2 166 | ``` 167 | */ 168 | public func nextResultSet() -> (CMySQLResult?, [CMySQLField]?, MySQLError?) { 169 | if mysql_next_result(connection) < 1 { 170 | return getResults() 171 | } else { 172 | print("No more results") 173 | return (nil, nil, MySQLError.NoMoreResults) 174 | } 175 | } 176 | 177 | private func getResults() -> (CMySQLResult?, [CMySQLField]?, MySQLError?){ 178 | clearResult() 179 | 180 | result = CMySQLClient.mysql_store_result(connection) 181 | if (result == nil) { 182 | print("Error getting results") 183 | print(String(cString: CMySQLClient.mysql_error(connection))) 184 | return (nil, nil, nil) 185 | } else { 186 | return (result, getHeaders(resultPointer: result!), nil) 187 | } 188 | } 189 | 190 | /** 191 | Clears any memory which has been allocated to a mysql result 192 | */ 193 | private func clearResult() { 194 | if result != nil { 195 | CMySQLClient.mysql_free_result(result) 196 | result = nil 197 | } 198 | } 199 | 200 | private func getHeaders(resultPointer: CMySQLResult) -> [CMySQLField] { 201 | let num_fields = CMySQLClient.mysql_num_fields(resultPointer); 202 | var fields = [CMySQLField]() 203 | 204 | for _ in 0.. MySQLConnectionProtocol? = { () -> MySQLConnectionProtocol? in 16 | return nil 17 | } 18 | 19 | static var logger:(message: MySQLConnectionPoolMessage) -> Void = { 20 | (message: MySQLConnectionPoolMessage) -> Void in 21 | } 22 | 23 | /** 24 | setPoolSize sets the size for the connection pool, default is 20 25 | 26 | - Parameters: 27 | - size: new size of the pool 28 | */ 29 | public static func setPoolSize(size: Int) { 30 | poolSize = size 31 | } 32 | 33 | public static func setLogger(logger: (message: MySQLConnectionPoolMessage) -> Void) { 34 | self.logger = logger 35 | } 36 | 37 | /** 38 | setConnectionProvider sets a reference to a closure which returns an object implementing MySQLConnectionProtocol. Everytime the connection pool requires a new connection this closure will be executed. 39 | 40 | - Parameters: 41 | - provider: Closure which returns an object implementing MySQLConnectionProtocol. 42 | */ 43 | public static func setConnectionProvider(provider: () -> MySQLConnectionProtocol?) { 44 | self.connectionProvider = provider 45 | } 46 | 47 | /** 48 | getConnection returns a connection from the pool, if a connection is unsuccessful then getConnection throws a MySQLError, 49 | if the pool has no available connections getConnection will block util either a connection is free or a timeout occurs. 50 | 51 | - Parameters: 52 | - host: The host name or ip address of the database. 53 | - user: The username to use for the connection. 54 | - password: The password to use for the connection. 55 | 56 | - Returns: An object conforming to the MySQLConnectionProtocol. 57 | */ 58 | public static func getConnection(host: String, user: String, password: String) throws -> MySQLConnectionProtocol? { 59 | return try getConnection(host: host, user: user, password: password, port: 3306, database: "") 60 | } 61 | 62 | /** 63 | getConnection returns a connection from the pool, if a connection is unsuccessful then getConnection throws a MySQLError, 64 | if the pool has no available connections getConnection will block util either a connection is free or a timeout occurs. 65 | 66 | - Parameters: 67 | - host: The host name or ip address of the database 68 | - user: The username to use for the connection 69 | - password: The password to use for the connection 70 | - port: The the port to connect to 71 | - database: The database to connect to 72 | 73 | - Returns: An object conforming to the MySQLConnectionProtocol. 74 | */ 75 | public static func getConnection(host: String, user: String, password: String, port: Int, database: String) throws -> MySQLConnectionProtocol? { 76 | // check pool has space 77 | var startTime = NSDate() 78 | 79 | while(countActive() >= poolSize) { 80 | if (NSDate().timeIntervalSince1970 - startTime.timeIntervalSince1970) > poolTimeout { 81 | throw MySQLError.ConnectionPoolTimeout 82 | } 83 | } 84 | 85 | lock.lock() 86 | defer { 87 | lock.unlock() 88 | } 89 | 90 | // check if there is something available in the pool if so return it 91 | let key = computeKey(host: host, user: user, password: password, database: database) 92 | 93 | if let connection = getInactive(key: key) { 94 | addActive(key: key, connection: connection) 95 | logger(message: MySQLConnectionPoolMessage.RetrievedConnectionFromPool) 96 | return connection 97 | } else { 98 | return try createAndAddActive(host: host, user: user, password: password, port: port, database: database) 99 | } 100 | } 101 | 102 | /** 103 | getConnection returns a connection from the pool, if a connection is unsuccessful then getConnection throws a MySQLError, 104 | if the pool has no available connections getConnection will block util either a connection is free or a timeout occurs. 105 | 106 | By passing the optional closure once the code has executed within the block the connection is automatically released 107 | back to the pool saving the requirement to manually call releaseConnection. 108 | 109 | - Parameters: 110 | - host: The host name or ip address of the database 111 | - user: The username to use for the connection 112 | - password: The password to use for the connection 113 | - port: The the port to connect to 114 | - database: The database to connect to 115 | - closure: Code that will be executed before connection is released back to the pool 116 | 117 | - Returns: An object implementing the MySQLConnectionProtocol. 118 | 119 | ``` 120 | MySQLConnectionPoolProtocol.getConnection(host: "127.0.0.1", 121 | user: "root", 122 | password: "mypassword", 123 | port: 3306, 124 | database: "mydatabase") { 125 | (connection: MySQLConnectionProtocol) in 126 | let result = connection.execute("SELECT * FROM TABLE") 127 | ... 128 | } 129 | ``` 130 | */ 131 | public static func getConnection(host: String, 132 | user: String, 133 | password: String, 134 | port: Int, 135 | database: String, 136 | closure: ((connection: MySQLConnectionProtocol) -> Void)) throws { 137 | do { 138 | let connection = try getConnection(host: host, user: user, password: password, port: port, database: database) 139 | defer { 140 | self.releaseConnection(connection: connection!) 141 | } 142 | 143 | closure(connection: connection!) 144 | } catch { 145 | logger(message: MySQLConnectionPoolMessage.FailedToCreateConnection) 146 | throw error 147 | } 148 | } 149 | 150 | /** 151 | releaseConnection returns a connection to the pool. 152 | 153 | - Parameters: 154 | - connection: Connection to be returned to the pool 155 | */ 156 | public static func releaseConnection(connection: MySQLConnectionProtocol) { 157 | lock.lock() 158 | defer { 159 | lock.unlock() 160 | } 161 | 162 | let (connectionKey, index) = findActiveConnection(connection: connection) 163 | 164 | if(connectionKey != nil) { 165 | activeConnections[connectionKey!]!.remove(at: index) 166 | addInactive(key: connectionKey!, connection: connection) 167 | } 168 | } 169 | 170 | private static func createAndAddActive(host: String, user: String, password: String, port: Int, database: String) throws -> MySQLConnectionProtocol? { 171 | let connection = connectionProvider() 172 | 173 | do { 174 | try connection!.connect(host: host, user: user, password: password, port: port, database: database) 175 | } catch { 176 | logger(message: MySQLConnectionPoolMessage.FailedToCreateConnection) 177 | throw error 178 | } 179 | 180 | let key = computeKey(host: host, user: user, password: password, database: database) 181 | addActive(key: key, connection: connection!) 182 | logger(message: MySQLConnectionPoolMessage.CreatedNewConnection) 183 | 184 | return connection 185 | } 186 | 187 | private static func findActiveConnection(connection: MySQLConnectionProtocol) -> (key: String?, index: Int) { 188 | var connectionKey:String? = nil 189 | var connectionIndex = -1 190 | 191 | for (key, value) in activeConnections { 192 | if let index = value.index(where:{$0.equals(otherObject: connection)}) { 193 | connectionIndex = index 194 | connectionKey = key 195 | } 196 | } 197 | 198 | return (connectionKey, connectionIndex) 199 | } 200 | 201 | private static func addActive(key: String, connection: MySQLConnectionProtocol) { 202 | if activeConnections[key] == nil { 203 | activeConnections[key] = [MySQLConnectionProtocol]() 204 | } 205 | 206 | activeConnections[key]!.append(connection) 207 | } 208 | 209 | private static func addInactive(key: String, connection: MySQLConnectionProtocol) { 210 | if inactiveConnections[key] == nil { 211 | inactiveConnections[key] = [MySQLConnectionProtocol]() 212 | } 213 | 214 | inactiveConnections[key]!.append(connection) 215 | } 216 | 217 | private static func getInactive(key: String) -> MySQLConnectionProtocol? { 218 | if inactiveConnections[key] != nil && inactiveConnections[key]!.count > 0 { 219 | // pop a connection off the stack 220 | let connection = inactiveConnections[key]![0] 221 | inactiveConnections[key]!.remove(at: 0) 222 | 223 | if connection.isConnected() { 224 | return connection 225 | } else { 226 | logger(message: MySQLConnectionPoolMessage.ConnectionDisconnected) 227 | return nil 228 | } 229 | } 230 | 231 | return nil 232 | } 233 | 234 | private static func countActive() -> Int { 235 | lock.lock() 236 | defer { 237 | lock.unlock() 238 | } 239 | 240 | var c = 0 241 | for (_, value) in activeConnections { 242 | c += value.count 243 | } 244 | return c 245 | } 246 | 247 | private static func computeKey(host: String, user: String, password: String, database: String) -> String { 248 | return "\(host)_\(user)_\(password)_\(database)" 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /Tests/MySQL/MySQLFieldParserTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import CMySQLClient 4 | 5 | @testable import MySQL 6 | 7 | public class MySQLFieldParserTests: XCTestCase { 8 | 9 | var parser: MySQLFieldParser? 10 | 11 | public override func setUp() { 12 | parser = MySQLFieldParser() 13 | } 14 | 15 | public func testParsesName() { 16 | var field = MYSQL_FIELD() 17 | field.name = "myname".getUnsafeMutablePointer() 18 | 19 | let parsed = parser!.parse(field: field) 20 | XCTAssertEqual("myname", parsed.name, "Name should be equal to myname") 21 | } 22 | 23 | public func testParsesTypeMYSQL_TYPE_TINYReturnsTiny() { 24 | var field = MYSQL_FIELD() 25 | field.name = "myname".getUnsafeMutablePointer() 26 | field.type = MYSQL_TYPE_TINY 27 | 28 | let parsed = parser!.parse(field: field) 29 | XCTAssertEqual(MySQLFieldType.Tiny, parsed.type) 30 | } 31 | 32 | public func testParsesTypeMYSQL_TYPE_SHORTReturnsShort() { 33 | var field = MYSQL_FIELD() 34 | field.name = "myname".getUnsafeMutablePointer() 35 | field.type = MYSQL_TYPE_SHORT 36 | 37 | let parsed = parser!.parse(field: field) 38 | XCTAssertEqual(MySQLFieldType.Short, parsed.type) 39 | } 40 | 41 | public func testParsesTypeMYSQL_TYPE_LONGReturnsLong() { 42 | var field = MYSQL_FIELD() 43 | field.name = "myname".getUnsafeMutablePointer() 44 | field.type = MYSQL_TYPE_LONG 45 | 46 | let parsed = parser!.parse(field: field) 47 | XCTAssertEqual(MySQLFieldType.Long, parsed.type) 48 | } 49 | 50 | public func testParsesTypeMYSQL_TYPE_INT24ReturnsInt24() { 51 | var field = MYSQL_FIELD() 52 | field.name = "myname".getUnsafeMutablePointer() 53 | field.type = MYSQL_TYPE_INT24 54 | 55 | let parsed = parser!.parse(field: field) 56 | XCTAssertEqual(MySQLFieldType.Int24, parsed.type) 57 | } 58 | 59 | public func testParsesTypeMYSQL_TYPE_LONGLONGReturnsLongLong() { 60 | var field = MYSQL_FIELD() 61 | field.name = "myname".getUnsafeMutablePointer() 62 | field.type = MYSQL_TYPE_LONGLONG 63 | 64 | let parsed = parser!.parse(field: field) 65 | XCTAssertEqual(MySQLFieldType.LongLong, parsed.type) 66 | } 67 | 68 | public func testParsesTypeMYSQL_TYPE_DECIMALReturnsDecimal() { 69 | var field = MYSQL_FIELD() 70 | field.name = "myname".getUnsafeMutablePointer() 71 | field.type = MYSQL_TYPE_DECIMAL 72 | 73 | let parsed = parser!.parse(field: field) 74 | XCTAssertEqual(MySQLFieldType.Decimal, parsed.type) 75 | } 76 | 77 | public func testParsesTypeMYSQL_TYPE_NEWDECIMALReturnsNewDecimal() { 78 | var field = MYSQL_FIELD() 79 | field.name = "myname".getUnsafeMutablePointer() 80 | field.type = MYSQL_TYPE_NEWDECIMAL 81 | 82 | let parsed = parser!.parse(field: field) 83 | XCTAssertEqual(MySQLFieldType.NewDecimal, parsed.type) 84 | } 85 | 86 | public func testParsesTypeMYSQL_TYPE_FLOATReturnsFloat() { 87 | var field = MYSQL_FIELD() 88 | field.name = "myname".getUnsafeMutablePointer() 89 | field.type = MYSQL_TYPE_FLOAT 90 | 91 | let parsed = parser!.parse(field: field) 92 | XCTAssertEqual(MySQLFieldType.Float, parsed.type) 93 | } 94 | 95 | public func testParsesTypeMYSQL_TYPE_DOUBLEReturnsDouble() { 96 | var field = MYSQL_FIELD() 97 | field.name = "myname".getUnsafeMutablePointer() 98 | field.type = MYSQL_TYPE_DOUBLE 99 | 100 | let parsed = parser!.parse(field: field) 101 | XCTAssertEqual(MySQLFieldType.Double, parsed.type) 102 | } 103 | 104 | public func testParsesTypeMYSQL_TYPE_BITReturnsBit() { 105 | var field = MYSQL_FIELD() 106 | field.name = "myname".getUnsafeMutablePointer() 107 | field.type = MYSQL_TYPE_BIT 108 | 109 | let parsed = parser!.parse(field: field) 110 | XCTAssertEqual(MySQLFieldType.Bit, parsed.type) 111 | } 112 | 113 | public func testParsesTypeMYSQL_TYPE_TIMESTAMPReturnsTimestamp() { 114 | var field = MYSQL_FIELD() 115 | field.name = "myname".getUnsafeMutablePointer() 116 | field.type = MYSQL_TYPE_TIMESTAMP 117 | 118 | let parsed = parser!.parse(field: field) 119 | XCTAssertEqual(MySQLFieldType.Timestamp, parsed.type) 120 | } 121 | 122 | public func testParsesTypeMYSQL_TYPE_DATEReturnsDate() { 123 | var field = MYSQL_FIELD() 124 | field.name = "myname".getUnsafeMutablePointer() 125 | field.type = MYSQL_TYPE_DATE 126 | 127 | let parsed = parser!.parse(field: field) 128 | XCTAssertEqual(MySQLFieldType.Date, parsed.type) 129 | } 130 | 131 | public func testParsesTypeMYSQL_TYPE_TIMEReturnsTime() { 132 | var field = MYSQL_FIELD() 133 | field.name = "myname".getUnsafeMutablePointer() 134 | field.type = MYSQL_TYPE_TIME 135 | 136 | let parsed = parser!.parse(field: field) 137 | XCTAssertEqual(MySQLFieldType.Time, parsed.type) 138 | } 139 | 140 | public func testParsesTypeMYSQL_TYPE_DATETIMEReturnsDateTime() { 141 | var field = MYSQL_FIELD() 142 | field.name = "myname".getUnsafeMutablePointer() 143 | field.type = MYSQL_TYPE_DATETIME 144 | 145 | let parsed = parser!.parse(field: field) 146 | XCTAssertEqual(MySQLFieldType.DateTime, parsed.type) 147 | } 148 | 149 | public func testParsesTypeMYSQL_TYPE_YEARReturnsYear() { 150 | var field = MYSQL_FIELD() 151 | field.name = "myname".getUnsafeMutablePointer() 152 | field.type = MYSQL_TYPE_YEAR 153 | 154 | let parsed = parser!.parse(field: field) 155 | XCTAssertEqual(MySQLFieldType.Year, parsed.type) 156 | } 157 | 158 | public func testParsesTypeMYSQL_TYPE_STRINGReturnsString() { 159 | var field = MYSQL_FIELD() 160 | field.name = "myname".getUnsafeMutablePointer() 161 | field.type = MYSQL_TYPE_STRING 162 | 163 | let parsed = parser!.parse(field: field) 164 | XCTAssertEqual(MySQLFieldType.String, parsed.type) 165 | } 166 | 167 | public func testParsesTypeMYSQL_TYPE_VAR_STRINGReturnsVarString() { 168 | var field = MYSQL_FIELD() 169 | field.name = "myname".getUnsafeMutablePointer() 170 | field.type = MYSQL_TYPE_VAR_STRING 171 | 172 | let parsed = parser!.parse(field: field) 173 | XCTAssertEqual(MySQLFieldType.VarString, parsed.type) 174 | } 175 | 176 | public func testParsesTypeMYSQL_TYPE_BLOBReturnsBlob() { 177 | var field = MYSQL_FIELD() 178 | field.name = "myname".getUnsafeMutablePointer() 179 | field.type = MYSQL_TYPE_BLOB 180 | 181 | let parsed = parser!.parse(field: field) 182 | XCTAssertEqual(MySQLFieldType.Blob, parsed.type) 183 | } 184 | 185 | public func testParsesTypeMYSQL_TYPE_SETReturnsSet() { 186 | var field = MYSQL_FIELD() 187 | field.name = "myname".getUnsafeMutablePointer() 188 | field.type = MYSQL_TYPE_SET 189 | 190 | let parsed = parser!.parse(field: field) 191 | XCTAssertEqual(MySQLFieldType.Set, parsed.type) 192 | } 193 | 194 | public func testParsesTypeMYSQL_TYPE_ENUMReturnsEnum() { 195 | var field = MYSQL_FIELD() 196 | field.name = "myname".getUnsafeMutablePointer() 197 | field.type = MYSQL_TYPE_ENUM 198 | 199 | let parsed = parser!.parse(field: field) 200 | XCTAssertEqual(MySQLFieldType.Enum, parsed.type) 201 | } 202 | 203 | public func testParsesTypeMYSQL_TYPE_GEOMETRYReturnsGeometry() { 204 | var field = MYSQL_FIELD() 205 | field.name = "myname".getUnsafeMutablePointer() 206 | field.type = MYSQL_TYPE_GEOMETRY 207 | 208 | let parsed = parser!.parse(field: field) 209 | XCTAssertEqual(MySQLFieldType.Geometry, parsed.type) 210 | } 211 | 212 | public func testParsesTypeMYSQL_TYPE_NULLReturnsNull() { 213 | var field = MYSQL_FIELD() 214 | field.name = "myname".getUnsafeMutablePointer() 215 | field.type = MYSQL_TYPE_NULL 216 | 217 | let parsed = parser!.parse(field: field) 218 | XCTAssertEqual(MySQLFieldType.Null, parsed.type) 219 | } 220 | } 221 | 222 | extension MySQLFieldParserTests { 223 | static var allTests: [(String, MySQLFieldParserTests -> () throws -> Void)] { 224 | return [ 225 | ("testParsesName", testParsesName), 226 | ("testParsesTypeMYSQL_TYPE_TINYReturnsTiny", testParsesTypeMYSQL_TYPE_TINYReturnsTiny), 227 | ("testParsesTypeMYSQL_TYPE_SHORTReturnsShort", testParsesTypeMYSQL_TYPE_SHORTReturnsShort), 228 | ("testParsesTypeMYSQL_TYPE_LONGReturnsLong", testParsesTypeMYSQL_TYPE_LONGReturnsLong), 229 | ("testParsesTypeMYSQL_TYPE_INT24ReturnsInt24", testParsesTypeMYSQL_TYPE_INT24ReturnsInt24), 230 | ("testParsesTypeMYSQL_TYPE_LONGLONGReturnsLongLong", testParsesTypeMYSQL_TYPE_LONGLONGReturnsLongLong), 231 | ("testParsesTypeMYSQL_TYPE_DECIMALReturnsDecimal", testParsesTypeMYSQL_TYPE_DECIMALReturnsDecimal), 232 | ("testParsesTypeMYSQL_TYPE_NEWDECIMALReturnsNewDecimal", testParsesTypeMYSQL_TYPE_NEWDECIMALReturnsNewDecimal), 233 | ("testParsesTypeMYSQL_TYPE_FLOATReturnsFloat", testParsesTypeMYSQL_TYPE_FLOATReturnsFloat), 234 | ("testParsesTypeMYSQL_TYPE_DOUBLEReturnsDouble", testParsesTypeMYSQL_TYPE_DOUBLEReturnsDouble), 235 | ("testParsesTypeMYSQL_TYPE_BITReturnsBit", testParsesTypeMYSQL_TYPE_BITReturnsBit), 236 | ("testParsesTypeMYSQL_TYPE_TIMESTAMPReturnsTimestamp", testParsesTypeMYSQL_TYPE_TIMESTAMPReturnsTimestamp), 237 | ("testParsesTypeMYSQL_TYPE_DATEReturnsDate", testParsesTypeMYSQL_TYPE_DATEReturnsDate), 238 | ("testParsesTypeMYSQL_TYPE_TIMEReturnsTime", testParsesTypeMYSQL_TYPE_TIMEReturnsTime), 239 | ("testParsesTypeMYSQL_TYPE_DATETIMEReturnsDateTime", testParsesTypeMYSQL_TYPE_DATETIMEReturnsDateTime), 240 | ("testParsesTypeMYSQL_TYPE_YEARReturnsYear", testParsesTypeMYSQL_TYPE_YEARReturnsYear), 241 | ("testParsesTypeMYSQL_TYPE_STRINGReturnsString", testParsesTypeMYSQL_TYPE_STRINGReturnsString), 242 | ("testParsesTypeMYSQL_TYPE_VAR_STRINGReturnsVarString", testParsesTypeMYSQL_TYPE_VAR_STRINGReturnsVarString), 243 | ("testParsesTypeMYSQL_TYPE_BLOBReturnsBlob", testParsesTypeMYSQL_TYPE_BLOBReturnsBlob), 244 | ("testParsesTypeMYSQL_TYPE_SETReturnsSet", testParsesTypeMYSQL_TYPE_SETReturnsSet), 245 | ("testParsesTypeMYSQL_TYPE_ENUMReturnsEnum", testParsesTypeMYSQL_TYPE_ENUMReturnsEnum), 246 | ("testParsesTypeMYSQL_TYPE_GEOMETRYReturnsGeometry", testParsesTypeMYSQL_TYPE_GEOMETRYReturnsGeometry), 247 | ("testParsesTypeMYSQL_TYPE_NULLReturnsNull", testParsesTypeMYSQL_TYPE_NULLReturnsNull) 248 | ] 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /Tests/MySQL/MySQLConnectionPoolTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import Dispatch 4 | 5 | @testable import MySQL 6 | 7 | public class MySQLConnectionPoolTests: XCTestCase { 8 | 9 | var mockConnection = MockMySQLConnection() 10 | var queue: dispatch_queue_t? 11 | 12 | public override func setUp() { 13 | queue = dispatch_queue_create("statsd_queue." + String(NSDate().timeIntervalSince1970), DISPATCH_QUEUE_CONCURRENT) 14 | 15 | mockConnection = MockMySQLConnection() 16 | MySQLConnectionPool.setConnectionProvider() { 17 | return self.mockConnection 18 | } 19 | 20 | MySQLConnectionPool.activeConnections = [String: [MySQLConnectionProtocol]]() 21 | MySQLConnectionPool.inactiveConnections = [String: [MySQLConnectionProtocol]]() 22 | } 23 | 24 | public func testSetsPoolSize() { 25 | MySQLConnectionPool.setPoolSize(size: 10) 26 | 27 | XCTAssertEqual(10, MySQLConnectionPool.poolSize) 28 | } 29 | 30 | public func testConnectionConnectCalled() { 31 | var _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "")! 32 | 33 | XCTAssertTrue(mockConnection.connectCalled, "Connect should have been called") 34 | } 35 | 36 | public func testGetConnectionWithNoInactiveConnectionsCreatesANewConnection() { 37 | let connection = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "")! 38 | 39 | XCTAssertTrue(connection.equals(otherObject: mockConnection), "Should have used connection from pool") 40 | } 41 | 42 | public func testGetConnectionWithNoInactiveConnectionsAddsAnActivePoolItem() { 43 | var _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "")! 44 | 45 | XCTAssertEqual(1, MySQLConnectionPool.activeConnections.values.first?.count, "Active connections should contain 1 item") 46 | } 47 | 48 | public func testGetConnectionWithInactivePoolItemUsesExistingConnection() { 49 | var inactiveConnections = [MySQLConnectionProtocol]() 50 | let tempConnection = MockMySQLConnection() 51 | inactiveConnections.append(tempConnection) 52 | 53 | MySQLConnectionPool.inactiveConnections["192.168.99.100_root_my-secret-pw_test"] = inactiveConnections 54 | 55 | let connection = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 56 | 57 | XCTAssertTrue(connection.equals(otherObject: tempConnection), "Should have used connection from pool") 58 | XCTAssertEqual(1, MySQLConnectionPool.activeConnections.values.first?.count, "There should be one active connections") 59 | XCTAssertEqual(0, MySQLConnectionPool.inactiveConnections.values.first?.count, "There should be no inactive connections") 60 | } 61 | 62 | public func testGetConnectionWithInactivePoolItemChecksIfConnectionActive() { 63 | var inactiveConnections = [MySQLConnectionProtocol]() 64 | let tempConnection = MockMySQLConnection() 65 | inactiveConnections.append(tempConnection) 66 | 67 | MySQLConnectionPool.inactiveConnections["192.168.99.100_root_my-secret-pw_test"] = inactiveConnections 68 | 69 | let _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 70 | 71 | XCTAssertTrue(tempConnection.isConnectedCalled, "Should have checked if connection active") 72 | } 73 | 74 | public func testGetConnectionWithInactivePoolWhenNotConnectedCreateNewConnection() { 75 | var inactiveConnections = [MySQLConnectionProtocol]() 76 | let tempConnection = MockMySQLConnection() 77 | inactiveConnections.append(tempConnection) 78 | 79 | MySQLConnectionPool.inactiveConnections["192.168.99.100_root_my-secret-pw_test"] = inactiveConnections 80 | 81 | var connection = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 82 | MySQLConnectionPool.releaseConnection(connection: connection) 83 | 84 | tempConnection.connectCalled = false 85 | tempConnection.isConnectedReturn = false 86 | 87 | connection = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 88 | 89 | XCTAssertTrue(mockConnection.connectCalled, "Should have created a new connection") 90 | XCTAssertEqual(1, MySQLConnectionPool.activeConnections.values.first?.count, "There should be one active connections") 91 | } 92 | 93 | public func testGetConnectionNoInactiveConnectionsAddsAnActivePoolItemWithAValidKey() { 94 | var _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 95 | 96 | XCTAssertEqual("192.168.99.100_root_my-secret-pw_test", MySQLConnectionPool.activeConnections.keys.first!, "Key should have correct value") 97 | } 98 | 99 | public func testGetConnectionWithClosureReleasesConnectionAfterUse() { 100 | var _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test") { 101 | (connection: MySQLConnectionProtocol) in 102 | XCTAssertEqual(1, MySQLConnectionPool.activeConnections.values.first?.count, "There should be one active connections") 103 | } 104 | 105 | XCTAssertEqual(1, MySQLConnectionPool.inactiveConnections.values.first?.count, "There should be one inactive connections") 106 | } 107 | 108 | public func testGetConnectionWithClosureExecutesClosurePassingConnection() { 109 | var closureCalled = false 110 | 111 | var _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test") { 112 | (connection: MySQLConnectionProtocol) in 113 | closureCalled = true 114 | } 115 | 116 | XCTAssertTrue(closureCalled, "Closure should have been called") 117 | } 118 | 119 | public func testReleaseConnectionReturnsConnectionToThePool() { 120 | let connection = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 121 | MySQLConnectionPool.releaseConnection(connection: connection) 122 | 123 | XCTAssertEqual(0, MySQLConnectionPool.activeConnections.values.first?.count, "There should be no active connections") 124 | XCTAssertEqual(1, MySQLConnectionPool.inactiveConnections.values.first?.count, "There should be one inactive connections") 125 | } 126 | 127 | // Async tests are not currently implemented for Swift mac 24_03 release 128 | //#if os(Linux) 129 | var timer = 0.0 130 | 131 | public func testGetConnectionBlocksWhenPoolIsExhausted() { 132 | let ex = expectation(withDescription: "Should have blocked when no pool connections are available") 133 | 134 | MySQLConnectionPool.poolSize = 1 135 | let connection = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 136 | 137 | dispatch_async(queue!, { 138 | let startTime = NSDate().timeIntervalSince1970 139 | let _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 140 | 141 | let endTime = NSDate().timeIntervalSince1970 142 | self.timer = endTime - startTime 143 | 144 | ex.fulfill() 145 | }) 146 | 147 | sleep(1) 148 | 149 | MySQLConnectionPool.releaseConnection(connection: connection) 150 | 151 | waitForExpectations(withTimeout: 3) { error in 152 | if let error = error { 153 | XCTFail("Error: \(error.localizedDescription)") 154 | } 155 | } 156 | 157 | //test equal elapsed time + delay interval 158 | XCTAssertTrue((timer >= 1), "getConnection should have blocked for 1 second") 159 | } 160 | 161 | public func testGetConnectionTimesoutWhenPoolIsExhausted() { 162 | let ex = expectation(withDescription: "MySQLConnectionPool getConnection should have timedout") 163 | 164 | MySQLConnectionPool.poolSize = 1 165 | MySQLConnectionPool.poolTimeout = 1 166 | let _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 167 | 168 | dispatch_async(queue!, { 169 | do { 170 | let _ = try MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 171 | } catch { 172 | ex.fulfill() 173 | } 174 | }) 175 | 176 | waitForExpectations(withTimeout: 3) { error in 177 | if let error = error { 178 | XCTFail("Error: \(error.localizedDescription)") 179 | } 180 | } 181 | } 182 | 183 | public func testBroadcastsEventWhenConnectionCreated() { 184 | var dispatchedMessage:MySQLConnectionPoolMessage? 185 | 186 | MySQLConnectionPool.setLogger { 187 | (message: MySQLConnectionPoolMessage) in 188 | dispatchedMessage = message 189 | } 190 | let _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 191 | 192 | XCTAssertEqual(MySQLConnectionPoolMessage.CreatedNewConnection, dispatchedMessage) 193 | } 194 | 195 | public func testBroadcastsEventWhenConnectionFailed() { 196 | mockConnection.connectError = MySQLError.UnableToCreateConnection 197 | 198 | var dispatchedMessage:MySQLConnectionPoolMessage? 199 | MySQLConnectionPool.setLogger { 200 | (message: MySQLConnectionPoolMessage) in 201 | dispatchedMessage = message 202 | } 203 | 204 | do { 205 | let _ = try MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 206 | } catch { 207 | 208 | } 209 | 210 | XCTAssertEqual(MySQLConnectionPoolMessage.FailedToCreateConnection, dispatchedMessage) 211 | } 212 | 213 | public func testBroadcastsEventWhenConnectionReused() { 214 | var dispatchedMessage:MySQLConnectionPoolMessage? 215 | 216 | MySQLConnectionPool.setLogger { 217 | (message: MySQLConnectionPoolMessage) in 218 | dispatchedMessage = message 219 | } 220 | let connection = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 221 | MySQLConnectionPool.releaseConnection(connection: connection) 222 | 223 | let _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 224 | 225 | XCTAssertEqual(MySQLConnectionPoolMessage.RetrievedConnectionFromPool, dispatchedMessage) 226 | } 227 | 228 | public func testBroadcastsEventWhenConnectionReconnected() { 229 | var dispatchedMessage = [MySQLConnectionPoolMessage]() 230 | 231 | MySQLConnectionPool.setLogger { 232 | (message: MySQLConnectionPoolMessage) in 233 | dispatchedMessage.append(message) // the event we are looking for will be the 2nd 234 | } 235 | let connection = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 236 | MySQLConnectionPool.releaseConnection(connection: connection) 237 | mockConnection.isConnectedReturn = false 238 | 239 | let _ = try! MySQLConnectionPool.getConnection(host: "192.168.99.100", user: "root", password: "my-secret-pw", port: 3306, database: "test")! 240 | 241 | XCTAssertEqual(MySQLConnectionPoolMessage.ConnectionDisconnected, dispatchedMessage[1]) 242 | } 243 | } 244 | 245 | extension MySQLConnectionPoolTests { 246 | static var allTests: [(String, MySQLConnectionPoolTests -> () throws -> Void)] { 247 | return [ 248 | ("testSetsPoolSize", testSetsPoolSize), 249 | ("testConnectionConnectCalled", testConnectionConnectCalled), 250 | ("testGetConnectionWithNoInactiveConnectionsCreatesANewConnection", testGetConnectionWithNoInactiveConnectionsCreatesANewConnection), 251 | ("testGetConnectionWithNoInactiveConnectionsAddsAnActivePoolItem", testGetConnectionWithNoInactiveConnectionsAddsAnActivePoolItem), 252 | ("testGetConnectionWithInactivePoolItemUsesExistingConnection", testGetConnectionWithInactivePoolItemUsesExistingConnection), 253 | ("testGetConnectionWithInactivePoolItemChecksIfConnectionActive", testGetConnectionWithInactivePoolItemChecksIfConnectionActive), 254 | ("testGetConnectionWithInactivePoolWhenNotConnectedCreateNewConnection", testGetConnectionWithInactivePoolWhenNotConnectedCreateNewConnection), 255 | ("testGetConnectionNoInactiveConnectionsAddsAnActivePoolItemWithAValidKey", testGetConnectionNoInactiveConnectionsAddsAnActivePoolItemWithAValidKey), 256 | ("testGetConnectionWithClosureReleasesConnectionAfterUse", testGetConnectionWithClosureReleasesConnectionAfterUse), 257 | ("testGetConnectionWithClosureExecutesClosurePassingConnection", testGetConnectionWithClosureExecutesClosurePassingConnection), 258 | ("testReleaseConnectionReturnsConnectionToThePool", testReleaseConnectionReturnsConnectionToThePool), 259 | ("testGetConnectionBlocksWhenPoolIsExhausted", testGetConnectionBlocksWhenPoolIsExhausted), 260 | ("testGetConnectionTimesoutWhenPoolIsExhausted", testGetConnectionTimesoutWhenPoolIsExhausted), 261 | ("testBroadcastsEventWhenConnectionCreated", testBroadcastsEventWhenConnectionCreated), 262 | ("testBroadcastsEventWhenConnectionReused", testBroadcastsEventWhenConnectionReused), 263 | ("testBroadcastsEventWhenConnectionReconnected", testBroadcastsEventWhenConnectionReconnected) 264 | ] 265 | } 266 | } 267 | --------------------------------------------------------------------------------