Skip to content

Commit 4a56ad3

Browse files
committed
New approach to detect data cleanup caused by table movement
1 parent 690f64b commit 4a56ad3

File tree

3 files changed

+103
-47
lines changed

3 files changed

+103
-47
lines changed

Orm/Xtensive.Orm/Modelling/Comparison/Hints/DeleteDataHint.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,23 @@ public class DeleteDataHint : DataHint
2525
/// </summary>
2626
public bool PostCopy { get; private set; }
2727

28+
/// <summary>
29+
/// Gets value indicating whether cause of data deletion was moving table to another hierarchy.
30+
/// This flag is used to ideitify and prevent unsafe data clean-up even if database structure
31+
/// remain identical but logically table serves completely different entity.
32+
/// </summary>
33+
public bool TableChangedOwner { get; private set; }
34+
2835
/// <inheritdoc/>
2936
public override string ToString()
3037
{
3138
return string.Format(
32-
"Delete from '{0}' where ({1}){2}",
39+
"Delete from '{0}' where ({1}){2}{3}",
3340
SourceTablePath,
3441
string.Join(" and ",
3542
Identities.Select(pair => pair.ToString()).ToArray()),
36-
PostCopy ? " (after data copying)" : string.Empty);
43+
PostCopy ? " (after data copying)" : string.Empty,
44+
TableChangedOwner ? " due to table owner changed" : string.Empty);
3745
}
3846

3947

@@ -58,5 +66,19 @@ public DeleteDataHint(string sourceTablePath, IList<IdentityPair> identities, b
5866
{
5967
PostCopy = postCopy;
6068
}
69+
70+
/// <summary>
71+
/// Initializes new instance of this type.
72+
/// </summary>
73+
/// <param name="sourceTablePath">Source table path.</param>
74+
/// <param name="identities">Identities for data operation.</param>
75+
/// <param name="postCopy"><see cref="PostCopy"/> property value.</param>
76+
/// <param name="tableChangedOwner"><see cref="TableChangedOwner"/> property value.</param>
77+
public DeleteDataHint(string sourceTablePath, IList<IdentityPair> identities, bool postCopy, bool tableChangedOwner)
78+
: base(sourceTablePath, identities)
79+
{
80+
PostCopy = postCopy;
81+
TableChangedOwner = tableChangedOwner;
82+
}
6183
}
6284
}

Orm/Xtensive.Orm/Orm/Upgrade/Internals/HintGenerator.cs

Lines changed: 78 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ public HintGenerationResult Run()
5151
GenerateCopyColumnHints(copyFieldHints);
5252

5353
var removedTypes = GetRemovedTypes();
54-
GenerateRecreateHints(removedTypes);
55-
GenerateRecordCleanupHints(removedTypes, false);
54+
var conflictsByTable = GetRecreatedTypes(removedTypes);
55+
//GenerateRecreateHints(removedTypes);
56+
GenerateRecordCleanupHints(removedTypes, conflictsByTable, false);
5657

5758
var movedTypes = GetMovedTypes();
58-
GenerateRecordCleanupHints(movedTypes, true);
59+
GenerateRecordCleanupHints(movedTypes, conflictsByTable, true);
5960

6061
// Adding useful info
6162
CalculateAffectedTablesAndColumns(hints);
@@ -218,64 +219,73 @@ private void GenerateCopyColumnHint(CopyFieldHint hint)
218219
}
219220
}
220221

