66///
77/// The use case for a custom schema source is enabling GRDB features that
88/// would not work with the built-in schema introspection that is provided
9- /// by SQLite. For example, a custom schema source can help record types
10- /// that read or write in a database view.
9+ /// by SQLite.
1110///
12- /// You do not interact directly with values of such a type. Instead, you
13- /// configure a database connection with ``Configuration/schemaSource``, and
14- /// you call <doc:DatabaseSchemaIntrospection> methods on a
15- /// ``Database`` connection.
11+ /// For example, if your database schema contains a view and you wish to use
12+ /// GRDB features that need a primary key, such as the
13+ /// ``FetchableRecord/find(_:id:)`` record method, or the persistence
14+ /// methods of the ``PersistableRecord`` protocol, then use a schema source
15+ /// that implements the ``columnsForPrimaryKey(_:inView:)`` method.
16+ ///
17+ /// ## Enabling a Schema Source
18+ ///
19+ /// To enable a schema source in a database connection, configure its
20+ /// ``Configuration/schemaSource``:
21+ ///
22+ /// ```swift
23+ /// struct MySchemaSource: DatabaseSchemaSource { ... }
24+ ///
25+ /// var config = Configuration()
26+ /// config.schemaSource = MySchemaSource()
27+ /// let dbQueue = try DatabaseQueue(path: "/path/to/db.sqlite", configuration: config)
28+ /// ```
29+ ///
30+ /// For temporary use, call ``Database/withSchemaSource(_:execute:)``:
31+ ///
32+ /// ```swift
33+ /// try dbQueue.read { db in
34+ /// try db.withSchemaSource(MySchemaSource()) {
35+ /// ...
36+ /// }
37+ /// }
38+ /// ```
39+ ///
40+ /// ## Topics
41+ ///
42+ /// ### Customizing the Database Schema
43+ ///
44+ /// - ``columnsForPrimaryKey(_:inView:)``
1645public protocol DatabaseSchemaSource : Sendable {
1746 /// Returns the names of the columns for the primary key in the
1847 /// provided database view.
@@ -30,18 +59,46 @@ public protocol DatabaseSchemaSource: Sendable {
3059 /// For example:
3160 ///
3261 /// ```swift
62+ /// // A schema source that specifies that views have an "id" primary key.
3363 /// struct MySchemaSource: DatabaseSchemaSource {
34- /// func columnsForPrimaryKey(
35- /// _ db: Database,
36- /// inView view: DatabaseObjectID
37- /// ) throws -> [String]? {
38- /// switch table.name {
39- /// case "player":
40- /// // Use the email column as the primary key of this view.
41- /// return ["email"]
42- ///
43- /// default:
44- /// // Do not customize.
64+ /// func columnsForPrimaryKey(_ db: Database, inView view: DatabaseObjectID) {
65+ /// ["id"]
66+ /// }
67+ /// }
68+ ///
69+ /// // A database connection configured with the schema source.
70+ /// var config = Configuration()
71+ /// config.schemaSource = MySchemaSource()
72+ /// let dbQueue = try DatabaseQueue(path: "/path/to/db.sqlite", configuration: config)
73+ ///
74+ /// // A record type that feeds from a view.
75+ /// struct Player: Decodable, Identifiable, FetchableRecord {
76+ /// static let databaseTableName = "playerView"
77+ /// var id: String
78+ /// var name: String
79+ /// }
80+ ///
81+ /// // Thanks to the schema source, we can fetch players by id:
82+ /// let player = try dbQueue.read { db in
83+ /// try Player.find(db, id: "alice")
84+ /// }
85+ /// ```
86+ ///
87+ /// When you are developing a library that accesses database files owned
88+ /// by the users of your library, then you you should allow the host
89+ /// application to deal with their own views. To do so, return nil for
90+ /// views that your library does not manage. When necessary, use the
91+ /// `db` argument in order to query the database schema.
92+ ///
93+ /// ```swift
94+ /// struct MyLbrarySchemaSource: DatabaseSchemaSource {
95+ /// func columnsForPrimaryKey(_ db: Database, inView view: DatabaseObjectID) {
96+ /// if view.name == "playerView" {
97+ /// // This is a view managed by my library:
98+ /// return ["id"]
99+ /// } else {
100+ /// // Not a view managed by my library:
101+ /// // don't mess with user's schema
45102 /// return nil
46103 /// }
47104 /// }
0 commit comments