DatabasePool

public final class DatabasePool

A DatabasePool grants concurrent accesses to an SQLite database.

  • Opens the SQLite database at path path.

    let dbPool = try DatabasePool(path: "/path/to/database.sqlite")
    

    Database connections get closed when the database pool gets deallocated.

    Throws

    A DatabaseError whenever an SQLite error occurs.

    Declaration

    Swift

    public init(path: String, configuration: Configuration = Configuration()) throws

    Parameters

    path

    The path to the database file.

    configuration

    A configuration.

    maximumReaderCount

    The maximum number of readers. Default is 5.

  • Free as much memory as possible.

    This method blocks the current thread until all database accesses are completed.

    See also setupMemoryManagement(application:)

    Declaration

    Swift

    public func releaseMemory()
  • Listens to UIApplicationDidEnterBackgroundNotification and UIApplicationDidReceiveMemoryWarningNotification in order to release as much memory as possible.

    • param application: The UIApplication that will start a background task to let the database pool release its memory when the application enters background.

    Declaration

    Swift

    public func setupMemoryManagement(in application: UIApplication)
  • Synchronously executes a read-only block in a protected dispatch queue, and returns its result. The block is wrapped in a deferred transaction.

    let persons = try dbPool.read { db in
        try Person.fetchAll(...)
    }
    

    The block is completely isolated. Eventual concurrent database updates are not visible inside the block:

    try dbPool.read { db in
        // Those two values are guaranteed to be equal, even if the
        // `wines` table is modified between the two requests:
        let count1 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")!
        let count2 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")!
    }
    
    try dbPool.read { db in
        // Now this value may be different:
        let count = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")!
    }
    

    This method is not reentrant.

    Throws

    The error thrown by the block, or any DatabaseError that would happen while establishing the read access to the database.

    Declaration

    Swift

    public func read<T>(_ block: (Database) throws -> T) throws -> T

    Parameters

    block

    A block that accesses the database.

  • Synchronously executes a read-only block in a protected dispatch queue, and returns its result.

    The block argument is not isolated: eventual concurrent database updates are visible inside the block:

    try dbPool.unsafeRead { db in
        // Those two values may be different because some other thread
        // may have inserted or deleted a wine between the two requests:
        let count1 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")!
        let count2 = try Int.fetchOne(db, "SELECT COUNT(*) FROM wines")!
    }
    

    Cursor iteration is safe, though:

    try dbPool.unsafeRead { db in
        // No concurrent update can mess with this iteration:
        let rows = try Row.fetchCursor(db, "SELECT ...")
        while let row = try rows.next() { ... }
    }
    

    This method is not reentrant.

    Throws

    The error thrown by the block, or any DatabaseError that would happen while establishing the read access to the database.

    Declaration

    Swift

    public func unsafeRead<T>(_ block: (Database) throws -> T) throws -> T

    Parameters

    block

    A block that accesses the database.

  • Add or redefine an SQL function.

    let fn = DatabaseFunction("succ", argumentCount: 1) { databaseValues in
        let dbv = databaseValues.first!
        guard let int = dbv.value() as Int? else {
            return nil
        }
        return int + 1
    }
    dbPool.add(function: fn)
    try dbPool.read { db in
        try Int.fetchOne(db, "SELECT succ(1)") // 2
    }
    

    Declaration

    Swift

    public func add(function: DatabaseFunction)
  • Remove an SQL function.

    Declaration

    Swift

    public func remove(function: DatabaseFunction)
  • Add or redefine a collation.

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

    Declaration

    Swift

    public func add(collation: DatabaseCollation)
  • Remove a collation.

    Declaration

    Swift

    public func remove(collation: DatabaseCollation)
  • Synchronously executes an update block in a protected dispatch queue, and returns its result.

    Eventual concurrent database updates are postponed until the block has executed.

    try dbPool.write { db in
        try db.execute(...)
    }
    

    To maintain database integrity, and preserve eventual concurrent reads from seeing an inconsistent database state, prefer the writeInTransaction method.

    This method is not reentrant.

    Throws

    The error thrown by the block.

    Declaration

    Swift

    public func write<T>(_ block: (Database) throws -> T) rethrows -> T
  • Synchronously executes a block in a protected dispatch queue, wrapped inside a transaction.

    Eventual concurrent database updates are postponed until the block has executed.

    If the block throws an error, the transaction is rollbacked and the error is rethrown. If the block returns .rollback, the transaction is also rollbacked, but no error is thrown.

    try dbPool.writeInTransaction { db in
        db.execute(...)
        return .commit
    }
    

    Eventual concurrent readers do not see partial changes:

    dbPool.writeInTransaction { db in
        // Eventually preserve a zero balance
        try db.execute(db, "INSERT INTO credits ...", arguments: [amount])
        try db.execute(db, "INSERT INTO debits ...", arguments: [amount])
    }
    
    dbPool.read { db in
        // Here the balance is guaranteed to be zero
    }
    

    This method is not reentrant.

    Throws

    The error thrown by the block, or any error establishing the transaction.

    Declaration

    Swift

    public func writeInTransaction(_ kind: Database.TransactionKind? = nil, _ block: (Database) throws -> Database.TransactionCompletion) throws

    Parameters

    kind

    The transaction type (default nil). If nil, the transaction type is configuration.defaultTransactionKind, which itself defaults to .immediate. See https://www.sqlite.org/lang_transaction.html for more information.

    block

    A block that executes SQL statements and return either .commit or .rollback.

  • Returns an optional database connection. If not nil, the caller is executing on the serialized writer dispatch queue.

    Declaration

    Swift

    public var availableDatabaseConnection: Database?
  • Asynchronously executes a read-only block in a protected dispatch queue, wrapped in a deferred transaction.

    This method must be called from the writing dispatch queue.

    The block argument is guaranteed to see the database in the last committed state at the moment this method is called. Eventual concurrent database updates are not visible inside the block.

    try dbPool.write { db in
        try db.execute("DELETE FROM persons")
        try dbPool.readFromCurrentState { db in
            // Guaranteed to be zero
            try Int.fetchOne(db, "SELECT COUNT(*) FROM persons")!
        }
        try db.execute("INSERT INTO persons ...")
    }
    

    This method blocks the current thread until the isolation guarantee has been established, and before the block argument has run.

    Throws

    The error thrown by the block, or any DatabaseError that would happen while establishing the read access to the database.

    Declaration

    Swift

    public func readFromCurrentState(_ block: @escaping (Database) -> Void) throws

    Parameters

    block

    A block that accesses the database.