vapor-docs/3.0/docs/databases/sqlite/overview.md

4.3 KiB

SQLite Overview

Let's dive into the vapor/sqlite package and see how to connect to and query a database.

!!! warning This documentation provides an overview for the SQLite API. If you are using SQLite with Fluent, you will likely never need to use this API. Use Fluent's APIs instead.

Follow the instructions in the package section to add the SQLite package to your project. Once its added, you should be able to use import SQLite.

Database

The first step to making a query is to create a Database.

In Memory

In-memory SQLite databases are great for testing as they aren't persisted between application boots.

import SQLite

let db = Database(storage: .memory)

File path

SQLite requires a single file to persist the database contents.

import SQLite

let db = Database(storage: .file(path: "/tmp/db.sqlite"))

!!! tip If the database file does not already exist, it will be created.

Connection

Once you have initialized your database, you can create a connection.

let conn = try db.makeConnection(on: .global())

!!! note Pay special attention to which DispatchQueue you pass to makeConnection(on:). This will be the queue SQLite calls you back on.

!!! tip If you are using SQLite with Vapor, make sure to pass the worker's queue here.

Query

Once you have a Connection, you can use it to create a Query.

let query = conn.query("SELECT * FROM users")

Binding Values

If you are executing a query that has input values, you should bind these using parameters.

let query = conn.query("INSERT INTO users (name, age) VALUES (?, ?)")
query.bind("Albert")
query.bind(138)

You can also bind values using method chaining.

let query = conn.query("INSERT INTO users (name, age) VALUES (?, ?)")
	.bind("Albert")
	.bind(138)

Output Stream

If you expect output from your query, you must attach a stream. The easiest way to do this is by using the drain convenience.

query.drain { row in
	print(row)
}

You can also use drain(into:) and pass in a custom InputStream to capture the query's results.

Row

The Query will output Rows. These are simple structs.

struct Row {
    var fields: [String: Field]
    subscript(field: String) -> Data? { get }
}

You can subscript a Row object to get the optional Data.

let nameData = row["name"] // Data?

Data is an enum that contains all possible types of SQLite data.

public enum Data {
    case integer(Int)
    case float(Double)
    case text(String)
    case blob(Foundation.Data)
    case null
}

For each option, there are convenience properties for casting the Data enum.

let name = row["name"]?.text // String

Run

Once your query is ready to execute, you simply call .execute(). This returns a Future<Void> that will be completed when the query is done executing.

query.execute().then {
	print("done!")
}

All

If you simply want to fetch all of the results, you can use the .all() convenience. This will automatically create a stream and return a future containing your results.

query.all().then { rows in
    print(rows)
}

Sync

For situations where blocking is appropriate (perhaps in tests) you can use .sync() to block until the query's results are ready and return them directly.

// don't do this unless blocking is OK
let rows = try conn.query("SELECT * FROM users").sync()

Example

Now for the complete example:

import SQLite

let db = Database(storage: .memory)
let conn = try db.makeConnection(on: .global()) // take care to use correct queue
conn.query("SELECT * FROM users")
	.all()
    .then { rows in
        print(rows)
    }
    .catch { err in
        print(err)
    }

// wait for results

An example with values being bound:

import SQLite

let db = Database(storage: .memory)
let conn = try db.makeConnection(on: .global()) // take care to use correct queue
conn.query("INSERT INTO users (name, age) VALUES (?, ?)")
    .bind("Albert")
    .bind(138)
    .execute()
    .then {
        print("done")
    }
    .catch { err in
        print(err)
    }

// wait for results