Skip to content

Commit

Permalink
Work on type mapping inference for string concatenation
Browse files Browse the repository at this point in the history
Fixes dotnet#32325, see also dotnet#32333
  • Loading branch information
roji committed Dec 2, 2023
1 parent 50a8ae5 commit 0de1263
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/EFCore.Relational/Query/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,40 @@ private SqlExpression ApplyTypeMappingOnLike(LikeExpression likeExpression)
}

case ExpressionType.Add:
inferredTypeMapping = typeMapping;

if (inferredTypeMapping is null)
{
// Infer null size (nvarchar(max)) if either side has no size.
// Note that for constants, we could instead look at the value length; but that requires we know the type mappings which
// can have a size (string/byte[], maybe something else?).
var inferredSize = left.TypeMapping?.Size is int leftSize && right.TypeMapping?.Size is int rightSize
? leftSize + rightSize
: (int?)null;

// Unless both sides are fixed length, the result isn't fixed length.
var inferredFixedLength = left.TypeMapping?.IsFixedLength is true && right.TypeMapping?.IsFixedLength is true;
// Default to Unicode unless both sides are non-unicode.
var inferredUnicode = !(left.TypeMapping?.IsUnicode is false && right.TypeMapping?.IsUnicode is false);

var baseTypeMapping = left.TypeMapping
?? right.TypeMapping
?? ApplyDefaultTypeMapping(left).TypeMapping
?? throw new InvalidOperationException("Couldn't find type mapping");

inferredTypeMapping = baseTypeMapping.Size == inferredSize
&& baseTypeMapping.IsFixedLength == inferredFixedLength
&& baseTypeMapping.IsUnicode == inferredUnicode
? baseTypeMapping
: _typeMappingSource.FindMapping(
baseTypeMapping.ClrType, storeTypeName: null, keyOrIndex: false, inferredUnicode, inferredSize,
rowVersion: false, inferredFixedLength, baseTypeMapping.Precision, baseTypeMapping.Scale);
}

resultType = inferredTypeMapping?.ClrType ?? left.Type;
resultTypeMapping = inferredTypeMapping;
break;

case ExpressionType.Subtract:
case ExpressionType.Multiply:
case ExpressionType.Divide:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5659,4 +5659,26 @@ public virtual Task Subquery_with_navigation_inside_inline_collection(bool async
=> AssertQuery(
async,
ss => ss.Set<Customer>().Where(c => new[] { 100, c.Orders.Count }.Sum() > 101));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Contains_over_concatenated_columns_with_different_sizes(bool async)
{
var data = new[] { "ALFKI" + "Alfreds Futterkiste", "ANATR" + "Ana Trujillo Emparedados y helados" };

return AssertQuery(
async,
ss => ss.Set<Customer>().Where(c => data.Contains(c.CustomerID + c.CompanyName)));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Contains_over_concatenated_column_and_constant(bool async)
{
var data = new[] { "ALFKI" + "SomeConstant", "ANATR" + "SomeConstant" };

return AssertQuery(
async,
ss => ss.Set<Customer>().Where(c => data.Contains(c.CustomerID + "SomeConstant")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7277,6 +7277,40 @@ SELECT COUNT(*)
""");
}

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

AssertSql(
"""
@__data_0='["ALFKIAlfreds Futterkiste","ANATRAna Trujillo Emparedados y helados"]' (Size = 4000)

SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
FROM [Customers] AS [c]
WHERE [c].[CustomerID] + [c].[CompanyName] IN (
SELECT [d].[value]
FROM OPENJSON(@__data_0) WITH ([value] nvarchar(45) '$') AS [d]
)
""");
}

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

AssertSql(
"""
@__data_0='["ALFKISomeConstant","ANATRSomeConstant"]' (Size = 4000)

SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
FROM [Customers] AS [c]
WHERE [c].[CustomerID] + N'SomeConstant' IN (
SELECT [d].[value]
FROM OPENJSON(@__data_0) WITH ([value] nvarchar(max) '$') AS [d]
)
""");
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

Expand Down

0 comments on commit 0de1263

Please sign in to comment.