From e5adba7c7343bbe377793b20f544565f71490461 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Tue, 14 Feb 2023 16:51:32 -0800 Subject: [PATCH] Fix to #28816 - Json: add support for Sqlite provider Adding support for Sqlite. Also adding some more query and update tests for properties with value converters. Limitation: When accessing element of a JSON array we can only use constant values. Unlike Sql Server which supports parameters, columns or even arbitrary expressions. Fixes #28816 --- .../Update/ModificationCommand.cs | 11 +- .../SqliteServiceCollectionExtensions.cs | 1 + .../Internal/SqliteAnnotationProvider.cs | 37 +- .../Properties/SqliteStrings.Designer.cs | 8 + .../Properties/SqliteStrings.resx | 3 + .../Query/Internal/SqliteQuerySqlGenerator.cs | 52 + .../SqliteQueryTranslationPostprocessor.cs | 14 +- .../Storage/Internal/SqliteJsonTypeMapping.cs | 95 ++ .../Internal/SqliteTypeMappingSource.cs | 5 +- .../Internal/SqliteModificationCommand.cs | 77 + .../SqliteModificationCommandFactory.cs | 33 + .../Internal/SqliteUpdateSqlGenerator.cs | 60 + .../Query/JsonQueryAdHocTestBase.cs | 48 +- .../Query/JsonQueryFixtureBase.cs | 49 + .../Query/JsonQueryTestBase.cs | 72 + .../JsonQuery/JsonEntityConverters.cs | 10 + .../JsonQuery/JsonOwnedConverters.cs | 14 + .../TestModels/JsonQuery/JsonQueryContext.cs | 3 + .../TestModels/JsonQuery/JsonQueryData.cs | 44 + .../Update/JsonUpdateFixtureBase.cs | 26 + .../Update/JsonUpdateTestBase.cs | 120 ++ .../Query/JsonQuerySqlServerTest.cs | 72 + .../Update/JsonUpdateSqlServerTest.cs | 108 ++ .../Query/JsonQueryAdHocSqliteTest.cs | 106 ++ .../Query/JsonQuerySqliteFixture.cs | 15 + .../Query/JsonQuerySqliteTest.cs | 180 +++ .../SqliteComplianceTest.cs | 3 - .../Update/JsonUpdateSqliteFixture.cs | 14 + .../Update/JsonUpdateSqliteTest.cs | 1321 +++++++++++++++++ 29 files changed, 2569 insertions(+), 32 deletions(-) create mode 100644 src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs create mode 100644 src/EFCore.Sqlite.Core/Update/Internal/SqliteModificationCommand.cs create mode 100644 src/EFCore.Sqlite.Core/Update/Internal/SqliteModificationCommandFactory.cs create mode 100644 test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityConverters.cs create mode 100644 test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedConverters.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/Query/JsonQueryAdHocSqliteTest.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/Query/JsonQuerySqliteFixture.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/Query/JsonQuerySqliteTest.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/Update/JsonUpdateSqliteFixture.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/Update/JsonUpdateSqliteTest.cs diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 5fd9ecccdd9..db336273df7 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -330,7 +330,7 @@ private List GenerateColumnModifications() if (updateInfo.Property != null) { - json = new JsonArray(JsonValue.Create(updateInfo.PropertyValue)); + json = GenerateJsonForSinglePropertyUpdate(updateInfo.Property, updateInfo.PropertyValue); jsonPathString = jsonPathString + "." + updateInfo.Property.GetJsonPropertyName(); } else @@ -699,6 +699,15 @@ static JsonPartialUpdateInfo FindCommonJsonPartialUpdateInfo( } } + /// + /// Generates representing the value to use for update in case a single property is being updated. + /// + /// Property to be updated. + /// Value object that the property will be updated to. + /// representing the value that the property will be updated to. + protected virtual JsonNode? GenerateJsonForSinglePropertyUpdate(IProperty property, object? propertyValue) + => new JsonArray(JsonValue.Create(propertyValue)); + private JsonNode? CreateJson(object? navigationValue, IUpdateEntry parentEntry, IEntityType entityType, int? ordinal, bool isCollection) { if (navigationValue == null) diff --git a/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs index ac63f282567..ed862f528a2 100644 --- a/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs +++ b/src/EFCore.Sqlite.Core/Extensions/SqliteServiceCollectionExtensions.cs @@ -100,6 +100,7 @@ public static IServiceCollection AddEntityFrameworkSqlite(this IServiceCollectio .TryAdd() .TryAdd() .TryAdd() + .TryAdd() .TryAdd(p => p.GetRequiredService()) .TryAdd() .TryAdd() diff --git a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs index bd4b72dd544..faf81efc55b 100644 --- a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs +++ b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; namespace Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal; @@ -47,24 +48,28 @@ public override IEnumerable For(IRelationalModel model, bool design /// public override IEnumerable For(IColumn column, bool designTime) { - // Model validation ensures that these facets are the same on all mapped properties - var property = column.PropertyMappings.First().Property; - // Only return auto increment for integer single column primary key - var primaryKey = property.DeclaringEntityType.FindPrimaryKey(); - if (primaryKey != null - && primaryKey.Properties.Count == 1 - && primaryKey.Properties[0] == property - && property.ValueGenerated == ValueGenerated.OnAdd - && property.ClrType.UnwrapNullableType().IsInteger() - && !HasConverter(property)) + // JSON columns have no property mappings so all annotations that rely on property mappings should be skipped for them + if (column is not JsonColumn) { - yield return new Annotation(SqliteAnnotationNames.Autoincrement, true); - } + // Model validation ensures that these facets are the same on all mapped properties + var property = column.PropertyMappings.First().Property; + // Only return auto increment for integer single column primary key + var primaryKey = property.DeclaringEntityType.FindPrimaryKey(); + if (primaryKey != null + && primaryKey.Properties.Count == 1 + && primaryKey.Properties[0] == property + && property.ValueGenerated == ValueGenerated.OnAdd + && property.ClrType.UnwrapNullableType().IsInteger() + && !HasConverter(property)) + { + yield return new Annotation(SqliteAnnotationNames.Autoincrement, true); + } - var srid = property.GetSrid(); - if (srid != null) - { - yield return new Annotation(SqliteAnnotationNames.Srid, srid); + var srid = property.GetSrid(); + if (srid != null) + { + yield return new Annotation(SqliteAnnotationNames.Srid, srid); + } } } diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs index 347b66fae15..d9c67ab2fc1 100644 --- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs +++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs @@ -73,6 +73,14 @@ public static string InvalidMigrationOperation(object? operation) public static string MigrationScriptGenerationNotSupported => GetString("MigrationScriptGenerationNotSupported"); + /// + /// JSON array element can only be accessed using constant value for the array index. Non-constant value is being used in PATH for JSON column '{jsonColumnName}'. + /// + public static string NonConstantJsonArrayIndexNotSupported(object? jsonColumnName) + => string.Format( + GetString("NonConstantJsonArrayIndexNotSupported", nameof(jsonColumnName)), + jsonColumnName); + /// /// SQLite does not support expressions of type '{type}' in ORDER BY clauses. Convert the values to a supported type, or use LINQ to Objects to order the results on the client side. /// diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx index 681c42f2ce3..9c8edc26314 100644 --- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx +++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx @@ -198,6 +198,9 @@ Generating idempotent scripts for migrations is not currently supported for SQLite. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + + JSON array element can only be accessed using constant value for the array index. Non-constant value is being used in PATH for JSON column '{jsonColumnName}'. + SQLite does not support expressions of type '{type}' in ORDER BY clauses. Convert the values to a supported type, or use LINQ to Objects to order the results on the client side. diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs index 7316f6fb164..b5cd52e1f8d 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQuerySqlGenerator.cs @@ -117,4 +117,56 @@ private Expression VisitRegexp(RegexpExpression regexpExpression) return regexpExpression; } + + /// + /// 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. + /// + protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression) + { + if (jsonScalarExpression.Path.Count == 1 + && jsonScalarExpression.Path[0].ToString() == "$") + { + Visit(jsonScalarExpression.JsonColumn); + + return jsonScalarExpression; + } + + Sql.Append("json_extract("); + + Visit(jsonScalarExpression.JsonColumn); + + Sql.Append(",'"); + foreach (var pathSegment in jsonScalarExpression.Path) + { + if (pathSegment.PropertyName != null) + { + Sql.Append((pathSegment.PropertyName == "$" ? "" : ".") + pathSegment.PropertyName); + } + + if (pathSegment.ArrayIndex != null) + { + Sql.Append("["); + + if (pathSegment.ArrayIndex is SqlConstantExpression) + { + Visit(pathSegment.ArrayIndex); + } + else + { + Sql.Append("' + "); + Visit(pathSegment.ArrayIndex); + Sql.Append(" + '"); + } + + Sql.Append("]"); + } + } + + Sql.Append("')"); + + return jsonScalarExpression; + } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryTranslationPostprocessor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryTranslationPostprocessor.cs index 74095a2beb9..6bb43cf0482 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryTranslationPostprocessor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryTranslationPostprocessor.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; /// public class SqliteQueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor { - private readonly ApplyValidatingVisitor _applyValidator = new(); + private readonly ValidatingVisitor _validator = new(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -39,12 +39,12 @@ public SqliteQueryTranslationPostprocessor( public override Expression Process(Expression query) { var result = base.Process(query); - _applyValidator.Visit(result); + _validator.Visit(result); return result; } - private sealed class ApplyValidatingVisitor : ExpressionVisitor + private sealed class ValidatingVisitor : ExpressionVisitor { protected override Expression VisitExtension(Expression extensionExpression) { @@ -62,6 +62,14 @@ protected override Expression VisitExtension(Expression extensionExpression) throw new InvalidOperationException(SqliteStrings.ApplyNotSupported); } + if (extensionExpression is JsonScalarExpression jsonScalarExpression + && jsonScalarExpression.Path.Any(x => x.ArrayIndex is not null and not SqlConstantExpression)) + { + throw new InvalidOperationException( + SqliteStrings.NonConstantJsonArrayIndexNotSupported( + jsonScalarExpression.JsonColumn.Name)); + } + return base.VisitExtension(extensionExpression); } } diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs new file mode 100644 index 00000000000..30e9a6027cf --- /dev/null +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; +using System.Text.Json; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; + +/// +/// 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 class SqliteJsonTypeMapping : JsonTypeMapping +{ + private static readonly MethodInfo _getStringMethod + = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetString), new[] { typeof(int) })!; + + private static readonly MethodInfo _jsonDocumentParseMethod + = typeof(JsonDocument).GetRuntimeMethod(nameof(JsonDocument.Parse), new[] { typeof(string), typeof(JsonDocumentOptions) })!; + + private static readonly MemberInfo _jsonDocumentRootElementMember + = typeof(JsonDocument).GetRuntimeProperty(nameof(JsonDocument.RootElement))!; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the database type. + public SqliteJsonTypeMapping(string storeType) + : base(storeType, typeof(JsonElement), System.Data.DbType.String) + { + } + + /// + /// 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. + /// + protected SqliteJsonTypeMapping(RelationalTypeMappingParameters parameters) + : base(parameters) + { + } + + /// + /// 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 MethodInfo GetDataReaderMethod() + => _getStringMethod; + + /// + /// 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 Expression CustomizeDataReaderExpression(Expression expression) + => Expression.MakeMemberAccess( + Expression.Call( + _jsonDocumentParseMethod, + expression, + Expression.Default(typeof(JsonDocumentOptions))), + _jsonDocumentRootElementMember); + + /// + /// 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. + /// + protected override string GenerateNonNullSqlLiteral(object value) + => $"'{EscapeSqlLiteral(JsonSerializer.Serialize(value))}'"; + + /// + /// 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. + /// + protected virtual string EscapeSqlLiteral(string literal) + => literal.Replace("'", "''"); + + /// + /// 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. + /// + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new SqliteJsonTypeMapping(parameters); +} diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs index 2dc07d88c8c..08ec4f9e887 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json; + namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; /// @@ -80,7 +82,8 @@ private static readonly HashSet SpatialiteTypes { typeof(decimal), new SqliteDecimalTypeMapping(TextTypeName) }, { typeof(double), Real }, { typeof(float), new FloatTypeMapping(RealTypeName) }, - { typeof(Guid), new SqliteGuidTypeMapping(TextTypeName) } + { typeof(Guid), new SqliteGuidTypeMapping(TextTypeName) }, + { typeof(JsonElement), new SqliteJsonTypeMapping(TextTypeName) } }; private readonly Dictionary _storeTypeMappings = new(StringComparer.OrdinalIgnoreCase) diff --git a/src/EFCore.Sqlite.Core/Update/Internal/SqliteModificationCommand.cs b/src/EFCore.Sqlite.Core/Update/Internal/SqliteModificationCommand.cs new file mode 100644 index 00000000000..bc1034beb0f --- /dev/null +++ b/src/EFCore.Sqlite.Core/Update/Internal/SqliteModificationCommand.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Nodes; + +namespace Microsoft.EntityFrameworkCore.Sqlite.Update.Internal; + +/// +/// 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 class SqliteModificationCommand : ModificationCommand +{ + /// + /// 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 SqliteModificationCommand(in ModificationCommandParameters modificationCommandParameters) + : base(modificationCommandParameters) + { + } + + /// + /// 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 SqliteModificationCommand(in NonTrackedModificationCommandParameters modificationCommandParameters) + : base(modificationCommandParameters) + { + } + + /// + /// 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. + /// + protected override JsonNode? GenerateJsonForSinglePropertyUpdate(IProperty property, object? propertyValue) + { + if (propertyValue is bool boolPropertyValue) + { + var valueConverter = property.GetValueConverter(); + if ((valueConverter is null && property.ClrType == typeof(bool)) + || valueConverter is not null && valueConverter.ProviderClrType == typeof(bool)) + { + // Sqlite converts true/false into native 0/1 when using json_extract + // so we convert those values to strings so that they stay as true/false + // which is what we want to store in json object in the end + var modifiedPropertyValue = boolPropertyValue + ? "true" + : "false"; + + return base.GenerateJsonForSinglePropertyUpdate(property, modifiedPropertyValue); + } + } + //if (valueConverter == null && property.ClrType == typeof(bool)) + //if (property.) + + //if (property.GetProviderClrType() == typeof(bool) + // && propertyValue is bool boolPropertyValue) + ////if (property.ClrType == typeof(bool) + //// && property.GetValueConverter() == null + //// && propertyValue is bool boolPropertyValue) + //{ + + // return base.GenerateJsonForSinglePropertyUpdate(property, modifiedPropertyValue); + //} + + return base.GenerateJsonForSinglePropertyUpdate(property, propertyValue); + } +} diff --git a/src/EFCore.Sqlite.Core/Update/Internal/SqliteModificationCommandFactory.cs b/src/EFCore.Sqlite.Core/Update/Internal/SqliteModificationCommandFactory.cs new file mode 100644 index 00000000000..d313e039b5e --- /dev/null +++ b/src/EFCore.Sqlite.Core/Update/Internal/SqliteModificationCommandFactory.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Sqlite.Update.Internal; + +/// +/// 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 class SqliteModificationCommandFactory : IModificationCommandFactory +{ + /// + /// 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 virtual IModificationCommand CreateModificationCommand( + in ModificationCommandParameters modificationCommandParameters) + => new SqliteModificationCommand(modificationCommandParameters); + + /// + /// 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 virtual INonTrackedModificationCommand CreateNonTrackedModificationCommand( + in NonTrackedModificationCommandParameters modificationCommandParameters) + => new SqliteModificationCommand(modificationCommandParameters); +} diff --git a/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs b/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs index fa7a13740d8..efcb7a3f123 100644 --- a/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs +++ b/src/EFCore.Sqlite.Core/Update/Internal/SqliteUpdateSqlGenerator.cs @@ -3,7 +3,9 @@ using System.Text; using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Internal; +using Microsoft.EntityFrameworkCore.Update; namespace Microsoft.EntityFrameworkCore.Sqlite.Update.Internal; @@ -143,6 +145,64 @@ protected override void AppendRowsAffectedWhereCondition(StringBuilder commandSt public override string GenerateNextSequenceValueOperation(string name, string? schema) => throw new NotSupportedException(SqliteStrings.SequencesNotSupported); + + /// + protected override void AppendUpdateColumnValue( + ISqlGenerationHelper updateSqlGeneratorHelper, + IColumnModification columnModification, + StringBuilder stringBuilder, + string name, + string? schema) + { + if (columnModification.JsonPath != null + && columnModification.JsonPath != "$") + { + stringBuilder.Append("json_set("); + updateSqlGeneratorHelper.DelimitIdentifier(stringBuilder, columnModification.ColumnName); + stringBuilder.Append(", '"); + stringBuilder.Append(columnModification.JsonPath); + stringBuilder.Append("', "); + + if (columnModification.Property != null) + { + // special handling for bool + // json_extract converts true/false into native 0/1 values, + // but we want to store the values as true/false in JSON + // in order to do that we modify the parameter value to "true"/"false" + // and wrap json() function around it to avoid conversion to 0/1 + var valueConverter = columnModification.Property.GetValueConverter(); + var boolProviderType = (valueConverter is null && columnModification.Property.ClrType == typeof(bool)) + || valueConverter is not null && valueConverter.ProviderClrType == typeof(bool); + + if (boolProviderType) + { + stringBuilder.Append("json("); + } + + stringBuilder.Append("json_extract("); + base.AppendUpdateColumnValue(updateSqlGeneratorHelper, columnModification, stringBuilder, name, schema); + stringBuilder.Append(", '$[0]')"); + + if (boolProviderType) + { + stringBuilder.Append(")"); + } + } + else + { + stringBuilder.Append("json("); + base.AppendUpdateColumnValue(updateSqlGeneratorHelper, columnModification, stringBuilder, name, schema); + stringBuilder.Append(")"); + } + + stringBuilder.Append(")"); + } + else + { + base.AppendUpdateColumnValue(updateSqlGeneratorHelper, columnModification, stringBuilder, name, schema); + } + } + private bool CanUseReturningClause(IReadOnlyModificationCommand command) => _isReturningClauseSupported && command.Table?.IsSqlReturningClauseUsed() == true; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs index ce63c8289e2..7318c7d3408 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs @@ -19,7 +19,10 @@ protected override string StoreName [MemberData(nameof(IsAsyncData))] public virtual async Task Optional_json_properties_materialized_as_null_when_the_element_in_json_is_not_present(bool async) { - var contextFactory = await InitializeAsync(seed: Seed29219); + var contextFactory = await InitializeAsync( + onConfiguring: OnConfiguring29219, + seed: Seed29219); + using (var context = contextFactory.CreateContext()) { var query = context.Entities.Where(x => x.Id == 3); @@ -38,7 +41,10 @@ public virtual async Task Optional_json_properties_materialized_as_null_when_the [MemberData(nameof(IsAsyncData))] public virtual async Task Can_project_nullable_json_property_when_the_element_in_json_is_not_present(bool async) { - var contextFactory = await InitializeAsync(seed: Seed29219); + var contextFactory = await InitializeAsync( + onConfiguring: OnConfiguring29219, + seed: Seed29219); + using (var context = contextFactory.CreateContext()) { var query = context.Entities.OrderBy(x => x.Id).Select(x => x.Reference.NullableScalar); @@ -54,6 +60,10 @@ public virtual async Task Can_project_nullable_json_property_when_the_element_in } } + protected virtual void OnConfiguring29219(DbContextOptionsBuilder builder) + { + } + protected abstract void Seed29219(MyContext29219 ctx); protected class MyContext29219 : DbContext @@ -94,7 +104,10 @@ public class MyJsonEntity29219 [MemberData(nameof(IsAsyncData))] public virtual async Task Project_json_array_of_primitives_on_reference(bool async) { - var contextFactory = await InitializeAsync(seed: SeedArrayOfPrimitives); + var contextFactory = await InitializeAsync( + onConfiguring: OnConfiguringArrayOfPrimitives, + seed: SeedArrayOfPrimitives); + using (var context = contextFactory.CreateContext()) { var query = context.Entities.OrderBy(x => x.Id).Select(x => new { x.Reference.IntArray, x.Reference.ListOfString }); @@ -115,7 +128,10 @@ public virtual async Task Project_json_array_of_primitives_on_reference(bool asy [MemberData(nameof(IsAsyncData))] public virtual async Task Project_json_array_of_primitives_on_collection(bool async) { - var contextFactory = await InitializeAsync(seed: SeedArrayOfPrimitives); + var contextFactory = await InitializeAsync( + onConfiguring: OnConfiguringArrayOfPrimitives, + seed: SeedArrayOfPrimitives); + using (var context = contextFactory.CreateContext()) { var query = context.Entities.OrderBy(x => x.Id).Select(x => new { x.Collection[0].IntArray, x.Collection[1].ListOfString }); @@ -136,7 +152,10 @@ public virtual async Task Project_json_array_of_primitives_on_collection(bool as [MemberData(nameof(IsAsyncData))] public virtual async Task Project_element_of_json_array_of_primitives(bool async) { - var contextFactory = await InitializeAsync(seed: SeedArrayOfPrimitives); + var contextFactory = await InitializeAsync( + onConfiguring: OnConfiguringArrayOfPrimitives, + seed: SeedArrayOfPrimitives); + using (var context = contextFactory.CreateContext()) { var query = context.Entities.OrderBy(x => x.Id).Select(x => new @@ -155,7 +174,10 @@ public virtual async Task Project_element_of_json_array_of_primitives(bool async [MemberData(nameof(IsAsyncData))] public virtual async Task Predicate_based_on_element_of_json_array_of_primitives1(bool async) { - var contextFactory = await InitializeAsync(seed: SeedArrayOfPrimitives); + var contextFactory = await InitializeAsync( + onConfiguring: OnConfiguringArrayOfPrimitives, + seed: SeedArrayOfPrimitives); + using (var context = contextFactory.CreateContext()) { var query = context.Entities.Where(x => x.Reference.IntArray[0] == 1); @@ -175,7 +197,10 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives [MemberData(nameof(IsAsyncData))] public virtual async Task Predicate_based_on_element_of_json_array_of_primitives2(bool async) { - var contextFactory = await InitializeAsync(seed: SeedArrayOfPrimitives); + var contextFactory = await InitializeAsync( + onConfiguring: OnConfiguringArrayOfPrimitives, + seed: SeedArrayOfPrimitives); + using (var context = contextFactory.CreateContext()) { var query = context.Entities.Where(x => x.Reference.ListOfString[1] == "Bar"); @@ -195,7 +220,10 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives [MemberData(nameof(IsAsyncData))] public virtual async Task Predicate_based_on_element_of_json_array_of_primitives3(bool async) { - var contextFactory = await InitializeAsync(seed: SeedArrayOfPrimitives); + var contextFactory = await InitializeAsync( + onConfiguring: OnConfiguringArrayOfPrimitives, + seed: SeedArrayOfPrimitives); + using (var context = contextFactory.CreateContext()) { var query = context.Entities.Where(x => x.Reference.IntArray.AsQueryable().ElementAt(0) == 1 @@ -215,6 +243,10 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives protected abstract void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx); + protected virtual void OnConfiguringArrayOfPrimitives(DbContextOptionsBuilder builder) + { + } + protected class MyContextArrayOfPrimitives : DbContext { public MyContextArrayOfPrimitives(DbContextOptions options) diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs index fc1e37448e5..8347fb60c35 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs @@ -277,6 +277,21 @@ public virtual ISetSource GetExpectedData() } } }, + { + typeof(JsonEntityConverters), (e, a) => + { + Assert.Equal(e == null, a == null); + if (a != null) + { + var ee = (JsonEntityConverters)e; + var aa = (JsonEntityConverters)a; + + Assert.Equal(ee.Id, aa.Id); + + AssertConverters(ee.Reference, aa.Reference); + } + } + }, }.ToDictionary(e => e.Key, e => (object)e.Value); private static void AssertOwnedRoot(JsonOwnedRoot expected, JsonOwnedRoot actual) @@ -356,6 +371,16 @@ public static void AssertAllTypes(JsonOwnedAllTypes expected, JsonOwnedAllTypes Assert.Equal(expected.TestNullableEnumWithConverterThatHandlesNulls, actual.TestNullableEnumWithConverterThatHandlesNulls); } + public static void AssertConverters(JsonOwnedConverters expected, JsonOwnedConverters actual) + { + Assert.Equal(expected.BoolConvertedToIntZeroOne, actual.BoolConvertedToIntZeroOne); + Assert.Equal(expected.BoolConvertedToStringTrueFalse, actual.BoolConvertedToStringTrueFalse); + Assert.Equal(expected.BoolConvertedToStringYN, actual.BoolConvertedToStringYN); + Assert.Equal(expected.IntZeroOneConvertedToBool, actual.IntZeroOneConvertedToBool); + Assert.Equal(expected.StringTrueFalseConvertedToBool, actual.StringTrueFalseConvertedToBool); + Assert.Equal(expected.StringYNConvertedToBool, actual.StringYNConvertedToBool); + } + protected override string StoreName { get; } = "JsonQueryTest"; public new RelationalTestStore TestStore @@ -536,5 +561,29 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Property(x => x.TestMaxLengthString).HasMaxLength(5); b.Property(x => x.TestDecimal).HasPrecision(18, 3); }); + + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne( + x => x.Reference, b => + { + b.ToJson(); + b.Property(x => x.BoolConvertedToIntZeroOne).HasConversion>(); + b.Property(x => x.BoolConvertedToStringTrueFalse).HasConversion(new BoolToStringConverter("False", "True")); + b.Property(x => x.BoolConvertedToStringYN).HasConversion(new BoolToStringConverter("N", "Y")); + b.Property(x => x.IntZeroOneConvertedToBool).HasConversion( + new ValueConverter( + x => x == 0 ? false : true, + x => x == false ? 0 : 1)); + + b.Property(x => x.StringTrueFalseConvertedToBool).HasConversion( + new ValueConverter( + x => x == "True" ? true : false, + x => x == true ? "True" : "False")); + + b.Property(x => x.StringYNConvertedToBool).HasConversion( + new ValueConverter( + x => x == "Y" ? true : false, + x => x == true ? "Y" : "N")); + }); } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs index 9f9d3a25e1c..fad0d980f82 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs @@ -1717,6 +1717,78 @@ public virtual Task Json_predicate_on_nullableint322(bool async) ss => ss.Set().Where(x => x.Reference.TestNullableInt32 != null), entryCount: 3); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_bool_converted_to_int_zero_one(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.BoolConvertedToIntZeroOne), + entryCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_bool_converted_to_int_zero_one_with_explicit_comparison(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.BoolConvertedToIntZeroOne == false), + entryCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_bool_converted_to_string_True_False(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.BoolConvertedToStringTrueFalse), + entryCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_bool_converted_to_string_True_False_with_explicit_comparison(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.BoolConvertedToStringTrueFalse == true), + entryCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_bool_converted_to_string_Y_N(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.BoolConvertedToStringYN), + entryCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_bool_converted_to_string_Y_N_with_explicit_comparison(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.BoolConvertedToStringYN == false), + entryCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_int_zero_one_converted_to_bool(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.IntZeroOneConvertedToBool == 1), + entryCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_string_True_False_converted_to_bool(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.StringTrueFalseConvertedToBool == "False"), + entryCount: 2); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_string_Y_N_converted_to_bool(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.StringYNConvertedToBool == "N"), + entryCount: 2); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task FromSql_on_entity_with_json_basic(bool async) diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityConverters.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityConverters.cs new file mode 100644 index 00000000000..a85acb4feaa --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityConverters.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery; + +public class JsonEntityConverters +{ + public int Id { get; set; } + public JsonOwnedConverters Reference { get; set; } +} diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedConverters.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedConverters.cs new file mode 100644 index 00000000000..9d53e321cdc --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedConverters.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery; + +public class JsonOwnedConverters +{ + public bool BoolConvertedToIntZeroOne { get; set; } + public bool BoolConvertedToStringTrueFalse { get; set; } + public bool BoolConvertedToStringYN { get; set; } + public int IntZeroOneConvertedToBool { get; set; } + public string StringTrueFalseConvertedToBool { get; set; } + public string StringYNConvertedToBool { get; set; } +} diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs index a9e8a3a6ea5..051926a721d 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs @@ -18,6 +18,7 @@ public JsonQueryContext(DbContextOptions options) public DbSet JsonEntitiesSingleOwned { get; set; } public DbSet JsonEntitiesInheritance { get; set; } public DbSet JsonEntitiesAllTypes { get; set; } + public DbSet JsonEntitiesConverters { get; set; } public static void Seed(JsonQueryContext context) { @@ -31,6 +32,7 @@ public static void Seed(JsonQueryContext context) var jsonEntitiesSingleOwned = JsonQueryData.CreateJsonEntitiesSingleOwned(); var jsonEntitiesInheritance = JsonQueryData.CreateJsonEntitiesInheritance(); var jsonEntitiesAllTypes = JsonQueryData.CreateJsonEntitiesAllTypes(); + var jsonEntitiesConverters = JsonQueryData.CreateJsonEntitiesConverters(); context.JsonEntitiesBasic.AddRange(jsonEntitiesBasic); context.EntitiesBasic.AddRange(entitiesBasic); @@ -40,6 +42,7 @@ public static void Seed(JsonQueryContext context) context.JsonEntitiesSingleOwned.AddRange(jsonEntitiesSingleOwned); context.JsonEntitiesInheritance.AddRange(jsonEntitiesInheritance); context.JsonEntitiesAllTypes.AddRange(jsonEntitiesAllTypes); + context.JsonEntitiesConverters.AddRange(jsonEntitiesConverters); context.SaveChanges(); } } diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs index 41eff9550c9..37380b35a31 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs @@ -17,6 +17,7 @@ public JsonQueryData() JsonEntitiesSingleOwned = CreateJsonEntitiesSingleOwned(); JsonEntitiesInheritance = CreateJsonEntitiesInheritance(); JsonEntitiesAllTypes = CreateJsonEntitiesAllTypes(); + JsonEntitiesConverters = CreateJsonEntitiesConverters(); } public IReadOnlyList EntitiesBasic { get; } @@ -27,6 +28,7 @@ public JsonQueryData() public IReadOnlyList JsonEntitiesSingleOwned { get; set; } public IReadOnlyList JsonEntitiesInheritance { get; set; } public IReadOnlyList JsonEntitiesAllTypes { get; set; } + public IReadOnlyList JsonEntitiesConverters { get; set; } public static IReadOnlyList CreateJsonEntitiesBasic() { @@ -773,6 +775,43 @@ public static IReadOnlyList CreateJsonEntitiesAllTypes() }; } + public static IReadOnlyList CreateJsonEntitiesConverters() + { + var r1 = new JsonOwnedConverters + { + BoolConvertedToIntZeroOne = true, + BoolConvertedToStringTrueFalse = false, + BoolConvertedToStringYN = true, + IntZeroOneConvertedToBool = 0, + StringTrueFalseConvertedToBool = "True", + StringYNConvertedToBool = "N", + }; + + var r2 = new JsonOwnedConverters + { + BoolConvertedToIntZeroOne = false, + BoolConvertedToStringTrueFalse = true, + BoolConvertedToStringYN = false, + IntZeroOneConvertedToBool = 1, + StringTrueFalseConvertedToBool = "False", + StringYNConvertedToBool = "Y", + }; + + return new List + { + new() + { + Id = 1, + Reference = r1, + }, + new() + { + Id = 2, + Reference = r2, + } + }; + } + public IQueryable Set() where TEntity : class { @@ -811,6 +850,11 @@ public IQueryable Set() return (IQueryable)JsonEntitiesAllTypes.OfType().AsQueryable(); } + if (typeof(TEntity) == typeof(JsonEntityConverters)) + { + return (IQueryable)JsonEntitiesConverters.OfType().AsQueryable(); + } + if (typeof(TEntity) == typeof(JsonEntityBasicForReference)) { return (IQueryable)JsonEntitiesBasicForReference.AsQueryable(); diff --git a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs index b3fe4d6c2e5..8c0ace8bb94 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs @@ -124,6 +124,30 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Property(x => x.TestDecimal).HasPrecision(18, 3); }); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne( + x => x.Reference, b => + { + b.ToJson(); + b.Property(x => x.BoolConvertedToIntZeroOne).HasConversion>(); + b.Property(x => x.BoolConvertedToStringTrueFalse).HasConversion(new BoolToStringConverter("False", "True")); + b.Property(x => x.BoolConvertedToStringYN).HasConversion(new BoolToStringConverter("N", "Y")); + b.Property(x => x.IntZeroOneConvertedToBool).HasConversion( + new ValueConverter( + x => x == 0 ? false : true, + x => x == false ? 0 : 1)); + + b.Property(x => x.StringTrueFalseConvertedToBool).HasConversion( + new ValueConverter( + x => x == "True" ? true : false, + x => x == true ? "True" : "False")); + + b.Property(x => x.StringYNConvertedToBool).HasConversion( + new ValueConverter( + x => x == "Y" ? true : false, + x => x == true ? "Y" : "N")); + }); + base.OnModelCreating(modelBuilder, context); } @@ -132,10 +156,12 @@ protected override void Seed(JsonQueryContext context) var jsonEntitiesBasic = JsonQueryData.CreateJsonEntitiesBasic(); var jsonEntitiesInheritance = JsonQueryData.CreateJsonEntitiesInheritance(); var jsonEntitiesAllTypes = JsonQueryData.CreateJsonEntitiesAllTypes(); + var jsonEntitiesConverters = JsonQueryData.CreateJsonEntitiesConverters(); context.JsonEntitiesBasic.AddRange(jsonEntitiesBasic); context.JsonEntitiesInheritance.AddRange(jsonEntitiesInheritance); context.JsonEntitiesAllTypes.AddRange(jsonEntitiesAllTypes); + context.JsonEntitiesConverters.AddRange(jsonEntitiesConverters); context.SaveChanges(); } } diff --git a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs index 12231b31047..8a753ae706f 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs @@ -1361,6 +1361,126 @@ public virtual Task Edit_a_scalar_property_and_another_property_behind_reference Assert.Equal("edit", result.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething); }); + [ConditionalFact] + public virtual Task Edit_single_property_with_converter_bool_to_int_zero_one() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesConverters.ToListAsync(); + var entity = query.Single(x => x.Id == 1); + entity.Reference.BoolConvertedToIntZeroOne = false; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(x => x.Id == 1); + Assert.Equal(false, result.Reference.BoolConvertedToIntZeroOne); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_with_converter_bool_to_string_True_False() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesConverters.ToListAsync(); + var entity = query.Single(x => x.Id == 1); + entity.Reference.BoolConvertedToStringTrueFalse = true; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(x => x.Id == 1); + Assert.Equal(true, result.Reference.BoolConvertedToStringTrueFalse); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_with_converter_bool_to_string_Y_N() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesConverters.ToListAsync(); + var entity = query.Single(x => x.Id == 1); + entity.Reference.BoolConvertedToStringYN = false; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(x => x.Id == 1); + Assert.Equal(false, result.Reference.BoolConvertedToStringYN); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_with_converter_int_zero_one_to_bool() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesConverters.ToListAsync(); + var entity = query.Single(x => x.Id == 1); + entity.Reference.IntZeroOneConvertedToBool = 1; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(x => x.Id == 1); + Assert.Equal(1, result.Reference.IntZeroOneConvertedToBool); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_with_converter_string_True_False_to_bool() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesConverters.ToListAsync(); + var entity = query.Single(x => x.Id == 1); + entity.Reference.StringTrueFalseConvertedToBool = "False"; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(x => x.Id == 1); + Assert.Equal("False", result.Reference.StringTrueFalseConvertedToBool); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_with_converter_string_Y_N_to_bool() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesConverters.ToListAsync(); + var entity = query.Single(x => x.Id == 1); + entity.Reference.StringYNConvertedToBool = "Y"; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(x => x.Id == 1); + Assert.Equal("Y", result.Reference.StringYNConvertedToBool); + }); + public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs index e516f9d697b..bba91e533a2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs @@ -1732,6 +1732,78 @@ WHERE CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt64') AS decimal(20,0)) < """); } + public override async Task Json_predicate_on_bool_converted_to_int_zero_one(bool async) + { + await base.Json_predicate_on_bool_converted_to_int_zero_one(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.BoolConvertedToIntZeroOne') AS int) = 1 +"""); + } + + public override async Task Json_predicate_on_bool_converted_to_string_True_False(bool async) + { + await base.Json_predicate_on_bool_converted_to_string_True_False(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE JSON_VALUE([j].[Reference],'$.BoolConvertedToStringTrueFalse') = N'True' +"""); + } + + public override async Task Json_predicate_on_bool_converted_to_string_Y_N(bool async) + { + await base.Json_predicate_on_bool_converted_to_string_Y_N(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE JSON_VALUE([j].[Reference],'$.BoolConvertedToStringYN') = N'Y' +"""); + } + + public override async Task Json_predicate_on_int_zero_one_converted_to_bool(bool async) + { + await base.Json_predicate_on_int_zero_one_converted_to_bool(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.IntZeroOneConvertedToBool') AS bit) = CAST(1 AS bit) +"""); + } + + public override async Task Json_predicate_on_string_True_False_converted_to_bool(bool async) + { + await base.Json_predicate_on_string_True_False_converted_to_bool(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.StringTrueFalseConvertedToBool') AS bit) = CAST(0 AS bit) +"""); + } + + public override async Task Json_predicate_on_string_Y_N_converted_to_bool(bool async) + { + await base.Json_predicate_on_string_Y_N_converted_to_bool(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.StringYNConvertedToBool') AS bit) = CAST(0 AS bit) +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_basic(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs index 9a56ee1b8b3..01fcdeeb35f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs @@ -1236,6 +1236,114 @@ FROM [JsonEntitiesBasic] AS [j] """); } + public override async Task Edit_single_property_with_converter_bool_to_int_zero_one() + { + await base.Edit_single_property_with_converter_bool_to_int_zero_one(); + + AssertSql( +""" +@p0='[0]' (Nullable = false) (Size = 3) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesConverters] SET [Reference] = JSON_MODIFY([Reference], 'strict $.BoolConvertedToIntZeroOne', CAST(JSON_VALUE(@p0, '$[0]') AS int)) +OUTPUT 1 +WHERE [Id] = @p1; +""", + // + """ +SELECT TOP(2) [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE [j].[Id] = 1 +"""); + } + + public override async Task Edit_single_property_with_converter_bool_to_string_True_False() + { + await base.Edit_single_property_with_converter_bool_to_string_True_False(); + + AssertSql( +""" +@p0='["True"]' (Nullable = false) (Size = 8) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesConverters] SET [Reference] = JSON_MODIFY([Reference], 'strict $.BoolConvertedToStringTrueFalse', CAST(JSON_VALUE(@p0, '$[0]') AS nvarchar(5))) +OUTPUT 1 +WHERE [Id] = @p1; +""", + // + """ +SELECT TOP(2) [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE [j].[Id] = 1 +"""); + } + + public override async Task Edit_single_property_with_converter_bool_to_string_Y_N() + { + await base.Edit_single_property_with_converter_bool_to_string_Y_N(); + + AssertSql( +""" +@p0='["N"]' (Nullable = false) (Size = 5) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesConverters] SET [Reference] = JSON_MODIFY([Reference], 'strict $.BoolConvertedToStringYN', CAST(JSON_VALUE(@p0, '$[0]') AS nvarchar(1))) +OUTPUT 1 +WHERE [Id] = @p1; +""", + // + """ +SELECT TOP(2) [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE [j].[Id] = 1 +"""); + } + + public override async Task Edit_single_property_with_converter_int_zero_one_to_bool() + { + await base.Edit_single_property_with_converter_int_zero_one_to_bool(); + + AssertSql( +""" +@p0='[true]' (Nullable = false) (Size = 6) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesConverters] SET [Reference] = JSON_MODIFY([Reference], 'strict $.IntZeroOneConvertedToBool', CAST(JSON_VALUE(@p0, '$[0]') AS bit)) +OUTPUT 1 +WHERE [Id] = @p1; +""", + // + """ +SELECT TOP(2) [j].[Id], [j].[Reference] +FROM [JsonEntitiesConverters] AS [j] +WHERE [j].[Id] = 1 +"""); + } + + [ConditionalFact(Skip = "issue #30330")] + public override async Task Edit_single_property_with_converter_string_True_False_to_bool() + { + await base.Edit_single_property_with_converter_string_True_False_to_bool(); + + AssertSql(); + } + + [ConditionalFact(Skip = "issue #30330")] + public override async Task Edit_single_property_with_converter_string_Y_N_to_bool() + { + await base.Edit_single_property_with_converter_string_Y_N_to_bool(); + + AssertSql(); + } + protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/JsonQueryAdHocSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/JsonQueryAdHocSqliteTest.cs new file mode 100644 index 00000000000..558d2637418 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/JsonQueryAdHocSqliteTest.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +public class JsonQueryAdHocSqliteTest : JsonQueryAdHocTestBase +{ + public JsonQueryAdHocSqliteTest(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; + + // issue #26708 + protected override void OnConfiguring29219(DbContextOptionsBuilder builder) + => base.AddOptions(builder.ConfigureWarnings(b => b.Ignore(SqliteEventId.CompositeKeyWithValueGeneration))); + + protected override void Seed29219(MyContext29219 ctx) + { + var entity1 = new MyEntity29219 + { + Id = 1, + Reference = new MyJsonEntity29219 { NonNullableScalar = 10, NullableScalar = 11 }, + Collection = new List + { + new MyJsonEntity29219 { NonNullableScalar = 100, NullableScalar = 101 }, + new MyJsonEntity29219 { NonNullableScalar = 200, NullableScalar = 201 }, + new MyJsonEntity29219 { NonNullableScalar = 300, NullableScalar = null }, + } + }; + + var entity2 = new MyEntity29219 + { + Id = 2, + Reference = new MyJsonEntity29219 { NonNullableScalar = 20, NullableScalar = null }, + Collection = new List + { + new MyJsonEntity29219 { NonNullableScalar = 1001, NullableScalar = null }, + } + }; + + ctx.Entities.AddRange(entity1, entity2); + ctx.SaveChanges(); + + ctx.Database.ExecuteSqlRaw(@"INSERT INTO ""Entities"" (""Id"", ""Reference"", ""Collection"") +VALUES(3, '{{ ""NonNullableScalar"" : 30 }}', '[{{ ""NonNullableScalar"" : 10001 }}]')"); + } + + // issue #26708 + protected override void OnConfiguringArrayOfPrimitives(DbContextOptionsBuilder builder) + => base.AddOptions(builder.ConfigureWarnings(b => b.Ignore(SqliteEventId.CompositeKeyWithValueGeneration))); + + protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) + { + var entity1 = new MyEntityArrayOfPrimitives + { + Id = 1, + Reference = new MyJsonEntityArrayOfPrimitives + { + IntArray = new int[] { 1, 2, 3 }, + ListOfString = new List { "Foo", "Bar", "Baz" } + }, + Collection = new List + { + new MyJsonEntityArrayOfPrimitives + { + IntArray = new int[] { 111, 112, 113 }, + ListOfString = new List { "Foo11", "Bar11" } + }, + new MyJsonEntityArrayOfPrimitives + { + IntArray = new int[] { 211, 212, 213 }, + ListOfString = new List { "Foo12", "Bar12" } + }, + } + }; + + var entity2 = new MyEntityArrayOfPrimitives + { + Id = 2, + Reference = new MyJsonEntityArrayOfPrimitives + { + IntArray = new int[] { 10, 20, 30 }, + ListOfString = new List { "A", "B", "C" } + }, + Collection = new List + { + new MyJsonEntityArrayOfPrimitives + { + IntArray = new int[] { 110, 120, 130 }, + ListOfString = new List { "A1", "Z1" } + }, + new MyJsonEntityArrayOfPrimitives + { + IntArray = new int[] { 210, 220, 230 }, + ListOfString = new List { "A2", "Z2" } + }, + } + }; + + ctx.Entities.AddRange(entity1, entity2); + ctx.SaveChanges(); + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/JsonQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/JsonQuerySqliteFixture.cs new file mode 100644 index 00000000000..a62a5afd707 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/JsonQuerySqliteFixture.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +public class JsonQuerySqliteFixture : JsonQueryFixtureBase +{ + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; + + + // issue #26708 + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder.ConfigureWarnings(b => b.Ignore(SqliteEventId.CompositeKeyWithValueGeneration))); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/JsonQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/JsonQuerySqliteTest.cs new file mode 100644 index 00000000000..4af66b79222 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/JsonQuerySqliteTest.cs @@ -0,0 +1,180 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Sqlite.Internal; +using Microsoft.EntityFrameworkCore.TestModels.JsonQuery; + +namespace Microsoft.EntityFrameworkCore.Query; + +public class JsonQuerySqliteTest : JsonQueryTestBase +{ + public JsonQuerySqliteTest(JsonQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Project_json_entity_FirstOrDefault_subquery(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Project_json_entity_FirstOrDefault_subquery(async))) + .Message); + + public override async Task Project_json_entity_FirstOrDefault_subquery_deduplication(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Project_json_entity_FirstOrDefault_subquery_deduplication(async))) + .Message); + + public override async Task Project_json_entity_FirstOrDefault_subquery_deduplication_and_outer_reference(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Project_json_entity_FirstOrDefault_subquery_deduplication_and_outer_reference(async))) + .Message); + + public override async Task Project_json_entity_FirstOrDefault_subquery_deduplication_outer_reference_and_pruning(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Project_json_entity_FirstOrDefault_subquery_deduplication_outer_reference_and_pruning(async))) + .Message); + + public override async Task Json_collection_element_access_in_predicate_nested_mix(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_predicate_nested_mix(async))) + .Message); + + public override async Task Json_collection_element_access_in_predicate_using_column(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_predicate_using_column(async))) + .Message); + + public override async Task Json_collection_element_access_in_predicate_using_complex_expression1(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_predicate_using_complex_expression1(async))) + .Message); + + public override async Task Json_collection_element_access_in_predicate_using_complex_expression2(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_predicate_using_complex_expression2(async))) + .Message); + + public override async Task Json_collection_element_access_in_predicate_using_variable(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_predicate_using_variable(async))) + .Message); + + public override async Task Json_collection_element_access_in_projection_nested(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_projection_nested(async))) + .Message); + + public override async Task Json_collection_element_access_in_projection_nested_project_collection(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_projection_nested_project_collection(async))) + .Message); + + public override async Task Json_collection_element_access_in_projection_nested_project_collection_anonymous_projection(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_projection_nested_project_collection_anonymous_projection(async))) + .Message); + + public override async Task Json_collection_element_access_in_projection_nested_project_reference(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_projection_nested_project_reference(async))) + .Message); + + public override async Task Json_collection_element_access_in_projection_nested_project_scalar(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_projection_nested_project_scalar(async))) + .Message); + + public override async Task Json_collection_element_access_in_projection_using_column(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_projection_using_column(async))) + .Message); + + public override async Task Json_collection_element_access_in_projection_using_parameter(bool async) + => Assert.Equal( + SqliteStrings.NonConstantJsonArrayIndexNotSupported("OwnedCollectionRoot"), + (await Assert.ThrowsAsync( + () => base.Json_collection_element_access_in_projection_using_parameter(async))) + .Message); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task FromSqlInterpolated_on_entity_with_json_with_predicate(bool async) + { + var parameter = new SqliteParameter { ParameterName = "prm", Value = 1 }; + await AssertQuery( + async, + ss => ((DbSet)ss.Set()).FromSql( + Fixture.TestStore.NormalizeDelimitersInInterpolatedString($"SELECT * FROM [JsonEntitiesBasic] AS j WHERE [j].[Id] = {parameter}")), + ss => ss.Set(), + entryCount: 40); + + AssertSql( +""" +prm='1' (DbType = String) + +SELECT "m"."Id", "m"."EntityBasicId", "m"."Name", "m"."OwnedCollectionRoot", "m"."OwnedReferenceRoot" +FROM ( + SELECT * FROM "JsonEntitiesBasic" AS j WHERE "j"."Id" = @prm +) AS "m" +"""); + } + + [ConditionalTheory(Skip = "issue #30326")] + public override async Task Json_predicate_on_bool_converted_to_int_zero_one(bool async) + { + await base.Json_predicate_on_bool_converted_to_int_zero_one(async); + + AssertSql(); + } + + [ConditionalTheory(Skip = "issue #30326")] + public override async Task Json_predicate_on_bool_converted_to_string_True_False(bool async) + { + await base.Json_predicate_on_bool_converted_to_string_True_False(async); + + AssertSql(); + } + + [ConditionalTheory(Skip = "issue #30326")] + public override async Task Json_predicate_on_bool_converted_to_string_Y_N(bool async) + { + await base.Json_predicate_on_bool_converted_to_string_Y_N(async); + + AssertSql(); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteComplianceTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteComplianceTest.cs index ddaf5dc308e..2119da5e25a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteComplianceTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/SqliteComplianceTest.cs @@ -8,9 +8,6 @@ public class SqliteComplianceTest : RelationalComplianceTestBase protected override ICollection IgnoredTestBases { get; } = new HashSet { typeof(FromSqlSprocQueryTestBase<>), - typeof(JsonQueryTestBase<>), - typeof(JsonUpdateTestBase<>), - typeof(JsonQueryAdHocTestBase), typeof(SqlExecutorTestBase<>), typeof(UdfDbFunctionTestBase<>), typeof(TPCRelationshipsQueryTestBase<>), // internal class is added diff --git a/test/EFCore.Sqlite.FunctionalTests/Update/JsonUpdateSqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Update/JsonUpdateSqliteFixture.cs new file mode 100644 index 00000000000..c1cb714684b --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Update/JsonUpdateSqliteFixture.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Update; + +public class JsonUpdateSqliteFixture : JsonUpdateFixtureBase +{ + protected override ITestStoreFactory TestStoreFactory + => SqliteTestStoreFactory.Instance; + + // issue #26708 + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder.ConfigureWarnings(b => b.Ignore(SqliteEventId.CompositeKeyWithValueGeneration))); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Update/JsonUpdateSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Update/JsonUpdateSqliteTest.cs new file mode 100644 index 00000000000..48046f19ec6 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Update/JsonUpdateSqliteTest.cs @@ -0,0 +1,1321 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Update; + +public class JsonUpdateSqliteTest : JsonUpdateTestBase +{ + public JsonUpdateSqliteTest(JsonUpdateSqliteFixture fixture) + : base(fixture) + { + ClearLog(); + } + + public override async Task Add_element_to_json_collection_branch() + { + await base.Add_element_to_json_collection_branch(); + + AssertSql( +""" +@p0='[{"Date":"2101-01-01T00:00:00","Enum":"Two","Fraction":10.1,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":"Three","Fraction":10.2,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2010-10-10T00:00:00","Enum":"Three","Fraction":42.42,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (Size = 684) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedCollectionBranch', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Add_element_to_json_collection_leaf() + { + await base.Add_element_to_json_collection_leaf(); + + AssertSql( +""" +@p0='[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"},{"SomethingSomething":"ss1"}]' (Nullable = false) (Size = 100) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedReferenceBranch.OwnedCollectionLeaf', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Add_element_to_json_collection_on_derived() + { + await base.Add_element_to_json_collection_on_derived(); + + AssertSql( +""" +@p0='[{"Date":"2221-01-01T00:00:00","Enum":"Two","Fraction":221.1,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2222-01-01T00:00:00","Enum":"Three","Fraction":222.1,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2010-10-10T00:00:00","Enum":"Three","Fraction":42.42,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (Size = 668) +@p1='2' + +UPDATE "JsonEntitiesInheritance" SET "CollectionOnDerived" = @p0 +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Discriminator", "j"."Name", "j"."Fraction", "j"."CollectionOnBase", "j"."ReferenceOnBase", "j"."CollectionOnDerived", "j"."ReferenceOnDerived" +FROM "JsonEntitiesInheritance" AS "j" +WHERE "j"."Discriminator" = 'JsonEntityInheritanceDerived' +LIMIT 2 +"""); + } + + public override async Task Add_element_to_json_collection_root() + { + await base.Add_element_to_json_collection_root(); + + AssertSql( +""" +@p0='[{"Name":"e1_c1","Number":11,"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":"Two","Fraction":11.1,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":"Three","Fraction":11.2,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":"One","Fraction":11.0,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"e1_c2","Number":12,"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":"Two","Fraction":12.1,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":"One","Fraction":12.2,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":"Three","Fraction":12.0,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Name":"new Name","Number":142,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":"Three","Fraction":42.42,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}]' (Nullable = false) (Size = 1867) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Add_entity_with_json() + { + await base.Add_entity_with_json(); + + AssertSql( +""" +@p0='{"Name":"RootName","Number":42,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":"Three","Fraction":42.42,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (Size = 296) +@p1='2' +@p2=NULL (DbType = Int32) +@p3='NewEntity' (Size = 9) + +INSERT INTO "JsonEntitiesBasic" ("OwnedReferenceRoot", "Id", "EntityBasicId", "Name") +VALUES (@p0, @p1, @p2, @p3); +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +"""); + } + + public override async Task Add_json_reference_leaf() + { + await base.Add_json_reference_leaf(); + + AssertSql( +""" +@p0='{"SomethingSomething":"ss3"}' (Nullable = false) (Size = 28) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedCollectionBranch[0].OwnedReferenceLeaf', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Add_json_reference_root() + { + await base.Add_json_reference_root(); + + AssertSql( +""" +@p0='{"Name":"RootName","Number":42,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":"Three","Fraction":42.42,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (Size = 296) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = @p0 +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Delete_entity_with_json() + { + await base.Delete_entity_with_json(); + + AssertSql( +""" +@p0='1' + +DELETE FROM "JsonEntitiesBasic" +WHERE "Id" = @p0 +RETURNING 1; +""", + // + """ +SELECT COUNT(*) +FROM "JsonEntitiesBasic" AS "j" +"""); + } + + public override async Task Delete_json_collection_branch() + { + await base.Delete_json_collection_branch(); + + AssertSql( +""" +@p0='[]' (Nullable = false) (Size = 2) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedCollectionBranch', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Delete_json_collection_root() + { + await base.Delete_json_collection_root(); + + AssertSql( +""" +@p0='[]' (Nullable = false) (Size = 2) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Delete_json_reference_leaf() + { + await base.Delete_json_reference_leaf(); + + AssertSql( +""" +@p0=NULL (Nullable = false) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedReferenceBranch.OwnedReferenceLeaf', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Delete_json_reference_root() + { + await base.Delete_json_reference_root(); + + AssertSql( +""" +@p0=NULL (Nullable = false) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = @p0 +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_element_in_json_collection_branch() + { + await base.Edit_element_in_json_collection_branch(); + + AssertSql( +""" +@p0='["2111-11-11T00:00:00"]' (Nullable = false) (Size = 23) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = json_set("OwnedCollectionRoot", '$[0].OwnedCollectionBranch[0].Date', json_extract(@p0, '$[0]')) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_element_in_json_collection_root1() + { + await base.Edit_element_in_json_collection_root1(); + + AssertSql( +""" +@p0='["Modified"]' (Nullable = false) (Size = 12) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = json_set("OwnedCollectionRoot", '$[0].Name', json_extract(@p0, '$[0]')) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_element_in_json_collection_root2() + { + await base.Edit_element_in_json_collection_root2(); + + AssertSql( +""" +@p0='["Modified"]' (Nullable = false) (Size = 12) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = json_set("OwnedCollectionRoot", '$[1].Name', json_extract(@p0, '$[0]')) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_element_in_json_multiple_levels_partial_update() + { + await base.Edit_element_in_json_multiple_levels_partial_update(); + + AssertSql( +""" +@p0='[{"Date":"2111-01-01T00:00:00","Enum":"Two","Fraction":11.1,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"...and another"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":"Three","Fraction":11.2,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"yet another change"},{"SomethingSomething":"and another"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}]' (Nullable = false) (Size = 485) +@p1='{"Name":"edit","Number":10,"OwnedCollectionBranch":[{"Date":"2101-01-01T00:00:00","Enum":"Two","Fraction":10.1,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":"Three","Fraction":10.2,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}}],"OwnedReferenceBranch":{"Date":"2111-11-11T00:00:00","Enum":"One","Fraction":10.0,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}}' (Nullable = false) (Size = 773) +@p2='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = json_set("OwnedCollectionRoot", '$[0].OwnedCollectionBranch', json(@p0)), "OwnedReferenceRoot" = @p1 +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_element_in_json_branch_collection_and_add_element_to_the_same_collection() + { + await base.Edit_element_in_json_branch_collection_and_add_element_to_the_same_collection(); + + AssertSql( +""" +@p0='[{"Date":"2101-01-01T00:00:00","Enum":"Two","Fraction":4321.3,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":"Three","Fraction":10.2,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2222-11-11T00:00:00","Enum":"Three","Fraction":45.32,"NullableEnum":null,"OwnedCollectionLeaf":[],"OwnedReferenceLeaf":{"SomethingSomething":"cc"}}]' (Nullable = false) (Size = 628) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedCollectionBranch', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_two_elements_in_the_same_json_collection() + { + await base.Edit_two_elements_in_the_same_json_collection(); + + AssertSql( +""" +@p0='[{"SomethingSomething":"edit1"},{"SomethingSomething":"edit2"}]' (Nullable = false) (Size = 63) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedCollectionBranch[0].OwnedCollectionLeaf', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_two_elements_in_the_same_json_collection_at_the_root() + { + await base.Edit_two_elements_in_the_same_json_collection_at_the_root(); + + AssertSql( +""" +@p0='[{"Name":"edit1","Number":11,"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":"Two","Fraction":11.1,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":"Three","Fraction":11.2,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":"One","Fraction":11.0,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"edit2","Number":12,"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":"Two","Fraction":12.1,"NullableEnum":"One","OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":"One","Fraction":12.2,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":"Three","Fraction":12.0,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}}]' (Nullable = false) (Size = 1569) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_collection_element_and_reference_at_once() + { + await base.Edit_collection_element_and_reference_at_once(); + + AssertSql( +""" +@p0='{"Date":"2102-01-01T00:00:00","Enum":"Three","Fraction":10.2,"NullableEnum":"Two","OwnedCollectionLeaf":[{"SomethingSomething":"edit1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit2"}}' (Nullable = false) (Size = 225) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedCollectionBranch[1]', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_single_enum_property() + { + await base.Edit_single_enum_property(); + + AssertSql( +""" +@p0='["Two"]' (Nullable = false) (Size = 7) +@p1='["Two"]' (Nullable = false) (Size = 7) +@p2='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = json_set("OwnedCollectionRoot", '$[1].OwnedCollectionBranch[1].Enum', json_extract(@p0, '$[0]')), "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedReferenceBranch.Enum', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_single_numeric_property() + { + await base.Edit_single_numeric_property(); + + AssertSql( +""" +@p0='[1024]' (Nullable = false) (Size = 6) +@p1='[999]' (Nullable = false) (Size = 5) +@p2='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = json_set("OwnedCollectionRoot", '$[1].Number', json_extract(@p0, '$[0]')), "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.Number', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_bool() + { + await base.Edit_single_property_bool(); + + AssertSql( +""" +@p0='["true"]' (Nullable = false) (Size = 8) +@p1='["false"]' (Nullable = false) (Size = 9) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestBoolean', json(json_extract(@p0, '$[0]'))), "Reference" = json_set("Reference", '$.TestBoolean', json(json_extract(@p1, '$[0]'))) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_byte() + { + await base.Edit_single_property_byte(); + + AssertSql( +""" +@p0='[14]' (Nullable = false) (Size = 4) +@p1='[25]' (Nullable = false) (Size = 4) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestByte', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestByte', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_char() + { + await base.Edit_single_property_char(); + + AssertSql( +""" +@p0='["t"]' (Nullable = false) (Size = 5) +@p1='1' + +UPDATE "JsonEntitiesAllTypes" SET "Reference" = json_set("Reference", '$.TestCharacter', json_extract(@p0, '$[0]')) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_datetime() + { + await base.Edit_single_property_datetime(); + + AssertSql( +""" +@p0='["3000-01-01T12:34:56"]' (Nullable = false) (Size = 23) +@p1='["3000-01-01T12:34:56"]' (Nullable = false) (Size = 23) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestDateTime', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestDateTime', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_datetimeoffset() + { + await base.Edit_single_property_datetimeoffset(); + + AssertSql( +""" +@p0='["3000-01-01T12:34:56-04:00"]' (Nullable = false) (Size = 29) +@p1='["3000-01-01T12:34:56-04:00"]' (Nullable = false) (Size = 29) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestDateTimeOffset', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestDateTimeOffset', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_decimal() + { + await base.Edit_single_property_decimal(); + + AssertSql( +""" +@p0='[-13579.01]' (Nullable = false) (Size = 11) +@p1='[-13579.01]' (Nullable = false) (Size = 11) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestDecimal', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestDecimal', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_double() + { + await base.Edit_single_property_double(); + + AssertSql( +""" +@p0='[-1.23579]' (Nullable = false) (Size = 10) +@p1='[-1.23579]' (Nullable = false) (Size = 10) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestDouble', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestDouble', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_guid() + { + await base.Edit_single_property_guid(); + + AssertSql( +""" +@p0='["12345678-1234-4321-5555-987654321000"]' (Nullable = false) (Size = 40) +@p1='["12345678-1234-4321-5555-987654321000"]' (Nullable = false) (Size = 40) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestGuid', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestGuid', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_int16() + { + await base.Edit_single_property_int16(); + + AssertSql( +""" +@p0='[-3234]' (Nullable = false) (Size = 7) +@p1='[-3234]' (Nullable = false) (Size = 7) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestInt16', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestInt16', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_int32() + { + await base.Edit_single_property_int32(); + + AssertSql( +""" +@p0='[-3234]' (Nullable = false) (Size = 7) +@p1='[-3234]' (Nullable = false) (Size = 7) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestInt32', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestInt32', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_int64() + { + await base.Edit_single_property_int64(); + + AssertSql( +""" +@p0='[-3234]' (Nullable = false) (Size = 7) +@p1='[-3234]' (Nullable = false) (Size = 7) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestInt64', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestInt64', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_signed_byte() + { + await base.Edit_single_property_signed_byte(); + + AssertSql( +""" +@p0='[-108]' (Nullable = false) (Size = 6) +@p1='[-108]' (Nullable = false) (Size = 6) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestSignedByte', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestSignedByte', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_single() + { + await base.Edit_single_property_single(); + + AssertSql( +""" +@p0='[-7.234]' (Nullable = false) (Size = 8) +@p1='[-7.234]' (Nullable = false) (Size = 8) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestSingle', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestSingle', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_timespan() + { + await base.Edit_single_property_timespan(); + + AssertSql( +""" +@p0='["10:01:01.0070000"]' (Nullable = false) (Size = 20) +@p1='["10:01:01.0070000"]' (Nullable = false) (Size = 20) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestTimeSpan', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestTimeSpan', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_uint16() + { + await base.Edit_single_property_uint16(); + + AssertSql( +""" +@p0='[1534]' (Nullable = false) (Size = 6) +@p1='[1534]' (Nullable = false) (Size = 6) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestUnsignedInt16', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestUnsignedInt16', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_uint32() + { + await base.Edit_single_property_uint32(); + + AssertSql( +""" +@p0='[1237775789]' (Nullable = false) (Size = 12) +@p1='[1237775789]' (Nullable = false) (Size = 12) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestUnsignedInt32', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestUnsignedInt32', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_uint64() + { + await base.Edit_single_property_uint64(); + + AssertSql( +""" +@p0='[1234555555123456789]' (Nullable = false) (Size = 21) +@p1='[1234555555123456789]' (Nullable = false) (Size = 21) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestUnsignedInt64', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestUnsignedInt64', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_nullable_int32() + { + await base.Edit_single_property_nullable_int32(); + + AssertSql( +""" +@p0='[122354]' (Nullable = false) (Size = 8) +@p1='[64528]' (Nullable = false) (Size = 7) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestNullableInt32', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestNullableInt32', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_nullable_int32_set_to_null() + { + await base.Edit_single_property_nullable_int32_set_to_null(); + + AssertSql( +""" +@p0='[null]' (Nullable = false) (Size = 6) +@p1='[null]' (Nullable = false) (Size = 6) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestNullableInt32', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestNullableInt32', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_enum() + { + await base.Edit_single_property_enum(); + + AssertSql( +""" +@p0='["Three"]' (Nullable = false) (Size = 9) +@p1='["Three"]' (Nullable = false) (Size = 9) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestEnum', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestEnum', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_enum_with_int_converter() + { + await base.Edit_single_property_enum_with_int_converter(); + + AssertSql( +""" +@p0='["Three"]' (Nullable = false) (Size = 9) +@p1='["Three"]' (Nullable = false) (Size = 9) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestEnumWithIntConverter', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestEnumWithIntConverter', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_nullable_enum() + { + await base.Edit_single_property_nullable_enum(); + + AssertSql( +""" +@p0='["Three"]' (Nullable = false) (Size = 9) +@p1='["Three"]' (Nullable = false) (Size = 9) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestEnum', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestEnum', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_nullable_enum_set_to_null() + { + await base.Edit_single_property_nullable_enum_set_to_null(); + + AssertSql( +""" +@p0='[null]' (Nullable = false) (Size = 6) +@p1='[null]' (Nullable = false) (Size = 6) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestNullableEnum', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestNullableEnum', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_nullable_enum_with_int_converter() + { + await base.Edit_single_property_nullable_enum_with_int_converter(); + + AssertSql( +""" +@p0='["One"]' (Nullable = false) (Size = 7) +@p1='["Three"]' (Nullable = false) (Size = 9) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestNullableEnumWithIntConverter', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestNullableEnumWithIntConverter', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_nullable_enum_with_int_converter_set_to_null() + { + await base.Edit_single_property_nullable_enum_with_int_converter_set_to_null(); + + AssertSql( +""" +@p0='[null]' (Nullable = false) (Size = 6) +@p1='[null]' (Nullable = false) (Size = 6) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestNullableEnumWithIntConverter', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestNullableEnumWithIntConverter', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_nullable_enum_with_converter_that_handles_nulls() + { + await base.Edit_single_property_nullable_enum_with_converter_that_handles_nulls(); + + AssertSql( +""" +@p0='["Three"]' (Nullable = false) (Size = 9) +@p1='["One"]' (Nullable = false) (Size = 7) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestNullableEnumWithConverterThatHandlesNulls', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestNullableEnumWithConverterThatHandlesNulls', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_nullable_enum_with_converter_that_handles_nulls_set_to_null() + { + await base.Edit_single_property_nullable_enum_with_converter_that_handles_nulls_set_to_null(); + + AssertSql( +""" +@p0='[null]' (Nullable = false) (Size = 6) +@p1='[null]' (Nullable = false) (Size = 6) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0].TestNullableEnumWithConverterThatHandlesNulls', json_extract(@p0, '$[0]')), "Reference" = json_set("Reference", '$.TestNullableEnumWithConverterThatHandlesNulls', json_extract(@p1, '$[0]')) +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_two_properties_on_same_entity_updates_the_entire_entity() + { + await base.Edit_two_properties_on_same_entity_updates_the_entire_entity(); + + AssertSql( +""" +@p0='{"TestBoolean":false,"TestByte":25,"TestCharacter":"h","TestDateTime":"2100-11-11T12:34:56","TestDateTimeOffset":"2200-11-11T12:34:56-05:00","TestDecimal":-123450.01,"TestDefaultString":"MyDefaultStringInCollection1","TestDouble":-1.2345,"TestEnum":"One","TestEnumWithIntConverter":"Two","TestGuid":"00000000-0000-0000-0000-000000000000","TestInt16":-12,"TestInt32":32,"TestInt64":64,"TestMaxLengthString":"Baz","TestNullableEnum":"One","TestNullableEnumWithConverterThatHandlesNulls":"Two","TestNullableEnumWithIntConverter":"Three","TestNullableInt32":90,"TestSignedByte":-18,"TestSingle":-1.4,"TestTimeSpan":"06:05:04.0030000","TestUnsignedInt16":12,"TestUnsignedInt32":12345,"TestUnsignedInt64":1234567867}' (Nullable = false) (Size = 710) +@p1='{"TestBoolean":true,"TestByte":255,"TestCharacter":"a","TestDateTime":"2000-01-01T12:34:56","TestDateTimeOffset":"2000-01-01T12:34:56-08:00","TestDecimal":-1234567890.01,"TestDefaultString":"MyDefaultStringInReference1","TestDouble":-1.23456789,"TestEnum":"One","TestEnumWithIntConverter":"Two","TestGuid":"12345678-1234-4321-7777-987654321000","TestInt16":-1234,"TestInt32":32,"TestInt64":64,"TestMaxLengthString":"Foo","TestNullableEnum":"One","TestNullableEnumWithConverterThatHandlesNulls":"Three","TestNullableEnumWithIntConverter":"Two","TestNullableInt32":78,"TestSignedByte":-128,"TestSingle":-1.234,"TestTimeSpan":"10:09:08.0070000","TestUnsignedInt16":1234,"TestUnsignedInt32":1234565789,"TestUnsignedInt64":1234567890123456789}' (Nullable = false) (Size = 738) +@p2='1' + +UPDATE "JsonEntitiesAllTypes" SET "Collection" = json_set("Collection", '$[0]', json(@p0)), "Reference" = @p1 +WHERE "Id" = @p2 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Collection", "j"."Reference" +FROM "JsonEntitiesAllTypes" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_a_scalar_property_and_reference_navigation_on_the_same_entity() + { + await base.Edit_a_scalar_property_and_reference_navigation_on_the_same_entity(); + + AssertSql( +""" +@p0='{"Date":"2100-01-01T00:00:00","Enum":"One","Fraction":523.532,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (Size = 227) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedReferenceBranch', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_a_scalar_property_and_collection_navigation_on_the_same_entity() + { + await base.Edit_a_scalar_property_and_collection_navigation_on_the_same_entity(); + + AssertSql( +""" +@p0='{"Date":"2100-01-01T00:00:00","Enum":"One","Fraction":523.532,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"edit"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}' (Nullable = false) (Size = 191) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedReferenceBranch', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_a_scalar_property_and_another_property_behind_reference_navigation_on_the_same_entity() + { + await base.Edit_a_scalar_property_and_another_property_behind_reference_navigation_on_the_same_entity(); + + AssertSql( +""" +@p0='{"Date":"2100-01-01T00:00:00","Enum":"One","Fraction":523.532,"NullableEnum":null,"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (Size = 227) +@p1='1' + +UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = json_set("OwnedReferenceRoot", '$.OwnedReferenceBranch', json(@p0)) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."EntityBasicId", "j"."Name", "j"."OwnedCollectionRoot", "j"."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS "j" +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_with_converter_bool_to_int_zero_one() + { + await base.Edit_single_property_with_converter_bool_to_int_zero_one(); + + AssertSql( +""" +@p0='[0]' (Nullable = false) (Size = 3) +@p1='1' + +UPDATE "JsonEntitiesConverters" SET "Reference" = json_set("Reference", '$.BoolConvertedToIntZeroOne', json_extract(@p0, '$[0]')) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Reference" +FROM "JsonEntitiesConverters" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_with_converter_bool_to_string_True_False() + { + await base.Edit_single_property_with_converter_bool_to_string_True_False(); + + AssertSql( +""" +@p0='["True"]' (Nullable = false) (Size = 8) +@p1='1' + +UPDATE "JsonEntitiesConverters" SET "Reference" = json_set("Reference", '$.BoolConvertedToStringTrueFalse', json_extract(@p0, '$[0]')) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Reference" +FROM "JsonEntitiesConverters" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_with_converter_bool_to_string_Y_N() + { + await base.Edit_single_property_with_converter_bool_to_string_Y_N(); + + AssertSql( +""" +@p0='["N"]' (Nullable = false) (Size = 5) +@p1='1' + +UPDATE "JsonEntitiesConverters" SET "Reference" = json_set("Reference", '$.BoolConvertedToStringYN', json_extract(@p0, '$[0]')) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Reference" +FROM "JsonEntitiesConverters" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_with_converter_int_zero_one_to_bool() + { + await base.Edit_single_property_with_converter_int_zero_one_to_bool(); + + AssertSql( +""" +@p0='["true"]' (Nullable = false) (Size = 8) +@p1='1' + +UPDATE "JsonEntitiesConverters" SET "Reference" = json_set("Reference", '$.IntZeroOneConvertedToBool', json(json_extract(@p0, '$[0]'))) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Reference" +FROM "JsonEntitiesConverters" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_with_converter_string_True_False_to_bool() + { + await base.Edit_single_property_with_converter_string_True_False_to_bool(); + + AssertSql( +""" +@p0='["false"]' (Nullable = false) (Size = 9) +@p1='1' + +UPDATE "JsonEntitiesConverters" SET "Reference" = json_set("Reference", '$.StringTrueFalseConvertedToBool', json(json_extract(@p0, '$[0]'))) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Reference" +FROM "JsonEntitiesConverters" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + public override async Task Edit_single_property_with_converter_string_Y_N_to_bool() + { + await base.Edit_single_property_with_converter_string_Y_N_to_bool(); + + AssertSql( +""" +@p0='["true"]' (Nullable = false) (Size = 8) +@p1='1' + +UPDATE "JsonEntitiesConverters" SET "Reference" = json_set("Reference", '$.StringYNConvertedToBool', json(json_extract(@p0, '$[0]'))) +WHERE "Id" = @p1 +RETURNING 1; +""", + // + """ +SELECT "j"."Id", "j"."Reference" +FROM "JsonEntitiesConverters" AS "j" +WHERE "j"."Id" = 1 +LIMIT 2 +"""); + } + + protected override void ClearLog() + => Fixture.TestSqlLoggerFactory.Clear(); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +}