Support comparing types with different names

This commit is contained in:
Simon Binder 2020-10-23 19:22:30 +02:00
parent 1c3ab61895
commit 71e5c4941e
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
4 changed files with 96 additions and 35 deletions

View File

@ -1,3 +1,4 @@
import 'package:meta/meta.dart';
import 'package:moor_generator/src/analyzer/moor/moor_ffi_extension.dart';
import 'package:sqlparser/sqlparser.dart';
// ignore: implementation_imports
@ -36,49 +37,62 @@ class FindSchemaDifferences {
this.referenceSchema, this.actualSchema, this.validateDropped);
CompareResult compare() {
final results = <String, CompareResult>{};
return _compareNamed<Input>(
reference: referenceSchema,
actual: actualSchema,
name: (e) => e.name,
compare: _compareInput,
validateActualInReference: validateDropped,
);
}
CompareResult _compareNamed<T>({
@required List<T> reference,
@required List<T> actual,
@required String Function(T) name,
@required CompareResult Function(T, T) compare,
bool validateActualInReference = true,
}) {
final results = <String, CompareResult>{};
final referenceByName = {
for (final ref in referenceSchema) ref.name: ref,
for (final ref in reference) name(ref): ref,
};
final actualByName = {
for (final ref in actualSchema) ref.name: ref,
for (final ref in actual) name(ref): ref,
};
final referenceToActual = <Input, Input>{};
final referenceToActual = <T, T>{};
// Handle the easy cases first: Is the actual schema missing anything?
for (final inReference in referenceByName.keys) {
if (!actualByName.containsKey(inReference)) {
results['comparing $inReference'] = FoundDifference('Expected entity, '
'but the actual schema does not contain anything with this name.');
results['comparing $inReference'] = FoundDifference(
'The actual schema does not contain anything with this name.');
} else {
referenceToActual[referenceByName[inReference]] =
actualByName[inReference];
}
}
if (validateDropped) {
if (validateActualInReference) {
// Also check the other way: Does the actual schema contain more than the
// reference?
final additional = actualByName.keys.toSet()
..removeAll(referenceByName.keys);
if (additional.isNotEmpty) {
results['additional entries'] = FoundDifference('The schema contains '
'the following unexpected entries: ${additional.join(', ')}');
results['additional'] = FoundDifference('Contains the following '
'unexpected entries: ${additional.join(', ')}');
}
}
for (final match in referenceToActual.entries) {
final name = match.key.name;
results[name] = _compare(match.key, match.value);
results[name(match.key)] = compare(match.key, match.value);
}
return MultiResult(results);
}
CompareResult _compare(Input reference, Input actual) {
CompareResult _compareInput(Input reference, Input actual) {
final parsedReference = _engine.parse(reference.create);
final parsedActual = _engine.parse(actual.create);
@ -139,30 +153,32 @@ class FindSchemaDifferences {
CompareResult _compareColumns(
List<ColumnDefinition> ref, List<ColumnDefinition> act) {
final results = <String, CompareResult>{};
return _compareNamed<ColumnDefinition>(
reference: ref,
actual: act,
name: (def) => def.columnName,
compare: _compareColumn,
);
}
final actByName = {for (final column in act) column.columnName: column};
// Additional columns in act that ref doesn't have. Built by iterating over
// ref.
final additionalColumns = actByName.keys.toSet();
CompareResult _compareColumn(ColumnDefinition ref, ColumnDefinition act) {
final refType = _engine.schemaReader.resolveColumnType(ref.typeName);
final actType = _engine.schemaReader.resolveColumnType(act.typeName);
for (final refColumn in ref) {
final name = refColumn.columnName;
final actColumn = actByName[name];
if (actColumn == null) {
results[name] = FoundDifference('Missing in schema');
} else {
results[name] = _compareByAst(refColumn, actColumn);
additionalColumns.remove(name);
}
if (refType != actType) {
return FoundDifference(
'Different types: ${ref.typeName} and ${act.typeName}');
}
for (final additional in additionalColumns) {
results[additional] = FoundDifference('Additional unexpected column');
try {
enforceEqualIterable(ref.constraints, act.constraints);
} catch (e) {
final firstSpan = ref.constraints.spanOrNull?.text ?? '';
final secondSpan = act.constraints.spanOrNull?.text ?? '';
return FoundDifference('Not equal: `$firstSpan` and `$secondSpan`');
}
return MultiResult(results);
return const Success();
}
CompareResult _compareByAst(AstNode a, AstNode b) {

View File

@ -26,7 +26,7 @@ void main() {
expect(result, hasChanges);
expect(
result.describe(),
contains('Missing in schema'),
contains('The actual schema does not contain'),
);
});
@ -39,7 +39,7 @@ void main() {
expect(result, hasChanges);
expect(
result.describe(),
contains('Additional unexpected column'),
contains('Contains the following unexpected entries: b'),
);
});
@ -51,6 +51,41 @@ void main() {
expect(result, hasNoChanges);
});
test('with different lexemes for the same column type', () {
final result = compare(
Input('a', 'CREATE TABLE a (id TEXT);'),
Input('a', 'CREATE TABLE a (id VARCHAR(42));'),
);
expect(result, hasNoChanges);
});
test('with mismatching column types', () {
final result = compare(
Input('a', 'CREATE TABLE a (id TEXT);'),
Input('a', 'CREATE TABLE a (id INTEGER);'),
);
expect(result, hasChanges);
expect(
result.describe(),
contains('Different types: TEXT and INTEGER'),
);
});
test('with different column constraints', () {
final result = compare(
Input('a', 'CREATE TABLE a (id INTEGER PRIMARY KEY NOT NULL);'),
Input('a', 'CREATE TABLE a (id INTEGER);'),
);
expect(result, hasChanges);
expect(
result.describe(),
contains('Not equal: `PRIMARY KEY NOT NULL` and ``'),
);
});
});
test('of different type', () {

View File

@ -47,4 +47,10 @@ extension UnionEntityExtension on Iterable<SyntacticEntity> {
(previousValue, entity) => previousValue.expand(entity.span),
);
}
/// Returns the common [span] of these syntactic entities, or `null` if the
/// iterable is empty.
FileSpan /*?*/ get spanOrNull {
return isEmpty ? null : span;
}
}

View File

@ -10,8 +10,12 @@ void enforceEqual(AstNode a, AstNode b) {
throw ArgumentError('Content not equal: $a and $b');
}
final childrenA = a.childNodes.iterator;
final childrenB = b.childNodes.iterator;
return enforceEqualIterable(a.childNodes, b.childNodes);
}
void enforceEqualIterable(Iterable<AstNode> a, Iterable<AstNode> b) {
final childrenA = a.iterator;
final childrenB = b.iterator;
// always move both iterators
while (childrenA.moveNext() & childrenB.moveNext()) {