Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ extension DependencyValues {

func appDatabase() throws -> any DatabaseWriter {
@Dependency(\.context) var context
let database: any DatabaseWriter
var configuration = Configuration()
configuration.foreignKeysEnabled = true
configuration.prepareDatabase { db in
Expand All @@ -128,21 +127,13 @@ func appDatabase() throws -> any DatabaseWriter {
}
#endif
}
if context == .preview {
database = try DatabaseQueue(configuration: configuration)
} else {
let path =
context == .live
? URL.documentsDirectory.appending(component: "db.sqlite").path()
: URL.temporaryDirectory.appending(component: "\(UUID().uuidString)-db.sqlite").path()
logger.debug(
"""
App database:
open "\(path)"
"""
)
database = try DatabasePool(path: path, configuration: configuration)
}
let database = try SQLiteData.defaultDatabase(configuration: configuration)
logger.debug(
"""
App database:
open "\(database.path)"
"""
)
var migrator = DatabaseMigrator()
#if DEBUG
migrator.eraseDatabaseOnSchemaChange = true
Expand Down
18 changes: 7 additions & 11 deletions Examples/SyncUps/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ extension Int {

func appDatabase() throws -> any DatabaseWriter {
@Dependency(\.context) var context
let database: any DatabaseWriter
var configuration = Configuration()
configuration.foreignKeysEnabled = true
configuration.prepareDatabase { db in
Expand All @@ -91,16 +90,13 @@ func appDatabase() throws -> any DatabaseWriter {
}
#endif
}
if context == .preview {
database = try DatabaseQueue(configuration: configuration)
} else {
let path =
context == .live
? URL.documentsDirectory.appending(component: "db.sqlite").path()
: URL.temporaryDirectory.appending(component: "\(UUID().uuidString)-db.sqlite").path()
logger.info("open \(path)")
database = try DatabasePool(path: path, configuration: configuration)
}
let database = try SQLiteData.defaultDatabase(configuration: configuration)
logger.debug(
"""
App database:
open "\(database.path)"
"""
)
var migrator = DatabaseMigrator()
#if DEBUG
migrator.eraseDatabaseOnSchemaChange = true
Expand Down
82 changes: 11 additions & 71 deletions Sources/SQLiteData/Documentation.docc/Articles/PreparingDatabase.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ specified a cascading action (such as delete).
We further recommend that you enable query tracing to log queries that are executed in your
application. This can be handy for tracking down long-running queries, or when more queries execute
than you expect. We also recommend only doing this in debug builds to avoid leaking sensitive
information when the app is running on a user's device, and we further recommned using OSLog
information when the app is running on a user's device, and we further recommend using OSLog
when running your app in the simulator/device and using `Swift.print` in previews:

```diff
Expand Down Expand Up @@ -101,7 +101,8 @@ matter.
### Step 3: Create database connection

Once a `Configuration` value is set up we can construct the actual database connection. The simplest
way to do this is to construct the database connection for a path on the file system like so:
way to do this is to construct the database connection using the
``defaultDatabase(path:configuration:)`` function:

```diff
-func appDatabase() -> any DatabaseWriter {
Expand All @@ -120,55 +121,14 @@ way to do this is to construct the database connection for a path on the file sy
}
}
#endif
+ let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
+ logger.info("open \(path)")
+ let database = try DatabasePool(path: path, configuration: configuration)
+ let database = try defaultDatabase(configuration: configuration)
+ logger.info("open '\(database.path)'")
+ return database
}
```

However, this can be improved. First, this code will crash if it is executed in Xcode previews
because SQLite is unable to form a connection to a database on disk in a preview context. And
second, in tests we should write this databadse to the temporary directoy on disk with a unique
name so that each test gets a fresh database and so that multiple tests can run in parallel.

To fix this we can use `@Dependency(\.context)` to determine if we are in a "live" application
context or if we're in a preview or test.

```diff
func appDatabase() -> any DatabaseWriter {
@Dependency(\.context) var context
var configuration = Configuration()
configuration.foreignKeysEnabled = true
#if DEBUG
configuration.prepareDatabase { db in
db.trace(options: .profile) {
if context == .preview {
print("\($0.expandedDescription)")
} else {
logger.debug("\($0.expandedDescription)")
}
}
}
#endif
- let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
- logger.info("open \(path)")
- let database = try DatabasePool(path: path, configuration: configuration)
+ let database: any DatabaseWriter
+ switch context {
+ case .live:
+ let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
+ logger.info("open \(path)")
+ database = try DatabasePool(path: path, configuration: configuration)
+ case .test:
+ let path = URL.temporaryDirectory.appending(component: "\(UUID().uuidString)-db.sqlite").path()
+ database = try DatabasePool(path: path, configuration: configuration)
+ case .preview:
+ database = try DatabaseQueue(configuration: configuration)
+ }
return database
}
```
This function provisions a context-dependent database for you, _e.g._ in previews and tests it
will provision unique, temporary databases that won't conflict with your live app's database.

### Step 4: Migrate database

Expand All @@ -193,18 +153,8 @@ database connection:
}
}
#endif
let database: any DatabaseWriter
switch context {
case .live:
let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
logger.info("open \(path)")
database = try DatabasePool(path: path, configuration: configuration)
case .test:
let path = URL.temporaryDirectory.appending(component: "\(UUID().uuidString)-db.sqlite").path()
database = try DatabasePool(path: path, configuration: configuration)
case .preview:
database = try DatabaseQueue(configuration: configuration)
}
let database = try defaultDatabase(configuration: configuration)
logger.info("open '\(database.path)'")
+ var migrator = DatabaseMigrator()
+ #if DEBUG
+ migrator.eraseDatabaseOnSchemaChange = true
Expand Down Expand Up @@ -278,18 +228,8 @@ func appDatabase() throws -> any DatabaseWriter {
}
}
#endif
let database: any DatabaseWriter
switch context {
case .live:
let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
logger.info("open \(path)")
database = try DatabasePool(path: path, configuration: configuration)
case .test:
let path = URL.temporaryDirectory.appending(component: "\(UUID().uuidString)-db.sqlite").path()
database = try DatabasePool(path: path, configuration: configuration)
case .preview:
database = try DatabaseQueue(configuration: configuration)
}
let database = try defaultDatabase(configuration: configuration)
logger.info("open '\(database.path)'")
var migrator = DatabaseMigrator()
#if DEBUG
migrator.eraseDatabaseOnSchemaChange = true
Expand Down
1 change: 1 addition & 0 deletions Sources/SQLiteData/Documentation.docc/SQLiteData.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ with SQLite to take full advantage of GRDB and SQLiteData.

### Database configuration and access

- ``defaultDatabase(path:configuration:)``
- ``GRDB/Database``
- ``Dependencies/DependencyValues/defaultDatabase``

Expand Down
44 changes: 44 additions & 0 deletions Sources/SQLiteData/StructuredQueries+GRDB/DefaultDatabase.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
import Dependencies
import Foundation
import GRDB

/// Prepares a context-sensitive database writer.
///
/// * In a live app context, a database is provisioned in the app container (unless explicitly
/// overridden with the `path` parameter).
/// * In an Xcode preview context, an in-memory database is provisioned.
/// * In a test context, a database pool is provisioned at a temporary file.
///
/// - Parameters:
/// - path: A path to the database. If `nil`, a path to a file in the application support
/// directory will be used.
/// - configuration: A database configuration.
/// - Returns: A context-sensitive database writer.
public func defaultDatabase(
path: String? = nil,
configuration: Configuration = Configuration()
) throws -> any DatabaseWriter {
let database: any DatabaseWriter
@Dependency(\.context) var context
switch context {
case .live:
var defaultPath: String {
get throws {
let applicationSupportDirectory = try FileManager.default.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
return applicationSupportDirectory.appendingPathComponent("SQLiteData.db").absoluteString
}
}
database = try DatabasePool(path: path ?? defaultPath, configuration: configuration)
case .preview:
database = try DatabaseQueue(configuration: configuration)
case .test:
database = try DatabasePool(
path: "\(NSTemporaryDirectory())\(UUID().uuidString).db",
configuration: configuration
)
}
return database
}

extension DependencyValues {
/// The default database used by `fetchAll`, `fetchOne`, and `fetch`.
///
Expand Down