From 84813879d7c07274f86564b8e39bc66068e478e6 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 14 Apr 2021 15:27:16 +0200 Subject: [PATCH] Add Redshift compatibility mode To not generate x = ANY(@array) construct. Also fix and cleanup various singleton options logic. Fixes #1794 --- .../Infrastructure/Internal/INpgsqlOptions.cs | 5 + .../Internal/NpgsqlOptionsExtension.cs | 79 +++++---- .../NpgsqlDbContextOptionsBuilder.cs | 8 + src/EFCore.PG/Internal/NpgsqlOptions.cs | 34 +++- .../Internal/NpgsqlArrayTranslator.cs | 37 ++--- .../NpgsqlMemberTranslatorProvider.cs | 6 +- .../NpgsqlMethodCallTranslatorProvider.cs | 2 +- .../Query/CompatibilityQueryNpgsqlTest.cs | 150 +++++++++--------- .../TestUtilities/NpgsqlTestHelpers.cs | 30 +--- 9 files changed, 196 insertions(+), 155 deletions(-) diff --git a/src/EFCore.PG/Infrastructure/Internal/INpgsqlOptions.cs b/src/EFCore.PG/Infrastructure/Internal/INpgsqlOptions.cs index 667825f62..605bddf38 100644 --- a/src/EFCore.PG/Infrastructure/Internal/INpgsqlOptions.cs +++ b/src/EFCore.PG/Infrastructure/Internal/INpgsqlOptions.cs @@ -14,6 +14,11 @@ public interface INpgsqlOptions : ISingletonOptions /// Version PostgresVersion { get; } + /// + /// Whether to target Redshift. + /// + bool UseRedshift { get; } + /// /// True if reverse null ordering is enabled; otherwise, false. /// diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index 5d48a7739..ec50268d7 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -16,6 +16,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal /// public class NpgsqlOptionsExtension : RelationalOptionsExtension { + public static readonly Version DefaultPostgresVersion = new(12, 0); + private DbContextOptionsExtensionInfo? _info; private readonly List _userRangeDefinitions; @@ -27,7 +29,12 @@ public class NpgsqlOptionsExtension : RelationalOptionsExtension /// /// The backend version to target. /// - public virtual Version? PostgresVersion { get; private set; } + public virtual Version PostgresVersion { get; private set; } = DefaultPostgresVersion; + + /// + /// Whether to target Redshift. + /// + public virtual bool UseRedshift { get; private set; } /// /// The list of range mappings specified by the user. @@ -132,7 +139,23 @@ public virtual NpgsqlOptionsExtension WithPostgresVersion(Version? postgresVersi { var clone = (NpgsqlOptionsExtension)Clone(); - clone.PostgresVersion = postgresVersion; + clone.PostgresVersion = postgresVersion ?? DefaultPostgresVersion; + + return clone; + } + + /// + /// Returns a copy of the current instance with the specified Redshift settings. + /// + /// Whether to target Redshift. + /// + /// A copy of the current instance with the specified Redshift setting. + /// + public virtual NpgsqlOptionsExtension WithRedshift(bool useRedshift) + { + var clone = (NpgsqlOptionsExtension)Clone(); + + clone.UseRedshift = useRedshift; return clone; } @@ -150,6 +173,17 @@ internal virtual NpgsqlOptionsExtension WithReverseNullOrdering(bool reverseNull return clone; } + /// + public override void Validate(IDbContextOptions options) + { + base.Validate(options); + + if (UseRedshift && !PostgresVersion.Equals(DefaultPostgresVersion)) + { + throw new InvalidOperationException($"{nameof(UseRedshift)} and {nameof(PostgresVersion)} cannot both be set"); + } + } + #region Authentication /// @@ -234,11 +268,16 @@ public override string LogFragment builder.Append(nameof(Extension.AdminDatabase)).Append("=").Append(Extension.AdminDatabase).Append(' '); } - if (Extension.PostgresVersion != null) + if (!Extension.PostgresVersion.Equals(DefaultPostgresVersion)) { builder.Append(nameof(Extension.PostgresVersion)).Append("=").Append(Extension.PostgresVersion).Append(' '); } + if (Extension.UseRedshift) + { + builder.Append(nameof(Extension.UseRedshift)).Append(' '); + } + if (Extension.ProvideClientCertificatesCallback != null) { builder.Append(nameof(Extension.ProvideClientCertificatesCallback)).Append(" "); @@ -296,6 +335,7 @@ public override long GetServiceProviderHashCode() (h, ud) => (h * 397) ^ ud.GetHashCode()); _serviceProviderHash = (_serviceProviderHash * 397) ^ Extension.AdminDatabase?.GetHashCode() ?? 0L; _serviceProviderHash = (_serviceProviderHash * 397) ^ (Extension.PostgresVersion?.GetHashCode() ?? 0L); + _serviceProviderHash = (_serviceProviderHash * 397) ^ Extension.UseRedshift.GetHashCode(); _serviceProviderHash = (_serviceProviderHash * 397) ^ (Extension.ProvideClientCertificatesCallback?.GetHashCode() ?? 0L); _serviceProviderHash = (_serviceProviderHash * 397) ^ (Extension.RemoteCertificateValidationCallback?.GetHashCode() ?? 0L); _serviceProviderHash = (_serviceProviderHash * 397) ^ (Extension.ProvidePasswordCallback?.GetHashCode() ?? 0L); @@ -315,6 +355,9 @@ public override void PopulateDebugInfo(IDictionary debugInfo) debugInfo["Npgsql.EntityFrameworkCore.PostgreSQL:" + nameof(NpgsqlDbContextOptionsBuilder.SetPostgresVersion)] = (Extension.PostgresVersion?.GetHashCode() ?? 0).ToString(CultureInfo.InvariantCulture); + debugInfo["Npgsql.EntityFrameworkCore.PostgreSQL:" + nameof(NpgsqlDbContextOptionsBuilder.UseRedshift)] + = Extension.UseRedshift.GetHashCode().ToString(CultureInfo.InvariantCulture); + debugInfo["Npgsql.EntityFrameworkCore.PostgreSQL:" + nameof(NpgsqlDbContextOptionsBuilder.ReverseNullOrdering)] = Extension.ReverseNullOrdering.GetHashCode().ToString(CultureInfo.InvariantCulture); @@ -338,7 +381,10 @@ public override void PopulateDebugInfo(IDictionary debugInfo) #endregion Infrastructure } - public class UserRangeDefinition : IEquatable + /// + /// A definition for a user-defined PostgreSQL range to be mapped. + /// + public record UserRangeDefinition { /// /// The name of the PostgreSQL range type to be mapped. @@ -374,30 +420,5 @@ public UserRangeDefinition( SubtypeClrType = Check.NotNull(subtypeClrType, nameof(subtypeClrType)); SubtypeName = subtypeName; } - - public override int GetHashCode() - => HashCode.Combine(RangeName, SchemaName, SubtypeClrType, SubtypeName); - - public override bool Equals(object? obj) => obj is UserRangeDefinition urd && Equals(urd); - - public virtual bool Equals(UserRangeDefinition? other) - => ReferenceEquals(this, other) || - !(other is null) && - RangeName == other.RangeName && - SchemaName == other.SchemaName && - SubtypeClrType == other.SubtypeClrType && - SubtypeName == other.SubtypeName; - - public virtual void Deconstruct( - out string rangeName, - out string? schemaName, - out Type subtypeClrType, - out string? subtypeName) - { - rangeName = RangeName; - schemaName = SchemaName; - subtypeClrType = SubtypeClrType; - subtypeName = SubtypeName; - } } } diff --git a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs index 35c4e33a2..512782756 100644 --- a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs +++ b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs @@ -44,6 +44,14 @@ public virtual NpgsqlDbContextOptionsBuilder SetPostgresVersion(Version? postgre public virtual NpgsqlDbContextOptionsBuilder SetPostgresVersion(int major, int minor) => SetPostgresVersion(new Version(major, minor)); + /// + /// Configures the provider to work in Redshift compatibility mode, which avoids certain unsupported features from modern + /// PostgreSQL versions. + /// + /// Whether to target Redshift. + public virtual NpgsqlDbContextOptionsBuilder UseRedshift(bool useRedshift = true) + => WithOption(e => e.WithRedshift(useRedshift)); + /// /// Maps a user-defined PostgreSQL range type for use. /// diff --git a/src/EFCore.PG/Internal/NpgsqlOptions.cs b/src/EFCore.PG/Internal/NpgsqlOptions.cs index d21777a8f..92323a212 100644 --- a/src/EFCore.PG/Internal/NpgsqlOptions.cs +++ b/src/EFCore.PG/Internal/NpgsqlOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -11,11 +12,12 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Internal /// public class NpgsqlOptions : INpgsqlOptions { - public static readonly Version DefaultPostgresVersion = new(12, 0); - /// public virtual Version PostgresVersion { get; private set; } = null!; + /// + public virtual bool UseRedshift { get; private set; } + /// public virtual bool ReverseNullOrderingEnabled { get; private set; } @@ -30,7 +32,8 @@ public virtual void Initialize(IDbContextOptions options) { var npgsqlOptions = options.FindExtension() ?? new NpgsqlOptionsExtension(); - PostgresVersion = npgsqlOptions.PostgresVersion ?? DefaultPostgresVersion; + PostgresVersion = npgsqlOptions.PostgresVersion; + UseRedshift = npgsqlOptions.UseRedshift; ReverseNullOrderingEnabled = npgsqlOptions.ReverseNullOrdering; UserRangeDefinitions = npgsqlOptions.UserRangeDefinitions; } @@ -40,6 +43,22 @@ public virtual void Validate(IDbContextOptions options) { var npgsqlOptions = options.FindExtension() ?? new NpgsqlOptionsExtension(); + if (!PostgresVersion.Equals(npgsqlOptions.PostgresVersion)) + { + throw new InvalidOperationException( + CoreStrings.SingletonOptionChanged( + nameof(NpgsqlDbContextOptionsBuilder.SetPostgresVersion), + nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); + } + + if (UseRedshift != npgsqlOptions.UseRedshift) + { + throw new InvalidOperationException( + CoreStrings.SingletonOptionChanged( + nameof(NpgsqlDbContextOptionsBuilder.UseRedshift), + nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); + } + if (ReverseNullOrderingEnabled != npgsqlOptions.ReverseNullOrdering) { throw new InvalidOperationException( @@ -47,6 +66,15 @@ public virtual void Validate(IDbContextOptions options) nameof(NpgsqlDbContextOptionsBuilder.ReverseNullOrdering), nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); } + + if (UserRangeDefinitions.Count != npgsqlOptions.UserRangeDefinitions.Count + || UserRangeDefinitions.Zip(npgsqlOptions.UserRangeDefinitions).Any(t => t.First != t.Second)) + { + throw new InvalidOperationException( + CoreStrings.SingletonOptionChanged( + nameof(NpgsqlDbContextOptionsBuilder.MapRange), + nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); + } } } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayTranslator.cs index 9f9ac21c7..2284c0473 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayTranslator.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore; @@ -35,18 +34,18 @@ public class NpgsqlArrayTranslator : IMethodCallTranslator, IMemberTranslator typeof(Enumerable).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) .Single(mi => mi.Name == nameof(Enumerable.Any) && mi.GetParameters().Length == 1); - private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; private readonly NpgsqlJsonPocoTranslator _jsonPocoTranslator; + private readonly bool _useRedshift; public NpgsqlArrayTranslator( - IRelationalTypeMappingSource typeMappingSource, NpgsqlSqlExpressionFactory sqlExpressionFactory, - NpgsqlJsonPocoTranslator jsonPocoTranslator) + NpgsqlJsonPocoTranslator jsonPocoTranslator, + bool useRedshift) { - _typeMappingSource = typeMappingSource; _sqlExpressionFactory = sqlExpressionFactory; _jsonPocoTranslator = jsonPocoTranslator; + _useRedshift = useRedshift; } public virtual SqlExpression? Translate( @@ -94,8 +93,8 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) if (method.IsClosedFormOf(EnumerableAnyWithoutPredicate)) { return _sqlExpressionFactory.GreaterThan( - _jsonPocoTranslator.TranslateArrayLength(arrayOrList) ?? - _sqlExpressionFactory.Function( + _jsonPocoTranslator.TranslateArrayLength(arrayOrList) + ?? _sqlExpressionFactory.Function( "cardinality", new[] { arrayOrList }, nullable: true, @@ -108,17 +107,18 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) // is pattern-matched in AllAnyToContainsRewritingExpressionVisitor, which transforms it to // new[] { "a", "b", "c" }.Contains(e.Some Text). - if ((method.IsClosedFormOf(EnumerableContains) || // Enumerable.Contains extension method - method.Name == nameof(List.Contains) && method.DeclaringType.IsGenericList() && - method.GetParameters().Length == 1) + if ((method.IsClosedFormOf(EnumerableContains) + || + method.Name == nameof(List.Contains) + && method.DeclaringType.IsGenericList() + && method.GetParameters().Length == 1) && - ( - // Handle either array columns (with an array mapping) or parameters/constants (no mapping). We specifically - // don't want to translate if the type mapping is bytea (CLR type is array, but not an array in - // the database). - // arrayOrList.TypeMapping == null && _typeMappingSource.FindMapping(arrayOrList.Type) != null || - arrayOrList.TypeMapping is NpgsqlArrayTypeMapping or null - )) + // Handle either array columns (with an array mapping) or parameters/constants (no mapping). We specifically + // don't want to translate if the type mapping is bytea (CLR type is array, but not an array in + // the database). + // arrayOrList.TypeMapping == null && _typeMappingSource.FindMapping(arrayOrList.Type) != null || + arrayOrList.TypeMapping is NpgsqlArrayTypeMapping or null + && !_useRedshift) { var item = arguments[0]; @@ -142,7 +142,8 @@ arrayOrList.TypeMapping is NpgsqlArrayTypeMapping or null typeof(int))); } - return _sqlExpressionFactory.Contains(arrayOrList, + return _sqlExpressionFactory.Contains( + arrayOrList, _sqlExpressionFactory.NewArrayOrConstant(new[] { item }, arrayOrList.Type)); // Don't do anything PG-specific for constant arrays since the general EF Core mechanism is fine diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs index 42c29f67d..7426c313d 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal { @@ -12,7 +13,8 @@ public class NpgsqlMemberTranslatorProvider : RelationalMemberTranslatorProvider public NpgsqlMemberTranslatorProvider( RelationalMemberTranslatorProviderDependencies dependencies, - IRelationalTypeMappingSource typeMappingSource) + IRelationalTypeMappingSource typeMappingSource, + INpgsqlOptions npgsqlOptions) : base(dependencies) { var sqlExpressionFactory = (NpgsqlSqlExpressionFactory)dependencies.SqlExpressionFactory; @@ -20,7 +22,7 @@ public NpgsqlMemberTranslatorProvider( AddTranslators( new IMemberTranslator[] { - new NpgsqlArrayTranslator(typeMappingSource, sqlExpressionFactory, JsonPocoTranslator), + new NpgsqlArrayTranslator(sqlExpressionFactory, JsonPocoTranslator, npgsqlOptions.UseRedshift), new NpgsqlDateTimeMemberTranslator(sqlExpressionFactory), new NpgsqlJsonDomTranslator(typeMappingSource, sqlExpressionFactory), new NpgsqlLTreeTranslator(typeMappingSource, sqlExpressionFactory), diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs index 6d2d0dbed..42b9f7803 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs @@ -22,7 +22,7 @@ public NpgsqlMethodCallTranslatorProvider( AddTranslators(new IMethodCallTranslator[] { - new NpgsqlArrayTranslator(typeMappingSource, npgsqlSqlExpressionFactory, jsonTranslator), + new NpgsqlArrayTranslator(npgsqlSqlExpressionFactory, jsonTranslator, npgsqlOptions.UseRedshift), new NpgsqlByteArrayMethodTranslator(npgsqlSqlExpressionFactory), new NpgsqlConvertTranslator(npgsqlSqlExpressionFactory), new NpgsqlDateTimeMethodTranslator(typeMappingSource, npgsqlSqlExpressionFactory), diff --git a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs index 24c377079..c86af0e65 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs @@ -1,7 +1,9 @@ using System; using System.Linq; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; using Xunit; @@ -18,103 +20,103 @@ public CompatibilityQueryNpgsqlTest(CompatibilityQueryNpgsqlFixture fixture, ITe { Fixture = fixture; Fixture.TestSqlLoggerFactory.Clear(); - //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - // CURRENTLY EMPTY - - private CompatibilityContext CreateContext(Version postgresVersion = null) + [ConditionalFact] + public async Task Array_contains_is_not_parameterized_with_array_on_redshift() { - var builder = new DbContextOptionsBuilder(Fixture.CreateOptions()); - if (postgresVersion != null) - new NpgsqlDbContextOptionsBuilder(builder).SetPostgresVersion(postgresVersion); - return new CompatibilityContext(builder.Options); + var ctx = CreateRedshiftContext(); + + var numbers = new[] { 8, 9 }; + var result = await ctx.TestEntities.Where(e => numbers.Contains(e.SomeInt)).SingleAsync(); + Assert.Equal(1, result.Id); + + AssertSql( + @"SELECT t.""Id"", t.""SomeInt"" +FROM ""TestEntities"" AS t +WHERE t.""SomeInt"" IN (8, 9) +LIMIT 2"); } - #region Fixtures + #region Support - /// - /// Represents a fixture suitable for testing backendVersion. - /// - public class CompatibilityQueryNpgsqlFixture : SharedStoreFixtureBase + private CompatibilityContext CreateContext(Version postgresVersion = null) + => Fixture.CreateContext(postgresVersion); + + private CompatibilityContext CreateRedshiftContext() + => Fixture.CreateRedshiftContext(); + + public class CompatibilityQueryNpgsqlFixture : FixtureBase, IDisposable, IAsyncLifetime { - protected override string StoreName => "CompatibilityTest"; - protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; - public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(CompatibilityContext context) => CompatibilityContext.Seed(context); + private TestStore _testStore; + + private const string StoreName = "CompatibilityTest"; + private readonly ListLoggerFactory _listLoggerFactory = NpgsqlTestStoreFactory.Instance.CreateListLoggerFactory(_ => false); + public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)_listLoggerFactory; + + public virtual CompatibilityContext CreateContext() + => CreateContext(null); + + public virtual CompatibilityContext CreateContext(Version postgresVersion) + { + var builder = new DbContextOptionsBuilder(); + _testStore.AddProviderOptions(builder); + builder + .UseNpgsql(o => o.SetPostgresVersion(postgresVersion)) + .UseLoggerFactory(_listLoggerFactory); + return new CompatibilityContext(builder.Options); + } + + public virtual CompatibilityContext CreateRedshiftContext() + { + var builder = new DbContextOptionsBuilder(); + _testStore.AddProviderOptions(builder); + builder + .UseNpgsql(o => o.UseRedshift()) + .UseLoggerFactory(_listLoggerFactory); + return new CompatibilityContext(builder.Options); + } + + public virtual Task InitializeAsync() + { + _testStore = NpgsqlTestStoreFactory.Instance.GetOrCreate(StoreName); + _testStore.Initialize(null, CreateContext, c => CompatibilityContext.Seed((CompatibilityContext)c)); + return Task.CompletedTask; + } + + // Called after DisposeAsync + public virtual void Dispose() + { + } + + public virtual Task DisposeAsync() + => _testStore.DisposeAsync(); } - /// - /// Represents an entity suitable for testing backendVersion. - /// public class CompatibilityTestEntity { - /// - /// The primary key. - /// public int Id { get; set; } - - /// - /// The date and time. - /// - public DateTime DateTime { get; set; } + public int SomeInt { get; set; } } - /// - /// Represents a database suitable for testing range operators. - /// - public class CompatibilityContext : PoolableDbContext + public class CompatibilityContext : DbContext { - /// - /// Represents a set of entities for backendVersion testing. - /// - public DbSet CompatibilityTestEntities { get; set; } - - /// + public DbSet TestEntities { get; set; } public CompatibilityContext(DbContextOptions options) : base(options) {} - /// - protected override void OnModelCreating(ModelBuilder builder) {} - public static void Seed(CompatibilityContext context) { - context.CompatibilityTestEntities.AddRange( - new CompatibilityTestEntity - { - Id = 1, - DateTime = new DateTime(2018, 06, 23) - }, - new CompatibilityTestEntity - { - Id = 2, - DateTime = new DateTime(2018, 06, 23) - }, - new CompatibilityTestEntity - { - Id = 3, - DateTime = new DateTime(2018, 06, 23) - }); - + context.TestEntities.AddRange( + new CompatibilityTestEntity { Id = 1, SomeInt = 8 }, + new CompatibilityTestEntity { Id = 2, SomeInt = 10 }); context.SaveChanges(); } } - #endregion - - #region Helpers - - /// - /// Asserts that the SQL fragment appears in the logs. - /// - /// The SQL statement or fragment to search for in the logs. - private void AssertContainsSql(string sql) => Assert.Contains(sql, Fixture.TestSqlLoggerFactory.Sql); - - /// - /// Asserts that the SQL fragment does not appears in the logs. - /// - /// The SQL statement or fragment to search for in the logs. - private void AssertDoesNotContainsSql(string sql) => Assert.DoesNotContain(sql, Fixture.TestSqlLoggerFactory.Sql); + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - #endregion + #endregion Support } } diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestHelpers.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestHelpers.cs index a76638ce7..57fbd9970 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestHelpers.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestHelpers.cs @@ -1,38 +1,13 @@ -using System; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Npgsql.EntityFrameworkCore.PostgreSQL.Diagnostics.Internal; -using Xunit; namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities { public class NpgsqlTestHelpers : TestHelpers { - private Version _postgresVersion; - - public class VersionScope : IDisposable - { - private readonly NpgsqlTestHelpers _helpers; - private readonly Version _oldVersion, _newVersion; - - internal VersionScope(NpgsqlTestHelpers helpers, Version version) - { - _helpers = helpers; - _oldVersion = helpers._postgresVersion; - _newVersion = helpers._postgresVersion = version; - } - - public void Dispose() - { - Assert.Equal(_helpers._postgresVersion, _newVersion); - _helpers._postgresVersion = _oldVersion; - } - } - - public VersionScope WithPostgresVersion(Version version) => new(this, version); - protected NpgsqlTestHelpers() {} public static NpgsqlTestHelpers Instance { get; } = new(); @@ -41,8 +16,7 @@ public override IServiceCollection AddProviderServices(IServiceCollection servic => services.AddEntityFrameworkNpgsql(); public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseNpgsql(new NpgsqlConnection("Host=localhost;Database=DummyDatabase"), - options => options.SetPostgresVersion(_postgresVersion)); + => optionsBuilder.UseNpgsql(new NpgsqlConnection("Host=localhost;Database=DummyDatabase")); public override LoggingDefinitions LoggingDefinitions { get; } = new NpgsqlLoggingDefinitions(); }