DatabasePool
public final class DatabasePool : DatabaseWriter
extension DatabasePool: @unchecked Sendable
extension DatabasePool: DatabaseReader
A DatabasePool grants concurrent accesses to an SQLite database.
-
The database configuration
Declaration
Swift
public var configuration: Configuration { get }
-
The path to the database.
Declaration
Swift
public var path: String { get }
-
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.
-
Frees as much memory as possible, by disposing non-essential memory from the writer connection, and closing all reader connections.
This method is synchronous, and blocks the current thread until all database accesses are completed.
Warning
This method can prevent concurrent reads from executing, until it returns. PreferreleaseMemoryEventually()
if you intend to keep on using the database while releasing memory.Declaration
Swift
public func releaseMemory()
-
Eventually frees as much memory as possible, by disposing non-essential memory from the writer connection, and closing all reader connections.
Unlike
releaseMemory()
, this method does not prevent concurrent database accesses when it is executing. But it does not notify when non-essential memory has been freed.Declaration
Swift
public func releaseMemoryEventually()
-
Declaration
Swift
public func close() throws
-
Declaration
Swift
public func interrupt()
-
Declaration
Swift
@_disfavoredOverload public func read<T>(_ value: (Database) throws -> T) throws -> T
-
Declaration
Swift
public func asyncRead(_ value: @escaping (Result<Database, Error>) -> Void)
-
Declaration
Swift
@_disfavoredOverload public func unsafeRead<T>(_ value: (Database) throws -> T) throws -> T
-
Declaration
Swift
public func asyncUnsafeRead(_ value: @escaping (Result<Database, Error>) -> Void)
-
Declaration
Swift
public func unsafeReentrantRead<T>(_ value: (Database) throws -> T) throws -> T
-
Declaration
Swift
public func concurrentRead<T>(_ value: @escaping (Database) throws -> T) -> DatabaseFuture<T>
-
Asynchronously executes a read-only function in a protected dispatch queue.
This method must be called from a writing dispatch queue, outside of any transaction. You’ll get a fatal error otherwise.
The
value
function 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 from the function.This method returns as soon as the isolation guarantees described above are established.
In the example below, the number of players is fetched concurrently with the player insertion. Yet the future is guaranteed to return zero:
try writer.asyncWriteWithoutTransaction { db in // Delete all players try Player.deleteAll() // Count players concurrently writer.asyncConcurrentRead { dbResult in do { let db = try dbResult.get() // Guaranteed to be zero let count = try Player.fetchCount(db) } catch { // Handle error } } // Insert a player try Player(...).insert(db) }
Declaration
Swift
public func asyncConcurrentRead(_ value: @escaping (Result<Database, Error>) -> Void)
Parameters
value
A function that accesses the database.
-
Invalidates open read-only SQLite connections.
After this method is called, read-only database access methods will use new SQLite connections.
Eventual concurrent read-only accesses are not invalidated: they will proceed until completion.
Declaration
Swift
public func invalidateReadOnlyConnections()
-
Declaration
Swift
@_disfavoredOverload public func writeWithoutTransaction<T>(_ updates: (Database) throws -> T) rethrows -> T
-
Declaration
Swift
@_disfavoredOverload public func barrierWriteWithoutTransaction<T>(_ updates: (Database) throws -> T) rethrows -> T
-
Declaration
Swift
public func asyncBarrierWriteWithoutTransaction(_ updates: @escaping (Database) -> Void)
-
Synchronously executes database updates in a protected dispatch queue, wrapped inside a transaction, and returns the result.
If the updates throws an error, the transaction is rollbacked and the error is rethrown. If the updates return .rollback, the transaction is also rollbacked, but no error is thrown.
Eventual concurrent database updates are postponed until the transaction has completed.
Eventual concurrent reads are guaranteed to not see any partial updates of the database until the transaction has completed.
This method is not reentrant.
try dbPool.writeInTransaction { db in db.execute(...) return .commit }
Throws
The error thrown by the updates, or by the wrapping transaction.Declaration
Parameters
kind
The transaction type (default nil). If nil, the transaction type is configuration.defaultTransactionKind, which itself defaults to .deferred. See https://www.sqlite.org/lang_transaction.html for more information.
updates
The updates to the database.
-
Declaration
Swift
public func unsafeReentrantWrite<T>(_ updates: (Database) throws -> T) rethrows -> T
-
Asynchronously executes database updates in a protected dispatch queue, outside of any transaction.
Eventual concurrent reads may see partial updates unless you wrap them in a transaction.
Declaration
Swift
public func asyncWriteWithoutTransaction(_ updates: @escaping (Database) -> Void)
-
Creates a database snapshot.
The snapshot sees an unchanging database content, as it existed at the moment it was created.
When you want to control the latest committed changes seen by a snapshot, create it from the pool’s writer protected dispatch queue:
let snapshot1 = try dbPool.write { db -> DatabaseSnapshot in try Player.deleteAll() return try dbPool.makeSnapshot() } // <- Other threads may modify the database here let snapshot2 = try dbPool.makeSnapshot() try snapshot1.read { db in // Guaranteed to be zero try Player.fetchCount(db) } try snapshot2.read { db in // Could be anything try Player.fetchCount(db) }
It is forbidden to create a snapshot from the writer protected dispatch queue when a transaction is opened, though, because it is likely a programmer error:
try dbPool.write { db in try db.inTransaction { try Player.deleteAll() // fatal error: makeSnapshot() must not be called from inside a transaction let snapshot = try dbPool.makeSnapshot() return .commit } }
To avoid this fatal error, create the snapshot before or after the transaction:
try dbPool.writeWithoutTransaction { db in // OK let snapshot = try dbPool.makeSnapshot() try db.inTransaction { try Player.deleteAll() return .commit } // OK let snapshot = try dbPool.makeSnapshot() }
You can create as many snapshots as you need, regardless of the maximum number of reader connections in the pool.
For more information, read about “snapshot isolation” at https://sqlite.org/isolation.html
Declaration
Swift
public func makeSnapshot() throws -> DatabaseSnapshot