# 进阶

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

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

## SQL

Fluent 的所有 SQL 数据库驱动程序都是建立在 [SQLKit](https://github.com/vapor/sql-kit) 之上。此通用 SQL 实现是在 Fluent 的 `FluentSQL` 模块中提供的。

### SQL 数据库

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

```swift
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](https://github.com/vapor/sql-kit)。

### 指定 SQL 数据库

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

```swift
import FluentPostgresDriver

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

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

|数据库|驱动|库|
|-|-|-|
|`PostgresDatabase`|[vapor/fluent-postgres-driver](https://github.com/vapor/fluent-postgres-driver)|[vapor/postgres-nio](https://github.com/vapor/postgres-nio)|
|`MySQLDatabase`|[vapor/fluent-mysql-driver](https://github.com/vapor/fluent-mysql-driver)|[vapor/mysql-nio](https://github.com/vapor/mysql-nio)|
|`SQLiteDatabase`|[vapor/fluent-sqlite-driver](https://github.com/vapor/fluent-sqlite-driver)|[vapor/sqlite-nio](https://github.com/vapor/sqlite-nio)|

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

### 自定义 SQL 

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

```swift
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` 模块为常见的用例提供了方便的方法。

```swift
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:)` 方法与模式构建器一起使用的便利性。

```swift
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 是一个集成了 [Fluent](../fluent/overview.md) 和 [MongoKitten](https://github.com/OpenKitten/MongoKitten/) 的驱动程序。它利用 Swift 的强类型特性以及 Fluent 使用与 MongoDB 数据库无关的接口。

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

```swift
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`。

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

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

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

### 使用正则表达式

你可以使用 `.custom()` 方法，并传递一个正则表达式来查询 MongoDB。[MongoDB](https://www.mongodb.com/docs/manual/reference/operator/query/regex/) 接受与 Perl 兼容的正则表达式。

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

```swift
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`，如下所示：

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

let mongodb = db.raw
```

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