Skip to main content
Find answers to common questions about GRDB, from opening connections to debugging queries.

Opening Connections

First choose a proper location for the database file. Document-based applications will let the user pick a location. Apps that use the database as a global storage will prefer the Application Support directory.The sample code below creates or opens a database file inside its dedicated directory (a recommended practice). On the first run, a new empty database file is created. On subsequent runs, the database file already exists, so it just opens a connection:
// Create the "Application Support/MyDatabase" directory
let fileManager = FileManager.default
let appSupportURL = try fileManager.url(
    for: .applicationSupportDirectory, in: .userDomainMask,
    appropriateFor: nil, create: true) 
let directoryURL = appSupportURL.appendingPathComponent("MyDatabase", isDirectory: true)
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)

// Open or create the database
let databaseURL = directoryURL.appendingPathComponent("db.sqlite")
let dbQueue = try DatabaseQueue(path: databaseURL.path)
Open a read-only connection to your resource:
// Get the path to the database resource
if let dbPath = Bundle.main.path(forResource: "db", ofType: "sqlite") {
    // If the resource exists, open a read-only connection
    // Writes are disallowed because resources cannot be modified
    var config = Configuration()
    config.readonly = true
    let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)
} else {
    // The database resource cannot be found
    // Fix your setup, or report the problem to the user
}
Database connections are automatically closed when DatabaseQueue or DatabasePool instances are deinitialized.If the correct execution of your program depends on precise database closing, perform an explicit call to close(). This method may fail and create zombie connections, so please check its detailed documentation.

SQL and Debugging

When debugging a request that doesn’t deliver expected results, you can compile the request into a prepared statement:
try dbQueue.read { db in
    let request = Player.filter { $0.email == "[email protected]" }
    let statement = try request.makePreparedRequest(db).statement
    print(statement) // SELECT * FROM player WHERE email = ?
    print(statement.arguments) // ["[email protected]"]
}
Or setup a tracing function:
var config = Configuration()
config.prepareDatabase { db in
    db.trace { print($0) }
}
let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)
The generated SQL may change between GRDB releases without notice: don’t have your application rely on any specific SQL output.
Use the trace(options:_:) method with the .profile option:
var config = Configuration()
config.prepareDatabase { db in
    db.trace(options: .profile) { event in
        // Prints all SQL statements with their duration
        print(event)
        
        // Access detailed profiling information
        if case let .profile(statement, duration) = event, duration > 0.5 {
            print("Slow query: \(statement.sql)")
        }
    }
}
let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)

Features and Compatibility

Since GRDB 1.0, all backwards compatibility guarantees of semantic versioning apply: no breaking change will happen until the next major version of the library.There is an exception: experimental features, marked with the ”🔥 EXPERIMENTAL” badge. These are advanced features that are too young or lack user feedback. They are not stabilized yet.Experimental features are not protected by semantic versioning and may break between two minor releases of the library. Your feedback is greatly appreciated to help them become stable.
No, GRDB does not support library evolution and ABI stability. The only promise is API stability according to semantic versioning, with an exception for experimental features.However, GRDB can be built with the “Build Libraries for Distribution” Xcode option (BUILD_LIBRARY_FOR_DISTRIBUTION), so you can build binary frameworks at your convenience.

Associations

Use joining(required:) to only fetch records that have an association:
struct Book: TableRecord {
    static let author = belongsTo(Author.self)
}

let books: [Book] = try dbQueue.read { db in
    // SELECT book.* FROM book 
    // JOIN author ON author.id = book.authorID
    let request = Book.joining(required: Book.author)
    return try request.fetchAll(db)
}
This fetches only books that have an author, discarding anonymous ones.
Use a combination of optional join and filter:
let books: [Book] = try dbQueue.read { db in
    // SELECT book.* FROM book
    // LEFT JOIN author ON author.id = book.authorID
    // WHERE author.id IS NULL
    let authorAlias = TableAlias<Author>()
    let request = Book
        .joining(optional: Book.author.aliased(authorAlias))
        .filter(!authorAlias.exists)
    return try request.fetchAll(db)
}
Use annotated(withOptional:) to append specific columns:
struct BookInfo: Decodable, FetchableRecord {
    var book: Book
    var authorName: String?
    
    static func all() -> QueryInterfaceRequest<BookInfo> {
        return Book
            .annotated(withOptional: Book.author.select { 
                $0.name.forKey(CodingKeys.authorName)
            })
            .asRequest(of: BookInfo.self)
    }
}

ValueObservation

There may be four possible reasons:
  1. Changes not committed: The expected changes were not committed into the database
  2. Changes overwritten: The changes were committed but quickly overwritten
  3. Observation stopped: The observation was stopped or cancelled
  4. Wrong region tracked: The observation doesn’t track the expected database region
To debug, enable SQL tracing:
var config = Configuration()
config.prepareDatabase { db in
    db.trace { print("SQL: \($0)") }
}
let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)
See the ValueObservation documentation for more troubleshooting tips.

Need More Help?

GitHub Discussions

Ask questions and get help from the community

Swift Forums

Discuss GRDB on the Swift forums

Report Issues

Found a bug? Report it on GitHub

Documentation

Browse the complete API reference

Build docs developers (and LLMs) love