detectPotentialDataLossWarnings function
List<String>
detectPotentialDataLossWarnings({
- required SchemaDocument from,
- required SchemaDocument to,
Returns human-readable warnings for schema transitions that may lose data.
Implementation
List<String> detectPotentialDataLossWarnings({
required SchemaDocument from,
required SchemaDocument to,
}) {
from = from.withoutIgnored();
to = to.withoutIgnored();
final warnings = <String>[];
final matchedSourceEnumNames = <String>{};
final matchedEnumPairs = <String>{};
final sourceEnumsByName = {
for (final definition in from.enums) definition.name: definition,
};
final sourceEnumsByDatabaseName = {
for (final definition in from.enums) definition.databaseName: definition,
};
for (final targetEnum in to.enums) {
final sourceEnum =
sourceEnumsByName.remove(targetEnum.name) ??
sourceEnumsByDatabaseName.remove(targetEnum.databaseName) ??
_inferRenamedSourceEnum(
sourceSchema: from,
targetSchema: to,
targetEnum: targetEnum,
matchedSourceEnumNames: matchedSourceEnumNames,
);
if (sourceEnum == null) {
continue;
}
matchedSourceEnumNames.add(sourceEnum.name);
matchedEnumPairs.add(_enumPairKey(sourceEnum, targetEnum));
sourceEnumsByName.remove(sourceEnum.name);
sourceEnumsByDatabaseName.remove(sourceEnum.databaseName);
final removedValues = sourceEnum.values
.where((value) => !targetEnum.values.contains(value))
.toList(growable: false);
if (removedValues.isNotEmpty &&
!_isSafeEnumTransition(sourceEnum, targetEnum)) {
warnings.add(
'Potential data loss: enum ${targetEnum.name} removes values ${removedValues.join(', ')}.',
);
}
}
for (final removedEnum in sourceEnumsByName.keys) {
warnings.add(
'Potential data loss: enum $removedEnum is removed from the target schema.',
);
}
final sourceModelsByName = {
for (final model in from.models) model.name: model,
};
final sourceModelsByDatabaseName = {
for (final model in from.models) model.databaseName: model,
};
final targetModels = {for (final model in to.models) model.name: model};
for (final entry in targetModels.entries) {
final targetModel = entry.value;
final sourceModel =
sourceModelsByName.remove(entry.key) ??
sourceModelsByDatabaseName.remove(targetModel.databaseName);
if (sourceModel == null) {
continue;
}
sourceModelsByName.remove(sourceModel.name);
sourceModelsByDatabaseName.remove(sourceModel.databaseName);
final sourceFieldsByName = {
for (final field in sourceModel.fields) field.name: field,
};
final sourceFieldsByDatabaseName = {
for (final field in sourceModel.fields) field.databaseName: field,
};
for (final targetField in targetModel.fields) {
final sourceField =
sourceFieldsByName.remove(targetField.name) ??
sourceFieldsByDatabaseName.remove(targetField.databaseName);
if (sourceField == null) {
continue;
}
sourceFieldsByName.remove(sourceField.name);
sourceFieldsByDatabaseName.remove(sourceField.databaseName);
final warning = _fieldRiskWarning(
modelName: targetModel.name,
sourceField: sourceField,
targetField: targetField,
sourceSchema: from,
targetSchema: to,
matchedEnumPairs: matchedEnumPairs,
);
if (warning != null) {
warnings.add(warning);
}
}
for (final removedField in sourceModel.fields.where(
(field) =>
sourceFieldsByName.containsKey(field.name) ||
sourceFieldsByDatabaseName.containsKey(field.databaseName),
)) {
if (_isRelationField(from, removedField)) {
continue;
}
warnings.add(
'Potential data loss: field ${sourceModel.name}.${removedField.name} is removed from the target schema.',
);
}
}
for (final removedModel in from.models.where(
(model) =>
sourceModelsByName.containsKey(model.name) ||
sourceModelsByDatabaseName.containsKey(model.databaseName),
)) {
warnings.add(
'Potential data loss: model ${removedModel.name} is removed from the target schema.',
);
}
final sourceStorages = {
for (final storage in collectImplicitManyToManyStorages(from))
storage.tableName: storage,
};
final targetStorages = {
for (final storage in collectImplicitManyToManyStorages(to))
storage.tableName: storage,
};
for (final removedStorage in sourceStorages.keys.where(
(name) => !targetStorages.containsKey(name),
)) {
warnings.add(
'Potential data loss: implicit relation storage $removedStorage is removed from the target schema.',
);
}
return List<String>.unmodifiable(_dedupe(warnings));
}