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

SqlServer: Translate ArrayAccess/First over byte[] #23210

Merged
1 commit merged into from
Dec 11, 2020
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 @@ -72,6 +72,25 @@ public SqlServerByteArrayMethodTranslator([NotNull] ISqlExpressionFactory sqlExp
_sqlExpressionFactory.Constant(0));
}

if (method.IsGenericMethod
&& method.GetGenericMethodDefinition().Equals(EnumerableMethods.FirstWithoutPredicate)
&& arguments[0].Type == typeof(byte[]))
{
return _sqlExpressionFactory.Convert(
_sqlExpressionFactory.Function(
"SUBSTRING",
new SqlExpression[]
{
arguments[0],
_sqlExpressionFactory.Constant(1),
_sqlExpressionFactory.Constant(1)
},
nullable: true,
argumentsPropagateNullability: new[] { true, true, true },
typeof(byte[])),
method.ReturnType);
}

return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
{
Check.NotNull(binaryExpression, nameof(binaryExpression));

if (binaryExpression.NodeType == ExpressionType.ArrayIndex
&& binaryExpression.Left.Type == typeof(byte[]))
{
var left = Visit(binaryExpression.Left);
var right = Visit(binaryExpression.Right);

if (left is SqlExpression leftSql
&& right is SqlExpression rightSql)
{
return Dependencies.SqlExpressionFactory.Convert(
Dependencies.SqlExpressionFactory.Function(
"SUBSTRING",
new SqlExpression[]
{
leftSql,
Dependencies.SqlExpressionFactory.Add(
Dependencies.SqlExpressionFactory.ApplyDefaultTypeMapping(rightSql),
Dependencies.SqlExpressionFactory.Constant(1)),
Dependencies.SqlExpressionFactory.Constant(1)
},
nullable: true,
argumentsPropagateNullability: new[] { true, true, true },
typeof(byte[])),
binaryExpression.Type);
}
}

return !(base.VisitBinary(binaryExpression) is SqlExpression visitedExpression)
? QueryCompilationContext.NotTranslatedExpression
: visitedExpression is SqlBinaryExpression sqlBinary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;

#nullable enable
Expand All @@ -23,16 +24,20 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal
public class SqliteByteArrayMethodTranslator : IMethodCallTranslator
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalTypeMappingSource _typeMappingSource;

/// <summary>
/// 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.
/// </summary>
public SqliteByteArrayMethodTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
public SqliteByteArrayMethodTranslator(
[NotNull] ISqlExpressionFactory sqlExpressionFactory,
[NotNull] IRelationalTypeMappingSource typeMappingSource)
{
_sqlExpressionFactory = sqlExpressionFactory;
_typeMappingSource = typeMappingSource;
}

/// <summary>
Expand Down Expand Up @@ -76,6 +81,32 @@ public SqliteByteArrayMethodTranslator([NotNull] ISqlExpressionFactory sqlExpres
_sqlExpressionFactory.Constant(0));
}

// See issue#16428
//if (method.IsGenericMethod
// && method.GetGenericMethodDefinition().Equals(EnumerableMethods.FirstWithoutPredicate)
// && arguments[0].Type == typeof(byte[]))
//{
// return _sqlExpressionFactory.Function(
// "unicode",
// new SqlExpression[]
// {
// _sqlExpressionFactory.Function(
// "substr",
// new SqlExpression[]
// {
// arguments[0],
// _sqlExpressionFactory.Constant(1),
// _sqlExpressionFactory.Constant(1)
// },
// nullable: true,
// argumentsPropagateNullability: new[] { true, true, true },
// typeof(byte[]))
// },
// nullable: true,
// argumentsPropagateNullability: new[] { true },
// method.ReturnType);
//}

