Database

A Database connection.

You don’t create a database directly. Instead, you use a DatabaseQueue, or a DatabasePool:

let dbQueue = try DatabaseQueue(...)

// The Database is the `db` in the closure:
try dbQueue.write { db in
    try Player(...).insert(db)
}

SQLite C API

  • The raw SQLite connection, suitable for the SQLite C API.

    It is nil after the database has been successfully closed with close().

Configuration

  • The error logging function.

    Quoting https://www.sqlite.org/errlog.html:

    SQLite can be configured to invoke a callback function containing an error code and a terse error message whenever anomalies occur. This mechanism is very helpful in tracking obscure problems that occur rarely and in the field. Application developers are encouraged to take advantage of the error logging facility of SQLite in their products, as it is very low CPU and memory cost but can be a huge aid for debugging.

  • The database configuration

Database Information

Limits

  • The maximum number of arguments accepted by an SQLite statement.

    For example, requests such as the one below must make sure the ids array does not contain more than maximumStatementArgumentCount elements:

    let ids: [Int] = ...
    try dbQueue.write { db in
        try Player.deleteAll(db, keys: ids)
    }
    

    See https://www.sqlite.org/limits.html and SQLITE_LIMIT_VARIABLE_NUMBER.

Functions

  • Add or redefine an SQL function.

    let fn = DatabaseFunction("succ", argumentCount: 1) { dbValues in
        guard let int = Int.fromDatabaseValue(dbValues[0]) else {
            return nil
        }
        return int + 1
    }
    db.add(function: fn)
    try Int.fetchOne(db, sql: "SELECT succ(1)")! // 2
    
  • Remove an SQL function.

Collations

  • Add or redefine a collation.

    let collation = DatabaseCollation("localized_standard") { (string1, string2) in
        return (string1 as NSString).localizedStandardCompare(string2)
    }
    db.add(collation: collation)
    try db.execute(sql: "CREATE TABLE files (name TEXT COLLATE localized_standard")
    
  • Remove a collation.

Trace

  • Registers a tracing function.

    For example:

    // Trace all SQL statements executed by the database
    var configuration = Configuration()
    configuration.prepareDatabase { db in
        db.trace(options: .statement) { event in
            print("SQL: \(event)")
        }
    }
    let dbQueue = try DatabaseQueue(path: "...", configuration: configuration)
    

    Pass an empty options set in order to stop database tracing:

    // Stop tracing
    db.trace(options: [])
    

    See https://www.sqlite.org/c3ref/trace_v2.html for more information.

WAL Checkpoints

Database Suspension

Transactions & Savepoint

  • Executes a block inside a database transaction.

    For example:

    try dbQueue.inDatabase do {
        try db.inTransaction {
            try db.execute(sql: "INSERT ...")
            return .commit
        }
    }
    

    If the block throws an error, the transaction is rollbacked and the error is rethrown.

    Warning

    This method is not reentrant: you can’t nest transactions. Use inSavepoint(_:) instead.

    Throws

    The error thrown by the block.

  • Executes a block inside a savepoint.

    For example:

    try dbQueue.inDatabase do {
        try db.inSavepoint {
            try db.execute(sql: "INSERT ...")
            return .commit
        }
    }
    

    If the block throws an error, the savepoint is rollbacked and the error is rethrown.

    This method is reentrant: you can nest savepoints.

    Throws

    The error thrown by the block.
  • Begins a database transaction.

    Throws

    A DatabaseError whenever an SQLite error occurs.

  • Rollbacks a database transaction.

  • Commits a database transaction.

Memory Management

