Skip to content

Commit c1ca4e5

Browse files
committed
HintGenerator: Improved work in case of conflicts by tables
1 parent 26093be commit c1ca4e5

File tree

1 file changed

+144
-72
lines changed

1 file changed

+144
-72
lines changed

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

Lines changed: 144 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ internal sealed class HintGenerator
4040
private readonly IReadOnlyList<StoredTypeInfo> extractedNonConnectorTypes;
4141

4242
private readonly List<Hint> schemaHints = new List<Hint>();
43+
private readonly HashSet<string> skipInPost = new HashSet<string>(StringComparer.Ordinal);
4344

4445
public HintGenerationResult Run()
4546
{
@@ -52,7 +53,7 @@ public HintGenerationResult Run()
5253
GenerateCopyColumnHints(copyFieldHints);
5354

5455
var removedTypes = GetRemovedTypes();
55-
var conflictsByTable = GetRecreatedTypes(removedTypes);
56+
var conflictsByTable = GetConflictsByTable(removedTypes);
5657
GenerateRecordCleanupHints(removedTypes, conflictsByTable, false);
5758

5859
var movedTypes = GetMovedTypes();
@@ -220,102 +221,151 @@ private void GenerateCopyColumnHint(CopyFieldHint hint)
220221
}
221222

222223
private void GenerateRecordCleanupHints(List<StoredTypeInfo> removedTypes,
223-
Dictionary<StoredTypeInfo, StoredTypeInfo> conflictsByTable, bool isMovedToAnotherHierarchy)
224+
HashSet<StoredTypeInfo> conflictsByTable, bool isMovedToAnotherHierarchy)
224225
{
225226
if (!isMovedToAnotherHierarchy) {
226227
removedTypes.ForEach((rType) => GenerateCleanupByForeignKeyHints(rType, GetDeleteReasonFK(rType)));
227228
}
228229
removedTypes.ForEach(type =>
229-
GenerateCleanupByPrimaryKeyHints(type, isMovedToAnotherHierarchy, conflictsByTable.ContainsKey(type.Hierarchy.Root)));
230+
GenerateCleanupByPrimaryKeyHints(type, conflictsByTable, isMovedToAnotherHierarchy));
230231

231232
return;
232233

233234
DataDeletionInfo GetDeleteReasonFK(StoredTypeInfo removedType)
234235
{
235-
return conflictsByTable.ContainsKey(removedType.Hierarchy.Root)
236-
? DataDeletionInfo.TableMovement
236+
return conflictsByTable.Contains(removedType.Hierarchy.Root)
237+
? DataDeletionInfo.None
237238
: DataDeletionInfo.None;
238239
}
239240
}
240241

