SwiftMysql

Build Status

一个纯 Swift 客户端,实现了 MySQL 协议。它不依赖于 libmysql。

特性

安装

let package = Package(
    name: "MyApp",
    dependencies: [
        .package(url: "https://github.com/noppoMan/SwiftMysql.git", .upToNextMajor(from: "0.1.0"))
    ],
)

基础查询

let url = URL(string: "mysql://:3306")
let con = try Connection(url: url!, user: "root", password: "password", database: "swift_mysql")

let result = try con.query("selct * from users")
if let rows = result.asRows() {
    for row in rows {
      print(row) // ["id": 1, "name": "Luke", "email": "test@example.com"]
    }
}

预处理语句

您可以轻松地使用预处理语句,如下所示。

let url = URL(string: "mysql://:3306")
let con = try Connection(url: url!, user: "root", password: "password", database: "swift_mysql")

let result = try con.query("selct * from books where published_at > ? and category_id = ?", [2017, 3])
if let rows = result.asRows() {
    for row in rs {
      print(row)
    }
}

连接池

这个模块还提供了内置的连接池,使用 ConnectionPool(url:user:database:minPoolSize:maxPoolSize),而不是逐个创建和管理连接。

let pool = try ConnectionPool(
    url: URL(string: "mysql://:3306")!,
    user: "root",
    database: "swift_mysql",
    minPoolSize: 3,
    maxPoolSize: 10
)

try pool.query("select * from users") // the connection is released after finishing query.

failedToGetConnectionFromPool 错误

当内部使用的连接数达到 maxPoolSize,并且调用 query 时,将抛出 failedToGetConnectionFromPool 错误。但这错误是可恢复的,所有开发者都可以像下面这样重试执行 query

do {
    try pool.query("select * from users")
} catch ConnectionPoolError.failedToGetConnectionFromPool {
    // may need to wait a moment...

    // try again.
    try pool.query("select * from users")
}

事务

在连接级别提供了简单的事务支持

提交

如果事务块中的程序在没有抛出错误的情况下完成,事务应该自动提交。

try con.transaction {
    $0.query("insert into users (name, email), value (\"Foo\", \"foo@example.com\")")
}

回滚

如果事务块中抛出错误,则应执行 rollback

try con.transaction {
    throw FooError
}

流式查询行

有时您可能想要选择大量行,并在接收到每一行时对其进行处理。可以这样做:

let result = try con.query("selct * from large_tables")
if let resultFetcher = result.asResultSet() {
    print(resultFetcher.columns) // [String]

    // resultFetcher.rows is a infinity Sequence
    // you can loop it until the Mysql sends EOF packet.
    for row in resultFetcher.rows {
        print(row)
    }
}

终止连接

一旦调用 close 方法,MySQL 连接将被安全终止。

try con.close()

非阻塞查询

SwiftMysql 通过 AsyncConnection 支持非阻塞查询。非阻塞意味着使用操作系统原生异步系统调用(epoll/kqueue)的事件驱动非阻塞 I/O。它不通过工作线程进行并发执行。

目前,所有非阻塞特性都是线程不安全的。因此,您应该在单线程上使用它们。

使用 AsyncConnection 进行非阻塞查询

您可以使用 AsyncConnection(url:user:password:database:queue) 异步连接到 MySQL。

一旦调用 AsyncConnection 的初始化器,连接将自动在指定的线程(队列)上打开。然后,您的所有操作(查询)都将按顺序排队和处理。该线程将在自己的线程上创建事件循环,以观察连接的文件描述符。

import Foundation
import SwiftMysql

let url = URL(string: "mysql://:3306")!
let con = try SwiftMysql.AsyncConnection(
    url: url,
    user: "root",
    password: nil,
    database: "swift_mysql"
)

con.onConnect {
    print("connected to \(url)")
}

con.onError { error in
    print("Error: \(error)")
}

con.query("select * from users where id = 1") { result in
    if let error = result.asError() {
        print(error)
        return
    }

    result.asRows {
        print($0) // [["id": 1, "name": "Jack....]]
    }
}

con.query("select * from users where id = 2") { result in
    if let error = result.asError() {
        print(error)
        return
    }

    result.asRows {
        print($0) // [["id": 2, "name": "Tonny....]]
    }
}

RunLoop.main.run()

事件循环线程

如果您不关心运行事件循环的线程,它会在内部由 DispatchQueue(attributes: .serial) 自动确定。

或者,您可以像下面这样通过初始化器的 queue 标签来提供它。

let url = URL(string: "mysql://:3306")!
let con = try SwiftMysql.AsyncConnection(
    url: url,
    user: "root",
    password: nil,
    database: "swift_mysql",
    queue: DispatchQueue.main
)

con.connect {
    print(Thread.current == Thread.main) // true
}

con.query("...") { _ in
    print(Thread.current == Thread.main) // true
}

事件驱动的查询行和字段

您可以使用 ResultSetEvent 来提高获取记录的内存效率。

ResultSetEvent 提供了两种方法来流式获取字段和行。

con.query("select * from users limit ?", bindParams: [100]) { result in
    let rs = result.asResultSet() // get ResultSetEvent

    rs.onFields { fields in
        print(fields) // ["id", "name", "age"...]
    }

    event.onRow { row in
        print(row) // [[1, "Jack", 35...]]
    }
}

连接池

您还可以将连接池用于使用 AsyncConnectionPool 的非阻塞查询。用法与同步版本大致相同。当同时使用的连接数达到 maxPoolSize 时,下一个查询队列应等待连接可用。

let pool = try AsyncConnectionPool(
    url: url,
    user: "root",
    database: "swift_mysql",
    minPoolSize: 2,
    maxPoolSize: 10
)

pool.onReady {
    print("The initial connections are ready")
}

pool.onNewConnectionIsReady {
    print("new Connection is ready")
}

// Uses a existing connection
pool.query("select * from users where id = ?", bindParams: [1]) { result in
    result.asRows()
    // connection will be released automatically,
    // when the all of packets of this query are received.
}

// Uses a existing connection
pool.query("select * from users where id = ?", bindParams: [2]) { result in
    result.asRows()
}

// May create a new connection asynchronously.
// Depends on the timing of first query finished.
pool.query("select * from users where id = ?", bindParams: [3]) { result in
    result.asRows()
}

事务

pool.transaction { error, con in
    con?.query("insert into ....") { result in
        if let error = result.asError() {
            con?.rollback { _ in
                done(error)
            }
            return
        }

        con?.query("update users set name = ....") { result in
          if let error = result.asError() {
              con?.rollback { _ in
                  done(error)
              }
              return
          }

          con?.commit { _ in
              done(nil)
          }
        }
    }
}

许可证

SwiftMysql 在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。