Backup

  • Copies the database contents into another database.

    The backup method blocks the current thread until the destination database contains the same contents as the source database.

    Usage:

    let source: DatabaseQueue = ...
    let destination: DatabaseQueue = ...
    try source.write { sourceDb in
        try destination.barrierWriteWithoutTransaction { destDb in
            try sourceDb.backup(to: destDb)
        }
    }
    

    When you’re after progress reporting during backup, you’ll want to perform the backup in several steps. Each step copies the number of database pages you specify. See https://www.sqlite.org/c3ref/backup_finish.html for more information:

    // Backup with progress reporting
    try sourceDb.backup(
        to: destDb,
        pagesPerStep: ...)
        { backupProgress in
           print("Database backup progress:", backupProgress)
        }
    

    The progress callback will be called at least once—when backupProgress.isCompleted == true. If the callback throws when backupProgress.isCompleted == false, the backup is aborted and the error is rethrown. If the callback throws when backupProgress.isCompleted == true, backup completion is unaffected and the error is silently ignored.

    See also DatabaseReader.backup().

    Throws

    The error thrown by progress if the backup is abandoned, or any DatabaseError that would happen while performing the backup.

Database Schema

  • Clears the database schema cache.

    You may need to clear the cache manually if the database schema is modified by another connection.

  • Returns whether a table exists, in the main or temp schema, or in an attached database.

  • Returns whether a table is an internal SQLite table.

    Those are tables whose name begins with sqlite_ and pragma_.

    For more information, see https://www.sqlite.org/fileformat2.html

  • Returns whether a table is an internal GRDB table.

    Those are tables whose name begins with “grdb_”.

  • Returns whether a view exists, in the main or temp schema, or in an attached database.

  • Returns whether a trigger exists, in the main or temp schema, or in an attached database.

  • The primary key for table named tableName.

    All tables have a primary key, even when it is not explicit. When a table has no explicit primary key, the result is the hidden “rowid” column.

    Throws

    A DatabaseError if table does not exist.
  • The indexes on table named tableName.

    Only indexes on columns are returned. Indexes on expressions are not returned.

    SQLite does not define any index for INTEGER PRIMARY KEY columns: this method does not return any index that represents the primary key.

    If you want to know if a set of columns uniquely identifies a row, because the columns contain the primary key or a unique index, use table(_:hasUniqueKey:).

    Throws

    A DatabaseError if table does not exist.
  • True if a sequence of columns uniquely identifies a row, that is to say if the columns contain the primary key, or a unique index.

    For example:

    // CREATE TABLE t(id INTEGER PRIMARY KEY, a, b, c);
    // CREATE UNIQUE INDEX i ON t(a, b);
    try db.table("t", hasUniqueKey: ["id"])                // true
    try db.table("t", hasUniqueKey: ["a", "b"])            // true
    try db.table("t", hasUniqueKey: ["c"])                 // false
    try db.table("t", hasUniqueKey: ["id", "a"])           // true
    try db.table("t", hasUniqueKey: ["id", "a", "b", "c"]) // true
    
  • The foreign keys defined on table named tableName.

    Throws

    A DatabaseError if table does not exist.
  • Returns a cursor over foreign key violations in the database.

  • Returns a cursor over foreign key violations in the table.

  • Throws a DatabaseError of extended code SQLITE_CONSTRAINT_FOREIGNKEY if there exists a foreign key violation in the database.

  • Throws a DatabaseError of extended code SQLITE_CONSTRAINT_FOREIGNKEY if there exists a foreign key violation in the table.

  • The columns in the table, or view, named tableName.

    Throws

    A DatabaseError if table does not exist.

