Skip to content

Commit

Permalink
Query: Add EF.Functions.Random (#23145)
Browse files Browse the repository at this point in the history
Resolves #16141
  • Loading branch information
RaymondHuy authored Dec 22, 2020
1 parent e5425e5 commit 03c1ba5
Show file tree
Hide file tree
Showing 15 changed files with 368 additions and 8 deletions.
2 changes: 1 addition & 1 deletion EFCore.Runtime.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
"src\\EFCore\\EFCore.csproj",
"src\\Microsoft.Data.Sqlite.Core\\Microsoft.Data.Sqlite.Core.csproj",
"test\\EFCore.Analyzers.Tests\\EFCore.Analyzers.Tests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj",
"test\\EFCore.AspNet.Specification.Tests\\EFCore.AspNet.Specification.Tests.csproj",
"test\\EFCore.AspNet.SqlServer.FunctionalTests\\EFCore.AspNet.SqlServer.FunctionalTests.csproj",
"test\\EFCore.AspNet.Sqlite.FunctionalTests\\EFCore.AspNet.Sqlite.FunctionalTests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj",
"test\\EFCore.Cosmos.FunctionalTests\\EFCore.Cosmos.FunctionalTests.csproj",
"test\\EFCore.Cosmos.Tests\\EFCore.Cosmos.Tests.csproj",
"test\\EFCore.CrossStore.FunctionalTests\\EFCore.CrossStore.FunctionalTests.csproj",
Expand Down
3 changes: 3 additions & 0 deletions EFCore.Sqlite.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"src\\EFCore\\EFCore.csproj",
"src\\Microsoft.Data.Sqlite.Core\\Microsoft.Data.Sqlite.Core.csproj",
"test\\EFCore.Analyzers.Tests\\EFCore.Analyzers.Tests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj",
"test\\EFCore.AspNet.Specification.Tests\\EFCore.AspNet.Specification.Tests.csproj",
"test\\EFCore.AspNet.Sqlite.FunctionalTests\\EFCore.AspNet.Sqlite.FunctionalTests.csproj",
"test\\EFCore.Design.Tests\\EFCore.Design.Tests.csproj",
"test\\EFCore.InMemory.FunctionalTests\\EFCore.InMemory.FunctionalTests.csproj",
"test\\EFCore.InMemory.Tests\\EFCore.InMemory.Tests.csproj",
Expand Down
2 changes: 1 addition & 1 deletion EFCore.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"src\\dotnet-ef\\dotnet-ef.csproj",
"src\\ef\\ef.csproj",
"test\\EFCore.Analyzers.Tests\\EFCore.Analyzers.Tests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj",
"test\\EFCore.AspNet.Specification.Tests\\EFCore.AspNet.Specification.Tests.csproj",
"test\\EFCore.AspNet.SqlServer.FunctionalTests\\EFCore.AspNet.SqlServer.FunctionalTests.csproj",
"test\\EFCore.AspNet.Sqlite.FunctionalTests\\EFCore.AspNet.Sqlite.FunctionalTests.csproj",
"test\\EFCore.AspNet.InMemory.FunctionalTests\\EFCore.AspNet.InMemory.FunctionalTests.csproj"
"test\\EFCore.Cosmos.FunctionalTests\\EFCore.Cosmos.FunctionalTests.csproj",
"test\\EFCore.Cosmos.Tests\\EFCore.Cosmos.Tests.csproj",
"test\\EFCore.CrossStore.FunctionalTests\\EFCore.CrossStore.FunctionalTests.csproj",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public CosmosMethodCallTranslatorProvider(
{
new EqualsTranslator(sqlExpressionFactory),
new StringMethodTranslator(sqlExpressionFactory),
new ContainsTranslator(sqlExpressionFactory)
new ContainsTranslator(sqlExpressionFactory),
new RandomTranslator(sqlExpressionFactory)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
//new GetValueOrDefaultTranslator(sqlExpressionFactory),
Expand Down
59 changes: 59 additions & 0 deletions src/EFCore.Cosmos/Query/Internal/RandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal
{
/// <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 class RandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <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 RandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <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 virtual SqlExpression Translate(
SqlExpression instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"RAND",
Array.Empty<SqlExpression>(),
method.ReturnType)
: null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public class InMemoryExpressionTranslatingExpressionVisitor : ExpressionVisitor
private static readonly MethodInfo _likeMethodInfoWithEscape = typeof(DbFunctionsExtensions).GetRequiredRuntimeMethod(
nameof(DbFunctionsExtensions.Like), new[] { typeof(DbFunctions), typeof(string), typeof(string), typeof(string) });

private static readonly MethodInfo _randomMethodInfo = typeof(DbFunctionsExtensions).GetRequiredRuntimeMethod(
nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });

private static readonly MethodInfo _randomNextDoubleMethodInfo = typeof(Random).GetRequiredRuntimeMethod(
nameof(Random.NextDouble), Array.Empty<Type>());

private static readonly MethodInfo _inMemoryLikeMethodInfo =
typeof(InMemoryExpressionTranslatingExpressionVisitor).GetRequiredDeclaredMethod(nameof(InMemoryLike));

Expand Down Expand Up @@ -750,6 +756,11 @@ static Expression RemapLambda(GroupingElementExpression groupingElement, LambdaE
return Expression.Call(_inMemoryLikeMethodInfo, visitedArguments);
}

if (methodCallExpression.Method == _randomMethodInfo)
{
return Expression.Call(Expression.New(typeof(Random)), _randomNextDoubleMethodInfo);
}

Expression? @object = null;
Expression[] arguments;
var method = methodCallExpression.Method;
Expand Down
64 changes: 64 additions & 0 deletions src/EFCore.Relational/Query/Internal/RandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

#nullable enable

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
/// <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 class RandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRequiredRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <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 RandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <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 virtual SqlExpression? Translate(
SqlExpression? instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"RAND",
Array.Empty<SqlExpression>(),
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType)
: null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ public RelationalMethodCallTranslatorProvider([NotNull] RelationalMethodCallTran
new EnumHasFlagTranslator(sqlExpressionFactory),
new GetValueOrDefaultTranslator(sqlExpressionFactory),
new ComparisonTranslator(sqlExpressionFactory),
new ByteArraySequenceEqualTranslator(sqlExpressionFactory)
new ByteArraySequenceEqualTranslator(sqlExpressionFactory),
new RandomTranslator(sqlExpressionFactory)
});
_sqlExpressionFactory = sqlExpressionFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public SqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslat
new SqliteHexMethodTranslator(sqlExpressionFactory),
new SqliteMathTranslator(sqlExpressionFactory),
new SqliteObjectToStringTranslator(sqlExpressionFactory),
new SqliteRandomTranslator(sqlExpressionFactory),
new SqliteRegexMethodTranslator(sqlExpressionFactory),
new SqliteStringMethodTranslator(sqlExpressionFactory),
new SqliteSubstrMethodTranslator(sqlExpressionFactory)
Expand Down
74 changes: 74 additions & 0 deletions src/EFCore.Sqlite.Core/Query/Internal/SqliteRandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal
{
/// <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 class SqliteRandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <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 SqliteRandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <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 virtual SqlExpression Translate(
SqlExpression instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

// Issue #15586: Query: TypeCompatibility chart for inference.
return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"abs",
new SqlExpression[]
{
_sqlExpressionFactory.Divide(
_sqlExpressionFactory.Function(
"random",
Array.Empty<SqlExpression>(),
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType),
_sqlExpressionFactory.Constant(9223372036854780000.0))
},
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType)
: null;
}
}
}
16 changes: 12 additions & 4 deletions src/EFCore/DbFunctionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static bool Like(
[CanBeNull] this DbFunctions _,
[CanBeNull] string matchExpression,
[CanBeNull] string pattern)
=> LikeCore(matchExpression, pattern, escapeCharacter: null);
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like)));