241-
private void GenerateCleanupByPrimaryKeyHints(StoredTypeInfo removedType, bool isMovedToAnotherHierarchy, bool conflictsWithNewType)
242+
private void GenerateCleanupByPrimaryKeyHints(StoredTypeInfo removedType,
243+
HashSet<StoredTypeInfo> conflictsByTable,
244+
bool isMovedToAnotherHierarchy)
242245
{
243-
var inheritanceSchema = removedType.Hierarchy.InheritanceSchema;
246+
var isConflictRoot = !removedType.AllAncestors.Any(t => conflictsByTable.Contains(t));
247+
var hasTableConflict = conflictsByTable.Contains(removedType) || !isConflictRoot;
244248

245-
IEnumerable<(StoredTypeInfo type, IdentityPair[] pairs)> hintInfo;
246-
switch (inheritanceSchema) {
247-
case InheritanceSchema.ClassTable: {
248-
hintInfo = GetTypesToCleanForClassTable(removedType, isMovedToAnotherHierarchy, conflictsWithNewType);
249+
IEnumerable<(StoredTypeInfo, IdentityPair)> typesToProcess;
250+
var hierarchy = removedType.Hierarchy;
251+
switch (hierarchy.InheritanceSchema) {
252+
case InheritanceSchema.ClassTable:
253+
typesToProcess = GetTypesToCleanForClassTable(removedType, isMovedToAnotherHierarchy,
254+
hasTableConflict, isConflictRoot);
249255
break;
250-
}
251-
case InheritanceSchema.SingleTable: {
252-
hintInfo = GetTypesToCleanForSinleTable(removedType, isMovedToAnotherHierarchy, conflictsWithNewType);
256+
case InheritanceSchema.SingleTable:
257+
typesToProcess = GetTypesToCleanForSingleTable(removedType, isMovedToAnotherHierarchy, hasTableConflict);
253258
break;
254-
}
255-
case InheritanceSchema.ConcreteTable: {
256-
//ConcreteTable schema doesn't include TypeId
257-
hintInfo = GetTypesToCleanForConcreteTable(removedType, isMovedToAnotherHierarchy, conflictsWithNewType);
259+
case InheritanceSchema.ConcreteTable:
260+
typesToProcess = GetTypesToCleanForConcreteTable(removedType, isMovedToAnotherHierarchy,
261+
hasTableConflict, isConflictRoot);
258262
break;
259-
}
260263
default:
261-
throw Exceptions.InternalError(
262-
string.Format(Strings.ExInheritanceSchemaIsInvalid, inheritanceSchema), UpgradeLog.Instance);
264+
throw Exceptions.InternalError(string.Format(Strings.ExInheritanceSchemaIsInvalid, hierarchy.InheritanceSchema), UpgradeLog.Instance);
263265
}
264266

265-
var deleteReason = DataDeletionInfo.None;
267+
var deleteInfo = DataDeletionInfo.None;
266268
if (isMovedToAnotherHierarchy)
267-
deleteReason |= DataDeletionInfo.PostCopy;
268-
if (conflictsWithNewType)
269-
deleteReason |= DataDeletionInfo.TableMovement;
270-
271-
foreach (var item in hintInfo) {
272-
schemaHints.Add(new DeleteDataHint(GetTablePath(item.type), item.pairs, deleteReason));
269+
deleteInfo |= DataDeletionInfo.PostCopy;
270+
if (hasTableConflict)
271+
deleteInfo |= DataDeletionInfo.TableMovement;
272+
273+
foreach (var info in typesToProcess) {
274+
var sourceTablePath = GetTablePath(info.Item1);
275+
var identities = info.Item2 != null
276+
? new IdentityPair[] { info.Item2 }
277+
: Array.Empty<IdentityPair>();
278+
schemaHints.Add(
279+
new DeleteDataHint(sourceTablePath, identities, deleteInfo));
273280
}
274281
}
275282

276-
private IEnumerable<(StoredTypeInfo type, IdentityPair[] pairs)> GetTypesToCleanForClassTable(
277-
StoredTypeInfo removedType, bool isMovedToAnotherHierarchy, bool conflictsWithNewType)
283+
private IEnumerable<(StoredTypeInfo, IdentityPair)> GetTypesToCleanForClassTable(
284+
StoredTypeInfo removedType,
285+
bool isMovedToAnotherHierarchy,
286+
bool conflictsWithNewType,
287+
bool isConflictRoot)
278288
{
279-
if (!conflictsWithNewType) {
280-
var typesToProcess = !isMovedToAnotherHierarchy
281-
? EnumerableUtils.One(removedType).Concat(removedType.AllAncestors)
282-
: removedType.AllAncestors;
283-
return typesToProcess.Select(t => (t, new[] {
284-
new IdentityPair(
285-
GetColumnPath(t, GetTypeIdMappingName(t)),
286-
removedType.TypeId.ToString(),
287-
true)}));
289+
if (!isMovedToAnotherHierarchy) {
290+
if (!conflictsWithNewType) {
291+
return EnumerableUtils.One(removedType)
292+
.Union(removedType.AllAncestors)
293+
.Select(t => (t, CreateIdentityPair(removedType, t)));
294+
}
295+
else {
296+
if (!isConflictRoot)
297+
return Array.Empty<(StoredTypeInfo, IdentityPair)>();
298+
299+
var capacity = (2 * removedType.AllAncestors.Length) + removedType.AllDescendants.Length + 1;
300+
var typesToProcess = new List<(StoredTypeInfo, IdentityPair)>(capacity);
301+
typesToProcess.Add((removedType, CreateIdentityPair(removedType, removedType)));
302+
foreach(var dType in removedType.AllDescendants) {
303+
typesToProcess.Add((removedType, CreateIdentityPair(removedType, removedType, dType.TypeId)));
304+
typesToProcess.Add((dType, null));
305+
}
306+
307+
foreach(var aType in removedType.AllAncestors) {
308+
typesToProcess.Add((aType, CreateIdentityPair(removedType, aType, removedType.TypeId)));
309+
foreach (var dType in removedType.AllDescendants) {
310+
typesToProcess.Add((aType, CreateIdentityPair(removedType, aType, dType.TypeId)));
311+
}
312+
}
313+
return typesToProcess;
314+
}
288315
}
289-
else if (!isMovedToAnotherHierarchy) {
290-
return removedType.Hierarchy.Types.Select(t => (t, Array.Empty<IdentityPair>()));
316+
else {
317+
if (!conflictsWithNewType) {
318+
return removedType.AllAncestors
319+
.Select(aType =>(aType, CreateIdentityPair(removedType, aType)));
320+
}
291321
}
292-
return Enumerable.Empty<(StoredTypeInfo type, IdentityPair[] pairs)>();
322+
return Array.Empty<(StoredTypeInfo, IdentityPair)>();
293323
}
294324

295-
private IEnumerable<(StoredTypeInfo type, IdentityPair[] pairs)> GetTypesToCleanForSinleTable(
296-
StoredTypeInfo removedType, bool isMovedToAnotherHierarchy, bool conflictsWithNewType)
325+
private IEnumerable<(StoredTypeInfo, IdentityPair)> GetTypesToCleanForSingleTable(
326+
StoredTypeInfo removedType,
327+
bool isMovedToAnotherHierarchy,
328+
bool conflictsWithNewType)
297329
{
298-
var hierarchy = removedType.Hierarchy;
299-
if (!conflictsWithNewType) {
300-
return EnumerableUtils.One(hierarchy.Root)
301-
.Select(t => (t, new[] {
302-
new IdentityPair(GetColumnPath(t, GetTypeIdMappingName(t)), removedType.TypeId.ToString(), true)}));
330+
var rootType = removedType.Hierarchy.Root;
331+
if (!isMovedToAnotherHierarchy) {
332+
if (!conflictsWithNewType)
333+
return new (StoredTypeInfo, IdentityPair)[] { (rootType, CreateIdentityPair(removedType, rootType)) };
334+
else {
335+
return EnumerableUtils.One(rootType)
336+
.Union(removedType.AllDescendants)
337+
.Select(t => (rootType, CreateIdentityPair(t, rootType)));
338+
}
303339
}
304-
else if (!isMovedToAnotherHierarchy) {
305-
return EnumerableUtils.One((hierarchy.Root, Array.Empty<IdentityPair>()));
340+
else {
341+
if (!conflictsWithNewType)
342+
return new(StoredTypeInfo, IdentityPair)[] {(rootType, CreateIdentityPair(removedType, rootType))};
306343
}
307-
return Enumerable.Empty<(StoredTypeInfo type, IdentityPair[] pairs)>();
344+
return Array.Empty<(StoredTypeInfo, IdentityPair)>();
308345
}
309346

310-
private IEnumerable<(StoredTypeInfo type, IdentityPair[] pairs)> GetTypesToCleanForConcreteTable(
311-
StoredTypeInfo removedType, bool isMovedToAnotherHierarchy, bool conflictsWithNewType)
347+
private IEnumerable<(StoredTypeInfo, IdentityPair)> GetTypesToCleanForConcreteTable(
348+
StoredTypeInfo removedType,
349+
bool isMovedToAnotherHierarchy,
350+
bool conflictsWithNewType,
351+
bool isConflictRoot)
312352
{
313-
var hierarchy = removedType.Hierarchy;
314-
return (!conflictsWithNewType)
315-
? EnumerableUtils.One(removedType).Select(t => (t, Array.Empty<IdentityPair>()))
316-
: (!isMovedToAnotherHierarchy)
317-
? hierarchy.Types.Select(t => (t, Array.Empty<IdentityPair>()))
318-
: Enumerable.Empty<(StoredTypeInfo type, IdentityPair[] pairs)>();
353+
if (!isMovedToAnotherHierarchy) {
354+
if (!conflictsWithNewType)
355+
return new (StoredTypeInfo, IdentityPair)[] { (removedType, null) };
356+
else {
357+
if (!isConflictRoot)
358+
return Array.Empty<(StoredTypeInfo, IdentityPair)>();
359+
return EnumerableUtils.One(removedType)
360+
.Union(removedType.AllDescendants)
361+
.Select(t => (t, (IdentityPair)null));
362+
}
363+
}
364+
else {
365+
if (!conflictsWithNewType)
366+
return new (StoredTypeInfo, IdentityPair)[] { (removedType, null) };
367+
}
368+
return Array.Empty<(StoredTypeInfo, IdentityPair)>();
319369
}
320370

321371
private void GenerateCleanupByForeignKeyHints(StoredTypeInfo removedType, DataDeletionInfo dataDeletionInfo)
@@ -440,7 +490,12 @@ private void GenerateClearReferenceHint(
440490
schemaHints.Add(new UpdateDataHint(sourceTablePath, identities, updatedColumns));
441491
}
442492
else {
443-
schemaHints.Add(new DeleteDataHint(sourceTablePath, identities, dataDeletionInfo));
493+
if (dataDeletionInfo.HasFlag(DataDeletionInfo.TableMovement)){
494+
schemaHints.Add(new DeleteDataHint(sourceTablePath, Array.Empty<IdentityPair>(), dataDeletionInfo));
495+
}
496+
else {
497+
schemaHints.Add(new DeleteDataHint(sourceTablePath, identities, dataDeletionInfo));
498+
}
444499
}
445500
}
446501

@@ -640,25 +695,35 @@ private List<string> GetAffectedColumns(StoredTypeInfo type, StoredFieldInfo fie
640695
return affectedColumns;
641696
}
642697

643-
private Dictionary<StoredTypeInfo, StoredTypeInfo> GetRecreatedTypes(IReadOnlyList<StoredTypeInfo> removedTypes)
698+
private HashSet<StoredTypeInfo> GetConflictsByTable(IReadOnlyList<StoredTypeInfo> removedTypes)
644699
{
645-
// Return types that were removed but there is a new type
646-
// which uses the same table in database (and|or) schema
647-
// that means further comparison will not find schema changes (table removes or renames),
648-
// but data in the table has different meaning so old data should be cleaned.
700+
// It gets new types and removed types and looks if they use
701+
// table with the same path (db/schema/table path is unique).
702+
// If there is such pair of new and removed types then table
703+
// will be reused on schema comparison but data will be cleared
704+
// because it no longer represents type connected to the table.
705+
706+
// IMPORTANT NOTE! SingleTable hierarchies use the same table
707+
// so any new type from such hierarhcy will conflict with removed
708+
// by table. Knowing that we basically cannot register table conflicts
709+
// on table basis, except for the case when root is conflict.
649710

650711
var capacity = currentModel.Types.Length - typeMapping.Count;
651-
var currentTables = new Dictionary<string, StoredTypeInfo>(capacity, StringComparer.Ordinal);
652-
foreach (var t in currentNonConnectorTypes.Where(t => !reverseTypeMapping.ContainsKey(t))) {
653-
currentTables.Add($"{t.MappingDatabase}.{t.MappingSchema}.{t.MappingName}", t);
712+
var currentTables = new HashSet<string>(capacity, StringComparer.Ordinal);
713+
foreach (var newType in currentNonConnectorTypes.Where(t => !reverseTypeMapping.ContainsKey(t))) {
714+
if (newType.Hierarchy == null
715+
|| (newType.Hierarchy.InheritanceSchema == InheritanceSchema.SingleTable && !newType.IsHierarchyRoot))
716+
continue;
717+
var key = $"{newType.MappingDatabase}.{newType.MappingSchema}.{newType.MappingName}";
718+
currentTables.Add(key);
654719
}
655720

656-
var conflictsByTable = new Dictionary<StoredTypeInfo, StoredTypeInfo>();
721+
var conflictsByTable = new HashSet<StoredTypeInfo>();
657722

658723
foreach (var rType in removedTypes) {
659724
var rTypeIdentifier = $"{rType.MappingDatabase}.{rType.MappingSchema}.{rType.MappingName}";
660-
if (suspiciousTypes.Contains(rType) && currentTables.TryGetValue(rTypeIdentifier, out var nType)) {
661-
conflictsByTable.Add(rType, nType);
725+
if (suspiciousTypes.Contains(rType) && currentTables.Contains(rTypeIdentifier)) {
726+
_ = conflictsByTable.Add(rType);
662727
}
663728
}
664729
return conflictsByTable;
@@ -732,6 +797,13 @@ private static StoredTypeInfo[] GetAffectedMappedTypesAsArray(StoredTypeInfo typ
732797

733798
}
734799

800+
private IdentityPair CreateIdentityPair(StoredTypeInfo rType, StoredTypeInfo cType, int? typeIdOverride = null)
801+
{
802+
return new IdentityPair(GetColumnPath(cType, GetTypeIdMappingName(rType)),
803+
(typeIdOverride ?? rType.TypeId).ToString(),
804+
true);
805+
}
806+
735807
private string GetTableName(StoredTypeInfo type)
736808
{
737809
return resolver.GetNodeName(

0 commit comments

Comments
 (0)