You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: GRDB/Documentation.docc/SingleRowTables.md
+85-57Lines changed: 85 additions & 57 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,9 +8,11 @@ Database tables that contain a single row can store configuration values, user p
8
8
9
9
They are a suitable alternative to `UserDefaults` in some applications, especially when configuration refers to values found in other database tables, and database integrity is a concern.
10
10
11
-
An alternative way to store such configuration is a table of key-value pairs: two columns, and one row for each configuration value. This technique works, but it has a few drawbacks: you will have to deal with the various types of configuration values (strings, integers, dates, etc), and you won't be able to define foreign keys. This is why we won't explore key-value tables.
11
+
A possible way to store such configuration is a table of key-value pairs: two columns, and one row for each configuration value. This technique works, but it has a few drawbacks: one has to deal with the various types of configuration values (strings, integers, dates, etc), and it is not possible to define foreign keys. This is why we won't explore key-value tables.
12
12
13
-
This guide helps implementing a single-row table with GRDB, with recommendations on the database schema, migrations, and the design of a matching record type.
13
+
In this guide, we'll implement a single-row table, with recommendations on the database schema, migrations, and the design of a Swift API for accessing the configuration values. The schema will define one column for each configuration value, because we aim at being able to deal with foreign keys and references to other tables. You may prefer storing configuration values in a single JSON column. In this case, take inspiration from this guide, as well as <doc:JSON>.
14
+
15
+
We will also aim at providing a default value for a given configuration, even when it is not stored on disk yet. This is a feature similar to [`UserDefaults.register(defaults:)`](https://developer.apple.com/documentation/foundation/userdefaults/1417065-register).
14
16
15
17
## The Single-Row Table
16
18
@@ -20,63 +22,43 @@ We want to instruct SQLite that our table must never contain more than one row.
20
22
21
23
SQLite is not able to guarantee that the table is never empty, so we have to deal with two cases: either the table is empty, or it contains one row.
22
24
23
-
Those two cases can create a nagging question for the application. By default, inserts fail when the row already exists, and updates fail when the table is empty. In order to avoid those errors, we will have the app deal with updates in the <doc:SingleRowTables#The-Single-Row-Record> section below. Right now, we instruct SQLite to just replace the eventual existing row in case of conflicting inserts:
24
-
25
-
```swift
26
-
// CREATE TABLE appConfiguration (
27
-
// id INTEGER PRIMARY KEY ON CONFLICT REPLACE CHECK (id = 1),
28
-
// flag BOOLEAN NOT NULL,
29
-
// ...)
30
-
try db.create(table: "appConfiguration") { t in
31
-
// Single row guarantee: have inserts replace the existing row
When you use <doc:Migrations>, you may wonder if it is a good idea or not to perform an initial insert just after the table is created. Well, this is not recommended:
25
+
Those two cases can create a nagging question for the application. By default, inserts fail when the row already exists, and updates fail when the table is empty. In order to avoid those errors, we will have the app deal with updates in the <doc:SingleRowTables#The-Single-Row-Record> section below. Right now, we instruct SQLite to just replace the eventual existing row in case of conflicting inserts.
43
26
44
27
```swift
45
-
// NOT RECOMMENDED
46
28
migrator.registerMigration("appConfiguration") { db in
29
+
// CREATE TABLE appConfiguration (
30
+
// id INTEGER PRIMARY KEY ON CONFLICT REPLACE CHECK (id = 1),
try db.execute(sql: "INSERT INTO appConfiguration DEFAULT VALUES")
59
43
}
60
44
```
61
45
62
-
It is not a good idea to populate the table in a migration, for two reasons:
46
+
Note how the database table is defined in a migration. That's because most apps evolve, and need to add other configuration columns eventually. See <doc:Migrations>for more information.
63
47
64
-
1. This migration is not a hard guarantee that the table will never be empty. As a consequence, this won't prevent the application code from dealing with the possibility of a missing row. On top of that, this application code may not use the same default values as the SQLite schema, with unclear consequences.
48
+
We have defined a `storedFlag` column that can be NULL. That may be surprising, because optional booleans are usually a bad idea! But we can deal with this NULL at runtime, and nullable columns have a few advantages:
65
49
66
-
2. Migrations that have been deployed on the users' devices should never change (see <doc:Migrations#Good-Practices-for-Defining-Migrations>). Inserting an initial row in a migration makes it difficult for the application to adjust the sensible default values in a future version.
50
+
- NULL means that the application user had not made a choice yet. When `storedFlag` is NULL, the app can use a default value, such as `true`.
51
+
- As application evolves, application will need to add new configuration columns. It is not always possible to provide a sensible default value for these new columns, at the moment the table is modified. On the other side, it is generally possible to deal with those NULL values at runtime.
67
52
68
-
The recommended migration creates the table, nothing more:
53
+
Despite those arguments, some apps absolutely require a value. In this case, don't weaken the application logic and make sure the database can't store a NULL value:
69
54
70
55
```swift
71
-
//RECOMMENDED
56
+
//DO NOT hesitate requiring NOT NULL columns when the app requires it.
72
57
migrator.registerMigration("appConfiguration") { db in
0 commit comments