diff --git a/Directory.Build.props b/Directory.Build.props index 59efc0cbe..64a9c63aa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0-preview.4 + 9.0.0-preview.7 latest true latest diff --git a/Directory.Packages.props b/Directory.Packages.props index 11a01c9ad..719cf3228 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - 9.0.0-preview.3.24172.4 - 9.0.0-preview.3.24172.9 + [9.0.0-preview.7.24405.3] + 9.0.0-preview.7.24405.7 8.0.3 @@ -21,14 +21,15 @@ + + + - - - - - - + + + + diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs index 1ab9fd45b..0bbeb6e0c 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs @@ -172,7 +172,7 @@ public NpgsqlGeometryMemberTranslator( _ => null }; - SqlFunctionExpression Function(string name, SqlExpression[] arguments, Type returnType, RelationalTypeMapping? typeMapping = null) + SqlExpression Function(string name, SqlExpression[] arguments, Type returnType, RelationalTypeMapping? typeMapping = null) => _sqlExpressionFactory.Function( name, arguments, nullable: true, argumentsPropagateNullability: TrueArrays[arguments.Length], diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs index 25e0b1f34..0b5afb739 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs @@ -220,7 +220,7 @@ public NpgsqlGeometryMethodTranslator( _ => null }; - SqlFunctionExpression Function(string name, SqlExpression[] arguments, Type returnType, RelationalTypeMapping? typeMapping = null) + SqlExpression Function(string name, SqlExpression[] arguments, Type returnType, RelationalTypeMapping? typeMapping = null) => _sqlExpressionFactory.Function( name, arguments, nullable: true, argumentsPropagateNullability: TrueArrays[arguments.Length], diff --git a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs index 266f14da2..3b5920e27 100644 --- a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs +++ b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs @@ -10,6 +10,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; /// public sealed class NpgsqlJsonGeometryWktReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonGeometryWktReaderWriter).GetProperty(nameof(Instance))!; + private static readonly WKTReader WktReader = new(); /// @@ -28,4 +30,7 @@ public override Geometry FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, Geometry value) => writer.WriteStringValue(value.ToText()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index f08aa47f6..e96117583 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -175,7 +175,7 @@ public NpgsqlNodaTimeMemberTranslator( _ => null, }; - SqlBinaryExpression TranslateDurationTotalMember(SqlExpression instance, double divisor) + SqlExpression TranslateDurationTotalMember(SqlExpression instance, double divisor) => _sqlExpressionFactory.Divide(GetDatePartExpressionDouble(instance, "epoch"), _sqlExpressionFactory.Constant(divisor)); } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs index 16cca6b5e..eea1ef0a5 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs @@ -116,6 +116,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonLocalDateReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLocalDateReaderWriter).GetProperty(nameof(Instance))!; + public static JsonLocalDateReaderWriter Instance { get; } = new(); public override LocalDate FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -138,5 +140,8 @@ public override LocalDate FromJsonTyped(ref Utf8JsonReaderManager manager, objec public override void ToJsonTyped(Utf8JsonWriter writer, LocalDate value) => writer.WriteStringValue(FormatLocalDate(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs index 27a2c2038..bb44e93bb 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs @@ -137,6 +137,8 @@ void Compose(Expression toAdd) private sealed class JsonDurationReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonDurationReaderWriter).GetProperty(nameof(Instance))!; + public static JsonDurationReaderWriter Instance { get; } = new(); public override Duration FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -144,5 +146,8 @@ public override Duration FromJsonTyped(ref Utf8JsonReaderManager manager, object public override void ToJsonTyped(Utf8JsonWriter writer, Duration value) => writer.WriteStringValue(NpgsqlIntervalTypeMapping.FormatTimeSpanAsInterval(value.ToTimeSpan())); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs index b46009434..bd001d58a 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs @@ -163,6 +163,8 @@ void Compose(Expression toAdd) private sealed class JsonPeriodReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonPeriodReaderWriter).GetProperty(nameof(Instance))!; + public static JsonPeriodReaderWriter Instance { get; } = new(); public override Period FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -170,5 +172,8 @@ public override Period FromJsonTyped(ref Utf8JsonReaderManager manager, object? public override void ToJsonTyped(Utf8JsonWriter writer, Period value) => writer.WriteStringValue(PeriodPattern.NormalizingIso.Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs index a9d25f72b..64c8f4c76 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs @@ -119,6 +119,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonLocalTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLocalTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonLocalTimeReaderWriter Instance { get; } = new(); public override LocalTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -126,5 +128,8 @@ public override LocalTime FromJsonTyped(ref Utf8JsonReaderManager manager, objec public override void ToJsonTyped(Utf8JsonWriter writer, LocalTime value) => writer.WriteStringValue(LocalTimePattern.ExtendedIso.Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs index 07ea8ac3a..4cf64a6d8 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs @@ -150,6 +150,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonOffsetTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonOffsetTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonOffsetTimeReaderWriter Instance { get; } = new(); public override OffsetTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -157,5 +159,8 @@ public override OffsetTime FromJsonTyped(ref Utf8JsonReaderManager manager, obje public override void ToJsonTyped(Utf8JsonWriter writer, OffsetTime value) => writer.WriteStringValue(Pattern.Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs index 6c85d56da..d9d069e0a 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs @@ -143,6 +143,8 @@ internal static Expression GenerateCodeLiteral(LocalDateTime dateTime) private sealed class JsonLocalDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLocalDateTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonLocalDateTimeReaderWriter Instance { get; } = new(); public override LocalDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -165,5 +167,8 @@ public override LocalDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, o public override void ToJsonTyped(Utf8JsonWriter writer, LocalDateTime value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs index ddeac0787..52da44391 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs @@ -125,6 +125,8 @@ private static readonly MethodInfo _fromUnixTimeTicks private sealed class JsonInstantReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonInstantReaderWriter).GetProperty(nameof(Instance))!; + public static JsonInstantReaderWriter Instance { get; } = new(); public override Instant FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -147,5 +149,8 @@ public override Instant FromJsonTyped(ref Utf8JsonReaderManager manager, object? public override void ToJsonTyped(Utf8JsonWriter writer, Instant value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs index 31d8d25fd..3fba21afe 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs @@ -123,6 +123,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonOffsetDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonOffsetDateTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonOffsetDateTimeReaderWriter Instance { get; } = new(); public override OffsetDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -130,5 +132,8 @@ public override OffsetDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, public override void ToJsonTyped(Utf8JsonWriter writer, OffsetDateTime value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs index d6e637156..03456b358 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs @@ -129,6 +129,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonZonedDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonZonedDateTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonZonedDateTimeReaderWriter Instance { get; } = new(); public override ZonedDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -136,5 +138,8 @@ public override ZonedDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, o public override void ToJsonTyped(Utf8JsonWriter writer, ZonedDateTime value) => writer.WriteStringValue(Pattern.Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/EFCore.PG.csproj b/src/EFCore.PG/EFCore.PG.csproj index 030379ded..c344ed2e2 100644 --- a/src/EFCore.PG/EFCore.PG.csproj +++ b/src/EFCore.PG/EFCore.PG.csproj @@ -9,7 +9,7 @@ PostgreSQL/Npgsql provider for Entity Framework Core. npgsql;postgresql;postgres;Entity Framework Core;entity-framework-core;ef;efcore;orm;sql README.md - EF1003 + EF9100 diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index 9cf6a2ed0..beaf86d7c 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -19,6 +19,81 @@ public NpgsqlHistoryRepository(HistoryRepositoryDependencies dependencies) { } + // TODO: We override Exists() as a workaround for https://github.com/dotnet/efcore/issues/34569; this should be fixed on the EF side + // before EF 9.0 is released + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool Exists() + => Dependencies.DatabaseCreator.Exists() + && InterpretExistsResult( + Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalar( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + null, + Dependencies.CurrentContext.Context, + Dependencies.CommandLogger, CommandSource.Migrations))); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override async Task ExistsAsync(CancellationToken cancellationToken = default) + => await Dependencies.DatabaseCreator.ExistsAsync(cancellationToken).ConfigureAwait(false) + && InterpretExistsResult( + await Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalarAsync( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + null, + Dependencies.CurrentContext.Context, + Dependencies.CommandLogger, CommandSource.Migrations), + cancellationToken).ConfigureAwait(false)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override IDisposable GetDatabaseLock(TimeSpan timeout) + { + // TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a + // transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. + + // Dependencies.RawSqlCommandBuilder + // .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") + // .ExecuteNonQuery(CreateRelationalCommandParameters()); + + return new DummyDisposable(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override Task GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default) + { + // TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a + // transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. + + // await Dependencies.RawSqlCommandBuilder + // .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") + // .ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken) + // .ConfigureAwait(false); + + return Task.FromResult(new DummyDisposable()); + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -83,10 +158,11 @@ public override string GetBeginIfNotExistsScript(string migrationId) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override string GetBeginIfExistsScript(string migrationId) - => $@" + => $""" DO $EF$ BEGIN - IF EXISTS(SELECT 1 FROM {SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} WHERE ""{MigrationIdColumnName}"" = '{migrationId}') THEN"; + IF EXISTS(SELECT 1 FROM {SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} WHERE "{MigrationIdColumnName}" = '{migrationId}') THEN +"""; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -95,6 +171,26 @@ public override string GetBeginIfExistsScript(string migrationId) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override string GetEndIfScript() - => @" END IF; -END $EF$;"; + => """ + END IF; +END $EF$; +"""; + + private RelationalCommandParameterObject CreateRelationalCommandParameters() + => new( + Dependencies.Connection, + null, + null, + Dependencies.CurrentContext.Context, + Dependencies.CommandLogger, CommandSource.Migrations); + + private sealed class DummyDisposable : IDisposable, IAsyncDisposable + { + public void Dispose() + { + } + + public ValueTask DisposeAsync() + => default; + } } diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index f2f1e6405..0d0969390 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -827,25 +827,6 @@ protected override void SequenceOptions( } builder.Append(operation.IsCyclic ? " CYCLE" : " NO CYCLE"); - - if (!operation.IsCached) - { - // The base implementation appends NO CACHE, which isn't supported by PG - builder - .Append(" CACHE 1"); - } - else if (operation.CacheSize != null) - { - builder - .Append(" CACHE ") - .Append(intTypeMapping.GenerateSqlLiteral(operation.CacheSize.Value)); - } - else if (forAlter) - { - // The base implementation just appends CACHE, which isn't supported by PG - builder - .Append(" CACHE 1"); - } } /// @@ -964,7 +945,7 @@ protected override void Generate( } /// - protected override void IndexOptions(CreateIndexOperation operation, IModel? model, MigrationCommandListBuilder builder) + protected override void IndexOptions(MigrationOperation operation, IModel? model, MigrationCommandListBuilder builder) { if (_postgresVersion.AtLeast(11) && operation[NpgsqlAnnotationNames.IndexInclude] is string[] { Length: > 0 } includeColumns) { diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs index 5ad41bcc8..84c27eb54 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs @@ -27,7 +27,8 @@ public class NpgsqlConvertTranslator : IMethodCallTranslator typeof(int), typeof(long), typeof(short), - typeof(string) + typeof(string), + typeof(object) ]; private static readonly List SupportedMethods diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs index d3e1cc758..7dc27b21c 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs @@ -148,7 +148,7 @@ public NpgsqlFullTextSearchMethodTranslator( "setweight", newArgs, nullable: true, - argumentsPropagateNullability: TrueArrays[2], + argumentsPropagateNullability: TrueArrays[newArgs.Count], method.ReturnType); } @@ -321,7 +321,7 @@ arguments[1] is SqlConstantExpression constant arguments[2] }, nullable: true, - argumentsPropagateNullability: TrueArrays[arguments.Count], + argumentsPropagateNullability: TrueArrays[2], method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model)); @@ -338,7 +338,7 @@ arguments[1] is SqlConstantExpression constant arguments[2] }, nullable: true, - argumentsPropagateNullability: TrueArrays[arguments.Count], + argumentsPropagateNullability: TrueArrays[2], method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model)); @@ -347,7 +347,7 @@ SqlExpression NonConfigAccepting(string functionName) functionName, new[] { arguments[1] }, nullable: true, - argumentsPropagateNullability: TrueArrays[arguments.Count], + argumentsPropagateNullability: TrueArrays[1], method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model)); } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs index 0b4fe3015..3a8118599 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs @@ -134,7 +134,7 @@ PgJsonTraversalExpression prevPathTraversal mapping.IsJsonb ? "jsonb_array_length" : "json_array_length", new[] { expression }, nullable: true, - argumentsPropagateNullability: TrueArrays[2], + argumentsPropagateNullability: TrueArrays[1], typeof(int)); case PgJsonTraversalExpression traversal: @@ -152,7 +152,7 @@ PgJsonTraversalExpression prevPathTraversal jsonMapping.IsJsonb ? "jsonb_array_length" : "json_array_length", new[] { newTraversal }, nullable: true, - argumentsPropagateNullability: TrueArrays[2], + argumentsPropagateNullability: TrueArrays[1], typeof(int)); default: diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs index 380463f6f..9568ba113 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs @@ -192,7 +192,7 @@ public NpgsqlMathTranslator( "trunc", new[] { argument }, nullable: true, - argumentsPropagateNullability: new[] { true, false, false }, + argumentsPropagateNullability: TrueArrays[1], argument.Type == typeof(float) ? typeof(double) : argument.Type); if (argument.Type == typeof(float)) @@ -213,7 +213,7 @@ public NpgsqlMathTranslator( "round", new[] { argument }, nullable: true, - argumentsPropagateNullability: new[] { true, true }, + argumentsPropagateNullability: TrueArrays[1], argument.Type == typeof(float) ? typeof(double) : argument.Type); if (argument.Type == typeof(float)) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs index 488994c93..1b972d54f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs @@ -78,7 +78,9 @@ public NpgsqlMiscAggregateMethodTranslator( }, source, nullable: true, - argumentsPropagateNullability: new[] { false, true }, + // string_agg can return nulls regardless of the nullability of its arguments, since if there's an aggregate predicate + // (string_agg(...) WHERE ...), it could cause there to be no elements, in which case string_agg returns null. + argumentsPropagateNullability: FalseArrays[2], typeof(string), _typeMappingSource.FindMapping("text")), // Note that string_agg returns text even if its inputs are varchar(x) _sqlExpressionFactory.Constant(string.Empty, typeof(string))); diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs index cb181134f..3f607894f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs @@ -248,7 +248,7 @@ public NpgsqlNetworkTranslator( _ => null }; - private SqlFunctionExpression NullPropagatingFunction( + private SqlExpression NullPropagatingFunction( string name, SqlExpression[] arguments, Type returnType, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs index 377939774..eddefd322 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs @@ -67,31 +67,40 @@ public NpgsqlObjectToStringTranslator(IRelationalTypeMappingSource typeMappingSo if (instance.Type == typeof(bool)) { - return instance is ColumnExpression { IsNullable: true } - ? _sqlExpressionFactory.Case( + if (instance.Type == typeof(bool)) + { + if (instance is not ColumnExpression { IsNullable: false }) + { + return _sqlExpressionFactory.Case( + instance, + new[] + { + new CaseWhenClause( + _sqlExpressionFactory.Constant(false), + _sqlExpressionFactory.Constant(false.ToString())), + new CaseWhenClause( + _sqlExpressionFactory.Constant(true), + _sqlExpressionFactory.Constant(true.ToString())) + }, + _sqlExpressionFactory.Constant(string.Empty)); + } + + return _sqlExpressionFactory.Case( new[] { new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)), - _sqlExpressionFactory.Constant(false.ToString())), - new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)), + instance, _sqlExpressionFactory.Constant(true.ToString())) }, - _sqlExpressionFactory.Constant(null, typeof(string))) - : _sqlExpressionFactory.Case( - new[] - { - new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)), - _sqlExpressionFactory.Constant(false.ToString())) - }, - _sqlExpressionFactory.Constant(true.ToString())); + _sqlExpressionFactory.Constant(false.ToString())); + } } return _typeMapping.Contains(instance.Type) || instance.Type.UnwrapNullableType().IsEnum && instance.TypeMapping is NpgsqlEnumTypeMapping - ? _sqlExpressionFactory.Convert(instance, typeof(string), _textTypeMapping) + ? _sqlExpressionFactory.Coalesce( + _sqlExpressionFactory.Convert(instance, typeof(string), _textTypeMapping), + _sqlExpressionFactory.Constant(string.Empty)) : null; } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs index 4a8cae301..9e0bb9113 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs @@ -171,7 +171,7 @@ public NpgsqlRangeTranslator( _ => null }; - SqlFunctionExpression SingleArgBoolFunction(string name, SqlExpression argument) + SqlExpression SingleArgBoolFunction(string name, SqlExpression argument) => _sqlExpressionFactory.Function( name, new[] { argument }, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs index 90efa7cb4..d56c7a9d3 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs @@ -31,11 +31,11 @@ public NpgsqlStringMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) MemberInfo member, Type returnType, IDiagnosticsLogger logger) - => member.Name == nameof(string.Length) && instance?.Type == typeof(string) + => member.Name == nameof(string.Length) && member.DeclaringType == typeof(string) ? _sqlExpressionFactory.Convert( _sqlExpressionFactory.Function( "length", - new[] { instance }, + new[] { instance! }, nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(long)), diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs index 27a02bb17..dbd1c13be 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs @@ -16,7 +16,7 @@ public class NpgsqlStringMethodTranslator : IMethodCallTranslator { private readonly ISqlExpressionFactory _sqlExpressionFactory; private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly SqlConstantExpression _whitespace; + private readonly SqlExpression _whitespace; #region MethodInfo diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs index 6efe34dae..b4b59966b 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs @@ -74,14 +74,14 @@ SqlExpression Floor(SqlExpression value) typeof(double)), typeof(int)); - SqlFunctionExpression DatePart(string part, SqlExpression value) + SqlExpression DatePart(string part, SqlExpression value) => _sqlExpressionFactory.Function( "date_part", new[] { _sqlExpressionFactory.Constant(part), value }, nullable: true, argumentsPropagateNullability: FalseTrueArray, returnType); - SqlBinaryExpression TranslateDurationTotalMember(SqlExpression instance, double divisor) + SqlExpression TranslateDurationTotalMember(SqlExpression instance, double divisor) => _sqlExpressionFactory.Divide(DatePart("epoch", instance), _sqlExpressionFactory.Constant(divisor)); } } diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs index 9c6d56fbe..c8a749402 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs @@ -80,8 +80,8 @@ public override Expression Quote() _quotingConstructor ??= typeof(PgArraySliceExpression).GetConstructor( [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(bool), typeof(Type), typeof(RelationalTypeMapping)])!, Array.Quote(), - RelationalExpressionQuotingUtilities.VisitOrNull(LowerBound), - RelationalExpressionQuotingUtilities.VisitOrNull(UpperBound), + RelationalExpressionQuotingUtilities.QuoteOrNull(LowerBound), + RelationalExpressionQuotingUtilities.QuoteOrNull(UpperBound), Constant(IsNullable), Constant(Type), RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs index 7e4601d3a..8b98e44e9 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs @@ -6,7 +6,6 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// Represents a SQL function call expression, supporting PostgreSQL's named parameter notation /// (e.g. make_interval(weeks => 2) and non-comma parameter separators (e.g. position(substring in string)). /// -[DebuggerDisplay("{" + nameof(ToString) + "()}")] public class PgFunctionExpression : SqlFunctionExpression, IEquatable { /// diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs index 2304618ec..9b816886d 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs @@ -69,7 +69,7 @@ public override Expression Quote() [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(RelationalTypeMapping)])!, Match.Quote(), Pattern.Quote(), - RelationalExpressionQuotingUtilities.VisitOrNull(EscapeChar), + RelationalExpressionQuotingUtilities.QuoteOrNull(EscapeChar), RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); /// diff --git a/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs index c2712d10b..bba009ead 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs @@ -41,7 +41,7 @@ protected virtual Expression VisitDelete(DeleteExpression deleteExpression) { throw new InvalidOperationException( RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration( - nameof(RelationalQueryableExtensions.ExecuteDelete))); + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))); } var fromItems = new List(); @@ -79,7 +79,7 @@ protected virtual Expression VisitDelete(DeleteExpression deleteExpression) default: throw new InvalidOperationException( RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration( - nameof(RelationalQueryableExtensions.ExecuteDelete))); + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))); } } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 5eba3759a..e81ebcf0a 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.NetworkInformation; +using System.Text.Json; using System.Text.RegularExpressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; @@ -131,6 +132,12 @@ ExpressionType.Add when ExpressionType.And when e.Type == typeof(bool) => " AND ", ExpressionType.Or when e.Type == typeof(bool) => " OR ", + + // In most databases/languages, the caret (^) is the bitwise XOR operator. But in PostgreSQL the caret is the exponentiation + // operator, and hash (#) is used instead. + ExpressionType.ExclusiveOr when e.Type == typeof(bool) => " <> ", + ExpressionType.ExclusiveOr => " # ", + _ => base.GetOperator(e) }; @@ -406,7 +413,7 @@ void LiftPredicate(TableExpressionBase joinTable) } throw new InvalidOperationException( - RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteUpdate))); + RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate))); } /// @@ -1122,7 +1129,8 @@ private void GenerateJsonPath(SqlExpression expression, bool returnsText, IReadO Sql.Append(returnsText ? " #>> " : " #> "); // Use simplified array literal syntax if all path components are constants for cleaner SQL - if (path.All(p => p is SqlConstantExpression)) + if (path.All(p => p is SqlConstantExpression { Value: var pathSegment } + && (pathSegment is not string s || s.All(char.IsAsciiLetterOrDigit)))) { Sql .Append("'{") diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs index 1b7e53079..9b89c039c 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs @@ -112,8 +112,7 @@ private Expression VisitSelect(SelectExpression selectExpression) _state = parentState == State.InNestedSetOperation ? State.AlreadyCompensated : parentState; return changed - ? selectExpression.Update( - projections, tables, predicate, groupBy, havingExpression, orderings, limit, offset) + ? selectExpression.Update(tables, predicate, groupBy, havingExpression, projections, orderings, offset, limit) : selectExpression; } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs index c309220f8..09644eb44 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs @@ -29,144 +29,170 @@ protected override SqlExpression VisitSqlBinary( bool allowOptimizedExpansion, out bool nullable) { - if (sqlBinaryExpression is not + return sqlBinaryExpression switch + { { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual, Left: PgRowValueExpression leftRowValue, Right: PgRowValueExpression rightRowValue - }) + } + => VisitRowValueComparison(sqlBinaryExpression.OperatorType, leftRowValue, rightRowValue, out nullable), + + _ => base.VisitSqlBinary(sqlBinaryExpression, allowOptimizedExpansion, out nullable) + }; + + SqlExpression VisitRowValueComparison( + ExpressionType operatorType, + PgRowValueExpression leftRowValue, + PgRowValueExpression rightRowValue, + out bool nullable) { - return base.VisitSqlBinary(sqlBinaryExpression, allowOptimizedExpansion, out nullable); - } + // Equality checks between row values require some special null semantics compensation. + // Row value equality/inequality works the same as regular equals/non-equals; this means that it's fine as long as we're + // comparing non-nullable values (no need to compensate), but for nullable values, we need to compensate. We go over the value + // pairs, and extract out pairs that require compensation to an expanded, non-value-tuple expression (regular equality null + // semantics). Note that PostgreSQL does have DISTINCT FROM/NOT DISTINCT FROM which would have been perfect here, but those + // still don't use indexes. + // Note that we don't do compensation for comparisons (e.g. greater than) since these are expressed via EF.Functions, which + // correspond directly to SQL constructs. + // The PG docs around this are in https://www.postgresql.org/docs/current/functions-comparisons.html#ROW-WISE-COMPARISON + + Check.DebugAssert(leftRowValue.Values.Count == rightRowValue.Values.Count, "left.Values.Count == right.Values.Count"); + var count = leftRowValue.Values.Count; + + SqlExpression? expandedExpression = null; + List? visitedLeftValues = null; + List? visitedRightValues = null; - // Equality checks between row values require some special null semantics compensation. - // Row value equality/inequality works the same as regular equals/non-equals; this means that it's fine as long as we're comparing - // non-nullable values (no need to compensate), but for nullable values, we need to compensate. We go over the value pairs, and - // extract out pairs that require compensation to an expanded, non-value-tuple expression (regular equality null semantics). - // Note that PostgreSQL does have DISTINCT FROM/NOT DISTINCT FROM which would have been perfect here, but those still don't use - // indexes. - // Note that we don't do compensation for comparisons (e.g. greater than) since these are expressed via EF.Functions, which - // correspond directly to SQL constructs. - // The PG docs around this are in https://www.postgresql.org/docs/current/functions-comparisons.html#ROW-WISE-COMPARISON + for (var i = 0; i < count; i++) + { + // Visit the left value, populating visitedLeftValues only if we haven't yet switched to an expanded expression, and only if + // the visitation actually changed something + var leftValue = leftRowValue.Values[i]; + var rightValue = rightRowValue.Values[i]; + var visitedLeftValue = Visit(leftRowValue.Values[i], out var leftNullable); + var visitedRightValue = Visit(rightRowValue.Values[i], out var rightNullable); + + // If both sides are non-nullable, no null expansion is required; the same is true if we're doing equality in optimized + // mode, and only one side is nullable (WHERE (NonNullable1, NonNullable2) = (NonNullable3, Nullable4)). + if (!leftNullable && !rightNullable + || allowOptimizedExpansion && operatorType is ExpressionType.Equal && (!leftNullable || !rightNullable)) + { + // The comparison for this value pair doesn't require expansion and can remain in the row value (so continue below). + // But if the visitation above changed a value, construct a list to hold the visited values. + if (visitedLeftValue != leftValue && visitedLeftValues is null) + { + visitedLeftValues = SliceToList(leftRowValue.Values, count, i); + } - Check.DebugAssert(leftRowValue.Values.Count == rightRowValue.Values.Count, "left.Values.Count == right.Values.Count"); - var count = leftRowValue.Values.Count; + visitedLeftValues?.Add(visitedLeftValue); - var operatorType = sqlBinaryExpression.OperatorType; + if (visitedRightValue != rightValue && visitedRightValues is null) + { + visitedRightValues = SliceToList(rightRowValue.Values, count, i); + } - SqlExpression? expandedExpression = null; - List? visitedLeftValues = null; - List? visitedRightValues = null; + visitedRightValues?.Add(visitedRightValue); - for (var i = 0; i < count; i++) - { - // Visit the left value, populating visitedLeftValues only if we haven't yet switched to an expanded expression, and only if - // the visitation actually changed something, and - var leftValue = leftRowValue.Values[i]; - var rightValue = rightRowValue.Values[i]; - var visitedRightValue = Visit(rightRowValue.Values[i], out var rightNullable); - var visitedLeftValue = Visit(leftRowValue.Values[i], out var leftNullable); - - if (!leftNullable && !rightNullable - || allowOptimizedExpansion && (!leftNullable || !rightNullable)) - { - // The comparison for this value pair doesn't require expansion and can remain in the row value (so continue below). - // But if the visitation above changed a value, construct a list to hold the visited values. - if (visitedLeftValue != leftValue && visitedLeftValues is null) - { - visitedLeftValues = SliceToList(leftRowValue.Values, count, i); + continue; } - if (visitedLeftValues is not null) - { - visitedLeftValues.Add(visitedLeftValue); - } + // If we're here, the value pair requires null semantics compensation. We build a binary expression around the pair and + // visit that (that adds the compensation). We then chain all such expressions together with AND. + var valueBinaryExpression = Visit( + _sqlExpressionFactory.MakeBinary( + operatorType, visitedLeftValue, visitedRightValue, typeMapping: null, existingExpr: sqlBinaryExpression)!, + allowOptimizedExpansion, + out _); - if (visitedRightValue != rightValue && visitedRightValues is null) + if (expandedExpression is null) { + // visitedLeft/RightValues will contain all pairs that can remain in the row value (since they don't require + // compensation) + visitedLeftValues = SliceToList(leftRowValue.Values, count, i); visitedRightValues = SliceToList(rightRowValue.Values, count, i); - } - if (visitedRightValues is not null) + expandedExpression = valueBinaryExpression; + } + else { - visitedRightValues.Add(visitedRightValue); + expandedExpression = operatorType switch + { + ExpressionType.Equal => _sqlExpressionFactory.AndAlso(expandedExpression, valueBinaryExpression), + ExpressionType.NotEqual => _sqlExpressionFactory.OrElse(expandedExpression, valueBinaryExpression), + _ => throw new UnreachableException() + }; } - - continue; } - // If we're here, the value pair requires null semantics compensation. We build a binary expression around the pair and visit - // that (that adds the compensation). We then chain all such expressions together with AND. - var valueBinaryExpression = Visit( - _sqlExpressionFactory.MakeBinary(operatorType, visitedLeftValue, visitedRightValue, null)!, allowOptimizedExpansion, out _); + // TODO: This is wrong, need to properly calculate this: #3250 + nullable = false; if (expandedExpression is null) { - // visitedLeft/RightValues will contain all pairs that can remain in the row value (since they don't require compensation) - visitedLeftValues = SliceToList(leftRowValue.Values, count, i); - visitedRightValues = SliceToList(rightRowValue.Values, count, i); - - expandedExpression = valueBinaryExpression; + // No pairs required compensation, so they all stay in the row value. + // Either return the original binary expression (if no value visitation changed anything), or construct a new one over the + // visited values. + return visitedLeftValues is null && visitedRightValues is null + ? sqlBinaryExpression + : _sqlExpressionFactory.MakeBinary( + operatorType, + visitedLeftValues is null + ? leftRowValue + : new PgRowValueExpression(visitedLeftValues, leftRowValue.Type, leftRowValue.TypeMapping), + visitedRightValues is null + ? rightRowValue + : new PgRowValueExpression(visitedRightValues, leftRowValue.Type, leftRowValue.TypeMapping), + typeMapping: null, + existingExpr: sqlBinaryExpression)!; } - else + + Check.DebugAssert(visitedLeftValues is not null, "visitedLeftValues is not null"); + Check.DebugAssert(visitedRightValues is not null, "visitedRightValues is not null"); + + // All pairs required compensation - none are left in the row value comparison. Just return the expanded expression. + if (visitedLeftValues.Count is 0) { - expandedExpression = _sqlExpressionFactory.AndAlso(expandedExpression, valueBinaryExpression); + return expandedExpression; } - } - - nullable = false; - if (expandedExpression is null) - { - // No pairs required compensation, so they all stay in the row value. - // Either return the original binary expression (if no value visitation changed anything), or construct a new one over the - // visited values. - return visitedLeftValues is null && visitedRightValues is null - ? sqlBinaryExpression + // Some pairs required compensation; we're going to return a combined expression of the unexpanded pairs (which didn't require + // compensation) and of the expanded expression. + // If there's only one unexpanded pair left, that's a special case that doesn't require row values (just a regular pair + // comparison). Otherwise create the new row comparison expression for the unexpanded pairs. + var unexpandedExpression = visitedLeftValues.Count is 1 + ? _sqlExpressionFactory.MakeBinary(operatorType, visitedLeftValues[0], visitedRightValues[0], typeMapping: null)! : _sqlExpressionFactory.MakeBinary( operatorType, - visitedLeftValues is null - ? leftRowValue - : new PgRowValueExpression(visitedLeftValues, leftRowValue.Type, leftRowValue.TypeMapping), - visitedRightValues is null - ? rightRowValue - : new PgRowValueExpression(visitedRightValues, leftRowValue.Type, leftRowValue.TypeMapping), - null)!; - } - - Check.DebugAssert(visitedLeftValues is not null, "visitedLeftValues is not null"); - Check.DebugAssert(visitedRightValues is not null, "visitedRightValues is not null"); + new PgRowValueExpression(visitedLeftValues, leftRowValue.Type, leftRowValue.TypeMapping), + new PgRowValueExpression(visitedRightValues, rightRowValue.Type, rightRowValue.TypeMapping), + typeMapping: null)!; - // Some pairs required compensation. Combine the pairs which didn't (in visitedLeft/RightValues) with expandedExpression - // (which contains the logic for those that did). - return visitedLeftValues.Count switch - { - 0 => expandedExpression, - 1 => _sqlExpressionFactory.AndAlso( - _sqlExpressionFactory.MakeBinary(operatorType, visitedLeftValues[0], visitedRightValues[0], null)!, - expandedExpression), // Technically the CLR type and type mappings are incorrect, as we're truncating the row values. // But that shouldn't matter. - _ => _sqlExpressionFactory.AndAlso( - _sqlExpressionFactory.MakeBinary( - sqlBinaryExpression.OperatorType, - new PgRowValueExpression(visitedLeftValues, leftRowValue.Type, leftRowValue.TypeMapping), - new PgRowValueExpression(visitedRightValues, rightRowValue.Type, rightRowValue.TypeMapping), - null)!, - expandedExpression) - }; + return _sqlExpressionFactory.MakeBinary( + operatorType: operatorType switch + { + ExpressionType.Equal => ExpressionType.AndAlso, + ExpressionType.NotEqual => ExpressionType.OrElse, + _ => throw new UnreachableException() + }, + unexpandedExpression, + expandedExpression, + typeMapping: null)!; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static List SliceToList(IReadOnlyList source, int capacity, int count) + { + var list = new List(capacity); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static List SliceToList(IReadOnlyList source, int capacity, int count) - { - var list = new List(capacity); + for (var i = 0; i < count; i++) + { + list.Add(source[i]); + } - for (var i = 0; i < count; i++) - { - list.Add(source[i]); + return list; } - - return list; } } @@ -514,13 +540,10 @@ protected virtual SqlExpression VisitILike(PgILikeExpression iLikeExpression, bo result = _sqlExpressionFactory.AndAlso(result, GenerateNotNullCheck(escapeChar)); } + // TODO: This revisits the operand; ideally we'd call ProcessNullNotNull directly but that's private SqlExpression GenerateNotNullCheck(SqlExpression operand) - => OptimizeNonNullableNotExpression( - _sqlExpressionFactory.Not( - VisitSqlUnary( - _sqlExpressionFactory.IsNull(operand), - allowOptimizedExpansion: false, - out _))); + => _sqlExpressionFactory.Not( + Visit(_sqlExpressionFactory.IsNull(operand), allowOptimizedExpansion, out _)); } return result; diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 6234f7097..9c4029c40 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -346,11 +346,13 @@ protected override Expression VisitNew(NewExpression newExpression) rewrittenArguments.Add(_sqlExpressionFactory.Constant("UTC")); } - return kind == DateTimeKind.Utc - ? _sqlExpressionFactory.Function( - "make_timestamptz", rewrittenArguments, nullable: true, TrueArrays[8], typeof(DateTime), _timestampTzMapping) - : _sqlExpressionFactory.Function( - "make_timestamp", rewrittenArguments, nullable: true, TrueArrays[7], typeof(DateTime), _timestampMapping); + return _sqlExpressionFactory.Function( + kind == DateTimeKind.Utc ? "make_timestamptz" : "make_timestamp", + rewrittenArguments, + nullable: true, + TrueArrays[rewrittenArguments.Count], + typeof(DateTime), + kind == DateTimeKind.Utc ? _timestampTzMapping : _timestampMapping); } } @@ -576,6 +578,44 @@ private static string EscapeLikePattern(string pattern) #endregion StartsWith/EndsWith/Contains + #region GREATEST/LEAST + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override SqlExpression GenerateGreatest(IReadOnlyList expressions, Type resultType) + { + // Docs: https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-GREATEST-LEAST + var resultTypeMapping = ExpressionExtensions.InferTypeMapping(expressions); + + // If one or more arguments aren't NULL, then NULL arguments are ignored during comparison. + // If all arguments are NULL, then GREATEST returns NULL. + return _sqlExpressionFactory.Function( + "GREATEST", expressions, nullable: true, Enumerable.Repeat(false, expressions.Count), resultType, resultTypeMapping); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override SqlExpression GenerateLeast(IReadOnlyList expressions, Type resultType) + { + // Docs: https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-GREATEST-LEAST + var resultTypeMapping = ExpressionExtensions.InferTypeMapping(expressions); + + // If one or more arguments aren't NULL, then NULL arguments are ignored during comparison. + // If all arguments are NULL, then LEAST returns NULL. + return _sqlExpressionFactory.Function( + "LEAST", expressions, nullable: true, Enumerable.Repeat(false, expressions.Count), resultType, resultTypeMapping); + } + + #endregion GREATEST/LEAST + #region Copied from RelationalSqlTranslatingExpressionVisitor private static Expression TryRemoveImplicitConvert(Expression expression) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs index ba50c0577..62a6444ec 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs @@ -10,6 +10,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; /// public class NpgsqlTypeMappingPostprocessor : RelationalTypeMappingPostprocessor { + private readonly IModel _model; private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly ISqlExpressionFactory _sqlExpressionFactory; @@ -25,6 +26,7 @@ public NpgsqlTypeMappingPostprocessor( RelationalQueryCompilationContext queryCompilationContext) : base(dependencies, relationalDependencies, queryCompilationContext) { + _model = queryCompilationContext.Model; _typeMappingSource = relationalDependencies.TypeMappingSource; _sqlExpressionFactory = relationalDependencies.SqlExpressionFactory; } @@ -42,7 +44,7 @@ protected override Expression VisitExtension(Expression expression) case PgUnnestExpression unnestExpression when TryGetInferredTypeMapping(unnestExpression.Alias, unnestExpression.ColumnName, out var elementTypeMapping): { - var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, Model, elementTypeMapping); + var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, _model, elementTypeMapping); if (collectionTypeMapping is null) { diff --git a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs index 4b1a5d38d..b4a1d578c 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs @@ -27,7 +27,9 @@ public class NpgsqlUnnestPostprocessor : ExpressionVisitor switch (expression) { case ShapedQueryExpression shapedQueryExpression: - return shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); + return shapedQueryExpression + .UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)) + .UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression)); case SelectExpression selectExpression: { @@ -83,14 +85,14 @@ bool IsOrdinalityColumn(SqlExpression expression) newTables is null ? selectExpression : selectExpression.Update( - selectExpression.Projection, newTables, selectExpression.Predicate, selectExpression.GroupBy, selectExpression.Having, + selectExpression.Projection, orderings, - selectExpression.Limit, - selectExpression.Offset)); + selectExpression.Offset, + selectExpression.Limit)); } default: diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 368ddc238..84fac6794 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -225,11 +225,12 @@ public virtual PgNewArrayExpression NewArray( => (PgNewArrayExpression)ApplyTypeMapping(new PgNewArrayExpression(expressions, type, typeMapping), typeMapping); /// - public override SqlBinaryExpression? MakeBinary( + public override SqlExpression? MakeBinary( ExpressionType operatorType, SqlExpression left, SqlExpression right, - RelationalTypeMapping? typeMapping) + RelationalTypeMapping? typeMapping, + SqlExpression? existingExpr = null) { switch (operatorType) { @@ -268,7 +269,7 @@ public virtual PgNewArrayExpression NewArray( } } - return base.MakeBinary(operatorType, left, right, typeMapping); + return base.MakeBinary(operatorType, left, right, typeMapping, existingExpr); } /// @@ -279,7 +280,7 @@ public virtual PgNewArrayExpression NewArray( /// The right operand of binary operation. /// A type mapping to be assigned to the created expression. /// A with the given arguments. - public virtual PgBinaryExpression MakePostgresBinary( + public virtual SqlExpression MakePostgresBinary( PgExpressionType operatorType, SqlExpression left, SqlExpression right, @@ -321,7 +322,7 @@ public virtual PgBinaryExpression MakePostgresBinary( /// /// Creates a new , for checking whether one value contains another. /// - public virtual PgBinaryExpression Contains(SqlExpression left, SqlExpression right) + public virtual SqlExpression Contains(SqlExpression left, SqlExpression right) { Check.NotNull(left, nameof(left)); Check.NotNull(right, nameof(right)); @@ -332,7 +333,7 @@ public virtual PgBinaryExpression Contains(SqlExpression left, SqlExpression rig /// /// Creates a new , for checking whether one value is contained by another. /// - public virtual PgBinaryExpression ContainedBy(SqlExpression left, SqlExpression right) + public virtual SqlExpression ContainedBy(SqlExpression left, SqlExpression right) { Check.NotNull(left, nameof(left)); Check.NotNull(right, nameof(right)); @@ -343,7 +344,7 @@ public virtual PgBinaryExpression ContainedBy(SqlExpression left, SqlExpression /// /// Creates a new , for checking whether one value overlaps with another. /// - public virtual PgBinaryExpression Overlaps(SqlExpression left, SqlExpression right) + public virtual SqlExpression Overlaps(SqlExpression left, SqlExpression right) { Check.NotNull(left, nameof(left)); Check.NotNull(right, nameof(right)); @@ -495,8 +496,19 @@ private SqlBinaryExpression ApplyTypeMappingOnSqlBinary(SqlBinaryExpression bina { var updatedElementBinaryExpression = MakeBinary(binary.OperatorType, leftValues[i], rightValues[i], typeMapping: null)!; - updatedLeftValues[i] = updatedElementBinaryExpression.Left; - updatedRightValues[i] = updatedElementBinaryExpression.Right; + if (updatedElementBinaryExpression is not SqlBinaryExpression + { + Left: var updatedLeft, + Right: var updatedRight, + OperatorType: var updatedOperatorType + } + || updatedOperatorType != binary.OperatorType) + { + throw new UnreachableException("MakeBinary modified binary expression type/operator when doing row value comparison"); + } + + updatedLeftValues[i] = updatedLeft; + updatedRightValues[i] = updatedRight; } // Note that we always return non-constant PostgresRowValueExpression operands, even if the original input was a diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 37ea2c68a..4be6a4d0c 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -1021,8 +1021,6 @@ WHERE NOT EXISTS (SELECT * FROM pg_depend AS dep WHERE dep.objid = cls.oid AND d MaxValue = seqInfo.MaxValue, IncrementBy = (int?)seqInfo.IncrementBy, IsCyclic = seqInfo.IsCyclic, - IsCached = seqInfo.CacheSize is not null, - CacheSize = seqInfo.CacheSize }; yield return sequence; diff --git a/src/EFCore.PG/Storage/Internal/Json/JsonBitArrayReaderWriter.cs b/src/EFCore.PG/Storage/Internal/Json/JsonBitArrayReaderWriter.cs index f74691d7e..eeb293210 100644 --- a/src/EFCore.PG/Storage/Internal/Json/JsonBitArrayReaderWriter.cs +++ b/src/EFCore.PG/Storage/Internal/Json/JsonBitArrayReaderWriter.cs @@ -12,6 +12,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Json; /// public sealed class JsonBitArrayReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonBitArrayReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -51,4 +53,7 @@ public override void ToJsonTyped(Utf8JsonWriter writer, BitArray value) s[i] = a[i] ? '1' : '0'; } })); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } diff --git a/src/EFCore.PG/Storage/Internal/Json/JsonMacaddrReaderWriter.cs b/src/EFCore.PG/Storage/Internal/Json/JsonMacaddrReaderWriter.cs index d2a9d4aed..ca1aa2bc6 100644 --- a/src/EFCore.PG/Storage/Internal/Json/JsonMacaddrReaderWriter.cs +++ b/src/EFCore.PG/Storage/Internal/Json/JsonMacaddrReaderWriter.cs @@ -12,6 +12,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Json; /// public sealed class JsonMacaddrReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonMacaddrReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -31,4 +33,7 @@ public override PhysicalAddress FromJsonTyped(ref Utf8JsonReaderManager manager, /// public override void ToJsonTyped(Utf8JsonWriter writer, PhysicalAddress value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index ef0e1aa0d..6b70853b3 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -133,10 +133,11 @@ private static RelationalTypeMappingParameters CreateParameters(string storeType ? null // TODO: Value comparer for multidimensional arrays : (ValueComparer?)Activator.CreateInstance( elementType.IsNullableValueType() - ? typeof(NullableValueTypeListComparer<>).MakeGenericType(elementType.UnwrapNullableType()) - : elementMapping.Comparer.Type.IsAssignableFrom(elementType) - ? typeof(ListComparer<>).MakeGenericType(elementType) - : typeof(ObjectListComparer<>).MakeGenericType(elementType), + ? typeof(ListOfNullableValueTypesComparer<,>) + .MakeGenericType(typeof(TConcreteCollection), elementType.UnwrapNullableType()) + : elementType.IsValueType + ? typeof(ListOfValueTypesComparer<,>).MakeGenericType(typeof(TConcreteCollection), elementType) + : typeof(ListOfReferenceTypesComparer<,>).MakeGenericType(typeof(TConcreteCollection), elementType), elementMapping.Comparer.ToNullableComparer(elementType)!); #pragma warning restore EF1001 @@ -155,9 +156,11 @@ private static RelationalTypeMappingParameters CreateParameters(string storeType ? null : (JsonValueReaderWriter?)Activator.CreateInstance( (elementType.IsNullableValueType() - ? typeof(JsonNullableStructCollectionReaderWriter<,,>) - : typeof(JsonCollectionReaderWriter<,,>)) - .MakeGenericType(typeof(TCollection), typeof(TConcreteCollection), elementType.UnwrapNullableType()), + ? typeof(JsonCollectionOfNullableStructsReaderWriter<,>) + : elementType.IsValueType + ? typeof(JsonCollectionOfStructsReaderWriter<,>) + : typeof(JsonCollectionOfReferencesReaderWriter<,>)) + .MakeGenericType(typeof(TConcreteCollection), elementType.UnwrapNullableType()), elementJsonReaderWriter); return new RelationalTypeMappingParameters( diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs index 11a18a0c2..4be7eb2f0 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs @@ -72,6 +72,8 @@ protected override string ProcessStoreType(RelationalTypeMappingParameters param /// public sealed class JsonBigIntegerReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonBigIntegerReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -98,5 +100,8 @@ public override BigInteger FromJsonTyped(ref Utf8JsonReaderManager manager, obje /// public override void ToJsonTyped(Utf8JsonWriter writer, BigInteger value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs index a07b54d41..7a943d887 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs @@ -91,6 +91,8 @@ public override Expression GenerateCodeLiteral(object value) /// public sealed class JsonCidrReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonCidrReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -116,5 +118,8 @@ public override NpgsqlCidr FromJsonTyped(ref Utf8JsonReaderManager manager, obje /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlCidr value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs index 55d181bec..3b6be8c3b 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs @@ -95,6 +95,8 @@ private static string Format(DateOnly date) /// public sealed class NpgsqlJsonDateOnlyReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonDateOnlyReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -135,5 +137,8 @@ public override DateOnly FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateOnly value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs index dba554f11..c966b483b 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs @@ -95,6 +95,8 @@ private static string Format(DateTime date) /// public sealed class NpgsqlJsonDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonDateTimeReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -135,5 +137,8 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs index faf8cc37b..108892bb3 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs @@ -150,6 +150,16 @@ protected override string GenerateNonNullSqlLiteral(object value) public sealed class JsonPgEnumReaderWriter : JsonValueReaderWriter where T : struct, Enum { + private static readonly PropertyInfo InstanceProperty = typeof(JsonPgEnumReaderWriter).GetProperty(nameof(Instance))!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static JsonPgEnumReaderWriter Instance { get; } = new(); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -167,5 +177,8 @@ public override T FromJsonTyped(ref Utf8JsonReaderManager manager, object? exist /// public override void ToJsonTyped(Utf8JsonWriter writer, T value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs index 90a6edb9d..ad99bd3c6 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs @@ -93,6 +93,8 @@ public override Expression GenerateCodeLiteral(object value) /// public sealed class JsonIPAddressReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonIPAddressReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -118,6 +120,9 @@ public override IPAddress FromJsonTyped(ref Utf8JsonReaderManager manager, objec /// public override void ToJsonTyped(Utf8JsonWriter writer, IPAddress value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } /// @@ -128,6 +133,8 @@ public override void ToJsonTyped(Utf8JsonWriter writer, IPAddress value) /// public sealed class JsonNpgsqlInetReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonNpgsqlInetReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -153,5 +160,8 @@ public override NpgsqlInet FromJsonTyped(ref Utf8JsonReaderManager manager, obje /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlInet value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs index 6c4706a7f..c5846088d 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs @@ -163,6 +163,8 @@ public static TimeSpan ParseIntervalAsTimeSpan(ReadOnlySpan s) /// public sealed class NpgsqlJsonTimeSpanReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonTimeSpanReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -188,5 +190,8 @@ public override TimeSpan FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, TimeSpan value) => writer.WriteStringValue(FormatTimeSpanAsInterval(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs index a761cffa7..8f31c436f 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs @@ -76,6 +76,8 @@ public override Expression GenerateCodeLiteral(object value) /// public sealed class JsonLTreeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLTreeReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -101,5 +103,8 @@ public override LTree FromJsonTyped(ref Utf8JsonReaderManager manager, object? e /// public override void ToJsonTyped(Utf8JsonWriter writer, LTree value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs index 7dcb7279e..f107520cb 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs @@ -91,6 +91,8 @@ public override Expression GenerateCodeLiteral(object value) /// public sealed class JsonLogSequenceNumberReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLogSequenceNumberReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -120,5 +122,8 @@ public override NpgsqlLogSequenceNumber FromJsonTyped(ref Utf8JsonReaderManager /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlLogSequenceNumber value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs index fba3bf838..fad0f4143 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs @@ -85,6 +85,8 @@ protected override string GenerateEmbeddedNonNullSqlLiteral(object value) /// public sealed class JsonTimeTzReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonTimeTzReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -114,5 +116,8 @@ public override DateTimeOffset FromJsonTyped(ref Utf8JsonReaderManager manager, /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTimeOffset value) => writer.WriteStringValue(value.ToString("HH:mm:ss.FFFFFFz")); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs index 0977641ba..c1e4ac41f 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs @@ -109,6 +109,8 @@ private static string FormatDateTime(DateTime dateTime) /// public sealed class NpgsqlJsonTimestampReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonTimestampReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -149,5 +151,8 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(FormatDateTime(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs index 8f3a09c20..0cba8cfe3 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs @@ -154,6 +154,9 @@ private static string Format(DateTimeOffset dateTimeOffset) /// public sealed class NpgsqlJsonTimestampTzDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty + = typeof(NpgsqlJsonTimestampTzDateTimeReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -196,6 +199,9 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } /// @@ -206,6 +212,9 @@ public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) /// public sealed class NpgsqlJsonTimestampTzDateTimeOffsetReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty + = typeof(NpgsqlJsonTimestampTzDateTimeOffsetReaderWriter).GetProperty(nameof(Instance))!; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -246,5 +255,8 @@ public override DateTimeOffset FromJsonTyped(ref Utf8JsonReaderManager manager, /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTimeOffset value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 5129fdb7c..d1b922231 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -441,11 +441,10 @@ public NpgsqlTypeMappingSource( } } - // TODO: the following is a workaround/hack for https://github.com/dotnet/efcore/issues/31505 if ((storeTypeName.EndsWith("[]", StringComparison.Ordinal) || storeTypeName is "int4multirange" or "int8multirange" or "nummultirange" or "datemultirange" or "tsmultirange" or "tstzmultirange") - && FindCollectionMapping(mappingInfo, mappingInfo.ClrType!, providerType: null, elementMapping: null) is + && FindCollectionMapping(mappingInfo, mappingInfo.ClrType, providerType: null, elementMapping: null) is RelationalTypeMapping collectionMapping) { return collectionMapping; @@ -460,7 +459,7 @@ public NpgsqlTypeMappingSource( if (ClrTypeMappings.TryGetValue(clrType, out var mapping)) { // Handle types with the size facet (string, bitarray) - if (mappingInfo.Size is > 0) + if (mappingInfo.Size > 0) { if (clrType == typeof(string)) { @@ -526,7 +525,9 @@ public NpgsqlTypeMappingSource( /// protected override RelationalTypeMapping? FindCollectionMapping( RelationalTypeMappingInfo info, - Type modelType, + // Note that modelType is nullable (in the relational base signature it isn't) because of array scaffolding, i.e. we call + // FindCollectionMapping from our own FindMapping, where the clrType is null when scaffolding. + Type? modelType, Type? providerType, CoreTypeMapping? elementMapping) { @@ -538,9 +539,6 @@ public NpgsqlTypeMappingSource( Type concreteCollectionType; Type? elementType = null; - // TODO: modelType can be null (contrary to nullable annotations) only because of https://github.com/dotnet/efcore/issues/31505, - // i.e. we call into here - // If there's a CLR type (i.e. not reverse-engineering), check that it's a compatible enumerable. if (modelType is not null) { // We do GetElementType for multidimensional arrays - these don't implement generic IEnumerable<> diff --git a/test/EFCore.PG.FunctionalTests/BatchingTest.cs b/test/EFCore.PG.FunctionalTests/BatchingTest.cs index a8cd3516f..f7c87c74d 100644 --- a/test/EFCore.PG.FunctionalTests/BatchingTest.cs +++ b/test/EFCore.PG.FunctionalTests/BatchingTest.cs @@ -20,11 +20,11 @@ public class BatchingTest(BatchingTest.BatchingTestFixture fixture) : IClassFixt [InlineData(false, true, false)] [InlineData(true, false, false)] [InlineData(false, false, false)] - public void Inserts_are_batched_correctly(bool clientPk, bool clientFk, bool clientOrder) + public async Task Inserts_are_batched_correctly(bool clientPk, bool clientFk, bool clientOrder) { var expectedBlogs = new List(); - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var owner1 = new Owner(); var owner2 = new Owner(); @@ -53,18 +53,18 @@ public void Inserts_are_batched_correctly(bool clientPk, bool clientFk, bool cli expectedBlogs.Add(blog); } - context.SaveChanges(); + await context.SaveChangesAsync(); }, context => AssertDatabaseState(context, clientOrder, expectedBlogs)); } [Fact] - public void Inserts_and_updates_are_batched_correctly() + public async Task Inserts_and_updates_are_batched_correctly() { var expectedBlogs = new List(); - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var owner1 = new Owner { Name = "0" }; var owner2 = new Owner { Name = "1" }; @@ -106,35 +106,35 @@ public void Inserts_and_updates_are_batched_correctly() context.Set().Add(blog3); expectedBlogs.Add(blog3); - context.SaveChanges(); + await context.SaveChangesAsync(); }, context => AssertDatabaseState(context, true, expectedBlogs)); } [Fact] - public void Inserts_when_database_type_is_different() - => ExecuteWithStrategyInTransaction( - context => + public Task Inserts_when_database_type_is_different() + => ExecuteWithStrategyInTransactionAsync( + async context => { var owner1 = new Owner { Id = "0", Name = "Zero" }; var owner2 = new Owner { Id = "A", Name = string.Join("", Enumerable.Repeat('A', 900)) }; context.Owners.Add(owner1); context.Owners.Add(owner2); - context.SaveChanges(); + await context.SaveChangesAsync(); }, - context => Assert.Equal(2, context.Owners.Count())); + async context => Assert.Equal(2, await context.Owners.CountAsync())); [ConditionalTheory] [InlineData(3)] [InlineData(4)] - public void Inserts_are_batched_only_when_necessary(int minBatchSize) + public Task Inserts_are_batched_only_when_necessary(int minBatchSize) { var expectedBlogs = new List(); - TestHelpers.ExecuteWithStrategyInTransaction( + return TestHelpers.ExecuteWithStrategyInTransactionAsync( () => (BloggingContext)Fixture.CreateContext(minBatchSize), UseTransaction, - context => + async context => { var owner = new Owner(); context.Owners.Add(owner); @@ -149,7 +149,7 @@ public void Inserts_are_batched_only_when_necessary(int minBatchSize) Fixture.TestSqlLoggerFactory.Clear(); - context.SaveChanges(); + await context.SaveChangesAsync(); Assert.Contains( minBatchSize == 3 @@ -163,13 +163,13 @@ public void Inserts_are_batched_only_when_necessary(int minBatchSize) }, context => AssertDatabaseState(context, false, expectedBlogs)); } - private void AssertDatabaseState(DbContext context, bool clientOrder, List expectedBlogs) + private async Task AssertDatabaseState(DbContext context, bool clientOrder, List expectedBlogs) { expectedBlogs = clientOrder ? expectedBlogs.OrderBy(b => b.Order).ToList() : expectedBlogs.OrderBy(b => b.Id).ToList(); var actualBlogs = clientOrder - ? context.Set().OrderBy(b => b.Order).ToList() + ? await context.Set().OrderBy(b => b.Order).ToListAsync() : expectedBlogs.OrderBy(b => b.Id).ToList(); Assert.Equal(expectedBlogs.Count, actualBlogs.Count); @@ -187,10 +187,10 @@ private void AssertDatabaseState(DbContext context, bool clientOrder, List private BloggingContext CreateContext() => (BloggingContext)Fixture.CreateContext(); - private void ExecuteWithStrategyInTransaction( - Action testOperation, - Action nestedTestOperation) - => TestHelpers.ExecuteWithStrategyInTransaction( + private Task ExecuteWithStrategyInTransactionAsync( + Func testOperation, + Func nestedTestOperation) + => TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, UseTransaction, testOperation, nestedTestOperation); protected void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) @@ -254,8 +254,8 @@ protected override ITestStoreFactory TestStoreFactory protected override bool ShouldLogCategory(string logCategory) => logCategory == DbLoggerCategory.Update.Name; - protected override void Seed(PoolableDbContext context) - => context.Database.EnsureCreatedResiliently(); + protected override Task SeedAsync(PoolableDbContext context) + => context.Database.EnsureCreatedResilientlyAsync(); public DbContext CreateContext(int minBatchSize) { diff --git a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs index 69125d02e..62ba84946 100644 --- a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs @@ -717,88 +717,102 @@ private static void AssertNullMappedNullableDataTypes(MappedNullableDataTypes en Assert.Null(entity.Mood); } - public override void Can_query_with_null_parameters_using_any_nullable_data_type() + public override async Task Can_query_with_null_parameters_using_any_nullable_data_type() { using (var context = CreateContext()) { context.Set().Add( new BuiltInNullableDataTypes { Id = 711 }); - Assert.Equal(1, context.SaveChanges()); + Assert.Equal(1, await context.SaveChangesAsync()); } using (var context = CreateContext()) { - var entity = context.Set().Where(e => e.Id == 711).ToList().Single(); + var entity = (await context.Set().Where(e => e.Id == 711).ToListAsync()).Single(); short? param1 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableInt16 == param1).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableInt16 == param1).ToListAsync()) + .Single()); Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && (long?)e.TestNullableInt16 == param1).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && (long?)e.TestNullableInt16 == param1) + .ToListAsync()) + .Single()); int? param2 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableInt32 == param2).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableInt32 == param2).ToListAsync()) + .Single()); long? param3 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableInt64 == param3).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableInt64 == param3).ToListAsync()) + .Single()); double? param4 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableDouble == param4).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableDouble == param4).ToListAsync()) + .Single()); decimal? param5 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableDecimal == param5).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableDecimal == param5).ToListAsync()) + .Single()); DateTime? param6 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableDateTime == param6).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableDateTime == param6).ToListAsync()) + .Single()); // We don't support DateTimeOffset TimeSpan? param8 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableTimeSpan == param8).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableTimeSpan == param8).ToListAsync()) + .Single()); float? param9 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableSingle == param9).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableSingle == param9).ToListAsync()) + .Single()); bool? param10 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableBoolean == param10).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableBoolean == param10).ToListAsync()) + .Single()); // We don't support byte Enum64? param12 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.Enum64 == param12).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.Enum64 == param12).ToListAsync()).Single()); Enum32? param13 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.Enum32 == param13).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.Enum32 == param13).ToListAsync()).Single()); Enum16? param14 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.Enum16 == param14).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.Enum16 == param14).ToListAsync()).Single()); Enum8? param15 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.Enum8 == param15).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.Enum8 == param15).ToListAsync()).Single()); var entityType = context.Model.FindEntityType(typeof(BuiltInNullableDataTypes)); if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableUnsignedInt16)) is not null) @@ -806,8 +820,9 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type ushort? param16 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt16 == param16).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt16 == param16) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableUnsignedInt32)) is not null) @@ -815,8 +830,9 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type uint? param17 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt32 == param17).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt32 == param17) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableUnsignedInt64)) is not null) @@ -824,8 +840,9 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type ulong? param18 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt64 == param18).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt64 == param18) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableCharacter)) is not null) @@ -833,8 +850,9 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type char? param19 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableCharacter == param19).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableCharacter == param19) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableSignedByte)) is not null) @@ -842,57 +860,68 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type sbyte? param20 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableSignedByte == param20).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableSignedByte == param20) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.EnumU64)) is not null) { EnumU64? param21 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.EnumU64 == param21).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.EnumU64 == param21).ToListAsync()).Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.EnumU32)) is not null) { EnumU32? param22 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.EnumU32 == param22).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.EnumU32 == param22).ToListAsync()).Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.EnumU16)) is not null) { EnumU16? param23 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.EnumU16 == param23).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.EnumU16 == param23).ToListAsync()).Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.EnumS8)) is not null) { EnumS8? param24 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.EnumS8 == param24).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.EnumS8 == param24).ToListAsync()).Single()); } } } [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_all_nullable_data_types_with_values_set_to_non_null() { } + public override Task Can_insert_and_read_back_all_nullable_data_types_with_values_set_to_non_null() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_non_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_non_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_object_backed_data_types() { } + public override Task Can_insert_and_read_back_object_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_query_using_any_data_type_nullable_shadow() { } + public override Task Can_query_using_any_data_type_nullable_shadow() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_query_using_any_data_type_shadow() { } + public override Task Can_query_using_any_data_type_shadow() + => Task.CompletedTask; [ConditionalFact] public void Sum_Conversions() @@ -998,25 +1027,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .HasColumnType("timestamp without time zone"); // We don't support DateTimeOffset with non-zero offset, so we need to override the seeding data - var builtInDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - builtInDataTypes[nameof(BuiltInDataTypes.TestDateTimeOffset)] + modelBuilder.Entity().Metadata.GetSeedData() + .Single(t => (int)t[nameof(BuiltInDataTypes.Id)] == 13)[nameof(BuiltInDataTypes.TestDateTimeOffset)] = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.Zero); - var builtInNullableDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - builtInNullableDataTypes[nameof(BuiltInNullableDataTypes.TestNullableDateTimeOffset)] + modelBuilder.Entity().Metadata.GetSeedData() + .Single(t => (int)t[nameof(BuiltInDataTypes.Id)] == 13)[nameof(BuiltInNullableDataTypes.TestNullableDateTimeOffset)] = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.Zero); - var objectBackedDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - objectBackedDataTypes[nameof(ObjectBackedDataTypes.DateTimeOffset)] - = new DateTimeOffset(new DateTime(), TimeSpan.Zero); + modelBuilder.Entity().Metadata.GetSeedData() + .Single()[nameof(ObjectBackedDataTypes.DateTimeOffset)] = new DateTimeOffset(new DateTime(), TimeSpan.Zero); - var nullableBackedDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - nullableBackedDataTypes[nameof(NullableBackedDataTypes.DateTimeOffset)] + modelBuilder.Entity().Metadata.GetSeedData() + .Single()[nameof(NullableBackedDataTypes.DateTimeOffset)] = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.Zero); - var nonNullableBackedDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - nonNullableBackedDataTypes[nameof(NonNullableBackedDataTypes.DateTimeOffset)] - = new DateTimeOffset(new DateTime(), TimeSpan.Zero); + modelBuilder.Entity().Metadata.GetSeedData() + .Single()[nameof(NonNullableBackedDataTypes.DateTimeOffset)] = new DateTimeOffset(new DateTime(), TimeSpan.Zero); modelBuilder.Entity( b => diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs index abaa17130..7bf72a670 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Update; public class ComplexTypeBulkUpdatesNpgsqlTest( ComplexTypeBulkUpdatesNpgsqlTest.ComplexTypeBulkUpdatesNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : ComplexTypeBulkUpdatesTestBase(fixture, testOutputHelper) + : ComplexTypeBulkUpdatesRelationalTestBase(fixture, testOutputHelper) { public override async Task Delete_entity_type_with_complex_type(bool async) { @@ -19,9 +19,9 @@ DELETE FROM "Customer" AS c """); } - public override async Task Delete_complex_type_throws(bool async) + public override async Task Delete_complex_type(bool async) { - await base.Delete_complex_type_throws(async); + await base.Delete_complex_type(async); AssertSql(); } @@ -87,9 +87,9 @@ public override async Task Update_multiple_projected_complex_types_via_anonymous """); } - public override async Task Update_projected_complex_type_via_OrderBy_Skip_throws(bool async) + public override async Task Update_projected_complex_type_via_OrderBy_Skip(bool async) { - await base.Update_projected_complex_type_via_OrderBy_Skip_throws(async); + await base.Update_projected_complex_type_via_OrderBy_Skip(async); AssertExecuteUpdateSql(); } @@ -229,7 +229,7 @@ private void AssertSql(params string[] expected) protected void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); - public class ComplexTypeBulkUpdatesNpgsqlFixture : ComplexTypeBulkUpdatesFixtureBase + public class ComplexTypeBulkUpdatesNpgsqlFixture : ComplexTypeBulkUpdatesRelationalFixtureBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs index 2704ed853..af6d6cfa2 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs @@ -3,7 +3,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Update; -public class NonSharedModelBulkUpdatesNpgsqlTest : NonSharedModelBulkUpdatesTestBase +public class NonSharedModelBulkUpdatesNpgsqlTest : NonSharedModelBulkUpdatesRelationalTestBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; @@ -50,6 +50,14 @@ DELETE FROM "Owner" AS o """); } + // TODO: #3253 + public override async Task Replace_ColumnExpression_in_column_setter(bool async) + { + var exception = await Assert.ThrowsAsync(() => base.Replace_ColumnExpression_in_column_setter(async)); + + Assert.Equal("42712", exception.SqlState); + } + public override async Task Delete_aggregate_root_when_table_sharing_with_non_owned_throws(bool async) { await base.Delete_aggregate_root_when_table_sharing_with_non_owned_throws(async); @@ -116,7 +124,7 @@ public override async Task Update_owned_and_non_owned_properties_with_table_shar """ UPDATE "Owner" AS o SET "OwnedReference_Number" = length(o."Title")::int, - "Title" = o."OwnedReference_Number"::text + "Title" = COALESCE(o."OwnedReference_Number"::text, '') """); } @@ -132,10 +140,10 @@ public override async Task Update_main_table_in_entity_with_entity_splitting(boo tb.Property(b => b.Title); tb.Property(b => b.Rating); }), - seed: context => + seed: async context => { context.Set().Add(new Blog { Title = "SomeBlog" }); - context.SaveChanges(); + await context.SaveChangesAsync(); }); await AssertUpdate( @@ -201,10 +209,10 @@ SELECT COALESCE(sum(o0."Amount"), 0)::int public virtual async Task Update_with_primitive_collection_in_value_selector(bool async) { var contextFactory = await InitializeAsync( - seed: ctx => + seed: async ctx => { ctx.AddRange(new EntityWithPrimitiveCollection { Tags = ["tag1", "tag2"] }); - ctx.SaveChanges(); + await ctx.SaveChangesAsync(); }); await AssertUpdate( diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs index 4dfa8eea2..13ea83511 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs @@ -5,7 +5,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class NorthwindBulkUpdatesNpgsqlFixture : NorthwindBulkUpdatesFixture +public class NorthwindBulkUpdatesNpgsqlFixture : NorthwindBulkUpdatesRelationalFixture where TModelCustomizer : ITestModelCustomizer, new() { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs index 60c20d9d9..40d60f7f5 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; public class NorthwindBulkUpdatesNpgsqlTest( NorthwindBulkUpdatesNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : NorthwindBulkUpdatesTestBase>(fixture, testOutputHelper) + : NorthwindBulkUpdatesRelationalTestBase>(fixture, testOutputHelper) { public override async Task Delete_Where_TagWith(bool async) { @@ -1375,12 +1375,12 @@ public override async Task Update_Where_Join_set_property_from_joined_single_res AssertExecuteUpdateSql( """ UPDATE "Customers" AS c -SET "City" = date_part('year', ( +SET "City" = COALESCE(date_part('year', ( SELECT o."OrderDate" FROM "Orders" AS o WHERE c."CustomerID" = o."CustomerID" ORDER BY o."OrderDate" DESC NULLS LAST - LIMIT 1))::int::text + LIMIT 1))::int::text, '') WHERE c."CustomerID" LIKE 'F%' """); } @@ -1409,12 +1409,12 @@ public override async Task Update_Where_Join_set_property_from_joined_single_res AssertExecuteUpdateSql( """ UPDATE "Customers" AS c -SET "City" = date_part('year', ( +SET "City" = COALESCE(date_part('year', ( SELECT o."OrderDate" FROM "Orders" AS o WHERE c."CustomerID" = o."CustomerID" ORDER BY o."OrderDate" DESC NULLS LAST - LIMIT 1))::int::text + LIMIT 1))::int::text, '') WHERE c."CustomerID" LIKE 'F%' """); } diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs index fcf00633d..ecab1b210 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,16 +2,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPHFiltersInheritanceBulkUpdatesNpgsqlTest : FiltersInheritanceBulkUpdatesTestBase< +public class TPHFiltersInheritanceBulkUpdatesNpgsqlTest : FiltersInheritanceBulkUpdatesRelationalTestBase< TPHFiltersInheritanceBulkUpdatesNpgsqlFixture> { public TPHFiltersInheritanceBulkUpdatesNpgsqlTest( TPHFiltersInheritanceBulkUpdatesNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) + : base(fixture, testOutputHelper) { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } public override async Task Delete_where_hierarchy(bool async) diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlTest.cs index 3fafaa0a8..21bb652f8 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,17 +2,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPHInheritanceBulkUpdatesNpgsqlTest : TPHInheritanceBulkUpdatesTestBase +public class TPHInheritanceBulkUpdatesNpgsqlTest( + TPHInheritanceBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : TPHInheritanceBulkUpdatesTestBase(fixture, testOutputHelper) { - public TPHInheritanceBulkUpdatesNpgsqlTest( - TPHInheritanceBulkUpdatesNpgsqlFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - public override async Task Delete_where_hierarchy(bool async) { await base.Delete_where_hierarchy(async); diff --git a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs index 17340262f..2cc8f767c 100644 --- a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs +++ b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs @@ -3,7 +3,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; [MinimumPostgresVersion(12, 0)] -public class ComputedColumnTest : IDisposable +public class ComputedColumnTest : IAsyncLifetime { [ConditionalFact] public void Can_use_computed_columns() @@ -128,8 +128,14 @@ public void Can_use_computed_columns_with_nullable_enum() Assert.Equal(FlagEnum.AValue | FlagEnum.BValue, entity.CalculatedFlagEnum); } - protected NpgsqlTestStore TestStore { get; } = NpgsqlTestStore.CreateInitialized("ComputedColumnTest"); + protected NpgsqlTestStore TestStore { get; private set; } - public virtual void Dispose() - => TestStore.Dispose(); + public async Task InitializeAsync() + => TestStore = await NpgsqlTestStore.CreateInitializedAsync("ComputedColumnTest"); + + public Task DisposeAsync() + { + TestStore.Dispose(); + return Task.CompletedTask; + } } diff --git a/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs index c7619c547..fbfe6f2c2 100644 --- a/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs @@ -45,7 +45,7 @@ protected override ITestStoreFactory TestStoreFactory // We don't support DateTimeOffset with non-zero offsets, so we unfortunately need to override the entire seeding method. // See https://github.com/dotnet/efcore/issues/26068 - protected override void Seed(ApplicationDbContext context) + protected override async Task SeedAsync(ApplicationDbContext context) { var attendees1 = new List { @@ -162,7 +162,7 @@ protected override void Seed(ApplicationDbContext context) } context.AddRange(tracks.Values); - context.SaveChanges(); + await context.SaveChangesAsync(); } } } diff --git a/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs b/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs index 152082d8b..2cac8e343 100644 --- a/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs @@ -7,25 +7,25 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class ConnectionSpecificationTest { [Fact] - public void Can_specify_connection_string_in_OnConfiguring() + public async Task Can_specify_connection_string_in_OnConfiguring() { var serviceProvider = new ServiceCollection() .AddDbContext() .BuildServiceProvider(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = serviceProvider.GetRequiredService(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = serviceProvider.GetRequiredService(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } [Fact] - public void Can_specify_connection_string_in_OnConfiguring_with_default_service_provider() + public async Task Can_specify_connection_string_in_OnConfiguring_with_default_service_provider() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = new StringInOnConfiguringContext(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = new StringInOnConfiguringContext(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } private class StringInOnConfiguringContext : NorthwindContextBase @@ -35,25 +35,25 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) } [Fact] - public void Can_specify_connection_in_OnConfiguring() + public async Task Can_specify_connection_in_OnConfiguring() { var serviceProvider = new ServiceCollection() .AddScoped(_ => new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)) .AddDbContext().BuildServiceProvider(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = serviceProvider.GetRequiredService(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = serviceProvider.GetRequiredService(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } [Fact] - public void Can_specify_connection_in_OnConfiguring_with_default_service_provider() + public async Task Can_specify_connection_in_OnConfiguring_with_default_service_provider() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = new ConnectionInOnConfiguringContext(new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = new ConnectionInOnConfiguringContext(new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } private class ConnectionInOnConfiguringContext(NpgsqlConnection connection) : NorthwindContextBase @@ -104,28 +104,28 @@ public void Throws_if_no_config_without_UseNpgsql() private class NoUseNpgsqlContext : NorthwindContextBase; [Fact] - public void Can_depend_on_DbContextOptions() + public async Task Can_depend_on_DbContextOptions() { var serviceProvider = new ServiceCollection() .AddScoped(_ => new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)) .AddDbContext() .BuildServiceProvider(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = serviceProvider.GetRequiredService(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = serviceProvider.GetRequiredService(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } [Fact] - public void Can_depend_on_DbContextOptions_with_default_service_provider() + public async Task Can_depend_on_DbContextOptions_with_default_service_provider() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = new OptionsContext( + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = new OptionsContext( new DbContextOptions(), new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } private class OptionsContext(DbContextOptions options, NpgsqlConnection connection) @@ -148,25 +148,25 @@ public override void Dispose() } [Fact] - public void Can_depend_on_non_generic_options_when_only_one_context() + public async Task Can_depend_on_non_generic_options_when_only_one_context() { var serviceProvider = new ServiceCollection() .AddDbContext() .BuildServiceProvider(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = serviceProvider.GetRequiredService(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = serviceProvider.GetRequiredService(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } [Fact] - public void Can_depend_on_non_generic_options_when_only_one_context_with_default_service_provider() + public async Task Can_depend_on_non_generic_options_when_only_one_context_with_default_service_provider() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = new NonGenericOptionsContext(new DbContextOptions()); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = new NonGenericOptionsContext(new DbContextOptions()); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } private class NonGenericOptionsContext(DbContextOptions options) : NorthwindContextBase(options) @@ -222,55 +222,55 @@ private class Customer #region Added for Npgsql [Fact] - public void Can_create_admin_connection_with_data_source() + public async Task Can_create_admin_connection_with_data_source() { - using var dataSource = NpgsqlDataSource.Create(NpgsqlTestStore.NorthwindConnectionString); + await using var dataSource = NpgsqlDataSource.Create(NpgsqlTestStore.NorthwindConnectionString); - using var _ = NpgsqlTestStore.GetNorthwindStore(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql(dataSource, b => b.ApplyConfiguration()); - using var context = new GeneralOptionsContext(optionsBuilder.Options); + await using var context = new GeneralOptionsContext(optionsBuilder.Options); var relationalConnection = context.GetService(); - using var adminConnection = relationalConnection.CreateAdminConnection(); + await using var adminConnection = relationalConnection.CreateAdminConnection(); Assert.Equal("postgres", new NpgsqlConnectionStringBuilder(adminConnection.ConnectionString).Database); - adminConnection.Open(); + await adminConnection.OpenAsync(CancellationToken.None); } [Fact] - public void Can_create_admin_connection_with_connection_string() + public async Task Can_create_admin_connection_with_connection_string() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql(NpgsqlTestStore.NorthwindConnectionString, b => b.ApplyConfiguration()); - using var context = new GeneralOptionsContext(optionsBuilder.Options); + await using var context = new GeneralOptionsContext(optionsBuilder.Options); var relationalConnection = context.GetService(); - using var adminConnection = relationalConnection.CreateAdminConnection(); + await using var adminConnection = relationalConnection.CreateAdminConnection(); Assert.Equal("postgres", new NpgsqlConnectionStringBuilder(adminConnection.ConnectionString).Database); - adminConnection.Open(); + await adminConnection.OpenAsync(CancellationToken.None); } [Fact] - public void Can_create_admin_connection_with_connection() + public async Task Can_create_admin_connection_with_connection() { - using var connection = new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString); + await using var connection = new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString); connection.Open(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql(connection, b => b.ApplyConfiguration()); - using var context = new GeneralOptionsContext(optionsBuilder.Options); + await using var context = new GeneralOptionsContext(optionsBuilder.Options); var relationalConnection = context.GetService(); - using var adminConnection = relationalConnection.CreateAdminConnection(); + await using var adminConnection = relationalConnection.CreateAdminConnection(); Assert.Equal("postgres", new NpgsqlConnectionStringBuilder(adminConnection.ConnectionString).Database); diff --git a/test/EFCore.PG.FunctionalTests/ConvertToProviderTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConvertToProviderTypesNpgsqlTest.cs index 8c7a1d96b..50fd6c5ee 100644 --- a/test/EFCore.PG.FunctionalTests/ConvertToProviderTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConvertToProviderTypesNpgsqlTest.cs @@ -12,53 +12,56 @@ public ConvertToProviderTypesNpgsqlTest(ConvertToProviderTypesNpgsqlFixture fixt Fixture.TestSqlLoggerFactory.Clear(); } - [Fact] - public override void Can_insert_and_read_with_max_length_set() - { - const string shortString = "Sky"; - var shortBinary = new byte[] { 8, 8, 7, 8, 7 }; - - var longString = new string('X', Fixture.LongStringLength); - var longBinary = new byte[Fixture.LongStringLength]; - for (var i = 0; i < longBinary.Length; i++) - { - longBinary[i] = (byte)i; - } - - using (var context = CreateContext()) - { - context.Set().Add( - new MaxLengthDataTypes - { - Id = 79, - String3 = shortString, - ByteArray5 = shortBinary, - String9000 = longString, - ByteArray9000 = longBinary - }); - - Assert.Equal(1, context.SaveChanges()); - } - - using (var context = CreateContext()) - { - var dt = context.Set().Where(e => e.Id == 79).ToList().Single(); - - Assert.Equal(shortString, dt.String3); - Assert.Equal(shortBinary, dt.ByteArray5); - Assert.Equal(longString, dt.String9000); - Assert.Equal(longBinary, dt.ByteArray9000); - } - } + // [Fact] + // public override void Can_insert_and_read_with_max_length_set() + // { + // const string shortString = "Sky"; + // var shortBinary = new byte[] { 8, 8, 7, 8, 7 }; + // + // var longString = new string('X', Fixture.LongStringLength); + // var longBinary = new byte[Fixture.LongStringLength]; + // for (var i = 0; i < longBinary.Length; i++) + // { + // longBinary[i] = (byte)i; + // } + // + // using (var context = CreateContext()) + // { + // context.Set().Add( + // new MaxLengthDataTypes + // { + // Id = 79, + // String3 = shortString, + // ByteArray5 = shortBinary, + // String9000 = longString, + // ByteArray9000 = longBinary + // }); + // + // Assert.Equal(1, context.SaveChanges()); + // } + // + // using (var context = CreateContext()) + // { + // var dt = context.Set().Where(e => e.Id == 79).ToList().Single(); + // + // Assert.Equal(shortString, dt.String3); + // Assert.Equal(shortBinary, dt.ByteArray5); + // Assert.Equal(longString, dt.String9000); + // Assert.Equal(longBinary, dt.ByteArray9000); + // } + // } [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_non_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_non_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_object_backed_data_types() { } + public override Task Can_insert_and_read_back_object_backed_data_types() + => Task.CompletedTask; public class ConvertToProviderTypesNpgsqlFixture : ConvertToProviderTypesFixtureBase { diff --git a/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs index 10f0cc9d9..a08007fe7 100644 --- a/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs @@ -6,19 +6,24 @@ public class CustomConvertersNpgsqlTest(CustomConvertersNpgsqlTest.CustomConvert : CustomConvertersTestBase(fixture) { // Disabled: PostgreSQL is case-sensitive - public override void Can_insert_and_read_back_with_case_insensitive_string_key() { } + public override Task Can_insert_and_read_back_with_case_insensitive_string_key() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_non_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_non_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_object_backed_data_types() { } + public override Task Can_insert_and_read_back_object_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_query_using_any_data_type_nullable_shadow() { } + public override Task Can_query_using_any_data_type_nullable_shadow() + => Task.CompletedTask; public override void Value_conversion_on_enum_collection_contains() => Assert.Contains( diff --git a/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs index 634a16cf2..71b56bd3a 100644 --- a/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs @@ -11,20 +11,14 @@ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransact protected override TestHelpers TestHelpers => NpgsqlTestHelpers.Instance; - public override void StringLengthAttribute_throws_while_inserting_value_longer_than_max_length() - { - // Npgsql does not support length - } + public override Task StringLengthAttribute_throws_while_inserting_value_longer_than_max_length() + => Task.CompletedTask; // Npgsql does not support length - public override void TimestampAttribute_throws_if_value_in_database_changed() - { - // Npgsql does not support length - } + public override Task TimestampAttribute_throws_if_value_in_database_changed() + => Task.CompletedTask; // Npgsql does not support length - public override void MaxLengthAttribute_throws_while_inserting_value_longer_than_max_length() - { - // Npgsql does not support length - } + public override Task MaxLengthAttribute_throws_while_inserting_value_longer_than_max_length() + => Task.CompletedTask; // Npgsql does not support length public class DataAnnotationNpgsqlFixture : DataAnnotationRelationalFixtureBase { diff --git a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj index ce5bd43a8..084ec149f 100644 --- a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj +++ b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj @@ -19,6 +19,9 @@ + + + diff --git a/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs b/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs index 2ef271f86..f155b67ba 100644 --- a/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs +++ b/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs @@ -20,7 +20,7 @@ private static async Task Can_use_an_existing_closed_connection_test(bool openCo .AddEntityFrameworkNpgsql() .BuildServiceProvider(); - await using (var store = NpgsqlTestStore.GetNorthwindStore()) + await using (var store = await NpgsqlTestStore.GetNorthwindStoreAsync()) { store.CloseConnection(); diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index 593c41c26..d5307dbbd 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -11,165 +11,192 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class JsonTypesNpgsqlTest : JsonTypesRelationalTestBase { - public override void Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) - { - // Relational databases don't support unsigned numeric types, so ulong is value-converted to long - if (value == EnumU64.Max) - { - json = """{"Prop":-1}"""; - } - - base.Can_read_write_ulong_enum_JSON_values(value, json); - } + #region Nested collections (unsupported) - public override void Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json) - { - // Relational databases don't support unsigned numeric types, so ulong is value-converted to long - if (Equals(value, ulong.MaxValue)) - { - json = """{"Prop":-1}"""; - } - - base.Can_read_write_nullable_ulong_enum_JSON_values(value, json); - } + // The following tests are disabled because they use nested collections, which are not supported by EFCore.PG (arrays of arrays aren't + // supported). - public override void Can_read_write_collection_of_ulong_enum_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(EnumU64CollectionType.EnumU64), - [ - EnumU64.Min, - EnumU64.Max, - EnumU64.Default, - EnumU64.One, - (EnumU64)8 - ], - // Relational databases don't support unsigned numeric types, so ulong is value-converted to long - """{"Prop":[0,-1,0,1,8]}""", - mappedCollection: true); - - public override void Can_read_write_collection_of_nullable_ulong_enum_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(NullableEnumU64CollectionType.EnumU64), - [ - EnumU64.Min, - null, - EnumU64.Max, - EnumU64.Default, - EnumU64.One, - (EnumU64?)8 - ], - // Relational databases don't support unsigned numeric types, so ulong is value-converted to long - """{"Prop":[0,null,-1,0,1,8]}""", - mappedCollection: true); + public override Task Can_read_write_array_of_array_of_array_of_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_array_of_array_of_int_JSON_values()); + + public override Task Can_read_write_array_of_list_of_array_of_IPAddress_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_array_of_IPAddress_JSON_values()); + + public override Task Can_read_write_array_of_list_of_array_of_string_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_array_of_string_JSON_values()); + + public override Task Can_read_write_array_of_list_of_binary_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_binary_JSON_values(expected)); + + public override Task Can_read_write_array_of_list_of_GUID_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_GUID_JSON_values(expected)); + + public override Task Can_read_write_array_of_list_of_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_int_JSON_values()); + + public override Task Can_read_write_array_of_list_of_IPAddress_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_IPAddress_JSON_values()); + + public override Task Can_read_write_array_of_list_of_string_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_string_JSON_values()); + + public override Task Can_read_write_array_of_list_of_ulong_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_ulong_JSON_values()); + + public override Task Can_read_write_list_of_array_of_GUID_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_GUID_JSON_values(expected)); + + public override Task Can_read_write_list_of_array_of_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_int_JSON_values()); + + public override Task Can_read_write_list_of_array_of_IPAddress_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_IPAddress_JSON_values()); + + public override Task Can_read_write_list_of_array_of_list_of_array_of_binary_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_array_of_binary_JSON_values(expected)); + + public override Task Can_read_write_list_of_array_of_list_of_IPAddress_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_IPAddress_JSON_values()); + + public override Task Can_read_write_list_of_array_of_list_of_string_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_string_JSON_values()); + + public override Task Can_read_write_list_of_array_of_list_of_ulong_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_ulong_JSON_values()); + + public override Task Can_read_write_list_of_array_of_nullable_GUID_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_GUID_JSON_values(expected)); + + public override Task Can_read_write_list_of_array_of_nullable_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_int_JSON_values()); + + public override Task Can_read_write_list_of_array_of_nullable_ulong_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_ulong_JSON_values()); + + public override Task Can_read_write_list_of_array_of_string_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_string_JSON_values()); + + public override Task Can_read_write_list_of_array_of_ulong_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_ulong_JSON_values()); + + public override Task Can_read_write_list_of_list_of_list_of_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_list_of_list_of_int_JSON_values()); + + #endregion Nested collections (unsupported) + + // IEnumerable property + public override Task Can_read_write_list_of_array_of_binary_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_binary_JSON_values(expected)); + + // public override Task Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) + // { + // // Relational databases don't support unsigned numeric types, so ulong is value-converted to long + // if (value == EnumU64.Max) + // { + // json = """{"Prop":-1}"""; + // } + // + // return base.Can_read_write_ulong_enum_JSON_values(value, json); + // } + // + // public override void Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json) + // { + // // Relational databases don't support unsigned numeric types, so ulong is value-converted to long + // if (Equals(value, ulong.MaxValue)) + // { + // json = """{"Prop":-1}"""; + // } + // + // base.Can_read_write_nullable_ulong_enum_JSON_values(value, json); + // } + // + // public override void Can_read_write_collection_of_ulong_enum_JSON_values() + // => Can_read_and_write_JSON_value>( + // nameof(EnumU64CollectionType.EnumU64), + // [ + // EnumU64.Min, + // EnumU64.Max, + // EnumU64.Default, + // EnumU64.One, + // (EnumU64)8 + // ], + // // Relational databases don't support unsigned numeric types, so ulong is value-converted to long + // """{"Prop":[0,-1,0,1,8]}""", + // mappedCollection: true); + // + // public override void Can_read_write_collection_of_nullable_ulong_enum_JSON_values() + // => Can_read_and_write_JSON_value>( + // nameof(NullableEnumU64CollectionType.EnumU64), + // [ + // EnumU64.Min, + // null, + // EnumU64.Max, + // EnumU64.Default, + // EnumU64.One, + // (EnumU64?)8 + // ], + // // Relational databases don't support unsigned numeric types, so ulong is value-converted to long + // """{"Prop":[0,null,-1,0,1,8]}""", + // mappedCollection: true); #region TimeSpan - public override void Can_read_write_TimeSpan_JSON_values(string value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } - - [ConditionalTheory] - [InlineData("-10675199.02:48:05.477580", """{"Prop":"-10675199 02:48:05.47758"}""")] - [InlineData("10675199.02:48:05.477580", """{"Prop":"10675199 02:48:05.47758"}""")] - [InlineData("00:00:00", """{"Prop":"00:00:00"}""")] - [InlineData("12:23:23.801885", """{"Prop":"12:23:23.801885"}""")] - public virtual void Can_read_write_TimeSpan_JSON_values_npgsql(string value, string json) - => Can_read_and_write_JSON_value( - nameof(TimeSpanType.TimeSpan), - TimeSpan.Parse(value, CultureInfo.InvariantCulture), - json); - - public override void Can_read_write_nullable_TimeSpan_JSON_values(string? value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } - - [ConditionalTheory] - [InlineData("-10675199.02:48:05.47758", """{"Prop":"-10675199 02:48:05.47758"}""")] - [InlineData("10675199.02:48:05.47758", """{"Prop":"10675199 02:48:05.47758"}""")] - [InlineData("00:00:00", """{"Prop":"00:00:00"}""")] - [InlineData("12:23:23.801885", """{"Prop":"12:23:23.801885"}""")] - [InlineData(null, """{"Prop":null}""")] - public virtual void Can_read_write_nullable_TimeSpan_JSON_values_npgsql(string? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableTimeSpanType.TimeSpan), - value == null ? default(TimeSpan?) : TimeSpan.Parse(value), json); - - [ConditionalFact] - public override void Can_read_write_collection_of_TimeSpan_JSON_values() - { - Can_read_and_write_JSON_value>( - nameof(TimeSpanCollectionType.TimeSpan), - [ - new(1, 2, 3, 4), - new(0, 2, 3, 4, 5, 678) - // TimeSpan.MaxValue - ], - """{"Prop":["1 02:03:04","02:03:04.005678"]}""", - mappedCollection: true); - } - - [ConditionalFact] - public override void Can_read_write_collection_of_nullable_TimeSpan_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(NullableTimeSpanCollectionType.TimeSpan), - [ - new(1, 2, 3, 4), - new(0, 2, 3, 4, 5, 678), - // TimeSpan.MaxValue - null - ], - """{"Prop":["1 02:03:04","02:03:04.005678",null]}""", - mappedCollection: true); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_TimeSpan_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_TimeSpan_JSON_values_npgsql + public override Task Can_read_write_TimeSpan_JSON_values(string value, string json) + => Task.CompletedTask; + + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_TimeSpan_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_nullable_TimeSpan_JSON_values_npgsql + public override Task Can_read_write_nullable_TimeSpan_JSON_values(string? value, string json) + => Task.CompletedTask; + + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_TimeSpan_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_collection_of_TimeSpan_JSON_values_npgsql + public override Task Can_read_write_collection_of_TimeSpan_JSON_values() + => Task.CompletedTask; + + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_TimeSpan_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_collection_of_nullable_TimeSpan_JSON_values_npgsql + public override Task Can_read_write_collection_of_nullable_TimeSpan_JSON_values() + => Task.CompletedTask; #endregion TimeSpan #region DateOnly - public override void Can_read_write_DateOnly_JSON_values(string value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_DateOnly_JSON_values_npgsql instead. + public override Task Can_read_write_DateOnly_JSON_values(string value, string json) + => Task.CompletedTask; [ConditionalTheory] [InlineData("1/1/0001", """{"Prop":"-infinity"}""")] [InlineData("12/31/9999", """{"Prop":"infinity"}""")] [InlineData("5/29/2023", """{"Prop":"2023-05-29"}""")] - public virtual void Can_read_write_DateOnly_JSON_values_npgsql(string value, string json) + public virtual Task Can_read_write_DateOnly_JSON_values_npgsql(string value, string json) => Can_read_and_write_JSON_value( nameof(DateOnlyType.DateOnly), DateOnly.Parse(value, CultureInfo.InvariantCulture), json); - public override void Can_read_write_nullable_DateOnly_JSON_values(string? value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_DateOnly_JSON_values_sqlite instead. - } - - [ConditionalTheory] - [InlineData("1/1/0001", """{"Prop":"-infinity"}""")] - [InlineData("12/31/9999", """{"Prop":"infinity"}""")] - [InlineData("5/29/2023", """{"Prop":"2023-05-29"}""")] - [InlineData(null, """{"Prop":null}""")] - public virtual void Can_read_write_nullable_DateOnly_JSON_values_npgsql(string? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableDateOnlyType.DateOnly), - value == null ? default(DateOnly?) : DateOnly.Parse(value, CultureInfo.InvariantCulture), json); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_DateOnly_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_nullable_DateOnly_JSON_values_npgsql + public override Task Can_read_write_nullable_DateOnly_JSON_values(string? value, string json) + => Task.CompletedTask; - [ConditionalFact] - public virtual void Can_read_write_DateOnly_JSON_values_infinity() - { - Can_read_and_write_JSON_value(nameof(DateOnlyType.DateOnly), DateOnly.MinValue, """{"Prop":"-infinity"}"""); - Can_read_and_write_JSON_value(nameof(DateOnlyType.DateOnly), DateOnly.MaxValue, """{"Prop":"infinity"}"""); - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_TimeSpan_JSON_values_npgsql instead. + public override Task Can_read_write_collection_of_DateOnly_JSON_values() + => Task.CompletedTask; [ConditionalFact] - public override void Can_read_write_collection_of_DateOnly_JSON_values() - => Can_read_and_write_JSON_value>( + public virtual Task Can_read_write_collection_of_DateOnly_JSON_values_npgsql() + => Can_read_and_write_JSON_value>( nameof(DateOnlyCollectionType.DateOnly), [ DateOnly.MinValue, @@ -179,8 +206,13 @@ public override void Can_read_write_collection_of_DateOnly_JSON_values() """{"Prop":["-infinity","2023-05-29","infinity"]}""", mappedCollection: true); + protected class NpgsqlDateOnlyCollectionType + { + public List DateOnly { get; set; } = null!; + } + [ConditionalFact] - public override void Can_read_write_collection_of_nullable_DateOnly_JSON_values() + public override Task Can_read_write_collection_of_nullable_DateOnly_JSON_values() => Can_read_and_write_JSON_value>( nameof(NullableDateOnlyCollectionType.DateOnly), [ @@ -196,136 +228,113 @@ public override void Can_read_write_collection_of_nullable_DateOnly_JSON_values( #region DateTime - public override void Can_read_write_DateTime_JSON_values(string value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_DateTime_JSON_values_npgsql instead. + public override Task Can_read_write_DateTime_JSON_values(string value, string json) + => Task.CompletedTask; [ConditionalTheory] - [InlineData("2023-05-29T10:52:47.206435Z", """{"Prop":"2023-05-29T10:52:47.206435Z"}""")] - public virtual void Can_read_write_DateTime_JSON_values_npgsql(string value, string json) - => Can_read_and_write_JSON_value( - nameof(DateTimeType.DateTime), - DateTime.Parse(value, CultureInfo.InvariantCulture).ToUniversalTime(), json); - - [ConditionalFact] - public virtual void Can_read_write_DateTime_JSON_values_npgsql_infinity() + [InlineData("0001-01-01T00:00:00.0000000", """{"Prop":"-infinity"}""")] + [InlineData("9999-12-31T23:59:59.9999999", """{"Prop":"infinity"}""")] + [InlineData("2023-05-29T10:52:47.2064350", """{"Prop":"2023-05-29T10:52:47.206435"}""")] + public virtual Task Can_read_write_DateTime_JSON_values_npgsql(string value, string json) => Can_read_and_write_JSON_value( + modelBuilder => modelBuilder + .Entity() + .HasNoKey() + .Property(nameof(DateTimeType.DateTime)) + .HasColumnType("timestamp without time zone"), + configureConventions: null, nameof(DateTimeType.DateTime), - DateTime.MaxValue, """{"Prop":"infinity"}"""); + DateTime.Parse(value, CultureInfo.InvariantCulture), json); - [ConditionalFact] - public virtual void Can_read_write_DateTime_JSON_values_npgsql_negative_infinity() - => Can_read_and_write_JSON_value( - nameof(DateTimeType.DateTime), - DateTime.MinValue, """{"Prop":"-infinity"}"""); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_DateTime_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_nullable_DateTime_JSON_values_npgsql + public override Task Can_read_write_nullable_DateTime_JSON_values(string? value, string json) + => Task.CompletedTask; - public override void Can_read_write_nullable_DateTime_JSON_values(string? value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_TimeSpan_JSON_values_npgsql instead. + public override Task Can_read_write_collection_of_DateTime_JSON_values(string expected) + => Task.CompletedTask; [ConditionalTheory] - [InlineData("0001-01-01T00:00:00.0000000", """{"Prop":"-infinity"}""")] - [InlineData("9999-12-31T23:59:59.9999999", """{"Prop":"infinity"}""")] - [InlineData("2023-05-29T10:52:47.206435", """{"Prop":"2023-05-29T10:52:47.206435Z"}""")] - [InlineData(null, """{"Prop":null}""")] - public virtual void Can_read_write_nullable_DateTime_JSON_values_npgsql(string? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableDateTimeType.DateTime), - value == null - ? default(DateTime?) - : DateTime.SpecifyKind(DateTime.Parse(value, CultureInfo.InvariantCulture), DateTimeKind.Utc), json); - - [ConditionalFact] - public override void Can_read_write_collection_of_DateTime_JSON_values() - => Can_read_and_write_JSON_value>( + [InlineData("""{"Prop":["-infinity","2023-05-29T10:52:47","infinity"]}""")] + public virtual Task Can_read_write_collection_of_DateTime_JSON_values_npgsql(string expected) + => Can_read_and_write_JSON_value>( + modelBuilder => modelBuilder + .Entity() + .HasNoKey() + .PrimitiveCollection(nameof(DateTimeCollectionType.DateTime)) + .ElementType() + .HasStoreType("timestamp without time zone"), + configureConventions: null, nameof(DateTimeCollectionType.DateTime), [ DateTime.MinValue, - new(2023, 5, 29, 10, 52, 47, DateTimeKind.Utc), + new(2023, 5, 29, 10, 52, 47), DateTime.MaxValue ], - """{"Prop":["-infinity","2023-05-29T10:52:47Z","infinity"]}""", + expected, mappedCollection: true); - [ConditionalFact] - public override void Can_read_write_collection_of_nullable_DateTime_JSON_values() + protected class NpgsqlDateTimeCollectionType + { + public List DateTime { get; set; } = null!; + } + + public override Task Can_read_write_collection_of_nullable_DateTime_JSON_values(string expected) => Can_read_and_write_JSON_value>( + modelBuilder => modelBuilder + .Entity() + .HasNoKey() + .PrimitiveCollection(nameof(NullableDateTimeCollectionType.DateTime)) + .ElementType() + .HasStoreType("timestamp without time zone"), + configureConventions: null, nameof(NullableDateTimeCollectionType.DateTime), [ DateTime.MinValue, null, - new(2023, 5, 29, 10, 52, 47, DateTimeKind.Utc), + new(2023, 5, 29, 10, 52, 47), DateTime.MaxValue ], - """{"Prop":["-infinity",null,"2023-05-29T10:52:47Z","infinity"]}""", + """{"Prop":["-infinity",null,"2023-05-29T10:52:47","infinity"]}""", mappedCollection: true); - [ConditionalFact] - public virtual void Can_read_write_DateTime_timestamptz_JSON_values_infinity() - { - Can_read_and_write_JSON_value(nameof(DateTimeType.DateTime), DateTime.MinValue, """{"Prop":"-infinity"}"""); - Can_read_and_write_JSON_value(nameof(DateTimeType.DateTime), DateTime.MaxValue, """{"Prop":"infinity"}"""); - } - - [ConditionalFact] - public virtual void Can_read_write_DateTime_timestamp_JSON_values_infinity() - { - Can_read_and_write_JSON_property_value( - b => b.HasColumnType("timestamp without time zone"), - nameof(DateTimeType.DateTime), - DateTime.MinValue, - """{"Prop":"-infinity"}"""); - - Can_read_and_write_JSON_property_value( - b => b.HasColumnType("timestamp without time zone"), - nameof(DateTimeType.DateTime), - DateTime.MaxValue, - """{"Prop":"infinity"}"""); - } - #endregion DateTime #region DateTimeOffset - public override void Can_read_write_DateTimeOffset_JSON_values(string value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_DateTimeOffset_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_DateTimeOffset_JSON_values_npgsql instead. + public override Task Can_read_write_DateTimeOffset_JSON_values(string value, string json) + => Task.CompletedTask; [ConditionalTheory] - [InlineData("0001-01-01T00:00:00.000000-01:00", """{"Prop":"0001-01-01T00:00:00-01:00"}""")] - [InlineData("9999-12-31T23:59:59.999999+02:00", """{"Prop":"9999-12-31T23:59:59.999999\u002B02:00"}""")] - [InlineData("0001-01-01T00:00:00.000000-03:00", """{"Prop":"0001-01-01T00:00:00-03:00"}""")] - [InlineData("2023-05-29T11:11:15.567285+04:00", """{"Prop":"2023-05-29T11:11:15.567285\u002B04:00"}""")] - public virtual void Can_read_write_DateTimeOffset_JSON_values_npgsql(string value, string json) + [InlineData("0001-01-01T00:00:00.0000000-01:00", """{"Prop":"0001-01-01T00:00:00-01:00"}""")] + [InlineData("9999-12-31T23:59:59.9999990+02:00", """{"Prop":"9999-12-31T23:59:59.999999\u002B02:00"}""")] + [InlineData("0001-01-01T00:00:00.0000000-03:00", """{"Prop":"0001-01-01T00:00:00-03:00"}""")] + [InlineData("2023-05-29T11:11:15.5672850+04:00", """{"Prop":"2023-05-29T11:11:15.567285\u002B04:00"}""")] + public virtual Task Can_read_write_DateTimeOffset_JSON_values_npgsql(string value, string json) => Can_read_and_write_JSON_value( nameof(DateTimeOffsetType.DateTimeOffset), DateTimeOffset.Parse(value, CultureInfo.InvariantCulture), json); - public override void Can_read_write_nullable_DateTimeOffset_JSON_values(string? value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_DateTimeOffset_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_DateTimeOffset_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_nullable_DateTimeOffset_JSON_values_npgsql + public override Task Can_read_write_nullable_DateTimeOffset_JSON_values(string? value, string json) + => Task.CompletedTask; - [ConditionalTheory] - [InlineData("0001-01-01T00:00:00.000000-01:00", """{"Prop":"0001-01-01T00:00:00-01:00"}""")] - [InlineData("9999-12-31T23:59:59.999999+02:00", """{"Prop":"9999-12-31T23:59:59.999999\u002B02:00"}""")] - [InlineData("0001-01-01T00:00:00.000000-03:00", """{"Prop":"0001-01-01T00:00:00-03:00"}""")] - [InlineData("2023-05-29T11:11:15.567285+04:00", """{"Prop":"2023-05-29T11:11:15.567285\u002B04:00"}""")] - [InlineData(null, """{"Prop":null}""")] - public virtual void Can_read_write_nullable_DateTimeOffset_JSON_values_npgsql(string? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableDateTimeOffsetType.DateTimeOffset), - value == null ? default(DateTimeOffset?) : DateTimeOffset.Parse(value, CultureInfo.InvariantCulture), json); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_collection_of_DateTimeOffset_JSON_values_npgsql instead. + public override Task Can_read_write_collection_of_DateTimeOffset_JSON_values(string expected) + => Task.CompletedTask; [ConditionalFact] - public override void Can_read_write_collection_of_DateTimeOffset_JSON_values() + public virtual Task Can_read_write_collection_of_DateTimeOffset_JSON_values_npgsql() => Can_read_and_write_JSON_value>( nameof(DateTimeOffsetCollectionType.DateTimeOffset), [ @@ -338,8 +347,13 @@ public override void Can_read_write_collection_of_DateTimeOffset_JSON_values() """{"Prop":["-infinity","2023-05-29T10:52:47-02:00","2023-05-29T10:52:47\u002B00:00","2023-05-29T10:52:47\u002B02:00","infinity"]}""", mappedCollection: true); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values_npgsql instead. + public override Task Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values(string expected) + => Task.CompletedTask; + [ConditionalFact] - public override void Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values() + public virtual Task Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values_npgsql() => Can_read_and_write_JSON_value>( nameof(NullableDateTimeOffsetCollectionType.DateTimeOffset), [ @@ -367,7 +381,7 @@ public virtual void Can_read_write_timetz_JSON_values(string value, string json) [ConditionalTheory] [InlineData(Mood.Happy, """{"Prop":"Happy"}""")] [InlineData(Mood.Sad, """{"Prop":"Sad"}""")] - public virtual void Can_read_write_pg_enum_JSON_values(Mood value, string json) + public virtual Task Can_read_write_pg_enum_JSON_values(Mood value, string json) => Can_read_and_write_JSON_value( nameof(EnumType.Mood), value, @@ -387,7 +401,7 @@ public enum Mood [ConditionalTheory] [InlineData(new[] { 1, 2, 3 }, """{"Prop":[1,2,3]}""")] [InlineData(new int[0], """{"Prop":[]}""")] - public virtual void Can_read_write_array_JSON_values(int[] value, string json) + public virtual Task Can_read_write_array_JSON_values(int[] value, string json) => Can_read_and_write_JSON_value( nameof(ArrayType.Array), value, @@ -400,9 +414,9 @@ protected class ArrayType } [ConditionalFact] - public virtual void Cannot_read_write_multidimensional_array_JSON_values() + public virtual Task Cannot_read_write_multidimensional_array_JSON_values() // EF currently throws NRE when the type mapping has no JsonValueReaderWriter (this has been improved for 9.0) - => Assert.Throws( + => Assert.ThrowsAsync( () => Can_read_and_write_JSON_value( nameof(MultidimensionalArrayType.MultidimensionalArray), new[,] { { 1, 2 }, { 3, 4 } }, @@ -414,7 +428,7 @@ protected class MultidimensionalArrayType } [ConditionalFact] - public virtual void Can_read_write_BigInteger_JSON_values() + public virtual Task Can_read_write_BigInteger_JSON_values() => Can_read_and_write_JSON_value( nameof(BigIntegerType.BigInteger), new BigInteger(ulong.MaxValue), @@ -429,7 +443,7 @@ protected class BigIntegerType [InlineData(new[] { true, false, true }, """{"Prop":"101"}""")] [InlineData(new[] { true, false, true, true, false, true, false, true, false }, """{"Prop":"101101010"}""")] [InlineData(new bool[0], """{"Prop":""}""")] - public virtual void Can_read_write_BitArray_JSON_values(bool[] value, string json) + public virtual Task Can_read_write_BitArray_JSON_values(bool[] value, string json) => Can_read_and_write_JSON_value( nameof(BitArrayType.BitArray), new BitArray(value), @@ -443,7 +457,7 @@ protected class BitArrayType [ConditionalTheory] [InlineData(1000, """{"Prop":"0/3E8"}""")] [InlineData(0, """{"Prop":"0/0"}""")] - public virtual void Can_read_write_LogSequenceNumber_JSON_values(ulong value, string json) + public virtual Task Can_read_write_LogSequenceNumber_JSON_values(ulong value, string json) => Can_read_and_write_JSON_value( nameof(LogSequenceNumberType.LogSequenceNumber), new NpgsqlLogSequenceNumber(value), diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs index 18a8eadcc..996e3402b 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs @@ -7,6 +7,25 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations public class MigrationsInfrastructureNpgsqlTest(MigrationsInfrastructureNpgsqlTest.MigrationsInfrastructureNpgsqlFixture fixture) : MigrationsInfrastructureTestBase(fixture) { + // TODO: The following test the migration lock, which isn't yet implemented - waiting for EF-side fixes in rc.2 + #region Unskip for 9.0.0-rc.2 + + public override void Can_apply_one_migration_in_parallel() + { + } + + public override Task Can_apply_one_migration_in_parallel_async() + => Task.CompletedTask; + + public override void Can_apply_second_migration_in_parallel() + { + } + + public override Task Can_apply_second_migration_in_parallel_async() + => Task.CompletedTask; + + #endregion Unskip for 9.0.0-rc.2 + public override void Can_get_active_provider() { base.Can_get_active_provider(); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 2277793c9..1f11df6bf 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2575,42 +2575,10 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'dbo2') THEN """, // """ -CREATE SEQUENCE dbo2."TestSequence" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE CACHE 20; +CREATE SEQUENCE dbo2."TestSequence" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE; """); } - public override async Task Create_sequence_nocache() - { - await base.Create_sequence_nocache(); - - AssertSql("""CREATE SEQUENCE "Alpha" START WITH 1 INCREMENT BY 1 NO CYCLE CACHE 1;"""); - } - - public override async Task Create_sequence_cache() - { - await base.Create_sequence_cache(); - - AssertSql("""CREATE SEQUENCE "Beta" START WITH 1 INCREMENT BY 1 NO CYCLE CACHE 20;"""); - } - - public override async Task Create_sequence_default_cache() - { - // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as - // disabling caching. - await Test( - builder => { }, - builder => builder.HasSequence("Gamma").UseCache(), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.Equal("Gamma", sequence.Name); - Assert.False(sequence.IsCached); - Assert.Null(sequence.CacheSize); - }); - - AssertSql("""CREATE SEQUENCE "Gamma" START WITH 1 INCREMENT BY 1 NO CYCLE;"""); - } - [Fact] public virtual async Task Create_sequence_smallint() { @@ -2634,7 +2602,7 @@ public override async Task Alter_sequence_all_settings() AssertSql( """ -ALTER SEQUENCE foo INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE CACHE 20; +ALTER SEQUENCE foo INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE; """, // """ @@ -2647,71 +2615,10 @@ public override async Task Alter_sequence_increment_by() { await base.Alter_sequence_increment_by(); - AssertSql("ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"); - } - - public override async Task Alter_sequence_default_cache_to_cache() - { - await base.Alter_sequence_default_cache_to_cache(); - - AssertSql("""ALTER SEQUENCE "Delta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); - } - - public override async Task Alter_sequence_default_cache_to_nocache() - { - await base.Alter_sequence_default_cache_to_nocache(); - - AssertSql("""ALTER SEQUENCE "Epsilon" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); - } - - public override async Task Alter_sequence_cache_to_nocache() - { - await base.Alter_sequence_cache_to_nocache(); - - AssertSql("""ALTER SEQUENCE "Zeta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); - } - - public override async Task Alter_sequence_cache_to_default_cache() - { - // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as - // disabling caching. - await Test( - builder => builder.HasSequence("Eta").UseCache(20), - builder => { }, - builder => builder.HasSequence("Eta").UseCache(), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.False(sequence.IsCached); - Assert.Null(sequence.CacheSize); - }); - - AssertSql("""ALTER SEQUENCE "Eta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); - } - - public override async Task Alter_sequence_nocache_to_cache() - { - await base.Alter_sequence_nocache_to_cache(); - - AssertSql("""ALTER SEQUENCE "Theta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); - } - - public override async Task Alter_sequence_nocache_to_default_cache() - { - // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as - // disabling caching. - await Test( - builder => builder.HasSequence("Iota").UseNoCache(), - builder => { }, - builder => builder.HasSequence("Iota").UseCache(), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.False(sequence.IsCached); - Assert.Null(sequence.CacheSize); - }); - - AssertSql("""ALTER SEQUENCE "Iota" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + AssertSql( + """ +ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE; +"""); } public override async Task Alter_sequence_restart_with() diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs index b51dea3bc..76e12c800 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs @@ -5,11 +5,16 @@ public class NpgsqlComplianceTest : RelationalComplianceTestBase protected override ICollection IgnoredTestBases { get; } = new HashSet { // Not implemented - typeof(CompiledModelTestBase), typeof(CompiledModelRelationalTestBase), // ##3087 + typeof(CompiledModelTestBase), typeof(CompiledModelRelationalTestBase), // #3087 typeof(FromSqlSprocQueryTestBase<>), typeof(UdfDbFunctionTestBase<>), typeof(UpdateSqlGeneratorTestBase), + // Precompiled query/NativeAOT (#3257) + typeof(AdHocPrecompiledQueryRelationalTestBase), + typeof(PrecompiledQueryRelationalTestBase), + typeof(PrecompiledSqlPregenerationQueryRelationalTestBase), + // Disabled typeof(GraphUpdatesTestBase<>), typeof(ProxyGraphUpdatesTestBase<>), diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs index 2c9d7e992..2c7bc8061 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs @@ -46,7 +46,7 @@ await context.Database.CreateExecutionStrategy().ExecuteAsync( [InlineData(false, true, true)] public async Task Returns_true_when_database_exists(bool async, bool ambientTransaction, bool useCanConnect) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("ExistingBlogging"); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("ExistingBlogging"); await using var context = new BloggingContext(testDatabase); var creator = GetDatabaseCreator(context); @@ -79,7 +79,7 @@ public class NpgsqlDatabaseCreatorEnsureDeletedTest : NpgsqlDatabaseCreatorTest [InlineData(false, true, false)] public async Task Deletes_database(bool async, bool open, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.CreateInitialized("EnsureDeleteBlogging"); + await using var testDatabase = await NpgsqlTestStore.CreateInitializedAsync("EnsureDeleteBlogging"); if (!open) { testDatabase.CloseConnection(); @@ -166,11 +166,11 @@ private static async Task Creates_physical_database_and_schema_test( await using var context = new BloggingContext(testDatabase); if (createDatabase) { - testDatabase.Initialize(null, (Func)null); + await testDatabase.InitializeAsync(null, (Func)null); } else { - testDatabase.DeleteDatabase(); + await testDatabase.DeleteDatabaseAsync(); } var creator = GetDatabaseCreator(context); @@ -232,7 +232,7 @@ private static async Task Creates_physical_database_and_schema_test( [InlineData(false)] public async Task Noop_when_database_exists_and_has_schema(bool async) { - await using var testDatabase = NpgsqlTestStore.CreateInitialized("InitializedBlogging"); + await using var testDatabase = await NpgsqlTestStore.CreateInitializedAsync("InitializedBlogging"); await using var context = new BloggingContext(testDatabase); context.Database.EnsureCreatedResiliently(); @@ -275,7 +275,7 @@ await databaseCreator.ExecutionStrategy.ExecuteAsync( [InlineData(false, true)] public async Task Returns_false_when_database_exists_but_has_no_tables(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("Empty"); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("Empty"); var creator = GetDatabaseCreator(testDatabase); await GetExecutionStrategy(testDatabase).ExecuteAsync( @@ -293,8 +293,8 @@ await GetExecutionStrategy(testDatabase).ExecuteAsync( [InlineData(false, false)] public async Task Returns_true_when_database_exists_and_has_any_tables(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.GetOrCreate("ExistingTables") - .InitializeNpgsql(null, t => new BloggingContext(t), null); + await using var testDatabase = await NpgsqlTestStore.GetOrCreate("ExistingTables") + .InitializeNpgsqlAsync(null, t => new BloggingContext(t), null); var creator = GetDatabaseCreator(testDatabase); await GetExecutionStrategy(testDatabase).ExecuteAsync( @@ -313,7 +313,7 @@ await GetExecutionStrategy(testDatabase).ExecuteAsync( [RequiresPostgis] public async Task Returns_false_when_database_exists_and_has_only_postgis_tables(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("Empty"); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("Empty"); testDatabase.ExecuteNonQuery("CREATE EXTENSION IF NOT EXISTS postgis"); var creator = GetDatabaseCreator(testDatabase); @@ -336,7 +336,7 @@ public class NpgsqlDatabaseCreatorDeleteTest : NpgsqlDatabaseCreatorTest [InlineData(false, false)] public static async Task Deletes_database(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.CreateInitialized("DeleteBlogging"); + await using var testDatabase = await NpgsqlTestStore.CreateInitializedAsync("DeleteBlogging"); testDatabase.CloseConnection(); var creator = GetDatabaseCreator(testDatabase); @@ -384,7 +384,7 @@ public class NpgsqlDatabaseCreatorCreateTablesTest : NpgsqlDatabaseCreatorTest [InlineData(false, false)] public async Task Creates_schema_in_existing_database_test(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("ExistingBlogging" + (async ? "Async" : "")); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("ExistingBlogging" + (async ? "Async" : "")); await using var context = new BloggingContext(testDatabase); var creator = GetDatabaseCreator(context); @@ -539,7 +539,7 @@ await testDatabase.QueryAsync( [InlineData(false)] public async Task Throws_if_database_already_exists(bool async) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("ExistingBlogging"); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("ExistingBlogging"); var creator = GetDatabaseCreator(testDatabase); var ex = async diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs index 6f663d7d9..1cdb9aed7 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs @@ -8,9 +8,9 @@ public class NpgsqlValueGenerationScenariosTest private static readonly string DatabaseName = "NpgsqlValueGenerationScenariosTest"; [Fact] - public void Insert_with_sequence_id() + public async Task Insert_with_sequence_id() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextSequence(testStore.Name)) { @@ -33,9 +33,9 @@ public void Insert_with_sequence_id() public class BlogContextSequence(string databaseName) : ContextBase(databaseName); [Fact] - public void Insert_with_sequence_HiLo() + public async Task Insert_with_sequence_HiLo() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextHiLo(testStore.Name)) { @@ -66,9 +66,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_default_value_from_sequence() + public async Task Insert_with_default_value_from_sequence() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextDefaultValue(testStore.Name)) { @@ -144,9 +144,9 @@ public class BlogWithStringKey } [Fact] - public void Insert_with_key_default_value_from_sequence() + public async Task Insert_with_key_default_value_from_sequence() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextKeyColumnWithDefaultValue(testStore.Name)) { @@ -185,9 +185,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [ConditionalFact] - public void Insert_uint_to_Identity_column_using_value_converter() + public async Task Insert_uint_to_Identity_column_using_value_converter() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextUIntToIdentityUsingValueConverter(testStore.Name)) { context.Database.EnsureCreatedResiliently(); @@ -229,9 +229,9 @@ public class BlogWithUIntKey } [ConditionalFact] - public void Insert_string_to_Identity_column_using_value_converter() + public async Task Insert_string_to_Identity_column_using_value_converter() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextStringToIdentityUsingValueConverter(testStore.Name)) { context.Database.EnsureCreatedResiliently(); @@ -274,9 +274,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_explicit_non_default_keys() + public async Task Insert_with_explicit_non_default_keys() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNoKeyGeneration(testStore.Name)) { @@ -310,9 +310,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_explicit_with_default_keys() + public async Task Insert_with_explicit_with_default_keys() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNoKeyGenerationNullableKey(testStore.Name)) { @@ -346,9 +346,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_non_key_default_value() + public async Task Insert_with_non_key_default_value() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNonKeyDefaultValue(testStore.Name)) { @@ -399,9 +399,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_non_key_default_value_readonly() + public async Task Insert_with_non_key_default_value_readonly() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNonKeyReadOnlyDefaultValue(testStore.Name)) { @@ -453,9 +453,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_serial_non_id() + public async Task Insert_with_serial_non_id() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); int afterSave; @@ -491,9 +491,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_client_generated_GUID_key() + public async Task Insert_with_client_generated_GUID_key() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); Guid afterSave; using (var context = new BlogContext(testStore.Name)) @@ -516,9 +516,9 @@ public void Insert_with_client_generated_GUID_key() public class BlogContext(string databaseName) : ContextBase(databaseName); [Fact] - public void Insert_with_server_generated_GUID_key() + public async Task Insert_with_server_generated_GUID_key() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); Guid afterSave; using (var context = new BlogContextServerGuidKey(testStore.Name)) diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocComplexTypeQueryNpgsqlTest.cs new file mode 100644 index 000000000..cf86a2b2f --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocComplexTypeQueryNpgsqlTest.cs @@ -0,0 +1,32 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class AdHocComplexTypeQueryNpgsqlTest : AdHocComplexTypeQueryTestBase +{ + public override async Task Complex_type_equals_parameter_with_nested_types_with_property_of_same_name() + { + await base.Complex_type_equals_parameter_with_nested_types_with_property_of_same_name(); + + AssertSql( + """ +@__entity_equality_container_0_Id='1' (Nullable = true) +@__entity_equality_container_0_Containee1_Id='2' (Nullable = true) +@__entity_equality_container_0_Containee2_Id='3' (Nullable = true) + +SELECT e."Id", e."ComplexContainer_Id", e."ComplexContainer_Containee1_Id", e."ComplexContainer_Containee2_Id" +FROM "EntityType" AS e +WHERE e."ComplexContainer_Id" = @__entity_equality_container_0_Id AND e."ComplexContainer_Containee1_Id" = @__entity_equality_container_0_Containee1_Id AND e."ComplexContainer_Containee2_Id" = @__entity_equality_container_0_Containee2_Id +LIMIT 2 +"""); + } + + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected void AssertSql(params string[] expected) + => TestSqlLoggerFactory.AssertBaseline(expected); + + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; +} diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs index c218b8a5d..cec0ec78a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs @@ -7,7 +7,7 @@ public class AdHocJsonQueryNpgsqlTest : AdHocJsonQueryTestBase protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; - protected override void Seed29219(MyContext29219 ctx) + protected override async Task Seed29219(MyContext29219 ctx) { var entity1 = new MyEntity29219 { @@ -29,19 +29,19 @@ protected override void Seed29219(MyContext29219 ctx) }; ctx.Entities.AddRange(entity1, entity2); - ctx.SaveChanges(); + await ctx.SaveChangesAsync(); - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Id", "Reference", "Collection") VALUES(3, '{ "NonNullableScalar" : 30 }', '[{ "NonNullableScalar" : 10001 }]') """); } - protected override void Seed30028(MyContext30028 ctx) + protected override async Task Seed30028(MyContext30028 ctx) { // complete - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( @@ -50,7 +50,7 @@ protected override void Seed30028(MyContext30028 ctx) """); // missing collection - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( @@ -59,7 +59,7 @@ protected override void Seed30028(MyContext30028 ctx) """); // missing optional reference - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( @@ -68,7 +68,7 @@ protected override void Seed30028(MyContext30028 ctx) """); // missing required reference - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( @@ -77,14 +77,14 @@ protected override void Seed30028(MyContext30028 ctx) """); } - protected override void Seed33046(Context33046 ctx) - => ctx.Database.ExecuteSql( + protected override async Task Seed33046(Context33046 ctx) + => await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Reviews" ("Rounds", "Id") VALUES('[{"RoundNumber":11,"SubRounds":[{"SubRoundNumber":111},{"SubRoundNumber":112}]}]', 1) """); - protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) + protected override async Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) { var entity1 = new MyEntityArrayOfPrimitives { @@ -127,11 +127,11 @@ protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) }; ctx.Entities.AddRange(entity1, entity2); - ctx.SaveChanges(); + await ctx.SaveChangesAsync(); } - protected override void SeedJunkInJson(MyContextJunkInJson ctx) - => ctx.Database.ExecuteSql( + protected override async Task SeedJunkInJson(MyContextJunkInJson ctx) + => await ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id") VALUES( @@ -142,16 +142,16 @@ protected override void SeedJunkInJson(MyContextJunkInJson ctx) 1) """); - protected override void SeedTrickyBuffering(MyContextTrickyBuffering ctx) - => ctx.Database.ExecuteSql( + protected override async Task SeedTrickyBuffering(MyContextTrickyBuffering ctx) + => await ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO "Entities" ("Reference", "Id") VALUES( '{"Name": "r1", "Number": 7, "JunkReference":{"Something": "SomeValue" }, "JunkCollection": [{"Foo": "junk value"}], "NestedReference": {"DoB": "2000-01-01T00:00:00Z"}, "NestedCollection": [{"DoB": "2000-02-01T00:00:00Z", "JunkReference": {"Something": "SomeValue"}}, {"DoB": "2000-02-02T00:00:00Z"}]}',1) """); - protected override void SeedShadowProperties(MyContextShadowProperties ctx) - => ctx.Database.ExecuteSql( + protected override async Task SeedShadowProperties(MyContextShadowProperties ctx) + => await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id", "Name") VALUES( @@ -163,9 +163,9 @@ protected override void SeedShadowProperties(MyContextShadowProperties ctx) 'e1') """); - protected override void SeedNotICollection(MyContextNotICollection ctx) + protected override async Task SeedNotICollection(MyContextNotICollection ctx) { - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Json", "Id") VALUES( @@ -173,7 +173,7 @@ protected override void SeedNotICollection(MyContextNotICollection ctx) 1) """); - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Json", "Id") VALUES( @@ -186,12 +186,12 @@ protected override void SeedNotICollection(MyContextNotICollection ctx) public virtual async Task Json_predicate_on_bytea(bool async) { var contextFactory = await InitializeAsync( - seed: context => + seed: async context => { context.Entities.AddRange( new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 3] } }, new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 4] } }); - context.SaveChanges(); + await context.SaveChangesAsync(); }); using (var context = contextFactory.CreateContext()) @@ -218,12 +218,12 @@ LIMIT 2 public virtual async Task Json_predicate_on_interval(bool async) { var contextFactory = await InitializeAsync( - seed: context => + seed: async context => { context.Entities.AddRange( new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Interval = new TimeSpan(1, 2, 3, 4, 123, 456) } }, new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Interval = new TimeSpan(2, 2, 3, 4, 123, 456) } }); - context.SaveChanges(); + await context.SaveChangesAsync(); }); using (var context = contextFactory.CreateContext()) diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs index 6d44a9bdd..cf497d6a5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs @@ -7,8 +7,8 @@ public class AdHocMiscellaneousQueryNpgsqlTest : AdHocMiscellaneousQueryRelation protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; - protected override void Seed2951(Context2951 context) - => context.Database.ExecuteSqlRaw( + protected override Task Seed2951(Context2951 context) + => context.Database.ExecuteSqlRawAsync( """ CREATE TABLE "ZeroKey" ("Id" int); INSERT INTO "ZeroKey" VALUES (NULL) diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index b89306d03..e00543860 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -835,7 +835,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE COALESCE(array_position(s."IntArray", 6) - 1, -1) = 1 +WHERE array_position(s."IntArray", 6) - 1 = 1 """); } @@ -849,7 +849,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE COALESCE(array_position(s."IntArray", 6, 2) - 1, -1) = 1 +WHERE array_position(s."IntArray", 6, 2) - 1 = 1 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs index 99e88ded6..361fb653b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs @@ -855,7 +855,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE COALESCE(array_position(s."IntList", 6) - 1, -1) = 1 +WHERE array_position(s."IntList", 6) - 1 = 1 """); } @@ -871,7 +871,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE COALESCE(array_position(s."IntList", 6, 2) - 1, -1) = 1 +WHERE array_position(s."IntList", 6, 2) - 1 = 1 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs index 2c470c46f..1809ce560 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs @@ -16,8 +16,8 @@ public TestSqlLoggerFactory TestSqlLoggerFactory public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) => base.AddOptions(builder).ConfigureWarnings(wcb => wcb.Ignore(CoreEventId.CollectionWithoutComparer)); - protected override void Seed(ArrayQueryContext context) - => ArrayQueryContext.Seed(context); + protected override Task SeedAsync(ArrayQueryContext context) + => ArrayQueryContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs index 207ba6a23..72950440a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs @@ -132,10 +132,10 @@ public class BigIntegerQueryContext(DbContextOptions options) : PoolableDbContex { public DbSet Entities { get; set; } - public static void Seed(BigIntegerQueryContext context) + public static async Task SeedAsync(BigIntegerQueryContext context) { context.Entities.AddRange(BigIntegerData.CreateEntities()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -158,8 +158,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(BigIntegerQueryContext context) - => BigIntegerQueryContext.Seed(context); + protected override Task SeedAsync(BigIntegerQueryContext context) + => BigIntegerQueryContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs index 1ff63672d..d87ad0c9c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs @@ -298,12 +298,12 @@ public class CitextQueryContext(DbContextOptions options) : PoolableDbContext(op { public DbSet SomeEntities { get; set; } - public static void Seed(CitextQueryContext context) + public static async Task SeedAsync(CitextQueryContext context) { context.SomeEntities.AddRange( new SomeArrayEntity { Id = 1, CaseInsensitiveText = "SomeText" }, new SomeArrayEntity { Id = 2, CaseInsensitiveText = "AnotherText" }); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -326,7 +326,7 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(CitextQueryContext context) - => CitextQueryContext.Seed(context); + protected override Task SeedAsync(CitextQueryContext context) + => CitextQueryContext.SeedAsync(context); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs index 19f73cf39..ebf72bbb9 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs @@ -73,11 +73,10 @@ public virtual CompatibilityContext CreateRedshiftContext() return new CompatibilityContext(builder.Options); } - public virtual Task InitializeAsync() + public virtual async Task InitializeAsync() { _testStore = NpgsqlTestStoreFactory.Instance.GetOrCreate(StoreName); - _testStore.Initialize(null, CreateContext, c => CompatibilityContext.Seed((CompatibilityContext)c)); - return Task.CompletedTask; + await _testStore.InitializeAsync(null, CreateContext, c => CompatibilityContext.SeedAsync((CompatibilityContext)c)); } // Called after DisposeAsync @@ -99,12 +98,12 @@ public class CompatibilityContext(DbContextOptions options) : DbContext(options) { public DbSet TestEntities { get; set; } - public static void Seed(CompatibilityContext context) + public static async Task SeedAsync(CompatibilityContext context) { context.TestEntities.AddRange( new CompatibilityTestEntity { Id = 1, SomeInt = 8 }, new CompatibilityTestEntity { Id = 2, SomeInt = 10 }); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryNpgsqlTest.cs index 7626042e8..69e20028d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryNpgsqlTest.cs @@ -19,14 +19,6 @@ public ComplexNavigationsSharedTypeQueryNpgsqlTest( public override Task Subquery_with_Distinct_Skip_FirstOrDefault_without_OrderBy(bool async) => base.Subquery_with_Distinct_Skip_FirstOrDefault_without_OrderBy(async); - [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/pull/22532")] - public override Task Distinct_skip_without_orderby(bool async) - => base.Distinct_skip_without_orderby(async); - - [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/pull/22532")] - public override Task Distinct_take_without_orderby(bool async) - => base.Distinct_take_without_orderby(async); - public override async Task Join_with_result_selector_returning_queryable_throws_validation_error(bool async) => await Assert.ThrowsAsync( () => base.Join_with_result_selector_returning_queryable_throws_validation_error(async)); diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs index 9ca70651d..d46b14a82 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs @@ -231,12 +231,12 @@ public override async Task Complex_type_equals_parameter(bool async) @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' @__entity_equality_address_0_Tags={ 'foo', 'bar' } (DbType = Object) @__entity_equality_address_0_ZipCode='38654' (Nullable = true) -@__entity_equality_address_0_Code='US' -@__entity_equality_address_0_FullName='United States' +@__entity_equality_address_0_Country_Code='US' +@__entity_equality_address_0_Country_FullName='United States' SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c -WHERE c."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c."ShippingAddress_AddressLine2" IS NULL AND c."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND c."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName +WHERE c."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c."ShippingAddress_AddressLine2" IS NULL AND c."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c."ShippingAddress_Country_Code" = @__entity_equality_address_0_Country_Code AND c."ShippingAddress_Country_FullName" = @__entity_equality_address_0_Country_FullName """); } @@ -263,15 +263,15 @@ public override async Task Contains_over_complex_type(bool async) @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' @__entity_equality_address_0_Tags={ 'foo', 'bar' } (DbType = Object) @__entity_equality_address_0_ZipCode='38654' (Nullable = true) -@__entity_equality_address_0_Code='US' -@__entity_equality_address_0_FullName='United States' +@__entity_equality_address_0_Country_Code='US' +@__entity_equality_address_0_Country_FullName='United States' SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE EXISTS ( SELECT 1 FROM "Customer" AS c0 - WHERE c0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c0."ShippingAddress_AddressLine2" IS NULL AND c0."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND c0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName) + WHERE c0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c0."ShippingAddress_AddressLine2" IS NULL AND c0."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Country_Code AND c0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_Country_FullName) """); } @@ -600,12 +600,12 @@ public override async Task Struct_complex_type_equals_parameter(bool async) """ @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' @__entity_equality_address_0_ZipCode='38654' (Nullable = true) -@__entity_equality_address_0_Code='US' -@__entity_equality_address_0_FullName='United States' +@__entity_equality_address_0_Country_Code='US' +@__entity_equality_address_0_Country_FullName='United States' SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName" FROM "ValuedCustomer" AS v -WHERE v."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND v."ShippingAddress_AddressLine2" IS NULL AND v."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND v."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND v."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName +WHERE v."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND v."ShippingAddress_AddressLine2" IS NULL AND v."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND v."ShippingAddress_Country_Code" = @__entity_equality_address_0_Country_Code AND v."ShippingAddress_Country_FullName" = @__entity_equality_address_0_Country_FullName """); } @@ -624,15 +624,15 @@ public override async Task Contains_over_struct_complex_type(bool async) """ @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' @__entity_equality_address_0_ZipCode='38654' (Nullable = true) -@__entity_equality_address_0_Code='US' -@__entity_equality_address_0_FullName='United States' +@__entity_equality_address_0_Country_Code='US' +@__entity_equality_address_0_Country_FullName='United States' SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName" FROM "ValuedCustomer" AS v WHERE EXISTS ( SELECT 1 FROM "ValuedCustomer" AS v0 - WHERE v0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND v0."ShippingAddress_AddressLine2" IS NULL AND v0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND v0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND v0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName) + WHERE v0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND v0."ShippingAddress_AddressLine2" IS NULL AND v0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND v0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Country_Code AND v0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_Country_FullName) """); } @@ -1023,6 +1023,44 @@ public override async Task Same_complex_type_projected_twice_with_pushdown_as_pa AssertSql(""); } + #region GroupBy + + public override async Task GroupBy_over_property_in_nested_complex_type(bool async) + { + await base.GroupBy_over_property_in_nested_complex_type(async); + + AssertSql( + """ +SELECT c."ShippingAddress_Country_Code" AS "Code", count(*)::int AS "Count" +FROM "Customer" AS c +GROUP BY c."ShippingAddress_Country_Code" +"""); + } + + public override async Task GroupBy_over_complex_type(bool async) + { + await base.GroupBy_over_complex_type(async); + + AssertSql( + """ +SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", count(*)::int AS "Count" +FROM "Customer" AS c +GROUP BY c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +"""); + } + + public override async Task GroupBy_over_nested_complex_type(bool async) + { + await base.GroupBy_over_nested_complex_type(async); + + AssertSql( + """ +SELECT c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", count(*)::int AS "Count" +FROM "Customer" AS c +GROUP BY c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +"""); + } + public override async Task Entity_with_complex_type_with_group_by_and_first(bool async) { await base.Entity_with_complex_type_with_group_by_and_first(async); @@ -1046,6 +1084,76 @@ LEFT JOIN ( """); } + #endregion GroupBy + + public override async Task Projecting_property_of_complex_type_using_left_join_with_pushdown(bool async) + { + await base.Projecting_property_of_complex_type_using_left_join_with_pushdown(async); + + AssertSql( + """ +SELECT c1."BillingAddress_ZipCode" +FROM "CustomerGroup" AS c +LEFT JOIN ( + SELECT c0."Id", c0."BillingAddress_ZipCode" + FROM "Customer" AS c0 + WHERE c0."Id" > 5 +) AS c1 ON c."Id" = c1."Id" +"""); + } + + public override async Task Projecting_complex_from_optional_navigation_using_conditional(bool async) + { + await base.Projecting_complex_from_optional_navigation_using_conditional(async); + + AssertSql( + """ +@__p_0='20' + +SELECT s0."ShippingAddress_ZipCode" +FROM ( + SELECT DISTINCT s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_Tags", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName" + FROM ( + SELECT c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" + FROM "CustomerGroup" AS c + LEFT JOIN "Customer" AS c0 ON c."OptionalCustomerId" = c0."Id" + ORDER BY c0."ShippingAddress_ZipCode" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); } + + public override async Task Project_entity_with_complex_type_pushdown_and_then_left_join(bool async) + { + await base.Project_entity_with_complex_type_pushdown_and_then_left_join(async); + + AssertSql( +""" +@__p_0='20' +@__p_1='30' + +SELECT c3."BillingAddress_ZipCode" AS "Zip1", c4."ShippingAddress_ZipCode" AS "Zip2" +FROM ( + SELECT DISTINCT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" + FROM ( + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" + FROM "Customer" AS c + ORDER BY c."Id" NULLS FIRST + LIMIT @__p_0 + ) AS c0 +) AS c3 +LEFT JOIN ( + SELECT DISTINCT c2."Id", c2."Name", c2."BillingAddress_AddressLine1", c2."BillingAddress_AddressLine2", c2."BillingAddress_Tags", c2."BillingAddress_ZipCode", c2."BillingAddress_Country_Code", c2."BillingAddress_Country_FullName", c2."ShippingAddress_AddressLine1", c2."ShippingAddress_AddressLine2", c2."ShippingAddress_Tags", c2."ShippingAddress_ZipCode", c2."ShippingAddress_Country_Code", c2."ShippingAddress_Country_FullName" + FROM ( + SELECT c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_Tags", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName" + FROM "Customer" AS c1 + ORDER BY c1."Id" DESC NULLS LAST + LIMIT @__p_1 + ) AS c2 +) AS c4 ON c3."Id" = c4."Id" +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index f73103648..a3b899507 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -226,10 +226,10 @@ public class EnumContext(DbContextOptions options) : PoolableDbContext(options) protected override void OnModelCreating(ModelBuilder builder) => builder.HasDefaultSchema("test"); - public static void Seed(EnumContext context) + public static async Task SeedAsync(EnumContext context) { context.AddRange(EnumData.CreateSomeEnumEntities()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -311,8 +311,8 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build private EnumData _expectedData; - protected override void Seed(EnumContext context) - => EnumContext.Seed(context); + protected override Task SeedAsync(EnumContext context) + => EnumContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs index 06fd4c016..dc391c993 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs @@ -1,5 +1,6 @@ using System.Data.Common; using Microsoft.EntityFrameworkCore.TestModels.Northwind; +using Xunit.Sdk; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; @@ -14,8 +15,12 @@ public override Task Bad_data_error_handling_invalid_cast(bool async) public override Task Bad_data_error_handling_invalid_cast_projection(bool async) => base.Bad_data_error_handling_invalid_cast_projection(async); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] + // The test attempts to project out a column with the wrong case; this works on other databases, and fails when EF tries to materialize. + // But in PG this fails at the database since PG is case-sensitive and the column does not exist. + public override Task FromSqlRaw_queryable_simple_different_cased_columns_and_not_enough_columns_throws(bool async) + => Assert.ThrowsAsync( + () => base.FromSqlRaw_queryable_simple_different_cased_columns_and_not_enough_columns_throws(async)); + public override async Task FromSqlInterpolated_queryable_multiple_composed_with_parameters_and_closure_parameters_interpolated( bool async) { diff --git a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs index 6c46a8ff5..fb7483f05 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs @@ -77,10 +77,10 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(FunkyDataContext context) + protected override async Task SeedAsync(FunkyDataContext context) { context.FunkyCustomers.AddRange(GetExpectedData().Set()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } } diff --git a/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs index 7648c8287..bf18e9670 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs @@ -140,8 +140,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(FuzzyStringMatchContext context) - => FuzzyStringMatchContext.Seed(context); + protected override Task SeedAsync(FuzzyStringMatchContext context) + => FuzzyStringMatchContext.SeedAsync(context); } /// @@ -190,7 +190,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } - public static void Seed(FuzzyStringMatchContext context) + public static async Task SeedAsync(FuzzyStringMatchContext context) { for (var i = 1; i <= 9; i++) { @@ -199,7 +199,7 @@ public static void Seed(FuzzyStringMatchContext context) new FuzzyStringMatchTestEntity { Id = i, Text = text }); } - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs index 81b30ae7d..e2c0054d5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs @@ -49,7 +49,7 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(GearsOfWarContext context) + protected override async Task SeedAsync(GearsOfWarContext context) { // GearsOfWarData contains DateTimeOffsets with various offsets, which we don't support. Change these to UTC. // Also chop sub-microsecond precision which PostgreSQL does not support. @@ -85,10 +85,10 @@ protected override void Seed(GearsOfWarContext context) context.LocustLeaders.AddRange(locustLeaders); context.Factions.AddRange(factions); context.LocustHighCommands.AddRange(locustHighCommands); - context.SaveChanges(); + await context.SaveChangesAsync(); GearsOfWarData.WireUp2(locustLeaders, factions); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs index fa10d5213..684786a6a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs @@ -103,7 +103,7 @@ public override async Task Where_datetimeoffset_utcnow(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Timeline" <> now() """); @@ -133,7 +133,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01T00:00:00' """); @@ -145,7 +145,7 @@ public override async Task Where_datetimeoffset_hour_component(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('hour', m."Timeline" AT TIME ZONE 'UTC')::int = 10 """); @@ -163,7 +163,7 @@ await AssertQuery( """ @__dateTimeOffset_Date_0='0002-03-01T00:00:00.0000000' -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamp >= @__dateTimeOffset_Date_0 """); @@ -188,7 +188,7 @@ await AssertQuery( @__end_1='1902-01-03T10:00:00.1234567+00:00' (DbType = DateTime) @__dates_2={ '1902-01-02T10:00:00.1234567+00:00' } (DbType = Object) -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE @__start_0 <= date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamptz AND m."Timeline" < @__end_1 AND m."Timeline" = ANY (@__dates_2) """); @@ -297,7 +297,7 @@ public override async Task Where_TimeSpan_Minutes(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE floor(date_part('minute', m."Duration"))::int = 2 """); @@ -309,7 +309,7 @@ public override async Task Where_TimeSpan_Seconds(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE floor(date_part('second', m."Duration"))::int = 3 """); @@ -321,7 +321,7 @@ public override async Task Where_TimeSpan_Milliseconds(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE floor(date_part('millisecond', m."Duration"))::int % 1000 = 456 """); @@ -337,7 +337,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") / 86400.0 < 0.042000000000000003 """); @@ -353,7 +353,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") / 3600.0 < 1.02 """); @@ -369,7 +369,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") / 60.0 < 61.0 """); @@ -385,7 +385,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") < 3700.0 """); @@ -401,7 +401,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") / 0.001 < 3700000.0 """); @@ -491,7 +491,7 @@ public override async Task Where_DateOnly_Month(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('month', m."Date")::int = 11 """); @@ -503,7 +503,7 @@ public override async Task Where_DateOnly_Day(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('day', m."Date")::int = 10 """); @@ -515,7 +515,7 @@ public override async Task Where_DateOnly_DayOfYear(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('doy', m."Date")::int = 314 """); @@ -527,7 +527,7 @@ public override async Task Where_DateOnly_DayOfWeek(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE floor(date_part('dow', m."Date"))::int = 6 """); @@ -539,7 +539,7 @@ public override async Task Where_DateOnly_AddYears(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE CAST(m."Date" + INTERVAL '3 years' AS date) = DATE '1993-11-10' """); @@ -551,7 +551,7 @@ public override async Task Where_DateOnly_AddMonths(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE CAST(m."Date" + INTERVAL '3 months' AS date) = DATE '1991-02-10' """); @@ -563,7 +563,7 @@ public override async Task Where_DateOnly_AddDays(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Date" + 3 = DATE '1990-11-13' """); @@ -644,7 +644,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('hour', m."Time")::int = 10 """); @@ -658,7 +658,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('minute', m."Time")::int = 15 """); @@ -672,7 +672,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('second', m."Time")::int = 50 """); @@ -689,7 +689,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" + INTERVAL '3 hours' = TIME '13:15:50.5' """); @@ -703,7 +703,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" + INTERVAL '3 mins' = TIME '10:18:50.5' """); @@ -717,7 +717,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" + INTERVAL '03:00:00' = TIME '13:15:50.5' """); @@ -731,7 +731,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" >= TIME '10:00:00' AND m."Time" < TIME '11:00:00' """); @@ -745,7 +745,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" - TIME '10:00:00' = INTERVAL '00:15:50.5' """); @@ -762,7 +762,7 @@ public virtual async Task TimeOnly_FromTimeSpan() AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Duration"::time without time zone = TIME '01:02:03' LIMIT 2 @@ -779,7 +779,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time"::interval = INTERVAL '15:30:10' """); diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs index b8bc094a2..3e26de810 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs @@ -598,7 +598,7 @@ public class JsonDomQueryContext(DbContextOptions options) : PoolableDbContext(o public DbSet JsonbEntities { get; set; } public DbSet JsonEntities { get; set; } - public static void Seed(JsonDomQueryContext context) + public static async Task SeedAsync(JsonDomQueryContext context) { var (customer1, customer2, customer3) = (CreateCustomer1(), CreateCustomer2(), CreateCustomer3()); @@ -640,7 +640,8 @@ public static void Seed(JsonDomQueryContext context) CustomerDocument = customer3, CustomerElement = customer3.RootElement }); - context.SaveChanges(); + + await context.SaveChangesAsync(); static JsonDocument CreateCustomer1() => JsonDocument.Parse( @@ -765,8 +766,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(JsonDomQueryContext context) - => JsonDomQueryContext.Seed(context); + protected override Task SeedAsync(JsonDomQueryContext context) + => JsonDomQueryContext.SeedAsync(context); } #endregion diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs index 096206a07..55668f31f 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs @@ -709,7 +709,7 @@ public class JsonPocoQueryContext(DbContextOptions options) : PoolableDbContext( public DbSet JsonbEntities { get; set; } public DbSet JsonEntities { get; set; } - public static void Seed(JsonPocoQueryContext context) + public static async Task SeedAsync(JsonPocoQueryContext context) { context.JsonbEntities.AddRange( new JsonbEntity @@ -727,7 +727,8 @@ public static void Seed(JsonPocoQueryContext context) ToplevelArray = ["one", "two", "three"] }, new JsonEntity { Id = 2, Customer = CreateCustomer2() }); - context.SaveChanges(); + + await context.SaveChangesAsync(); static Customer CreateCustomer1() => new() @@ -861,8 +862,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(JsonPocoQueryContext context) - => JsonPocoQueryContext.Seed(context); + protected override Task SeedAsync(JsonPocoQueryContext context) + => JsonPocoQueryContext.SeedAsync(context); } public class Customer diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs index 53c34d6a3..9af98af68 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs @@ -34,6 +34,17 @@ public override async Task Basic_json_projection_owner_entity_NoTracking(bool as """); } + public override async Task Basic_json_projection_owner_entity_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owner_entity_duplicated(bool async) { await base.Basic_json_projection_owner_entity_duplicated(async); @@ -56,6 +67,17 @@ public override async Task Basic_json_projection_owner_entity_duplicated_NoTrack """); } + public override async Task Basic_json_projection_owner_entity_duplicated_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_duplicated_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."Name", j."OwnedCollection", j."OwnedCollection" +FROM "JsonEntitiesSingleOwned" AS j +"""); + } + public override async Task Basic_json_projection_owner_entity_twice(bool async) { await base.Basic_json_projection_owner_entity_twice(async); @@ -78,6 +100,17 @@ public override async Task Basic_json_projection_owner_entity_twice_NoTracking(b """); } + public override async Task Basic_json_projection_owner_entity_twice_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_twice_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Project_json_reference_in_tracking_query_fails(bool async) { await base.Project_json_reference_in_tracking_query_fails(async); @@ -113,6 +146,17 @@ public override async Task Basic_json_projection_owned_reference_root(bool async """); } + public override async Task Basic_json_projection_owned_reference_root_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_root_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot", j."Id" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owned_reference_duplicated2(bool async) { await base.Basic_json_projection_owned_reference_duplicated2(async); @@ -125,6 +169,18 @@ ORDER BY j."Id" NULLS FIRST """); } + public override async Task Basic_json_projection_owned_reference_duplicated2_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_duplicated2_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot", j."Id", j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedReferenceLeaf}', j."OwnedReferenceRoot", j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedReferenceLeaf}' +FROM "JsonEntitiesBasic" AS j +ORDER BY j."Id" NULLS FIRST +"""); + } + public override async Task Basic_json_projection_owned_reference_duplicated(bool async) { await base.Basic_json_projection_owned_reference_duplicated(async); @@ -137,6 +193,18 @@ ORDER BY j."Id" NULLS FIRST """); } + public override async Task Basic_json_projection_owned_reference_duplicated_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_duplicated_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot", j."Id", j."OwnedReferenceRoot" -> 'OwnedReferenceBranch', j."OwnedReferenceRoot", j."OwnedReferenceRoot" -> 'OwnedReferenceBranch' +FROM "JsonEntitiesBasic" AS j +ORDER BY j."Id" NULLS FIRST +"""); + } + public override async Task Basic_json_projection_owned_collection_root(bool async) { await base.Basic_json_projection_owned_collection_root(async); @@ -148,6 +216,17 @@ public override async Task Basic_json_projection_owned_collection_root(bool asyn """); } + public override async Task Basic_json_projection_owned_collection_root_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_collection_root_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedCollectionRoot", j."Id" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owned_reference_branch(bool async) { await base.Basic_json_projection_owned_reference_branch(async); @@ -159,6 +238,17 @@ public override async Task Basic_json_projection_owned_reference_branch(bool asy """); } + public override async Task Basic_json_projection_owned_reference_branch_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_branch_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot" -> 'OwnedReferenceBranch', j."Id" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owned_collection_branch(bool async) { await base.Basic_json_projection_owned_collection_branch(async); @@ -170,6 +260,17 @@ public override async Task Basic_json_projection_owned_collection_branch(bool as """); } + public override async Task Basic_json_projection_owned_collection_branch_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_collection_branch_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot" -> 'OwnedCollectionBranch', j."Id" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owned_reference_leaf(bool async) { await base.Basic_json_projection_owned_reference_leaf(async); @@ -232,7 +333,7 @@ public override async Task Json_projection_enum_with_custom_conversion(bool asyn AssertSql( """ -SELECT j."Id", CAST(j.json_reference_custom_naming ->> 'CustomEnum' AS integer) AS "Enum" +SELECT j."Id", CAST(j.json_reference_custom_naming ->> '1CustomEnum' AS integer) AS "Enum" FROM "JsonEntitiesCustomNaming" AS j """); } @@ -456,7 +557,7 @@ public override async Task Custom_naming_projection_owned_reference(bool async) AssertSql( """ -SELECT j.json_reference_custom_naming -> 'CustomOwnedReferenceBranch', j."Id" +SELECT j.json_reference_custom_naming -> 'Custom#OwnedReferenceBranch`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?独角兽π獨角獸', j."Id" FROM "JsonEntitiesCustomNaming" AS j """); } @@ -479,7 +580,7 @@ public override async Task Custom_naming_projection_owned_scalar(bool async) AssertSql( """ -SELECT CAST(j.json_reference_custom_naming #>> '{CustomOwnedReferenceBranch,CustomFraction}' AS double precision) +SELECT CAST(j.json_reference_custom_naming #>> ARRAY['Custom#OwnedReferenceBranch`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?独角兽π獨角獸','ユニコーンFraction一角獣']::text[] AS double precision) FROM "JsonEntitiesCustomNaming" AS j """); } @@ -490,7 +591,7 @@ public override async Task Custom_naming_projection_everything(bool async) AssertSql( """ -SELECT j."Id", j."Title", j.json_collection_custom_naming, j.json_reference_custom_naming, j.json_reference_custom_naming, j.json_reference_custom_naming -> 'CustomOwnedReferenceBranch', j.json_collection_custom_naming, j.json_reference_custom_naming -> 'CustomOwnedCollectionBranch', j.json_reference_custom_naming ->> 'CustomName', CAST(j.json_reference_custom_naming #>> '{CustomOwnedReferenceBranch,CustomFraction}' AS double precision) +SELECT j."Id", j."Title", j.json_collection_custom_naming, j.json_reference_custom_naming, j.json_reference_custom_naming, j.json_reference_custom_naming -> 'Custom#OwnedReferenceBranch`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?独角兽π獨角獸', j.json_collection_custom_naming, j.json_reference_custom_naming -> 'CustomOwnedCollectionBranch', j.json_reference_custom_naming ->> 'CustomName', CAST(j.json_reference_custom_naming #>> ARRAY['Custom#OwnedReferenceBranch`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?独角兽π獨角獸','ユニコーンFraction一角獣']::text[] AS double precision) FROM "JsonEntitiesCustomNaming" AS j """); } @@ -1566,7 +1667,12 @@ public override async Task Json_collection_of_primitives_SelectMany(bool async) { await base.Json_collection_of_primitives_SelectMany(async); - AssertSql(""); + AssertSql( + """ +SELECT n.value +FROM "JsonEntitiesBasic" AS j +JOIN LATERAL unnest((ARRAY(SELECT CAST(element AS text) FROM jsonb_array_elements_text(j."OwnedReferenceRoot" -> 'Names') WITH ORDINALITY AS t(element) ORDER BY ordinality))) AS n(value) ON TRUE +"""); } public override async Task Json_collection_of_primitives_index_used_in_predicate(bool async) @@ -2267,7 +2373,7 @@ public override async Task Json_all_types_entity_projection(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j """); } @@ -2300,7 +2406,7 @@ public override async Task Json_boolean_predicate(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE CAST(j."Reference" ->> 'TestBoolean' AS boolean) """); @@ -2312,7 +2418,7 @@ public override async Task Json_boolean_predicate_negated(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE NOT (CAST(j."Reference" ->> 'TestBoolean' AS boolean)) """); @@ -2346,7 +2452,7 @@ public override async Task Json_predicate_on_default_string(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (j."Reference" ->> 'TestDefaultString') <> 'MyDefaultStringInReference1' OR (j."Reference" ->> 'TestDefaultString') IS NULL """); @@ -2358,7 +2464,7 @@ public override async Task Json_predicate_on_max_length_string(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (j."Reference" ->> 'TestMaxLengthString') <> 'Foo' OR (j."Reference" ->> 'TestMaxLengthString') IS NULL """); @@ -2370,7 +2476,7 @@ public override async Task Json_predicate_on_string_condition(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE CASE WHEN NOT (CAST(j."Reference" ->> 'TestBoolean' AS boolean)) THEN j."Reference" ->> 'TestMaxLengthString' @@ -2385,19 +2491,31 @@ public override async Task Json_predicate_on_byte(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestByte' AS smallint)) <> 3 OR (CAST(j."Reference" ->> 'TestByte' AS smallint)) IS NULL """); } + public override async Task Json_predicate_on_byte_array(bool async) + { + await base.Json_predicate_on_byte_array(async); + + AssertSql( + """ +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +FROM "JsonEntitiesAllTypes" AS j +WHERE (decode(j."Reference" ->> 'TestByteArray', 'base64')) <> BYTEA E'\\x010203' OR (decode(j."Reference" ->> 'TestByteArray', 'base64')) IS NULL +"""); + } + public override async Task Json_predicate_on_character(bool async) { await base.Json_predicate_on_character(async); AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestCharacter' AS character(1))) <> 'z' OR (CAST(j."Reference" ->> 'TestCharacter' AS character(1))) IS NULL """); @@ -2409,7 +2527,7 @@ public override async Task Json_predicate_on_datetime(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDateTime' AS timestamp without time zone)) <> TIMESTAMP '2000-01-03T00:00:00' OR (CAST(j."Reference" ->> 'TestDateTime' AS timestamp without time zone)) IS NULL """); @@ -2421,7 +2539,7 @@ public override async Task Json_predicate_on_datetimeoffset(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDateTimeOffset' AS timestamp with time zone)) <> TIMESTAMPTZ '2000-01-04T00:00:00+03:02' OR (CAST(j."Reference" ->> 'TestDateTimeOffset' AS timestamp with time zone)) IS NULL """); @@ -2433,7 +2551,7 @@ public override async Task Json_predicate_on_decimal(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDecimal' AS numeric(18,3))) <> 1.35 OR (CAST(j."Reference" ->> 'TestDecimal' AS numeric(18,3))) IS NULL """); @@ -2445,7 +2563,7 @@ public override async Task Json_predicate_on_double(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDouble' AS double precision)) <> 33.25 OR (CAST(j."Reference" ->> 'TestDouble' AS double precision)) IS NULL """); @@ -2457,7 +2575,7 @@ public override async Task Json_predicate_on_enum(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestEnum' AS integer)) <> 2 OR (CAST(j."Reference" ->> 'TestEnum' AS integer)) IS NULL """); @@ -2469,7 +2587,7 @@ public override async Task Json_predicate_on_enumwithintconverter(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestEnumWithIntConverter' AS integer)) <> -3 OR (CAST(j."Reference" ->> 'TestEnumWithIntConverter' AS integer)) IS NULL """); @@ -2481,7 +2599,7 @@ public override async Task Json_predicate_on_guid(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestGuid' AS uuid)) <> '00000000-0000-0000-0000-000000000000' OR (CAST(j."Reference" ->> 'TestGuid' AS uuid)) IS NULL """); @@ -2493,7 +2611,7 @@ public override async Task Json_predicate_on_int16(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestInt16' AS smallint)) <> 3 OR (CAST(j."Reference" ->> 'TestInt16' AS smallint)) IS NULL """); @@ -2505,7 +2623,7 @@ public override async Task Json_predicate_on_int32(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestInt32' AS integer)) <> 33 OR (CAST(j."Reference" ->> 'TestInt32' AS integer)) IS NULL """); @@ -2517,7 +2635,7 @@ public override async Task Json_predicate_on_int64(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestInt64' AS bigint)) <> 333 OR (CAST(j."Reference" ->> 'TestInt64' AS bigint)) IS NULL """); @@ -2529,7 +2647,7 @@ public override async Task Json_predicate_on_nullableenum1(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) <> -1 OR (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) IS NULL """); @@ -2541,7 +2659,7 @@ public override async Task Json_predicate_on_nullableenum2(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) IS NOT NULL """); @@ -2553,7 +2671,7 @@ public override async Task Json_predicate_on_nullableenumwithconverter1(bool asy AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) <> 2 OR (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) IS NULL """); @@ -2565,7 +2683,7 @@ public override async Task Json_predicate_on_nullableenumwithconverter2(bool asy AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) IS NOT NULL """); @@ -2577,7 +2695,7 @@ public override async Task Json_predicate_on_nullableenumwithconverterthathandle AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (j."Reference" ->> 'TestNullableEnumWithConverterThatHandlesNulls') <> 'One' OR (j."Reference" ->> 'TestNullableEnumWithConverterThatHandlesNulls') IS NULL """); @@ -2599,7 +2717,7 @@ public override async Task Json_predicate_on_nullableint321(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableInt32' AS integer)) <> 100 OR (CAST(j."Reference" ->> 'TestNullableInt32' AS integer)) IS NULL """); @@ -2611,7 +2729,7 @@ public override async Task Json_predicate_on_nullableint322(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableInt32' AS integer)) IS NOT NULL """); @@ -2623,7 +2741,7 @@ public override async Task Json_predicate_on_signedbyte(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestSignedByte' AS smallint)) <> 100 OR (CAST(j."Reference" ->> 'TestSignedByte' AS smallint)) IS NULL """); @@ -2635,7 +2753,7 @@ public override async Task Json_predicate_on_single(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestSingle' AS real)) <> 10.4 OR (CAST(j."Reference" ->> 'TestSingle' AS real)) IS NULL """); @@ -2647,7 +2765,7 @@ public override async Task Json_predicate_on_timespan(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestTimeSpan' AS interval)) <> INTERVAL '03:02:00' OR (CAST(j."Reference" ->> 'TestTimeSpan' AS interval)) IS NULL """); @@ -2659,7 +2777,7 @@ public override async Task Json_predicate_on_dateonly(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDateOnly' AS date)) <> DATE '0003-02-01' OR (CAST(j."Reference" ->> 'TestDateOnly' AS date)) IS NULL """); @@ -2671,7 +2789,7 @@ public override async Task Json_predicate_on_timeonly(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestTimeOnly' AS time without time zone)) <> TIME '03:02:00' OR (CAST(j."Reference" ->> 'TestTimeOnly' AS time without time zone)) IS NULL """); @@ -2683,7 +2801,7 @@ public override async Task Json_predicate_on_unisgnedint16(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestUnsignedInt16' AS integer)) <> 100 OR (CAST(j."Reference" ->> 'TestUnsignedInt16' AS integer)) IS NULL """); @@ -2695,7 +2813,7 @@ public override async Task Json_predicate_on_unsignedint32(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestUnsignedInt32' AS bigint)) <> 1000 OR (CAST(j."Reference" ->> 'TestUnsignedInt32' AS bigint)) IS NULL """); @@ -2707,7 +2825,7 @@ public override async Task Json_predicate_on_unsignedint64(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestUnsignedInt64' AS numeric(20,0))) <> 10000.0 OR (CAST(j."Reference" ->> 'TestUnsignedInt64' AS numeric(20,0))) IS NULL """); @@ -2821,8 +2939,6 @@ public override async Task Json_predicate_on_string_Y_N_converted_to_bool(bool a """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_basic(bool async) { await base.FromSql_on_entity_with_json_basic(async); @@ -2836,8 +2952,6 @@ public override async Task FromSql_on_entity_with_json_basic(bool async) """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_project_json_reference(bool async) { await base.FromSql_on_entity_with_json_project_json_reference(async); @@ -2851,8 +2965,6 @@ public override async Task FromSql_on_entity_with_json_project_json_reference(bo """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_project_json_collection(bool async) { await base.FromSql_on_entity_with_json_project_json_collection(async); @@ -2866,8 +2978,6 @@ public override async Task FromSql_on_entity_with_json_project_json_collection(b """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_inheritance_on_base(bool async) { await base.FromSql_on_entity_with_json_inheritance_on_base(async); @@ -2881,8 +2991,6 @@ public override async Task FromSql_on_entity_with_json_inheritance_on_base(bool """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_inheritance_on_derived(bool async) { await base.FromSql_on_entity_with_json_inheritance_on_derived(async); @@ -2897,8 +3005,6 @@ public override async Task FromSql_on_entity_with_json_inheritance_on_derived(bo """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_inheritance_project_reference_on_base(bool async) { await base.FromSql_on_entity_with_json_inheritance_project_reference_on_base(async); @@ -2913,8 +3019,6 @@ ORDER BY m."Id" NULLS FIRST """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_inheritance_project_reference_on_derived(bool async) { await base.FromSql_on_entity_with_json_inheritance_project_reference_on_derived(async); @@ -2930,6 +3034,335 @@ ORDER BY m."Id" NULLS FIRST """); } + public override async Task Json_projection_using_queryable_methods_on_top_of_JSON_collection_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_using_queryable_methods_on_top_of_JSON_collection_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_nested_collection_anonymous_projection_in_projection_NoTrackingWithIdentityResolution(bool async) + { + await base.Json_nested_collection_anonymous_projection_in_projection_NoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_nested_collection_and_element_using_parameter_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nested_collection_and_element_using_parameter_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_nested_collection_and_element_using_parameter_AsNoTrackingWithIdentityResolution2(bool async) + { + await base.Json_projection_nested_collection_and_element_using_parameter_AsNoTrackingWithIdentityResolution2(async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_different_values_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_different_values_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_projected_before_owner_nested_AsNoTrackingWithIdentityResolution2( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_projected_before_owner_nested_AsNoTrackingWithIdentityResolution2( + async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_projected_after_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_projected_after_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_constant_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_constant_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( +); + } + + public override async Task Json_branch_collection_distinct_and_other_collection_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_branch_collection_distinct_and_other_collection_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_collection_SelectMany_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_collection_SelectMany_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_deduplication_with_collection_indexer_in_target_AsNoTrackingWithIdentityResolution( + bool async) + { + await base.Json_projection_deduplication_with_collection_indexer_in_target_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_nested_collection_and_element_wrong_order_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nested_collection_and_element_wrong_order_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_second_element_projected_before_entire_collection_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_entire_collection_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_second_element_projected_before_owner_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_owner_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_second_element_projected_before_owner_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_owner_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_collection_element_and_reference_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_collection_element_and_reference_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,1}', j."OwnedReferenceRoot" -> 'OwnedReferenceBranch' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_nothing_interesting_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nothing_interesting_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."Name" +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_owner_entity_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_owner_entity_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_nested_collection_anonymous_projection_of_primitives_in_projection_NoTrackingWithIdentityResolution(bool async) + { + await base.Json_nested_collection_anonymous_projection_of_primitives_in_projection_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", s.ordinality, s.c, s.c0, s.c1, s.c2, s.ordinality0 +FROM "JsonEntitiesBasic" AS j +LEFT JOIN LATERAL ( + SELECT o.ordinality, o0."Date" AS c, o0."Enum" AS c0, o0."Enums" AS c1, o0."Fraction" AS c2, o0.ordinality AS ordinality0 + FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedCollectionBranch" jsonb)) WITH ORDINALITY AS o + LEFT JOIN LATERAL ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( + "Date" timestamp without time zone, + "Enum" integer, + "Enums" integer[], + "Fraction" numeric(18,2) + )) WITH ORDINALITY AS o0 ON TRUE +) AS s ON TRUE +ORDER BY j."Id" NULLS FIRST, s.ordinality NULLS FIRST +"""); + } + + public override async Task Json_projection_second_element_through_collection_element_constant_projected_after_owner_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_through_collection_element_constant_projected_after_owner_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_reference_collection_and_collection_element_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_reference_collection_and_collection_element_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedReferenceLeaf}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_correctly_projected_after_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_correctly_projected_after_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( + """ +@__prm_0='1' + +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf}', j."OwnedReferenceRoot" #> ARRAY['OwnedCollectionBranch',0,'OwnedCollectionLeaf',@__prm_0]::text[], @__prm_0 +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task + Json_projection_only_second_element_through_collection_element_constant_projected_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_only_second_element_through_collection_element_constant_projected_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_only_second_element_through_collection_element_parameter_projected_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_only_second_element_through_collection_element_parameter_projected_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +@__prm1_0='0' +@__prm2_1='1' + +SELECT j."Id", j."OwnedReferenceRoot" #> ARRAY['OwnedCollectionBranch',@__prm1_0,'OwnedCollectionLeaf',@__prm2_1]::text[], @__prm1_0, @__prm2_1 +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_second_element_through_collection_element_constant_different_values_projected_before_owner_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_through_collection_element_constant_different_values_projected_before_owner_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,1,OwnedCollectionLeaf}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_nested_collection_and_element_correct_order_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nested_collection_and_element_correct_order_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_nested_collection_element_using_parameter_and_the_owner_in_correct_order_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nested_collection_element_using_parameter_and_the_owner_in_correct_order_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +@__prm_0='0' + +SELECT j."Id", j."OwnedReferenceRoot", j."OwnedReferenceRoot" #> ARRAY['OwnedCollectionBranch',@__prm_0,'OwnedCollectionLeaf',1]::text[], @__prm_0 +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_second_element_projected_before_owner_as_well_as_root_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_owner_as_well_as_root_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,1}', j."OwnedReferenceRoot", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_second_element_projected_before_owner_nested_as_well_as_root_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_owner_nested_as_well_as_root_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedCollectionLeaf,1}', j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedCollectionLeaf}', j."OwnedReferenceRoot" -> 'OwnedReferenceBranch', j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); @@ -2937,12 +3370,55 @@ public virtual void Check_all_tests_overridden() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class JsonQueryNpgsqlFixture : JsonQueryFixtureBase + public class JsonQueryNpgsqlFixture : JsonQueryFixtureBase, IQueryFixtureBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; private JsonQueryData _expectedData; + private readonly IReadOnlyDictionary _entityAsserters; + + public JsonQueryNpgsqlFixture() + { + var entityAsserters = base.EntityAsserters.ToDictionary(); + + entityAsserters[typeof(JsonEntityAllTypes)] = (object e, object a) => + { + Assert.Equal(e == null, a == null); + if (a != null) + { + var ee = (JsonEntityAllTypes)e; + var aa = (JsonEntityAllTypes)a; + + Assert.Equal(ee.Id, aa.Id); + + AssertAllTypes(ee.Reference, aa.Reference); + + Assert.Equal(ee.Collection?.Count ?? 0, aa.Collection?.Count ?? 0); + for (var i = 0; i < ee.Collection.Count; i++) + { + AssertAllTypes(ee.Collection[i], aa.Collection[i]); + } + } + }; + + entityAsserters[typeof(JsonOwnedAllTypes)] = (object e, object a) => + { + Assert.Equal(e == null, a == null); + if (a != null) + { + var ee = (JsonOwnedAllTypes)e; + var aa = (JsonOwnedAllTypes)a; + + AssertAllTypes(ee, aa); + } + }; + + _entityAsserters = entityAsserters; + } + + IReadOnlyDictionary IQueryFixtureBase.EntityAsserters + => _entityAsserters; protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { @@ -2978,6 +3454,77 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Ignore(j => j.TestDoubleCollection); b.Ignore(j => j.TestInt16Collection); }); + + // These use collection types which are unsupported for arrays at the Npgsql level - we currently only support List/array. + modelBuilder.Entity( + b => + { + b.Ignore(j => j.TestInt64Collection); + b.Ignore(j => j.TestGuidCollection); + }); + + // Ignore nested collections - these aren't supported on PostgreSQL (no arrays of arrays). + // TODO: Remove these after syncing to 9.0.0-rc.1, and extending from the relational test base and fixture + modelBuilder.Entity( + b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); + + modelBuilder.Entity().OwnsOne( + x => x.Reference, b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); + + modelBuilder.Entity().OwnsMany( + x => x.Collection, b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); } public override ISetSource GetExpectedData() @@ -3009,7 +3556,7 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(JsonQueryContext context) + protected override async Task SeedAsync(JsonQueryContext context) { // The test data contains DateTimeOffsets with various offsets, which we don't support. Change these to UTC. // Also chop sub-microsecond precision which PostgreSQL does not support. @@ -3053,7 +3600,66 @@ protected override void Seed(JsonQueryContext context) dto => new DateTimeOffset(dto.Ticks - (dto.Ticks % (TimeSpan.TicksPerMillisecond / 1000)), TimeSpan.Zero)).ToList(); } - context.SaveChanges(); + await context.SaveChangesAsync(); + } + + public static new void AssertAllTypes(JsonOwnedAllTypes expected, JsonOwnedAllTypes actual) + { + Assert.Equal(expected.TestDefaultString, actual.TestDefaultString); + Assert.Equal(expected.TestMaxLengthString, actual.TestMaxLengthString); + Assert.Equal(expected.TestBoolean, actual.TestBoolean); + Assert.Equal(expected.TestCharacter, actual.TestCharacter); + Assert.Equal(expected.TestDateTime, actual.TestDateTime); + Assert.Equal(expected.TestDateTimeOffset, actual.TestDateTimeOffset); + Assert.Equal(expected.TestDouble, actual.TestDouble); + Assert.Equal(expected.TestGuid, actual.TestGuid); + Assert.Equal(expected.TestInt16, actual.TestInt16); + Assert.Equal(expected.TestInt32, actual.TestInt32); + Assert.Equal(expected.TestInt64, actual.TestInt64); + Assert.Equal(expected.TestSignedByte, actual.TestSignedByte); + Assert.Equal(expected.TestSingle, actual.TestSingle); + Assert.Equal(expected.TestTimeSpan, actual.TestTimeSpan); + Assert.Equal(expected.TestDateOnly, actual.TestDateOnly); + Assert.Equal(expected.TestTimeOnly, actual.TestTimeOnly); + Assert.Equal(expected.TestUnsignedInt16, actual.TestUnsignedInt16); + Assert.Equal(expected.TestUnsignedInt32, actual.TestUnsignedInt32); + Assert.Equal(expected.TestUnsignedInt64, actual.TestUnsignedInt64); + Assert.Equal(expected.TestNullableInt32, actual.TestNullableInt32); + Assert.Equal(expected.TestEnum, actual.TestEnum); + Assert.Equal(expected.TestEnumWithIntConverter, actual.TestEnumWithIntConverter); + Assert.Equal(expected.TestNullableEnum, actual.TestNullableEnum); + Assert.Equal(expected.TestNullableEnumWithIntConverter, actual.TestNullableEnumWithIntConverter); + Assert.Equal(expected.TestNullableEnumWithConverterThatHandlesNulls, actual.TestNullableEnumWithConverterThatHandlesNulls); + + AssertPrimitiveCollection(expected.TestDefaultStringCollection, actual.TestDefaultStringCollection); + AssertPrimitiveCollection(expected.TestMaxLengthStringCollection, actual.TestMaxLengthStringCollection); + AssertPrimitiveCollection(expected.TestBooleanCollection, actual.TestBooleanCollection); + AssertPrimitiveCollection(expected.TestCharacterCollection, actual.TestCharacterCollection); + AssertPrimitiveCollection(expected.TestDateTimeCollection, actual.TestDateTimeCollection); + AssertPrimitiveCollection(expected.TestDateTimeOffsetCollection, actual.TestDateTimeOffsetCollection); + AssertPrimitiveCollection(expected.TestDoubleCollection, actual.TestDoubleCollection); + AssertPrimitiveCollection(expected.TestGuidCollection, actual.TestGuidCollection); + AssertPrimitiveCollection((IList)expected.TestInt16Collection, (IList)actual.TestInt16Collection); + AssertPrimitiveCollection(expected.TestInt32Collection, actual.TestInt32Collection); + AssertPrimitiveCollection(expected.TestInt64Collection, actual.TestInt64Collection); + AssertPrimitiveCollection(expected.TestSignedByteCollection, actual.TestSignedByteCollection); + AssertPrimitiveCollection(expected.TestSingleCollection, actual.TestSingleCollection); + AssertPrimitiveCollection(expected.TestTimeSpanCollection, actual.TestTimeSpanCollection); + AssertPrimitiveCollection(expected.TestDateOnlyCollection, actual.TestDateOnlyCollection); + AssertPrimitiveCollection(expected.TestTimeOnlyCollection, actual.TestTimeOnlyCollection); + AssertPrimitiveCollection(expected.TestUnsignedInt16Collection, actual.TestUnsignedInt16Collection); + AssertPrimitiveCollection(expected.TestUnsignedInt32Collection, actual.TestUnsignedInt32Collection); + AssertPrimitiveCollection(expected.TestUnsignedInt64Collection, actual.TestUnsignedInt64Collection); + AssertPrimitiveCollection(expected.TestNullableInt32Collection, actual.TestNullableInt32Collection); + AssertPrimitiveCollection(expected.TestEnumCollection, actual.TestEnumCollection); + AssertPrimitiveCollection(expected.TestEnumWithIntConverterCollection, actual.TestEnumWithIntConverterCollection); + AssertPrimitiveCollection(expected.TestNullableEnumCollection, actual.TestNullableEnumCollection); + AssertPrimitiveCollection( + expected.TestNullableEnumWithIntConverterCollection, actual.TestNullableEnumWithIntConverterCollection); + + // AssertPrimitiveCollection( + // expected.TestNullableEnumWithConverterThatHandlesNullsCollection, + // actual.TestNullableEnumWithConverterThatHandlesNullsCollection); } } } diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs index 403b2be92..5504d001c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs @@ -251,7 +251,7 @@ public class JsonStringQueryContext(DbContextOptions options) : PoolableDbContex { public DbSet JsonEntities { get; set; } - public static void Seed(JsonStringQueryContext context) + public static async Task SeedAsync(JsonStringQueryContext context) { const string customer1 = @" { @@ -331,7 +331,8 @@ public static void Seed(JsonStringQueryContext context) CustomerJsonb = array, CustomerJson = array }); - context.SaveChanges(); + + await context.SaveChangesAsync(); } } @@ -359,8 +360,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(JsonStringQueryContext context) - => JsonStringQueryContext.Seed(context); + protected override Task SeedAsync(JsonStringQueryContext context) + => JsonStringQueryContext.SeedAsync(context); } #endregion diff --git a/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs index 91781976a..c09820ac1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs @@ -506,7 +506,7 @@ public class LTreeQueryContext(DbContextOptions options) : PoolableDbContext(opt { public DbSet LTreeEntities { get; set; } - public static void Seed(LTreeQueryContext context) + public static async Task SeedAsync(LTreeQueryContext context) { var ltreeEntities = new LTreeEntity[] { @@ -527,7 +527,7 @@ public static void Seed(LTreeQueryContext context) } context.LTreeEntities.AddRange(ltreeEntities); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -560,8 +560,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(LTreeQueryContext context) - => LTreeQueryContext.Seed(context); + protected override Task SeedAsync(LTreeQueryContext context) + => LTreeQueryContext.SeedAsync(context); } #endregion diff --git a/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs index 13a4dcb34..3e0201d9a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs @@ -111,10 +111,13 @@ public LegacyTimestampQueryFixture() NpgsqlTypeMappingSource.LegacyTimestampBehavior = true; } - public override void Dispose() - => NpgsqlTypeMappingSource.LegacyTimestampBehavior = false; + public override Task DisposeAsync() + { + NpgsqlTypeMappingSource.LegacyTimestampBehavior = false; + return Task.CompletedTask; + } - protected override void Seed(TimestampQueryContext context) + protected override async Task SeedAsync(TimestampQueryContext context) { using var ctx = CreateContext(); @@ -136,7 +139,8 @@ protected override void Seed(TimestampQueryContext context) TimestampDateTime = DateTime.SpecifyKind(utcDateTime2.ToLocalTime(), DateTimeKind.Unspecified), TimestampDateTimeOffset = new DateTimeOffset(utcDateTime2) }); - ctx.SaveChanges(); + + await ctx.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/MathQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/MathQueryTest.cs deleted file mode 100644 index c45f7e47b..000000000 --- a/test/EFCore.PG.FunctionalTests/Query/MathQueryTest.cs +++ /dev/null @@ -1,367 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; - -/// -/// Provides unit tests for PostgreSQL-specific math functions. -/// -/// -/// See: -/// - https://www.postgresql.org/docs/current/static/functions-math.html -/// - https://www.postgresql.org/docs/current/static/functions-conditional.html#FUNCTIONS-GREATEST-LEAST -/// -public class MathQueryTest : IClassFixture -{ - private MathQueryNpgsqlFixture Fixture { get; } - - // ReSharper disable once UnusedParameter.Local - public MathQueryTest(MathQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - { - Fixture = fixture; - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - #region GREATEST - - //[Fact] - public void Max_ushort_ushort() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.UShort, x.UShort)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"UShort\", x.\"UShort\")"); - } - - //[Fact] - public void Max_uint_uint() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.UInt, x.UInt)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"UInt\", x.\"UInt\")"); - } - - //[Fact] - public void Max_ulong_ulong() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.ULong, x.ULong)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"ULong\", x.\"ULong\")"); - } - - [Fact] - public void Max_short_short() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Short, x.Short)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Short\", x.\"Short\")"); - } - - [Fact] - public void Max_int_int() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Int, x.Int)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Int\", x.\"Int\")"); - } - - [Fact] - public void Max_long_long() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Long, x.Long)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Long\", x.\"Long\")"); - } - - [Fact] - public void Max_float_float() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Float, x.Float)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Float\", x.\"Float\")"); - } - - [Fact] - public void Max_double_double() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Double, x.Double)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Double\", x.\"Double\")"); - } - - [Fact] - public void Max_decimal_decimal() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Decimal, x.Decimal)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Decimal\", x.\"Decimal\")"); - } - - //[Fact] - public void Max_sbyte_sbyte() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.SByte, x.SByte)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"SByte\", x.\"SByte\")"); - } - - //[Fact] - public void Max_byte_byte() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Byte, x.Byte)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Byte\", x.\"Byte\")"); - } - - #endregion - - #region Least - - //[Fact] - public void Min_ushort_ushort() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.UShort, x.UShort)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"UShort\", x.\"UShort\")"); - } - - //[Fact] - public void Min_uint_uint() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.UInt, x.UInt)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"UInt\", x.\"UInt\")"); - } - - //[Fact] - public void Min_ulong_ulong() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.ULong, x.ULong)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"ULong\", x.\"ULong\")"); - } - - [Fact] - public void Min_short_short() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Short, x.Short)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Short\", x.\"Short\")"); - } - - [Fact] - public void Min_int_int() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Int, x.Int)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Int\", x.\"Int\")"); - } - - [Fact] - public void Min_long_long() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Long, x.Long)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Long\", x.\"Long\")"); - } - - [Fact] - public void Min_float_float() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Float, x.Float)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Float\", x.\"Float\")"); - } - - [Fact] - public void Min_double_double() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Double, x.Double)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Double\", x.\"Double\")"); - } - - [Fact] - public void Min_decimal_decimal() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Decimal, x.Decimal)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Decimal\", x.\"Decimal\")"); - } - - //[Fact] - public void Min_sbyte_sbyte() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.SByte, x.SByte)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"SByte\", x.\"SByte\")"); - } - - //[Fact] - public void Min_byte_byte() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Byte, x.Byte)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Byte\", x.\"Byte\")"); - } - - #endregion - - #region Fixtures - - /// - /// Represents a fixture suitable for testing GREATEST(...) and LEAST(...)/ - /// - public class MathQueryNpgsqlFixture : SharedStoreFixtureBase - { - protected override string StoreName - => "MathQueryTest"; - - protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; - - public TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - } - - /// - /// Represents an entity suitable for testing GREATEST(...) and LEAST(...) operators. - /// - public class MathTestEntity - { - public int Id { get; set; } - public ushort UShort { get; set; } - public uint UInt { get; set; } - public ulong ULong { get; set; } - public short Short { get; set; } - public int Int { get; set; } - public long Long { get; set; } - public float Float { get; set; } - public double Double { get; set; } - public decimal Decimal { get; set; } - public byte Byte { get; set; } - public sbyte SByte { get; set; } - } - - /// - /// Represents a database suitable for testing GREATEST(...) and LEAST(...). - /// - public class MathContext : PoolableDbContext - { - /// - /// Represents a set of entities with numeric properties. - /// - public DbSet MathTestEntities { get; set; } - - /// - public MathContext(DbContextOptions options) - : base(options) - { - } - } - - #endregion - - #region Helpers - - protected MathContext CreateContext() - => Fixture.CreateContext(); - - /// - /// Asserts that the SQL fragment appears in the logs. - /// - /// The SQL statement or fragment to search for in the logs. - public void AssertContainsSql(string sql) - => Assert.Contains( - sql.Replace(Environment.NewLine, " ").Remove('\r').Remove('\n'), - Fixture.TestSqlLoggerFactory.Sql.Replace(Environment.NewLine, " ").Remove('\r').Remove('\n')); - - #endregion -} diff --git a/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs index d62f2518c..5f896f298 100644 --- a/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs @@ -730,8 +730,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(MultirangeContext context) - => MultirangeContext.Seed(context); + protected override Task SeedAsync(MultirangeContext context) + => MultirangeContext.SeedAsync(context); } public class MultirangeTestEntity @@ -753,7 +753,7 @@ public class MultirangeContext(DbContextOptions options) : PoolableDbContext(opt { public DbSet TestEntities { get; set; } - public static void Seed(MultirangeContext context) + public static async Task SeedAsync(MultirangeContext context) { context.TestEntities.AddRange( new MultirangeTestEntity @@ -789,7 +789,7 @@ public static void Seed(MultirangeContext context) ] }); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs index 0af672aef..3301d9e0b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs @@ -1458,8 +1458,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(NetContext context) - => NetContext.Seed(context); + protected override Task SeedAsync(NetContext context) + => NetContext.SeedAsync(context); } /// @@ -1537,7 +1537,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } - public static void Seed(NetContext context) + public static async Task SeedAsync(NetContext context) { for (var i = 1; i <= 9; i++) { @@ -1557,7 +1557,7 @@ public static void Seed(NetContext context) }); } - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs index c6fa0961b..1ed6bed17 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs @@ -1869,10 +1869,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.HasPostgresExtension("btree_gist"); } - public static void Seed(NodaTimeContext context) + public static async Task SeedAsync(NodaTimeContext context) { context.AddRange(NodaTimeData.CreateNodaTimeTypes()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -1928,8 +1928,8 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build return optionsBuilder; } - protected override void Seed(NodaTimeContext context) - => NodaTimeContext.Seed(context); + protected override Task SeedAsync(NodaTimeContext context) + => NodaTimeContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs index 90ef8b15c..fa4e4ab1b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs @@ -57,11 +57,11 @@ public override async Task Multidimensional_array_is_not_supported() // supported). var contextFactory = await InitializeAsync( mb => mb.Entity().Property("MultidimensionalArray"), - seed: context => + seed: async context => { var entry = context.Add(new TestEntity()); entry.Property("MultidimensionalArray").CurrentValue = new[,] { { 1, 2 }, { 3, 4 } }; - context.SaveChanges(); + await context.SaveChangesAsync(); }); await using var context = contextFactory.CreateContext(); @@ -77,9 +77,22 @@ await Assert.ThrowsAsync( public override async Task Column_collection_inside_json_owned_entity() { - var exception = await Assert.ThrowsAsync(() => base.Column_collection_inside_json_owned_entity()); - - Assert.Equal(exception.Message, NpgsqlStrings.Ef7JsonMappingNotSupported); + await base.Column_collection_inside_json_owned_entity(); + + AssertSql( + """ +SELECT t."Id", t."Owned" +FROM "TestOwner" AS t +WHERE cardinality((ARRAY(SELECT CAST(element AS text) FROM jsonb_array_elements_text(t."Owned" -> 'Strings') WITH ORDINALITY AS t(element) ORDER BY ordinality))) = 2 +LIMIT 2 +""", + // + """ +SELECT t."Id", t."Owned" +FROM "TestOwner" AS t +WHERE ((ARRAY(SELECT CAST(element AS text) FROM jsonb_array_elements_text(t."Owned" -> 'Strings') WITH ORDINALITY AS t(element) ORDER BY ordinality)))[2] = 'bar' +LIMIT 2 +"""); } protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs index 4b9f7a5da..0b13fc070 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs @@ -234,10 +234,10 @@ public void ToDate() var count = context.Orders.Count(c => EF.Functions.ToDate(c.OrderDate.ToString(), "YYYY-MM-DD") < new DateOnly(2000, 01, 01)); Assert.Equal(830, count); AssertSql( -""" + """ SELECT count(*)::int FROM "Orders" AS o -WHERE to_date(o."OrderDate"::text, 'YYYY-MM-DD') < DATE '2000-01-01' +WHERE to_date(COALESCE(o."OrderDate"::text, ''), 'YYYY-MM-DD') < DATE '2000-01-01' """); } @@ -251,7 +251,7 @@ public void ToTimestamp() """ SELECT count(*)::int FROM "Orders" AS o -WHERE to_timestamp(o."OrderDate"::text, 'YYYY-MM-DD') < TIMESTAMPTZ '2000-01-01T00:00:00Z' +WHERE to_timestamp(COALESCE(o."OrderDate"::text, ''), 'YYYY-MM-DD') < TIMESTAMPTZ '2000-01-01T00:00:00Z' """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs index 2950d6723..48f0fa784 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs @@ -45,28 +45,21 @@ public override Task Where_mathf_round2(bool async) public override Task Convert_ToString(bool async) => AssertTranslationFailed(() => base.Convert_ToString(async)); -// [ConditionalTheory] -// [MemberData(nameof(IsAsyncData))] -// public virtual async Task String_Join_non_aggregate(bool async) -// { -// var param = "param"; -// string nullParam = null; -// -// await AssertQuery( -// async, -// ss => ss.Set().Where( -// c => string.Join("|", c.CustomerID, c.CompanyName, param, nullParam, "constant", null) -// == "ALFKI|Alfreds Futterkiste|param||constant|")); -// -// AssertSql( -// """ -// @__param_0='param' -// -// SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" -// FROM "Customers" AS c -// WHERE concat_ws('|', c."CustomerID", c."CompanyName", COALESCE(@__param_0, ''), COALESCE(NULL, ''), 'constant', '') = 'ALFKI|Alfreds Futterkiste|param||constant|' -// """); -// } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public override async Task String_Join_non_aggregate(bool async) + { + await base.String_Join_non_aggregate(async); + + AssertSql( + """ +@__foo_0='foo' + +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" +FROM "Customers" AS c +WHERE concat_ws('|', c."CompanyName", @__foo_0, '', 'bar') = 'Around the Horn|foo||bar' +"""); + } #region Substring diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs index 1f497fa40..d413e5c8c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs @@ -2670,15 +2670,18 @@ public override async Task GroupBy_Count_in_projection(bool async) SELECT o."OrderID", o."OrderDate", EXISTS ( SELECT 1 FROM "Order Details" AS o0 - WHERE o."OrderID" = o0."OrderID" AND o0."ProductID" < 25) AS "HasOrderDetails", ( - SELECT count(*)::int - FROM ( - SELECT 1 - FROM "Order Details" AS o1 - INNER JOIN "Products" AS p ON o1."ProductID" = p."ProductID" - WHERE o."OrderID" = o1."OrderID" AND o1."ProductID" < 25 - GROUP BY p."ProductName" - ) AS s) > 1 AS "HasMultipleProducts" + WHERE o."OrderID" = o0."OrderID" AND o0."ProductID" < 25) AS "HasOrderDetails", CASE + WHEN ( + SELECT count(*)::int + FROM ( + SELECT 1 + FROM "Order Details" AS o1 + INNER JOIN "Products" AS p ON o1."ProductID" = p."ProductID" + WHERE o."OrderID" = o1."OrderID" AND o1."ProductID" < 25 + GROUP BY p."ProductName" + ) AS s) > 1 THEN TRUE + ELSE FALSE +END AS "HasMultipleProducts" FROM "Orders" AS o WHERE o."OrderDate" IS NOT NULL """); diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs index 89f18e27e..d6db7b54d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs @@ -24,7 +24,7 @@ public override async Task Query_expression_with_to_string_and_contains(bool asy """ SELECT o."CustomerID" FROM "Orders" AS o -WHERE o."OrderDate" IS NOT NULL AND o."EmployeeID"::text LIKE '%7%' +WHERE o."OrderDate" IS NOT NULL AND COALESCE(o."EmployeeID"::text, '') LIKE '%7%' """); } @@ -199,6 +199,18 @@ ORDER BY o0."CustomerID" NULLS FIRST """); } + public override async Task Where_bitwise_binary_xor(bool async) + { + await base.Where_bitwise_binary_xor(async); + + AssertSql( + """ +SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" +FROM "Orders" AS o +WHERE (o."OrderID" # 1) = 10249 +"""); + } + // TODO: Array tests can probably move to the dedicated ArrayQueryTest suite #region Array contains diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs index 18109a3c8..552db3a89 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs @@ -29,7 +29,6 @@ public override async Task Where_datetime_today(bool async) """ SELECT e."EmployeeID", e."City", e."Country", e."FirstName", e."ReportsTo", e."Title" FROM "Employees" AS e -WHERE date_trunc('day', now()::timestamp) = date_trunc('day', now()::timestamp) """); } @@ -172,15 +171,19 @@ public override Task Where_datetimeoffset_utcnow(bool async) public override async Task Where_bitwise_xor(bool async) { - // Cannot eval 'where (([c].CustomerID == \"ALFKI\") ^ True)'. Issue #16645. - await AssertTranslationFailed(() => base.Where_bitwise_xor(async)); + await base.Where_bitwise_xor(async); - AssertSql(); + AssertSql( + """ +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" +FROM "Customers" AS c +WHERE (c."CustomerID" = 'ALFKI') <> TRUE +"""); } public override async Task Where_compare_constructed_equal(bool async) { - // Anonymous type to constant comparison. Issue #14672. + // Anonymous type to constant comparison. Issue #14672. await AssertTranslationFailed(() => base.Where_compare_constructed_equal(async)); AssertSql(); @@ -481,10 +484,29 @@ public async Task Row_value_not_equals() { await using var ctx = CreateContext(); + // Everything is non-nullable, so we use the nicer row value comparison syntax + _ = await ctx.Customers + .Where(c => !ValueTuple.Create(c.CustomerID, c.CustomerID).Equals(ValueTuple.Create("OCEAN", "OCEAN"))) + .CountAsync(); + + AssertSql( + """ +SELECT count(*)::int +FROM "Customers" AS c +WHERE (c."CustomerID", c."CustomerID") <> ('OCEAN', 'OCEAN') +"""); + } + + [ConditionalFact] + public async Task Row_value_not_equals_with_nullable() + { + await using var ctx = CreateContext(); + _ = await ctx.Customers .Where(c => !ValueTuple.Create(c.City, c.CustomerID).Equals(ValueTuple.Create("Buenos Aires", "OCEAN"))) .CountAsync(); + // City is nullable, so we must extract that comparison out of the row value AssertSql( """ SELECT count(*)::int diff --git a/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlFixture.cs deleted file mode 100644 index 497d71540..000000000 --- a/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlFixture.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; - -public class NullSemanticsQueryNpgsqlFixture : NullSemanticsQueryFixtureBase -{ - protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; -} diff --git a/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlTest.cs index a5bb64c03..eb0ba354a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlTest.cs @@ -1,10 +1,12 @@ using Microsoft.EntityFrameworkCore.TestModels.NullSemanticsModel; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; // ReSharper disable once UnusedMember.Global -public class NullSemanticsQueryNpgsqlTest : NullSemanticsQueryTestBase +public class NullSemanticsQueryNpgsqlTest : NullSemanticsQueryTestBase { public NullSemanticsQueryNpgsqlTest(NullSemanticsQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) @@ -180,4 +182,26 @@ protected override NullSemanticsContext CreateContext(bool useRelationalNulls = return context; } + + public class NullSemanticsQueryNpgsqlFixture : NullSemanticsQueryFixtureBase + { + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + // The base implementations maps this function to bool constants with BoolTypeMapping.Default which doesn't work with PG; + // override to use NpgsqlBoolTypeMapping instead. + modelBuilder.HasDbFunction( + typeof(NullSemanticsQueryFixtureBase).GetMethod(nameof(BoolSwitch))!, + b => b.HasTranslation(args => new CaseExpression( + operand: args[0], + [ + new CaseWhenClause(new SqlConstantExpression(true, typeMapping: NpgsqlBoolTypeMapping.Default), args[1]), + new CaseWhenClause(new SqlConstantExpression(false, typeMapping: NpgsqlBoolTypeMapping.Default), args[2]) + ]))); + } + } } diff --git a/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs index 3bbbe57d7..97bdda412 100644 --- a/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs @@ -15,21 +15,46 @@ public override async Task Bitwise_and_on_expression_with_like_and_null_check_be { await base.Bitwise_and_on_expression_with_like_and_null_check_being_compared_to_false(); - AssertSql(""); + AssertSql( + """ +SELECT o."Value" AS "Value1", o0."Value" AS "Value2", o1."Value" AS "Value3" +FROM "OperatorEntityString" AS o +CROSS JOIN "OperatorEntityString" AS o0 +CROSS JOIN "OperatorEntityBool" AS o1 +WHERE ((o0."Value" LIKE 'B' AND o0."Value" IS NOT NULL) OR o1."Value") AND o."Value" IS NOT NULL +ORDER BY o."Id" NULLS FIRST, o0."Id" NULLS FIRST, o1."Id" NULLS FIRST +"""); } public override async Task Complex_predicate_with_bitwise_and_modulo_and_negation() { await base.Complex_predicate_with_bitwise_and_modulo_and_negation(); - AssertSql(""); + AssertSql( + """ +SELECT o."Value" AS "Value0", o0."Value" AS "Value1", o1."Value" AS "Value2", o2."Value" AS "Value3" +FROM "OperatorEntityLong" AS o +CROSS JOIN "OperatorEntityLong" AS o0 +CROSS JOIN "OperatorEntityLong" AS o1 +CROSS JOIN "OperatorEntityLong" AS o2 +WHERE (o0."Value" % 2) / o."Value" & ((o2."Value" | o1."Value") - o."Value") - o1."Value" * o1."Value" >= ((o0."Value" / (~o2."Value")) % 2) % ((~o."Value") + 1) +ORDER BY o."Id" NULLS FIRST, o0."Id" NULLS FIRST, o1."Id" NULLS FIRST, o2."Id" NULLS FIRST +"""); } public override async Task Complex_predicate_with_bitwise_and_arithmetic_operations() { await base.Complex_predicate_with_bitwise_and_arithmetic_operations(); - AssertSql(""); + AssertSql( + """ +SELECT o."Value" AS "Value0", o0."Value" AS "Value1", o1."Value" AS "Value2" +FROM "OperatorEntityInt" AS o +CROSS JOIN "OperatorEntityInt" AS o0 +CROSS JOIN "OperatorEntityBool" AS o1 +WHERE (o0."Value" & o."Value" + o."Value" & o."Value") / 1 > o0."Value" & 10 AND o1."Value" +ORDER BY o."Id" NULLS FIRST, o0."Id" NULLS FIRST, o1."Id" NULLS FIRST +"""); } public override async Task Or_on_two_nested_binaries_and_another_simple_comparison() @@ -44,7 +69,7 @@ CROSS JOIN "OperatorEntityString" AS o0 CROSS JOIN "OperatorEntityString" AS o1 CROSS JOIN "OperatorEntityString" AS o2 CROSS JOIN "OperatorEntityInt" AS o3 -WHERE ((o."Value" = 'A' AND o."Value" IS NOT NULL AND o0."Value" = 'A' AND o0."Value" IS NOT NULL) OR (o1."Value" = 'B' AND o1."Value" IS NOT NULL AND o2."Value" = 'B' AND o2."Value" IS NOT NULL)) AND o3."Value" = 2 +WHERE ((o."Value" = 'A' AND o0."Value" = 'A') OR (o1."Value" = 'B' AND o2."Value" = 'B')) AND o3."Value" = 2 ORDER BY o."Id" NULLS FIRST, o0."Id" NULLS FIRST, o1."Id" NULLS FIRST, o2."Id" NULLS FIRST, o3."Id" NULLS FIRST """); } @@ -129,12 +154,12 @@ LIMIT 2 public virtual async Task AtTimeZone_and_addition() { var contextFactory = await InitializeAsync( - seed: context => + seed: async context => { context.Set().AddRange( new OperatorEntityDateTime { Id = 1, Value = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, new OperatorEntityDateTime { Id = 2, Value = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc) }); - context.SaveChanges(); + await context.SaveChangesAsync(); }, onModelCreating: modelBuilder => modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever()); @@ -160,7 +185,7 @@ public class OperatorEntityDateTime : OperatorEntityBase public DateTime Value { get; set; } } - protected override void Seed(OperatorsContext ctx) + protected override async Task Seed(OperatorsContext ctx) { ctx.Set().AddRange(ExpectedData.OperatorEntitiesString); ctx.Set().AddRange(ExpectedData.OperatorEntitiesInt); @@ -170,6 +195,6 @@ protected override void Seed(OperatorsContext ctx) ctx.Set().AddRange(ExpectedData.OperatorEntitiesNullableBool); // ctx.Set().AddRange(ExpectedData.OperatorEntitiesDateTimeOffset); - ctx.SaveChanges(); + await ctx.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 7d34b3711..9b8cd9926 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -197,6 +197,20 @@ public override async Task Inline_collection_Contains_with_mixed_value_types(boo """ @__i_0='11' +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Int" IN (999, @__i_0, p."Id", p."Id" + p."Int") +"""); + } + + public override async Task Inline_collection_List_Contains_with_mixed_value_types(bool async) + { + await base.Inline_collection_List_Contains_with_mixed_value_types(async); + + AssertSql( + """ +@__i_0='11' + SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE p."Int" IN (999, @__i_0, p."Id", p."Id" + p."Int") @@ -239,6 +253,18 @@ WHERE LEAST(30, p."Int") = 30 """); } + public override async Task Inline_collection_List_Min_with_two_values(bool async) + { + await base.Inline_collection_List_Min_with_two_values(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."Int") = 30 +"""); + } + public override async Task Inline_collection_Max_with_two_values(bool async) { await base.Inline_collection_Max_with_two_values(async); @@ -251,6 +277,18 @@ WHERE GREATEST(30, p."Int") = 30 """); } + public override async Task Inline_collection_List_Max_with_two_values(bool async) + { + await base.Inline_collection_List_Max_with_two_values(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."Int") = 30 +"""); + } + public override async Task Inline_collection_Min_with_three_values(bool async) { await base.Inline_collection_Min_with_three_values(async); @@ -259,6 +297,20 @@ public override async Task Inline_collection_Min_with_three_values(bool async) """ @__i_0='25' +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."Int", @__i_0) = 25 +"""); + } + + public override async Task Inline_collection_List_Min_with_three_values(bool async) + { + await base.Inline_collection_List_Min_with_three_values(async); + + AssertSql( + """ +@__i_0='25' + SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE LEAST(30, p."Int", @__i_0) = 25 @@ -279,6 +331,72 @@ WHERE GREATEST(30, p."Int", @__i_0) = 35 """); } + public override async Task Inline_collection_List_Max_with_three_values(bool async) + { + await base.Inline_collection_List_Max_with_three_values(async); + + AssertSql( + """ +@__i_0='35' + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."Int", @__i_0) = 35 +"""); + } + + public override async Task Inline_collection_of_nullable_value_type_Min(bool async) + { + await base.Inline_collection_of_nullable_value_type_Min(async); + + AssertSql( + """ +@__i_0='25' (Nullable = true) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."Int", @__i_0) = 25 +"""); + } + + public override async Task Inline_collection_of_nullable_value_type_Max(bool async) + { + await base.Inline_collection_of_nullable_value_type_Max(async); + + AssertSql( + """ +@__i_0='35' (Nullable = true) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."Int", @__i_0) = 35 +"""); + } + + public override async Task Inline_collection_of_nullable_value_type_with_null_Min(bool async) + { + await base.Inline_collection_of_nullable_value_type_with_null_Min(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."NullableInt", NULL) = 30 +"""); + } + + public override async Task Inline_collection_of_nullable_value_type_with_null_Max(bool async) + { + await base.Inline_collection_of_nullable_value_type_with_null_Max(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."NullableInt", NULL) = 30 +"""); + } + public override async Task Parameter_collection_Count(bool async) { await base.Parameter_collection_Count(async); @@ -312,6 +430,28 @@ public override async Task Parameter_collection_of_ints_Contains_int(bool async) """ @__ints_0={ '10', '999' } (DbType = Object) +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."Int" = ANY (@__ints_0) AND p."Int" = ANY (@__ints_0) IS NOT NULL) +"""); + } + + public override async Task Parameter_collection_HashSet_of_ints_Contains_int(bool async) + { + await base.Parameter_collection_HashSet_of_ints_Contains_int(async); + + AssertSql( + """ +@__ints_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Int" = ANY (@__ints_0) +""", + // + """ +@__ints_0={ '10', '999' } (DbType = Object) + SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE NOT (p."Int" = ANY (@__ints_0) AND p."Int" = ANY (@__ints_0) IS NOT NULL) @@ -648,6 +788,36 @@ WHERE cardinality(p."Ints") = 2 """); } + public override async Task Column_collection_Count_with_predicate(bool async) + { + await base.Column_collection_Count_with_predicate(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1) = 2 +"""); + } + + public override async Task Column_collection_Where_Count(bool async) + { + await base.Column_collection_Where_Count(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1) = 2 +"""); + } + public override async Task Column_collection_index_int(bool async) { await base.Column_collection_index_int(async); @@ -736,6 +906,38 @@ ORDER BY v._ord NULLS FIRST """); } + public override async Task Inline_collection_value_index_Column(bool async) + { + await base.Inline_collection_value_index_Column(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT v."Value" + FROM (VALUES (0, 1::int), (1, p."Int"), (2, 3)) AS v(_ord, "Value") + ORDER BY v._ord NULLS FIRST + LIMIT 1 OFFSET p."Int") = 1 +"""); + } + + public override async Task Inline_collection_List_value_index_Column(bool async) + { + await base.Inline_collection_List_value_index_Column(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT v."Value" + FROM (VALUES (0, 1::int), (1, p."Int"), (2, 3)) AS v(_ord, "Value") + ORDER BY v._ord NULLS FIRST + LIMIT 1 OFFSET p."Int") = 1 +"""); + } + public override async Task Parameter_collection_index_Column_equal_Column(bool async) { await base.Parameter_collection_index_Column_equal_Column(async); @@ -776,6 +978,66 @@ public override async Task Column_collection_ElementAt(bool async) """); } + public override async Task Column_collection_First(bool async) + { + await base.Column_collection_First(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + LIMIT 1) = 1 +"""); + } + + public override async Task Column_collection_FirstOrDefault(bool async) + { + await base.Column_collection_FirstOrDefault(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE COALESCE(( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + LIMIT 1), 0) = 1 +"""); + } + + public override async Task Column_collection_Single(bool async) + { + await base.Column_collection_Single(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + LIMIT 1) = 1 +"""); + } + + public override async Task Column_collection_SingleOrDefault(bool async) + { + await base.Column_collection_SingleOrDefault(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE COALESCE(( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + LIMIT 1), 0) = 1 +"""); + } + public override async Task Column_collection_Skip(bool async) { await base.Column_collection_Skip(async); @@ -809,6 +1071,79 @@ public override async Task Column_collection_Skip_Take(bool async) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE 11 = ANY (p."Ints"[2:3]) +"""); + } + + public override async Task Column_collection_Where_Skip(bool async) + { + await base.Column_collection_Where_Skip(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM ( + SELECT 1 + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 + OFFSET 1 + ) AS i0) = 3 +"""); + } + + public override async Task Column_collection_Where_Take(bool async) + { + await base.Column_collection_Where_Take(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM ( + SELECT 1 + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 + LIMIT 2 + ) AS i0) = 2 +"""); + } + + public override async Task Column_collection_Where_Skip_Take(bool async) + { + await base.Column_collection_Where_Skip_Take(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM ( + SELECT 1 + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 + LIMIT 2 OFFSET 1 + ) AS i0) = 1 +"""); + } + + public override async Task Column_collection_Contains_over_subquery(bool async) + { + await base.Column_collection_Contains_over_subquery(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE 11 IN ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 +) """); } @@ -828,6 +1163,22 @@ ORDER BY i.value DESC NULLS LAST """); } + public override async Task Column_collection_Where_ElementAt(bool async) + { + await base.Column_collection_Where_ElementAt(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 + LIMIT 1 OFFSET 0) = 11 +"""); + } + public override async Task Column_collection_Any(bool async) { await base.Column_collection_Any(async); @@ -869,6 +1220,34 @@ JOIN LATERAL unnest(p."Ints") AS i(value) ON TRUE """); } + public override async Task Column_collection_SelectMany_with_filter(bool async) + { + await base.Column_collection_SelectMany_with_filter(async); + + AssertSql( + """ +SELECT i0.value +FROM "PrimitiveCollectionsEntity" AS p +JOIN LATERAL ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 +) AS i0 ON TRUE +"""); + } + + public override async Task Column_collection_SelectMany_with_Select_to_anonymous_type(bool async) + { + await base.Column_collection_SelectMany_with_Select_to_anonymous_type(async); + + AssertSql( + """ +SELECT i.value AS "Original", i.value + 1 AS "Incremented" +FROM "PrimitiveCollectionsEntity" AS p +JOIN LATERAL unnest(p."Ints") AS i(value) ON TRUE +"""); + } + public override async Task Column_collection_projection_from_top_level(bool async) { await base.Column_collection_projection_from_top_level(async); @@ -1009,6 +1388,26 @@ FROM unnest(p."Ints") AS i(value) """); } + public override async Task Column_collection_Where_Union(bool async) + { + await base.Column_collection_Where_Union(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 100 + UNION + VALUES (50::int) + ) AS u) = 2 +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Parameter_collection_Concat_Column_collection_Concat_parameter(bool async) @@ -1084,6 +1483,13 @@ await AssertQuery( """); } + public override async Task Column_collection_Where_equality_inline_collection(bool async) + { + await base.Column_collection_Where_equality_inline_collection(async); + + AssertSql(); + } + public override async Task Parameter_collection_in_subquery_Union_column_collection_as_compiled_query(bool async) { await base.Parameter_collection_in_subquery_Union_column_collection_as_compiled_query(async); @@ -1252,7 +1658,7 @@ public override async Task Project_collection_of_datetimes_filtered(bool async) LEFT JOIN LATERAL ( SELECT d.value, d.ordinality FROM unnest(p."DateTimes") WITH ORDINALITY AS d(value) - WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 OR d.value AT TIME ZONE 'UTC' IS NULL + WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 ) AS d0 ON TRUE ORDER BY p."Id" NULLS FIRST, d0.ordinality NULLS FIRST """); @@ -1396,7 +1802,7 @@ LEFT JOIN LATERAL unnest(p."Ints") WITH ORDINALITY AS i0(value) ON TRUE LEFT JOIN LATERAL ( SELECT d.value, d.ordinality FROM unnest(p."DateTimes") WITH ORDINALITY AS d(value) - WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 OR d.value AT TIME ZONE 'UTC' IS NULL + WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 ) AS d1 ON TRUE LEFT JOIN LATERAL ( SELECT d0.value, d0.ordinality @@ -1420,6 +1826,43 @@ ORDER BY p."Id" NULLS FIRST """); } + public override async Task Project_inline_collection(bool async) + { + await base.Project_inline_collection(async); + + AssertSql( + """ +SELECT ARRAY[p."String",'foo']::text[] +FROM "PrimitiveCollectionsEntity" AS p +"""); + } + + public override async Task Project_inline_collection_with_Union(bool async) + { + await base.Project_inline_collection_with_Union(async); + + AssertSql( + """ +SELECT p."Id", u."Value" +FROM "PrimitiveCollectionsEntity" AS p +LEFT JOIN LATERAL ( + SELECT v."Value" + FROM (VALUES (p."String")) AS v("Value") + UNION + SELECT p0."String" AS "Value" + FROM "PrimitiveCollectionsEntity" AS p0 +) AS u ON TRUE +ORDER BY p."Id" NULLS FIRST +"""); + } + + public override async Task Project_inline_collection_with_Concat(bool async) + { + await base.Project_inline_collection_with_Concat(async); + + AssertSql(); + } + public override async Task Nested_contains_with_Lists_and_no_inferred_type_mapping(bool async) { await base.Nested_contains_with_Lists_and_no_inferred_type_mapping(async); diff --git a/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs b/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs index 8b626b5c9..26bb1e04d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs @@ -20,16 +20,16 @@ public QueryBugsTest(NpgsqlFixture fixture, ITestOutputHelper testOutputHelper) #region Bug920 [Fact] - public void Bug920() + public async Task Bug920() { - using var _ = CreateDatabase920(); + using var _ = await CreateDatabase920Async(); using var context = new Bug920Context(_options); context.Entities.Add(new Bug920Entity { Enum = Bug920Enum.Two }); context.SaveChanges(); } - private NpgsqlTestStore CreateDatabase920() - => CreateTestStore(() => new Bug920Context(_options), _ => ClearLog()); + private Task CreateDatabase920Async() + => CreateTestStoreAsync(() => new Bug920Context(_options), _ => ClearLog()); public enum Bug920Enum { One, Two } @@ -50,17 +50,17 @@ private class Bug920Context(DbContextOptions options) : DbContext(options) private DbContextOptions _options; - private NpgsqlTestStore CreateTestStore( + private async Task CreateTestStoreAsync( Func contextCreator, Action contextInitializer) where TContext : DbContext, IDisposable { - var testStore = NpgsqlTestStore.CreateInitialized("QueryBugsTest"); + var testStore = await NpgsqlTestStore.CreateInitializedAsync("QueryBugsTest"); _options = Fixture.CreateOptions(testStore); - using var context = contextCreator(); - context.Database.EnsureCreatedResiliently(); + await using var context = contextCreator(); + await context.Database.EnsureCreatedResilientlyAsync(); contextInitializer?.Invoke(context); return testStore; } diff --git a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs index 7021a6497..29772f60e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs @@ -626,8 +626,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(RangeContext context) - => RangeContext.Seed(context); + protected override Task SeedAsync(RangeContext context) + => RangeContext.SeedAsync(context); public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) { @@ -662,7 +662,7 @@ protected override void OnModelCreating(ModelBuilder builder) => builder.HasPostgresRange("doublerange", "double precision") .HasPostgresRange("test", "Schema_Range", "real"); - public static void Seed(RangeContext context) + public static async Task SeedAsync(RangeContext context) { context.RangeTestEntities.AddRange( new RangeTestEntity @@ -688,7 +688,7 @@ public static void Seed(RangeContext context) UserDefinedRangeWithSchema = new NpgsqlRange(5, 15) }); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/SharedTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SharedTypeQueryNpgsqlTest.cs index 6fa1f5c1b..91ef5653a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SharedTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SharedTypeQueryNpgsqlTest.cs @@ -9,9 +9,4 @@ protected override ITestStoreFactory TestStoreFactory public override Task Can_use_shared_type_entity_type_in_query_filter_with_from_sql(bool async) => Task.CompletedTask; // https://github.com/dotnet/efcore/issues/25661 - - [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/30367")] - public override void Ad_hoc_query_for_shared_type_entity_type_works() - { - } } diff --git a/test/EFCore.PG.FunctionalTests/Query/SqlQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SqlQueryNpgsqlTest.cs index 20f43c4e8..d666a26f1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SqlQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SqlQueryNpgsqlTest.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using Xunit.Sdk; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; @@ -40,6 +41,12 @@ public override async Task SqlQueryRaw_queryable_simple_columns_out_of_order_and """); } + // The test attempts to project out a column with the wrong case; this works on other databases, and fails when EF tries to materialize. + // But in PG this fails at the database since PG is case-sensitive and the column does not exist. + public override Task SqlQueryRaw_queryable_simple_different_cased_columns_and_not_enough_columns_throws(bool async) + => Assert.ThrowsAsync( + () => base.SqlQueryRaw_queryable_simple_different_cased_columns_and_not_enough_columns_throws(async)); + public override async Task SqlQueryRaw_queryable_composed(bool async) { await base.SqlQueryRaw_queryable_composed(async); diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs index b0703bb02..a6510bd5e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs @@ -46,12 +46,12 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(GearsOfWarContext context) + protected override Task SeedAsync(GearsOfWarContext context) // GearsOfWarData contains DateTimeOffsets with various offsets, which we don't support. Change these to UTC. // Also chop sub-microsecond precision which PostgreSQL does not support. - => SeedForNpgsql(context); + => SeedForNpgsqlAsync(context); - public static void SeedForNpgsql(GearsOfWarContext context) + public static async Task SeedForNpgsqlAsync(GearsOfWarContext context) { var squads = GearsOfWarData.CreateSquads(); var missions = GearsOfWarData.CreateMissions(); @@ -85,10 +85,10 @@ public static void SeedForNpgsql(GearsOfWarContext context) context.LocustLeaders.AddRange(locustLeaders); context.Factions.AddRange(factions); context.LocustHighCommands.AddRange(locustHighCommands); - context.SaveChanges(); + await context.SaveChangesAsync(); GearsOfWarData.WireUp2(locustLeaders, factions); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs index 5f047dd73..5fae337e4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs @@ -48,7 +48,7 @@ await AssertQuery( @__end_1='1902-01-03T10:00:00.1234567+00:00' (DbType = DateTime) @__dates_2={ '1902-01-02T10:00:00.1234567+00:00' } (DbType = Object) -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE @__start_0 <= date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamptz AND m."Timeline" < @__end_1 AND m."Timeline" = ANY (@__dates_2) """); @@ -66,7 +66,7 @@ await AssertQuery( """ @__dateTimeOffset_Date_0='0002-03-01T00:00:00.0000000' -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamp >= @__dateTimeOffset_Date_0 """); @@ -82,7 +82,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01T00:00:00' """); diff --git a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs index a6dce923f..68ca376e4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs @@ -46,12 +46,12 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(GearsOfWarContext context) + protected override Task SeedAsync(GearsOfWarContext context) // GearsOfWarData contains DateTimeOffsets with various offsets, which we don't support. Change these to UTC. // Also chop sub-microsecond precision which PostgreSQL does not support. - => SeedForNpgsql(context); + => SeedForNpgsqlAsync(context); - public static void SeedForNpgsql(GearsOfWarContext context) + public static async Task SeedForNpgsqlAsync(GearsOfWarContext context) { var squads = GearsOfWarData.CreateSquads(); var missions = GearsOfWarData.CreateMissions(); @@ -85,10 +85,10 @@ public static void SeedForNpgsql(GearsOfWarContext context) context.LocustLeaders.AddRange(locustLeaders); context.Factions.AddRange(factions); context.LocustHighCommands.AddRange(locustHighCommands); - context.SaveChanges(); + await context.SaveChangesAsync(); GearsOfWarData.WireUp2(locustLeaders, factions); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs index 9247f53db..dafbe74e1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs @@ -52,7 +52,7 @@ await AssertQuery( @__end_1='1902-01-03T10:00:00.1234567+00:00' (DbType = DateTime) @__dates_2={ '1902-01-02T10:00:00.1234567+00:00' } (DbType = Object) -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE @__start_0 <= date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamptz AND m."Timeline" < @__end_1 AND m."Timeline" = ANY (@__dates_2) """); @@ -70,7 +70,7 @@ await AssertQuery( """ @__dateTimeOffset_Date_0='0002-03-01T00:00:00.0000000' -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC')::timestamp >= @__dateTimeOffset_Date_0 """); @@ -86,7 +86,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01T00:00:00' """); diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index 2feac4876..429c4cc9c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -829,10 +829,10 @@ public class TimestampQueryContext(DbContextOptions options) : PoolableDbContext { public DbSet Entities { get; set; } - public static void Seed(TimestampQueryContext context) + public static async Task SeedAsync (TimestampQueryContext context) { context.Entities.AddRange(TimestampData.CreateEntities()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -876,8 +876,8 @@ public TestSqlLoggerFactory TestSqlLoggerFactory private TimestampData _expectedData; - protected override void Seed(TimestampQueryContext context) - => TimestampQueryContext.Seed(context); + protected override Task SeedAsync(TimestampQueryContext context) + => TimestampQueryContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs index d42779741..c78819df2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs @@ -232,8 +232,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(TrigramsContext context) - => TrigramsContext.Seed(context); + protected override Task SeedAsync(TrigramsContext context) + => TrigramsContext.SeedAsync(context); } /// @@ -282,7 +282,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } - public static void Seed(TrigramsContext context) + public static async Task SeedAsync(TrigramsContext context) { for (var i = 1; i <= 9; i++) { @@ -291,7 +291,7 @@ public static void Seed(TrigramsContext context) new TrigramsTestEntity { Id = i, Text = text }); } - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs index 02ed4cdf2..549194272 100644 --- a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs +++ b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs @@ -671,11 +671,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .HasParameter("startDate").Metadata.TypeMapping = typeMappingSource.GetMapping("timestamp without time zone"); } - protected override void Seed(DbContext context) + protected override async Task SeedAsync(DbContext context) { - base.Seed(context); + await base.SeedAsync(context); - context.Database.ExecuteSqlRaw( + await context.Database.ExecuteSqlRawAsync( """ CREATE FUNCTION "CustomerOrderCount" ("customerId" INTEGER) RETURNS INTEGER AS $$ SELECT COUNT("Id")::INTEGER FROM "Orders" WHERE "CustomerId" = $1 $$ @@ -776,7 +776,7 @@ EXCEPTION WHEN OTHERS THEN $$ LANGUAGE PLPGSQL; """); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs b/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs index 9b4d29b8a..21a0e97fe 100644 --- a/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs +++ b/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs @@ -2,7 +2,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class SequenceEndToEndTest : IDisposable +public class SequenceEndToEndTest : IAsyncLifetime { [ConditionalFact] public void Can_use_sequence_end_to_end() @@ -389,8 +389,14 @@ private class Unicon public string Name { get; set; } } - protected NpgsqlTestStore TestStore { get; } = NpgsqlTestStore.CreateInitialized("SequenceEndToEndTest"); + protected NpgsqlTestStore TestStore { get; private set; } - public void Dispose() - => TestStore.Dispose(); + public async Task InitializeAsync() + => TestStore = await NpgsqlTestStore.CreateInitializedAsync("SequenceEndToEndTest"); + + public Task DisposeAsync() + { + TestStore.Dispose(); + return Task.CompletedTask; + } } diff --git a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs index 32e4e50e7..fa81d3a87 100644 --- a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs @@ -11,5 +11,6 @@ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransact // This test requires DbConnection to be used with the test store, but SpatialNpgsqlFixture must set useConnectionString to true // in order to properly set up the NetTopologySuite internally with the data source. public override void Mutation_of_tracked_values_does_not_mutate_values_in_store() - => Assert.Throws(() => base.Mutation_of_tracked_values_does_not_mutate_values_in_store()); + { + } } diff --git a/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs index 1133cb7f3..c290140b1 100644 --- a/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs @@ -6,9 +6,9 @@ public class StoreGeneratedFixupNpgsqlTest(StoreGeneratedFixupNpgsqlTest.StoreGe : StoreGeneratedFixupRelationalTestBase(fixture) { [Fact] - public void Temp_values_are_replaced_on_save() - => ExecuteWithStrategyInTransaction( - context => + public Task Temp_values_are_replaced_on_save() + => ExecuteWithStrategyInTransactionAsync( + async context => { var entry = context.Add(new TestTemp()); @@ -17,7 +17,7 @@ public void Temp_values_are_replaced_on_save() var tempValue = entry.Property(e => e.Id).CurrentValue; - context.SaveChanges(); + await context.SaveChangesAsync(); Assert.False(entry.Property(e => e.Id).IsTemporary); Assert.NotEqual(tempValue, entry.Property(e => e.Id).CurrentValue); diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs index 0c45ed887..389023fd6 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs @@ -54,14 +54,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) e.HasIndex(ae => ae.NonNullableText); }); - public static void Seed(ArrayQueryContext context) + public static async Task SeedAsync(ArrayQueryContext context) { var arrayEntities = ArrayQueryData.CreateArrayEntities(); context.SomeEntities.AddRange(arrayEntities); - context.SomeEntityContainers.Add( - new ArrayContainerEntity { Id = 1, ArrayEntities = arrayEntities.ToList() } - ); - context.SaveChanges(); + context.SomeEntityContainers.Add(new ArrayContainerEntity { Id = 1, ArrayEntities = arrayEntities.ToList() }); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs index 7ed735a7f..8b4ba0381 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs @@ -1,6 +1,5 @@ using System.Data; using System.Data.Common; -using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; @@ -20,13 +19,12 @@ public class NpgsqlTestStore : RelationalTestStore public static readonly string NorthwindConnectionString = CreateConnectionString(Northwind); - public static NpgsqlTestStore GetNorthwindStore() - => (NpgsqlTestStore)NpgsqlNorthwindTestStoreFactory.Instance - .GetOrCreate(NpgsqlNorthwindTestStoreFactory.Name).Initialize(null, (Func?)null); + public static async Task GetNorthwindStoreAsync() + => (NpgsqlTestStore)await NpgsqlNorthwindTestStoreFactory.Instance + .GetOrCreate(NpgsqlNorthwindTestStoreFactory.Name).InitializeAsync(null, (Func?)null); - // ReSharper disable once UnusedMember.Global - public static NpgsqlTestStore GetOrCreateInitialized(string name) - => new NpgsqlTestStore(name).InitializeNpgsql(null, (Func?)null, null); + public static Task GetOrCreateInitializedAsync(string name) + => new NpgsqlTestStore(name).InitializeNpgsqlAsync(null, (Func?)null, null); public static NpgsqlTestStore GetOrCreate( string name, @@ -39,9 +37,8 @@ public static NpgsqlTestStore GetOrCreate( public static NpgsqlTestStore Create(string name, string? connectionStringOptions = null) => new(name, connectionStringOptions: connectionStringOptions, shared: false); - public static NpgsqlTestStore CreateInitialized(string name) - => new NpgsqlTestStore(name, shared: false) - .InitializeNpgsql(null, (Func?)null, null); + public static Task CreateInitializedAsync(string name) + => new NpgsqlTestStore(name, shared: false).InitializeNpgsqlAsync(null, (Func?)null, null); public NpgsqlTestStore( string name, @@ -72,22 +69,22 @@ private static NpgsqlConnection CreateConnection(string name, string? connection => new(CreateConnectionString(name, connectionStringOptions)); // ReSharper disable once MemberCanBePrivate.Global - public NpgsqlTestStore InitializeNpgsql( + public async Task InitializeNpgsqlAsync( IServiceProvider? serviceProvider, Func? createContext, - Action? seed) - => (NpgsqlTestStore)Initialize(serviceProvider, createContext, seed); + Func? seed) + => (NpgsqlTestStore)await InitializeAsync(serviceProvider, createContext, seed); // ReSharper disable once UnusedMember.Global - public NpgsqlTestStore InitializeNpgsql( + public async Task InitializeNpgsqlAsync( IServiceProvider serviceProvider, Func createContext, - Action seed) - => InitializeNpgsql(serviceProvider, () => createContext(this), seed); + Func seed) + => await InitializeNpgsqlAsync(serviceProvider, () => createContext(this), seed); - protected override void Initialize(Func createContext, Action? seed, Action? clean) + protected override async Task InitializeAsync(Func createContext, Func? seed, Func? clean) { - if (CreateDatabase(clean)) + if (await CreateDatabaseAsync(clean)) { if (_scriptPath is not null) { @@ -100,15 +97,18 @@ protected override void Initialize(Func createContext, Action command.ExecuteNonQuery(), _additionalSql); } - seed?.Invoke(context); + if (seed is not null) + { + await seed(context); + } } } } @@ -129,40 +129,31 @@ public override DbContextOptionsBuilder AddProviderOptions(DbContextOptionsBuild : builder.UseNpgsql(_connectionString, npgsqlOptionsBuilder); } - private bool CreateDatabase(Action? clean) + private async Task CreateDatabaseAsync(Func? clean) { - using (var master = new NpgsqlConnection(CreateAdminConnectionString())) + await using var master = new NpgsqlConnection(CreateAdminConnectionString()); + + if (await DatabaseExistsAsync(Name)) { - if (DatabaseExists(Name)) + if (_scriptPath is not null) { - if (_scriptPath is not null) - { - return false; - } - - using (var context = new DbContext( - AddProviderOptions( - new DbContextOptionsBuilder() - .EnableServiceProviderCaching(false)) - .Options)) - { - clean?.Invoke(context); - Clean(context); - return true; - } + return false; } - ExecuteNonQuery(master, GetCreateDatabaseStatement(Name)); - WaitForExists((NpgsqlConnection)Connection); + await using var context = new DbContext( + AddProviderOptions(new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options); + clean?.Invoke(context); + await CleanAsync(context); + return true; } + await ExecuteNonQueryAsync(master, GetCreateDatabaseStatement(Name)); + await WaitForExistsAsync((NpgsqlConnection)Connection); + return true; } - private static void WaitForExists(NpgsqlConnection connection) - => WaitForExistsImplementation(connection); - - private static void WaitForExistsImplementation(NpgsqlConnection connection) + private static async Task WaitForExistsAsync(NpgsqlConnection connection) { var retryCount = 0; while (true) @@ -171,13 +162,13 @@ private static void WaitForExistsImplementation(NpgsqlConnection connection) { if (connection.State != ConnectionState.Closed) { - connection.Close(); + await connection.CloseAsync(); } NpgsqlConnection.ClearPool(connection); - connection.Open(); - connection.Close(); + await connection.OpenAsync(); + await connection.CloseAsync(); return; } catch (PostgresException e) @@ -188,7 +179,7 @@ private static void WaitForExistsImplementation(NpgsqlConnection connection) throw; } - Thread.Sleep(100); + await Task.Delay(100); } } } @@ -212,61 +203,46 @@ public void ExecuteScript(string scriptPath) }, ""); } - // ReSharper disable once UnusedMember.Local - private static void Clean(string name) - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(CreateConnectionString(name), b => b.ApplyConfiguration()) - .UseInternalServiceProvider( - new ServiceCollection() - .AddEntityFrameworkNpgsql() - .BuildServiceProvider()) - .Options; - - using (var context = new DbContext(options)) - { - context.Database.EnsureClean(); - } - } - private static string GetCreateDatabaseStatement(string name) - => $@"CREATE DATABASE ""{name}"""; + => $""" + CREATE DATABASE "{name}" + """; - private static bool DatabaseExists(string name) + private static async Task DatabaseExistsAsync(string name) { - using (var master = new NpgsqlConnection(CreateAdminConnectionString())) - { - return ExecuteScalar(master, $@"SELECT COUNT(*) FROM pg_database WHERE datname = '{name}'") > 0; - } + await using var master = new NpgsqlConnection(CreateAdminConnectionString()); + + return await ExecuteScalarAsync(master, $@"SELECT COUNT(*) FROM pg_database WHERE datname = '{name}'") > 0; } - public void DeleteDatabase() + public async Task DeleteDatabaseAsync() { - if (!DatabaseExists(Name)) + if (!await DatabaseExistsAsync(Name)) { return; } - using (var master = new NpgsqlConnection(CreateAdminConnectionString())) - { - ExecuteNonQuery(master, GetDisconnectDatabaseSql(Name)); - ExecuteNonQuery(master, GetDropDatabaseSql(Name)); + await using var master = new NpgsqlConnection(CreateAdminConnectionString()); - NpgsqlConnection.ClearAllPools(); - } + await ExecuteNonQueryAsync(master, GetDisconnectDatabaseSql(Name)); + await ExecuteNonQueryAsync(master, GetDropDatabaseSql(Name)); + + NpgsqlConnection.ClearAllPools(); } // Kill all connection to the database - // TODO: Pre-9.2 PG has column name procid instead of pid private static string GetDisconnectDatabaseSql(string name) - => $@" -REVOKE CONNECT ON DATABASE ""{name}"" FROM PUBLIC; + => $""" +REVOKE CONNECT ON DATABASE "{name}" FROM PUBLIC; SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity - WHERE datname = '{name}'"; + WHERE datname = '{name}' +"""; private static string GetDropDatabaseSql(string name) - => $@"DROP DATABASE ""{name}"""; + => $""" + DROP DATABASE "{name}" + """; public override void OpenConnection() => Connection.Open(); @@ -310,16 +286,15 @@ private static IEnumerable Query(DbConnection connection, string sql, obje => Execute( connection, command => { - using (var dataReader = command.ExecuteReader()) - { - var results = Enumerable.Empty(); - while (dataReader.Read()) - { - results = results.Concat(new[] { dataReader.GetFieldValue(0) }); - } + using var dataReader = command.ExecuteReader(); - return results; + var results = Enumerable.Empty(); + while (dataReader.Read()) + { + results = results.Concat([dataReader.GetFieldValue(0)]); } + + return results; }, sql, false, parameters); // ReSharper disable once UnusedMember.Global @@ -330,16 +305,15 @@ private static Task> QueryAsync(DbConnection connection, strin => ExecuteAsync( connection, async command => { - await using (var dataReader = await command.ExecuteReaderAsync()) - { - var results = Enumerable.Empty(); - while (await dataReader.ReadAsync()) - { - results = results.Concat(new[] { await dataReader.GetFieldValueAsync(0) }); - } + await using var dataReader = await command.ExecuteReaderAsync(); - return results; + var results = Enumerable.Empty(); + while (await dataReader.ReadAsync()) + { + results = results.Concat([await dataReader.GetFieldValueAsync(0)]); } + + return results; }, sql, false, parameters); private static T Execute( @@ -365,19 +339,18 @@ private static T ExecuteCommand( connection.Open(); try { - using (var transaction = useTransaction ? connection.BeginTransaction() : null) + using var transaction = useTransaction ? connection.BeginTransaction() : null; + + T result; + using (var command = CreateCommand(connection, sql, parameters)) { - T result; - using (var command = CreateCommand(connection, sql, parameters)) - { - command.Transaction = transaction; - result = execute(command); - } + command.Transaction = transaction; + result = execute(command); + } - transaction?.Commit(); + transaction?.Commit(); - return result; - } + return result; } finally { @@ -406,31 +379,33 @@ private static async Task ExecuteCommandAsync( { if (connection.State != ConnectionState.Closed) { - connection.Close(); + await connection.CloseAsync(); } await connection.OpenAsync(); try { - await using (var transaction = useTransaction ? connection.BeginTransaction() : null) - { - T result; - await using (var command = CreateCommand(connection, sql, parameters)) - { - result = await executeAsync(command); - } + await using var transaction = useTransaction ? await connection.BeginTransactionAsync() : null; - transaction?.Commit(); + T result; + await using (var command = CreateCommand(connection, sql, parameters)) + { + result = await executeAsync(command); + } - return result; + if (transaction is not null) + { + await transaction.CommitAsync(); } + + return result; } finally { if (connection.State == ConnectionState.Closed && connection.State != ConnectionState.Closed) { - connection.Close(); + await connection.CloseAsync(); } } } @@ -471,6 +446,9 @@ public static string CreateConnectionString(string name, string? options = null) private static string CreateAdminConnectionString() => CreateConnectionString("postgres"); - public override void Clean(DbContext context) - => context.Database.EnsureClean(); + public override Task CleanAsync(DbContext context) + { + context.Database.EnsureClean(); + return Task.CompletedTask; + } } diff --git a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs index 8b64a5cb1..20dc78487 100644 --- a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs @@ -512,7 +512,7 @@ public override async Task Edit_single_property_bool() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -534,7 +534,7 @@ public override async Task Edit_single_property_byte() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -555,7 +555,7 @@ public override async Task Edit_single_property_char() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -577,7 +577,7 @@ public override async Task Edit_single_property_datetime() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -599,7 +599,7 @@ public override async Task Edit_single_property_datetimeoffset() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -621,7 +621,7 @@ public override async Task Edit_single_property_decimal() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -643,7 +643,7 @@ public override async Task Edit_single_property_double() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -665,7 +665,7 @@ public override async Task Edit_single_property_guid() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -687,7 +687,7 @@ public override async Task Edit_single_property_int16() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -709,7 +709,7 @@ public override async Task Edit_single_property_int32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -731,7 +731,7 @@ public override async Task Edit_single_property_int64() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -753,7 +753,7 @@ public override async Task Edit_single_property_signed_byte() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -775,7 +775,7 @@ public override async Task Edit_single_property_single() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -797,7 +797,7 @@ public override async Task Edit_single_property_timespan() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -819,7 +819,7 @@ public override async Task Edit_single_property_uint16() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -841,7 +841,7 @@ public override async Task Edit_single_property_uint32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -863,7 +863,7 @@ public override async Task Edit_single_property_uint64() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -885,7 +885,7 @@ public override async Task Edit_single_property_nullable_int32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -907,7 +907,7 @@ public override async Task Edit_single_property_nullable_int32_set_to_null() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -929,7 +929,7 @@ public override async Task Edit_single_property_enum() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -951,7 +951,7 @@ public override async Task Edit_single_property_enum_with_int_converter() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -973,7 +973,7 @@ public override async Task Edit_single_property_nullable_enum() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -995,7 +995,7 @@ public override async Task Edit_single_property_nullable_enum_set_to_null() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1017,7 +1017,7 @@ public override async Task Edit_single_property_nullable_enum_with_int_converter """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1039,7 +1039,7 @@ public override async Task Edit_single_property_nullable_enum_with_int_converter """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1061,7 +1061,7 @@ public override async Task Edit_single_property_nullable_enum_with_converter_tha """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1083,7 +1083,7 @@ public override async Task Edit_single_property_nullable_enum_with_converter_tha """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1096,8 +1096,8 @@ public override async Task Edit_two_properties_on_same_entity_updates_the_entire AssertSql( """ -@p0='{"TestBoolean":false,"TestBooleanCollection":[true,false],"TestByte":25,"TestByteCollection":null,"TestCharacter":"h","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2323-04-03","TestDateOnlyCollection":["3234-01-23","4331-01-21"],"TestDateTime":"2100-11-11T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2200-11-11T12:34:56-05:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-123450.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInCollection1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.2345,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"00000000-0000-0000-0000-000000000000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-12,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Baz","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Two","TestNullableEnumWithConverterThatHandlesNullsCollection":[-1,null,-7],"TestNullableEnumWithIntConverter":-3,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":90,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-18,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.4,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"05:07:08.0000000","TestTimeOnlyCollection":["13:42:23.0000000","07:17:25.0000000"],"TestTimeSpan":"06:05:04.003","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":12,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":12345,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567867,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) -@p1='{"TestBoolean":true,"TestBooleanCollection":[true,false],"TestByte":255,"TestByteCollection":null,"TestCharacter":"a","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2023-10-10","TestDateOnlyCollection":["1234-01-23","4321-01-21"],"TestDateTime":"2000-01-01T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2000-01-01T12:34:56-08:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-1234567890.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInReference1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.23456789,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"12345678-1234-4321-7777-987654321000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-1234,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Foo","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Three","TestNullableEnumWithConverterThatHandlesNullsCollection":[-1,null,-7],"TestNullableEnumWithIntConverter":2,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":78,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-128,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.234,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"11:12:13.0000000","TestTimeOnlyCollection":["11:42:23.0000000","07:17:27.0000000"],"TestTimeSpan":"10:09:08.007","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":1234,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":1234565789,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567890123456789,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) +@p0='{"TestBoolean":false,"TestBooleanCollection":[true,false],"TestByte":25,"TestByteArray":"","TestByteCollection":null,"TestCharacter":"h","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2323-04-03","TestDateOnlyCollection":["3234-01-23","4331-01-21"],"TestDateTime":"2100-11-11T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2200-11-11T12:34:56-05:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-123450.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInCollection1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.2345,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"00000000-0000-0000-0000-000000000000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-12,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Baz","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Two","TestNullableEnumWithIntConverter":-3,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":90,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-18,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.4,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"05:07:08.0000000","TestTimeOnlyCollection":["13:42:23.0000000","07:17:25.0000000"],"TestTimeSpan":"06:05:04.003","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":12,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":12345,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567867,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) +@p1='{"TestBoolean":true,"TestBooleanCollection":[true,false],"TestByte":255,"TestByteArray":"AQID","TestByteCollection":null,"TestCharacter":"a","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2023-10-10","TestDateOnlyCollection":["1234-01-23","4321-01-21"],"TestDateTime":"2000-01-01T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2000-01-01T12:34:56-08:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-1234567890.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInReference1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.23456789,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"12345678-1234-4321-7777-987654321000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-1234,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Foo","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Three","TestNullableEnumWithIntConverter":2,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":78,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-128,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.234,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"11:12:13.0000000","TestTimeOnlyCollection":["11:42:23.0000000","07:17:27.0000000"],"TestTimeSpan":"10:09:08.007","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":1234,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":1234565789,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567890123456789,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0}', @p0), "Reference" = @p1 @@ -1105,7 +1105,7 @@ public override async Task Edit_two_properties_on_same_entity_updates_the_entire """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1336,7 +1336,7 @@ public override async Task Edit_single_property_collection_of_bool() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1358,7 +1358,7 @@ public override async Task Edit_single_property_collection_of_byte() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1388,7 +1388,7 @@ public override async Task Edit_single_property_collection_of_datetime() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1410,7 +1410,7 @@ public override async Task Edit_single_property_collection_of_datetimeoffset() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1432,7 +1432,7 @@ public override async Task Edit_single_property_collection_of_decimal() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1454,7 +1454,7 @@ public override async Task Edit_single_property_collection_of_double() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1476,7 +1476,7 @@ public override async Task Edit_single_property_collection_of_guid() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1498,7 +1498,7 @@ public override async Task Edit_single_property_collection_of_int16() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1520,7 +1520,7 @@ public override async Task Edit_single_property_collection_of_int32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1542,7 +1542,7 @@ public override async Task Edit_single_property_collection_of_int64() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1564,7 +1564,7 @@ public override async Task Edit_single_property_collection_of_signed_byte() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1586,7 +1586,7 @@ public override async Task Edit_single_property_collection_of_single() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1608,7 +1608,7 @@ public override async Task Edit_single_property_collection_of_timespan() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1630,7 +1630,7 @@ public override async Task Edit_single_property_collection_of_dateonly() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1652,7 +1652,7 @@ public override async Task Edit_single_property_collection_of_timeonly() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1674,7 +1674,7 @@ public override async Task Edit_single_property_collection_of_uint16() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1696,7 +1696,7 @@ public override async Task Edit_single_property_collection_of_uint32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1718,7 +1718,7 @@ public override async Task Edit_single_property_collection_of_uint64() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1740,7 +1740,7 @@ public override async Task Edit_single_property_collection_of_nullable_int32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1762,7 +1762,7 @@ public override async Task Edit_single_property_collection_of_nullable_int32_set """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1784,7 +1784,7 @@ public override async Task Edit_single_property_collection_of_enum() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1806,7 +1806,7 @@ public override async Task Edit_single_property_collection_of_enum_with_int_conv """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1828,7 +1828,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1850,7 +1850,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum_set_ """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1872,7 +1872,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum_with """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1894,29 +1894,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum_with """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" -FROM "JsonEntitiesAllTypes" AS j -WHERE j."Id" = 1 -LIMIT 2 -"""); - } - - public override async Task Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls() - { - await base.Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls(); - - AssertSql( - """ -@p0='[-3]' (Nullable = false) (DbType = Object) -@p1='[-1]' (Nullable = false) (DbType = Object) -@p2='1' - -UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestNullableEnumWithConverterThatHandlesNullsCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestNullableEnumWithConverterThatHandlesNullsCollection}', @p1) -WHERE "Id" = @p2; -""", - // - """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1929,16 +1907,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum_with AssertSql( """ -@p0='null' (Nullable = false) (DbType = Object) -@p1='null' (Nullable = false) (DbType = Object) -@p2='1' - -UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestNullableEnumWithConverterThatHandlesNullsCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestNullableEnumWithConverterThatHandlesNullsCollection}', @p1) -WHERE "Id" = @p2; -""", - // - """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1982,6 +1951,9 @@ public override Task Edit_single_property_relational_collection_of_enum() public override Task Edit_single_property_relational_collection_of_int16() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_int16()); + public override Task Edit_single_property_relational_collection_of_guid() + => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_int16()); + public override Task Edit_single_property_relational_collection_of_nullable_enum() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum()); @@ -2007,8 +1979,53 @@ public override Task Edit_single_property_relational_collection_of_uint16() public override Task Edit_single_property_relational_collection_of_uint64() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_uint64()); + public override Task Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls() + => Assert.ThrowsAsync( + () => base.Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls()); + + public override Task Edit_single_property_relational_collection_of_nullable_enum_with_converter_that_handles_nulls() + => Assert.ThrowsAsync( + () => base.Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls()); + #endregion + #region Skipped tests because of nested collections outside of JSON (nested arrays not supported in PG) + + public override Task Edit_single_property_collection_of_collection_of_bool() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_char() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_double() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_int16() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_int32() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_int64() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_single() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_nullable_int32() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_nullable_int32_set_to_null() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_nullable_enum_set_to_null() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_nullable_enum_with_int_converter() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + #endregion Skipped tests because of nested collections outside of JSON (nested arrays not supported in PG) + protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); @@ -2054,6 +2071,77 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Ignore(j => j.TestDoubleCollection); b.Ignore(j => j.TestInt16Collection); }); + + // These use collection types which are unsupported for arrays at the Npgsql level - we currently only support List/array. + modelBuilder.Entity( + b => + { + b.Ignore(j => j.TestInt64Collection); + b.Ignore(j => j.TestGuidCollection); + }); + + // Ignore nested collections - these aren't supported on PostgreSQL (no arrays of arrays). + // TODO: Remove these after syncing to 9.0.0-rc.1, and extending from the relational test base and fixture + modelBuilder.Entity( + b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); + + modelBuilder.Entity().OwnsOne( + x => x.Reference, b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); + + modelBuilder.Entity().OwnsMany( + x => x.Collection, b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); } } } diff --git a/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs index 6b7a7869f..56ea743cf 100644 --- a/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs @@ -6,7 +6,7 @@ public class ValueConvertersEndToEndNpgsqlTest(ValueConvertersEndToEndNpgsqlTest : ValueConvertersEndToEndTestBase(fixture) { [ConditionalTheory(Skip = "DateTime and DateTimeOffset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_with_conversions(int[] valueOrder) + public override Task Can_insert_and_read_back_with_conversions(int[] valueOrder) => base.Can_insert_and_read_back_with_conversions(valueOrder); [ConditionalTheory] diff --git a/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj b/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj index db1b1e6d9..7f23750d4 100644 --- a/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj +++ b/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj @@ -3,6 +3,10 @@ Npgsql.EntityFrameworkCore.PostgreSQL.Tests Npgsql.EntityFrameworkCore.PostgreSQL + + $(NoWarn);NU1903 + false + critical diff --git a/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs b/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs index f7a4c5571..046271648 100644 --- a/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs +++ b/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs @@ -128,7 +128,6 @@ public void GetBeginIfExistsScript_works() Assert.Equal( """ - DO $EF$ BEGIN IF EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = 'Migration1') THEN