
A toolkit for SQLite databases, with a focus on application development
Proudly serving the community since 2015
Latest release: June 18, 2026 • version 7.11.1 • CHANGELOG • Migrating From GRDB 6 to GRDB 7
Requirements: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 7.0+ • SQLite 3.20.0+ • Swift 6.1+ / Xcode 16.3+
Contact:
- Release announcements and usage tips: follow @groue@hachyderm.io on Mastodon.
- Report bugs in a Github issue. Make sure you check the existing issues first.
- A question? Looking for advice? Do you wonder how to contribute? Fancy a chat? Go to the GitHub discussions, or the GRDB forums.
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
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
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))
""")
}
Access to raw database rows and values
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
Database model types aka "records"
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 player.update(db)
try player.updateChanges { $0.score += 10 }
// Delete a record
try player.delete(db)
}
See Records
Query the database with the Swift query interface
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
Database changes notifications
// 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 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. For general and detailed information, jump to the SQLite Documentation.
Demo Applications & Frequently Asked Questions
- [Demo Applications]
- [FAQ]
Reference
Getting Started
- Installation
- [Database Connections]: Connect to SQLite databases
SQLite and SQL
- SQLite API: The low-level SQLite API • executing updates • fetch queries • [SQL Interpolation]
Records and the Query Interface
- Records: Fetching and persistence methods for your custom structs and class hierarchies
- Query Interface: A swift way to generate SQL • create tables, indexes, etc • requests • associations between record types
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: Encrypt your database with SQLCipher.
- Backup: Dump the content of a database to another.
- 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: Access and observe the database with Combine publishers.
- Avoiding SQL Injection
- Error Handling
- Unicode
- Memory Management
- Data Protection
- :bulb: Migrating From GRDB 6 to GRDB 7
- :bulb: Why Adopt GRDB?
- :bulb: Recommended Practices for Designing Record Types
Companion Libraries
- GRDBQuery: Access and observe the database from your SwiftUI views.
- GRDBSnapshotTesting: Test your database.
[FAQ]
Installation
The installation procedures below have GRDB use the version of SQLite that ships with the target operating system.
See Encryption for the installation procedure of GRDB with SQLCipher.
See Custom SQLite builds for the installation procedure of GRDB with a customized build of SQLite.
Swift Package Manager
The Swift 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 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 is a dependency manager for Xcode projects. To use GRDB with CocoaPods (version 1.2 or higher), specify in your Podfile:
pod 'GRDB.swift'
GRDB can be installed as a framework, or a static library.
Important Note for CocoaPods installation
Due to an issue 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
GRDB7branch. This is more or less equivalent to whatpod 'GRDB.swift', '~> 7.0'would normally do, if CocoaPods would accept new GRDB versions to be published:# 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):
# 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 is unsupported. For some context about this decision, see #433.
Manually
Download a copy of GRDB, or clone its repository and make sure you checkout the latest tagged version.
Embed the
GRDB.xcodeprojproject in your own project.Add the
GRDBtarget in the Target Dependencies section of the Build Phases tab of your application target (extension target for WatchOS).Add the
GRDB.frameworkto 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]:
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 (unless read-only).
- Database queues support in-memory databases.
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.
SQLite API
In this section of the documentation, we will talk SQL. Jump to the query interface if SQL is not your cup of tea.
- Executing Updates
- Fetch Queries
- Values
- Data
- Date and DateComponents
- NSNumber, NSDecimalNumber, and Decimal
- Swift enums
- [
DatabaseValueConvertible]: the protocol for custom value types
- [Transactions and Savepoints]
- [SQL Interpolation]
Advanced topics:
- [Prepared Statements]
- Custom SQL Functions and Aggregates
- Database Schema Introspection
- Row Adapters
- Raw SQLite Pointers
Executing Updates
Once granted with a [database connection], the 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:
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 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:), as in the example below. See [SQL Interpolation] for more details.
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 for more information:
// 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:
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:
try db.execute(
sql: "INSERT INTO player (name, score) VALUES (?, ?)",
arguments: ["Arthur", 1000])
let playerId = db.lastInsertedRowID
Don't miss Records, that provide classic persistence methods:
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:
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:
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:
let wines = try dbQueue.read { db in
try Wine.fetchAll(db, sql: "SELECT * FROM wine")
}
Fetching Methods
Throughout GRDB, you can always fetch cursors, arrays, sets, or single values of any fetchable type (database row, simple value, or custom record):
try Row.fetchCursor(...) // A Cursor of Row
try Row.fetchAll(...) // [Row]
try Row.fetchSet(...) // Set<Row>
try Row.fetchOne(...) // Row?
fetchCursorreturns a cursor over fetched values:let rows = try Row.fetchCursor(db, sql: "SELECT ...") // A Cursor of RowfetchAllreturns an array:let players = try Player.fetchAll(db, sql: "SELECT ...") // [Player]fetchSetreturns a set:let names = try String.fetchSet(db, sql: "SELECT ...") // SetfetchOnereturns a single optional value, and consumes a single database row (if any).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
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:
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:
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:
// 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:
// 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
fetchAllorfetchSetwhen 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 optionalminimumCapacityargument which helps optimizing your app when you have an idea of the number of elements in a cursor (the built-infetchAllandfetchSetdo not perform such an optimization).Arrays and all types conforming to
RangeReplaceableCollection:// [String] let cursor = try String.fetchCursor(db, ...) let array = try Array(cursor)Sets:
// Set let cursor = try Int.fetchCursor(db, ...) let set = try Set(cursor)Dictionaries:
// [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 protocol, which looks a lot like standard lazy sequences 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:// 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:
// Undefined behavior while let player = try players.next() { try db.execute(sql: "DELETE ...") }Don't turn a cursor of
Rowinto an array or a set. You would not get the distinct rows you expect. To get a array of rows, useRow.fetchAll(...). To get a set of rows, useRow.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
Fetch cursors of rows, arrays, sets, or single rows (see fetching methods):
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<Row>
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:
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 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
Rowinto an array or a set. You would not get the distinct rows you expect. To get a array of rows, useRow.fetchAll(...). To get a set of rows, useRow.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:
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:
let name: String? = row["name"]
The row[] subscript returns the type you ask for. See Values for more information on supported value types:
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:
row[...] as Int
row[...] as Int?
Throwing accessors exist as well. Their use is not encouraged, because a database decoding error is a programming error. If an application stores invalid data in the database file, that is a bug that needs to be fixed:
let name = try row.decode(String.self, atIndex: 0)
let bookCount = try row.decode(Int.self, forColumn: "bookCount")
Warning: avoid the
as!andas?operators: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
coalescemethod instead: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 for more information on supported types (Bool, Int, String, Date, Swift enums, etc.)
NULL returns nil.
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 type:
row[0] as DatabaseValue // DatabaseValue.nullMissing columns return nil.
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: missingYou can explicitly check for a column presence with the
hasColumnmethod.Invalid conversions throw a fatal error.
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 type:
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 for more information.
SQLite has a weak type system, and provides convenience conversions that can turn String to Int, Double to Blob, etc.
GRDB will sometimes let those conversions go through:
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 type.
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:
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 (Bool, Int, String, Date, Swift enums, etc.) from DatabaseValue with the fromDatabaseValue() method:
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:
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 protocol, and can be seen as a dictionary of DatabaseValue:
// 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 for more information on supported types:
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:
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: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: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:let dict = Dictionary( row.map { (column, dbValue) in (column, dbValue.storage.value) }, uniquingKeysWith: { (left, _) in left })
See the documentation of Dictionary.init(_:uniquingKeysWith:) for more information.
Value Queries
Instead of rows, you can directly fetch values. There are many supported value types (Bool, Int, String, Date, Swift enums, etc.).
Like rows, fetch values as cursors, arrays, sets, or single values (see fetching methods). Values are extracted from the leftmost column of the SQL queries:
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<Int>
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:
// 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:
try dbQueue.read { db in
try Optional<Int>.fetchCursor(db, sql: "SELECT ...", arguments: ...) // A Cursor of Int?
try Optional<Int>.fetchAll(db, sql: "SELECT ...", arguments: ...) // [Int?]
try Optional<Int>.fetchSet(db, sql: "SELECT ...", arguments: ...) // Set<Int?>
}
: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 optionalInt??:// 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 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.
Foundation: Data, Date, DateComponents, Decimal, NSNull, NSNumber, NSString, URL, UUID.
CoreGraphics: CGFloat.
DatabaseValue, the type which gives information about the raw value stored in the database.
Full-Text Patterns: FTS3Pattern and FTS5Pattern.
Generally speaking, all types that adopt the [
DatabaseValueConvertible] protocol.
Values can be used as statement arguments:
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:
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:
let urls = try URL.fetchAll(db, sql: "SELECT url FROM link") // [URL]
Use values in Records:
struct Link: FetchableRecord {
var url: URL
var isVerified: Bool
init(row: Row) {
url = row["url"]
isVerified = row["verified"]
}
}
Use values in the query interface:
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:
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:
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 and DateComponents can be stored and fetched from the database.
Here is how GRDB supports the various date formats 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-DDTHH:MM | Read ¹ ² | Read ² |
| YYYY-MM-DDTHH:MM:SS | Read ¹ ² | Read ² |
| YYYY-MM-DDTHH: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. 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:
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 dateworks)- Comparable with the SQLite keyword CURRENT_TIMESTAMP (
WHERE date > CURRENT_TIMESTAMPworks)- Able to feed SQLite date & time functions
- 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.
Some applications may prefer another date format:
- Some may prefer ISO-8601, with a
Tseparator. - 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
Dateroundtrip. - 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
Dateroundtrip 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,Datecomparison is at least as hard and nasty as floating point comparison.
The customization of date format is explicit. For example:
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, 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. See Date for more information.
DatabaseDateComponents can be stored and fetched from the database just like other values:
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.
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:
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") // DecimalAll three types decode database strings as decimal numbers:
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'") // DecimalNSNumberandNSDecimalNumbersend 64-bit signed integers and doubles in the database:// 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
NSDecimalNumbercan result in a loss of precision during the conversion to double.Instead of sending non-integer
NSDecimalNumberto the database, you may prefer:- Send
Decimalinstead (those store decimal strings in the database). - Send integers instead (for example, store amounts of cents instead of amounts of Euros).
- Send
Decimalsends decimal strings in the database:// 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.
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 protocol can be stored and fetched from the database just like their raw values:
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 type:
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:
SELECT reverse(name) FROM player; -- custom function
SELECT maxLength(name) FROM player; -- custom aggregate
Custom SQL Functions
A function argument takes an array of DatabaseValue, and returns any valid value (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.
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:
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:
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:
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:
// 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.
Custom Aggregates
📖 DatabaseFunction, DatabaseAggregate
Before registering a custom aggregate, you need to define a type that adopts the DatabaseAggregate protocol:
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:
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, you make an aggregate function available to a database connection through its configuration:
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. 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 (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:
// 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 and call SQLite C functions.
To access the C SQLite functions from SQLCipher or the system SQLite, you need to perform an extra import:
import SQLite3 // System SQLite
import SQLCipher // SQLCipher
let sqliteVersion = String(cString: sqlite3_libversion())
Raw pointers to database