https://github.com/groue/grdb.swift
A toolkit for SQLite databases, with a focus on application development
https://github.com/groue/grdb.swift
database database-observation grdb spm sql sql-builder sqlite sqlite-databases
Last synced: about 1 year ago
JSON representation
A toolkit for SQLite databases, with a focus on application development
- Host: GitHub
- URL: https://github.com/groue/grdb.swift
- Owner: groue
- License: mit
- Created: 2015-06-30T11:17:06.000Z (almost 11 years ago)
- Default Branch: master
- Last Pushed: 2025-04-12T18:34:06.000Z (about 1 year ago)
- Last Synced: 2025-04-18T14:56:58.683Z (about 1 year ago)
- Topics: database, database-observation, grdb, spm, sql, sql-builder, sqlite, sqlite-databases
- Language: Swift
- Homepage:
- Size: 213 MB
- Stars: 7,364
- Watchers: 89
- Forks: 744
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Support: Support/GRDB-Bridging.h
Awesome Lists containing this project
README

A toolkit for SQLite databases, with a focus on application development
Proudly serving the community since 2015
**Latest release**: May 11, 2025 • [version 7.5.0](https://github.com/groue/GRDB.swift/tree/v7.5.0) • [CHANGELOG](CHANGELOG.md) • [Migrating From GRDB 6 to GRDB 7](Documentation/GRDB7MigrationGuide.md)
**Requirements**: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 7.0+ • SQLite 3.20.0+ • Swift 6+ / Xcode 16+
**Contact**:
- Release announcements and usage tips: follow [@groue@hachyderm.io](https://hachyderm.io/@groue) on Mastodon.
- Report bugs in a [Github issue](https://github.com/groue/GRDB.swift/issues/new). Make sure you check the [existing issues](https://github.com/groue/GRDB.swift/issues?q=is%3Aopen) first.
- A question? Looking for advice? Do you wonder how to contribute? Fancy a chat? Go to the [GitHub discussions](https://github.com/groue/GRDB.swift/discussions), or the [GRDB forums](https://forums.swift.org/c/related-projects/grdb).
## What is GRDB?
Use this library to save your application’s permanent data into SQLite databases. It comes with built-in tools that address common needs:
- **SQL Generation**
Enhance your application models with persistence and fetching methods, so that you don't have to deal with SQL and raw database rows when you don't want to.
- **Database Observation**
Get notifications when database values are modified.
- **Robust Concurrency**
Multi-threaded applications can efficiently use their databases, including WAL databases that support concurrent reads and writes.
- **Migrations**
Evolve the schema of your database as you ship new versions of your application.
- **Leverage your SQLite skills**
Not all developers need advanced SQLite features. But when you do, GRDB is as sharp as you want it to be. Come with your SQL and SQLite skills, or learn new ones as you go!
---
Usage •
Documentation •
Installation •
FAQ
---
## Usage
Start using the database in four steps
```swift
import GRDB
// 1. Open a database connection
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")
// 2. Define the database schema
try dbQueue.write { db in
try db.create(table: "player") { t in
t.primaryKey("id", .text)
t.column("name", .text).notNull()
t.column("score", .integer).notNull()
}
}
// 3. Define a record type
struct Player: Codable, Identifiable, FetchableRecord, PersistableRecord {
var id: String
var name: String
var score: Int
enum Columns {
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
}
// 4. Write and read in the database
try dbQueue.write { db in
try Player(id: "1", name: "Arthur", score: 100).insert(db)
try Player(id: "2", name: "Barbara", score: 1000).insert(db)
}
try dbQueue.read { db in
let player = try Player.find(db, id: "1"))
let bestPlayers = try Player
.order(\.score.desc)
.limit(10)
.fetchAll(db)
}
```
Access to raw SQL
```swift
try dbQueue.write { db in
try db.execute(sql: """
CREATE TABLE player (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
score INT NOT NULL)
""")
try db.execute(sql: """
INSERT INTO player (id, name, score)
VALUES (?, ?, ?)
""", arguments: ["1", "Arthur", 100])
// Avoid SQL injection with SQL interpolation
let id = "2"
let name = "O'Brien"
let score = 1000
try db.execute(literal: """
INSERT INTO player (id, name, score)
VALUES (\(id), \(name), \(score))
""")
}
```
See [Executing Updates](#executing-updates)
Access to raw database rows and values
```swift
try dbQueue.read { db in
// Fetch database rows
let rows = try Row.fetchCursor(db, sql: "SELECT * FROM player")
while let row = try rows.next() {
let id: String = row["id"]
let name: String = row["name"]
let score: Int = row["score"]
}
// Fetch values
let playerCount = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM player")! // Int
let playerNames = try String.fetchAll(db, sql: "SELECT name FROM player") // [String]
}
let playerCount = try dbQueue.read { db in
try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM player")!
}
```
See [Fetch Queries](#fetch-queries)
Database model types aka "records"
```swift
struct Player: Codable, Identifiable, FetchableRecord, PersistableRecord {
var id: String
var name: String
var score: Int
enum Columns {
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
}
try dbQueue.write { db in
// Create database table
try db.create(table: "player") { t in
t.primaryKey("id", .text)
t.column("name", .text).notNull()
t.column("score", .integer).notNull()
}
// Insert a record
var player = Player(id: "1", name: "Arthur", score: 100)
try player.insert(db)
// Update a record
player.score += 10
try score.update(db)
try player.updateChanges { $0.score += 10 }
// Delete a record
try player.delete(db)
}
```
See [Records](#records)
Query the database with the Swift query interface
```swift
try dbQueue.read { db in
// Player
let player = try Player.find(db, id: "1")
// Player?
let arthur = try Player.filter { $0.name == "Arthur" }.fetchOne(db)
// [Player]
let bestPlayers = try Player.order(\.score.desc).limit(10).fetchAll(db)
// Int
let playerCount = try Player.fetchCount(db)
// SQL is always welcome
let players = try Player.fetchAll(db, sql: "SELECT * FROM player")
}
```
See the [Query Interface](#the-query-interface)
Database changes notifications
```swift
// Define the observed value
let observation = ValueObservation.tracking { db in
try Player.fetchAll(db)
}
// Start observation
let cancellable = observation.start(
in: dbQueue,
onError: { error in ... },
onChange: { (players: [Player]) in print("Fresh players: \(players)") })
```
Ready-made support for Combine and RxSwift:
```swift
// Swift concurrency
for try await players in observation.values(in: dbQueue) {
print("Fresh players: \(players)")
}
// Combine
let cancellable = observation.publisher(in: dbQueue).sink(
receiveCompletion: { completion in ... },
receiveValue: { (players: [Player]) in print("Fresh players: \(players)") })
// RxSwift
let disposable = observation.rx.observe(in: dbQueue).subscribe(
onNext: { (players: [Player]) in print("Fresh players: \(players)") },
onError: { error in ... })
```
See [Database Observation], [Combine Support], [RxGRDB].
Documentation
=============
**GRDB runs on top of SQLite**: you should get familiar with the [SQLite FAQ](http://www.sqlite.org/faq.html). For general and detailed information, jump to the [SQLite Documentation](http://www.sqlite.org/docs.html).
#### Demo Applications & Frequently Asked Questions
- [Demo Applications]
- [FAQ]
#### Reference
- 📖 [GRDB Reference](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/)
#### Getting Started
- [Installation](#installation)
- [Database Connections]: Connect to SQLite databases
#### SQLite and SQL
- [SQLite API](#sqlite-api): The low-level SQLite API • [executing updates](#executing-updates) • [fetch queries](#fetch-queries) • [SQL Interpolation]
#### Records and the Query Interface
- [Records](#records): Fetching and persistence methods for your custom structs and class hierarchies
- [Query Interface](#the-query-interface): A swift way to generate SQL • [create tables, indexes, etc](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databaseschema) • [requests](#requests) • [associations between record types](Documentation/AssociationsBasics.md)
#### Application Tools
- [Migrations]: Transform your database as your application evolves.
- [Full-Text Search]: Perform efficient and customizable full-text searches.
- [Database Observation]: Observe database changes and transactions.
- [Encryption](#encryption): Encrypt your database with SQLCipher.
- [Backup](#backup): Dump the content of a database to another.
- [Interrupt a Database](#interrupt-a-database): Abort any pending database operation.
- [Sharing a Database]: How to share an SQLite database between multiple processes - recommendations for App Group containers, App Extensions, App Sandbox, and file coordination.
#### Good to Know
- [Concurrency]: How to access databases in a multi-threaded application.
- [Combine](Documentation/Combine.md): Access and observe the database with Combine publishers.
- [Avoiding SQL Injection](#avoiding-sql-injection)
- [Error Handling](#error-handling)
- [Unicode](#unicode)
- [Memory Management](#memory-management)
- [Data Protection](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databaseconnections)
- :bulb: [Migrating From GRDB 6 to GRDB 7](Documentation/Documentation/GRDB7MigrationGuide.md)
- :bulb: [Why Adopt GRDB?](Documentation/WhyAdoptGRDB.md)
- :bulb: [Recommended Practices for Designing Record Types](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/recordrecommendedpractices)
#### Companion Libraries
- [GRDBQuery](https://github.com/groue/GRDBQuery): Access and observe the database from your SwiftUI views.
- [GRDBSnapshotTesting](https://github.com/groue/GRDBSnapshotTesting): Test your database.
**[FAQ]**
**[Sample Code](#sample-code)**
Installation
============
**The installation procedures below have GRDB use the version of SQLite that ships with the target operating system.**
See [Encryption](#encryption) for the installation procedure of GRDB with SQLCipher.
See [Custom SQLite builds](Documentation/CustomSQLiteBuilds.md) for the installation procedure of GRDB with a customized build of SQLite.
## Swift Package Manager
The [Swift Package Manager](https://swift.org/package-manager/) automates the distribution of Swift code. To use GRDB with SPM, add a dependency to `https://github.com/groue/GRDB.swift.git`
GRDB offers two libraries, `GRDB` and `GRDB-dynamic`. Pick only one. When in doubt, prefer `GRDB`. The `GRDB-dynamic` library can reveal useful if you are going to link it with multiple targets within your app and only wish to link to a shared, dynamic framework once. See [How to link a Swift Package as dynamic](https://forums.swift.org/t/how-to-link-a-swift-package-as-dynamic/32062) for more information.
> **Note**: Linux support is provided by contributors. It is not automatically tested, and not officially maintained. If you notice a build or runtime failure on Linux, please open a pull request with the necessary fix, thank you!
## CocoaPods
[CocoaPods](http://cocoapods.org/) is a dependency manager for Xcode projects. To use GRDB with CocoaPods (version 1.2 or higher), specify in your `Podfile`:
```ruby
pod 'GRDB.swift'
```
GRDB can be installed as a framework, or a static library.
**Important Note for CocoaPods installation**
Due to an [issue](https://github.com/CocoaPods/CocoaPods/issues/11839) in CocoaPods, it is currently not possible to deploy new versions of GRDB to CocoaPods. The last version available on CocoaPods is 6.24.1. To install later versions of GRDB using CocoaPods, use one of the following workarounds:
- Depend on the `GRDB7` branch. This is more or less equivalent to what `pod 'GRDB.swift', '~> 7.0'` would normally do, if CocoaPods would accept new GRDB versions to be published:
```ruby
# Can't use semantic versioning due to https://github.com/CocoaPods/CocoaPods/issues/11839
pod 'GRDB.swift', git: 'https://github.com/groue/GRDB.swift.git', branch: 'GRDB7'
```
- Depend on a specific version explicitly (Replace the tag with the version you want to use):
```ruby
# Can't use semantic versioning due to https://github.com/CocoaPods/CocoaPods/issues/11839
# Replace the tag with the tag that you want to use.
pod 'GRDB.swift', git: 'https://github.com/groue/GRDB.swift.git', tag: 'v6.29.0'
```
## Carthage
[Carthage](https://github.com/Carthage/Carthage) is **unsupported**. For some context about this decision, see [#433](https://github.com/groue/GRDB.swift/issues/433).
## Manually
1. [Download](https://github.com/groue/GRDB.swift/releases) a copy of GRDB, or clone its repository and make sure you checkout the latest tagged version.
2. Embed the `GRDB.xcodeproj` project in your own project.
3. Add the `GRDB` target in the **Target Dependencies** section of the **Build Phases** tab of your application target (extension target for WatchOS).
4. Add the `GRDB.framework` to the **Embedded Binaries** section of the **General** tab of your application target (extension target for WatchOS).
Database Connections
====================
GRDB provides two classes for accessing SQLite databases: [`DatabaseQueue`] and [`DatabasePool`]:
```swift
import GRDB
// Pick one:
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")
let dbPool = try DatabasePool(path: "/path/to/database.sqlite")
```
The differences are:
- Database pools allow concurrent database accesses (this can improve the performance of multithreaded applications).
- Database pools open your SQLite database in the [WAL mode](https://www.sqlite.org/wal.html) (unless read-only).
- Database queues support [in-memory databases](https://www.sqlite.org/inmemorydb.html).
**If you are not sure, choose [`DatabaseQueue`].** You will always be able to switch to [`DatabasePool`] later.
For more information and tips when opening connections, see [Database Connections](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databaseconnections).
SQLite API
==========
**In this section of the documentation, we will talk SQL.** Jump to the [query interface](#the-query-interface) if SQL is not your cup of tea.
- [Executing Updates](#executing-updates)
- [Fetch Queries](#fetch-queries)
- [Fetching Methods](#fetching-methods)
- [Row Queries](#row-queries)
- [Value Queries](#value-queries)
- [Values](#values)
- [Data](#data-and-memory-savings)
- [Date and DateComponents](#date-and-datecomponents)
- [NSNumber, NSDecimalNumber, and Decimal](#nsnumber-nsdecimalnumber-and-decimal)
- [Swift enums](#swift-enums)
- [`DatabaseValueConvertible`]: the protocol for custom value types
- [Transactions and Savepoints]
- [SQL Interpolation]
Advanced topics:
- [Prepared Statements]
- [Custom SQL Functions and Aggregates](#custom-sql-functions-and-aggregates)
- [Database Schema Introspection](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databaseschemaintrospection)
- [Row Adapters](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/rowadapter)
- [Raw SQLite Pointers](#raw-sqlite-pointers)
## Executing Updates
Once granted with a [database connection], the [`execute(sql:arguments:)`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/database/execute(sql:arguments:)) method executes the SQL statements that do not return any database row, such as `CREATE TABLE`, `INSERT`, `DELETE`, `ALTER`, etc.
For example:
```swift
try dbQueue.write { db in
try db.execute(sql: """
CREATE TABLE player (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
score INT)
""")
try db.execute(
sql: "INSERT INTO player (name, score) VALUES (?, ?)",
arguments: ["Barbara", 1000])
try db.execute(
sql: "UPDATE player SET score = :score WHERE id = :id",
arguments: ["score": 1000, "id": 1])
}
}
```
The `?` and colon-prefixed keys like `:score` in the SQL query are the **statements arguments**. You pass arguments with arrays or dictionaries, as in the example above. See [Values](#values) for more information on supported arguments types (Bool, Int, String, Date, Swift enums, etc.), and [`StatementArguments`] for a detailed documentation of SQLite arguments.
You can also embed query arguments right into your SQL queries, with [`execute(literal:)`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/database/execute(literal:)), as in the example below. See [SQL Interpolation] for more details.
```swift
try dbQueue.write { db in
let name = "O'Brien"
let score = 550
try db.execute(literal: """
INSERT INTO player (name, score) VALUES (\(name), \(score))
""")
}
```
**Never ever embed values directly in your raw SQL strings**. See [Avoiding SQL Injection](#avoiding-sql-injection) for more information:
```swift
// WRONG: don't embed values in raw SQL strings
let id = 123
let name = textField.text
try db.execute(
sql: "UPDATE player SET name = '\(name)' WHERE id = \(id)")
// CORRECT: use arguments dictionary
try db.execute(
sql: "UPDATE player SET name = :name WHERE id = :id",
arguments: ["name": name, "id": id])
// CORRECT: use arguments array
try db.execute(
sql: "UPDATE player SET name = ? WHERE id = ?",
arguments: [name, id])
// CORRECT: use SQL Interpolation
try db.execute(
literal: "UPDATE player SET name = \(name) WHERE id = \(id)")
```
**Join multiple statements with a semicolon**:
```swift
try db.execute(sql: """
INSERT INTO player (name, score) VALUES (?, ?);
INSERT INTO player (name, score) VALUES (?, ?);
""", arguments: ["Arthur", 750, "Barbara", 1000])
try db.execute(literal: """
INSERT INTO player (name, score) VALUES (\("Arthur"), \(750));
INSERT INTO player (name, score) VALUES (\("Barbara"), \(1000));
""")
```
When you want to make sure that a single statement is executed, use a prepared [`Statement`].
**After an INSERT statement**, you can get the row ID of the inserted row with [`lastInsertedRowID`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/database/lastinsertedrowid):
```swift
try db.execute(
sql: "INSERT INTO player (name, score) VALUES (?, ?)",
arguments: ["Arthur", 1000])
let playerId = db.lastInsertedRowID
```
Don't miss [Records](#records), that provide classic **persistence methods**:
```swift
var player = Player(name: "Arthur", score: 1000)
try player.insert(db)
let playerId = player.id
```
## Fetch Queries
[Database connections] let you fetch database rows, plain values, and custom models aka "records".
**Rows** are the raw results of SQL queries:
```swift
try dbQueue.read { db in
if let row = try Row.fetchOne(db, sql: "SELECT * FROM wine WHERE id = ?", arguments: [1]) {
let name: String = row["name"]
let color: Color = row["color"]
print(name, color)
}
}
```
**Values** are the Bool, Int, String, Date, Swift enums, etc. stored in row columns:
```swift
try dbQueue.read { db in
let urls = try URL.fetchCursor(db, sql: "SELECT url FROM wine")
while let url = try urls.next() {
print(url)
}
}
```
**Records** are your application objects that can initialize themselves from rows:
```swift
let wines = try dbQueue.read { db in
try Wine.fetchAll(db, sql: "SELECT * FROM wine")
}
```
- [Fetching Methods](#fetching-methods) and [Cursors](#cursors)
- [Row Queries](#row-queries)
- [Value Queries](#value-queries)
- [Records](#records)
### Fetching Methods
**Throughout GRDB**, you can always fetch *cursors*, *arrays*, *sets*, or *single values* of any fetchable type (database [row](#row-queries), simple [value](#value-queries), or custom [record](#records)):
```swift
try Row.fetchCursor(...) // A Cursor of Row
try Row.fetchAll(...) // [Row]
try Row.fetchSet(...) // Set
try Row.fetchOne(...) // Row?
```
- `fetchCursor` returns a **[cursor](#cursors)** over fetched values:
```swift
let rows = try Row.fetchCursor(db, sql: "SELECT ...") // A Cursor of Row
```
- `fetchAll` returns an **array**:
```swift
let players = try Player.fetchAll(db, sql: "SELECT ...") // [Player]
```
- `fetchSet` returns a **set**:
```swift
let names = try String.fetchSet(db, sql: "SELECT ...") // Set
```
- `fetchOne` returns a **single optional value**, and consumes a single database row (if any).
```swift
let count = try Int.fetchOne(db, sql: "SELECT COUNT(*) ...") // Int?
```
**All those fetching methods require an SQL string that contains a single SQL statement.** When you want to fetch from multiple statements joined with a semicolon, iterate the multiple [prepared statements] found in the SQL string.
### Cursors
📖 [`Cursor`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/cursor)
**Whenever you consume several rows from the database, you can fetch an Array, a Set, or a Cursor**.
The `fetchAll()` and `fetchSet()` methods return regular Swift array and sets, that you iterate like all other arrays and sets:
```swift
try dbQueue.read { db in
// [Player]
let players = try Player.fetchAll(db, sql: "SELECT ...")
for player in players {
// use player
}
}
```
Unlike arrays and sets, cursors returned by `fetchCursor()` load their results step after step:
```swift
try dbQueue.read { db in
// Cursor of Player
let players = try Player.fetchCursor(db, sql: "SELECT ...")
while let player = try players.next() {
// use player
}
}
```
- **Cursors can not be used on any thread**: you must consume a cursor on the dispatch queue it was created in. Particularly, don't extract a cursor out of a database access method:
```swift
// Wrong
let cursor = try dbQueue.read { db in
try Player.fetchCursor(db, ...)
}
while let player = try cursor.next() { ... }
```
Conversely, arrays and sets may be consumed on any thread:
```swift
// OK
let array = try dbQueue.read { db in
try Player.fetchAll(db, ...)
}
for player in array { ... }
```
- **Cursors can be iterated only one time.** Arrays and sets can be iterated many times.
- **Cursors iterate database results in a lazy fashion**, and don't consume much memory. Arrays and sets contain copies of database values, and may take a lot of memory when there are many fetched results.
- **Cursors are granted with direct access to SQLite,** unlike arrays and sets that have to take the time to copy database values. If you look after extra performance, you may prefer cursors.
- **Cursors can feed Swift collections.**
You will most of the time use `fetchAll` or `fetchSet` when you want an array or a set. For more specific needs, you may prefer one of the initializers below. All of them accept an extra optional `minimumCapacity` argument which helps optimizing your app when you have an idea of the number of elements in a cursor (the built-in `fetchAll` and `fetchSet` do not perform such an optimization).
**Arrays** and all types conforming to `RangeReplaceableCollection`:
```swift
// [String]
let cursor = try String.fetchCursor(db, ...)
let array = try Array(cursor)
```
**Sets**:
```swift
// Set
let cursor = try Int.fetchCursor(db, ...)
let set = try Set(cursor)
```
**Dictionaries**:
```swift
// [Int64: [Player]]
let cursor = try Player.fetchCursor(db)
let dictionary = try Dictionary(grouping: cursor, by: { $0.teamID })
// [Int64: Player]
let cursor = try Player.fetchCursor(db).map { ($0.id, $0) }
let dictionary = try Dictionary(uniqueKeysWithValues: cursor)
```
- **Cursors adopt the [Cursor](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/cursor) protocol, which looks a lot like standard [lazy sequences](https://developer.apple.com/reference/swift/lazysequenceprotocol) of Swift.** As such, cursors come with many convenience methods: `compactMap`, `contains`, `dropFirst`, `dropLast`, `drop(while:)`, `enumerated`, `filter`, `first`, `flatMap`, `forEach`, `joined`, `joined(separator:)`, `max`, `max(by:)`, `min`, `min(by:)`, `map`, `prefix`, `prefix(while:)`, `reduce`, `reduce(into:)`, `suffix`:
```swift
// Prints all Github links
try URL
.fetchCursor(db, sql: "SELECT url FROM link")
.filter { url in url.host == "github.com" }
.forEach { url in print(url) }
// An efficient cursor of coordinates:
let locations = try Row.
.fetchCursor(db, sql: "SELECT latitude, longitude FROM place")
.map { row in
CLLocationCoordinate2D(latitude: row[0], longitude: row[1])
}
```
- **Cursors are not Swift sequences.** That's because Swift sequences can't handle iteration errors, when reading SQLite results may fail at any time.
- **Cursors require a little care**:
- Don't modify the results during a cursor iteration:
```swift
// Undefined behavior
while let player = try players.next() {
try db.execute(sql: "DELETE ...")
}
```
- Don't turn a cursor of `Row` into an array or a set. You would not get the distinct rows you expect. To get a array of rows, use `Row.fetchAll(...)`. To get a set of rows, use `Row.fetchSet(...)`. Generally speaking, make sure you copy a row whenever you extract it from a cursor for later use: `row.copy()`.
If you don't see, or don't care about the difference, use arrays. If you care about memory and performance, use cursors when appropriate.
### Row Queries
- [Fetching Rows](#fetching-rows)
- [Column Values](#column-values)
- [DatabaseValue](#databasevalue)
- [Rows as Dictionaries](#rows-as-dictionaries)
- 📖 [`Row`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/row)
#### Fetching Rows
Fetch **cursors** of rows, **arrays**, **sets**, or **single** rows (see [fetching methods](#fetching-methods)):
```swift
try dbQueue.read { db in
try Row.fetchCursor(db, sql: "SELECT ...", arguments: ...) // A Cursor of Row
try Row.fetchAll(db, sql: "SELECT ...", arguments: ...) // [Row]
try Row.fetchSet(db, sql: "SELECT ...", arguments: ...) // Set
try Row.fetchOne(db, sql: "SELECT ...", arguments: ...) // Row?
let rows = try Row.fetchCursor(db, sql: "SELECT * FROM wine")
while let row = try rows.next() {
let name: String = row["name"]
let color: Color = row["color"]
print(name, color)
}
}
let rows = try dbQueue.read { db in
try Row.fetchAll(db, sql: "SELECT * FROM player")
}
```
Arguments are optional arrays or dictionaries that fill the positional `?` and colon-prefixed keys like `:name` in the query:
```swift
let rows = try Row.fetchAll(db,
sql: "SELECT * FROM player WHERE name = ?",
arguments: ["Arthur"])
let rows = try Row.fetchAll(db,
sql: "SELECT * FROM player WHERE name = :name",
arguments: ["name": "Arthur"])
```
See [Values](#values) for more information on supported arguments types (Bool, Int, String, Date, Swift enums, etc.), and [`StatementArguments`] for a detailed documentation of SQLite arguments.
Unlike row arrays that contain copies of the database rows, row cursors are close to the SQLite metal, and require a little care:
> **Note**: **Don't turn a cursor of `Row` into an array or a set**. You would not get the distinct rows you expect. To get a array of rows, use `Row.fetchAll(...)`. To get a set of rows, use `Row.fetchSet(...)`. Generally speaking, make sure you copy a row whenever you extract it from a cursor for later use: `row.copy()`.
#### Column Values
**Read column values** by index or column name:
```swift
let name: String = row[0] // 0 is the leftmost column
let name: String = row["name"] // Leftmost matching column - lookup is case-insensitive
let name: String = row[Column("name")] // Using query interface's Column
```
Make sure to ask for an optional when the value may be NULL:
```swift
let name: String? = row["name"]
```
The `row[]` subscript returns the type you ask for. See [Values](#values) for more information on supported value types:
```swift
let bookCount: Int = row["bookCount"]
let bookCount64: Int64 = row["bookCount"]
let hasBooks: Bool = row["bookCount"] // false when 0
let string: String = row["date"] // "2015-09-11 18:14:15.123"
let date: Date = row["date"] // Date
self.date = row["date"] // Depends on the type of the property.
```
You can also use the `as` type casting operator:
```swift
row[...] as Int
row[...] as Int?
```
> **Warning**: avoid the `as!` and `as?` operators:
>
> ```swift
> if let int = row[...] as? Int { ... } // BAD - doesn't work
> if let int = row[...] as Int? { ... } // GOOD
> ```
> **Warning**: avoid nil-coalescing row values, and prefer the `coalesce` method instead:
>
> ```swift
> let name: String? = row["nickname"] ?? row["name"] // BAD - doesn't work
> let name: String? = row.coalesce(["nickname", "name"]) // GOOD
> ```
Generally speaking, you can extract the type you need, provided it can be converted from the underlying SQLite value:
- **Successful conversions include:**
- All numeric SQLite values to all numeric Swift types, and Bool (zero is the only false boolean).
- Text SQLite values to Swift String.
- Blob SQLite values to Foundation Data.
See [Values](#values) for more information on supported types (Bool, Int, String, Date, Swift enums, etc.)
- **NULL returns nil.**
```swift
let row = try Row.fetchOne(db, sql: "SELECT NULL")!
row[0] as Int? // nil
row[0] as Int // fatal error: could not convert NULL to Int.
```
There is one exception, though: the [DatabaseValue](#databasevalue) type:
```swift
row[0] as DatabaseValue // DatabaseValue.null
```
- **Missing columns return nil.**
```swift
let row = try Row.fetchOne(db, sql: "SELECT 'foo' AS foo")!
row["missing"] as String? // nil
row["missing"] as String // fatal error: no such column: missing
```
You can explicitly check for a column presence with the `hasColumn` method.
- **Invalid conversions throw a fatal error.**
```swift
let row = try Row.fetchOne(db, sql: "SELECT 'Mom’s birthday'")!
row[0] as String // "Mom’s birthday"
row[0] as Date? // fatal error: could not convert "Mom’s birthday" to Date.
row[0] as Date // fatal error: could not convert "Mom’s birthday" to Date.
let row = try Row.fetchOne(db, sql: "SELECT 256")!
row[0] as Int // 256
row[0] as UInt8? // fatal error: could not convert 256 to UInt8.
row[0] as UInt8 // fatal error: could not convert 256 to UInt8.
```
Those conversion fatal errors can be avoided with the [DatabaseValue](#databasevalue) type:
```swift
let row = try Row.fetchOne(db, sql: "SELECT 'Mom’s birthday'")!
let dbValue: DatabaseValue = row[0]
if dbValue.isNull {
// Handle NULL
} else if let date = Date.fromDatabaseValue(dbValue) {
// Handle valid date
} else {
// Handle invalid date
}
```
This extra verbosity is the consequence of having to deal with an untrusted database: you may consider fixing the content of your database instead. See [Fatal Errors](#fatal-errors) for more information.
- **SQLite has a weak type system, and provides [convenience conversions](https://www.sqlite.org/c3ref/column_blob.html) that can turn String to Int, Double to Blob, etc.**
GRDB will sometimes let those conversions go through:
```swift
let rows = try Row.fetchCursor(db, sql: "SELECT '20 small cigars'")
while let row = try rows.next() {
row[0] as Int // 20
}
```
Don't freak out: those conversions did not prevent SQLite from becoming the immensely successful database engine you want to use. And GRDB adds safety checks described just above. You can also prevent those convenience conversions altogether by using the [DatabaseValue](#databasevalue) type.
#### DatabaseValue
📖 [`DatabaseValue`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasevalue)
**`DatabaseValue` is an intermediate type between SQLite and your values, which gives information about the raw value stored in the database.**
You get `DatabaseValue` just like other value types:
```swift
let dbValue: DatabaseValue = row[0]
let dbValue: DatabaseValue? = row["name"] // nil if and only if column does not exist
// Check for NULL:
dbValue.isNull // Bool
// The stored value:
dbValue.storage.value // Int64, Double, String, Data, or nil
// All the five storage classes supported by SQLite:
switch dbValue.storage {
case .null: print("NULL")
case .int64(let int64): print("Int64: \(int64)")
case .double(let double): print("Double: \(double)")
case .string(let string): print("String: \(string)")
case .blob(let data): print("Data: \(data)")
}
```
You can extract regular [values](#values) (Bool, Int, String, Date, Swift enums, etc.) from `DatabaseValue` with the [fromDatabaseValue()](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasevalueconvertible/fromdatabasevalue(_:)-21zzv) method:
```swift
let dbValue: DatabaseValue = row["bookCount"]
let bookCount = Int.fromDatabaseValue(dbValue) // Int?
let bookCount64 = Int64.fromDatabaseValue(dbValue) // Int64?
let hasBooks = Bool.fromDatabaseValue(dbValue) // Bool?, false when 0
let dbValue: DatabaseValue = row["date"]
let string = String.fromDatabaseValue(dbValue) // "2015-09-11 18:14:15.123"
let date = Date.fromDatabaseValue(dbValue) // Date?
```
`fromDatabaseValue` returns nil for invalid conversions:
```swift
let row = try Row.fetchOne(db, sql: "SELECT 'Mom’s birthday'")!
let dbValue: DatabaseValue = row[0]
let string = String.fromDatabaseValue(dbValue) // "Mom’s birthday"
let int = Int.fromDatabaseValue(dbValue) // nil
let date = Date.fromDatabaseValue(dbValue) // nil
```
#### Rows as Dictionaries
Row adopts the standard [RandomAccessCollection](https://developer.apple.com/documentation/swift/randomaccesscollection) protocol, and can be seen as a dictionary of [DatabaseValue](#databasevalue):
```swift
// All the (columnName, dbValue) tuples, from left to right:
for (columnName, dbValue) in row {
...
}
```
**You can build rows from dictionaries** (standard Swift dictionaries and NSDictionary). See [Values](#values) for more information on supported types:
```swift
let row: Row = ["name": "foo", "date": nil]
let row = Row(["name": "foo", "date": nil])
let row = Row(/* [AnyHashable: Any] */) // nil if invalid dictionary
```
Yet rows are not real dictionaries: they may contain duplicate columns:
```swift
let row = try Row.fetchOne(db, sql: "SELECT 1 AS foo, 2 AS foo")!
row.columnNames // ["foo", "foo"]
row.databaseValues // [1, 2]
row["foo"] // 1 (leftmost matching column)
for (columnName, dbValue) in row { ... } // ("foo", 1), ("foo", 2)
```
**When you build a dictionary from a row**, you have to disambiguate identical columns, and choose how to present database values. For example:
- A `[String: DatabaseValue]` dictionary that keeps leftmost value in case of duplicated column name:
```swift
let dict = Dictionary(row, uniquingKeysWith: { (left, _) in left })
```
- A `[String: AnyObject]` dictionary which keeps rightmost value in case of duplicated column name. This dictionary is identical to FMResultSet's resultDictionary from FMDB. It contains NSNull values for null columns, and can be shared with Objective-C:
```swift
let dict = Dictionary(
row.map { (column, dbValue) in
(column, dbValue.storage.value as AnyObject)
},
uniquingKeysWith: { (_, right) in right })
```
- A `[String: Any]` dictionary that can feed, for example, JSONSerialization:
```swift
let dict = Dictionary(
row.map { (column, dbValue) in
(column, dbValue.storage.value)
},
uniquingKeysWith: { (left, _) in left })
```
See the documentation of [`Dictionary.init(_:uniquingKeysWith:)`](https://developer.apple.com/documentation/swift/dictionary/2892961-init) for more information.
### Value Queries
📖 [`DatabaseValueConvertible`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasevalueconvertible)
**Instead of rows, you can directly fetch values.** There are many supported [value types](#values) (Bool, Int, String, Date, Swift enums, etc.).
Like rows, fetch values as **cursors**, **arrays**, **sets**, or **single** values (see [fetching methods](#fetching-methods)). Values are extracted from the leftmost column of the SQL queries:
```swift
try dbQueue.read { db in
try Int.fetchCursor(db, sql: "SELECT ...", arguments: ...) // A Cursor of Int
try Int.fetchAll(db, sql: "SELECT ...", arguments: ...) // [Int]
try Int.fetchSet(db, sql: "SELECT ...", arguments: ...) // Set
try Int.fetchOne(db, sql: "SELECT ...", arguments: ...) // Int?
let maxScore = try Int.fetchOne(db, sql: "SELECT MAX(score) FROM player") // Int?
let names = try String.fetchAll(db, sql: "SELECT name FROM player") // [String]
}
```
`Int.fetchOne` returns nil in two cases: either the SELECT statement yielded no row, or one row with a NULL value:
```swift
// No row:
try Int.fetchOne(db, sql: "SELECT 42 WHERE FALSE") // nil
// One row with a NULL value:
try Int.fetchOne(db, sql: "SELECT NULL") // nil
// One row with a non-NULL value:
try Int.fetchOne(db, sql: "SELECT 42") // 42
```
For requests which may contain NULL, fetch optionals:
```swift
try dbQueue.read { db in
try Optional.fetchCursor(db, sql: "SELECT ...", arguments: ...) // A Cursor of Int?
try Optional.fetchAll(db, sql: "SELECT ...", arguments: ...) // [Int?]
try Optional.fetchSet(db, sql: "SELECT ...", arguments: ...) // Set
}
```
> :bulb: **Tip**: One advanced use case, when you fetch one value, is to distinguish the cases of a statement that yields no row, or one row with a NULL value. To do so, use `Optional.fetchOne`, which returns a double optional `Int??`:
>
> ```swift
> // No row:
> try Optional.fetchOne(db, sql: "SELECT 42 WHERE FALSE") // .none
> // One row with a NULL value:
> try Optional.fetchOne(db, sql: "SELECT NULL") // .some(.none)
> // One row with a non-NULL value:
> try Optional.fetchOne(db, sql: "SELECT 42") // .some(.some(42))
> ```
There are many supported value types (Bool, Int, String, Date, Swift enums, etc.). See [Values](#values) for more information.
## Values
GRDB ships with built-in support for the following value types:
- **Swift Standard Library**: Bool, Double, Float, all signed and unsigned integer types, String, [Swift enums](#swift-enums).
- **Foundation**: [Data](#data-and-memory-savings), [Date](#date-and-datecomponents), [DateComponents](#date-and-datecomponents), [Decimal](#nsnumber-nsdecimalnumber-and-decimal), NSNull, [NSNumber](#nsnumber-nsdecimalnumber-and-decimal), NSString, URL, [UUID](#uuid).
- **CoreGraphics**: CGFloat.
- **[DatabaseValue](#databasevalue)**, the type which gives information about the raw value stored in the database.
- **Full-Text Patterns**: [FTS3Pattern](Documentation/FullTextSearch.md#fts3pattern) and [FTS5Pattern](Documentation/FullTextSearch.md#fts5pattern).
- Generally speaking, all types that adopt the [`DatabaseValueConvertible`] protocol.
Values can be used as [statement arguments](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/statementarguments):
```swift
let url: URL = ...
let verified: Bool = ...
try db.execute(
sql: "INSERT INTO link (url, verified) VALUES (?, ?)",
arguments: [url, verified])
```
Values can be [extracted from rows](#column-values):
```swift
let rows = try Row.fetchCursor(db, sql: "SELECT * FROM link")
while let row = try rows.next() {
let url: URL = row["url"]
let verified: Bool = row["verified"]
}
```
Values can be [directly fetched](#value-queries):
```swift
let urls = try URL.fetchAll(db, sql: "SELECT url FROM link") // [URL]
```
Use values in [Records](#records):
```swift
struct Link: FetchableRecord {
var url: URL
var isVerified: Bool
init(row: Row) {
url = row["url"]
isVerified = row["verified"]
}
}
```
Use values in the [query interface](#the-query-interface):
```swift
let url: URL = ...
let link = try Link.filter { $0.url == url }.fetchOne(db)
```
### Data (and Memory Savings)
**Data** suits the BLOB SQLite columns. It can be stored and fetched from the database just like other [values](#values):
```swift
let rows = try Row.fetchCursor(db, sql: "SELECT data, ...")
while let row = try rows.next() {
let data: Data = row["data"]
}
```
At each step of the request iteration, the `row[]` subscript creates *two copies* of the database bytes: one fetched by SQLite, and another, stored in the Swift Data value.
**You have the opportunity to save memory** by not copying the data fetched by SQLite:
```swift
while let row = try rows.next() {
try row.withUnsafeData(name: "data") { (data: Data?) in
...
}
}
```
The non-copied data does not live longer than the iteration step: make sure that you do not use it past this point.
### Date and DateComponents
[**Date**](#date) and [**DateComponents**](#datecomponents) can be stored and fetched from the database.
Here is how GRDB supports the various [date formats](https://www.sqlite.org/lang_datefunc.html) supported by SQLite:
| SQLite format | Date | DateComponents |
|:---------------------------- |:------------------:|:--------------:|
| YYYY-MM-DD | Read ¹ | Read / Write |
| YYYY-MM-DD HH:MM | Read ¹ ² | Read ² / Write |
| YYYY-MM-DD HH:MM:SS | Read ¹ ² | Read ² / Write |
| YYYY-MM-DD HH:MM:SS.SSS | Read ¹ ² / Write ¹ | Read ² / Write |
| YYYY-MM-DD**T**HH:MM | Read ¹ ² | Read ² |
| YYYY-MM-DD**T**HH:MM:SS | Read ¹ ² | Read ² |
| YYYY-MM-DD**T**HH:MM:SS.SSS | Read ¹ ² | Read ² |
| HH:MM | | Read ² / Write |
| HH:MM:SS | | Read ² / Write |
| HH:MM:SS.SSS | | Read ² / Write |
| Timestamps since unix epoch | Read ³ | |
| `now` | | |
¹ Missing components are assumed to be zero. Dates are stored and read in the UTC time zone, unless the format is followed by a timezone indicator ⁽²⁾.
² This format may be optionally followed by a timezone indicator of the form `[+-]HH:MM` or just `Z`.
³ GRDB 2+ interprets numerical values as timestamps that fuel `Date(timeIntervalSince1970:)`. Previous GRDB versions used to interpret numbers as [julian days](https://en.wikipedia.org/wiki/Julian_day). Julian days are still supported, with the `Date(julianDay:)` initializer.
> **Warning**: the range of valid years in the SQLite date formats is 0000-9999. You will need to pick another date format when your application needs to process years outside of this range. See the following chapters.
#### Date
**Date** can be stored and fetched from the database just like other [values](#values):
```swift
try db.execute(
sql: "INSERT INTO player (creationDate, ...) VALUES (?, ...)",
arguments: [Date(), ...])
let row = try Row.fetchOne(db, ...)!
let creationDate: Date = row["creationDate"]
```
Dates are stored using the format "YYYY-MM-DD HH:MM:SS.SSS" in the UTC time zone. It is precise to the millisecond.
> **Note**: this format was chosen because it is the only format that is:
>
> - Comparable (`ORDER BY date` works)
> - Comparable with the SQLite keyword CURRENT_TIMESTAMP (`WHERE date > CURRENT_TIMESTAMP` works)
> - Able to feed [SQLite date & time functions](https://www.sqlite.org/lang_datefunc.html)
> - Precise enough
>
> **Warning**: the range of valid years in the SQLite date format is 0000-9999. You will experience problems with years outside of this range, such as decoding errors, or invalid date computations with [SQLite date & time functions](https://www.sqlite.org/lang_datefunc.html).
Some applications may prefer another date format:
- Some may prefer ISO-8601, with a `T` separator.
- Some may prefer ISO-8601, with a time zone.
- Some may need to store years beyond the 0000-9999 range.
- Some may need sub-millisecond precision.
- Some may need exact `Date` roundtrip.
- Etc.
**You should think twice before choosing a different date format:**
- ISO-8601 is about *exchange and communication*, when SQLite is about *storage and data manipulation*. Sharing the same representation in your database and in JSON files only provides a superficial convenience, and should be the least of your priorities. Don't store dates as ISO-8601 without understanding what you lose. For example, ISO-8601 time zones forbid database-level date comparison.
- Sub-millisecond precision and exact `Date` roundtrip are not as obvious needs as it seems at first sight. Dates generally don't precisely roundtrip as soon as they leave your application anyway, because the other systems your app communicates with use their own date representation (the Android version of your app, the server your application is talking to, etc.) On top of that, `Date` comparison is at least as hard and nasty as [floating point comparison](https://www.google.com/search?q=floating+point+comparison+is+hard).
The customization of date format is explicit. For example:
```swift
let date = Date()
let timeInterval = date.timeIntervalSinceReferenceDate
try db.execute(
sql: "INSERT INTO player (creationDate, ...) VALUES (?, ...)",
arguments: [timeInterval, ...])
if let row = try Row.fetchOne(db, ...) {
let timeInterval: TimeInterval = row["creationDate"]
let creationDate = Date(timeIntervalSinceReferenceDate: timeInterval)
}
```
See also [Codable Records] for more date customization options, and [`DatabaseValueConvertible`] if you want to define a Date-wrapping type with customized database representation.
#### DateComponents
DateComponents is indirectly supported, through the **DatabaseDateComponents** helper type.
DatabaseDateComponents reads date components from all [date formats supported by SQLite](https://www.sqlite.org/lang_datefunc.html), and stores them in the format of your choice, from HH:MM to YYYY-MM-DD HH:MM:SS.SSS.
> **Warning**: the range of valid years is 0000-9999. You will experience problems with years outside of this range, such as decoding errors, or invalid date computations with [SQLite date & time functions](https://www.sqlite.org/lang_datefunc.html). See [Date](#date) for more information.
DatabaseDateComponents can be stored and fetched from the database just like other [values](#values):
```swift
let components = DateComponents()
components.year = 1973
components.month = 9
components.day = 18
// Store "1973-09-18"
let dbComponents = DatabaseDateComponents(components, format: .YMD)
try db.execute(
sql: "INSERT INTO player (birthDate, ...) VALUES (?, ...)",
arguments: [dbComponents, ...])
// Read "1973-09-18"
let row = try Row.fetchOne(db, sql: "SELECT birthDate ...")!
let dbComponents: DatabaseDateComponents = row["birthDate"]
dbComponents.format // .YMD (the actual format found in the database)
dbComponents.dateComponents // DateComponents
```
### NSNumber, NSDecimalNumber, and Decimal
**NSNumber** and **Decimal** can be stored and fetched from the database just like other [values](#values).
Here is how GRDB supports the various data types supported by SQLite:
| | Integer | Double | String |
|:--------------- |:------------:|:------------:|:------------:|
| NSNumber | Read / Write | Read / Write | Read |
| NSDecimalNumber | Read / Write | Read / Write | Read |
| Decimal | Read | Read | Read / Write |
- All three types can decode database integers and doubles:
```swift
let number = try NSNumber.fetchOne(db, sql: "SELECT 10") // NSNumber
let number = try NSDecimalNumber.fetchOne(db, sql: "SELECT 1.23") // NSDecimalNumber
let number = try Decimal.fetchOne(db, sql: "SELECT -100") // Decimal
```
- All three types decode database strings as decimal numbers:
```swift
let number = try NSNumber.fetchOne(db, sql: "SELECT '10'") // NSDecimalNumber (sic)
let number = try NSDecimalNumber.fetchOne(db, sql: "SELECT '1.23'") // NSDecimalNumber
let number = try Decimal.fetchOne(db, sql: "SELECT '-100'") // Decimal
```
- `NSNumber` and `NSDecimalNumber` send 64-bit signed integers and doubles in the database:
```swift
// INSERT INTO transfer VALUES (10)
try db.execute(sql: "INSERT INTO transfer VALUES (?)", arguments: [NSNumber(value: 10)])
// INSERT INTO transfer VALUES (10.0)
try db.execute(sql: "INSERT INTO transfer VALUES (?)", arguments: [NSNumber(value: 10.0)])
// INSERT INTO transfer VALUES (10)
try db.execute(sql: "INSERT INTO transfer VALUES (?)", arguments: [NSDecimalNumber(string: "10.0")])
// INSERT INTO transfer VALUES (10.5)
try db.execute(sql: "INSERT INTO transfer VALUES (?)", arguments: [NSDecimalNumber(string: "10.5")])
```
> **Warning**: since SQLite does not support decimal numbers, sending a non-integer `NSDecimalNumber` can result in a loss of precision during the conversion to double.
>
> Instead of sending non-integer `NSDecimalNumber` to the database, you may prefer:
>
> - Send `Decimal` instead (those store decimal strings in the database).
> - Send integers instead (for example, store amounts of cents instead of amounts of Euros).
- `Decimal` sends decimal strings in the database:
```swift
// INSERT INTO transfer VALUES ('10')
try db.execute(sql: "INSERT INTO transfer VALUES (?)", arguments: [Decimal(10)])
// INSERT INTO transfer VALUES ('10.5')
try db.execute(sql: "INSERT INTO transfer VALUES (?)", arguments: [Decimal(string: "10.5")!])
```
### UUID
**UUID** can be stored and fetched from the database just like other [values](#values).
GRDB stores uuids as 16-bytes data blobs, and decodes them from both 16-bytes data blobs and strings such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F".
### Swift Enums
**Swift enums** and generally all types that adopt the [RawRepresentable](https://developer.apple.com/library/tvos/documentation/Swift/Reference/Swift_RawRepresentable_Protocol/index.html) protocol can be stored and fetched from the database just like their raw [values](#values):
```swift
enum Color : Int {
case red, white, rose
}
enum Grape : String {
case chardonnay, merlot, riesling
}
// Declare empty DatabaseValueConvertible adoption
extension Color : DatabaseValueConvertible { }
extension Grape : DatabaseValueConvertible { }
// Store
try db.execute(
sql: "INSERT INTO wine (grape, color) VALUES (?, ?)",
arguments: [Grape.merlot, Color.red])
// Read
let rows = try Row.fetchCursor(db, sql: "SELECT * FROM wine")
while let row = try rows.next() {
let grape: Grape = row["grape"]
let color: Color = row["color"]
}
```
**When a database value does not match any enum case**, you get a fatal error. This fatal error can be avoided with the [DatabaseValue](#databasevalue) type:
```swift
let row = try Row.fetchOne(db, sql: "SELECT 'syrah'")!
row[0] as String // "syrah"
row[0] as Grape? // fatal error: could not convert "syrah" to Grape.
row[0] as Grape // fatal error: could not convert "syrah" to Grape.
let dbValue: DatabaseValue = row[0]
if dbValue.isNull {
// Handle NULL
} else if let grape = Grape.fromDatabaseValue(dbValue) {
// Handle valid grape
} else {
// Handle unknown grape
}
```
## Custom SQL Functions and Aggregates
**SQLite lets you define SQL functions and aggregates.**
A custom SQL function or aggregate extends SQLite:
```sql
SELECT reverse(name) FROM player; -- custom function
SELECT maxLength(name) FROM player; -- custom aggregate
```
- [Custom SQL Functions](#custom-sql-functions)
- [Custom Aggregates](#custom-aggregates)
### Custom SQL Functions
📖 [`DatabaseFunction`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasefunction)
A *function* argument takes an array of [DatabaseValue](#databasevalue), and returns any valid [value](#values) (Bool, Int, String, Date, Swift enums, etc.) The number of database values is guaranteed to be *argumentCount*.
SQLite has the opportunity to perform additional optimizations when functions are "pure", which means that their result only depends on their arguments. So make sure to set the *pure* argument to true when possible.
```swift
let reverse = DatabaseFunction("reverse", argumentCount: 1, pure: true) { (values: [DatabaseValue]) in
// Extract string value, if any...
guard let string = String.fromDatabaseValue(values[0]) else {
return nil
}
// ... and return reversed string:
return String(string.reversed())
}
```
You make a function available to a database connection through its configuration:
```swift
var config = Configuration()
config.prepareDatabase { db in
db.add(function: reverse)
}
let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)
try dbQueue.read { db in
// "oof"
try String.fetchOne(db, sql: "SELECT reverse('foo')")!
}
```
**Functions can take a variable number of arguments:**
When you don't provide any explicit *argumentCount*, the function can take any number of arguments:
```swift
let averageOf = DatabaseFunction("averageOf", pure: true) { (values: [DatabaseValue]) in
let doubles = values.compactMap { Double.fromDatabaseValue($0) }
return doubles.reduce(0, +) / Double(doubles.count)
}
db.add(function: averageOf)
// 2.0
try Double.fetchOne(db, sql: "SELECT averageOf(1, 2, 3)")!
```
**Functions can throw:**
```swift
let sqrt = DatabaseFunction("sqrt", argumentCount: 1, pure: true) { (values: [DatabaseValue]) in
guard let double = Double.fromDatabaseValue(values[0]) else {
return nil
}
guard double >= 0 else {
throw DatabaseError(message: "invalid negative number")
}
return sqrt(double)
}
db.add(function: sqrt)
// SQLite error 1 with statement `SELECT sqrt(-1)`: invalid negative number
try Double.fetchOne(db, sql: "SELECT sqrt(-1)")!
```
**Use custom functions in the [query interface](#the-query-interface):**
```swift
// SELECT reverseString("name") FROM player
Player.select { reverseString($0.name) }
```
**GRDB ships with built-in SQL functions that perform unicode-aware string transformations.** See [Unicode](#unicode).
### Custom Aggregates
📖 [`DatabaseFunction`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasefunction), [`DatabaseAggregate`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databaseaggregate)
Before registering a custom aggregate, you need to define a type that adopts the `DatabaseAggregate` protocol:
```swift
protocol DatabaseAggregate {
// Initializes an aggregate
init()
// Called at each step of the aggregation
mutating func step(_ dbValues: [DatabaseValue]) throws
// Returns the final result
func finalize() throws -> DatabaseValueConvertible?
}
```
For example:
```swift
struct MaxLength : DatabaseAggregate {
var maxLength: Int = 0
mutating func step(_ dbValues: [DatabaseValue]) {
// At each step, extract string value, if any...
guard let string = String.fromDatabaseValue(dbValues[0]) else {
return
}
// ... and update the result
let length = string.count
if length > maxLength {
maxLength = length
}
}
func finalize() -> DatabaseValueConvertible? {
maxLength
}
}
let maxLength = DatabaseFunction(
"maxLength",
argumentCount: 1,
pure: true,
aggregate: MaxLength.self)
```
Like [custom SQL Functions](#custom-sql-functions), you make an aggregate function available to a database connection through its configuration:
```swift
var config = Configuration()
config.prepareDatabase { db in
db.add(function: maxLength)
}
let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)
try dbQueue.read { db in
// Some Int
try Int.fetchOne(db, sql: "SELECT maxLength(name) FROM player")!
}
```
The `step` method of the aggregate takes an array of [DatabaseValue](#databasevalue). This array contains as many values as the *argumentCount* parameter (or any number of values, when *argumentCount* is omitted).
The `finalize` method of the aggregate returns the final aggregated [value](#values) (Bool, Int, String, Date, Swift enums, etc.).
SQLite has the opportunity to perform additional optimizations when aggregates are "pure", which means that their result only depends on their inputs. So make sure to set the *pure* argument to true when possible.
**Use custom aggregates in the [query interface](#the-query-interface):**
```swift
// SELECT maxLength("name") FROM player
let request = Player.select { maxLength($0.name) }
try Int.fetchOne(db, request) // Int?
```
## Raw SQLite Pointers
**If not all SQLite APIs are exposed in GRDB, you can still use the [SQLite C Interface](https://www.sqlite.org/c3ref/intro.html) and call [SQLite C functions](https://www.sqlite.org/c3ref/funclist.html).**
To access the C SQLite functions from SQLCipher or the system SQLite, you need to perform an extra import:
```swift
import SQLite3 // System SQLite
import SQLCipher // SQLCipher
let sqliteVersion = String(cString: sqlite3_libversion())
```
Raw pointers to database connections and statements are available through the `Database.sqliteConnection` and `Statement.sqliteStatement` properties:
```swift
try dbQueue.read { db in
// The raw pointer to a database connection:
let sqliteConnection = db.sqliteConnection
// The raw pointer to a statement:
let statement = try db.makeStatement(sql: "SELECT ...")
let sqliteStatement = statement.sqliteStatement
}
```
> **Note**
>
> - Those pointers are owned by GRDB: don't close connections or finalize statements created by GRDB.
> - GRDB opens SQLite connections in the "[multi-thread mode](https://www.sqlite.org/threadsafe.html)", which (oddly) means that **they are not thread-safe**. Make sure you touch raw databases and statements inside their dedicated dispatch queues.
> - Use the raw SQLite C Interface at your own risk. GRDB won't prevent you from shooting yourself in the foot.
Records
=======
**On top of the [SQLite API](#sqlite-api), GRDB provides protocols** that help manipulating database rows as regular objects named "records":
```swift
try dbQueue.write { db in
if var place = try Player.fetchOne(db, id: 1) {
player.score += 10
try player.update(db)
}
}
```
Of course, you need to open a [database connection], and [create database tables](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databaseschema) first.
To define a record type, define a type and extend it with database protocols:
- `FetchableRecord` makes it possible to fetch instances from the database.
- `PersistableRecord` makes it possible to save instances into the database.
- `Codable` (not mandatory) provides ready-made serialization to and from database rows.
- `Identifiable` (not mandatory) provides extra convenience database methods.
To make it easier to customize database requests, also nest a `Columns` enum:
```swift
struct Player: Codable, Identifiable {
var id: Int64
var name: String
var score: Int
var team: String?
}
// Add database support
extension Player: FetchableRecord, PersistableRecord {
enum Columns {
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
static let team = Column(CodingKeys.team)
}
}
```
See more [examples of record definitions](#examples-of-record-definitions) below.
> Note: if you are familiar with Core Data's NSManagedObject or Realm's Object, you may experience a cultural shock: GRDB records are not uniqued, do not auto-update, and do not lazy-load. This is both a purpose, and a consequence of protocol-oriented programming.
>
> Tip: The [Recommended Practices for Designing Record Types](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/recordrecommendedpractices) guide provides general guidance..
>
> Tip: See the [Demo Applications] for sample apps that uses records.
**Overview**
- [Inserting Records](#inserting-records)
- [Fetching Records](#fetching-records)
- [Updating Records](#updating-records)
- [Deleting Records](#deleting-records)
- [Counting Records](#counting-records)
**Protocols and the Record Class**
- [Record Protocols Overview](#record-protocols-overview)
- [FetchableRecord Protocol](#fetchablerecord-protocol)
- [TableRecord Protocol](#tablerecord-protocol)
- [PersistableRecord Protocol](#persistablerecord-protocol)
- [Persistence Methods]
- [Persistence Methods and the `RETURNING` clause]
- [Persistence Callbacks]
- [Identifiable Records]
- [Codable Records]
- [Record Comparison]
- [Record Customization Options]
- [Record Timestamps and Transaction Date]
### Inserting Records
To insert a record in the database, call the `insert` method:
```swift
let player = Player(id: 1, name: "Arthur", score: 1000)
try player.insert(db)
```
:point_right: `insert` is available for types that adopt the [PersistableRecord] protocol.
### Fetching Records
To fetch records from the database, call a [fetching method](#fetching-methods):
```swift
let arthur = try Player.fetchOne(db, // Player?
sql: "SELECT * FROM players WHERE name = ?",
arguments: ["Arthur"])
let bestPlayers = try Player // [Player]
.order(\.score.desc)
.limit(10)
.fetchAll(db)
let spain = try Country.fetchOne(db, id: "ES") // Country?
let italy = try Country.find(db, id: "IT") // Country
```
:point_right: Fetching from raw SQL is available for types that adopt the [FetchableRecord] protocol.
:point_right: Fetching without SQL, using the [query interface](#the-query-interface), is available for types that adopt both [FetchableRecord] and [TableRecord] protocol.
### Updating Records
To update a record in the database, call the `update` method:
```swift
var player: Player = ...
player.score = 1000
try player.update(db)
```
It is possible to [avoid useless updates](#record-comparison):
```swift
// does not hit the database if score has not changed
try player.updateChanges(db) {
$0.score = 1000
}
```
See the [query interface](#the-query-interface) for batch updates:
```swift
try Player
.filter { $0.team == "red" }
.updateAll(db) { $0.score += 1 }
```
:point_right: update methods are available for types that adopt the [PersistableRecord] protocol. Batch updates are available on the [TableRecord] protocol.
### Deleting Records
To delete a record in the database, call the `delete` method:
```swift
let player: Player = ...
try player.delete(db)
```
You can also delete by primary key, unique key, or perform batch deletes (see [Delete Requests](#delete-requests)):
```swift
try Player.deleteOne(db, id: 1)
try Player.deleteOne(db, key: ["email": "arthur@example.com"])
try Country.deleteAll(db, ids: ["FR", "US"])
try Player
.filter { $0.email == nil }
.deleteAll(db)
```
:point_right: delete methods are available for types that adopt the [PersistableRecord] protocol. Batch deletes are available on the [TableRecord] protocol.
### Counting Records
To count records, call the `fetchCount` method:
```swift
let playerCount: Int = try Player.fetchCount(db)
let playerWithEmailCount: Int = try Player
.filter { $0.email == nil }
.fetchCount(db)
```
:point_right: `fetchCount` is available for types that adopt the [TableRecord] protocol.
Details follow:
- [Record Protocols Overview](#record-protocols-overview)
- [FetchableRecord Protocol](#fetchablerecord-protocol)
- [TableRecord Protocol](#tablerecord-protocol)
- [PersistableRecord Protocol](#persistablerecord-protocol)
- [Identifiable Records]
- [Codable Records]
- [Record Comparison]
- [Record Customization Options]
- [Examples of Record Definitions](#examples-of-record-definitions)
## Record Protocols Overview
**GRDB ships with three record protocols**. Your own types will adopt one or several of them, according to the abilities you want to extend your types with.
- [FetchableRecord] is able to **decode database rows**.
```swift
struct Place: FetchableRecord { ... }
let places = try dbQueue.read { db in
try Place.fetchAll(db, sql: "SELECT * FROM place")
}
```
> :bulb: **Tip**: `FetchableRecord` can derive its implementation from the standard `Decodable` protocol. See [Codable Records] for more information.
`FetchableRecord` can decode database rows, but it is not able to build SQL requests for you. For that, you also need `TableRecord`:
- [TableRecord] is able to **generate SQL queries**:
```swift
struct Place: TableRecord { ... }
let placeCount = try dbQueue.read { db in
// Generates and runs `SELECT COUNT(*) FROM place`
try Place.fetchCount(db)
}
```
When a type adopts both `TableRecord` and `FetchableRecord`, it can load from those requests:
```swift
struct Place: TableRecord, FetchableRecord { ... }
try dbQueue.read { db in
let places = try Place.order(\.title).fetchAll(db)
let paris = try Place.fetchOne(id: 1)
}
```
- [PersistableRecord] is able to **write**: it can create, update, and delete rows in the database:
```swift
struct Place : PersistableRecord { ... }
try dbQueue.write { db in
try Place.delete(db, id: 1)
try Place(...).insert(db)
}
```
A persistable record can also [compare](#record-comparison) itself against other records, and avoid useless database updates.
> :bulb: **Tip**: `PersistableRecord` can derive its implementation from the standard `Encodable` protocol. See [Codable Records] for more information.
## FetchableRecord Protocol
📖 [`FetchableRecord`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/fetchablerecord)
**The FetchableRecord protocol grants fetching methods to any type** that can be built from a database row:
```swift
protocol FetchableRecord {
/// Row initializer
init(row: Row) throws
}
```
For example:
```swift
struct Place {
var id: Int64?
var title: String
var coordinate: CLLocationCoordinate2D
}
extension Place: FetchableRecord {
enum Columns {
static let id = Column("id")
static let title = Column("title")
static let latitude = Column("latitude")
static let longitude = Column("longitude")
}
init(row: Row) {
id = row[Columns.id]
title = row[Columns.title]
coordinate = CLLocationCoordinate2D(
latitude: row[Columns.latitude],
longitude: row[Columns.longitude])
}
}
```
See [column values](#column-values) for more information about the `row[]` subscript.
When your record type adopts the standard Decodable protocol, you don't have to provide the implementation for `init(row:)`. See [Codable Records] for more information:
```swift
// That's all
struct Player: Decodable, FetchableRecord {
var id: Int64
var name: String
var score: Int
enum Columns {
static let id = Column(CodingKeys.id)
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
}
```
FetchableRecord allows adopting types to be fetched from SQL queries:
```swift
try Place.fetchCursor(db, sql: "SELECT ...", arguments:...) // A Cursor of Place
try Place.fetchAll(db, sql: "SELECT ...", arguments:...) // [Place]
try Place.fetchSet(db, sql: "SELECT ...", arguments:...) // Set
try Place.fetchOne(db, sql: "SELECT ...", arguments:...) // Place?
```
See [fetching methods](#fetching-methods) for information about the `fetchCursor`, `fetchAll`, `fetchSet` and `fetchOne` methods. See [`StatementArguments`] for more information about the query arguments.
> **Note**: for performance reasons, the same row argument to `init(row:)` is reused during the iteration of a fetch query. If you want to keep the row for later use, make sure to store a copy: `self.row = row.copy()`.
> **Note**: The `FetchableRecord.init(row:)` initializer fits the needs of most applications. But some application are more demanding than others. When FetchableRecord does not exactly provide the support you need, have a look at the [Beyond FetchableRecord] chapter.
## TableRecord Protocol
📖 [`TableRecord`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/tablerecord)
**The TableRecord protocol** generates SQL for you:
```swift
protocol TableRecord {
static var databaseTableName: String { get }
static var databaseSelection: [any SQLSelectable] { get }
}
```
The `databaseSelection` type property is optional, and documented in the [Columns Selected by a Request] chapter.
The `databaseTableName` type property is the name of a database table. By default, it is derived from the type name:
```swift
struct Place: TableRecord { }
print(Place.databaseTableName) // prints "place"
```
For example:
- Place: `place`
- Country: `country`
- PostalAddress: `postalAddress`
- HTTPRequest: `httpRequest`
- TOEFL: `toefl`
You can still provide a custom table name:
```swift
struct Place: TableRecord {
static let databaseTableName = "location"
}
print(Place.databaseTableName) // prints "location"
```
When a type adopts both TableRecord and [FetchableRecord](#fetchablerecord-protocol), it can be fetched using the [query interface](#the-query-interface):
```swift
// SELECT * FROM place WHERE name = 'Paris'
let paris = try Place.filter { $0.name == "Paris" }.fetchOne(db)
```
TableRecord can also fetch deal with primary and unique keys: see [Fetching by Key](#fetching-by-key) and [Testing for Record Existence](#testing-for-record-existence).
## PersistableRecord Protocol
📖 [`EncodableRecord`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/encodablerecord), [`MutablePersistableRecord`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/mutablepersistablerecord), [`PersistableRecord`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/persistablerecord)
**GRDB record types can create, update, and delete rows in the database.**
Those abilities are granted by three protocols:
```swift
// Defines how a record encodes itself into the database
protocol EncodableRecord {
/// Defines the values persisted in the database
func encode(to container: inout PersistenceContainer) throws
}
// Adds persistence methods
protocol MutablePersistableRecord: TableRecord, EncodableRecord {
/// Optional method that lets your adopting type store its rowID upon
/// successful insertion. Don't call it directly: it is called for you.
mutating func didInsert(_ inserted: InsertionSuccess)
}
// Adds immutability
protocol PersistableRecord: MutablePersistableRecord {
/// Non-mutating version of the optional didInsert(_:)
func didInsert(_ inserted: InsertionSuccess)
}
```
Yes, three protocols instead of one. Here is how you pick one or the other:
- **If your type is a class**, choose `PersistableRecord`. On top of that, implement `didInsert(_:)` if the database table has an auto-incremented primary key.
- **If your type is a struct, and the database table has an auto-incremented primary key**, choose `MutablePersistableRecord`, and implement `didInsert(_:)`.
- **Otherwise**, choose `PersistableRecord`, and ignore `didInsert(_:)`.
The `encode(to:)` method defines which [values](#values) (Bool, Int, String, Date, Swift enums, etc.) are assigned to database columns.
The optional `didInsert` method lets the adopting type store its rowID after successful insertion, and is only useful for tables that have an auto-incremented primary key. It is called from a protected dispatch queue, and serialized with all database updates.
For example:
```swift
extension Place: MutablePersistableRecord {
enum Columns {
static let id = Column("id")
static let title = Column("title")
static let latitude = Column("latitude")
static let longitude = Column("longitude")
}
/// The values persisted in the database
func encode(to container: inout PersistenceContainer) {
container[Columns.id] = id
container[Columns.title] = title
container[Columns.latitude] = coordinate.latitude
container[Columns.longitude] = coordinate.longitude
}
// Update auto-incremented id upon successful insertion
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
var paris = Place(
id: nil,
title: "Paris",
coordinate: CLLocationCoordinate2D(latitude: 48.8534100, longitude: 2.3488000))
try paris.insert(db)
paris.id // some value
```
When your record type adopts the standard Encodable protocol, you don't have to provide the implementation for `encode(to:)`. See [Codable Records] for more information:
```swift
// That's all
struct Player: Encodable, MutablePersistableRecord {
var id: Int64?
var name: String
var score: Int
enum Columns {
static let id = Column(CodingKeys.id)
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
// Update auto-incremented id upon successful insertion
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
```
### Persistence Methods
Types that adopt the [PersistableRecord] protocol are given methods that insert, update, and delete:
```swift
// INSERT
try place.insert(db)
let insertedPlace = try place.inserted(db) // non-mutating
// UPDATE
try place.update(db)
try place.update(db, columns: ["title"])
// Maybe UPDATE
try place.updateChanges(db, from: otherPlace)
try place.updateChanges(db) { $0.isFavorite = true }
// INSERT or UPDATE
try place.save(db)
let savedPlace = place.saved(db) // non-mutating
// UPSERT
try place.upsert(db)
let insertedPlace = place.upsertAndFetch(db)
// DELETE
try place.delete(db)
// EXISTENCE CHECK
let exists = try place.exists(db)
```
See [Upsert](#upsert) below for more information about upserts.
**The [TableRecord] protocol comes with batch operations**:
```swift
// UPDATE
try Place.updateAll(db, ...)
// DELETE
try Place.deleteAll(db)
try Place.deleteAll(db, ids:...)
try Place.deleteAll(db, keys:...)
try Place.deleteOne(db, id:...)
try Place.deleteOne(db, key:...)
```
For more information about batch updates, see [Update Requests](#update-requests).
- All persistence methods can throw a [DatabaseError](#error-handling).
- `update` and `updateChanges` throw [RecordError] if the database does not contain any row for the primary key of the record.
- `save` makes sure your values are stored in the database. It performs an UPDATE if the record has a non-null primary key, and then, if no row was modified, an INSERT. It directly performs an INSERT if the record has no primary key, or a null primary key.
- `delete` and `deleteOne` returns whether a database row was deleted or not. `deleteAll` returns the number of deleted rows. `updateAll` returns the number of updated rows. `updateChanges` returns whether a database row was updated or not.
**All primary keys are supported**, including composite primary keys that span several columns, and the [hidden `rowid` column](https://www.sqlite.org/rowidtable.html).
**To customize persistence methods**, you provide [Persistence Callbacks], described below. Do not attempt at overriding the ready-made persistence methods.
### Upsert
[UPSERT](https://www.sqlite.org/lang_UPSERT.html) is an SQLite feature that causes an INSERT to behave as an UPDATE or a no-op if the INSERT would violate a uniqueness constraint (primary key or unique index).
> **Note**: Upsert apis are available from SQLite 3.35.0+: iOS 15.0+, macOS 12.0+, tvOS 15.0+, watchOS 8.0+, or with a [custom SQLite build] or [SQLCipher](#encryption).
>
> **Note**: With regard to [persistence callbacks](#available-callbacks), an upsert behaves exactly like an insert. In particular: the `aroundInsert(_:)` and `didInsert(_:)` callbacks reports the rowid of the inserted or updated row; `willUpdate`, `aroundUdate`, `didUdate` are not called.
[PersistableRecord] provides three upsert methods:
- `upsert(_:)`
Inserts or updates a record.
The upsert behavior is triggered by a violation of any uniqueness constraint on the table (primary key or unique index). In case of conflict, all columns but the primary key are overwritten with the inserted values:
```swift
struct Player: Encodable, PersistableRecord {
var id: Int64
var name: String
var score: Int
}
// INSERT INTO player (id, name, score)
// VALUES (1, 'Arthur', 1000)
// ON CONFLICT DO UPDATE SET
// name = excluded.name,
// score = excluded.score
let player = Player(id: 1, name: "Arthur", score: 1000)
try player.upsert(db)
```
- `upsertAndFetch(_:onConflict:doUpdate:)` (requires [FetchableRecord] conformance)
Inserts or updates a record, and returns the upserted record.
The `onConflict` and `doUpdate` arguments let you further control the upsert behavior. Make sure you check the [SQLite UPSERT documentation](https://www.sqlite.org/lang_UPSERT.html) for detailed information.
- `onConflict`: the "conflict target" is the array of columns in the uniqueness constraint (primary key or unique index) that triggers the upsert.
If empty (the default), all uniqueness constraint are considered.
- `doUpdate`: a closure that returns columns assignments to perform in case of conflict. Other columns are overwritten with the inserted values.
By default, all inserted columns but the primary key and the conflict target are overwritten.
In the example below, we upsert the new vocabulary word "jovial". It is inserted if that word is not already in the dictionary. Otherwise, `count` is incremented, `isTainted` is not overwritten, and `kind` is overwritten:
```swift
// CREATE TABLE vocabulary(
// word TEXT NOT NULL PRIMARY KEY,
// kind TEXT NOT NULL,
// isTainted BOOLEAN DEFAULT 0,
// count INT DEFAULT 1))
struct Vocabulary: Encodable, PersistableRecord {
var word: String
var kind: String
var isTainted: Bool
}
// INSERT INTO vocabulary(word, kind, isTainted)
// VALUES('jovial', 'adjective', 0)
// ON CONFLICT(word) DO UPDATE SET \
// count = count + 1, -- on conflict, count is incremented
// kind = excluded.kind -- on conflict, kind is overwritten
// RETURNING *
let vocabulary = Vocabulary(word: "jovial", kind: "adjective", isTainted: false)
let upserted = try vocabulary.upsertAndFetch(
db, onConflict: ["word"],
doUpdate: { _ in
[Column("count") += 1, // on conflict, count is incremented
Column("isTainted").noOverwrite] // on conflict, isTainted is NOT overwritten
})
```
The `doUpdate` closure accepts an `excluded` TableAlias argument that refers to the inserted values that trigger the conflict. You can use it to specify an explicit overwrite, or to perform a computation. In the next example, the upsert keeps the maximum date in case of conflict:
```swift
// INSERT INTO message(id, text, date)
// VALUES(...)
// ON CONFLICT DO UPDATE SET \
// text = excluded.text,
// date = MAX(date, excluded.date)
// RETURNING *
let upserted = try message.upsertAndFetch(doUpdate: { excluded in
// keep the maximum date in case of conflict
[Column("date").set(to: max(Column("date"), excluded["date"]))]
})
```
- `upsertAndFetch(_:as:onConflict:doUpdate:)` (does not require [FetchableRecord] conformance)
This method is identical to `upsertAndFetch(_:onConflict:doUpdate:)` described above, but you can provide a distinct [FetchableRecord] record type as a result, in order to specify the returned columns.
### Persistence Methods and the `RETURNING` clause
SQLite is able to return values from a inserted, updated, or deleted row, with the [`RETURNING` clause](https://www.sqlite.org/lang_returning.html).
> **Note**: Support for the `RETURNING` clause is available from SQLite 3.35.0+: iOS 15.0+, macOS 12.0+, tvOS 15.0+, watchOS 8.0+, or with a [custom SQLite build] or [SQLCipher](#encryption).
The `RETURNING` clause helps dealing with database features such as auto-incremented ids, default values, and [generated columns](https://sqlite.org/gencol.html). You can, for example, insert a few columns and fetch the default or generated ones in one step.
GRDB uses the `RETURNING` clause in all persistence methods that contain `AndFetch` in their name.
For example, given a database table with an auto-incremented primary key and a default score:
```swift
try dbQueue.write { db in
try db.execute(sql: """
CREATE TABLE player(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
score INTEGER NOT NULL DEFAULT 1000)
""")
}
```
You can define a record type with full database information, and another partial record type that deals with a subset of columns:
```swift
// A player with full database information
struct Player: Codable, PersistableRecord, FetchableRecord {
var id: Int64
var name: String
var score: Int
enum Columns {
static let id = Column(CodingKeys.id)
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
}
// A partial player
struct PartialPlayer: Encodable, PersistableRecord {
static let databaseTableName = "player"
var name: String
typealias Columns = Player.Columns
}
```
And now you can get a full player by inserting a partial one:
```swift
try dbQueue.write { db in
let partialPlayer = PartialPlayer(name: "Alice")
// INSERT INTO player (name) VALUES ('Alice') RETURNING *
let player = try partialPlayer.insertAndFetch(db, as: Player.self)
print(player.id) // The inserted id
print(player.name) // The inserted name
print(player.score) // The default score
}
```
For extra precision, you can select only the columns you need, and fetch the desired value from the provided prepared [`Statement`]:
```swift
try dbQueue.write { db in
let partialPlayer = PartialPlayer(name: "Alice")
// INSERT INTO player (name) VALUES ('Alice') RETURNING score
let score = try partialPlayer.insertAndFetch(db) { statement in
try Int.fetchOne(statement)
} select: {
[$0.score]
}
print(score) // Prints 1000, the default score
}
```
There are other similar persistence methods, such as `upsertAndFetch`, `saveAndFetch`, `updateAndFetch`, `updateChangesAndFetch`, etc. They all behave like `upsert`, `save`, `update`, `updateChanges`, except that they return saved values. For example:
```swift
// Save and return the saved player
let savedPlayer = try player.saveAndFetch(db)
```
See [Persistence Methods], [Upsert](#upsert), and [`updateChanges` methods](#the-updatechanges-methods) for more information.
**Batch operations** can return updated or deleted values:
> **Warning**: Make sure you check the [documentation of the `RETURNING` clause](https://www.sqlite.org/lang_returning.html#limitations_and_caveats), which describes important limitations and caveats for batch operations.
```swift
let request = Player.filter(...)...
// Fetch all deleted players
// DELETE FROM player RETURNING *
let deletedPlayers = try request.deleteAndFetchAll(db) // [Player]
// Fetch a selection of columns from the deleted rows
// DELETE FROM player RETURNING name
let statement = try request.deleteAndFetchStatement(db) { [$0.name] }
let deletedNames = try String.fetchSet(statement)
// Fetch all updated players
// UPDATE player SET score = score + 10 RETURNING *
let updatedPlayers = try request.updateAndFetchAll(db) { [$0.score += 10] } // [Player]
// Fetch a selection of columns from the updated rows
// UPDATE player SET score = score + 10 RETURNING score
let statement = try request.updateAndFetchStatement(db) {
[$0.score += 10]
} select: {
[$0.score]
}
let updatedScores = try Int.fetchAll(statement)
```
### Persistence Callbacks
Your custom type may want to perform extra work when the persistence methods are invoked.
To this end, your record type can implement **persistence callbacks**. Callbacks are methods that get called at certain moments of a record's life cycle. With callbacks it is possible to write code that will run whenever an record is inserted, updated, or deleted.
In order to use a callback method, you need to provide its implementation. For example, a frequently used callback is `didInsert`, in the case of auto-incremented database ids:
```swift
struct Player: MutablePersistableRecord {
var id: Int64?
// Update auto-incremented id upon successful insertion
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
try dbQueue.write { db in
var player = Player(id: nil, ...)
try player.insert(db)
print(player.id) // didInsert was called: prints some non-nil id
}
```
Callbacks can also help implementing record validation:
```swift
struct Link: PersistableRecord {
var url: URL
func willSave(_ db: Database) throws {
if url.host == nil {
throw ValidationError("url must be absolute.")
}
}
}
try link.insert(db) // Calls the willSave callback
try link.update(db) // Calls the willSave callback
try link.save(db) // Calls the willSave callback
try link.upsert(db) // Calls the willSave callback
```
#### Available Callbacks
Here is a list with all the available [persistence callbacks], listed in the same order in which they will get called during the respective operations:
- Inserting a record (all `record.insert` and `record.upsert` methods)
- `willSave`
- `aroundSave`
- `willInsert`
- `aroundInsert`
- `didInsert`
- `didSave`
- Updating a record (all `record.update` methods)
- `willSave`
- `aroundSave`
- `willUpdate`
- `aroundUpdate`
- `didUpdate`
- `didSave`
- Deleting a record (only the `record.delete(_:)` method)
- `willDelete`
- `aroundDelete`
- `didDelete`
For detailed information about each callback, check the [reference](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/mutablepersistablerecord/).
In the `MutablePersistableRecord` protocol, `willInsert` and `didInsert` are mutating methods. In `PersistableRecord`, they are not mutating.
> **Note**: The `record.save(_:)` method performs an UPDATE if the record has a non-null primary key, and then, if no row was modified, an INSERT. It directly performs an INSERT if the record has no primary key, or a null primary key. It triggers update and/or insert callbacks accordingly.
>
> **Warning**: Callbacks are only invoked from persistence methods called on record instances. Callbacks are not invoked when you call a type method, perform a batch operations, or execute raw SQL.
>
> **Warning**: When a `did***` callback is invoked, do not assume that the change is actually persisted on disk, because the database may still be inside an uncommitted transaction. When you need to handle transaction completions, use the [afterNextTransaction(onCommit:onRollback:)](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/database/afternexttransaction(oncommit:onrollback:)). For example:
>
> ```swift
> struct PictureFile: PersistableRecord {
> var path: String
>
> func willDelete(_ db: Database) {
> db.afterNextTransaction { _ in
> try? deleteFileOnDisk()
> }
> }
> }
> ```
## Identifiable Records
**When a record type maps a table with a single-column primary key, it is recommended to have it adopt the standard [Identifiable] protocol.**
```swift
struct Player: Identifiable, FetchableRecord, PersistableRecord {
var id: Int64 // fulfills the Identifiable requirement
var name: String
var score: Int
}
```
When `id` has a [database-compatible type](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasevalueconvertible) (Int64, Int, String, UUID, ...), the `Identifiable` conformance unlocks type-safe record and request methods:
```swift
let player = try Player.find(db, id: 1) // Player
let player = try Player.fetchOne(db, id: 1) // Player?
let players = try Player.fetchAll(db, ids: [1, 2, 3]) // [Player]
let players = try Player.fetchSet(db, ids: [1, 2, 3]) // Set
let request = Player.filter(id: 1)
let request = Player.filter(ids: [1, 2, 3])
try Player.deleteOne(db, id: 1)
try Player.deleteAll(db, ids: [1, 2, 3])
```
> **Note**: Not all record types can be made `Identifiable`, and not all tables have a single-column primary key. GRDB provides other methods that deal with primary and unique keys, but they won't check the type of their arguments:
>
> ```swift
> // Available on non-Identifiable types
> try Player.fetchOne(db, key: 1)
> try Player.fetchOne(db, key: ["email": "arthur@example.com"])
> try Country.fetchAll(db, keys: ["FR", "US"])
> try Citizenship.fetchOne(db, key: ["citizenId": 1, "countryCode": "FR"])
>
> let request = Player.filter(key: 1)
> let request = Player.filter(keys: [1, 2, 3])
>
> try Player.deleteOne(db, key: 1)
> try Player.deleteAll(db, keys: [1, 2, 3])
> ```
> **Note**: It is not recommended to use `Identifiable` on record types that use an auto-incremented primary key:
>
> ```swift
> // AVOID declaring Identifiable conformance when key is auto-incremented
> struct Player {
> var id: Int64? // Not an id suitable for Identifiable
> var name: String
> var score: Int
> }
>
> extension Player: FetchableRecord, MutablePersistableRecord {
> // Update auto-incremented id upon successful insertion
> mutating func didInsert(_ inserted: InsertionSuccess) {
> id = inserted.rowID
> }
> }
> ```
>
> For a detailed rationale, please see [issue #1435](https://github.com/groue/GRDB.swift/issues/1435#issuecomment-1740857712).
Some database tables have a single-column primary key which is not called "id":
```swift
try db.create(table: "country") { t in
t.primaryKey("isoCode", .text)
t.column("name", .text).notNull()
t.column("population", .integer).notNull()
}
```
In this case, `Identifiable` conformance can be achieved, for example, by returning the primary key column from the `id` property:
```swift
struct Country: Identifiable, FetchableRecord, PersistableRecord {
var isoCode: String
var name: String
var population: Int
// Fulfill the Identifiable requirement
var id: String { isoCode }
}
let france = try dbQueue.read { db in
try Country.fetchOne(db, id: "FR")
}
```
## Codable Records
Record types that adopt an archival protocol ([Codable, Encodable or Decodable](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types)) get free database support just by declaring conformance to the desired [record protocols](#record-protocols-overview):
```swift
// Declare a record...
struct Player: Codable, FetchableRecord, PersistableRecord {
var id: Int64
var name: String
var score: Int
enum Columns {
static let id = Column(CodingKeys.id)
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
}
// ...and there you go:
try dbQueue.write { db in
try Player(id: 1, name: "Arthur", score: 100).insert(db)
let players = try Player.order(\.score.desc).fetchAll(db)
}
```
Codable records encode and decode their properties according to their own implementation of the Encodable and Decodable protocols. Yet databases have specific requirements:
- Properties are always coded according to their preferred database representation, when they have one (all [values](#values) that adopt the [`DatabaseValueConvertible`] protocol).
- You can customize the encoding and decoding of dates and uuids.
- Complex properties (arrays, dictionaries, nested structs, etc.) are stored as JSON.
For more information about Codable records, see:
- [JSON Columns]
- [Column Names Coding Strategies]
- [Data, Date, and UUID Coding Strategies]
- [The userInfo Dictionary]
- [Tip: Derive Columns from Coding Keys](#tip-derive-columns-from-coding-keys)
> :bulb: **Tip**: see the [Demo Applications] for sample code that uses Codable records.
### JSON Columns
When a [Codable record](#codable-records) contains a property that is not a simple [value](#values) (Bool, Int, String, Date, Swift enums, etc.), that value is encoded and decoded as a **JSON string**. For example:
```swift
enum AchievementColor: String, Codable {
case bronze, silver, gold
}
struct Achievement: Codable {
var name: String
var color: AchievementColor
}
struct Player: Codable, FetchableRecord, PersistableRecord {
var name: String
var score: Int
var achievements: [Achievement] // stored in a JSON column
}
try dbQueue.write { db in
// INSERT INTO player (name, score, achievements)
// VALUES (
// 'Arthur',
// 100,
// '[{"color":"gold","name":"Use Codable Records"}]')
let achievement = Achievement(name: "Use Codable Records", color: .gold)
let player = Player(name: "Arthur", score: 100, achievements: [achievement])
try player.insert(db)
}
```
GRDB uses the standard [JSONDecoder](https://developer.apple.com/documentation/foundation/jsondecoder) and [JSONEncoder](https://developer.apple.com/documentation/foundation/jsonencoder) from Foundation. By default, Data values are handled with the `.base64` strategy, Date with the `.millisecondsSince1970` strategy, and non conforming floats with the `.throw` strategy.
You can customize the JSON format by implementing those methods:
```swift
protocol FetchableRecord {
static func databaseJSONDecoder(for column: String) -> JSONDecoder
}
protocol EncodableRecord {
static func databaseJSONEncoder(for column: String) -> JSONEncoder
}
```
> :bulb: **Tip**: Make sure you set the JSONEncoder `sortedKeys` option. This option makes sure that the JSON output is stable. This stability is required for [Record Comparison] to work as expected, and database observation tools such as [ValueObservation] to accurately recognize changed records.
### Column Names Coding Strategies
By default, [Codable Records] store their values into database columns that match their coding keys: the `teamID` property is stored into the `teamID` column.
This behavior can be overridden, so that you can, for example, store the `teamID` property into the `team_id` column:
```swift
protocol FetchableRecord {
static var databaseColumnDecodingStrategy: DatabaseColumnDecodingStrategy { get }
}
protocol EncodableRecord {
static var databaseColumnEncodingStrategy: DatabaseColumnEncodingStrategy { get }
}
```
See [DatabaseColumnDecodingStrategy](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasecolumndecodingstrategy) and [DatabaseColumnEncodingStrategy](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasecolumnencodingstrategy/) to learn about all available strategies.
### Data, Date, and UUID Coding Strategies
By default, [Codable Records] encode and decode their Data properties as blobs, and Date and UUID properties as described in the general [Date and DateComponents](#date-and-datecomponents) and [UUID](#uuid) chapters.
To sum up: dates encode themselves in the "YYYY-MM-DD HH:MM:SS.SSS" format, in the UTC time zone, and decode a variety of date formats and timestamps. UUIDs encode themselves as 16-bytes data blobs, and decode both 16-bytes data blobs and strings such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F".
Those behaviors can be overridden:
```swift
protocol FetchableRecord {
static func databaseDataDecodingStrategy(for column: String) -> DatabaseDataDecodingStrategy
static func databaseDateDecodingStrategy(for column: String) -> DatabaseDateDecodingStrategy
}
protocol EncodableRecord {
static func databaseDataEncodingStrategy(for column: String) -> DatabaseDataEncodingStrategy
static func databaseDateEncodingStrategy(for column: String) -> DatabaseDateEncodingStrategy
static func databaseUUIDEncodingStrategy(for column: String) -> DatabaseUUIDEncodingStrategy
}
```
See [DatabaseDataDecodingStrategy](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasedatadecodingstrategy/), [DatabaseDateDecodingStrategy](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasedatedecodingstrategy/), [DatabaseDataEncodingStrategy](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasedataencodingstrategy/), [DatabaseDateEncodingStrategy](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasedateencodingstrategy/), and [DatabaseUUIDEncodingStrategy](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databaseuuidencodingstrategy/) to learn about all available strategies.
There is no customization of uuid decoding, because UUID can already decode all