Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/9.0] Fix type mapping management for JsonScalarExpression #34663

Merged
merged 1 commit into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ protected override Expression VisitConditional(ConditionalExpression conditional
|| TranslationFailed(conditionalExpression.IfTrue, ifTrue, out var sqlIfTrue)
|| TranslationFailed(conditionalExpression.IfFalse, ifFalse, out var sqlIfFalse)
? QueryCompilationContext.NotTranslatedExpression
: _sqlExpressionFactory.Case(new[] { new CaseWhenClause(sqlTest!, sqlIfTrue!) }, sqlIfFalse);
: _sqlExpressionFactory.Case([new CaseWhenClause(sqlTest!, sqlIfTrue!)], sqlIfFalse);
}

/// <inheritdoc />
Expand Down
22 changes: 13 additions & 9 deletions src/EFCore.Relational/Query/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ private InExpression ApplyTypeMappingOnIn(InExpression inExpression)

private SqlExpression ApplyTypeMappingOnJsonScalar(
JsonScalarExpression jsonScalarExpression,
RelationalTypeMapping? typeMapping)
RelationalTypeMapping? elementMapping)
{
if (jsonScalarExpression is not { Json: var array, Path: [{ ArrayIndex: { } index }] })
{
Expand All @@ -369,24 +369,28 @@ private SqlExpression ApplyTypeMappingOnJsonScalar(
var newPath = indexWithTypeMapping == index ? jsonScalarExpression.Path : [new PathSegment(indexWithTypeMapping)];

// If a type mapping is being applied from the outside, it applies to the element resulting from the array indexing operation;
// we can infer the array's type mapping from it. Otherwise there's nothing to do but apply the default type mapping to the array.
if (typeMapping is null)
// we can infer the array's type mapping from it.
if (elementMapping is null)
{
return new JsonScalarExpression(
ApplyDefaultTypeMapping(array),
array,
newPath,
jsonScalarExpression.Type,
_typeMappingSource.FindMapping(jsonScalarExpression.Type, Dependencies.Model),
jsonScalarExpression.TypeMapping,
jsonScalarExpression.IsNullable);
}

// TODO: blocked on #30730: we need to be able to construct a JSON collection type mapping based on the element's.
// For now, hacking to apply the default type mapping instead.
// Resolve the array type mapping for the given element mapping.
if (_typeMappingSource.FindMapping(array.Type, Dependencies.Model, elementMapping) is not RelationalTypeMapping arrayMapping)
{
throw new UnreachableException($"Couldn't find collection type mapping for element type mapping {elementMapping.ClrType.Name}");
}

return new JsonScalarExpression(
ApplyDefaultTypeMapping(array), // Hack, until #30730
ApplyTypeMapping(array, arrayMapping),
newPath,
jsonScalarExpression.Type,
typeMapping,
elementMapping,
jsonScalarExpression.IsNullable);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,27 @@ FROM root c
""");
});

public override async Task Parameter_collection_with_type_inference_for_JsonScalarExpression(bool async)
{
// Always throws for sync.
if (async)
{
// Member indexer (c.Array[c.SomeMember]) isn't supported by Cosmos
var exception = await Assert.ThrowsAsync<CosmosException>(
() => base.Parameter_collection_with_type_inference_for_JsonScalarExpression(async));

Assert.Equal(HttpStatusCode.BadRequest, exception.StatusCode);

AssertSql(
"""
@__values_0='["one","two"]'

SELECT VALUE ((c["Id"] != 0) ? @__values_0[(c["Int"] % 2)] : "foo")
FROM root c
""");
}
}

public override Task Column_collection_Union_parameter_collection(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -925,13 +925,24 @@ public virtual Task Inline_collection_Join_ordered_column_collection(bool async)
[MemberData(nameof(IsAsyncData))]
public virtual Task Parameter_collection_Concat_column_collection(bool async)
{
var ints = new[] { 11, 111 };
int[] ints = [11, 111];

return AssertQuery(
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => ints.Concat(c.Ints).Count() == 2));
}

[ConditionalTheory] // #33582
[MemberData(nameof(IsAsyncData))]
public virtual Task Parameter_collection_with_type_inference_for_JsonScalarExpression(bool async)
{
string[] values = ["one", "two"];

return AssertQuery(
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Select(c => c.Id != 0 ? values[c.Int % 2] : "foo"));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Column_collection_Union_parameter_collection(bool async)
Expand Down Expand Up @@ -1557,8 +1568,8 @@ private static IReadOnlyList<PrimitiveCollectionsEntity> CreatePrimitiveArrayEnt
DateTimes = [],
Bools = [],
Enums = [],
NullableInts = Array.Empty<int?>(),
NullableStrings = Array.Empty<string?>()
NullableInts = [],
NullableStrings = []
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,9 @@ public override Task Inline_collection_Join_ordered_column_collection(bool async
public override Task Parameter_collection_Concat_column_collection(bool async)
=> AssertCompatibilityLevelTooLow(() => base.Parameter_collection_Concat_column_collection(async));

public override Task Parameter_collection_with_type_inference_for_JsonScalarExpression(bool async)
=> AssertCompatibilityLevelTooLow(() => base.Parameter_collection_Concat_column_collection(async));

public override Task Column_collection_Union_parameter_collection(bool async)
=> AssertCompatibilityLevelTooLow(() => base.Column_collection_Union_parameter_collection(async));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1507,6 +1507,22 @@ FROM OPENJSON([p].[Ints]) AS [i0]
""");
}

public override async Task Parameter_collection_with_type_inference_for_JsonScalarExpression(bool async)
{
await base.Parameter_collection_with_type_inference_for_JsonScalarExpression(async);

AssertSql(
"""
@__values_0='["one","two"]' (Size = 4000)

SELECT CASE
WHEN [p].[Id] <> 0 THEN JSON_VALUE(@__values_0, '$[' + CAST([p].[Int] % 2 AS nvarchar(max)) + ']')
ELSE N'foo'
END
FROM [PrimitiveCollectionsEntity] AS [p]
""");
}

public override async Task Column_collection_Union_parameter_collection(bool async)
{
await base.Column_collection_Union_parameter_collection(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,22 @@ FROM OPENJSON(CAST([p].[Ints] AS nvarchar(max))) AS [i0]
""");
}

public override async Task Parameter_collection_with_type_inference_for_JsonScalarExpression(bool async)
{
await base.Parameter_collection_with_type_inference_for_JsonScalarExpression(async);

AssertSql(
"""
@__values_0='["one","two"]' (Size = 4000)

SELECT CASE
WHEN [p].[Id] <> 0 THEN JSON_VALUE(@__values_0, '$[' + CAST([p].[Int] % 2 AS nvarchar(max)) + ']')
ELSE N'foo'
END
FROM [PrimitiveCollectionsEntity] AS [p]
""");
}

public override async Task Column_collection_Union_parameter_collection(bool async)
{
await base.Column_collection_Union_parameter_collection(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,23 @@ FROM OPENJSON([p].[Ints]) AS [i0]
""");
}

[SqlServerCondition(SqlServerCondition.SupportsJsonPathExpressions)]
public override async Task Parameter_collection_with_type_inference_for_JsonScalarExpression(bool async)
{
await base.Parameter_collection_with_type_inference_for_JsonScalarExpression(async);

AssertSql(
"""
@__values_0='["one","two"]' (Size = 4000)

SELECT CASE
WHEN [p].[Id] <> 0 THEN JSON_VALUE(@__values_0, '$[' + CAST([p].[Int] % 2 AS nvarchar(max)) + ']')
ELSE N'foo'
END
FROM [PrimitiveCollectionsEntity] AS [p]
""");
}

public override async Task Column_collection_Union_parameter_collection(bool async)
{
await base.Column_collection_Union_parameter_collection(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,22 @@ FROM json_each("p"."Ints") AS "i0"
""");
}

public override async Task Parameter_collection_with_type_inference_for_JsonScalarExpression(bool async)
{
await base.Parameter_collection_with_type_inference_for_JsonScalarExpression(async);

AssertSql(
"""
@__values_0='["one","two"]' (Size = 13)

SELECT CASE
WHEN "p"."Id" <> 0 THEN @__values_0 ->> ("p"."Int" % 2)
ELSE 'foo'
END
FROM "PrimitiveCollectionsEntity" AS "p"
""");
}

public override async Task Column_collection_Union_parameter_collection(bool async)
{
await base.Column_collection_Union_parameter_collection(async);
Expand Down