return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public SqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslat
AddTranslators(
new IMethodCallTranslator[]
{
new SqliteByteArrayMethodTranslator(sqlExpressionFactory),
new SqliteByteArrayMethodTranslator(sqlExpressionFactory, dependencies.RelationalTypeMappingSource),
new SqliteCharMethodTranslator(sqlExpressionFactory),
new SqliteDateTimeAddTranslator(sqlExpressionFactory),
new SqliteGlobMethodTranslator(sqlExpressionFactory),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,40 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
{
Check.NotNull(binaryExpression, nameof(binaryExpression));

// See issue#16428
//if (binaryExpression.NodeType == ExpressionType.ArrayIndex
// && binaryExpression.Left.Type == typeof(byte[]))
//{
// var left = Visit(binaryExpression.Left);
// var right = Visit(binaryExpression.Right);

// if (left is SqlExpression leftSql
// && right is SqlExpression rightSql)
// {
// return Dependencies.SqlExpressionFactory.Function(
// "unicode",
// new SqlExpression[]
// {
// Dependencies.SqlExpressionFactory.Function(
// "substr",
// new SqlExpression[]
// {
// leftSql,
// Dependencies.SqlExpressionFactory.Add(
// Dependencies.SqlExpressionFactory.ApplyDefaultTypeMapping(rightSql),
// Dependencies.SqlExpressionFactory.Constant(1)),
// Dependencies.SqlExpressionFactory.Constant(1)
// },
// nullable: true,
// argumentsPropagateNullability: new[] { true, true, true },
// typeof(byte[]))
// },
// nullable: true,
// argumentsPropagateNullability: new[] { true },
// binaryExpression.Type);
// }
//}

if (!(base.VisitBinary(binaryExpression) is SqlExpression visitedExpression))
{
return QueryCompilationContext.NotTranslatedExpression;
Expand Down
76 changes: 52 additions & 24 deletions test/EFCore.Specification.Tests/MonsterFixupTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -519,11 +519,17 @@ public virtual void Composite_fixup_happens_when_FKs_change_test()
var productReview2 = context.ProductReviews.Single(e => e.Review.StartsWith("Good"));
var productReview3 = context.ProductReviews.Single(e => e.Review.StartsWith("Eeky"));

// Issue #16428
var productPhotos = context.ProductPhotos.ToList();
var productPhoto1 = productPhotos.Single(e => e.Photo[0] == 101);
var productPhoto2 = productPhotos.Single(e => e.Photo[0] == 103);
var productPhoto3 = productPhotos.Single(e => e.Photo[0] == 105);
// See issue#16428
var sqlite = context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite";
var productPhoto1 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 101)
: context.ProductPhotos.Single(e => e.Photo[0] == 101);
var productPhoto2 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 103)
: context.ProductPhotos.Single(e => e.Photo[0] == 103);
var productPhoto3 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 105)
: context.ProductPhotos.Single(e => e.Photo[0] == 105);

var productWebFeature1 = context.ProductWebFeatures.Single(e => e.Heading.StartsWith("Waffle"));
var productWebFeature2 = context.ProductWebFeatures.Single(e => e.Heading.StartsWith("What"));
Expand Down Expand Up @@ -834,10 +840,19 @@ protected void SimpleVerification()
new[] { "Better than Tarqies!", "Eeky says yes!", "Good with maple syrup." },
context.ProductReviews.Select(c => c.Review).OrderBy(n => n));

// Issue #16428
Assert.Equal(
new[] { "101", "103", "105" },
context.ProductPhotos.ToList().Select(c => c.Photo.First().ToString()).OrderBy(n => n));
// See issue#16428
if (context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite")
{
Assert.Equal(
new[] { "101", "103", "105" },
context.ProductPhotos.ToList().Select(c => c.Photo.First().ToString()).OrderBy(n => n));
}
else
{
Assert.Equal(
new[] { "101", "103", "105" },
context.ProductPhotos.Select(c => c.Photo.First().ToString()).OrderBy(n => n));
}

Assert.Equal(
new[] { "Waffle Style", "What does the waffle say?" },
Expand All @@ -847,7 +862,6 @@ protected void SimpleVerification()
new[] { "Ants By Boris", "Trading As Trent" },
context.Suppliers.Select(c => c.Name).OrderBy(n => n));

// Issue #16428
Assert.Equal(
new[] { "201", "202" },
context.SupplierLogos.ToList().SelectMany(c => c.Logo).Select(l => l.ToString()).OrderBy(n => n));
Expand Down Expand Up @@ -1027,11 +1041,17 @@ protected void FkVerification()
Assert.Equal(product1.ProductId, productReview2.ProductId);
Assert.Equal(product2.ProductId, productReview3.ProductId);

// Issue #16428
var productPhotos = context.ProductPhotos.ToList();
var productPhoto1 = productPhotos.Single(e => e.Photo[0] == 101);
var productPhoto2 = productPhotos.Single(e => e.Photo[0] == 103);
var productPhoto3 = productPhotos.Single(e => e.Photo[0] == 105);
// See issue#16428
var sqlite = context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite";
var productPhoto1 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 101)
: context.ProductPhotos.Single(e => e.Photo[0] == 101);
var productPhoto2 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 103)
: context.ProductPhotos.Single(e => e.Photo[0] == 103);
var productPhoto3 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 105)
: context.ProductPhotos.Single(e => e.Photo[0] == 105);

Assert.Equal(product1.ProductId, productPhoto1.ProductId);
Assert.Equal(product1.ProductId, productPhoto2.ProductId);
Expand All @@ -1050,8 +1070,9 @@ protected void FkVerification()
var supplier1 = context.Suppliers.Single(e => e.Name.StartsWith("Trading"));
var supplier2 = context.Suppliers.Single(e => e.Name.StartsWith("Ants"));

