diff --git a/docs/data-annotations/continuous-aggregates.md b/docs/data-annotations/continuous-aggregates.md index 77e09e6..3b864ae 100644 --- a/docs/data-annotations/continuous-aggregates.md +++ b/docs/data-annotations/continuous-aggregates.md @@ -159,8 +159,9 @@ Structured aggregates (those configured through attributes rather than a raw vie - Duplicate output column names are rejected with an `InvalidOperationException`. The check compares the bucket column, all `[GroupByColumn]` columns, and every `[Aggregate]` alias after resolving them to database column names. A source column that collides with the bucket column name — previously surfacing only at `migrate` time — is now caught at model build. - A property designated by a property-level `[TimeBucket]` that does not exist on the entity raises an `InvalidOperationException` (this cannot occur through attributes alone, but the same check guards Fluent-configured models). +- An aggregate with no time-bucket designation (property-level `[TimeBucket]`) and no property mapping to the default bucket column `time_bucket` emits a warning through the configured EF logger. The view still exposes a `time_bucket` column, but it cannot be queried through the entity; previously this surfaced only at query time as an opaque Postgres "column does not exist" error. Remedy by designating a property or mapping one to `time_bucket`. This is a warning rather than an exception because deliberately not exposing the bucket is legal. -> :warning: **Note:** Entities scaffolded with a raw view definition are exempt from both checks, because the structured projection fields are unused on that path. +> :warning: **Note:** Entities scaffolded with a raw view definition are exempt from all checks, because the structured projection fields are unused on that path. ## Configuration Options diff --git a/docs/fluent-api/continuous-aggregates.md b/docs/fluent-api/continuous-aggregates.md index e484da2..f4bbf47 100644 --- a/docs/fluent-api/continuous-aggregates.md +++ b/docs/fluent-api/continuous-aggregates.md @@ -203,8 +203,9 @@ Structured aggregates (those configured through the builders rather than a raw v - Duplicate output column names are rejected with an `InvalidOperationException`. The check compares the bucket column, all GROUP BY columns, and every aggregate alias after resolving them to database column names. A source column that collides with the bucket column name is caught at model build. - A property designated via `.WithTimeBucketProperty(...)` that does not exist on the entity raises an `InvalidOperationException`. +- An aggregate with no time-bucket designation (`.WithTimeBucketProperty(...)`) and no property mapping to the default bucket column `time_bucket` emits a warning through the configured EF logger. The view still exposes a `time_bucket` column, but it cannot be queried through the entity; previously this surfaced only at query time as an opaque Postgres "column does not exist" error. Remedy by designating a property or mapping one to `time_bucket`. This is a warning rather than an exception because deliberately not exposing the bucket is legal. -> :warning: **Note:** Entities scaffolded with a raw view definition are exempt from both checks, because the structured projection fields are unused on that path. +> :warning: **Note:** Entities scaffolded with a raw view definition are exempt from all checks, because the structured projection fields are unused on that path. ## Hierarchical Continuous Aggregates diff --git a/samples/Eftdb.Samples.Shared/Configurations/HourlyStationAggregateConfiguration.cs b/samples/Eftdb.Samples.Shared/Configurations/HourlyStationAggregateConfiguration.cs index b4a7cac..d07e8f9 100644 --- a/samples/Eftdb.Samples.Shared/Configurations/HourlyStationAggregateConfiguration.cs +++ b/samples/Eftdb.Samples.Shared/Configurations/HourlyStationAggregateConfiguration.cs @@ -22,6 +22,7 @@ public void Configure(EntityTypeBuilder builder) propertyExpression: source => source.RecordedAt, timeBucketGroupBy: true) + .WithTimeBucketProperty(x => x.Bucket) .AddAggregateFunction( agg => agg.AvgLatitude, source => source.Location.Coordinates.Latitude, diff --git a/samples/Eftdb.Samples.Shared/Models/HourlySensorAggregate.cs b/samples/Eftdb.Samples.Shared/Models/HourlySensorAggregate.cs index 79c92eb..67c7d69 100644 --- a/samples/Eftdb.Samples.Shared/Models/HourlySensorAggregate.cs +++ b/samples/Eftdb.Samples.Shared/Models/HourlySensorAggregate.cs @@ -2,6 +2,11 @@ namespace CmdScale.EntityFrameworkCore.TimescaleDB.Samples.Shared.Models { public class HourlySensorAggregate { + /// + /// Start of the hour-wide bucket this row summarizes. + /// + public DateTime TimeBucket { get; set; } + public double AvgPrimaryValue { get; set; } public double MinPrimaryValue { get; set; } public double MaxPrimaryValue { get; set; } diff --git a/samples/Eftdb.Samples.Shared/Models/TradeAggregate.cs b/samples/Eftdb.Samples.Shared/Models/TradeAggregate.cs index d8d82f0..ebc7f3e 100644 --- a/samples/Eftdb.Samples.Shared/Models/TradeAggregate.cs +++ b/samples/Eftdb.Samples.Shared/Models/TradeAggregate.cs @@ -2,6 +2,11 @@ { public class TradeAggregate { + /// + /// Start of the hour-wide bucket this row summarizes. + /// + public DateTime TimeBucket { get; set; } + public decimal AveragePrice { get; set; } public decimal MaxPrice { get; set; } public decimal MinPrice { get; set; } diff --git a/samples/Eftdb.Samples.Shared/Models/WeatherAggregate.cs b/samples/Eftdb.Samples.Shared/Models/WeatherAggregate.cs index 890c333..78fb791 100644 --- a/samples/Eftdb.Samples.Shared/Models/WeatherAggregate.cs +++ b/samples/Eftdb.Samples.Shared/Models/WeatherAggregate.cs @@ -31,6 +31,11 @@ namespace CmdScale.EntityFrameworkCore.TimescaleDB.Samples.Shared.Models [CompressionPolicy(After = "90 days", ScheduleInterval = "1 day")] public class WeatherAggregate { + /// + /// Start of the day-wide bucket this row summarizes. + /// + public DateTime TimeBucket { get; set; } + // Avg aggregate function [Aggregate(EAggregateFunction.Avg, nameof(WeatherData.Temperature))] public double AverageTemperature { get; set; } diff --git a/src/Eftdb.Design/Features/CompressionPolicy/CompressionPolicyAnnotationRenderer.cs b/src/Eftdb.Design/Features/CompressionPolicy/CompressionPolicyAnnotationRenderer.cs index a1ffb6d..e75d51d 100644 --- a/src/Eftdb.Design/Features/CompressionPolicy/CompressionPolicyAnnotationRenderer.cs +++ b/src/Eftdb.Design/Features/CompressionPolicy/CompressionPolicyAnnotationRenderer.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.CompressionPolicy; diff --git a/src/Eftdb.Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationRenderer.cs b/src/Eftdb.Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationRenderer.cs index 1a650a7..bb8e48e 100644 --- a/src/Eftdb.Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationRenderer.cs +++ b/src/Eftdb.Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationRenderer.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy; diff --git a/src/Eftdb.Design/Features/Hypertable/HypertableAnnotationRenderer.cs b/src/Eftdb.Design/Features/Hypertable/HypertableAnnotationRenderer.cs index b879d85..ef9592f 100644 --- a/src/Eftdb.Design/Features/Hypertable/HypertableAnnotationRenderer.cs +++ b/src/Eftdb.Design/Features/Hypertable/HypertableAnnotationRenderer.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; diff --git a/src/Eftdb.Design/Features/ReorderPolicy/ReorderPolicyAnnotationRenderer.cs b/src/Eftdb.Design/Features/ReorderPolicy/ReorderPolicyAnnotationRenderer.cs index 5fb8776..e8b80eb 100644 --- a/src/Eftdb.Design/Features/ReorderPolicy/ReorderPolicyAnnotationRenderer.cs +++ b/src/Eftdb.Design/Features/ReorderPolicy/ReorderPolicyAnnotationRenderer.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ReorderPolicy; diff --git a/src/Eftdb.Design/Features/RetentionPolicy/RetentionPolicyAnnotationRenderer.cs b/src/Eftdb.Design/Features/RetentionPolicy/RetentionPolicyAnnotationRenderer.cs index 61ce23f..1800028 100644 --- a/src/Eftdb.Design/Features/RetentionPolicy/RetentionPolicyAnnotationRenderer.cs +++ b/src/Eftdb.Design/Features/RetentionPolicy/RetentionPolicyAnnotationRenderer.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; diff --git a/src/Eftdb.Design/Generators/TimescaleCSharpHelper.cs b/src/Eftdb.Design/Generators/TimescaleCSharpHelper.cs index db7bc9c..44490b8 100644 --- a/src/Eftdb.Design/Generators/TimescaleCSharpHelper.cs +++ b/src/Eftdb.Design/Generators/TimescaleCSharpHelper.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Storage; diff --git a/src/Eftdb.Design/Generators/TimescaleDbAnnotationCodeGenerator.cs b/src/Eftdb.Design/Generators/TimescaleDbAnnotationCodeGenerator.cs index 7511a6b..4183840 100644 --- a/src/Eftdb.Design/Generators/TimescaleDbAnnotationCodeGenerator.cs +++ b/src/Eftdb.Design/Generators/TimescaleDbAnnotationCodeGenerator.cs @@ -6,7 +6,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregatePolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using CmdScale.EntityFrameworkCore.TimescaleDB.Internals; using Microsoft.EntityFrameworkCore; diff --git a/src/Eftdb.Design/TimescaleCSharpMigrationOperationGenerator.cs b/src/Eftdb.Design/TimescaleCSharpMigrationOperationGenerator.cs index 4a6d8c9..2e43b8d 100644 --- a/src/Eftdb.Design/TimescaleCSharpMigrationOperationGenerator.cs +++ b/src/Eftdb.Design/TimescaleCSharpMigrationOperationGenerator.cs @@ -4,7 +4,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.CompressionPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregatePolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations.Design; diff --git a/src/Eftdb/Configuration/ContinuousAggregateViewColumnValidationConvention.cs b/src/Eftdb/Configuration/ContinuousAggregateViewColumnValidationConvention.cs index f48fc6d..ffdc416 100644 --- a/src/Eftdb/Configuration/ContinuousAggregateViewColumnValidationConvention.cs +++ b/src/Eftdb/Configuration/ContinuousAggregateViewColumnValidationConvention.cs @@ -44,7 +44,7 @@ private static void ValidateViewColumns(IModel model, IEntityType entityType) return; } - StoreObjectIdentifier? aggregateStoreIdentifier = GetStoreObjectIdentifier(entityType); + StoreObjectIdentifier? aggregateStoreIdentifier = EntityStoreObjectResolver.GetStoreObjectIdentifier(entityType); if (aggregateStoreIdentifier == null) { return; @@ -53,7 +53,7 @@ private static void ValidateViewColumns(IModel model, IEntityType entityType) string bucketColumnName = ResolveBucketColumnName(entityType, aggregateStoreIdentifier.Value, materializedViewName); IEntityType? parentEntityType = ResolveParent(model, entityType); - StoreObjectIdentifier? parentStoreIdentifier = parentEntityType == null ? null : GetStoreObjectIdentifier(parentEntityType); + StoreObjectIdentifier? parentStoreIdentifier = parentEntityType == null ? null : EntityStoreObjectResolver.GetStoreObjectIdentifier(parentEntityType); List outputColumns = [bucketColumnName]; outputColumns.AddRange(ResolveGroupByColumns(entityType, parentEntityType, parentStoreIdentifier)); @@ -65,7 +65,7 @@ private static void ValidateViewColumns(IModel model, IEntityType entityType) if (!seen.Add(column)) { throw new InvalidOperationException( - $"The continuous aggregate '{DisplayName(entityType)}' (materialized view '{materializedViewName}') " + + $"The continuous aggregate '{EntityStoreObjectResolver.DisplayName(entityType)}' (materialized view '{materializedViewName}') " + $"produces the output column '{column}' more than once. Rename the conflicting property or use " + $"WithTimeBucketProperty to map the bucket column to a distinct property."); } @@ -88,7 +88,7 @@ private static string ResolveBucketColumnName(IEntityType entityType, StoreObjec if (property == null) { throw new InvalidOperationException( - $"The continuous aggregate '{DisplayName(entityType)}' (materialized view '{materializedViewName}') " + + $"The continuous aggregate '{EntityStoreObjectResolver.DisplayName(entityType)}' (materialized view '{materializedViewName}') " + $"designates '{targetPropertyName}' as its time-bucket property, but no such property exists on the entity."); } @@ -154,24 +154,5 @@ private static IEnumerable ResolveAggregateAliasColumns(IEntityType enti string? parentName = entityType.FindAnnotation(ContinuousAggregateAnnotations.ParentName)?.Value as string; return string.IsNullOrWhiteSpace(parentName) ? null : ParentEntityTypeResolver.Resolve(model, parentName); } - - private static StoreObjectIdentifier? GetStoreObjectIdentifier(IEntityType entityType) - { - string? tableName = entityType.GetTableName(); - if (!string.IsNullOrWhiteSpace(tableName)) - { - return StoreObjectIdentifier.Table(tableName, entityType.GetSchema()); - } - - string? viewName = entityType.GetViewName(); - if (!string.IsNullOrWhiteSpace(viewName)) - { - return StoreObjectIdentifier.View(viewName, entityType.GetViewSchema() ?? entityType.GetSchema()); - } - - return null; - } - - private static string DisplayName(IEntityType entityType) => entityType.ClrType?.Name ?? entityType.Name; } } diff --git a/src/Eftdb/Internals/EntityStoreObjectResolver.cs b/src/Eftdb/Internals/EntityStoreObjectResolver.cs new file mode 100644 index 0000000..afd9dd2 --- /dev/null +++ b/src/Eftdb/Internals/EntityStoreObjectResolver.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace CmdScale.EntityFrameworkCore.TimescaleDB.Internals +{ + /// + /// Resolves the store object (table or view) an entity maps to, and a human-readable display + /// name for diagnostics. + /// + internal static class EntityStoreObjectResolver + { + /// + /// Returns the table store object when the entity maps to a table, otherwise the view store + /// object, or null when the entity maps to neither. + /// + public static StoreObjectIdentifier? GetStoreObjectIdentifier(IEntityType entityType) + { + string? tableName = entityType.GetTableName(); + if (!string.IsNullOrWhiteSpace(tableName)) + { + return StoreObjectIdentifier.Table(tableName, entityType.GetSchema()); + } + + string? viewName = entityType.GetViewName(); + if (!string.IsNullOrWhiteSpace(viewName)) + { + return StoreObjectIdentifier.View(viewName, entityType.GetViewSchema() ?? entityType.GetSchema()); + } + + return null; + } + + /// + /// Returns the CLR type name for diagnostics, falling back to the EF entity-type name for + /// shared-type or keyless entities without a distinct CLR type. + /// + public static string DisplayName(IEntityType entityType) => entityType.ClrType?.Name ?? entityType.Name; + } +} diff --git a/src/Eftdb/Internals/TimescaleModelValidator.cs b/src/Eftdb/Internals/TimescaleModelValidator.cs new file mode 100644 index 0000000..c6c9881 --- /dev/null +++ b/src/Eftdb/Internals/TimescaleModelValidator.cs @@ -0,0 +1,87 @@ +using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.Extensions.Logging; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + +namespace CmdScale.EntityFrameworkCore.TimescaleDB.Internals +{ +#pragma warning disable EF1001 // Npgsql internal validator/options are the intended base to preserve Npgsql validations. + /// + /// Extends Npgsql's model validator with TimescaleDB-specific model warnings. Derives from + /// all Npgsql validations still run via 's base call. + /// + internal class TimescaleModelValidator( + ModelValidatorDependencies dependencies, + RelationalModelValidatorDependencies relationalDependencies, + INpgsqlSingletonOptions npgsqlSingletonOptions) + : NpgsqlModelValidator(dependencies, relationalDependencies, npgsqlSingletonOptions) + { + public override void Validate(IModel model, IDiagnosticsLogger logger) + { + base.Validate(model, logger); + + foreach (IEntityType entityType in model.GetEntityTypes()) + { + WarnWhenBucketColumnUnmapped(entityType, logger); + } + } + + /// + /// Warns when a structured continuous aggregate leaves its bucket column unmapped: no + /// designation and no + /// property resolving to the fallback bucket column. Such a model is legal — the entity simply + /// cannot query the bucket — but any LINQ touching the (absent) bucket property fails at runtime + /// with an opaque "column does not exist" error, so the warning points at the fix. Entities + /// carrying a raw are exempt, matching + /// the scaffolded raw-definition exemption in the view-column validation convention. + /// + private static void WarnWhenBucketColumnUnmapped(IEntityType entityType, IDiagnosticsLogger logger) + { + string? materializedViewName = entityType.FindAnnotation(ContinuousAggregateAnnotations.MaterializedViewName)?.Value as string; + if (string.IsNullOrWhiteSpace(materializedViewName)) + { + return; + } + + string? viewDefinition = entityType.FindAnnotation(ContinuousAggregateAnnotations.ViewDefinition)?.Value as string; + if (!string.IsNullOrWhiteSpace(viewDefinition)) + { + return; + } + + string? targetPropertyName = entityType.FindAnnotation(ContinuousAggregateAnnotations.TimeBucketTargetProperty)?.Value as string; + if (!string.IsNullOrWhiteSpace(targetPropertyName)) + { + return; + } + + StoreObjectIdentifier? aggregateStoreIdentifier = EntityStoreObjectResolver.GetStoreObjectIdentifier(entityType); + if (aggregateStoreIdentifier == null) + { + return; + } + + // Without a designation the bucket column is always the fallback; warn only when nothing maps to it. + string bucketColumn = DefaultValues.ContinuousAggregateTimeBucketColumnName; + string? mappedColumn = ColumnNameResolver.Resolve(entityType, bucketColumn, aggregateStoreIdentifier.Value); + if (!string.IsNullOrWhiteSpace(mappedColumn)) + { + return; + } + + logger.Logger.LogWarning( + "The continuous aggregate '{Aggregate}' (materialized view '{MaterializedView}') exposes its bucket column as " + + "'{BucketColumn}', but no property maps to that column, so the bucket cannot be queried through the entity. " + + "Designate the bucket property with WithTimeBucketProperty(...), annotate a property with [TimeBucket], or map a " + + "property to '{BucketColumnFix}' with HasColumnName.", + EntityStoreObjectResolver.DisplayName(entityType), + materializedViewName, + bucketColumn, + bucketColumn); + } + } +} +#pragma warning restore EF1001 diff --git a/src/Eftdb/TimescaleDbContextOptionsBuilderExtensions.cs b/src/Eftdb/TimescaleDbContextOptionsBuilderExtensions.cs index cf84aa6..aca6b02 100644 --- a/src/Eftdb/TimescaleDbContextOptionsBuilderExtensions.cs +++ b/src/Eftdb/TimescaleDbContextOptionsBuilderExtensions.cs @@ -104,6 +104,7 @@ public void ApplyServices(IServiceCollection services) services.AddSingleton(); services.AddScoped(); services.Replace(ServiceDescriptor.Scoped()); + services.Replace(ServiceDescriptor.Singleton()); services.TryAddEnumerable( ServiceDescriptor.Scoped()); } diff --git a/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyAnnotationApplierTests.cs b/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyAnnotationApplierTests.cs index d9bb2de..877bf29 100644 --- a/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyAnnotationApplierTests.cs +++ b/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyAnnotationApplierTests.cs @@ -1,7 +1,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.CompressionPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.CompressionPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using static CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.CompressionPolicy.CompressionPolicyScaffoldingExtractor; diff --git a/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyAnnotationRendererTests.cs b/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyAnnotationRendererTests.cs index f9f0b24..f73bf73 100644 --- a/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyAnnotationRendererTests.cs +++ b/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyAnnotationRendererTests.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.CompressionPolicy; #pragma warning disable EF1001 // IOperationReporter and AnnotationCodeGeneratorDependencies are design-time internals. using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.CompressionPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; diff --git a/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyCSharpGeneratorTests.cs b/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyCSharpGeneratorTests.cs index 9c63664..530e642 100644 --- a/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyCSharpGeneratorTests.cs +++ b/tests/Eftdb.Tests/Design/Features/CompressionPolicy/CompressionPolicyCSharpGeneratorTests.cs @@ -1,5 +1,4 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.CompressionPolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateAnnotationApplierTests.cs b/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateAnnotationApplierTests.cs index 65716a7..f8337c9 100644 --- a/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateAnnotationApplierTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateAnnotationApplierTests.cs @@ -1,6 +1,5 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using static CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregate.ContinuousAggregateScaffoldingExtractor; diff --git a/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateAnnotationRendererTests.cs b/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateAnnotationRendererTests.cs index 399a462..2128a62 100644 --- a/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateAnnotationRendererTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateAnnotationRendererTests.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregate; #pragma warning disable EF1001 // IOperationReporter and AnnotationCodeGeneratorDependencies are design-time internals. using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; diff --git a/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateCSharpGeneratorTests.cs b/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateCSharpGeneratorTests.cs index 7d831f0..54a820c 100644 --- a/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateCSharpGeneratorTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateCSharpGeneratorTests.cs @@ -1,5 +1,4 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregate; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateCompressionCSharpGeneratorTests.cs b/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateCompressionCSharpGeneratorTests.cs index 7daf264..e3ad3f9 100644 --- a/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateCompressionCSharpGeneratorTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ContinuousAggregate/ContinuousAggregateCompressionCSharpGeneratorTests.cs @@ -1,5 +1,4 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregate; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationApplierTests.cs b/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationApplierTests.cs index ba727c6..d4ad481 100644 --- a/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationApplierTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationApplierTests.cs @@ -1,6 +1,5 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregatePolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using static CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregatePolicy.ContinuousAggregatePolicyScaffoldingExtractor; diff --git a/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationRendererTests.cs b/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationRendererTests.cs index e5f118d..e28ce12 100644 --- a/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationRendererTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyAnnotationRendererTests.cs @@ -1,5 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregatePolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregate; #pragma warning disable EF1001 // IOperationReporter and AnnotationCodeGeneratorDependencies are design-time internals. using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy; diff --git a/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyCSharpGeneratorTests.cs b/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyCSharpGeneratorTests.cs index 25ae879..0b5e0b7 100644 --- a/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyCSharpGeneratorTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ContinuousAggregatePolicy/ContinuousAggregatePolicyCSharpGeneratorTests.cs @@ -1,5 +1,4 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregatePolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableAnnotationApplierTests.cs b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableAnnotationApplierTests.cs index 8c88393..a82c438 100644 --- a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableAnnotationApplierTests.cs +++ b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableAnnotationApplierTests.cs @@ -1,7 +1,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using System.Text.Json; using static CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable.HypertableScaffoldingExtractor; diff --git a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableAnnotationRendererTests.cs b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableAnnotationRendererTests.cs index d4f593b..ce73dc0 100644 --- a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableAnnotationRendererTests.cs +++ b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableAnnotationRendererTests.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Design; diff --git a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableCSharpGeneratorTests.cs b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableCSharpGeneratorTests.cs index 3f8f231..0ba5df3 100644 --- a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableCSharpGeneratorTests.cs +++ b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableCSharpGeneratorTests.cs @@ -1,6 +1,5 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableColumnstoreAnnotationApplierTests.cs b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableColumnstoreAnnotationApplierTests.cs index f787d3a..d756ce3 100644 --- a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableColumnstoreAnnotationApplierTests.cs +++ b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableColumnstoreAnnotationApplierTests.cs @@ -1,6 +1,5 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using static CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable.HypertableScaffoldingExtractor; diff --git a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableColumnstoreCSharpGeneratorTests.cs b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableColumnstoreCSharpGeneratorTests.cs index 6ad1a3d..f049624 100644 --- a/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableColumnstoreCSharpGeneratorTests.cs +++ b/tests/Eftdb.Tests/Design/Features/Hypertable/HypertableColumnstoreCSharpGeneratorTests.cs @@ -1,5 +1,4 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyAnnotationApplierTests.cs b/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyAnnotationApplierTests.cs index b2a4bd5..da43628 100644 --- a/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyAnnotationApplierTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyAnnotationApplierTests.cs @@ -1,6 +1,5 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ReorderPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ReorderPolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using static CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ReorderPolicy.ReorderPolicyScaffoldingExtractor; diff --git a/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyAnnotationRendererTests.cs b/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyAnnotationRendererTests.cs index 447ac93..26ea9af 100644 --- a/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyAnnotationRendererTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyAnnotationRendererTests.cs @@ -1,4 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ReorderPolicy; #pragma warning disable EF1001 // IOperationReporter and AnnotationCodeGeneratorDependencies are design-time internals. using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ReorderPolicy; diff --git a/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyCSharpGeneratorTests.cs b/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyCSharpGeneratorTests.cs index b0d0b6e..b778171 100644 --- a/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyCSharpGeneratorTests.cs +++ b/tests/Eftdb.Tests/Design/Features/ReorderPolicy/ReorderPolicyCSharpGeneratorTests.cs @@ -1,5 +1,4 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ReorderPolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyAnnotationApplierTests.cs b/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyAnnotationApplierTests.cs index 5d7f10a..9c92b53 100644 --- a/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyAnnotationApplierTests.cs +++ b/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyAnnotationApplierTests.cs @@ -1,6 +1,5 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.RetentionPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.RetentionPolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using static CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.RetentionPolicy.RetentionPolicyScaffoldingExtractor; diff --git a/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyAnnotationRendererTests.cs b/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyAnnotationRendererTests.cs index 44de402..45e5c2b 100644 --- a/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyAnnotationRendererTests.cs +++ b/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyAnnotationRendererTests.cs @@ -1,6 +1,3 @@ -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ContinuousAggregate; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.RetentionPolicy; #pragma warning disable EF1001 // IOperationReporter and AnnotationCodeGeneratorDependencies are design-time internals. using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; diff --git a/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyCSharpGeneratorTests.cs b/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyCSharpGeneratorTests.cs index f6b5bf9..10dc7bc 100644 --- a/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyCSharpGeneratorTests.cs +++ b/tests/Eftdb.Tests/Design/Features/RetentionPolicy/RetentionPolicyCSharpGeneratorTests.cs @@ -1,5 +1,4 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.RetentionPolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Generators/CSharpGeneratorHelperTests.cs b/tests/Eftdb.Tests/Design/Generators/CSharpGeneratorHelperTests.cs index 6613007..2fc659a 100644 --- a/tests/Eftdb.Tests/Design/Generators/CSharpGeneratorHelperTests.cs +++ b/tests/Eftdb.Tests/Design/Generators/CSharpGeneratorHelperTests.cs @@ -1,6 +1,5 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Design/Generators/MigrationCallWriterTests.cs b/tests/Eftdb.Tests/Design/Generators/MigrationCallWriterTests.cs index 4e38ae8..debc6f7 100644 --- a/tests/Eftdb.Tests/Design/Generators/MigrationCallWriterTests.cs +++ b/tests/Eftdb.Tests/Design/Generators/MigrationCallWriterTests.cs @@ -1,5 +1,4 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ReorderPolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Generators; using CmdScale.EntityFrameworkCore.TimescaleDB.Operations; using CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Utils; using Microsoft.EntityFrameworkCore.Design; diff --git a/tests/Eftdb.Tests/Integration/CompressionPolicyScaffoldingExtractorTests.cs b/tests/Eftdb.Tests/Integration/CompressionPolicyScaffoldingExtractorTests.cs index e156d13..a72de22 100644 --- a/tests/Eftdb.Tests/Integration/CompressionPolicyScaffoldingExtractorTests.cs +++ b/tests/Eftdb.Tests/Integration/CompressionPolicyScaffoldingExtractorTests.cs @@ -1,7 +1,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.CompressionPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.CompressionPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using Npgsql; diff --git a/tests/Eftdb.Tests/Integration/ContinuousAggregateCompressionScaffoldingExtractorTests.cs b/tests/Eftdb.Tests/Integration/ContinuousAggregateCompressionScaffoldingExtractorTests.cs index edca6b9..c19a2bd 100644 --- a/tests/Eftdb.Tests/Integration/ContinuousAggregateCompressionScaffoldingExtractorTests.cs +++ b/tests/Eftdb.Tests/Integration/ContinuousAggregateCompressionScaffoldingExtractorTests.cs @@ -2,7 +2,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore; using Npgsql; using Testcontainers.PostgreSql; diff --git a/tests/Eftdb.Tests/Integration/ContinuousAggregatePolicyScaffoldingExtractorTests.cs b/tests/Eftdb.Tests/Integration/ContinuousAggregatePolicyScaffoldingExtractorTests.cs index 2f74768..edf9aa6 100644 --- a/tests/Eftdb.Tests/Integration/ContinuousAggregatePolicyScaffoldingExtractorTests.cs +++ b/tests/Eftdb.Tests/Integration/ContinuousAggregatePolicyScaffoldingExtractorTests.cs @@ -3,7 +3,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore; using Npgsql; using Testcontainers.PostgreSql; diff --git a/tests/Eftdb.Tests/Integration/HypertableScaffoldingExtractorTests.cs b/tests/Eftdb.Tests/Integration/HypertableScaffoldingExtractorTests.cs index dcb6db4..a39853d 100644 --- a/tests/Eftdb.Tests/Integration/HypertableScaffoldingExtractorTests.cs +++ b/tests/Eftdb.Tests/Integration/HypertableScaffoldingExtractorTests.cs @@ -1,7 +1,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore; using Npgsql; using Testcontainers.PostgreSql; diff --git a/tests/Eftdb.Tests/Integration/ReorderPolicyScaffoldingExtractorTests.cs b/tests/Eftdb.Tests/Integration/ReorderPolicyScaffoldingExtractorTests.cs index 1ad447c..556c3e4 100644 --- a/tests/Eftdb.Tests/Integration/ReorderPolicyScaffoldingExtractorTests.cs +++ b/tests/Eftdb.Tests/Integration/ReorderPolicyScaffoldingExtractorTests.cs @@ -1,7 +1,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.ReorderPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ReorderPolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore; using Npgsql; using Testcontainers.PostgreSql; diff --git a/tests/Eftdb.Tests/Integration/RetentionPolicyScaffoldingExtractorTests.cs b/tests/Eftdb.Tests/Integration/RetentionPolicyScaffoldingExtractorTests.cs index dfb1f4e..c533e4a 100644 --- a/tests/Eftdb.Tests/Integration/RetentionPolicyScaffoldingExtractorTests.cs +++ b/tests/Eftdb.Tests/Integration/RetentionPolicyScaffoldingExtractorTests.cs @@ -1,7 +1,6 @@ using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Features.RetentionPolicy; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.RetentionPolicy; -using CmdScale.EntityFrameworkCore.TimescaleDB.Design.Scaffolding; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using Npgsql; diff --git a/tests/Eftdb.Tests/Internals/TimescaleModelValidatorTests.cs b/tests/Eftdb.Tests/Internals/TimescaleModelValidatorTests.cs new file mode 100644 index 0000000..a5d9484 --- /dev/null +++ b/tests/Eftdb.Tests/Internals/TimescaleModelValidatorTests.cs @@ -0,0 +1,404 @@ +using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; +using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; +using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Internals; + +/// +/// Tests that verify TimescaleModelValidator warns exactly once per structured continuous aggregate +/// whose bucket column stays unmapped, and stays silent when the bucket is designated, mapped, raw, +/// or absent entirely. +/// +public class TimescaleModelValidatorTests +{ + private const string BucketWarningFragment = "time_bucket"; + + private sealed class CapturingLoggerProvider(List warnings) : ILoggerProvider + { + public ILogger CreateLogger(string categoryName) => new CapturingLogger(warnings); + + public void Dispose() { } + + private sealed class CapturingLogger(List warnings) : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull => null; + + public bool IsEnabled(LogLevel logLevel) => logLevel == LogLevel.Warning; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (logLevel == LogLevel.Warning) + { + warnings.Add(formatter(state, exception)); + } + } + } + } + + private static List RunValidationAndCapture(Func, TContext> factory) + where TContext : DbContext + { + List warnings = []; + using TContext context = factory(warnings); + _ = context.Model; + return [.. warnings.Where(w => w.Contains("continuous aggregate", StringComparison.OrdinalIgnoreCase))]; + } + + #region Should_Warn_When_Bucket_Property_Mapped_Elsewhere + + private class UndesignatedRaw + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class UndesignatedAggregate + { + public DateTime Bucket { get; set; } + public double AvgValue { get; set; } + } + + private class UndesignatedContext(List warnings) : DbContext + { + public DbSet Metrics => Set(); + public DbSet HourlyMetrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseLoggerFactory(LoggerFactory.Create(b => b.AddProvider(new CapturingLoggerProvider(warnings)))) + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.ToTable("undesignated_raw"); + entity.IsHypertable(x => x.Timestamp); + }); + + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.Property(x => x.Bucket).HasColumnName("bucket"); + entity.IsContinuousAggregate( + "undesignated_hourly", + "1 hour", + x => x.Timestamp + ).AddAggregateFunction(x => x.AvgValue, x => x.Value, EAggregateFunction.Avg); + }); + } + } + + [Fact] + public void Should_Warn_When_Bucket_Property_Mapped_Elsewhere() + { + // Act + List warnings = RunValidationAndCapture(w => new UndesignatedContext(w)); + + // Assert + string warning = Assert.Single(warnings); + Assert.Contains("UndesignatedAggregate", warning); + Assert.Contains(BucketWarningFragment, warning); + } + + #endregion + + #region Should_Not_Warn_When_Bucket_Property_Designated + + private class DesignatedRaw + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class DesignatedAggregate + { + public DateTime Bucket { get; set; } + public double AvgValue { get; set; } + } + + private class DesignatedContext(List warnings) : DbContext + { + public DbSet Metrics => Set(); + public DbSet HourlyMetrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseLoggerFactory(LoggerFactory.Create(b => b.AddProvider(new CapturingLoggerProvider(warnings)))) + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.ToTable("designated_raw"); + entity.IsHypertable(x => x.Timestamp); + }); + + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.Property(x => x.Bucket).HasColumnName("bucket"); + entity.IsContinuousAggregate( + "designated_hourly", + "1 hour", + x => x.Timestamp + ).WithTimeBucketProperty(x => x.Bucket) + .AddAggregateFunction(x => x.AvgValue, x => x.Value, EAggregateFunction.Avg); + }); + } + } + + [Fact] + public void Should_Not_Warn_When_Bucket_Property_Designated() + { + // Act + List warnings = RunValidationAndCapture(w => new DesignatedContext(w)); + + // Assert + Assert.Empty(warnings); + } + + #endregion + + #region Should_Not_Warn_When_Property_Maps_To_BucketColumn + + private class MappedRaw + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class MappedAggregate + { + public DateTime Bucket { get; set; } + public double AvgValue { get; set; } + } + + private class MappedContext(List warnings) : DbContext + { + public DbSet Metrics => Set(); + public DbSet HourlyMetrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseLoggerFactory(LoggerFactory.Create(b => b.AddProvider(new CapturingLoggerProvider(warnings)))) + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.ToTable("mapped_raw"); + entity.IsHypertable(x => x.Timestamp); + }); + + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.Property(x => x.Bucket).HasColumnName("time_bucket"); + entity.IsContinuousAggregate( + "mapped_hourly", + "1 hour", + x => x.Timestamp + ).AddAggregateFunction(x => x.AvgValue, x => x.Value, EAggregateFunction.Avg); + }); + } + } + + [Fact] + public void Should_Not_Warn_When_Property_Maps_To_BucketColumn() + { + // Act + List warnings = RunValidationAndCapture(w => new MappedContext(w)); + + // Assert + Assert.Empty(warnings); + } + + #endregion + + #region Should_Not_Warn_For_RawViewDefinition + + private class RawDefinitionRaw + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class RawDefinitionAggregate + { + public DateTime Bucket { get; set; } + public double AvgValue { get; set; } + } + + private class RawDefinitionContext(List warnings) : DbContext + { + public DbSet Metrics => Set(); + public DbSet HourlyMetrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseLoggerFactory(LoggerFactory.Create(b => b.AddProvider(new CapturingLoggerProvider(warnings)))) + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.ToTable("raw_definition_raw"); + entity.IsHypertable(x => x.Timestamp); + }); + + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.Property(x => x.Bucket).HasColumnName("bucket"); + entity.IsContinuousAggregate( + "raw_definition_hourly", + "1 hour", + x => x.Timestamp + ).AddAggregateFunction(x => x.AvgValue, x => x.Value, EAggregateFunction.Avg); + entity.HasAnnotation( + ContinuousAggregateAnnotations.ViewDefinition, + "SELECT time_bucket('1 hour', \"timestamp\") AS time_bucket, AVG(\"value\") AS avg_value FROM \"raw_definition_raw\" GROUP BY 1"); + }); + } + } + + [Fact] + public void Should_Not_Warn_For_RawViewDefinition() + { + // Act + List warnings = RunValidationAndCapture(w => new RawDefinitionContext(w)); + + // Assert + Assert.Empty(warnings); + } + + #endregion + + #region Should_Not_Warn_For_Plain_Hypertable + + private class PlainHypertable + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class PlainHypertableContext(List warnings) : DbContext + { + public DbSet Metrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseLoggerFactory(LoggerFactory.Create(b => b.AddProvider(new CapturingLoggerProvider(warnings)))) + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.ToTable("plain_hypertable"); + entity.IsHypertable(x => x.Timestamp); + }); + } + } + + [Fact] + public void Should_Not_Warn_For_Plain_Hypertable() + { + // Act + List warnings = RunValidationAndCapture(w => new PlainHypertableContext(w)); + + // Assert + Assert.Empty(warnings); + } + + #endregion + + #region Should_Warn_Per_Offending_Aggregate + + private class MultiRaw + { + public DateTime Timestamp { get; set; } + public double Value { get; set; } + } + + private class MultiAggregateOne + { + public DateTime Bucket { get; set; } + public double AvgValue { get; set; } + } + + private class MultiAggregateTwo + { + public DateTime Bucket { get; set; } + public double MaxValue { get; set; } + } + + private class MultiContext(List warnings) : DbContext + { + public DbSet Metrics => Set(); + public DbSet HourlyMetrics => Set(); + public DbSet DailyMetrics => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql("Host=localhost;Database=test;Username=test;Password=test") + .UseLoggerFactory(LoggerFactory.Create(b => b.AddProvider(new CapturingLoggerProvider(warnings)))) + .UseTimescaleDb(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.ToTable("multi_raw"); + entity.IsHypertable(x => x.Timestamp); + }); + + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.Property(x => x.Bucket).HasColumnName("bucket"); + entity.IsContinuousAggregate( + "multi_hourly", + "1 hour", + x => x.Timestamp + ).AddAggregateFunction(x => x.AvgValue, x => x.Value, EAggregateFunction.Avg); + }); + + modelBuilder.Entity(entity => + { + entity.HasNoKey(); + entity.Property(x => x.Bucket).HasColumnName("bucket"); + entity.IsContinuousAggregate( + "multi_daily", + "1 day", + x => x.Timestamp + ).AddAggregateFunction(x => x.MaxValue, x => x.Value, EAggregateFunction.Max); + }); + } + } + + [Fact] + public void Should_Warn_Per_Offending_Aggregate() + { + // Act + List warnings = RunValidationAndCapture(w => new MultiContext(w)); + + // Assert + Assert.Equal(2, warnings.Count); + Assert.Contains(warnings, w => w.Contains("MultiAggregateOne", StringComparison.Ordinal)); + Assert.Contains(warnings, w => w.Contains("MultiAggregateTwo", StringComparison.Ordinal)); + } + + #endregion +}