Skip to content

Advanced

Fluent strives to create a general, database-agnostic API for working with your data. This makes it easier to learn Fluent regardless of which database driver you are using. Creating generalized APIs can also make working with your database feel more at home in Swift.

However, you may need to use a feature of your underlying database driver that is not yet supported through Fluent. This guide covers advanced patterns and APIs in Fluent that only work with certain databases.

SQL

All of Fluent's SQL database drivers are built on SQLKit. This general SQL implementation is shipped with Fluent in the FluentSQL module.

SQL Database

Any Fluent Database can be cast to a SQLDatabase. This includes req.db, app.db, the database passed to Migration, etc.

import FluentSQL

if let sql = req.db as? SQLDatabase {
    // The underlying database driver is SQL.
    sql.raw("SELECT * FROM planets").all()
} else {
    // The underlying database driver is _not_ SQL.
}

This cast will only work if the underlying database driver is a SQL database. Learn more about SQLDatabase's methods in SQLKit's README.

Specific SQL Database

You can also cast to specific SQL databases by importing the driver.

import FluentPostgresDriver

if let postgres = req.db as? PostgresDatabase {
    // The underlying database driver is PostgreSQL.
    postgres.simpleQuery("SELECT * FROM planets").all()
} else {
    // The underlying database is _not_ PostgreSQL.
}

At the time of writing, the following SQL drivers are supported.

Database Driver Library
PostgresDatabase vapor/fluent-postgres-driver vapor/postgres-nio
MySQLDatabase vapor/fluent-mysql-driver vapor/mysql-nio
SQLiteDatabase vapor/fluent-sqlite-driver vapor/sqlite-nio

Visit the library's README for more information on the database-specific APIs.

SQL Custom

Almost all of Fluent's query and schema types support a .custom case. This lets you utilize database features that Fluent doesn't support yet.

import FluentPostgresDriver

let query = Planet.query(on: req.db)
if req.db is PostgresDatabase {
    // ILIKE supported.
    query.filter(\.$name, .custom("ILIKE"), "earth")
} else {
    // ILIKE not supported.
    query.filter(.or) { or in
        or.filter(\.$name == "earth").filter(\.$name == "Earth")
    }
}
query.all()

SQL databases support both String and SQLExpression in all .custom cases. The FluentSQL module provides convenience methods for common use cases.

import FluentSQL

let query = Planet.query(on: req.db)
if req.db is SQLDatabase {
    // The underlying database driver is SQL.
    query.filter(.sql(raw: "LOWER(name) = 'earth'"))
} else {
    // The underlying database driver is _not_ SQL.
}

Below is an example of .custom via the .sql(raw:) convenience being used with the schema builder.

import FluentSQL

let builder = database.schema("planets").id()
if database is MySQLDatabase {
    // The underlying database driver is MySQL.
    builder.field("name", .sql(raw: "VARCHAR(64)"), .required)
} else {
    // The underlying database driver is _not_ MySQL.
    builder.field("name", .string, .required)
}
builder.create()

MongoDB

Fluent MongoDB is an integration between Fluent and the MongoKitten driver. It leverages Swift's strong type system and Fluent's database agnostic interface using MongoDB.

The most common identifier in MongoDB is ObjectId. You can use this for your project using @ID(custom: .id). If you need to use the same models with SQL, do not use ObjectId. Use UUID instead.

final class User: Model {
    // Name of the table or collection.
    static let schema = "users"

    // Unique identifier for this User.
    // In this case, ObjectId is used
    // Fluent recommends using UUID by default, however ObjectId is also supported
    @ID(custom: .id)
    var id: ObjectId?

    // The User's email address
    @Field(key: "email")
    var email: String

    // The User's password stores as a BCrypt hash
    @Field(key: "password")
    var passwordHash: String

    // Creates a new, empty User instance, for use by Fluent
    init() { }

    // Creates a new User with all properties set.
    init(id: ObjectId? = nil, email: String, passwordHash: String, profile: Profile) {
        self.id = id
        self.email = email
        self.passwordHash = passwordHash
        self.profile = profile
    }
}

Data Modelling

In MongoDB, Models are defined in the same as in any other Fluent environment. The main difference between SQL databases and MongoDB lies in relationships and architecture.

In SQL environments, it's very common to create join tables for relationships between two entities. In MongoDB, however, an array can be used to store related identifiers. Due to the design of MongoDB, it's more efficient and practical to design your models with nested data structures.

Flexible Data

You can add flexible data in MongoDB, but this code will not work in SQL environments. To create grouped arbitrary data storage you can use Document.

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

Fluent cannot support strictly types queries on these values. You can use a dot notated key path in your query for querying. This is accepted in MongoDB to access nested values.

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

Raw Access

To access the raw MongoDatabase instance, cast the database instance to MongoDatabaseRepresentable as such:

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

let mongodb = db.raw

From here you can use all of the MongoKitten APIs.