// Issue #16428
var supplierLogo1 = context.SupplierLogos.ToList().Single(e => e.Logo[0] == 201);
var supplierLogo1 = sqlite
? context.SupplierLogos.ToList().Single(e => e.Logo[0] == 201)
: context.SupplierLogos.Single(e => e.Logo[0] == 201);

Assert.Equal(supplier1.SupplierId, supplierLogo1.SupplierId);

Expand Down Expand Up @@ -1293,11 +1314,17 @@ protected void NavigationVerification()

Assert.True(product3.Reviews == null || product3.Reviews.Count == 0);

// Issue #16428
var productPhotos = context.ProductPhotos.ToList();
var productPhoto1 = productPhotos.Single(e => e.Photo[0] == 101);
var productPhoto2 = productPhotos.Single(e => e.Photo[0] == 103);
var productPhoto3 = productPhotos.Single(e => e.Photo[0] == 105);
// See issue#16428
var sqlite = context.Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite";
var productPhoto1 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 101)
: context.ProductPhotos.Single(e => e.Photo[0] == 101);
var productPhoto2 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 103)
: context.ProductPhotos.Single(e => e.Photo[0] == 103);
var productPhoto3 = sqlite
? context.ProductPhotos.ToList().Single(e => e.Photo[0] == 105)
: context.ProductPhotos.Single(e => e.Photo[0] == 105);

Assert.Equal(
new[] { productPhoto1, productPhoto2 },
Expand Down Expand Up @@ -1327,8 +1354,9 @@ protected void NavigationVerification()
var supplier1 = context.Suppliers.Single(e => e.Name.StartsWith("Trading"));
var supplier2 = context.Suppliers.Single(e => e.Name.StartsWith("Ants"));

// Issue #16428
var supplierLogo1 = context.SupplierLogos.ToList().Single(e => e.Logo[0] == 201);
var supplierLogo1 = sqlite
? context.SupplierLogos.ToList().Single(e => e.Logo[0] == 201)
: context.SupplierLogos.Single(e => e.Logo[0] == 201);

Assert.Same(supplierLogo1, supplier1.Logo);

Expand Down
30 changes: 24 additions & 6 deletions test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1256,8 +1256,8 @@ public virtual Task Where_coalesce_with_anonymous_types(bool async)
return AssertQuery(
async,
ss => from g in ss.Set<Gear>()
where (new { Name = g.LeaderNickname } ?? new { Name = g.FullName }) != null
select g.Nickname);
where (new { Name = g.LeaderNickname } ?? new { Name = g.FullName }) != null
select g.Nickname);
}

[ConditionalTheory(Skip = "issue #8421")]
Expand Down Expand Up @@ -3284,7 +3284,7 @@ public virtual Task Comparing_two_collection_navigations_composite_key(bool asyn
async,
ss => from g1 in ss.Set<Gear>()
from g2 in ss.Set<Gear>()
// ReSharper disable once PossibleUnintendedReferenceComparison
// ReSharper disable once PossibleUnintendedReferenceComparison
where g1.Weapons == g2.Weapons
orderby g1.Nickname
select new { Nickname1 = g1.Nickname, Nickname2 = g2.Nickname },
Expand Down Expand Up @@ -6000,9 +6000,9 @@ public virtual Task GetValueOrDefault_on_DateTimeOffset(bool async)
{
var defaultValue = default(DateTimeOffset);

return AssertQuery(
async,
ss => ss.Set<Mission>().Where(m => ((DateTimeOffset?)m.Timeline).GetValueOrDefault() == defaultValue));
return AssertQuery(
async,
ss => ss.Set<Mission>().Where(m => ((DateTimeOffset?)m.Timeline).GetValueOrDefault() == defaultValue));
}

[ConditionalTheory]
Expand Down Expand Up @@ -7936,6 +7936,24 @@ where g.Tag.IssueDate > invalidTagIssueDate
select new { g.Nickname, invalidTagIssueDate });
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task First_on_byte_array(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Squad>().Where(e => e.Banner.First() == 0x02));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Array_access_on_byte_array(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Squad>().Where(e => e.Banner5[2] == 0x06));
}

protected GearsOfWarContext CreateContext()
=> Fixture.CreateContext();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7344,6 +7344,26 @@ FROM [Tags] AS [t1]
ORDER BY [t1].[Id]), '0001-01-01T00:00:00.0000000')");
}

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

AssertSql(
@"SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name]
FROM [Squads] AS [s]
WHERE CAST(SUBSTRING([s].[Banner], 1, 1) AS tinyint) = CAST(2 AS tinyint)");
}

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

AssertSql(
@"SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name]
FROM [Squads] AS [s]
WHERE CAST(SUBSTRING([s].[Banner5], 2 + 1, 1) AS tinyint) = CAST(6 AS tinyint)");
}

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