Skip to content

Commit 4116b95

Browse files
committed
Schema validation: Allow ignoring column constraints
Closes #3575
1 parent 9946b02 commit 4116b95

File tree

8 files changed

+120
-42
lines changed

8 files changed

+120
-42
lines changed

drift_dev/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
## 2.26.2-dev
1+
## 2.27.0-dev
22

33
- Fix generating versioned schema code for columns referencing other columns in
44
`generatedAs` expressions.
5+
- Schema validation: Allow ignoring column constraints.
56

67
## 2.26.1
78

drift_dev/lib/api/migrations_common.dart

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,17 @@ abstract class SchemaVerifier<DB extends CommonDatabase> {
5454
/// the database into the expected schema. If the comparison fails, a
5555
/// [SchemaMismatch] exception will be thrown.
5656
///
57-
/// If [validateDropped] is enabled (defaults to `false`), the method also
58-
/// validates that no further tables, triggers or views apart from those
59-
/// expected exist.
60-
Future<void> migrateAndValidate(GeneratedDatabase db, int expectedVersion,
61-
{bool validateDropped = false});
57+
/// The [ValidationOptions] can be used to make the schema validation more
58+
/// strict (e.g. by enabling [ValidationOptions.validateDropped] to ensure
59+
/// that no old tables continue to exist if they're not referenced in the new
60+
/// schema) or more lenient (e.g. by disabling
61+
/// [ValidationOptions.validateColumnConstraints]).
62+
Future<void> migrateAndValidate(
63+
GeneratedDatabase db,
64+
int expectedVersion, {
65+
ValidationOptions options = const ValidationOptions(),
66+
@Deprecated('Use field in ValidationOptions instead') bool? validateDropped,
67+
});
6268

6369
/// Utility function used by generated tests to verify that migrations
6470
/// modify the database schema as expected.
@@ -156,3 +162,36 @@ class InitializedSchema<DB extends CommonDatabase> {
156162
/// forgetting to call [close] does not have terrible side-effects.
157163
void close() => rawDatabase.dispose();
158164
}
165+
166+
/// Options that control how schemas are compared to find mismatches.
167+
final class ValidationOptions {
168+
/// When enabled (defaults to `false`), validate that no furhter tables,
169+
/// triggers or views apart from those expected exist.
170+
final bool validateDropped;
171+
172+
/// When enabled (defualts to `true`), validate column constraints.
173+
///
174+
/// When disabled, schema verification passes even without
175+
final bool validateColumnConstraints;
176+
177+
const ValidationOptions({
178+
this.validateDropped = false,
179+
this.validateColumnConstraints = true,
180+
});
181+
182+
/// Returns new [ValidationOptions] with the [ValidationOptions.validateDropped]
183+
/// field replaced if [validateDropped] is not null.validateDropped
184+
///
185+
/// This is used for backwards-compatibility when [validateDropped] was the
186+
/// only option and no [ValidationOptions] class existed.
187+
ValidationOptions applyDeprecatedValidateDroppedParam(bool? validateDropped) {
188+
if (validateDropped case final changed?) {
189+
return ValidationOptions(
190+
validateColumnConstraints: validateColumnConstraints,
191+
validateDropped: changed,
192+
);
193+
} else {
194+
return this;
195+
}
196+
}
197+
}

drift_dev/lib/api/migrations_native.dart

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,23 @@ extension VerifySelf on GeneratedDatabase {
4343
/// database, (perhaps in a [MigrationStrategy.beforeOpen] callback) to verify
4444
/// that your database schema is what drift expects.
4545
///
46-
/// When [validateDropped] is enabled (it is by default), this method also
47-
/// verifies that all schema elements that you've deleted at some point are no
48-
/// longer present in your runtime schema.
46+
/// The [common.ValidationOptions] can be used to make the schema validation
47+
/// more strict (e.g. by enabling [common.ValidationOptions.validateDropped]
48+
/// to ensure that no old tables continue to exist if they're not referenced
49+
/// in the new schema) or more lenient (e.g. by disabling
50+
/// [common.ValidationOptions.validateColumnConstraints]).
4951
///
5052
/// This variant of [validateDatabaseSchema] is only supported on native
5153
/// platforms (Android, iOS, macOS, Linux and Windows).
52-
Future<void> validateDatabaseSchema({bool validateDropped = true}) async {
53-
await verifyDatabase(this, validateDropped, NativeDatabase.memory);
54+
Future<void> validateDatabaseSchema({
55+
common.ValidationOptions options = const common.ValidationOptions(),
56+
@Deprecated('Use field in ValidationOptions instead') bool? validateDropped,
57+
}) async {
58+
await verifyDatabase(
59+
this,
60+
options.applyDeprecatedValidateDroppedParam(validateDropped),
61+
NativeDatabase.memory,
62+
);
5463
}
5564
}
5665

drift_dev/lib/api/migrations_web.dart

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,23 @@ extension VerifySelf on GeneratedDatabase {
4545
/// database, (perhaps in a [MigrationStrategy.beforeOpen] callback) to verify
4646
/// that your database schema is what drift expects.
4747
///
48-
/// When [validateDropped] is enabled (it is by default), this method also
49-
/// verifies that all schema elements that you've deleted at some point are no
50-
/// longer present in your runtime schema.
48+
/// The [common.ValidationOptions] can be used to make the schema validation
49+
/// more strict (e.g. by enabling [common.ValidationOptions.validateDropped]
50+
/// to ensure that no old tables continue to exist if they're not referenced
51+
/// in the new schema) or more lenient (e.g. by disabling
52+
/// [common.ValidationOptions.validateColumnConstraints]).
5153
///
52-
/// This variant of [validateDatabaseSchema] is only supported on native
53-
/// platforms (Android, iOS, macOS, Linux and Windows).
54+
/// This variant of [validateDatabaseSchema] is only supported on the web as
55+
/// a platform.
5456
Future<void> validateDatabaseSchema({
5557
required CommonSqlite3 sqlite3,
56-
bool validateDropped = true,
58+
common.ValidationOptions options = const common.ValidationOptions(),
59+
@Deprecated('Use field in ValidationOptions instead') bool? validateDropped,
5760
}) async {
5861
await verifyDatabase(
59-
this, validateDropped, () => WasmDatabase.inMemory(sqlite3));
62+
this,
63+
options.applyDeprecatedValidateDroppedParam(validateDropped),
64+
() => WasmDatabase.inMemory(sqlite3),
65+
);
6066
}
6167
}

drift_dev/lib/src/services/schema/find_differences.dart

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:sqlparser/sqlparser.dart';
22
// ignore: implementation_imports
33
import 'package:sqlparser/src/utils/ast_equality.dart';
44

5+
import '../../../api/migrations_common.dart';
56
import '../../analysis/drift_native_functions.dart';
67

78
class Input {
@@ -21,9 +22,7 @@ class FindSchemaDifferences {
2122
/// The actual schema entities
2223
final List<Input> actualSchema;
2324

24-
/// When set, [actualSchema] may not contain more entities than
25-
/// [referenceSchema].
26-
final bool validateDropped;
25+
final ValidationOptions options;
2726

2827
final SqlEngine _engine = SqlEngine(
2928
EngineOptions(enabledExtensions: const [
@@ -33,16 +32,15 @@ class FindSchemaDifferences {
3332
]),
3433
);
3534

36-
FindSchemaDifferences(
37-
this.referenceSchema, this.actualSchema, this.validateDropped);
35+
FindSchemaDifferences(this.referenceSchema, this.actualSchema, this.options);
3836

3937
CompareResult compare() {
4038
return _compareNamed<Input>(
4139
reference: referenceSchema,
4240
actual: actualSchema,
4341
name: (e) => e.name,
4442
compare: _compareInput,
45-
validateActualInReference: validateDropped,
43+
validateActualInReference: options.validateDropped,
4644
);
4745
}
4846

@@ -170,13 +168,15 @@ class FindSchemaDifferences {
170168
'Different types: Expected ${ref.typeName}, got ${act.typeName}');
171169
}
172170

173-
try {
174-
enforceEqualIterable(ref.constraints, act.constraints);
175-
} catch (e) {
176-
final firstSpan = ref.constraints.spanOrNull?.text ?? '';
177-
final secondSpan = act.constraints.spanOrNull?.text ?? '';
178-
return FoundDifference(
179-
'Not equal: `$firstSpan` (expected) and `$secondSpan` (actual)');
171+
if (options.validateColumnConstraints) {
172+
try {
173+
enforceEqualIterable(ref.constraints, act.constraints);
174+
} catch (e) {
175+
final firstSpan = ref.constraints.spanOrNull?.text ?? '';
176+
final secondSpan = act.constraints.spanOrNull?.text ?? '';
177+
return FoundDifference(
178+
'Not equal: `$firstSpan` (expected) and `$secondSpan` (actual)');
179+
}
180180
}
181181

182182
return const Success();

drift_dev/lib/src/services/schema/verifier_common.dart

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ bool isInternalElement(String name, List<String> virtualTables) {
2020
}
2121

2222
void verify(List<Input> referenceSchema, List<Input> actualSchema,
23-
bool validateDropped) {
23+
ValidationOptions options) {
2424
final result =
25-
FindSchemaDifferences(referenceSchema, actualSchema, validateDropped)
26-
.compare();
25+
FindSchemaDifferences(referenceSchema, actualSchema, options).compare();
2726

2827
if (!result.noChanges) {
2928
throw SchemaMismatch(result.describe());
@@ -32,7 +31,7 @@ void verify(List<Input> referenceSchema, List<Input> actualSchema,
3231

3332
Future<void> verifyDatabase(
3433
GeneratedDatabase db,
35-
bool validateDropped,
34+
ValidationOptions options,
3635
QueryExecutor Function() open,
3736
) async {
3837
final virtualTables = db.allTables
@@ -57,7 +56,7 @@ Future<void> verifyDatabase(
5756
await referenceDb.close();
5857
}
5958

60-
verify(referenceSchema, schemaOfThisDatabase, validateDropped);
59+
verify(referenceSchema, schemaOfThisDatabase, options);
6160
}
6261

6362
/// Thrown when the actual schema differs from the expected schema.
@@ -87,8 +86,12 @@ abstract base class VerifierImplementation<DB extends CommonDatabase>
8786
QueryExecutor wrapOpened(DB db, {required bool closeUnderlyingOnClose});
8887

8988
@override
90-
Future<void> migrateAndValidate(GeneratedDatabase db, int expectedVersion,
91-
{bool validateDropped = false}) async {
89+
Future<void> migrateAndValidate(
90+
GeneratedDatabase db,
91+
int expectedVersion, {
92+
ValidationOptions options = const ValidationOptions(),
93+
bool? validateDropped,
94+
}) async {
9295
final virtualTables = <String>[
9396
for (final table in db.allTables)
9497
if (table is VirtualTableInfo) table.entityName,
@@ -110,7 +113,11 @@ abstract base class VerifierImplementation<DB extends CommonDatabase>
110113
await db.executor.ensureOpen(_DelegatingUser(expectedVersion, db));
111114
final actualSchema = await db.executor.collectSchemaInput(virtualTables);
112115

113-
verify(referenceSchema, actualSchema, validateDropped);
116+
verify(
117+
referenceSchema,
118+
actualSchema,
119+
options.applyDeprecatedValidateDroppedParam(validateDropped),
120+
);
114121
}
115122

116123
DB _setupDatabase() {

drift_dev/test/services/schema/find_differences_test.dart

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:drift_dev/api/migrations_common.dart';
12
import 'package:drift_dev/src/services/schema/find_differences.dart';
23
import 'package:test/test.dart';
34

@@ -87,6 +88,16 @@ void main() {
8788
'Not equal: `PRIMARY KEY NOT NULL` (expected) and `` (actual)'),
8889
);
8990
});
91+
92+
test('ignored column constraints diff', () {
93+
final result = compare(
94+
Input('a', 'CREATE TABLE a (id INTEGER PRIMARY KEY NOT NULL);'),
95+
Input('a', 'CREATE TABLE a (id INTEGER);'),
96+
options: const ValidationOptions(validateColumnConstraints: false),
97+
);
98+
99+
expect(result, hasNoChanges);
100+
});
90101
});
91102

92103
test('of different type', () {
@@ -104,8 +115,12 @@ void main() {
104115
});
105116
}
106117

107-
CompareResult compare(Input a, Input b) {
108-
return FindSchemaDifferences([a], [b], false).compare();
118+
CompareResult compare(
119+
Input a,
120+
Input b, {
121+
ValidationOptions options = const ValidationOptions(),
122+
}) {
123+
return FindSchemaDifferences([a], [b], options).compare();
109124
}
110125

111126
Matcher hasChanges = _matchChanges(false);

extras/drift_devtools_extension/lib/src/schema_validator.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:devtools_app_shared/ui.dart';
2+
import 'package:drift_dev/api/migrations_common.dart';
23
// ignore: implementation_imports
34
import 'package:drift_dev/src/services/schema/find_differences.dart';
45
// ignore: implementation_imports
@@ -68,7 +69,7 @@ class SchemaVerifier extends AutoDisposeAsyncNotifier<SchemaStatus> {
6869
}
6970

7071
try {
71-
verify(expected, actual, true);
72+
verify(expected, actual, const ValidationOptions());
7273
return SchemaComparisonResult(
7374
schemaValid: true,
7475
message: 'The schema of the database matches its Dart and .drift '

0 commit comments

Comments
 (0)