221-
private void GenerateRecreateHints(IList<StoredTypeInfo> removedTypes)
222-
{
223-
var capacity = currentModel.Types.Length - typeMapping.Count;
224-
var newTypes = new Dictionary<string, StoredTypeInfo>(capacity, StringComparer.Ordinal);
225-
currentNonConnectorTypes
226-
.Where(t => !reverseTypeMapping.ContainsKey(t))
227-
.ForEach(t => newTypes.Add($"{t.MappingDatabase}.{t.MappingSchema}.{t.MappingName}", t));
228-
229-
for (var i = removedTypes.Count - 1; i >= 0; i--) {
230-
var rType = removedTypes[i];
231-
var rTypeIdentifier = $"{rType.MappingDatabase}.{rType.MappingSchema}.{rType.MappingName}";
232-
if (suspiciousTypes.Contains(rType) && newTypes.ContainsKey(rTypeIdentifier)) {
233-
schemaHints.Add(new DeleteDataHint(GetTablePath(rType), Array.Empty<IdentityPair>(), true));
234-
_ = removedTypes.Remove(rType);
235-
}
236-
}
237-
}
238-
239-
private void GenerateRecordCleanupHints(List<StoredTypeInfo> removedTypes, bool isMovedToAnotherHierarchy)
222+
private void GenerateRecordCleanupHints(List<StoredTypeInfo> removedTypes,
223+
Dictionary<StoredTypeInfo, StoredTypeInfo> conflictsByTable, bool isMovedToAnotherHierarchy)
240224
{
241225
if (!isMovedToAnotherHierarchy) {
242226
removedTypes.ForEach(GenerateCleanupByForeignKeyHints);
243227
}
244-
removedTypes.ForEach(type => GenerateCleanupByPrimaryKeyHints(type, isMovedToAnotherHierarchy));
228+
removedTypes.ForEach(type => GenerateCleanupByPrimaryKeyHints(type, isMovedToAnotherHierarchy, conflictsByTable.ContainsKey(type.Hierarchy.Root)));
245229
}
246230

