Skip to content

Commit cdf480e

Browse files
committed
Migrations guide cleanup
1 parent da3059f commit cdf480e

File tree

2 files changed

+54
-55
lines changed

2 files changed

+54
-55
lines changed

GRDB/Documentation.docc/Migrations.md

Lines changed: 53 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ You setup migrations in a ``DatabaseMigrator`` instance. For example:
1414
var migrator = DatabaseMigrator()
1515

1616
// 1st migration
17-
migrator.registerMigration("createLibrary") { db in
17+
migrator.registerMigration("Create authors") { db in
1818
try db.create(table: "author") { t in
1919
t.autoIncrementedPrimaryKey("id")
2020
t.column("creationDate", .datetime)
2121
t.column("name", .text)
2222
}
23+
}
2324

25+
// 2nd migration
26+
migrator.registerMigration("Add books and author.birthYear") { db in
2427
try db.create(table: "book") { t in
2528
t.autoIncrementedPrimaryKey("id")
2629
t.column("authorId", .integer)
@@ -29,10 +32,7 @@ migrator.registerMigration("createLibrary") { db in
2932
.references("author", onDelete: .cascade)
3033
t.column("title", .text).notNull()
3134
}
32-
}
3335

34-
// 2nd migration
35-
migrator.registerMigration("AddBirthYearToAuthors") { db in
3636
try db.alter(table: "author") { t
3737
t.add(column: "birthYear", .integer)
3838
}
@@ -83,14 +83,55 @@ try dbQueue.read { db in
8383

8484
**The memory of applied migrations is stored in the database itself** (in a reserved table).
8585

86+
## Defining the Database Schema
87+
88+
SQLite directly supports a [limited set of schema alterations](https://www.sqlite.org/lang.html). Many of them are available as `Database` methods such as ``Database/create(table:options:body:)``, ``Database/alter(table:body:)``, etc.
89+
90+
> Tip: When you use a Swift API instead of a raw SQL query, GRDB can check its availability on the SQLite version that ships on the target operating system.
91+
92+
Other changes to the schema are still possible, by recreating tables. For example:
93+
94+
```swift
95+
migrator.registerMigration("Add NOT NULL check on author.name") { db in
96+
try db.create(table: "new_author") { t in
97+
t.autoIncrementedPrimaryKey("id")
98+
t.column("creationDate", .datetime)
99+
t.column("name", .text).notNull()
100+
}
101+
try db.execute(sql: "INSERT INTO new_author SELECT * FROM author")
102+
try db.drop(table: "author")
103+
try db.rename(table: "new_author", to: "author")
104+
}
105+
```
106+
107+
This technique is described in the [Making Other Kinds Of Table Schema Changes](https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes) section of the SQLite documentation.
108+
109+
The detailed sequence of operations for recreating a database table is:
110+
111+
1. Remember the format of all indexes, triggers, and views associated with table X. This information will be needed in step 5 below. One way to do this is to run a query like the following: `SELECT type, sql FROM sqlite_schema WHERE tbl_name='X'`.
112+
113+
2. Use `CREATE TABLE` to construct a new table "new_X" that is in the desired revised format of table X. Make sure that the name "new_X" does not collide with any existing table name, of course.
114+
115+
2. Transfer content from X into new_X using a statement like: `INSERT INTO new_X SELECT ... FROM X`.
116+
117+
3. Drop the old table X: `DROP TABLE X`.
118+
119+
4. Change the name of new_X to X using: `ALTER TABLE new_X RENAME TO X`.
120+
121+
5. Use `CREATE INDEX`, `CREATE TRIGGER`, and `CREATE VIEW` to reconstruct indexes, triggers, and views associated with table X. Perhaps use the old format of the triggers, indexes, and views saved from step 3 above as a guide, making changes as appropriate for the alteration.
122+
123+
6. If any views refer to table X in a way that is affected by the schema change, then drop those views using `DROP VIEW` and recreate them with whatever changes are necessary to accommodate the schema change using `CREATE VIEW`.
124+
125+
> Important: When recreating a table, be sure to follow the above procedure exactly, in the given order, or you might corrupt triggers, views, and foreign key constraints.
126+
86127
## Good Practices for Defining Migrations
87128

88129
**A good migration is a migration that is never modified once it has shipped.**
89130

90131
It is must easier to control the schema of all databases deployed on users' devices when migrations define a stable timeline of schema versions. For this reason, it is recommended that migrations define the database schema with **strings**:
91132

92133
```swift
93-
migrator.registerMigration("createLibrary") { db in
134+
migrator.registerMigration("Create authors") { db in
94135
// 👍 RECOMMENDED
95136
try db.create(table: "author") { t in
96137
t.autoIncrementedPrimaryKey("id")
@@ -120,7 +161,7 @@ Setting ``DatabaseMigrator/eraseDatabaseOnSchemaChange`` is useful during applic
120161

121162
> Warning: This option can destroy your precious users' data!
122163
123-
It is recommended that this option does not ship in the released application. Hide it behind `#if DEBUG`:
164+
It is recommended that this option does not ship in the released application: hide it behind `#if DEBUG` as below.
124165

125166
```swift
126167
var migrator = DatabaseMigrator()
@@ -130,51 +171,9 @@ migrator.eraseDatabaseOnSchemaChange = true
130171
#endif
131172
```
132173

133-
## Advanced Database Schema Changes
134-
135-
SQLite directly supports a [limited set of schema alterations](https://www.sqlite.org/lang.html). Many of them are available as `Database` methods such as ``Database/create(table:options:body:)``, etc.
136-
137-
> You should prefer the Swift API, because GRDB only enables apis that are available on SQLite version that ships on the target operating system.
138-
139-
Arbitrary changes to the schema design of any table are still possible, by recreating the table. For example:
140-
141-
```swift
142-
migrator.registerMigration("Add NOT NULL check on author.name") { db in
143-
try db.create(table: "new_author") { t in
144-
t.autoIncrementedPrimaryKey("id")
145-
t.column("creationDate", .datetime)
146-
t.column("name", .text).notNull()
147-
}
148-
try db.execute(sql: "INSERT INTO new_author SELECT * FROM author")
149-
try db.drop(table: "author")
150-
try db.rename(table: "new_author", to: "author")
151-
}
152-
```
153-
154-
This technique is described in the [Making Other Kinds Of Table Schema Changes](https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes) section of the SQLite documentation.
155-
156-
The detailed sequence of operations for recreating a database table is:
157-
158-
1. Remember the format of all indexes, triggers, and views associated with table X. This information will be needed in step 5 below. One way to do this is to run a query like the following: `SELECT type, sql FROM sqlite_schema WHERE tbl_name='X'`.
159-
160-
2. Use `CREATE TABLE` to construct a new table "new_X" that is in the desired revised format of table X. Make sure that the name "new_X" does not collide with any existing table name, of course.
161-
162-
2. Transfer content from X into new_X using a statement like: `INSERT INTO new_X SELECT ... FROM X`.
163-
164-
3. Drop the old table X: `DROP TABLE X`.
165-
166-
4. Change the name of new_X to X using: `ALTER TABLE new_X RENAME TO X`.
167-
168-
5. Use `CREATE INDEX`, `CREATE TRIGGER`, and `CREATE VIEW` to reconstruct indexes, triggers, and views associated with table X. Perhaps use the old format of the triggers, indexes, and views saved from step 3 above as a guide, making changes as appropriate for the alteration.
169-
170-
6. If any views refer to table X in a way that is affected by the schema change, then drop those views using `DROP VIEW` and recreate them with whatever changes are necessary to accommodate the schema change using `CREATE VIEW`.
171-
172-
> Warning: Be sure to follow the above procedure exactly, in the given order, or you might corrupt triggers, views, and foreign key constraints.
173-
174174
## Foreign Key Checks
175175

176-
The technique described in the previous <doc:Migrations#Advanced-Database-Schema-Changes> chapter creates very undesired churn w.r.t. foreign keys:
177-
by default, each migration temporarily disables foreign keys, and performs a full check of all foreign keys in the database before it is committed on disk.
176+
By default, each migration temporarily disables foreign keys, and performs a full check of all foreign keys in the database before it is committed on disk.
178177

179178
When the database becomes very big, those checks may have a noticeable impact on migration performances. You'll know this by profiling migrations, and looking for the time spent in the `checkForeignKeys` method.
180179

@@ -188,7 +187,7 @@ When you register a migration with `.immediate` foreign key checks, the migratio
188187
migrator.registerMigration("Faster migration", foreignKeyChecks: .immediate) { db in ... }
189188
```
190189

191-
Such a migration is much faster, and it still guarantees database integrity. But it must only execute schema alterations directly supported by SQLite. Migrations that recreate tables as described in <doc:Migrations#Advanced-Database-Schema-Changes> **must not** run with immediate foreign keys checks. You'll need to use the second mitigation technique:
190+
Such a migration is much faster, and it still guarantees database integrity. But it must only execute schema alterations directly supported by SQLite. Migrations that recreate tables as described in <doc:Migrations#Defining-the-Database-Schema> **must not** run with immediate foreign keys checks. You'll need to use the second mitigation technique:
192191

193192
**Your second mitigation technique is to disable deferred foreign key checks.**
194193

@@ -202,14 +201,14 @@ migrator = migrator.disablingDeferredForeignKeyChecks()
202201
203202
In order to prevent foreign key violations from being committed to disk, you can:
204203

205-
- Register migrations with immediate foreign key checks, as long as they do not recreate tables as described in <doc:Migrations#Advanced-Database-Schema-Changes>:
204+
- Register migrations with immediate foreign key checks, as long as they do not recreate tables as described in <doc:Migrations#Defining-the-Database-Schema>:
206205

207206
```swift
208207
migrator = migrator.disablingDeferredForeignKeyChecks()
209-
migrator.registerMigration("Checked migration", foreignKeyChecks: .immediate) { db in ... }
208+
migrator.registerMigration("Checked migration", foreignKeyChecks: .immediate) { db in ... }
210209
```
211210

212-
- Perform foreign key checks on some tables only, at the end of a migration:
211+
- Perform foreign key checks on some tables only, before the migration is committed on disk:
213212

214213
```swift
215214
migrator = migrator.disablingDeferredForeignKeyChecks()
@@ -230,7 +229,7 @@ As in the above example, check for foreign key violations with the ``Database/ch
230229
try db.checkForeignKeys(in: "book")
231230
```
232231

233-
Alternatively, you can iterate a cursor of ``ForeignKeyViolation``.
232+
Alternatively, you can deal with each individual violation by iterating a cursor of ``ForeignKeyViolation``.
234233

235234
## Topics
236235

GRDB/Migration/DatabaseMigrator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public struct DatabaseMigrator {
6969
///
7070
/// Immediate foreign key checks are NOT compatible with migrations that
7171
/// recreate tables as described
72-
/// in <doc:Migrations#Advanced-Database-Schema-Changes>.
72+
/// in <doc:Migrations#Defining-the-Database-Schema>.
7373
case immediate
7474
}
7575

0 commit comments

Comments
 (0)