@@ -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