Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/Core/Configuration.swift
T

489 lines
18 KiB
Swift

import Dispatch
import Foundation
public struct Configuration {
// MARK: - Misc options
/// A boolean value indicating whether foreign key support is enabled.
///
/// The default is true.
///
/// Related SQLite documentation: <https://www.sqlite.org/foreignkeys.html>.
public var foreignKeysEnabled = true
/// A boolean value indicating whether an SQLite connection is read-only.
///
/// The default is false.
///
/// ```swift
/// var config = Configuration()
/// config.readonly = true
///
/// let dbQueue = try DatabaseQueue( // or DatabasePool
/// path: "/path/to/database.sqlite",
/// configuration: config)
/// ```
public var readonly = false
/// A label that describes a database connection.
///
/// You can query this label at runtime:
///
/// ```swift
/// var config = Configuration()
/// config.label = "MyDatabase"
/// let dbQueue = try DatabaseQueue(configuration: config)
///
/// try dbQueue.read { db in
/// print(db.configuration.label) // Prints "MyDatabase"
/// }
/// ```
///
/// The configuration label is also used to name ``Database`` connections
/// (their ``Database/description`` property), and the various dispatch
/// queues created by GRDB, visible in debugging sessions and crash logs.
///
/// Those connection names and dispatch queue labels are intended for
/// debugging only. Their format may change between GRDB releases.
/// Applications should not depend on connection names and dispatch
/// queue labels.
///
/// If the configuration label is nil, the current GRDB implementation uses
/// the following names:
///
/// - `GRDB.DatabaseQueue`: the (unique) connection of a DatabaseQueue
/// - `GRDB.DatabasePool.writer`: the (unique) writer connection of
/// a DatabasePool
/// - `GRDB.DatabasePool.reader.N`, where N is 1, 2, ...: one of the reader
/// connection(s) of a DatabasePool. N may get bigger than the maximum
/// number of concurrent readers, as SQLite connections get closed and new
/// ones are opened.
/// - `GRDB.DatabasePool.snapshot.N`: the connection of a DatabaseSnapshot.
/// N grows with the number of snapshots.
///
/// If the configuration label is not nil, for example "MyDatabase", the
/// current GRDB implementation uses the following names:
///
/// - `MyDatabase`: the (unique) connection of a DatabaseQueue
/// - `MyDatabase.writer`: the (unique) writer connection of a DatabasePool
/// - `MyDatabase.reader.N`, where N is 1, 2, ...: one of the reader
/// connection(s) of a DatabasePool. N may get bigger than the maximum
/// number of concurrent readers, as SQLite connections get closed and new
/// ones are opened.
/// - `MyDatabase.snapshot.N`: the connection of a DatabaseSnapshot. N grows
/// with the number of snapshots.
///
/// The default configuration label is nil.
public var label: String? = nil
/// A boolean value indicating whether SQLite 3.29+ interprets
/// double-quoted strings as string literals when they does not match any
/// valid identifier.
///
/// The default and recommended value is false:
///
/// ```swift
/// // Error: no such column: missingColumn
/// let name = try String.fetchOne(db, sql: """
/// SELECT "missingColumn" FROM "player"
/// """)
/// ```
///
/// When true, or before SQLite version 3.29.0, double-quoted strings that
/// do not match any valid identifier are interpreted as string literals,
/// as in the example below. This is an SQLite
/// [misfeature](https://sqlite.org/quirks.html#double_quoted_string_literals_are_accepted):
///
/// ```swift
/// // MISFEATURE: This query succeeds with result "missingColumn"
/// let name = try String.fetchOne(db, sql: """
/// SELECT "missingColumn" FROM "player"
/// """)
/// ```
public var acceptsDoubleQuotedStringLiterals = false
/// A boolean value indicating whether the database connection listens to
/// the ``Database/suspendNotification`` and ``Database/resumeNotification``
/// notifications.
///
/// - note: [**🔥 EXPERIMENTAL**](https://github.com/groue/GRDB.swift/blob/master/README.md#what-are-experimental-features)
///
/// Set this flag to true when you apply the technique described in
/// <doc:DatabaseSharing#How-to-limit-the-0xDEAD10CC-exception>. See
/// ``Database/suspendNotification`` for more informations about
/// suspended databases.
public var observesSuspensionNotifications = false
/// A boolean value indicating whether statement arguments are visible in
/// the description of database errors and trace events.
///
/// The default and recommended value is false: statement arguments are not
/// visible in database errors and trace events, preventing sensitive
/// information from leaking in unexpected places. For example:
///
/// ```swift
/// db.trace { event in
/// // By default, sensitive information is NOT printed
/// // when a statement is traced:
/// print(event)
/// }
///
/// do {
/// // The sensitive information to protect
/// let email = "..."
/// let player = try Player.filter(Column("email") == email).fetchOne(db)
/// } catch {
/// // By default, sensitive information is NOT printed
/// // when an error occurs:
/// print(error)
/// }
/// ```
///
/// For debugging purpose, you can set this flag to true, and get more
/// precise database reports. It is your responsibility to prevent sensitive
/// information from leaking in unexpected locations, so you should not set
/// this flag in release builds (think about GDPR and other
/// privacy-related rules):
///
/// ```swift
/// var config = Configuration()
/// #if DEBUG
/// // Enable verbose debugging in DEBUG builds only
/// config.publicStatementArguments = true
/// #endif
///
/// db.trace { event in
/// // Sensitive information is printed in DEBUG builds:
/// print(event)
/// }
///
/// do {
/// // The sensitive information to protect
/// let email = "..."
/// let player = try Player.filter(Column("email") == email).fetchOne(db)
/// } catch {
/// // Sensitive information is printed in DEBUG builds:
/// print(error)
/// }
/// ```
public var publicStatementArguments = false
/// The clock that feeds ``Database/transactionDate``.
///
/// - note: [**🔥 EXPERIMENTAL**](https://github.com/groue/GRDB.swift/blob/master/README.md#what-are-experimental-features)
///
/// The default clock is ``DefaultTransactionClock`` (which returns the
/// start date of the current transaction).
///
/// For example:
///
/// ```swift
/// var config = Configuration()
/// config.transactionClock = .custom { db in /* return some Date */ }
/// ```
public var transactionClock: any TransactionClock = .default
// MARK: - Managing SQLite Connections
private var setups: [(Database) throws -> Void] = []
/// Defines a function to run whenever an SQLite connection is opened.
///
/// The preparation function is run before the connection is made available
/// for database access methods.
///
/// This method can be called several times. The preparation functions are
/// run in the same order.
///
/// For example:
///
/// ```swift
/// var config = Configuration()
/// config.prepareDatabase { db in
/// // Prints all SQL statements
/// db.trace { print("SQL >", $0) }
/// }
/// ```
///
/// When you use a ``DatabasePool``, preparation functions are called for
/// the writer connection and all reader connections. You can distinguish
/// them by querying `db.configuration.readonly`:
///
/// ```swift
/// var config = Configuration()
/// config.prepareDatabase { db in
/// if db.configuration.readonly {
/// // reader connection
/// } else {
/// // writer connection
/// }
/// }
/// ```
///
/// On newly created databases files, ``DatabasePool`` activates the WAL
/// mode after the preparation functions have run.
public mutating func prepareDatabase(_ setup: @escaping (Database) throws -> Void) {
setups.append(setup)
}
// MARK: - Transactions
/// The default kind of write transactions.
///
/// The default is ``Database/TransactionKind/deferred``.
///
/// You can change the default transaction kind. For example, you can force
/// all write transactions to be `IMMEDIATE`:
///
/// ```swift
/// var config = Configuration()
/// config.defaultTransactionKind = .immediate
/// let dbQueue = try DatabaseQueue(configuration: config)
///
/// // BEGIN IMMEDIATE TRANSACTION; ...; COMMIT TRANSACTION;
/// try dbQueue.write { db in ... }
/// ```
///
/// This property is ignored for read-only transactions. Those always open
/// `DEFERRED` SQLite transactions.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_transaction.html>
public var defaultTransactionKind: Database.TransactionKind = .deferred
/// A boolean value indicating whether it is valid to leave a transaction
/// opened at the end of a database access method.
///
/// The default value is false: not completing a transaction is a
/// programmer error:
///
/// ```swift
/// let dbQueue = try DatabaseQueue()
///
/// // fatal error: A transaction has been left opened at the end of a database access
/// try dbQueue.inDatabase { db in
/// try db.beginTransaction()
/// }
/// ```
///
/// When true, one can leave opened transaction at the end of database
/// access method:
///
/// ```swift
/// var config = Configuration()
/// config.allowsUnsafeTransactions = true
/// let dbQueue = try DatabaseQueue(configuration: config)
///
/// try dbQueue.inDatabase { db in
/// try db.beginTransaction()
/// }
///
/// try dbQueue.inDatabase { db in
/// try db.commit()
/// }
/// ```
///
/// This configuration flag has no effect on ``DatabasePool`` reader
/// connections: those never allow leaving a transaction opened at the end
/// of a read access.
public var allowsUnsafeTransactions = false
// MARK: - Journal Mode
/// Defines how the journal mode is configured when the database
/// connection is opened.
///
/// Related SQLite documentation: <https://www.sqlite.org/pragma.html#pragma_journal_mode>
public enum JournalModeConfiguration: Sendable {
/// The default setup has ``DatabaseQueue`` perform no specific
/// configuration of the journal mode, and ``DatabasePool``
/// configure the database for the WAL mode (just like the
/// ``wal`` case).
case `default`
/// The journal mode is set to WAL (plus extra configurations that
/// make life easier with WAL databases).
case wal
}
/// Defines how the journal mode is configured when the database
/// connection is opened.
///
/// This configuration is ignored when ``readonly`` is true.
///
/// The default value has ``DatabaseQueue`` perform no specific
/// configuration of the journal mode, and ``DatabasePool`` configure
/// the database for the WAL mode.
///
/// Applications that need to open a WAL database with a
/// ``DatabaseQueue`` should set the `journalMode` to `wal`:
///
/// ```swift
/// // Open a WAL database with DatabaseQueue
/// var config = Configuration()
/// config.journalMode = .wal
/// let dbQueue = try DatabaseQueue(path: "...", configuration: config)
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/pragma.html#pragma_journal_mode>
public var journalMode = JournalModeConfiguration.default
// MARK: - Concurrency
/// Defines the how `SQLITE_BUSY` errors are handled.
///
/// The default is ``Database/BusyMode/immediateError``.
///
/// Related SQLite documentation: <https://www.sqlite.org/rescode.html#busy>
public var busyMode: Database.BusyMode = .immediateError
/// The behavior in case of SQLITE_BUSY error, for read-only connections.
/// If nil, GRDB picks a default one.
var readonlyBusyMode: Database.BusyMode? = nil
/// The maximum number of concurrent reader connections.
///
/// This configuration has effect on ``DatabasePool`` and
/// ``DatabaseSnapshotPool`` only. The default value is 5.
///
/// You can query this value at runtime in order to get the actual capacity
/// for concurrent reads of any ``DatabaseReader``. In this context,
/// ``DatabaseQueue`` and ``DatabaseSnapshot`` have a capacity of 1,
/// because they can't perform two concurrent reads. For example:
///
/// ```swift
/// var config = Configuration()
/// config.maximumReaderCount = 5
///
/// let path = "/path/to/database.sqlite"
/// let dbQueue = try DatabaseQueue(path: path, configuration: config)
/// let dbPool = try DatabasePool(path: path, configuration: config)
/// let dbSnapshot = try dbPool.makeSnapshot()
///
/// print(dbQueue.configuration.maximumReaderCount) // 1
/// print(dbPool.configuration.maximumReaderCount) // 5
/// print(dbSnapshot.configuration.maximumReaderCount) // 1
/// ```
public var maximumReaderCount: Int = 5
/// The quality of service of database accesses.
///
/// The quality of service is ignored if you supply a ``targetQueue``.
///
/// The default is `userInitiated`.
public var qos: DispatchQoS = .userInitiated
/// The effective quality of service of read-only database accesses.
public var readQoS: DispatchQoS {
targetQueue?.qos ?? self.qos
}
/// The effective quality of service of write database accesses.
public var writeQoS: DispatchQoS {
writeTargetQueue?.qos ?? targetQueue?.qos ?? self.qos
}
/// The target dispatch queue for database accesses.
///
/// Database connections which are not read-only will prefer
/// ``writeTargetQueue`` instead, if it is not nil.
///
/// When you use ``DatabasePool``, make sure this queue is concurrent. This
/// is because in a serial dispatch queue, no concurrent database access can
/// happen, and you may experience deadlocks.
///
/// If the queue is nil, all database accesses happen in unspecified
/// dispatch queues whose quality of service is determined by the
/// ``qos`` property.
///
/// The default is nil.
public var targetQueue: DispatchQueue? = nil
/// The target dispatch queue for write database accesses.
///
/// If this queue is nil, writer connections are controlled
/// by ``targetQueue``.
///
/// The default is nil.
public var writeTargetQueue: DispatchQueue? = nil
#if os(iOS)
/// A boolean value indicating whether the database connection releases
/// memory when entering the background or upon receiving a memory warning
/// in iOS.
///
/// The default is true.
public var automaticMemoryManagement = true
#endif
/// A boolean value indicating whether read-only connections should be
/// kept open.
///
/// This configuration flag applies to ``DatabasePool`` only. The
/// default value is false.
///
/// When the flag is false, a `DatabasePool` closes read-only
/// connections when requested to dispose non-essential memory with
/// ``DatabasePool/releaseMemory()``. When true, those connections are
/// kept open.
///
/// Consider setting this flag to true when profiling your application
/// reveals that a lot of time is spent opening new SQLite connections.
public var persistentReadOnlyConnections = false
// MARK: - Factory Configuration
/// Creates a factory configuration.
public init() { }
// MARK: - Not Public
/// The SQLite [threading mode](https://www.sqlite.org/threadsafe.html).
///
/// - Note: Only the multi-thread mode (`SQLITE_OPEN_NOMUTEX`) is currently
/// supported, since all <doc:DatabaseConnections> access SQLite connections
/// through a `SerializedDatabase`.
var threadingMode = Database.ThreadingMode.default
var SQLiteConnectionDidOpen: (() -> Void)?
var SQLiteConnectionWillClose: ((SQLiteConnection) -> Void)?
var SQLiteConnectionDidClose: (() -> Void)?
var SQLiteOpenFlags: CInt {
var flags = readonly ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE)
if sqlite3_libversion_number() >= 3037000 {
flags |= 0x02000000 // SQLITE_OPEN_EXRESCODE
}
return threadingMode.SQLiteOpenFlags | flags
}
func setUp(_ db: Database) throws {
for f in setups {
try f(db)
}
}
func identifier(defaultLabel: String, purpose: String? = nil) -> String {
(self.label ?? defaultLabel) + (purpose.map { "." + $0 } ?? "")
}
/// Creates a DispatchQueue which has the quality of service and target
/// queue of write accesses.
func makeWriterDispatchQueue(label: String) -> DispatchQueue {
if let targetQueue = writeTargetQueue ?? targetQueue {
return DispatchQueue(label: label, target: targetQueue)
} else {
return DispatchQueue(label: label, qos: qos)
}
}
/// Creates a DispatchQueue which has the quality of service and target
/// queue of read accesses.
func makeReaderDispatchQueue(label: String) -> DispatchQueue {
if let targetQueue {
return DispatchQueue(label: label, target: targetQueue)
} else {
return DispatchQueue(label: label, qos: qos)
}
}
}