247-
private void GenerateCleanupByPrimaryKeyHints(StoredTypeInfo removedType, bool isMovedToAnotherHierarchy)
231+
private void GenerateCleanupByPrimaryKeyHints(StoredTypeInfo removedType, bool isMovedToAnotherHierarchy, bool conflictsWithNewType)
248232
{
249233
var hierarchy = removedType.Hierarchy;
250234
switch (hierarchy.InheritanceSchema) {
251-
case InheritanceSchema.ClassTable:
252-
IEnumerable<StoredTypeInfo> typesToProcess;
253-
typesToProcess = !isMovedToAnotherHierarchy
254-
? EnumerableUtils.One(removedType).Concat(removedType.AllAncestors)
255-
: removedType.AllAncestors;
256-
foreach (var type in typesToProcess) {
257-
var identities1 = new[] { new IdentityPair(
258-
GetColumnPath(type, GetTypeIdMappingName(type)),
259-
removedType.TypeId.ToString(),
260-
true) };
261-
schemaHints.Add(
262-
new DeleteDataHint(GetTablePath(type), identities1, isMovedToAnotherHierarchy));
235+
case InheritanceSchema.ClassTable: {
236+
if (!conflictsWithNewType) {
237+
var typesToProcess = !isMovedToAnotherHierarchy
238+
? EnumerableUtils.One(removedType).Concat(removedType.AllAncestors)
239+
: (IEnumerable<StoredTypeInfo>) removedType.AllAncestors;
240+
foreach (var type in typesToProcess) {
241+
var identities1 = new[] { new IdentityPair(
242+
GetColumnPath(type, GetTypeIdMappingName(type)),
243+
removedType.TypeId.ToString(),
244+
true)
245+
};
246+
schemaHints.Add(
247+
new DeleteDataHint(GetTablePath(type), identities1, isMovedToAnotherHierarchy, conflictsWithNewType));
248+
}
249+
}
250+
else if (isMovedToAnotherHierarchy) {
251+
foreach (var type in hierarchy.Types) {
252+
schemaHints.Add(
253+
new DeleteDataHint(GetTablePath(type), Array.Empty<IdentityPair>(), isMovedToAnotherHierarchy, conflictsWithNewType));
254+
}
263255
}
264256
break;
257+
}
265258
case InheritanceSchema.SingleTable:
266-
var rootType = hierarchy.Root;
267-
var identities2 = new[] { new IdentityPair(
259+
if (!conflictsWithNewType) {
260+
var rootType = hierarchy.Root;
261+
var identities2 = new[] { new IdentityPair(
268262
GetColumnPath(rootType, GetTypeIdMappingName(rootType)),
269263
removedType.TypeId.ToString(),
270264
true) };
271-
schemaHints.Add(
272-
new DeleteDataHint(GetTablePath(rootType), identities2, isMovedToAnotherHierarchy));
265+
schemaHints.Add(
266+
new DeleteDataHint(GetTablePath(rootType), identities2, isMovedToAnotherHierarchy, conflictsWithNewType));
267+
}
268+
else if (!isMovedToAnotherHierarchy) {
269+
var rootType = hierarchy.Root;
270+
schemaHints.Add(
271+
new DeleteDataHint(GetTablePath(rootType), Array.Empty<IdentityPair>(), isMovedToAnotherHierarchy, conflictsWithNewType));
272+
}
273273
break;
274-
case InheritanceSchema.ConcreteTable:
275-
// ConcreteTable schema doesn't include TypeId
276-
schemaHints.Add(
277-
new DeleteDataHint(GetTablePath(removedType), Array.Empty<IdentityPair>(), isMovedToAnotherHierarchy));
274+
case InheritanceSchema.ConcreteTable: {
275+
//ConcreteTable schema doesn't include TypeId
276+
if (!conflictsWithNewType) {
277+
schemaHints.Add(
278+
new DeleteDataHint(GetTablePath(removedType), Array.Empty<IdentityPair>(), isMovedToAnotherHierarchy));
279+
}
280+
else if (!isMovedToAnotherHierarchy) {
281+
var typesToProcess = hierarchy.Types;
282+
foreach (var type in typesToProcess) {
283+
schemaHints.Add(
284+
new DeleteDataHint(GetTablePath(type), Array.Empty<IdentityPair>(), isMovedToAnotherHierarchy, conflictsWithNewType));
285+
}
286+
}
278287
break;
288+
}
279289
default:
280290
throw Exceptions.InternalError(string.Format(Strings.ExInheritanceSchemaIsInvalid, hierarchy.InheritanceSchema), UpgradeLog.Instance);
281291
}
@@ -598,6 +608,30 @@ private List<string> GetAffectedColumns(StoredTypeInfo type, StoredFieldInfo fie
598608
return affectedColumns;
599609
}
600610

611+
private Dictionary<StoredTypeInfo, StoredTypeInfo> GetRecreatedTypes(IReadOnlyList<StoredTypeInfo> removedTypes)
612+
{
613+
// Return types that were removed but there is a new type
614+
// which uses the same table in database (and|or) schema
615+
// that means further comparison will not find schema changes (table removes or renames),
616+
// but data in the table has different meaning so old data should be cleaned.
617+
618+
var capacity = currentModel.Types.Length - typeMapping.Count;
619+
var currentTables = new Dictionary<string, StoredTypeInfo>(capacity, StringComparer.Ordinal);
620+
foreach (var t in currentNonConnectorTypes.Where(t => !reverseTypeMapping.ContainsKey(t))) {
621+
currentTables.Add($"{t.MappingDatabase}.{t.MappingSchema}.{t.MappingName}", t);
622+
}
623+
624+
var conflictsByTable = new Dictionary<StoredTypeInfo, StoredTypeInfo>();
625+
626+
foreach (var rType in removedTypes) {
627+
var rTypeIdentifier = $"{rType.MappingDatabase}.{rType.MappingSchema}.{rType.MappingName}";
628+
if (suspiciousTypes.Contains(rType) && currentTables.TryGetValue(rTypeIdentifier, out var nType)) {
629+
conflictsByTable.Add(rType, nType);
630+
}
631+
}
632+
return conflictsByTable;
633+
}
634+
601635
private List<StoredTypeInfo> GetRemovedTypes()
602636
{
603637
return (

Orm/Xtensive.Orm/Orm/Upgrade/Internals/SchemaComparer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ private static void GetCrossHierarchicalMovements(IEnumerable<NodeAction> action
235235
{
236236
(from action in actions.OfType<DataAction>()
237237
let deleteDataHint = action.DataHint as DeleteDataHint
238-
where deleteDataHint!=null && deleteDataHint.PostCopy
238+
where deleteDataHint!=null && (deleteDataHint.PostCopy || deleteDataHint.TableChangedOwner)
239239
select action).ForEach(output.Add);
240240
}
241241

0 commit comments

Comments
 (0)