/// <summary>
/// <para>
Expand All @@ -61,9 +61,17 @@ public static bool Like(
[CanBeNull] string matchExpression,
[CanBeNull] string pattern,
[CanBeNull] string escapeCharacter)
=> LikeCore(matchExpression, pattern, escapeCharacter);

private static bool LikeCore(string matchExpression, string pattern, string escapeCharacter)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like)));

/// <summary>
/// <para>
/// A random double number generator which generates a number between 0 and 1, exclusive.
/// This is usually directly translated to server.
/// </para>
/// </summary>
/// <param name="_"> The DbFunctions instance. </param>
/// <returns> A random double number between 0 and 1, exclusive. </returns>
public static double Random([CanBeNull] this DbFunctions _)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Random)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.EntityFrameworkCore.Query
{
public class NorthwindDbFunctionsQueryCosmosTest : NorthwindDbFunctionsQueryTestBase<NorthwindQueryCosmosFixture<NoopModelCustomizer>>
{
public NorthwindDbFunctionsQueryCosmosTest(
NorthwindQueryCosmosFixture<NoopModelCustomizer> fixture,
ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
}

public override Task Like_all_literals(bool async)
{
return AssertTranslationFailed(() => base.Like_all_literals(async));
}

public override Task Like_all_literals_with_escape(bool async)
{
return AssertTranslationFailed(() => base.Like_all_literals_with_escape(async));
}

public override Task Like_literal(bool async)
{
return AssertTranslationFailed(() => base.Like_literal(async));
}

public override Task Like_literal_with_escape(bool async)
{
return AssertTranslationFailed(() => base.Like_literal_with_escape(async));
}

public override Task Like_identity(bool async)
{
return AssertTranslationFailed(() => base.Like_identity(async));
}

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

AssertSql(
@"SELECT COUNT(1) AS c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (RAND() < 1.0))");
}

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

AssertSql(
@"SELECT COUNT(1) AS c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (RAND() >= 0.0))");
}

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

protected void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
}
}
Loading

0 comments on commit 03c1ba5

Please sign in to comment.