Skip to content

Commit

Permalink
Merge pull request #30 from vapor/mysql-2.0
Browse files Browse the repository at this point in the history
mysql 2.0 + connections
  • Loading branch information
tanner0101 authored Feb 16, 2017
2 parents 0075fe8 + 1870c41 commit 809d773
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 105 deletions.
6 changes: 4 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import PackageDescription
let package = Package(
name: "FluentMySQL",
dependencies: [
.Package(url: "https://github.com/vapor/mysql.git", majorVersion: 1),
.Package(url: "https://github.com/vapor/fluent.git", majorVersion: 1),
// Robust MySQL interface for Swift.
.Package(url: "https://github.com/vapor/mysql.git", Version(2,0,0, prereleaseIdentifiers: ["alpha"])),
// Swift models, relationships, and querying for NoSQL and SQL databases.
.Package(url: "https://github.com/vapor/fluent.git", Version(2,0,0, prereleaseIdentifiers: ["alpha"]))
]
)
187 changes: 98 additions & 89 deletions Sources/FluentMySQL/MySQLDriver.swift
Original file line number Diff line number Diff line change
@@ -1,58 +1,61 @@
import Fluent
import MySQL

public class MySQLDriver: Fluent.Driver {
public var idKey: String = "id"
public var database: MySQL.Database

/**
Tells the driver wether or not to make a new database
connection on every request.
Keep this off if you want session variables to be retained.
Setting it to `true` might cause timeout errors if there is no
activity during an extended period of time (MySQL default is 8hr).
*/
public var retainConnection: Bool = false

/**
The active connection with the database.
*/
public var connection: Connection?

/**
Attempts to establish a connection to a MySQL database
engine running on host.
public final class MySQLDriver: Fluent.Driver {
/// The string value for the
/// default identifier key.
///
/// The `idKey` will be used when
/// `Model.find(_:)` or other find
/// by identifier methods are used.
///
/// This value is overriden by
/// entities that implement the
/// `Entity.idKey` static property.
public let idKey: String

- parameter host: May be either a host name or an IP address.
If host is the string "localhost", a connection to the local host is assumed.
- parameter user: The user's MySQL login ID.
- parameter password: Password for user.
- parameter database: Database name.
The connection sets the default database to this value.
- parameter port: If port is not 0, the value is used as
the port number for the TCP/IP connection.
- parameter socket: If socket is not NULL,
the string specifies the socket or named pipe to use.
- parameter flag: Usually 0, but can be set to a combination of the
flags at http://dev.mysql.com/doc/refman/5.7/en/mysql-real-connect.html
- parameter encoding: Usually "utf8", but something like "utf8mb4" may be
used, since "utf8" does not fully implement the UTF8 standard and does
not support Unicode.
/// The default type for values stored against the identifier key.
///
/// The `idType` will be accessed by those Entity implementations
/// which do not themselves implement `Entity.idType`.
public let idType: IdentifierType


- throws: `Error.connection(String)` if the call to
`mysql_real_connect()` fails.
*/
public init(
/// The underlying MySQL Database
public let database: MySQL.Database

/// Attempts to establish a connection to a MySQL database
/// engine running on host.
///
/// - parameter host: May be either a host name or an IP address.
/// If host is the string "localhost", a connection to the local host is assumed.
/// - parameter user: The user's MySQL login ID.
/// - parameter password: Password for user.
/// - parameter database: Database name.
/// The connection sets the default database to this value.
/// - parameter port: If port is not 0, the value is used as
/// the port number for the TCP/IP connection.
/// - parameter socket: If socket is not NULL,
/// the string specifies the socket or named pipe to use.
/// - parameter flag: Usually 0, but can be set to a combination of the
/// flags at http://dev.mysql.com/doc/refman/5.7/en/mysql-real-connect.html
/// - parameter encoding: Usually "utf8", but something like "utf8mb4" may be
/// used, since "utf8" does not fully implement the UTF8 standard and does
/// not support Unicode.
///
/// - throws: `Error.connection(String)` if the call to
///
public convenience init(
host: String,
user: String,
password: String,
database: String,
port: UInt = 3306,
flag: UInt = 0,
encoding: String = "utf8"
encoding: String = "utf8",
idKey: String = "id",
idType: IdentifierType = .int
) throws {
self.database = try MySQL.Database(
let database = try MySQL.Database(
host: host,
user: user,
password: password,
Expand All @@ -61,32 +64,48 @@ public class MySQLDriver: Fluent.Driver {
flag: flag,
encoding: encoding
)
self.init(database, idKey: idKey, idType: idType)
}

/**
Creates the driver from an already
initialized database.
*/
public init(_ database: MySQL.Database) {
/// Creates the driver from an already
/// initialized database.
public init(
_ database: MySQL.Database,
idKey: String = "id",
idType: IdentifierType = .int
) {
self.database = database
self.idKey = idKey
self.idType = idType
}

/**
Queries the database.
*/

/// Creates a connection for executing
/// queries. This method is used to
/// automatically create a connection
/// if any Executor methods are called on
/// the Driver.
public func makeConnection() throws -> Fluent.Connection {
return try database.makeConnection()
}
}

extension MySQL.Connection: Fluent.Connection {
public var closed: Bool {
// TODO: FIXME
return false
}

/// Executes a `Query` from and
/// returns an array of results fetched,
/// created, or updated by the action.
@discardableResult
public func query<T: Entity>(_ query: Query<T>) throws -> Node {
let serializer = MySQLSerializer(sql: query.sql)
let (statement, values) = serializer.serialize()

// create a reusable connection
// so that LAST_INSERT_ID can be fetched
let connection = try database.makeConnection()

let results = try mysql(statement, values, connection)

let results = try mysql(statement, values)

if query.action == .create {
let insert = try mysql("SELECT LAST_INSERT_ID() as id", [], connection)
let insert = try mysql("SELECT LAST_INSERT_ID() as id", [])
if
case .array(let array) = insert,
let first = array.first,
Expand All @@ -96,45 +115,35 @@ public class MySQLDriver: Fluent.Driver {
return id
}
}

return results
}

/**
Creates the desired schema.
*/

/// Creates the `Schema` indicated
/// by the `Builder`.
public func schema(_ schema: Schema) throws {
let serializer = MySQLSerializer(sql: schema.sql)
let (statement, values) = serializer.serialize()

try mysql(statement, values)
}
/**
Conformance to the RawQueryable protocol
allowing plain query strings and value arrays
to be attempted.
*/

/// Drivers that support raw querying
/// accept string queries and parameterized values.
///
/// This allows Fluent extensions to be written that
/// can support custom querying behavior.
@discardableResult
public func raw(_ query: String, _ values: [Node] = []) throws -> Node {
return try mysql(query, values)
public func raw(_ raw: String, _ values: [Node]) throws -> Node {
return try mysql(raw, values)
}

/**
Provides access to the underlying MySQL database
for running raw queries.
*/

@discardableResult
public func mysql(_ query: String, _ values: [Node] = [], _ conn: MySQL.Connection? = nil) throws -> Node {
// Create and save a connection if none provided
var conn = conn ?? connection
if conn == nil && retainConnection {
conn = try database.makeConnection()
connection = conn
} // If `conn` is still nil, the database will create a temporary connection

let results = try database.execute(query, values.map({ $0 as NodeRepresentable }), conn).map { Node.object($0) }
private func mysql(_ query: String, _ values: [Node] = []) throws -> Node {
let results = try execute(
query,
values as [NodeRepresentable]
).map { Node.object($0) }
return .array(results)
}
}

19 changes: 16 additions & 3 deletions Sources/FluentMySQL/MySQLSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,23 @@ import Fluent
MySQL flavored SQL serializer.
*/
public final class MySQLSerializer: GeneralSQLSerializer {
public override func sql(_ type: Schema.Field.DataType) -> String {
public override func sql(_ type: Schema.Field.DataType, primaryKey: Bool) -> String {
switch type {
case .id:
return "INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT"
case .id(let type):
let typeString: String
switch type {
case .int:
if primaryKey {
typeString = "INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT"
} else {
typeString = "INT(10) UNSIGNED"
}
case .uuid:
typeString = "CHAR(36)"
case .custom(let custom):
typeString = custom
}
return typeString
case .int:
return "INT(11)"
case .string(let length):
Expand Down
21 changes: 15 additions & 6 deletions Tests/FluentMySQLTests/JoinTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,31 @@ class JoinTests: XCTestCase {
}

func testBasic() throws {
try Atom.prepare(database)
try Compound.prepare(database)
try Pivot<Atom, Compound>.prepare(database)
try Atom.revert(database)
try Compound.revert(database)
try Pivot<Atom, Compound>.revert(database)

Atom.database = database
Compound.database = database
Pivot<Atom, Compound>.database = database

try Atom.prepare(database)
try Compound.prepare(database)
try Pivot<Atom, Compound>.prepare(database)


var hydrogen = Atom(name: "Hydrogen", protons: 1)
try hydrogen.save()
print(hydrogen.exists)

var water = Compound(name: "Water")
try water.save()
var hydrogenWater = Pivot<Atom, Compound>(hydrogen, water)
var hydrogenWater = try Pivot<Atom, Compound>(hydrogen, water)
try hydrogenWater.save()

var sugar = Compound(name: "Sugar")
try sugar.save()
var hydrogenSugar = Pivot<Atom, Compound>(hydrogen, sugar)
var hydrogenSugar = try Pivot<Atom, Compound>(hydrogen, sugar)
try hydrogenSugar.save()


Expand All @@ -54,6 +60,9 @@ class JoinTests: XCTestCase {
func testTester() {
let tester = Tester(database: database)
do {
try Atom.revert(database)
try Compound.revert(database)
try Pivot<Atom, Compound>.revert(database)
try database.delete("students")
try tester.testAll()
} catch {
Expand Down Expand Up @@ -128,7 +137,7 @@ final class Student: Entity {

static func prepare(_ database: Database) throws {
try database.create("students") { students in
students.id()
students.id(for: self)
students.string("name", length: 64)
students.int("age")
students.string("ssn", unique: true)
Expand Down
2 changes: 1 addition & 1 deletion Tests/FluentMySQLTests/SchemaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class SchemaTests: XCTestCase {

static func prepare(_ database: Database) throws {
try database.create("schema_tests") { builder in
builder.id()
builder.id(for: self)
builder.int("int")
builder.string("string_default")
builder.string("string_64", length: 64)
Expand Down
5 changes: 3 additions & 2 deletions Tests/FluentMySQLTests/Utilities/Atom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ final class Atom: Entity {
var id: Node?
var name: String
var protons: Int
var exists: Bool = false

init(name: String, protons: Int) {
self.name = name
Expand All @@ -24,13 +25,13 @@ final class Atom: Entity {
])
}

func compounds() throws -> Siblings<Compound> {
func compounds() throws -> Siblings<Atom, Compound> {
return try siblings()
}

static func prepare(_ database: Fluent.Database) throws {
try database.create(entity) { builder in
builder.id()
builder.id(for: self)
builder.string("name")
builder.int("protons")
}
Expand Down
3 changes: 2 additions & 1 deletion Tests/FluentMySQLTests/Utilities/Compound.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Fluent
final class Compound: Entity {
var id: Node?
var name: String
var exists: Bool = false

init(name: String) {
self.name = name
Expand All @@ -22,7 +23,7 @@ final class Compound: Entity {

static func prepare(_ database: Fluent.Database) throws {
try database.create(entity) { builder in
builder.id()
builder.id(for: self)
builder.string("name")
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/FluentMySQLTests/Utilities/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class User: Entity {

static func prepare(_ database: Fluent.Database) throws {
try database.create(entity) { builder in
builder.id()
builder.id(for: self)
builder.string("name")
builder.string("email")
}
Expand Down

0 comments on commit 809d773

Please sign in to comment.