Statements

  • Returns a new prepared statement that can be reused.

    For example:

    let statement = try db.makeStatement(sql: "SELECT * FROM player WHERE id = ?")
    let player1 = try Player.fetchOne(statement, arguments: [1])!
    let player2 = try Player.fetchOne(statement, arguments: [2])!
    
    let statement = try db.makeStatement(sql: "INSERT INTO player (name) VALUES (?)")
    try statement.execute(arguments: ["Arthur"])
    try statement.execute(arguments: ["Barbara"])
    

    Throws

    A DatabaseError whenever SQLite could not parse the sql query.
  • Returns a new prepared statement that can be reused.

    For example:

    let statement = try db.makeStatement(literal: "SELECT * FROM player WHERE id = ?")
    let player1 = try Player.fetchOne(statement, arguments: [1])!
    let player2 = try Player.fetchOne(statement, arguments: [2])!
    
    let statement = try db.makeStatement(literal: "INSERT INTO player (name) VALUES (?)")
    try statement.execute(arguments: ["Arthur"])
    try statement.execute(arguments: ["Barbara"])
    

    Throws

    A DatabaseError whenever SQLite could not parse the sql query.

    Precondition

    No argument must be set, or all arguments must be set. An error is raised otherwise.

    // OK
    try makeStatement(literal: """
        SELECT COUNT(*) FROM player WHERE score > ?
        """)
    try makeStatement(literal: """
        SELECT COUNT(*) FROM player WHERE score > \(1000)
        """)
    
    // NOT OK
    try makeStatement(literal: """
        SELECT COUNT(*) FROM player
        WHERE color = ? AND score > \(1000)
        """)
    
  • Returns a prepared statement that can be reused.

    For example:

    let statement = try db.cachedStatement(sql: "SELECT * FROM player WHERE id = ?")
    let player1 = try Player.fetchOne(statement, arguments: [1])!
    let player2 = try Player.fetchOne(statement, arguments: [2])!
    
    let statement = try db.cachedStatement(sql: "INSERT INTO player (name) VALUES (?)")
    try statement.execute(arguments: ["Arthur"])
    try statement.execute(arguments: ["Barbara"])
    

    The returned statement may have already been used: it may or may not contain values for its eventual arguments.

    Throws

    A DatabaseError whenever SQLite could not parse the sql query.
  • Returns a prepared statement that can be reused.

    For example:

    let statement = try db.cachedStatement(literal: "SELECT * FROM player WHERE id = ?")
    let player1 = try Player.fetchOne(statement, arguments: [1])!
    let player2 = try Player.fetchOne(statement, arguments: [2])!
    
    let statement = try db.cachedStatement(literal: "INSERT INTO player (name) VALUES (?)")
    try statement.execute(arguments: ["Arthur"])
    try statement.execute(arguments: ["Barbara"])
    

    Throws

    A DatabaseError whenever SQLite could not parse the sql query.

    Precondition

    No argument must be set, or all arguments must be set. An error is raised otherwise.

    // OK
    try cachedStatement(literal: """
        SELECT COUNT(*) FROM player WHERE score > ?
        """)
    try cachedStatement(literal: """
        SELECT COUNT(*) FROM player WHERE score > \(1000)
        """)
    
    // NOT OK
    try cachedStatement(literal: """
        SELECT COUNT(*) FROM player
        WHERE color = ? AND score > \(1000)
        """)
    
  • Returns a cursor of all SQL statements separated by semi-colons.

    For example:

    let statements = try db.allStatements(sql: """
        INSERT INTO player (name) VALUES (?);
        INSERT INTO player (name) VALUES (?);
        INSERT INTO player (name) VALUES (?);
        """, arguments: ["Arthur", "Barbara", "O'Brien"])
    while let statement = try statements.next() {
        try statement.execute()
    }
    

    Throws

    A DatabaseError whenever an SQLite error occurs.

    Precondition

    Arguments must be nil, or all arguments must be set. The returned cursor will throw an error otherwise.

    // OK
    try allStatements(sql: """
        SELECT COUNT(*) FROM player WHERE score < ?;
        SELECT COUNT(*) FROM player WHERE score > ?;
        """)
    try allStatements(sql: """
        SELECT COUNT(*) FROM player WHERE score < ?;
        SELECT COUNT(*) FROM player WHERE score > ?;
        """, arguments: [1000, 1000])
    
    // NOT OK
    try allStatements(sql: """
        SELECT COUNT(*) FROM player WHERE score < ?;
        SELECT COUNT(*) FROM player WHERE score > ?;
        """, arguments: [1000])
    
  • Returns a cursor of all SQL statements separated by semi-colons.

    Literals allow you to safely embed raw values in your SQL, without any risk of syntax errors or SQL injection:

    let statements = try db.allStatements(literal: """
        INSERT INTO player (name) VALUES (\("Arthur"));
        INSERT INTO player (name) VALUES (\("Barbara"));
        INSERT INTO player (name) VALUES (\("O'Brien"));
        """)
    while let statement = try statements.next() {
        try statement.execute()
    }
    

    Throws

    A DatabaseError whenever an SQLite error occurs.

    Precondition

    No argument must be set, or all arguments must be set. The returned cursor will throw an error otherwise.

    // OK
    try allStatements(literal: """
        SELECT COUNT(*) FROM player WHERE score < ?;
        SELECT COUNT(*) FROM player WHERE score > ?;
        """)
    try allStatements(literal: """
        SELECT COUNT(*) FROM player WHERE score < \(1000);
        SELECT COUNT(*) FROM player WHERE score > \(1000);
        """)
    
    // NOT OK
    try allStatements(literal: """
        SELECT COUNT(*) FROM player WHERE score < \(1000);
        SELECT COUNT(*) FROM player WHERE score > ?;
        """)
    
  • Executes one or several SQL statements, separated by semi-colons.

    For example:

    try db.execute(
        sql: "INSERT INTO player (name) VALUES (:name)",
        arguments: ["name": "Arthur"])
    
    try db.execute(sql: """
        INSERT INTO player (name) VALUES (?);
        INSERT INTO player (name) VALUES (?);
        INSERT INTO player (name) VALUES (?);
        """, arguments: ["Arthur", "Barbara", "O'Brien"])
    

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • Executes one or several SQL statements, separated by semi-colons.

    Literals allow you to safely embed raw values in your SQL, without any risk of syntax errors or SQL injection:

    try db.execute(literal: """
        INSERT INTO player (name) VALUES (\("Arthur"))
        """)
    
    try db.execute(literal: """
        INSERT INTO player (name) VALUES (\("Arthur"));
        INSERT INTO player (name) VALUES (\("Barbara"));
        INSERT INTO player (name) VALUES (\("O'Brien"));
        """)
    

    Throws

    A DatabaseError whenever an SQLite error occurs.

