diff --git a/src/Eftdb/Internals/Features/CompressionPolicies/CompressionPolicyDiffer.cs b/src/Eftdb/Internals/Features/CompressionPolicies/CompressionPolicyDiffer.cs index 1a20895..43abf31 100644 --- a/src/Eftdb/Internals/Features/CompressionPolicies/CompressionPolicyDiffer.cs +++ b/src/Eftdb/Internals/Features/CompressionPolicies/CompressionPolicyDiffer.cs @@ -25,11 +25,22 @@ public IReadOnlyList GetDifferences(IRelationalModel? source List operations = []; // Apply table renames to the source so a rename isn't seen as a drop-and-add. - List sourceEntries = + List allSourceEntries = [.. CompressionPolicyModelExtractor.GetCompressionPolicyEntries(source).Select(e => RewriteSourceEntry(e, context))]; - List targetEntries = + List allTargetEntries = [.. CompressionPolicyModelExtractor.GetCompressionPolicyEntries(target)]; + // Recreating an aggregate drops its compression policy, so re-add it and skip the normal diff. + foreach (CompressionPolicyModelExtractor.CompressionPolicyEntry entry in allTargetEntries.Where(t => context.RecreatedAggregates.Contains((t.Operation.Schema, t.Operation.TableName)))) + { + operations.Add(entry.Operation); + } + + List sourceEntries = + [.. allSourceEntries.Where(s => !context.RecreatedAggregates.Contains((s.Operation.Schema, s.Operation.TableName)))]; + List targetEntries = + [.. allTargetEntries.Where(t => !context.RecreatedAggregates.Contains((t.Operation.Schema, t.Operation.TableName)))]; + // Identify new compression policies IEnumerable newPolicies = targetEntries .Where(t => !sourceEntries.Any(s => s.Operation.Schema == t.Operation.Schema && s.Operation.TableName == t.Operation.TableName)) diff --git a/tests/Eftdb.Tests/Differs/CompressionPolicyDifferTests.cs b/tests/Eftdb.Tests/Differs/CompressionPolicyDifferTests.cs index 06ace96..773aa79 100644 --- a/tests/Eftdb.Tests/Differs/CompressionPolicyDifferTests.cs +++ b/tests/Eftdb.Tests/Differs/CompressionPolicyDifferTests.cs @@ -1367,4 +1367,191 @@ public void Should_Produce_Exactly_One_Add_And_No_Drop_When_Policy_Added() } #endregion + + #region Should_ReAdd_CompressionPolicy_When_CA_Is_Recreated + + private class MetricEntity20 + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class ContinuousAggregateWithPolicyContext20 : DbContext + { + public DbSet Metrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.ToTable("ca_recreate_metrics"); + entity.HasNoKey(); + entity.IsHypertable(x => x.Timestamp); + entity.WithCompressionPolicy(after: "7 days"); + }); + } + } + + [Fact] + public void Should_ReAdd_CompressionPolicy_When_CA_Is_Recreated() + { + // Arrange + using ContinuousAggregateWithPolicyContext20 sourceContext = new(); + using ContinuousAggregateWithPolicyContext20 targetContext = new(); + + IRelationalModel sourceModel = GetModel(sourceContext); + IRelationalModel targetModel = GetModel(targetContext); + + FeatureDiffContext context = new() + { + RecreatedAggregates = new HashSet<(string, string)> { ("public", "ca_recreate_metrics") } + }; + + CompressionPolicyDiffer differ = new(); + + // Act + IReadOnlyList operations = differ.GetDifferences(sourceModel, targetModel, context); + + // Assert + AddCompressionPolicyOperation addOp = Assert.Single(operations.OfType()); + Assert.Equal("ca_recreate_metrics", addOp.TableName); + Assert.Equal("7 days", addOp.After); + + Assert.Empty(operations.OfType()); + Assert.Empty(operations.OfType()); + } + + #endregion + + #region Should_Not_ReAdd_CompressionPolicy_When_CA_Is_Not_Recreated + + private class MetricEntity21 + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class ContinuousAggregateWithPolicyContext21 : DbContext + { + public DbSet Metrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.ToTable("ca_recreate_metrics"); + entity.HasNoKey(); + entity.IsHypertable(x => x.Timestamp); + entity.WithCompressionPolicy(after: "7 days"); + }); + } + } + + [Fact] + public void Should_Not_ReAdd_CompressionPolicy_When_CA_Is_Not_Recreated() + { + // Arrange + using ContinuousAggregateWithPolicyContext21 sourceContext = new(); + using ContinuousAggregateWithPolicyContext21 targetContext = new(); + + IRelationalModel sourceModel = GetModel(sourceContext); + IRelationalModel targetModel = GetModel(targetContext); + + CompressionPolicyDiffer differ = new(); + + // Act + IReadOnlyList operations = differ.GetDifferences(sourceModel, targetModel); + + // Assert + Assert.Empty(operations); + } + + #endregion + + #region Should_ReAdd_CompressionPolicy_With_New_Settings_When_Changed_And_Recreated + + private class MetricEntity22 + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class OldCompressionPolicyContext22 : DbContext + { + public DbSet Metrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.ToTable("ca_recreate_metrics"); + entity.HasNoKey(); + entity.IsHypertable(x => x.Timestamp); + entity.WithCompressionPolicy(after: "7 days"); + }); + } + } + + private class NewCompressionPolicyContext22 : DbContext + { + public DbSet Metrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.ToTable("ca_recreate_metrics"); + entity.HasNoKey(); + entity.IsHypertable(x => x.Timestamp); + entity.WithCompressionPolicy(after: "14 days"); + }); + } + } + + [Fact] + public void Should_ReAdd_CompressionPolicy_With_New_Settings_When_Changed_And_Recreated() + { + // Arrange + using OldCompressionPolicyContext22 sourceContext = new(); + using NewCompressionPolicyContext22 targetContext = new(); + + IRelationalModel sourceModel = GetModel(sourceContext); + IRelationalModel targetModel = GetModel(targetContext); + + FeatureDiffContext context = new() + { + RecreatedAggregates = new HashSet<(string, string)> { ("public", "ca_recreate_metrics") } + }; + + CompressionPolicyDiffer differ = new(); + + // Act + IReadOnlyList operations = differ.GetDifferences(sourceModel, targetModel, context); + + // Assert + AddCompressionPolicyOperation addOp = Assert.Single(operations.OfType()); + Assert.Equal("ca_recreate_metrics", addOp.TableName); + Assert.Equal("14 days", addOp.After); + + Assert.Empty(operations.OfType()); + Assert.Empty(operations.OfType()); + } + + #endregion }