2727/// let dbQueue = try DatabaseQueue(path: "/path/to/db.sqlite", configuration: config)
2828/// ```
2929///
30- /// For temporary use, call ``Database/withSchemaSource(_:execute:)``:
30+ /// ## Schema Sources and Migrations
31+ ///
32+ /// By default, schema sources are disabled during <doc:Migrations>. In
33+ /// migrations that need a schema source, you can use
34+ /// ``Database/withSchemaSource(_:execute:)``:
3135///
3236/// ```swift
33- /// try dbQueue.read { db in
37+ /// migrator.registerMigration("My migration") { db in
38+ /// // No schema source is enabled at this point.
3439/// try db.withSchemaSource(MySchemaSource()) {
35- /// .. .
40+ /// // Here the provided schema source is in effect .
3641/// }
3742/// }
3843/// ```
3944///
45+ /// Take care that **a good migration is a migration that is never
46+ /// modified once it has shipped**: check
47+ /// <doc:Migrations#Good-Practices-for-Defining-Migrations>.
48+ ///
4049/// ## Topics
4150///
4251/// ### Customizing the Database Schema
@@ -54,16 +63,22 @@ public protocol DatabaseSchemaSource: Sendable {
5463 /// to specify that the view has no primary key. The default
5564 /// implementation returns nil.
5665 ///
57- /// In your implementation, make sure that:
66+ /// In your implementation, make sure that the returned columns define
67+ /// a genuine **primary key**:
68+ ///
69+ /// - All columns exist in the provided view.
70+ /// - The set of columns reliably identifies and distinguishes between
71+ /// each individual row in the view.
72+ /// - No column contains NULL values.
5873 ///
59- /// - The returned columns exist in the database schema.
60- /// - The returned columns identify unique rows.
61- /// - The returned columns do not contain NULL values.
74+ /// It is a programmer error with undefined consequences to miss
75+ /// those requirements.
6276 ///
6377 /// For example:
6478 ///
6579 /// ```swift
66- /// // A schema source that specifies that views have an "id" primary key.
80+ /// // A schema source that specifies that all views are identified by
81+ /// // their "id" column:
6782 /// struct MySchemaSource: DatabaseSchemaSource {
6883 /// func columnsForPrimaryKey(_ db: Database, inView view: DatabaseObjectID) {
6984 /// ["id"]
@@ -88,18 +103,32 @@ public protocol DatabaseSchemaSource: Sendable {
88103 /// }
89104 /// ```
90105 ///
91- /// When you are developing a library that accesses database files owned
92- /// by the users of your library, then you you should allow the host
93- /// application to deal with their own views. To do so, return nil for
94- /// views that your library does not manage. When necessary, use the
95- /// `db` argument in order to query the database schema.
106+ /// ### Schema Sources in Libraries
107+ ///
108+ /// When you are developing a schema source for a library, some extra
109+ /// care is necessary whenever the database file is owned by the users
110+ /// of your library. Your users may define views for their own purposes,
111+ /// and your library knows nothing about the eventual primary key of
112+ /// those views.
113+ ///
114+ /// In this case, make your schema source `public`, and have this method
115+ /// return `nil` for those unknow views. If needed, use the `db`
116+ /// argument and query the database schema with
117+ /// <doc:DatabaseSchemaIntrospection> methods.
118+ ///
119+ /// For example:
96120 ///
97121 /// ```swift
98- /// struct MyLbrarySchemaSource: DatabaseSchemaSource {
99- /// func columnsForPrimaryKey(_ db: Database, inView view: DatabaseObjectID) {
122+ /// // A well-behaved schema source defined by a library is public
123+ /// // and returns nil for unknown views.
124+ /// public struct MyLibrarySchemaSource: DatabaseSchemaSource {
125+ /// public func columnsForPrimaryKey(_ db: Database, inView view: DatabaseObjectID) {
100126 /// if view.name == "playerView" {
101127 /// // This is a view managed by my library:
102128 /// return ["id"]
129+ /// } else if try db.tableExists(view.name + "MyLibrary") {
130+ /// // This is a view managed by my library:
131+ /// return ["uuid"]
103132 /// } else {
104133 /// // Not a view managed by my library:
105134 /// // don't mess with user's schema
@@ -108,6 +137,28 @@ public protocol DatabaseSchemaSource: Sendable {
108137 /// }
109138 /// }
110139 /// ```
140+ ///
141+ /// With such a setup, your user will be able to deal with their
142+ /// own views, by chaining your schema source with other ones
143+ /// (see ``DatabaseSchemaSource/then(_:)``):
144+ ///
145+ /// ```swift
146+ /// // Application code
147+ /// import MyLibrary
148+ /// import GRDB
149+ ///
150+ /// let myLibrarySchemaSource = MyLibrarySchemaSource()
151+ /// let customSchemaSource = TheirCustomSchemaSource()
152+ /// let schemaSource = myLibrarySchemaSource.then(customSchemaSource)
153+ ///
154+ /// var config = Configuration()
155+ /// config.schemaSource = schemaSource
156+ /// let dbQueue = try DatabaseQueue(path: "...", configuration: config)
157+ /// ```
158+ ///
159+ /// - Parameters:
160+ /// - db: A database connection.
161+ /// - view: The identifier of a database view.
111162 func columnsForPrimaryKey(
112163 _ db: Database ,
113164 inView view: DatabaseObjectID
@@ -128,6 +179,18 @@ extension DatabaseSchemaSource {
128179extension DatabaseSchemaSource {
129180 /// Returns a schema source that queries `other` when this source does
130181 /// not perform customization.
182+ ///
183+ /// For example:
184+ ///
185+ /// ```swift
186+ /// let schemaSource1 = SomeSchemaSource()
187+ /// let schemaSource2 = AnotherSchemaSource()
188+ /// let schemaSource = schemaSource1.then(schemaSource2)
189+ ///
190+ /// var config = Configuration()
191+ /// config.schemaSource = schemaSource
192+ /// let dbQueue = try DatabaseQueue(path: "...", configuration: config)
193+ /// ```
131194 public func then( _ other: some DatabaseSchemaSource ) -> some DatabaseSchemaSource {
132195 Chained2SchemaSource ( first: self , second: other)
133196 }
0 commit comments