mirror of https://github.com/AMT-Cheif/drift.git
Support comparing types with different names
This commit is contained in:
parent
1c3ab61895
commit
71e5c4941e
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moor_generator/src/analyzer/moor/moor_ffi_extension.dart';
|
import 'package:moor_generator/src/analyzer/moor/moor_ffi_extension.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
|
@ -36,49 +37,62 @@ class FindSchemaDifferences {
|
||||||
this.referenceSchema, this.actualSchema, this.validateDropped);
|
this.referenceSchema, this.actualSchema, this.validateDropped);
|
||||||
|
|
||||||
CompareResult compare() {
|
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 = {
|
final referenceByName = {
|
||||||
for (final ref in referenceSchema) ref.name: ref,
|
for (final ref in reference) name(ref): ref,
|
||||||
};
|
};
|
||||||
final actualByName = {
|
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) {
|
for (final inReference in referenceByName.keys) {
|
||||||
if (!actualByName.containsKey(inReference)) {
|
if (!actualByName.containsKey(inReference)) {
|
||||||
results['comparing $inReference'] = FoundDifference('Expected entity, '
|
results['comparing $inReference'] = FoundDifference(
|
||||||
'but the actual schema does not contain anything with this name.');
|
'The actual schema does not contain anything with this name.');
|
||||||
} else {
|
} else {
|
||||||
referenceToActual[referenceByName[inReference]] =
|
referenceToActual[referenceByName[inReference]] =
|
||||||
actualByName[inReference];
|
actualByName[inReference];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validateDropped) {
|
if (validateActualInReference) {
|
||||||
// Also check the other way: Does the actual schema contain more than the
|
// Also check the other way: Does the actual schema contain more than the
|
||||||
// reference?
|
// reference?
|
||||||
final additional = actualByName.keys.toSet()
|
final additional = actualByName.keys.toSet()
|
||||||
..removeAll(referenceByName.keys);
|
..removeAll(referenceByName.keys);
|
||||||
|
|
||||||
if (additional.isNotEmpty) {
|
if (additional.isNotEmpty) {
|
||||||
results['additional entries'] = FoundDifference('The schema contains '
|
results['additional'] = FoundDifference('Contains the following '
|
||||||
'the following unexpected entries: ${additional.join(', ')}');
|
'unexpected entries: ${additional.join(', ')}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final match in referenceToActual.entries) {
|
for (final match in referenceToActual.entries) {
|
||||||
final name = match.key.name;
|
results[name(match.key)] = compare(match.key, match.value);
|
||||||
results[name] = _compare(match.key, match.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return MultiResult(results);
|
return MultiResult(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompareResult _compare(Input reference, Input actual) {
|
CompareResult _compareInput(Input reference, Input actual) {
|
||||||
final parsedReference = _engine.parse(reference.create);
|
final parsedReference = _engine.parse(reference.create);
|
||||||
final parsedActual = _engine.parse(actual.create);
|
final parsedActual = _engine.parse(actual.create);
|
||||||
|
|
||||||
|
@ -139,30 +153,32 @@ class FindSchemaDifferences {
|
||||||
|
|
||||||
CompareResult _compareColumns(
|
CompareResult _compareColumns(
|
||||||
List<ColumnDefinition> ref, List<ColumnDefinition> act) {
|
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};
|
CompareResult _compareColumn(ColumnDefinition ref, ColumnDefinition act) {
|
||||||
// Additional columns in act that ref doesn't have. Built by iterating over
|
final refType = _engine.schemaReader.resolveColumnType(ref.typeName);
|
||||||
// ref.
|
final actType = _engine.schemaReader.resolveColumnType(act.typeName);
|
||||||
final additionalColumns = actByName.keys.toSet();
|
|
||||||
|
|
||||||
for (final refColumn in ref) {
|
if (refType != actType) {
|
||||||
final name = refColumn.columnName;
|
return FoundDifference(
|
||||||
final actColumn = actByName[name];
|
'Different types: ${ref.typeName} and ${act.typeName}');
|
||||||
|
|
||||||
if (actColumn == null) {
|
|
||||||
results[name] = FoundDifference('Missing in schema');
|
|
||||||
} else {
|
|
||||||
results[name] = _compareByAst(refColumn, actColumn);
|
|
||||||
additionalColumns.remove(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final additional in additionalColumns) {
|
try {
|
||||||
results[additional] = FoundDifference('Additional unexpected column');
|
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) {
|
CompareResult _compareByAst(AstNode a, AstNode b) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ void main() {
|
||||||
expect(result, hasChanges);
|
expect(result, hasChanges);
|
||||||
expect(
|
expect(
|
||||||
result.describe(),
|
result.describe(),
|
||||||
contains('Missing in schema'),
|
contains('The actual schema does not contain'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ void main() {
|
||||||
expect(result, hasChanges);
|
expect(result, hasChanges);
|
||||||
expect(
|
expect(
|
||||||
result.describe(),
|
result.describe(),
|
||||||
contains('Additional unexpected column'),
|
contains('Contains the following unexpected entries: b'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -51,6 +51,41 @@ void main() {
|
||||||
|
|
||||||
expect(result, hasNoChanges);
|
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', () {
|
test('of different type', () {
|
||||||
|
|
|
@ -47,4 +47,10 @@ extension UnionEntityExtension on Iterable<SyntacticEntity> {
|
||||||
(previousValue, entity) => previousValue.expand(entity.span),
|
(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,12 @@ void enforceEqual(AstNode a, AstNode b) {
|
||||||
throw ArgumentError('Content not equal: $a and $b');
|
throw ArgumentError('Content not equal: $a and $b');
|
||||||
}
|
}
|
||||||
|
|
||||||
final childrenA = a.childNodes.iterator;
|
return enforceEqualIterable(a.childNodes, b.childNodes);
|
||||||
final childrenB = b.childNodes.iterator;
|
}
|
||||||
|
|
||||||
|
void enforceEqualIterable(Iterable<AstNode> a, Iterable<AstNode> b) {
|
||||||
|
final childrenA = a.iterator;
|
||||||
|
final childrenB = b.iterator;
|
||||||
|
|
||||||
// always move both iterators
|
// always move both iterators
|
||||||
while (childrenA.moveNext() & childrenB.moveNext()) {
|
while (childrenA.moveNext() & childrenB.moveNext()) {
|
||||||
|
|
Loading…
Reference in New Issue