From 1083e6fa594218bb561a1a107a9be9d0c9141cb0 Mon Sep 17 00:00:00 2001 From: maumar Date: Mon, 24 Oct 2022 18:39:02 -0700 Subject: [PATCH] Fix to #28881 - Consider removing unnecessary CASTs around JSON_VALUE We can remove cast for string scalars, other types maybe cause conversion issues in the generated sql. Fixes #28881 --- .../Internal/SqlServerQuerySqlGenerator.cs | 12 +- .../Query/JsonQueryTestBase.cs | 217 +++++++++- .../Query/JsonQuerySqlServerTest.cs | 370 ++++++++++++++++-- 3 files changed, 572 insertions(+), 27 deletions(-) diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index 39a3ec2731a..449b44c5e49 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -313,14 +313,22 @@ protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExp } else { - Sql.Append("CAST(JSON_VALUE("); + if (jsonScalarExpression.TypeMapping is not StringTypeMapping) + { + Sql.Append("CAST(JSON_VALUE("); + } + else + { + Sql.Append("JSON_VALUE("); + } } Visit(jsonScalarExpression.JsonColumn); Sql.Append($",'{string.Join("", jsonScalarExpression.Path.Select(e => e.ToString()))}')"); - if (jsonScalarExpression.Type != typeof(JsonElement)) + if (jsonScalarExpression.TypeMapping is not SqlServerJsonTypeMapping + && jsonScalarExpression.TypeMapping is not StringTypeMapping) { Sql.Append(" AS "); Sql.Append(jsonScalarExpression.TypeMapping!.StoreType); diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs index 844c2091978..e62c7031f2c 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs @@ -796,7 +796,6 @@ public virtual Task Json_all_types_projection_individual_properties(bool async) x.Reference.TestUnsignedInt16, x.Reference.TestUnsignedInt32, x.Reference.TestUnsignedInt64, - x.Reference.TestNullableInt32, x.Reference.TestEnum, x.Reference.TestEnumWithIntConverter, x.Reference.TestNullableEnum, @@ -804,6 +803,222 @@ public virtual Task Json_all_types_projection_individual_properties(bool async) x.Reference.TestNullableEnumWithConverterThatHandlesNulls, })); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_boolean(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestBoolean != false), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_byte(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestByte != 3), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_character(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestCharacter != 'z'), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_datetime(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestDateTime != new DateTime(2000, 1, 3)), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_datetimeoffset(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestDateTimeOffset != new DateTimeOffset(new DateTime(2000, 1, 4), new TimeSpan(3, 2, 0))), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_decimal(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestDecimal != 1.35M), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_double(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestDouble != 33.25), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_guid(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestGuid != new Guid()), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_int16(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestInt16 != 3), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_int32(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestInt32 != 33), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_int64(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestInt64 != 333), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_signedbyte(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestSignedByte != 100), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_single(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestSingle != 10.4f), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_timespan(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestTimeSpan != new TimeSpan(3, 2, 0)), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_unisgnedint16(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestUnsignedInt16 != 100), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_unsignedint32(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestUnsignedInt32 != 1000), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_unsignedint64(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestUnsignedInt64 != 10000), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_enum(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestEnum != JsonEnum.Two), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_enumwithintconverter(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestEnumWithIntConverter != JsonEnum.Three), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_nullableenum1(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestNullableEnum != JsonEnum.One), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_nullableenum2(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestNullableEnum != null), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_nullableenumwithconverterthathandlesnulls1(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestNullableEnumWithConverterThatHandlesNulls != JsonEnum.One), + entryCount: 6); + + [ConditionalTheory(Skip = "issue #29416")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_nullableenumwithconverterthathandlesnulls2(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestNullableEnumWithConverterThatHandlesNulls != null), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_nullableenumwithconverter1(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestNullableEnumWithIntConverter != JsonEnum.Two), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_nullableenumwithconverter2(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestNullableEnumWithIntConverter != null), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_nullableint321(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestNullableInt32 != 100), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_predicate_on_nullableint322(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(x => x.Reference.TestNullableInt32 != null), + entryCount: 3); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task FromSql_on_entity_with_json_basic(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs index e09786a2899..d374b04be37 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs @@ -110,7 +110,7 @@ public override async Task Basic_json_projection_scalar(bool async) AssertSql( """ -SELECT CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) +SELECT JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') FROM [JsonEntitiesBasic] AS [j] """); } @@ -123,7 +123,7 @@ public override async Task Json_scalar_length(bool async) """ SELECT [j].[Name] FROM [JsonEntitiesBasic] AS [j] -WHERE CAST(LEN(CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max))) AS int) > 2 +WHERE CAST(LEN(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name')) AS int) > 2 """); } @@ -133,7 +133,7 @@ public override async Task Basic_json_projection_enum_inside_json_entity(bool as AssertSql( """ -SELECT [j].[Id], CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.Enum') AS nvarchar(max)) AS [Enum] +SELECT [j].[Id], JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.Enum') AS [Enum] FROM [JsonEntitiesBasic] AS [j] """); } @@ -155,7 +155,7 @@ public override async Task Json_projection_with_deduplication(bool async) AssertSql( """ -SELECT [j].[Id], [j].[EntityBasicId], [j].[Name], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot], CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething') AS nvarchar(max)) +SELECT [j].[Id], [j].[EntityBasicId], [j].[Name], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot], JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething') FROM [JsonEntitiesBasic] AS [j] """); } @@ -195,7 +195,7 @@ SELECT CAST(LEN([t0].[c]) AS int) FROM ( SELECT DISTINCT [t].[c] FROM ( - SELECT TOP(@__p_0) CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething') AS nvarchar(max)) AS [c] + SELECT TOP(@__p_0) JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething') AS [c] FROM [JsonEntitiesBasic] AS [j] ORDER BY [j].[Id] ) AS [t] @@ -291,7 +291,7 @@ FROM [JsonEntitiesBasic] AS [j] ORDER BY [j].[Id] ) AS [t] ) AS [t0] - ORDER BY CAST(JSON_VALUE([t0].[c0],'$.Name') AS nvarchar(max)) + ORDER BY JSON_VALUE([t0].[c0],'$.Name') ) AS [t1] ) AS [t2] """); @@ -318,7 +318,7 @@ FROM [JsonEntitiesBasic] AS [j] ORDER BY [j].[Id] ) AS [t] ) AS [t0] - ORDER BY CAST(JSON_VALUE([t0].[c0],'$.Name') AS nvarchar(max)) + ORDER BY JSON_VALUE([t0].[c0],'$.Name') ) AS [t1] ) AS [t2] """); @@ -332,7 +332,7 @@ public override async Task Json_subquery_reference_pushdown_property(bool async) """ @__p_0='10' -SELECT CAST(JSON_VALUE([t0].[c],'$.SomethingSomething') AS nvarchar(max)) +SELECT JSON_VALUE([t0].[c],'$.SomethingSomething') FROM ( SELECT DISTINCT [t].[c] AS [c], [t].[Id] FROM ( @@ -395,7 +395,7 @@ public override async Task Custom_naming_projection_everything(bool async) AssertSql( """ -SELECT [j].[Id], [j].[Title], [j].[json_collection_custom_naming], [j].[json_reference_custom_naming], CAST(JSON_VALUE([j].[json_reference_custom_naming],'$.CustomName') AS nvarchar(max)), CAST(JSON_VALUE([j].[json_reference_custom_naming],'$.CustomOwnedReferenceBranch.CustomFraction') AS float) +SELECT [j].[Id], [j].[Title], [j].[json_collection_custom_naming], [j].[json_reference_custom_naming], JSON_VALUE([j].[json_reference_custom_naming],'$.CustomName'), CAST(JSON_VALUE([j].[json_reference_custom_naming],'$.CustomOwnedReferenceBranch.CustomFraction') AS float) FROM [JsonEntitiesCustomNaming] AS [j] """); } @@ -484,7 +484,7 @@ public override async Task Project_json_entity_FirstOrDefault_subquery_deduplica SELECT [t].[c], [t].[Id], [t].[c0], [t].[Id0], [t].[c1], [t].[c2], [t].[c3], [t].[c4] FROM [JsonEntitiesBasic] AS [j] OUTER APPLY ( - SELECT TOP(1) JSON_QUERY([j].[OwnedReferenceRoot],'$.OwnedCollectionBranch') AS [c], [j].[Id], [j0].[OwnedReferenceRoot] AS [c0], [j0].[Id] AS [Id0], JSON_QUERY([j0].[OwnedReferenceRoot],'$.OwnedReferenceBranch') AS [c1], CAST(JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [c2], CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.Enum') AS nvarchar(max)) AS [c3], 1 AS [c4] + SELECT TOP(1) JSON_QUERY([j].[OwnedReferenceRoot],'$.OwnedCollectionBranch') AS [c], [j].[Id], [j0].[OwnedReferenceRoot] AS [c0], [j0].[Id] AS [Id0], JSON_QUERY([j0].[OwnedReferenceRoot],'$.OwnedReferenceBranch') AS [c1], JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS [c2], JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.Enum') AS [c3], 1 AS [c4] FROM [JsonEntitiesBasic] AS [j0] ORDER BY [j0].[Id] ) AS [t] @@ -501,7 +501,7 @@ public override async Task Project_json_entity_FirstOrDefault_subquery_deduplica SELECT [t].[c], [t].[Id], [t].[c0], [t].[Id0], [t].[c1], [t].[c2], [t].[c3], [t].[c4] FROM [JsonEntitiesBasic] AS [j] OUTER APPLY ( - SELECT TOP(1) JSON_QUERY([j].[OwnedReferenceRoot],'$.OwnedCollectionBranch') AS [c], [j].[Id], [j0].[OwnedReferenceRoot] AS [c0], [j0].[Id] AS [Id0], JSON_QUERY([j0].[OwnedReferenceRoot],'$.OwnedReferenceBranch') AS [c1], CAST(JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [c2], CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.Enum') AS nvarchar(max)) AS [c3], 1 AS [c4] + SELECT TOP(1) JSON_QUERY([j].[OwnedReferenceRoot],'$.OwnedCollectionBranch') AS [c], [j].[Id], [j0].[OwnedReferenceRoot] AS [c0], [j0].[Id] AS [Id0], JSON_QUERY([j0].[OwnedReferenceRoot],'$.OwnedReferenceBranch') AS [c1], JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS [c2], JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.Enum') AS [c3], 1 AS [c4] FROM [JsonEntitiesBasic] AS [j0] ORDER BY [j0].[Id] ) AS [t] @@ -608,7 +608,7 @@ public override async Task Json_scalar_required_null_semantics(bool async) """ SELECT [j].[Name] FROM [JsonEntitiesBasic] AS [j] -WHERE CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Number') AS int) <> CAST(LEN(CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max))) AS int) OR (CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) IS NULL) +WHERE CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Number') AS int) <> CAST(LEN(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name')) AS int) OR (JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') IS NULL) """); } @@ -620,7 +620,7 @@ public override async Task Json_scalar_optional_null_semantics(bool async) """ SELECT [j].[Name] FROM [JsonEntitiesBasic] AS [j] -WHERE CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) = CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething') AS nvarchar(max)) OR ((CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) IS NULL) AND (CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething') AS nvarchar(max)) IS NULL)) +WHERE JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') = JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething') OR ((JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') IS NULL) AND (JSON_VALUE([j].[OwnedReferenceRoot],'$.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething') IS NULL)) """); } @@ -632,7 +632,7 @@ public override async Task Group_by_on_json_scalar(bool async) """ SELECT [t].[Key], COUNT(*) AS [Count] FROM ( - SELECT CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j] ) AS [t] GROUP BY [t].[Key] @@ -649,7 +649,7 @@ public override async Task Group_by_First_on_json_scalar(bool async) FROM ( SELECT [t].[Key] FROM ( - SELECT CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j] ) AS [t] GROUP BY [t].[Key] @@ -659,7 +659,7 @@ LEFT JOIN ( FROM ( SELECT [t3].[Id], [t3].[EntityBasicId], [t3].[Name], [t3].[c] AS [c], [t3].[c0] AS [c0], [t3].[Key], ROW_NUMBER() OVER(PARTITION BY [t3].[Key] ORDER BY [t3].[Id]) AS [row] FROM ( - SELECT [j0].[Id], [j0].[EntityBasicId], [j0].[Name], [j0].[OwnedCollectionRoot] AS [c], [j0].[OwnedReferenceRoot] AS [c0], CAST(JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT [j0].[Id], [j0].[EntityBasicId], [j0].[Name], [j0].[OwnedCollectionRoot] AS [c], [j0].[OwnedReferenceRoot] AS [c0], JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j0] ) AS [t3] ) AS [t2] @@ -678,7 +678,7 @@ public override async Task Group_by_FirstOrDefault_on_json_scalar(bool async) FROM ( SELECT [t].[Key] FROM ( - SELECT CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j] ) AS [t] GROUP BY [t].[Key] @@ -688,7 +688,7 @@ LEFT JOIN ( FROM ( SELECT [t3].[Id], [t3].[EntityBasicId], [t3].[Name], [t3].[c] AS [c], [t3].[c0] AS [c0], [t3].[Key], ROW_NUMBER() OVER(PARTITION BY [t3].[Key] ORDER BY [t3].[Id]) AS [row] FROM ( - SELECT [j0].[Id], [j0].[EntityBasicId], [j0].[Name], [j0].[OwnedCollectionRoot] AS [c], [j0].[OwnedReferenceRoot] AS [c0], CAST(JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT [j0].[Id], [j0].[EntityBasicId], [j0].[Name], [j0].[OwnedCollectionRoot] AS [c], [j0].[OwnedReferenceRoot] AS [c0], JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j0] ) AS [t3] ) AS [t2] @@ -707,7 +707,7 @@ public override async Task Group_by_Skip_Take_on_json_scalar(bool async) FROM ( SELECT [t].[Key] FROM ( - SELECT CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j] ) AS [t] GROUP BY [t].[Key] @@ -717,7 +717,7 @@ LEFT JOIN ( FROM ( SELECT [t3].[Id], [t3].[EntityBasicId], [t3].[Name], [t3].[c] AS [c], [t3].[c0] AS [c0], [t3].[Key], ROW_NUMBER() OVER(PARTITION BY [t3].[Key] ORDER BY [t3].[Id]) AS [row] FROM ( - SELECT [j0].[Id], [j0].[EntityBasicId], [j0].[Name], [j0].[OwnedCollectionRoot] AS [c], [j0].[OwnedReferenceRoot] AS [c0], CAST(JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT [j0].[Id], [j0].[EntityBasicId], [j0].[Name], [j0].[OwnedCollectionRoot] AS [c], [j0].[OwnedReferenceRoot] AS [c0], JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j0] ) AS [t3] ) AS [t2] @@ -742,14 +742,14 @@ public override async Task Group_by_json_scalar_Skip_First_project_json_scalar(b AssertSql( """ SELECT ( - SELECT TOP(1) CAST(JSON_VALUE([t0].[c0],'$.OwnedReferenceBranch.Enum') AS nvarchar(max)) + SELECT TOP(1) JSON_VALUE([t0].[c0],'$.OwnedReferenceBranch.Enum') FROM ( - SELECT [j0].[Id], [j0].[EntityBasicId], [j0].[Name], [j0].[OwnedCollectionRoot] AS [c], [j0].[OwnedReferenceRoot] AS [c0], CAST(JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT [j0].[Id], [j0].[EntityBasicId], [j0].[Name], [j0].[OwnedCollectionRoot] AS [c], [j0].[OwnedReferenceRoot] AS [c0], JSON_VALUE([j0].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j0] ) AS [t0] WHERE [t].[Key] = [t0].[Key] OR (([t].[Key] IS NULL) AND ([t0].[Key] IS NULL))) FROM ( - SELECT CAST(JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS nvarchar(max)) AS [Key] + SELECT JSON_VALUE([j].[OwnedReferenceRoot],'$.Name') AS [Key] FROM [JsonEntitiesBasic] AS [j] ) AS [t] GROUP BY [t].[Key] @@ -836,11 +836,333 @@ public override async Task Json_all_types_projection_individual_properties(bool AssertSql( """ -SELECT CAST(JSON_VALUE([j].[Reference],'$.TestBoolean') AS bit) AS [TestBoolean], CAST(JSON_VALUE([j].[Reference],'$.TestByte') AS tinyint) AS [TestByte], CAST(JSON_VALUE([j].[Reference],'$.TestCharacter') AS nvarchar(1)) AS [TestCharacter], CAST(JSON_VALUE([j].[Reference],'$.TestDateTime') AS datetime2) AS [TestDateTime], CAST(JSON_VALUE([j].[Reference],'$.TestDateTimeOffset') AS datetimeoffset) AS [TestDateTimeOffset], CAST(JSON_VALUE([j].[Reference],'$.TestDecimal') AS decimal(18,3)) AS [TestDecimal], CAST(JSON_VALUE([j].[Reference],'$.TestDouble') AS float) AS [TestDouble], CAST(JSON_VALUE([j].[Reference],'$.TestGuid') AS uniqueidentifier) AS [TestGuid], CAST(JSON_VALUE([j].[Reference],'$.TestInt16') AS smallint) AS [TestInt16], CAST(JSON_VALUE([j].[Reference],'$.TestInt32') AS int) AS [TestInt32], CAST(JSON_VALUE([j].[Reference],'$.TestInt64') AS bigint) AS [TestInt64], CAST(JSON_VALUE([j].[Reference],'$.TestSignedByte') AS smallint) AS [TestSignedByte], CAST(JSON_VALUE([j].[Reference],'$.TestSingle') AS real) AS [TestSingle], CAST(JSON_VALUE([j].[Reference],'$.TestTimeSpan') AS time) AS [TestTimeSpan], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt16') AS int) AS [TestUnsignedInt16], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt32') AS bigint) AS [TestUnsignedInt32], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt64') AS decimal(20,0)) AS [TestUnsignedInt64], CAST(JSON_VALUE([j].[Reference],'$.TestNullableInt32') AS int) AS [TestNullableInt32], CAST(JSON_VALUE([j].[Reference],'$.TestEnum') AS nvarchar(max)) AS [TestEnum], CAST(JSON_VALUE([j].[Reference],'$.TestEnumWithIntConverter') AS int) AS [TestEnumWithIntConverter], CAST(JSON_VALUE([j].[Reference],'$.TestNullableEnum') AS nvarchar(max)) AS [TestNullableEnum], CAST(JSON_VALUE([j].[Reference],'$.TestNullableEnumWithIntConverter') AS int) AS [TestNullableEnumWithIntConverter], CAST(JSON_VALUE([j].[Reference],'$.TestNullableEnumWithConverterThatHandlesNulls') AS nvarchar(max)) AS [TestNullableEnumWithConverterThatHandlesNulls] +SELECT CAST(JSON_VALUE([j].[Reference],'$.TestBoolean') AS bit) AS [TestBoolean], CAST(JSON_VALUE([j].[Reference],'$.TestByte') AS tinyint) AS [TestByte], JSON_VALUE([j].[Reference],'$.TestCharacter') AS [TestCharacter], CAST(JSON_VALUE([j].[Reference],'$.TestDateTime') AS datetime2) AS [TestDateTime], CAST(JSON_VALUE([j].[Reference],'$.TestDateTimeOffset') AS datetimeoffset) AS [TestDateTimeOffset], CAST(JSON_VALUE([j].[Reference],'$.TestDecimal') AS decimal(18,3)) AS [TestDecimal], CAST(JSON_VALUE([j].[Reference],'$.TestDouble') AS float) AS [TestDouble], CAST(JSON_VALUE([j].[Reference],'$.TestGuid') AS uniqueidentifier) AS [TestGuid], CAST(JSON_VALUE([j].[Reference],'$.TestInt16') AS smallint) AS [TestInt16], CAST(JSON_VALUE([j].[Reference],'$.TestInt32') AS int) AS [TestInt32], CAST(JSON_VALUE([j].[Reference],'$.TestInt64') AS bigint) AS [TestInt64], CAST(JSON_VALUE([j].[Reference],'$.TestSignedByte') AS smallint) AS [TestSignedByte], CAST(JSON_VALUE([j].[Reference],'$.TestSingle') AS real) AS [TestSingle], CAST(JSON_VALUE([j].[Reference],'$.TestTimeSpan') AS time) AS [TestTimeSpan], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt16') AS int) AS [TestUnsignedInt16], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt32') AS bigint) AS [TestUnsignedInt32], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt64') AS decimal(20,0)) AS [TestUnsignedInt64], JSON_VALUE([j].[Reference],'$.TestEnum') AS [TestEnum], CAST(JSON_VALUE([j].[Reference],'$.TestEnumWithIntConverter') AS int) AS [TestEnumWithIntConverter], JSON_VALUE([j].[Reference],'$.TestNullableEnum') AS [TestNullableEnum], CAST(JSON_VALUE([j].[Reference],'$.TestNullableEnumWithIntConverter') AS int) AS [TestNullableEnumWithIntConverter], JSON_VALUE([j].[Reference],'$.TestNullableEnumWithConverterThatHandlesNulls') AS [TestNullableEnumWithConverterThatHandlesNulls] FROM [JsonEntitiesAllTypes] AS [j] """); } + public override async Task Json_predicate_on_boolean(bool async) + { + await base.Json_predicate_on_boolean(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestBoolean') AS bit) <> CAST(0 AS bit) OR (CAST(JSON_VALUE([j].[Reference],'$.TestBoolean') AS bit) IS NULL) +"""); + } + + public override async Task Json_predicate_on_byte(bool async) + { + await base.Json_predicate_on_byte(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestByte') AS tinyint) <> CAST(3 AS tinyint) OR (CAST(JSON_VALUE([j].[Reference],'$.TestByte') AS tinyint) IS NULL) +"""); + } + + public override async Task Json_predicate_on_character(bool async) + { + await base.Json_predicate_on_character(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE JSON_VALUE([j].[Reference],'$.TestCharacter') <> N'z' OR (JSON_VALUE([j].[Reference],'$.TestCharacter') IS NULL) +"""); + } + + public override async Task Json_predicate_on_datetime(bool async) + { + await base.Json_predicate_on_datetime(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestDateTime') AS datetime2) <> '2000-01-03T00:00:00.0000000' OR (CAST(JSON_VALUE([j].[Reference],'$.TestDateTime') AS datetime2) IS NULL) +"""); + } + + public override async Task Json_predicate_on_datetimeoffset(bool async) + { + await base.Json_predicate_on_datetimeoffset(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestDateTimeOffset') AS datetimeoffset) <> '2000-01-04T00:00:00.0000000+03:02' OR (CAST(JSON_VALUE([j].[Reference],'$.TestDateTimeOffset') AS datetimeoffset) IS NULL) +"""); + } + + public override async Task Json_predicate_on_decimal(bool async) + { + await base.Json_predicate_on_decimal(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestDecimal') AS decimal(18,3)) <> 1.35 OR (CAST(JSON_VALUE([j].[Reference],'$.TestDecimal') AS decimal(18,3)) IS NULL) +"""); + } + + public override async Task Json_predicate_on_double(bool async) + { + await base.Json_predicate_on_double(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestDouble') AS float) <> 33.25E0 OR (CAST(JSON_VALUE([j].[Reference],'$.TestDouble') AS float) IS NULL) +"""); + } + + public override async Task Json_predicate_on_enum(bool async) + { + await base.Json_predicate_on_enum(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE JSON_VALUE([j].[Reference],'$.TestEnum') <> N'Two' OR (JSON_VALUE([j].[Reference],'$.TestEnum') IS NULL) +"""); + } + + public override async Task Json_predicate_on_enumwithintconverter(bool async) + { + await base.Json_predicate_on_enumwithintconverter(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestEnumWithIntConverter') AS int) <> 2 OR (CAST(JSON_VALUE([j].[Reference],'$.TestEnumWithIntConverter') AS int) IS NULL) +"""); + } + + public override async Task Json_predicate_on_guid(bool async) + { + await base.Json_predicate_on_guid(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestGuid') AS uniqueidentifier) <> '00000000-0000-0000-0000-000000000000' OR (CAST(JSON_VALUE([j].[Reference],'$.TestGuid') AS uniqueidentifier) IS NULL) +"""); + } + + public override async Task Json_predicate_on_int16(bool async) + { + await base.Json_predicate_on_int16(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestInt16') AS smallint) <> CAST(3 AS smallint) OR (CAST(JSON_VALUE([j].[Reference],'$.TestInt16') AS smallint) IS NULL) +"""); + } + + public override async Task Json_predicate_on_int32(bool async) + { + await base.Json_predicate_on_int32(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestInt32') AS int) <> 33 OR (CAST(JSON_VALUE([j].[Reference],'$.TestInt32') AS int) IS NULL) +"""); + } + + public override async Task Json_predicate_on_int64(bool async) + { + await base.Json_predicate_on_int64(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestInt64') AS bigint) <> CAST(333 AS bigint) OR (CAST(JSON_VALUE([j].[Reference],'$.TestInt64') AS bigint) IS NULL) +"""); + } + + public override async Task Json_predicate_on_nullableenum1(bool async) + { + await base.Json_predicate_on_nullableenum1(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE JSON_VALUE([j].[Reference],'$.TestNullableEnum') <> N'One' OR (JSON_VALUE([j].[Reference],'$.TestNullableEnum') IS NULL) +"""); + } + + public override async Task Json_predicate_on_nullableenum2(bool async) + { + await base.Json_predicate_on_nullableenum2(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE JSON_VALUE([j].[Reference],'$.TestNullableEnum') IS NOT NULL +"""); + } + + public override async Task Json_predicate_on_nullableenumwithconverter1(bool async) + { + await base.Json_predicate_on_nullableenumwithconverter1(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestNullableEnumWithIntConverter') AS int) <> 1 OR (CAST(JSON_VALUE([j].[Reference],'$.TestNullableEnumWithIntConverter') AS int) IS NULL) +"""); + } + + public override async Task Json_predicate_on_nullableenumwithconverter2(bool async) + { + await base.Json_predicate_on_nullableenumwithconverter2(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestNullableEnumWithIntConverter') AS int) IS NOT NULL +"""); + } + + public override async Task Json_predicate_on_nullableenumwithconverterthathandlesnulls1(bool async) + { + await base.Json_predicate_on_nullableenumwithconverterthathandlesnulls1(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE JSON_VALUE([j].[Reference],'$.TestNullableEnumWithConverterThatHandlesNulls') <> N'One' OR (JSON_VALUE([j].[Reference],'$.TestNullableEnumWithConverterThatHandlesNulls') IS NULL) +"""); + } + + public override async Task Json_predicate_on_nullableenumwithconverterthathandlesnulls2(bool async) + { + await base.Json_predicate_on_nullableenumwithconverterthathandlesnulls2(async); + + AssertSql( +""" +x +"""); + } + + public override async Task Json_predicate_on_nullableint321(bool async) + { + await base.Json_predicate_on_nullableint321(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestNullableInt32') AS int) <> 100 OR (CAST(JSON_VALUE([j].[Reference],'$.TestNullableInt32') AS int) IS NULL) +"""); + } + + public override async Task Json_predicate_on_nullableint322(bool async) + { + await base.Json_predicate_on_nullableint322(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestNullableInt32') AS int) IS NOT NULL +"""); + } + + public override async Task Json_predicate_on_signedbyte(bool async) + { + await base.Json_predicate_on_signedbyte(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestSignedByte') AS smallint) <> CAST(100 AS smallint) OR (CAST(JSON_VALUE([j].[Reference],'$.TestSignedByte') AS smallint) IS NULL) +"""); + } + + public override async Task Json_predicate_on_single(bool async) + { + await base.Json_predicate_on_single(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestSingle') AS real) <> CAST(10.4 AS real) OR (CAST(JSON_VALUE([j].[Reference],'$.TestSingle') AS real) IS NULL) +"""); + } + + public override async Task Json_predicate_on_timespan(bool async) + { + await base.Json_predicate_on_timespan(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestTimeSpan') AS time) <> '03:02:00' OR (CAST(JSON_VALUE([j].[Reference],'$.TestTimeSpan') AS time) IS NULL) +"""); + } + + public override async Task Json_predicate_on_unisgnedint16(bool async) + { + await base.Json_predicate_on_unisgnedint16(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt16') AS int) <> 100 OR (CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt16') AS int) IS NULL) +"""); + } + + public override async Task Json_predicate_on_unsignedint32(bool async) + { + await base.Json_predicate_on_unsignedint32(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt32') AS bigint) <> CAST(1000 AS bigint) OR (CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt32') AS bigint) IS NULL) +"""); + } + + public override async Task Json_predicate_on_unsignedint64(bool async) + { + await base.Json_predicate_on_unsignedint64(async); + + AssertSql( +""" +SELECT [j].[Id], [j].[Collection], [j].[Reference] +FROM [JsonEntitiesAllTypes] AS [j] +WHERE CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt64') AS decimal(20,0)) <> 10000.0 OR (CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt64') AS decimal(20,0)) IS NULL) +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_basic(bool async)