Skip to content

Commit 2365bb3

Browse files
committed
db: Create GlobalSettings row as part of schema setup
This is a little more complicated in the code overall; but it simplifies the logic that runs each time the app starts up.
1 parent 6c041b1 commit 2365bb3

File tree

7 files changed

+824
-28
lines changed

7 files changed

+824
-28
lines changed

lib/model/database.dart

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import 'settings.dart';
99

1010
part 'database.g.dart';
1111

12-
/// The table of the user's chosen settings independent of account, on this
13-
/// client.
12+
/// The table of one [GlobalSettingsData] record, the user's chosen settings
13+
/// on this client that are independent of account.
1414
///
1515
/// These apply across all the user's accounts on this client (i.e. on this
1616
/// install of the app on this device).
17+
///
18+
/// This table should always have exactly one row (it's created by a migration).
1719
@DataClassName('GlobalSettingsData')
1820
class GlobalSettings extends Table {
1921
Column<String> get themeSetting => textEnum<ThemeSetting>()
@@ -78,6 +80,8 @@ VersionedSchema _getSchema({
7880
return Schema3(database: database);
7981
case 4:
8082
return Schema4(database: database);
83+
case 5:
84+
return Schema5(database: database);
8185
default:
8286
throw Exception('unknown schema version: $schemaVersion');
8387
}
@@ -98,7 +102,7 @@ class AppDatabase extends _$AppDatabase {
98102
// * Write a migration in `_migrationSteps` below.
99103
// * Write tests.
100104
@override
101-
int get schemaVersion => 4; // See note.
105+
int get schemaVersion => 5; // See note.
102106

103107
static Future<void> _dropAndCreateAll(Migrator m, {
104108
required int schemaVersion,
@@ -136,13 +140,31 @@ class AppDatabase extends _$AppDatabase {
136140
await m.addColumn(
137141
schema.globalSettings, schema.globalSettings.browserPreference);
138142
},
143+
from4To5: (m, schema) async {
144+
// Corresponds to the `into(globalSettings).insert` in `onCreate`.
145+
// This migration ensures there is a row in GlobalSettings.
146+
// (If the app already ran at schema 3 or 4, there will be;
147+
// if not, there won't be before this point.)
148+
await m.database.transaction(() async {
149+
final rows = await m.database.select(schema.globalSettings).get();
150+
if (rows.isEmpty) {
151+
await m.database.into(schema.globalSettings).insert(
152+
// No field values; just use the defaults for both fields.
153+
// (This is like `GlobalSettingsCompanion.insert()`, but
154+
// without dependence on the current schema.)
155+
RawValuesInsertable({}));
156+
}
157+
});
158+
},
139159
);
140160

141161
@override
142162
MigrationStrategy get migration {
143163
return MigrationStrategy(
144164
onCreate: (Migrator m) async {
145165
await m.createAll();
166+
// Corresponds to `from4to5` above.
167+
await into(globalSettings).insert(GlobalSettingsCompanion());
146168
},
147169
onUpgrade: (Migrator m, int from, int to) async {
148170
if (from > to) {
@@ -159,23 +181,10 @@ class AppDatabase extends _$AppDatabase {
159181
});
160182
}
161183

184+
// TODO rename ensureGlobalSettings to reflect its reduced role
162185
Future<GlobalSettingsData> ensureGlobalSettings() async {
163-
final settings = await select(globalSettings).get();
164-
// TODO(db): Enforce the singleton constraint more robustly.
165-
if (settings.isNotEmpty) {
166-
if (settings.length > 1) {
167-
assert(debugLog('Expected one globalSettings, got multiple: $settings'));
168-
}
169-
return settings.first;
170-
}
171-
172-
final rowsAffected = await into(globalSettings).insert(GlobalSettingsCompanion.insert());
173-
assert(rowsAffected == 1);
174-
final result = await select(globalSettings).get();
175-
if (result.length > 1) {
176-
assert(debugLog('Expected one globalSettings, got multiple: $result'));
177-
}
178-
return result.first;
186+
// The migrations ensure there is a row.
187+
return await (select(globalSettings)..limit(1)).getSingle();
179188
}
180189

181190
Future<int> createAccount(AccountsCompanion values) async {

lib/model/schema_versions.g.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,56 @@ i1.GeneratedColumn<String> _column_10(String aliasedName) =>
241241
true,
242242
type: i1.DriftSqlType.string,
243243
);
244+
245+
final class Schema5 extends i0.VersionedSchema {
246+
Schema5({required super.database}) : super(version: 5);
247+
@override
248+
late final List<i1.DatabaseSchemaEntity> entities = [
249+
globalSettings,
250+
accounts,
251+
];
252+
late final Shape2 globalSettings = Shape2(
253+
source: i0.VersionedTable(
254+
entityName: 'global_settings',
255+
withoutRowId: false,
256+
isStrict: false,
257+
tableConstraints: [],
258+
columns: [_column_9, _column_10],
259+
attachedDatabase: database,
260+
),
261+
alias: null,
262+
);
263+
late final Shape0 accounts = Shape0(
264+
source: i0.VersionedTable(
265+
entityName: 'accounts',
266+
withoutRowId: false,
267+
isStrict: false,
268+
tableConstraints: [
269+
'UNIQUE(realm_url, user_id)',
270+
'UNIQUE(realm_url, email)',
271+
],
272+
columns: [
273+
_column_0,
274+
_column_1,
275+
_column_2,
276+
_column_3,
277+
_column_4,
278+
_column_5,
279+
_column_6,
280+
_column_7,
281+
_column_8,
282+
],
283+
attachedDatabase: database,
284+
),
285+
alias: null,
286+
);
287+
}
288+
244289
i0.MigrationStepWithVersion migrationSteps({
245290
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
246291
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
247292
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
293+
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
248294
}) {
249295
return (currentVersion, database) async {
250296
switch (currentVersion) {
@@ -263,6 +309,11 @@ i0.MigrationStepWithVersion migrationSteps({
263309
final migrator = i1.Migrator(database, schema);
264310
await from3To4(migrator, schema);
265311
return 4;
312+
case 4:
313+
final schema = Schema5(database: database);
314+
final migrator = i1.Migrator(database, schema);
315+
await from4To5(migrator, schema);
316+
return 5;
266317
default:
267318
throw ArgumentError.value('Unknown migration from $currentVersion');
268319
}
@@ -273,10 +324,12 @@ i1.OnUpgrade stepByStep({
273324
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
274325
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
275326
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
327+
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
276328
}) => i0.VersionedSchema.stepByStepHelper(
277329
step: migrationSteps(
278330
from1To2: from1To2,
279331
from2To3: from2To3,
280332
from3To4: from3To4,
333+
from4To5: from4To5,
281334
),
282335
);

lib/model/store.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ abstract class GlobalStore extends ChangeNotifier {
6565
GlobalSettingsData _globalSettings;
6666

6767
/// Update the global settings in the store.
68-
///
69-
/// The global settings must already exist in the store.
7068
Future<void> updateGlobalSettings(GlobalSettingsCompanion data) async {
7169
await doUpdateGlobalSettings(data);
7270
_globalSettings = _globalSettings.copyWithCompanion(data);

test/model/database_test.dart

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'schemas/schema_v1.dart' as v1;
1111
import 'schemas/schema_v2.dart' as v2;
1212
import 'schemas/schema_v3.dart' as v3;
1313
import 'schemas/schema_v4.dart' as v4;
14+
import 'schemas/schema_v5.dart' as v5;
1415
import 'store_checks.dart';
1516

1617
void main() {
@@ -29,8 +30,6 @@ void main() {
2930
});
3031

3132
test('ensure single GlobalSettings row', () async {
32-
check(await database.select(database.globalSettings).get()).isEmpty();
33-
3433
final globalSettings = await database.ensureGlobalSettings();
3534
check(await database.select(database.globalSettings).get())
3635
.single.equals(globalSettings);
@@ -44,12 +43,9 @@ void main() {
4443
test('does not crash if multiple global settings rows', () async {
4544
await database.into(database.globalSettings)
4645
.insert(const GlobalSettingsCompanion(themeSetting: Value(ThemeSetting.dark)));
47-
await database.into(database.globalSettings)
48-
.insert(const GlobalSettingsCompanion(themeSetting: Value(ThemeSetting.light)));
4946

5047
check(await database.select(database.globalSettings).get()).length.equals(2);
51-
check(await database.ensureGlobalSettings())
52-
.themeSetting.equals(ThemeSetting.dark);
48+
check(await database.ensureGlobalSettings()).themeSetting.isNull();
5349
});
5450

5551
test('GlobalSettings updates work', () async {
@@ -236,6 +232,42 @@ void main() {
236232
check(globalSettings.browserPreference).isNull();
237233
await after.close();
238234
});
235+
236+
test('upgrade to v5: with existing GlobalSettings row, do nothing', () async {
237+
final schema = await verifier.schemaAt(4);
238+
final before = v4.DatabaseAtV4(schema.newConnection());
239+
await before.into(before.globalSettings).insert(
240+
v4.GlobalSettingsCompanion.insert(
241+
themeSetting: Value(ThemeSetting.light.name)));
242+
await before.close();
243+
244+
final db = AppDatabase(schema.newConnection());
245+
await verifier.migrateAndValidate(db, 5);
246+
await db.close();
247+
248+
final after = v5.DatabaseAtV5(schema.newConnection());
249+
final globalSettings = await after.select(after.globalSettings).getSingle();
250+
check(globalSettings.themeSetting).equals(ThemeSetting.light.name);
251+
check(globalSettings.browserPreference).isNull();
252+
await after.close();
253+
});
254+
255+
test('upgrade to v5: with no existing GlobalSettings row, insert one', () async {
256+
final schema = await verifier.schemaAt(4);
257+
final before = v4.DatabaseAtV4(schema.newConnection());
258+
check(await before.select(before.globalSettings).get()).isEmpty();
259+
await before.close();
260+
261+
final db = AppDatabase(schema.newConnection());
262+
await verifier.migrateAndValidate(db, 5);
263+
await db.close();
264+
265+
final after = v5.DatabaseAtV5(schema.newConnection());
266+
final globalSettings = await after.select(after.globalSettings).getSingle();
267+
check(globalSettings.themeSetting).isNull();
268+
check(globalSettings.browserPreference).isNull();
269+
await after.close();
270+
});
239271
});
240272
}
241273

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"global_settings","was_declared_in_moor":false,"columns":[{"name":"theme_setting","getter_name":"themeSetting","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<ThemeSetting>(ThemeSetting.values)","dart_type_name":"ThemeSetting"}},{"name":"browser_preference","getter_name":"browserPreference","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<BrowserPreference>(BrowserPreference.values)","dart_type_name":"BrowserPreference"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"accounts","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"realm_url","getter_name":"realmUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const UriConverter()","dart_type_name":"Uri"}},{"name":"user_id","getter_name":"userId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"api_key","getter_name":"apiKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_version","getter_name":"zulipVersion","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_merge_base","getter_name":"zulipMergeBase","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_feature_level","getter_name":"zulipFeatureLevel","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"acked_push_token","getter_name":"ackedPushToken","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"unique_keys":[["realm_url","user_id"],["realm_url","email"]]}}]}

test/model/schemas/schema.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'schema_v1.dart' as v1;
77
import 'schema_v2.dart' as v2;
88
import 'schema_v3.dart' as v3;
99
import 'schema_v4.dart' as v4;
10+
import 'schema_v5.dart' as v5;
1011

1112
class GeneratedHelper implements SchemaInstantiationHelper {
1213
@override
@@ -20,10 +21,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
2021
return v3.DatabaseAtV3(db);
2122
case 4:
2223
return v4.DatabaseAtV4(db);
24+
case 5:
25+
return v5.DatabaseAtV5(db);
2326
default:
2427
throw MissingSchemaException(version, versions);
2528
}
2629
}
2730

28-
static const versions = const [1, 2, 3, 4];
31+
static const versions = const [1, 2, 3, 4, 5];
2932
}

0 commit comments

Comments
 (0)