跳转至

进阶

Fluent 致力于创建一个通用的、与数据库无关的 API 来处理数据。无论你使用哪种数据库驱动程序,都可以轻松的学习 Fluent。创建通用 API 还可以让你在 Swift 中更轻松自在的使用数据库。

然而,你可能需要使用 Fluent 尚不支持的某个功能来驱动底层数据库。本指南涵盖了 Fluent 中仅适用于某些数据库的高级模式和 API。

SQL

Fluent 的所有 SQL 数据库驱动程序都是建立在 SQLKit 之上。此通用 SQL 实现是在 Fluent 的 FluentSQL 模块中提供的。

SQL 数据库

任何 Fluent 数据库 都可以转换为 SQLDatabase。 这包括 req.dbapp.db,传递给迁移数据库等。

import FluentSQL

if let sql = req.db as? SQLDatabase {
    // 底层数据库驱动程序是 SQL
    let planets = try await sql.raw("SELECT * FROM planets").all(decoding: Planet.self)
} else {
    // 其它驱动
}

此转换仅在底层数据库驱动程序是 SQL 数据库时才有效。了解有关 SQLDatabase 方法的更多信息,请参阅 SQLKit's README

指定 SQL 数据库

你还可以通过导入驱动程序转换为指定的 SQL 数据库。

import FluentPostgresDriver

if let postgres = req.db as? PostgresDatabase {
    // 底层数据库驱动程序是 PostgreSQL.
    postgres.simpleQuery("SELECT * FROM planets").all()
} else {
    // 其它驱动
}

在撰写本文时,支持以下 SQL 驱动程序。

数据库 驱动
PostgresDatabase vapor/fluent-postgres-driver vapor/postgres-nio
MySQLDatabase vapor/fluent-mysql-driver vapor/mysql-nio
SQLiteDatabase vapor/fluent-sqlite-driver vapor/sqlite-nio

了解更多特定数据库的 API 信息,请参阅该库的 README。

自定义 SQL

几乎所有 Fluent 的查询和模式类型都支持 .custom 方法。你就可以使用那些 Fluent 还未支持的数据库功能。

import FluentPostgresDriver

let query = Planet.query(on: req.db)
if req.db is PostgresDatabase {
    // 支持 ILIKE 语句查询。
    query.filter(\.$name, .custom("ILIKE"), "earth")
} else {
    // 不支持 ILIKE 语句的查询。
    query.group(.or) { or in
        or.filter(\.$name == "earth").filter(\.$name == "Earth")
    }
}
query.all()

在 SQL 数据库中所有 .custom 用例都支持字符串SQL 表达式FluentSQL 模块为常见的用例提供了方便的方法。

import FluentSQL

let query = Planet.query(on: req.db)
if req.db is SQLDatabase {
    // 底层数据库驱动程序是 SQL.
    query.filter(.sql(raw: "LOWER(name) = 'earth'"))
} else {
    // 其它驱动
}

下面是 .custom 的一个示例,通过 .sql(raw:) 方法与模式构建器一起使用的便利性。

import FluentSQL

let builder = database.schema("planets").id()
if database is MySQLDatabase {
    // 底层数据库驱动程序是 MySQL.
    builder.field("name", .sql(raw: "VARCHAR(64)"), .required)
} else {
    // 其它驱动
    builder.field("name", .string, .required)
}
builder.create()

MongoDB

Fluent MongoDB 是一个集成了 FluentMongoKitten 的驱动程序。它利用 Swift 的强类型特性以及 Fluent 使用与 MongoDB 数据库无关的接口。

MongoDB 中最常见的标识符是 ObjectId。你可以在项目中使用 @ID(custom: .id) 自定义标志符。 如果需要在 SQL 中使用相同的模型,请不要使用 ObjectId。改为使用 UUID

final class User: Model {
    // 表名或集合名
    static let schema = "users"

    // 用户标志符
    // 本例中使用 ObjectId
    // Fluent默认推荐使用 UUID,当然 ObjectId 也是支持的
    @ID(custom: .id)
    var id: ObjectId?

    // 用户邮箱
    @Field(key: "email")
    var email: String

    // 用户密码存储为 BCrypt 散列
    @Field(key: "password")
    var passwordHash: String

    // 创建一个新的空 User 实例,供 Fluent 使用
    init() { }

    // 创建用户时设置所有属性
    init(id: ObjectId? = nil, email: String, passwordHash: String, profile: Profile) {
        self.id = id
        self.email = email
        self.passwordHash = passwordHash
        self.profile = profile
    }
}

数据建模

在 MongoDB 中,模型的定义与任何其它 Fluent 环境中的定义相同。SQL 数据库和 MongoDB 的主要区别在于关系和架构。

在 SQL 环境中,为两个实体之间的关系创建连接表是很常见的。然而,在 MongoDB 中,可以使用数组来存储相关的标识符。由于 MongoDB 的设计,使用嵌套数据结构设计模型更加高效和实用。

Flexible Data

您可以在 MongoDB 中添加灵活的数据,但此代码在 SQL 环境中不起作用。要创建分组的任意数据存储,你可以使用 Document

@Field(key: "document")
var document: Document

Fluent 不支持对这些值进行严格的类型查询。你可以在查询中使用 .key 路径进行查询。MongoDB 中接受这样的参数,用以访问嵌套值。

Something.query(on: db).filter("document.key", .equal, 5).first()

使用正则表达式

你可以使用 .custom() 方法,并传递一个正则表达式来查询 MongoDB。MongoDB 接受与 Perl 兼容的正则表达式。

例如,你可以在字段 name 下查询不区分大小写的字符:

import FluentMongoDriver

var queryDocument = Document()
queryDocument["name"]["$regex"] = "e"
queryDocument["name"]["$options"] = "i"

let planets = try Planet.query(on: req.db).filter(.custom(queryDocument)).all()

这将返回包含 'e' 和 'E' 的行星。你还可以创建 MongoDB 接受的任何其他复杂的 RegEx。

访问原始数据

要访问原始的 MongoDatabase 实例,将数据库实例转换为 MongoDatabaseRepresentable,如下所示:

guard let db = req.db as? MongoDatabaseRepresentable else {
  throw Abort(.internalServerError)
}

let mongodb = db.raw

接下来你可以使用所有 MongoKitten 的 API。