GRDB.swift is a free, open source databases project written in Swift and released under MIT. It has 8,721 GitHub stars, 1,097 forks and 15 open issues, and was last pushed 8 hours ago. On this registry it ranks #102 of 206 tracked projects in Databases, with 5 head-to-head comparisons available. It gained 47 stars over the last 30 tracked days.

What is GRDB.swift?

GRDB.swift is an MIT-licensed Swift toolkit for SQLite databases, focused on application development, for Swift developers who need to store an app's permanent data on iOS, macOS, tvOS, or watchOS without hand-writing SQL and raw row handling.

What it is

GRDB.swift is a library that lets Swift applications save permanent data into SQLite databases. It lives in the Swift and Apple-platform ecosystem and is distributed as a Swift package. The library pairs SQLite with application-level tools: SQL generation from Swift models, database observation, concurrency support, and schema migrations. It has been maintained in public since 2015, with version 7.11.1 released on June 18, 2026, and it targets iOS 13.0+, macOS 10.15+, tvOS 13.0+, watchOS 7.0+, SQLite 3.20.0+, Swift 6.1+, and Xcode 16.3+.

The concrete problem it solves is the gap between an application's Swift model types and the SQLite C-level interface. Without such a toolkit, a developer writes SQL strings by hand, opens connections, and reads untyped database rows, converting each column into a value manually. GRDB.swift replaces that layer: records conforming to Codable, FetchableRecord, and PersistableRecord gain persistence and fetching methods, so models are inserted, queried, and ordered directly, and raw SQL is only written when the developer chooses to write it. The README states the intent plainly: "so that you don't have to deal with SQL and raw database rows when you don't want to."

Key capabilities

  • SQL generation from Swift types: Column, Player.order(\.score.desc).limit(10).fetchAll(db), and a four-step usage flow starting with try DatabaseQueue(path: "/path/to/database.sqlite").
  • Database observation: the database-observation topic and the README's Database Observation section cover notifications when database values are modified.
  • Robust concurrency for multi-threaded applications, including WAL databases that support concurrent reads and writes.
  • Migrations that evolve a schema as new application versions ship, with Documentation/GRDB7MigrationGuide.md documenting the GRDB 6 to GRDB 7 upgrade path.
  • Escape hatches to raw access: db.execute(sql:), Row.fetchCursor, Int.fetchOne, and String.fetchAll.
  • Safe SQL interpolation through db.execute(literal:), which the README presents as the way to avoid SQL injection.
  • Swift Package Manager support, reflected in the spm topic.

Who uses it and how

  • Apple-platform application developers storing permanent app data locally, across iOS, macOS, tvOS, and watchOS targets.
  • Multi-threaded applications that need efficient database use and WAL-mode concurrent reads and writes.
  • Teams shipping successive app versions that must migrate an existing SQLite schema without losing user data.
  • Developers who already know SQLite and want to keep using their skills, dropping to raw SQL and raw rows where the model layer is not enough.
  • Maintainers tracking releases and usage tips through the author's Mastodon account, GitHub issues, GitHub discussions, and the GRDB category on the Swift forums.

Getting started

Add the GRDB.swift package through Swift Package Manager and import GRDB; the README's four steps are opening a DatabaseQueue, defining a schema with db.create(table:), declaring a record type, then writing and reading inside dbQueue.write and dbQueue.read blocks.

How it compares

No paid products or competing libraries are named in the facts for this entry, so there is no licence, hosting, or cost-model comparison to make here. On the evidence available, GRDB.swift stands alone in this registry as a Swift SQLite toolkit.

When to use it — and when not to

Choose it for a Swift application on Apple platforms that needs embedded SQLite with model mapping, observation, and migrations, and that can meet the Swift 6.1+ and Xcode 16.3+ requirements alongside minimum OS versions of iOS 13.0, macOS 10.15, tvOS 13.0, and watchOS 7.0. Do not pick it for non-Swift projects, for server-side systems outside the Apple toolchain, or for teams that cannot upgrade their toolchain and deployment targets.

Budget for the upgrade work that major versions bring, since GRDB 6 to GRDB 7 required a dedicated migration guide, and expect to use SQL directly for anything beyond the model layer.

project readme (upstream, from github) — read inline
GRDB: A toolkit for SQLite databases, with a focus on application development.

A toolkit for SQLite databases, with a focus on application development
Proudly serving the community since 2015

Swift 6.1 License CI Status

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:

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))
        """)
}

See Executing Updates

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
Records and the Query Interface
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
Companion Libraries

[FAQ]

Sample Code

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 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:

    # 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

  1. Download 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]:

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.

Advanced topics:

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?
  • fetchCursor returns a cursor over fetched values:

    let rows = try Row.fetchCursor(db, sql: "SELECT ...") // A Cursor of Row
    
  • fetchAll returns an array:

    let players = try Player.fetchAll(db, sql: "SELECT ...") // [Player]
    
  • fetchSet returns a set:

    let names = try String.fetchSet(db, sql: "SELECT ...") // Set
    
  • fetchOne returns 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 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:

    // [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 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

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 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:

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! and as? 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 coalesce method 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.null
    
  • Missing 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: missing
    

    You can explicitly check for a column presence with the hasColumn method.

  • 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

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

📖 DatabaseValueConvertible

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 optional Int??:

// 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 date works)
  • Comparable with the SQLite keyword CURRENT_TIMESTAMP (WHERE date > CURRENT_TIMESTAMP works)
  • 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 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.

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")           // Decimal
    
  • All 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'")         // Decimal
    
  • NSNumber and NSDecimalNumber send 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 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:

    // 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

📖 DatabaseFunction

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

Frequently asked questions

Is GRDB.swift free to use?

GRDB.swift is open source under the MIT licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does GRDB.swift do?

A toolkit for SQLite databases, with a focus on application development

What is GRDB.swift written in?

GRDB.swift is primarily written in Swift. Its source is publicly available at https://github.com/groue/GRDB.swift, and it has 8,721 GitHub stars.