Encryption

  • Sets the passphrase used to crypt and decrypt an SQLCipher database.

    Call this method from Configuration.prepareDatabase, as in the example below:

    var config = Configuration()
    config.prepareDatabase { db in
        try db.usePassphrase("secret")
    }
    
  • Changes the passphrase used by an SQLCipher encrypted database.

Database-Related Types

Database Observation

FTS5

  • Add a custom FTS5 tokenizer.

    class MyTokenizer : FTS5CustomTokenizer { ... }
    db.add(tokenizer: MyTokenizer.self)
    
  • Creates a pattern from a raw pattern string; throws DatabaseError on invalid syntax.

    The pattern syntax is documented at https://www.sqlite.org/fts5.html#full_text_query_syntax

    try db.makeFTS5Pattern(rawPattern: "and", forTable: "document") // OK
    try db.makeFTS5Pattern(rawPattern: "AND", forTable: "document") // malformed MATCH expression: [AND]
    
  • Creates an FTS5 tokenizer, given its descriptor.

    let unicode61 = try db.makeTokenizer(.unicode61())
    

    It is a programmer error to use the tokenizer outside of a protected database queue, or after the database has been closed.

    Use this method when you implement a custom wrapper tokenizer:

    final class MyTokenizer : FTS5WrapperTokenizer {
        var wrappedTokenizer: FTS5Tokenizer
    
        init(db: Database, arguments: [String]) throws {
            wrappedTokenizer = try db.makeTokenizer(.unicode61())
        }
    }
    

Database Schema