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 #32325, see also #32333
  • Loading branch information
roji authored and ajcvickers committed Dec 4, 2023
1 parent 5533b74 commit b2763b5
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 ApplyTypeMappingOnSqlBinary(
}

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 @@ -5674,5 +5674,27 @@ await AssertQuery(
.Select(g => new { g.Key, MaxTimestamp = g.Select(e => e.Order.OrderDate).Max() })
.OrderBy(x => x.MaxTimestamp)
.Select(x => x));
}

[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 @@ -7316,6 +7316,40 @@ FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i0]
) AND [o].[Quantity] = [o2].[Quantity])
""");
#endif
}

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)
Expand Down

0 comments on commit b2763b5

Please sign in to comment.