通过纯 NIO 驱动程序,使用 SwifQL 处理 Postgres 和 MySQL。
它可以用在纯 NIO2 应用程序上,但我目前没有示例。
您可以参考 VaporBridges
的实现,使其在您的纯 NIO2 应用程序中工作。
.package(url: "https://github.com/SwifQL/PostgresBridge.git", from:"1.0.0-rc"),
.package(url: "https://github.com/SwifQL/VaporBridges.git", from:"1.0.0-rc"),
.target(name: "App", dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "PostgresBridge", package: "PostgresBridge"),
.product(name: "VaporBridges", package: "VaporBridges")
]),
.package(url: "https://github.com/SwifQL/MySQLBridge.git", from:"1.0.0-rc"),
.package(url: "https://github.com/SwifQL/VaporBridges.git", from:"1.0.0-rc"),
.target(name: "App", dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "MySQLBridge", package: "MySQLBridge"),
.product(name: "VaporBridges", package: "VaporBridges")
]),
以下所有示例都将针对 Vapor 4 和 PostgreSQL,但您可以采用相同的方式为 MySQL 和任何其他框架实现它。
您可以设置日志级别,例如在 configure.swift
中
// optionally set global application log level before setting bridges log level
app.logger.logLevel = .notice
app.bridges.logger.logLevel = .debug
在您的应用程序启动之前,初始化(但不是必须)连接池到您的数据库(在 configure.swift
中)
否则,当您第一次尝试获取数据库连接时,将创建 poll。
app.postgres.register(.psqlEnvironment)
这里 .psqlEnvironment
是您的数据库的标识符。
这是一种基于环境变量的默认自动标识符,它期望以下环境变量
PG_DB
PG_HOST - optional, 127.0.0.1 by default
PG_PORT - optional, 5432 by default
PG_USER - optional, `postgres` by default
PG_PWD - optional, empty string by default (will fix it to nil by default)
因此,PG_DB
是使此自动标识符工作的唯一必需环境变量。
您可以通过编写此类扩展来为您所有的数据库甚至不同的主机创建您自己的标识符
extension DatabaseIdentifier {
public static var myDb1: DatabaseIdentifier {
.init(name: "my-db1", host: .myMasterHost, maxConnectionsPerEventLoop: 1)
}
public static var myDb1Slave: DatabaseIdentifier {
.init(name: "my-db1", host: .mySlaveHost, maxConnectionsPerEventLoop: 1)
}
}
extension DatabaseHost {
public static var myMasterHost: DatabaseHost {
return .init(hostname: "127.0.0.1", username: "<username>", password: "<password or nil>", port: 5432, tlsConfiguration: nil)
}
public static var mySlaveHost: DatabaseHost {
return .init(hostname: "192.168.0.200", username: "<username>", password: "<password or nil>", port: 5432, tlsConfiguration: nil)
}
}
一旦您配置了数据库连接,您就可以开始使用它们了。
让我们从 Enum
和 Struct
开始,然后在 Table
中使用它们。
枚举声明非常简单,只需将其符合 String
或 Int
,以及 BridgesEnum
import Bridges
enum Gender: String, BridgesEnum {
case male, female, other
}
或者
import Bridges
enum Priority: Int, BridgesEnum {
case high = 0
case medium = 1
case low = 2
}
结构体声明类似于枚举:只需将其符合 SwifQLCodable
import Bridges
struct AccountOptions: SwifQLCodable {
var twoFactorAuthEnabled: Bool
var lastPasswordChangeDate: Date
}
主要的事情是将您的模型符合 Table
并对所有字段使用 @Column
import Bridges
final class User: Table {
@Column("id")
var id: UUID
@Column("email")
var email: String
@Column("name")
var name: String
@Column("password")
var password: String
@Column("gender")
var gender: Gender
@Column("account_options")
var accountOptions: AccountOptions
@Column("createdAt")
public var createdAt: Date
@Column("updatedAt")
public var updatedAt: Date
@Column("deletedAt")
public var deletedAt: Date?
/// See `Table`
init () {}
}
默认情况下,Bridges 创建的表名类似于类名 User
。 如果您想给表自定义名称,请使用 tableName
静态变量来设置它
final class User: Table {
/// set any custom name here
/// otherwise it will take the name of the table class (`User` in this case)
static var tableName: String { "users" }
@Column("id")
var id: UUID
// ...
上面的例子将为类 User
创建 users
表。
为了方便起见,您的迁移结构体应符合 TableMigration
struct CreateUser: TableMigration {
/// set any custom name here
/// otherwise it will take the name of the migration struct (`CreateUser` in this case)
static var migrationName: String { "create_user_table" }
typealias Table = User
static func prepare(on conn: BridgeConnection) -> EventLoopFuture<Void> {
createBuilder
.column("id", .uuid, .primaryKey)
.column("email", .text, .unique, .notNull)
.column("name", .text, .notNull)
.column("password", .text, .notNull)
.column("gender", .auto(from: Gender.self), .notNull)
.column("account_options", .jsonb, .notNull)
.column("createdAt", .timestamptz, .default(Fn.now()), .notNull)
.column("updatedAt", .timestamptz, .notNull)
.column("deletedAt", .timestamptz)
.execute(on: conn)
}
static func revert(on conn: BridgeConnection) -> EventLoopFuture<Void> {
dropBuilder.execute(on: conn)
}
}
migrationName
变量在运行后设置 migrations
表中迁移的描述,以便 Bridges 知道哪些迁移已部署以及哪些需要在当前批处理中部署。
警告: 尽管可以使用列的键路径 .column(\.$id, .uuid, .primaryKey)
,但强烈建议您使用 String 类型的列名 .column("id", .uuid, .primaryKey)
,因为以后当您有很多列重命名的迁移时,您应该能够从头开始运行项目,并且所有迁移将一个接一个地运行,并且它们应该通过。 如果您使用键路径,它们将会失败。
.column()
非常强大,您可以在此处设置名称、类型、默认值和约束
带有检查约束的 rating 列的示例
.column(\.$rating, .int, .notNull, .check(\WorkerReview.$rating >= 0 && \WorkerReview.$rating <= 5))
带有引用(外键)约束的列的示例
.column("workerId", .uuid, .notNull, .references(Worker.self, onDelete: .cascade, onUpdate: .noAction))
另外,我应该说在 TableMigration
中我们有 createBuilder
、updateBuilder
和 dropBuilder
在上面的示例中,您可以看到如何使用 createBuilder
和 dropBuilder
createBuilder
和 dropBuilder
都实现了在创建和删除表时的安全检查。在创建表之前,您可以强制迁移检查是否不存在您要添加到数据库的表。当您想删除表时,同样适用,检查是否存在这样的表。
struct CreateUser: TableMigration {
typealias Table = User
static func prepare(on conn: BridgeConnection) -> EventLoopFuture<Void> {
createBuilder
.checkIfNotExists()
.column("id", .uuid, .primaryKey)
// ...
.execute(on: conn)
}
static func revert(on conn: BridgeConnection) -> EventLoopFuture<Void> {
dropBuilder
.checkIfExists()
.execute(on: conn)
}
}
要更新表,您可以使用 updateBuilder
struct UpdateUser: TableMigration {
typealias Table = User
static func prepare(on conn: BridgeConnection) -> EventLoopFuture<Void> {
updateBuilder
// adds a check with constraint or expression
.addCheck(...)
// you could add new column same way as with `createBuilder`
.addColumn(...)
// adds foreign key
.addForeignKey(...)
// adds primary key to one or more columns
.addPrimaryKey(...)
// adds unique constraint to one or more columns
.addUnique(...)
// creates index
.createIndex(...)
// drops column
.dropColumn(...)
// drops default value at specified column
.dropDefault(...)
// drops index by its name
.dropIndex(...)
// drops constraint by its name
.dropConstraint(...)
// drops `not null` mark at specified column
.dropNotNull(...)
// renames column
.renameColumn(...)
// renames table
.renameTable(...)
// set default value for specified column
.setDefault(...)
// mark column as `not null`
.setNotNull(...)
// ...
.execute(on: conn)
}
static func revert(on conn: BridgeConnection) -> EventLoopFuture<Void> {
updateBuilder
// use update builder to revert updates as well
.execute(on: conn)
}
}
为了方便起见,您的迁移结构体应符合 EnumMigration
struct CreateEnumGender: EnumMigration {
/// set any custom name here
/// otherwise it will take the name of the migration struct (`CreateEnumGender` in this case)
static var migrationName: String { "CreateEnumGender" }
typealias Enum = Gender
static func prepare(on conn: BridgeConnection) -> EventLoopFuture<Void> {
createBuilder
.add(.male, .female, .other)
.execute(on: conn)
}
static func revert(on conn: BridgeConnection) -> EventLoopFuture<Void> {
dropBuilder.execute(on: conn)
}
}
您还可以使用带有 .add()
方法的原始字符串,如下所示
createBuilder
.add("male", "female", "other")
.execute(on: conn)
如您所见,我们这里也有 createBuilder
和 dropBuilder
,但这里我们也有完全可用的 updateBuilder
// to add one value in the end
updateBuilder.add("bigender").execute(on: conn)
// to add multiple values
updateBuilder.add("bigender").add("mtf", after: "male").add("ftm", before: "female")
我更喜欢在 configure.swift
附近创建 migrations.swift
,因为我们在应用程序启动之前执行迁移
// migrations.swift
import Vapor
import PostgresBridge
func migrations(_ app: Application) throws {
// create `migrations` object on your database connection
let migrator = app.postgres.migrator(for: .myDb1)
// Enums
migrator.add(CreateEnumGender.self) // to create `Gender` enum type in db
// Models
migrator.add(CreateUser.self) // to create `User` table
// migrator.add(SomeCustomMigration.self) // could be some seed migration :)
try migrator.migrate().wait() // will run all provided migrations one by one inside a transaction
// try migrator.revertLast().wait() // will revert only last batch
// try migrator.revertAll().wait() // will revert all migrations one by one in desc order
}
然后在 configure.swift 的末尾某处运行它们
// Called before your application initializes.
public func configure(_ app: Application) throws {
// some initializations
try migrations(app)
try routes(app)
}
使用 SwifQL
的全部功能来构建您的查询。 一旦查询准备就绪,就在连接上执行它。
💡您可以在
Application
和Request
对象上获取连接。
Application
对象的示例,例如 configure.swift
文件
// Called before your application initializes.
public func configure(_ app: Application) throws {
app.postgres.connection(to: .myDb1) { conn in
SwifQL.select(User.table.*).from(User.table).execute(on: conn).all(decoding: User.self).flatMap { rows in
print("yaaay it works and returned \(rows.count) rows!")
}
}.whenComplete {
switch $0 {
case .success: print("query was successful")
case .failure(let error): print("query failed: \(error)")
}
}
}
Request
对象的示例
💡
User
表模型应该符合Content
协议才能作为请求响应返回
func routes(_ app: Application) throws {
app.get("users") { req -> EventLoopFuture<[User]> in
req.postgres.connection(to: .myDb1) { conn in
SwifQL.select(User.table.*).from(User.table).execute(on: conn).all(decoding: User.self)
}
}
}
您可以在事务中执行多个查询
app.postgres.transaction(to: .myDb1) { conn in
/// `BEGIN` calls automatically
/// do any amount of queries here
/// once you finish if everything is ok then `COMMIT` calls automatically
/// if error has occured then `ROLLBACK` calls automatically
}
不。连接会自动释放。
User.select.where(\User.$email == "hello@gmail.com").execute(on: conn).first(decoding: User.self)
let user = User(email: "hello@gmail.com", name: "John", password: "qwerty".sha512, gender: .male)
user.insert(on: conn)
let user1 = User(email: "hello@gmail.com", name: "John", password: "qwerty".sha512, gender: .male)
let user2 = User(email: "byebye@gmail.com", name: "Amily", password: "asdfgh".sha512, gender: .female)
let user3 = User(email: "trololo@gmail.com", name: "Trololo", password: "zxcvbn".sha512, gender: .other)
[user1, user2, user3].batchInsert(on: conn)
User.select.where(\User.$email == "hello@gmail.com").execute(on: conn).first(decoding: User.self).flatMap { user in
guard let user = user else { return conn.eventLoop.makeFailedFuture(...) }
user.password = "asdfg"
return user.update(on: \.$id, on: conn) // executes update just for `password` column and returns EventLoopFuture<User>
}
user.delete(on: \.$id, on: conn) // executes `DELETE FROM User WHERE id=...` returns EventLoopFuture<Void>
请随时贡献
提交一个 issue,或者您始终可以在 Vapor 服务器的 Discord 中 #swifql
分支或直接作为 iMike#3049
找到我。